Fix stats defined in tests being overwritteng by stat change (#8018)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
FosterProgramming 2025-10-23 18:59:15 +02:00 committed by GitHub
parent 1343ddf7c3
commit 5b4403ddfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 154 additions and 41 deletions

View File

@ -789,6 +789,7 @@ u32 GetSpeciesBaseDefense(u16 species);
u32 GetSpeciesBaseSpAttack(u16 species);
u32 GetSpeciesBaseSpDefense(u16 species);
u32 GetSpeciesBaseSpeed(u16 species);
u32 GetSpeciesBaseStat(u16 species, u32 statIndex);
const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species);
const u16 *GetSpeciesTeachableLearnset(u16 species);
const u16 *GetSpeciesEggMoves(u16 species);

View File

@ -1691,32 +1691,10 @@ static u16 CalculateBoxMonChecksumReencrypt(struct BoxPokemon *boxMon)
return checksum;
}
#define CALC_STAT(base, iv, ev, statIndex, field) \
{ \
u8 baseStat = gSpeciesInfo[species].base; \
s32 n = (((2 * baseStat + iv + ev / 4) * level) / 100) + 5; \
n = ModifyStatByNature(nature, n, statIndex); \
if (B_FRIENDSHIP_BOOST == TRUE) \
n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100));\
SetMonData(mon, field, &n); \
}
void CalculateMonStats(struct Pokemon *mon)
{
s32 oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP, NULL);
s32 currentHP = GetMonData(mon, MON_DATA_HP, NULL);
s32 hpIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_HP_IV, NULL);
s32 hpEV = GetMonData(mon, MON_DATA_HP_EV, NULL);
s32 attackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_ATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_ATK_IV, NULL);
s32 attackEV = GetMonData(mon, MON_DATA_ATK_EV, NULL);
s32 defenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_DEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_DEF_IV, NULL);
s32 defenseEV = GetMonData(mon, MON_DATA_DEF_EV, NULL);
s32 speedIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPEED) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPEED_IV, NULL);
s32 speedEV = GetMonData(mon, MON_DATA_SPEED_EV, NULL);
s32 spAttackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPATK_IV, NULL);
s32 spAttackEV = GetMonData(mon, MON_DATA_SPATK_EV, NULL);
s32 spDefenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPDEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPDEF_IV, NULL);
s32 spDefenseEV = GetMonData(mon, MON_DATA_SPDEF_EV, NULL);
u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL);
u8 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, NULL);
s32 level = GetLevelFromMonExp(mon);
@ -1726,28 +1704,55 @@ void CalculateMonStats(struct Pokemon *mon)
SetMonData(mon, MON_DATA_LEVEL, &level);
bool32 hyperTrained[NUM_STATS]; //In a battle test, hyper training flag indicates a fixed stat
s32 iv[NUM_STATS];
s32 ev[NUM_STATS];
for (u32 i = 0; i < NUM_STATS; i++)
{
hyperTrained[i] = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP + i);
iv[i] = GetMonData(mon, MON_DATA_HP_IV + i);
ev[i] = GetMonData(mon, MON_DATA_HP_EV + i);
if (hyperTrained[i])
{
#if TESTING
if (gMain.inBattle)
continue;
#endif
iv[i] = MAX_PER_STAT_IVS;
}
if (i == STAT_HP)
continue;
u8 baseStat = GetSpeciesBaseStat(species, i);
s32 n = (((2 * baseStat + iv[i] + ev[i] / 4) * level) / 100) + 5;
n = ModifyStatByNature(nature, n, i);
if (B_FRIENDSHIP_BOOST == TRUE)
n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100));
SetMonData(mon, MON_DATA_MAX_HP + i, &n);
}
#if TESTING
if (hyperTrained[STAT_HP] && gMain.inBattle)
return;
#endif
if (species == SPECIES_SHEDINJA)
{
newMaxHP = 1;
}
else
{
s32 n = 2 * GetSpeciesBaseHP(species) + hpIV;
newMaxHP = (((n + hpEV / 4) * level) / 100) + level + 10;
s32 n = 2 * GetSpeciesBaseHP(species) + iv[STAT_HP];
newMaxHP = (((n + ev[STAT_HP] / 4) * level) / 100) + level + 10;
}
gBattleScripting.levelUpHP = newMaxHP - oldMaxHP;
if (gBattleScripting.levelUpHP == 0)
gBattleScripting.levelUpHP = 1;
SetMonData(mon, MON_DATA_MAX_HP, &newMaxHP);
CALC_STAT(baseAttack, attackIV, attackEV, STAT_ATK, MON_DATA_ATK)
CALC_STAT(baseDefense, defenseIV, defenseEV, STAT_DEF, MON_DATA_DEF)
CALC_STAT(baseSpeed, speedIV, speedEV, STAT_SPEED, MON_DATA_SPEED)
CALC_STAT(baseSpAttack, spAttackIV, spAttackEV, STAT_SPATK, MON_DATA_SPATK)
CALC_STAT(baseSpDefense, spDefenseIV, spDefenseEV, STAT_SPDEF, MON_DATA_SPDEF)
// Since a pokemon's maxHP data could either not have
// been initialized at this point or this pokemon is
// just fainted, the check for oldMaxHP is important.
@ -3567,6 +3572,26 @@ u32 GetSpeciesBaseSpeed(u16 species)
return gSpeciesInfo[SanitizeSpeciesId(species)].baseSpeed;
}
u32 GetSpeciesBaseStat(u16 species, u32 statIndex)
{
switch (statIndex)
{
case STAT_HP:
return GetSpeciesBaseHP(species);
case STAT_ATK:
return GetSpeciesBaseAttack(species);
case STAT_DEF:
return GetSpeciesBaseDefense(species);
case STAT_SPEED:
return GetSpeciesBaseSpeed(species);
case STAT_SPATK:
return GetSpeciesBaseSpAttack(species);
case STAT_SPDEF:
return GetSpeciesBaseSpDefense(species);
}
return 0;
}
const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species)
{
const struct LevelUpMove *learnset = gSpeciesInfo[SanitizeSpeciesId(species)].levelUpLearnset;

View File

@ -75,10 +75,10 @@ SINGLE_BATTLE_TEST("Mega Evolution doesn't affect turn order (Gen6)")
{
GIVEN {
WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_6);
PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(106); }
PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); }
OPPONENT(SPECIES_WOBBUFFET) {}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); }
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); }
} SCENE {
MESSAGE("The opposing Wobbuffet used Celebrate!");
MESSAGE("Gardevoir used Celebrate!");
@ -91,10 +91,10 @@ SINGLE_BATTLE_TEST("Mega Evolution affects turn order (Gen7+)")
{
GIVEN {
WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7);
PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(106); }
PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE);}
OPPONENT(SPECIES_WOBBUFFET) {}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); }
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); }
} SCENE {
MESSAGE("Gardevoir used Celebrate!");
MESSAGE("The opposing Wobbuffet used Celebrate!");
@ -117,7 +117,7 @@ SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn orde
MESSAGE("Sableye used Celebrate!");
MESSAGE("The opposing Wobbuffet used Celebrate!");
} THEN {
ASSUME(player->speed == 45);
ASSUME(player->speed == 105);
}
}

View File

@ -59,10 +59,10 @@ SINGLE_BATTLE_TEST("Ultra Burst affects turn order")
{
GIVEN {
WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(106); }
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z);}
OPPONENT(SPECIES_WOBBUFFET) {}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); }
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); }
} SCENE {
MESSAGE("Necrozma used Celebrate!");
MESSAGE("The opposing Wobbuffet used Celebrate!");

View File

@ -25,3 +25,58 @@ SINGLE_BATTLE_TEST("Forced abilities activate on switch-in")
MESSAGE("Kadabra's Sp. Atk was heightened!");
}
}
SINGLE_BATTLE_TEST("Setting level doesn't overwrite set stats")
{
u32 level = 0;
PARAMETRIZE{level = 1;}
PARAMETRIZE{level = 10;}
PARAMETRIZE{level = 50;}
PARAMETRIZE{level = 99;}
GIVEN {
PLAYER(SPECIES_WOBBUFFET) {HP(5); MaxHP(10); Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Level(level); };
OPPONENT(SPECIES_WOBBUFFET) {Speed(1);}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE);}
} THEN {
EXPECT_EQ(player->hp, 5);
EXPECT_EQ(player->maxHP, 10);
EXPECT_EQ(player->attack, 10);
EXPECT_EQ(player->defense, 10);
EXPECT_EQ(player->speed, 10);
EXPECT_EQ(player->spAttack, 10);
EXPECT_EQ(player->spDefense, 10);
}
}
SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (not HP)")
{
GIVEN {
PLAYER(SPECIES_DIANCIE) {Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Item(ITEM_DIANCITE);}
OPPONENT(SPECIES_WOBBUFFET) {Speed(1);}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); }
} SCENE {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player);
} THEN {
EXPECT_EQ(player->attack, 10);
EXPECT_EQ(player->defense, 10);
EXPECT_EQ(player->speed, 10);
EXPECT_EQ(player->spAttack, 10);
EXPECT_EQ(player->spDefense, 10);
}
}
SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (HP)")
{
GIVEN {
PLAYER(SPECIES_TERAPAGOS) {HP(5); MaxHP(10); TeraType(TYPE_STELLAR);}
OPPONENT(SPECIES_WOBBUFFET) {}
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE);}
} THEN {
EXPECT_EQ(player->hp, 5);
EXPECT_EQ(player->maxHP, 10);
}
}

View File

@ -477,6 +477,24 @@ TEST("Optimised SetMonData")
EXPECT_FASTER(optimised, vanilla);
}
//Sanity check for a CalculateMonStats refactor (could be deleted or improved)
TEST("CalculateMonStats")
{
ZeroPlayerPartyMons();
RUN_OVERWORLD_SCRIPT(
givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, shinyMode=SHINY_MODE_ALWAYS, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7;
);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_MAX_HP), 497);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_ATK), 71);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_DEF), 143);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPEED), 82);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPATK), 83);
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPDEF), 134);
}
TEST("BoxPokemon encryption works")
{
u32 raw[20] =

View File

@ -1681,7 +1681,9 @@ void Level_(u32 sourceLine, u32 level)
INVALID_IF(level == 0 || level > MAX_LEVEL, "Illegal level: %d", level);
SetMonData(DATA.currentMon, MON_DATA_LEVEL, &level);
SetMonData(DATA.currentMon, MON_DATA_EXP, &gExperienceTables[gSpeciesInfo[species].growthRate][level]);
gMain.inBattle = TRUE;
CalculateMonStats(DATA.currentMon);
gMain.inBattle = FALSE;
}
void MaxHP_(u32 sourceLine, u32 maxHP)
@ -1689,6 +1691,8 @@ void MaxHP_(u32 sourceLine, u32 maxHP)
INVALID_IF(!DATA.currentMon, "MaxHP outside of PLAYER/OPPONENT");
INVALID_IF(maxHP == 0, "Illegal max HP: %d", maxHP);
SetMonData(DATA.currentMon, MON_DATA_MAX_HP, &maxHP);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_HP, &hyperTrainingFlag);
}
void HP_(u32 sourceLine, u32 hp)
@ -1704,6 +1708,8 @@ void Attack_(u32 sourceLine, u32 attack)
INVALID_IF(!DATA.currentMon, "Attack outside of PLAYER/OPPONENT");
INVALID_IF(attack == 0, "Illegal attack: %d", attack);
SetMonData(DATA.currentMon, MON_DATA_ATK, &attack);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_ATK, &hyperTrainingFlag);
}
void Defense_(u32 sourceLine, u32 defense)
@ -1711,6 +1717,8 @@ void Defense_(u32 sourceLine, u32 defense)
INVALID_IF(!DATA.currentMon, "Defense outside of PLAYER/OPPONENT");
INVALID_IF(defense == 0, "Illegal defense: %d", defense);
SetMonData(DATA.currentMon, MON_DATA_DEF, &defense);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_DEF, &hyperTrainingFlag);
}
void SpAttack_(u32 sourceLine, u32 spAttack)
@ -1718,6 +1726,8 @@ void SpAttack_(u32 sourceLine, u32 spAttack)
INVALID_IF(!DATA.currentMon, "SpAttack outside of PLAYER/OPPONENT");
INVALID_IF(spAttack == 0, "Illegal special attack: %d", spAttack);
SetMonData(DATA.currentMon, MON_DATA_SPATK, &spAttack);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPATK, &hyperTrainingFlag);
}
void SpDefense_(u32 sourceLine, u32 spDefense)
@ -1725,6 +1735,8 @@ void SpDefense_(u32 sourceLine, u32 spDefense)
INVALID_IF(!DATA.currentMon, "SpDefense outside of PLAYER/OPPONENT");
INVALID_IF(spDefense == 0, "Illegal special defense: %d", spDefense);
SetMonData(DATA.currentMon, MON_DATA_SPDEF, &spDefense);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPDEF, &hyperTrainingFlag);
}
void Speed_(u32 sourceLine, u32 speed)
@ -1732,6 +1744,8 @@ void Speed_(u32 sourceLine, u32 speed)
INVALID_IF(!DATA.currentMon, "Speed outside of PLAYER/OPPONENT");
INVALID_IF(speed == 0, "Illegal speed: %d", speed);
SetMonData(DATA.currentMon, MON_DATA_SPEED, &speed);
bool32 hyperTrainingFlag = TRUE;
SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPEED, &hyperTrainingFlag);
DATA.hasExplicitSpeeds = TRUE;
DATA.explicitSpeeds[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}