From fe2d7442af2004783358e2106a766d49f175cbce Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sat, 3 May 2025 10:54:49 +0200 Subject: [PATCH 01/15] Remove old slide workaround (#6754) Co-authored-by: Hedara --- data/battle_scripts_2.s | 10 ++++------ include/battle.h | 1 - include/constants/battle.h | 6 ++++++ src/battle_script_commands.c | 11 ++--------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 572d076fab..27e3706e3f 100644 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -265,13 +265,12 @@ BattleScript_ActionWallyThrow: end2 BattleScript_TrainerASlideMsgRet:: - handletrainerslidemsg BS_SCRIPTING, 0 trainerslidein BS_OPPONENT1 - handletrainerslidemsg BS_SCRIPTING, 1 + handletrainerslidemsg BS_SCRIPTING, PRINT_SLIDE_MESSAGE waitstate trainerslideout BS_OPPONENT1 waitstate - handletrainerslidemsg BS_SCRIPTING, 2 + handletrainerslidemsg BS_SCRIPTING, RESTORE_BATTLER_SLIDE_CONTROL return BattleScript_TrainerASlideMsgEnd2:: @@ -279,13 +278,12 @@ BattleScript_TrainerASlideMsgEnd2:: end2 BattleScript_TrainerBSlideMsgRet:: - handletrainerslidemsg BS_SCRIPTING, 0 trainerslidein BS_OPPONENT2 - handletrainerslidemsg BS_SCRIPTING, 1 + handletrainerslidemsg BS_SCRIPTING, PRINT_SLIDE_MESSAGE waitstate trainerslideout BS_OPPONENT2 waitstate - handletrainerslidemsg BS_SCRIPTING, 2 + handletrainerslidemsg BS_SCRIPTING, RESTORE_BATTLER_SLIDE_CONTROL return BattleScript_TrainerBSlideMsgEnd2:: diff --git a/include/battle.h b/include/battle.h index 49bd92cb1c..8874857efb 100644 --- a/include/battle.h +++ b/include/battle.h @@ -825,7 +825,6 @@ struct BattleStruct u8 usedMicleBerry; struct MessageStatus slideMessageStatus; u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT]; - u8 storeBattlerSpriteId; u16 opponentMonCanTera:6; u16 opponentMonCanDynamax:6; u16 padding:4; diff --git a/include/constants/battle.h b/include/constants/battle.h index 3bc59e8773..86226013d5 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -605,4 +605,10 @@ enum StartingStatus STARTING_STATUS_SWAMP_OPPONENT, }; +enum SlideMsgStates +{ + PRINT_SLIDE_MESSAGE, + RESTORE_BATTLER_SLIDE_CONTROL, +}; + #endif // GUARD_CONSTANTS_BATTLE_H diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c9db4dbc99..03680aa5fc 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11128,20 +11128,13 @@ static void Cmd_various(void) case VARIOUS_HANDLE_TRAINER_SLIDE_MSG: { VARIOUS_ARGS(u8 case_); - if (cmd->case_ == 0) - { - // Save sprite IDs, because trainer slide in will overwrite gBattlerSpriteIds variable. - gBattleStruct->storeBattlerSpriteId = (gBattlerSpriteIds[battler] & 0xFF) | (gBattlerSpriteIds[BATTLE_PARTNER(battler)] << 8); - } - else if (cmd->case_ == 1) + if (cmd->case_ == PRINT_SLIDE_MESSAGE) { BtlController_EmitPrintString(battler, BUFFER_A, STRINGID_TRAINERSLIDE); MarkBattlerForControllerExec(battler); } - else + else if (cmd->case_ == RESTORE_BATTLER_SLIDE_CONTROL) { - gBattlerSpriteIds[BATTLE_PARTNER(battler)] = gBattleStruct->storeBattlerSpriteId >> 8; - gBattlerSpriteIds[battler] = gBattleStruct->storeBattlerSpriteId & 0xFF; if (IsBattlerAlive(battler)) { SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species); From 904b399a8129d149fb3591bbcf57943a7f9441b7 Mon Sep 17 00:00:00 2001 From: PCG <75729017+PCG06@users.noreply.github.com> Date: Sun, 4 May 2025 15:13:57 +0530 Subject: [PATCH 02/15] Added Egg Moves for Basculin White Striped (#6769) --- src/data/pokemon/egg_moves.h | 7 +++++++ src/data/pokemon/species_info/gen_5_families.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/data/pokemon/egg_moves.h b/src/data/pokemon/egg_moves.h index 23c233de58..392d2a0549 100644 --- a/src/data/pokemon/egg_moves.h +++ b/src/data/pokemon/egg_moves.h @@ -4278,6 +4278,13 @@ static const u16 sBasculinEggMoveLearnset[] = { MOVE_HEAD_SMASH, MOVE_UNAVAILABLE, }; +#if P_HISUIAN_FORMS +static const u16 sBasculinWhiteStripedEggMoveLearnset[] = { + MOVE_ENDEAVOR, + MOVE_LAST_RESPECTS, + MOVE_UNAVAILABLE, +}; +#endif //P_HISUIAN_FORMS #endif //P_FAMILY_BASCULIN #if P_FAMILY_SANDILE diff --git a/src/data/pokemon/species_info/gen_5_families.h b/src/data/pokemon/species_info/gen_5_families.h index d55a7bf1d0..f1df023cec 100644 --- a/src/data/pokemon/species_info/gen_5_families.h +++ b/src/data/pokemon/species_info/gen_5_families.h @@ -4440,6 +4440,7 @@ const struct SpeciesInfo gSpeciesInfoGen5[] = ) .levelUpLearnset = sBasculinWhiteStripedLevelUpLearnset, .teachableLearnset = sBasculinWhiteStripedTeachableLearnset, + .eggMoveLearnset = sBasculinWhiteStripedEggMoveLearnset, .formSpeciesIdTable = sBasculinFormSpeciesIdTable, .evolutions = EVOLUTION({EVO_RECOIL_DAMAGE_MALE, 294, SPECIES_BASCULEGION_M}, {EVO_RECOIL_DAMAGE_FEMALE, 294, SPECIES_BASCULEGION_F}), From 0742741dd3668dd2e38d8bb445b4f1cf23d75f81 Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Sun, 4 May 2025 22:26:12 +0200 Subject: [PATCH 03/15] Fix Life Orb inflicting self damage when using status moves (#6767) (#6773) --- src/battle_util.c | 1 + test/battle/hold_effect/life_orb.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/battle_util.c b/src/battle_util.c index a9a976fb42..9bf82b0670 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8202,6 +8202,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) break; case HOLD_EFFECT_LIFE_ORB: if (IsBattlerAlive(gBattlerAttacker) + && !IsBattleMoveStatus(gCurrentMove) && (IsBattlerTurnDamaged(gBattlerTarget) || !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Needs the second check in case of Substitute && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 61acd60660..f27ce29d03 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -15,3 +15,19 @@ SINGLE_BATTLE_TEST("Life Orb activates if it hits a Substitute") MESSAGE("Wobbuffet was hurt by the Life Orb!"); } } + +SINGLE_BATTLE_TEST("Life Orb does not activate if using a status move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GROWL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} From 4e7b1f2a973384cf84b187b41232b63d81d27d9c Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 6 May 2025 12:07:01 +0200 Subject: [PATCH 04/15] Fixes Stomping Tantrum not boosting damage when missed due to Accuracy (#6762) --- include/battle.h | 4 ++-- src/battle_main.c | 5 ++++- src/battle_script_commands.c | 12 +++++++++--- src/battle_util.c | 6 +++--- test/battle/move_effect/stomping_tantrum.c | 12 ++++++------ 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/include/battle.h b/include/battle.h index 8874857efb..a1134d4fb1 100644 --- a/include/battle.h +++ b/include/battle.h @@ -622,7 +622,6 @@ struct BattlerState u32 multipleSwitchInBattlers:1; u32 alreadyStatusedMoveAttempt:1; // For example when using Thunder Wave on an already paralyzed Pokémon. u32 activeAbilityPopUps:1; - u32 lastMoveFailed:1; // For Stomping Tantrum u32 forcedSwitch:1; u32 storedHealingWish:1; u32 storedLunarDance:1; @@ -630,7 +629,8 @@ struct BattlerState u32 sleepClauseEffectExempt:1; // Stores whether effect should be exempt from triggering Sleep Clause (Effect Spore) u32 usedMicleBerry:1; u32 pursuitTarget:1; - u32 padding:17; + u32 stompingTantrumTimer:2; + u32 padding:16; // End of Word }; diff --git a/src/battle_main.c b/src/battle_main.c index c220b37c4b..671a31737b 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3183,7 +3183,7 @@ void SwitchInClearSetData(u32 battler) gBattleStruct->lastTakenMoveFrom[battler][1] = 0; gBattleStruct->lastTakenMoveFrom[battler][2] = 0; gBattleStruct->lastTakenMoveFrom[battler][3] = 0; - gBattleStruct->battlerState[battler].lastMoveFailed = FALSE; + gBattleStruct->battlerState[battler].stompingTantrumTimer = 0; gBattleStruct->palaceFlags &= ~(1u << battler); gBattleStruct->canPickupItem &= ~(1u << battler); @@ -3985,6 +3985,9 @@ void BattleTurnPassed(void) gBattleStruct->battlerState[i].absentBattlerFlags = (gAbsentBattlerFlags & (1u << i) ? TRUE : FALSE); gBattleStruct->monToSwitchIntoId[i] = PARTY_SIZE; gStatuses4[i] &= ~STATUS4_ELECTRIFIED; + + if (gBattleStruct->battlerState[i].stompingTantrumTimer > 0) + gBattleStruct->battlerState[i].stompingTantrumTimer--; } for (i = 0; i < NUM_BATTLE_SIDES; i++) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 03680aa5fc..60c105bdab 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1643,6 +1643,8 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u } else { + u32 numTargets = 0; + u32 numMisses = 0; u32 moveType = GetBattleMoveType(move); u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move); bool32 calcSpreadMove = IsSpreadMove(moveTarget) && !IsBattleMoveStatus(move); @@ -1657,6 +1659,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u || (gBattleStruct->noResultString[battlerDef] && gBattleStruct->noResultString[battlerDef] != DO_ACCURACY_CHECK)) continue; + numTargets++; if (JumpIfMoveAffectedByProtect(move, battlerDef, FALSE) || AccuracyCalcHelper(move, battlerDef)) continue; @@ -1672,6 +1675,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u { gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_MISSED; gBattleStruct->missStringId[battlerDef] = gBattleCommunication[MISS_TYPE] = B_MSG_MISSED; + numMisses++; if (holdEffectAtk == HOLD_EFFECT_BLUNDER_POLICY) gBattleStruct->blunderPolicy = TRUE; // Only activates from missing through acc/evasion checks @@ -1683,6 +1687,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u && !TargetFullyImmuneToCurrMove(gBattlerAttacker, BATTLE_PARTNER(battlerDef))) { // Smart target to partner if miss + numMisses = 0; // Other dart might hit gBattlerTarget = BATTLE_PARTNER(battlerDef); AccuracyCheck(TRUE, nextInstr, failInstr, move); return; @@ -1693,6 +1698,9 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u } } + if (numTargets == numMisses) + gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2; + if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED) gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED; @@ -6821,9 +6829,7 @@ static void Cmd_moveend(void) if ((gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE)) || (gBattleMons[gBattlerAttacker].status2 & (STATUS2_FLINCHED)) || gProtectStructs[gBattlerAttacker].nonVolatileStatusImmobility) - gBattleStruct->battlerState[gBattlerAttacker].lastMoveFailed = TRUE; - else - gBattleStruct->battlerState[gBattlerAttacker].lastMoveFailed = FALSE; + gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2; // Set ShellTrap to activate after the attacker's turn if target was hit by a physical move. if (GetMoveEffect(gChosenMoveByBattler[gBattlerTarget]) == EFFECT_SHELL_TRAP diff --git a/src/battle_util.c b/src/battle_util.c index 9bf82b0670..b03718f4b9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -9353,7 +9353,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageCalculationData * modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); break; case EFFECT_STOMPING_TANTRUM: - if (gBattleStruct->battlerState[battlerAtk].lastMoveFailed) + if (gBattleStruct->battlerState[battlerAtk].stompingTantrumTimer == 1) modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); break; case EFFECT_MAGNITUDE: @@ -12202,8 +12202,8 @@ void ClearDamageCalcResults(void) bool32 DoesDestinyBondFail(u32 battler) { if (B_DESTINY_BOND_FAIL >= GEN_7 - && GetMoveEffect(gLastResultingMoves[battler]) == EFFECT_DESTINY_BOND - && !gBattleStruct->battlerState[battler].lastMoveFailed) + && GetMoveEffect(gLastLandedMoves[battler]) == EFFECT_DESTINY_BOND + && GetMoveEffect(gLastResultingMoves[battler]) == EFFECT_DESTINY_BOND) return TRUE; return FALSE; } diff --git a/test/battle/move_effect/stomping_tantrum.c b/test/battle/move_effect/stomping_tantrum.c index 18d8ffb7e9..95d93e07e6 100644 --- a/test/battle/move_effect/stomping_tantrum.c +++ b/test/battle/move_effect/stomping_tantrum.c @@ -6,7 +6,7 @@ ASSUMPTIONS ASSUME(GetMoveEffect(MOVE_STOMPING_TANTRUM) == EFFECT_STOMPING_TANTRUM); } -SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user flinched on the previous turn") +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user flinched on the previous turn") { s16 damage[3]; GIVEN { @@ -36,7 +36,7 @@ SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user flinched on } } -SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user failed to attack due to paralysis") +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user failed to attack due to paralysis") { s16 damage[3]; PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); @@ -66,7 +66,7 @@ SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user failed to at } } -SINGLE_BATTLE_TEST("Stomping Tatrum will not deal double damage if target protects") +SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double damage if target protects") { s16 damage[2]; GIVEN { @@ -90,7 +90,7 @@ SINGLE_BATTLE_TEST("Stomping Tatrum will not deal double damage if target protec } } -SINGLE_BATTLE_TEST("Stomping Tatrum will not deal double if it missed") +SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double if it missed") { s16 damage[2]; GIVEN { @@ -107,11 +107,11 @@ SINGLE_BATTLE_TEST("Stomping Tatrum will not deal double if it missed") ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); HP_BAR(opponent, captureDamage: &damage[1]); } THEN { - EXPECT_EQ(damage[0], damage[1]); + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); } } -SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user was immune to previous move") +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user was immune to previous move") { s16 damage[2]; GIVEN { From 7c4fb512a1938580f841d6d311a5727def9044b5 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 7 May 2025 11:04:11 +0200 Subject: [PATCH 05/15] Fixes Symbiosis not triggering when a weakness berry was consumed (#6782) --- src/battle_script_commands.c | 25 +++++++++++++------------ test/battle/ability/symbiosis.c | 23 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 60c105bdab..bd9e19b035 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2331,7 +2331,6 @@ static inline bool32 TryActivateWeakenessBerry(u32 battlerDef) { if (gSpecialStatuses[battlerDef].berryReduced && gBattleMons[battlerDef].item != ITEM_NONE) { - gSpecialStatuses[battlerDef].berryReduced = FALSE; gBattleScripting.battler = battlerDef; gLastUsedItem = gBattleMons[battlerDef].item; gBattleStruct->ateBerry[battlerDef & BIT_SIDE] |= 1u << gBattlerPartyIndexes[battlerDef]; @@ -6187,14 +6186,16 @@ static bool32 TryKnockOffBattleScript(u32 battlerDef) return FALSE; } -#define SYMBIOSIS_CHECK(battler, ally) \ - GetBattlerAbility(ally) == ABILITY_SYMBIOSIS \ - && gBattleMons[battler].item == ITEM_NONE \ - && gBattleMons[ally].item != ITEM_NONE \ - && CanBattlerGetOrLoseItem(battler, gBattleMons[ally].item) \ - && CanBattlerGetOrLoseItem(ally, gBattleMons[ally].item) \ - && IsBattlerAlive(battler) \ - && IsBattlerAlive(ally) +static inline bool32 TryTriggerSymbiosis(u32 battler, u32 ally) +{ + return GetBattlerAbility(ally) == ABILITY_SYMBIOSIS + && gBattleMons[battler].item == ITEM_NONE + && gBattleMons[ally].item != ITEM_NONE + && CanBattlerGetOrLoseItem(battler, gBattleMons[ally].item) + && CanBattlerGetOrLoseItem(ally, gBattleMons[ally].item) + && IsBattlerAlive(battler) + && IsBattlerAlive(ally); +} static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent) { @@ -7354,7 +7355,7 @@ static void Cmd_moveend(void) { if ((gSpecialStatuses[i].berryReduced || (B_SYMBIOSIS_GEMS >= GEN_7 && gSpecialStatuses[i].gemBoost)) - && SYMBIOSIS_CHECK(i, BATTLE_PARTNER(i))) + && TryTriggerSymbiosis(i, BATTLE_PARTNER(i))) { BestowItem(BATTLE_PARTNER(i), i); gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability; @@ -9108,7 +9109,7 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId) && (B_SYMBIOSIS_GEMS < GEN_7 || !(gSpecialStatuses[battler].gemBoost)) && gCurrentMove != MOVE_FLING //Fling and damage-reducing berries are handled separately. && !gSpecialStatuses[battler].berryReduced - && SYMBIOSIS_CHECK(battler, BATTLE_PARTNER(battler))) + && TryTriggerSymbiosis(battler, BATTLE_PARTNER(battler))) { BestowItem(BATTLE_PARTNER(battler), battler); gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability; @@ -17020,7 +17021,7 @@ void BS_TrySymbiosis(void) u32 battler = GetBattlerForBattleScript(cmd->battler); //called by Bestow, Fling, and Bug Bite, which don't work with Cmd_removeitem. u32 partner = BATTLE_PARTNER(battler); - if (SYMBIOSIS_CHECK(battler, partner)) + if (TryTriggerSymbiosis(battler, partner)) { BestowItem(partner, battler); gLastUsedAbility = gBattleMons[partner].ability; diff --git a/test/battle/ability/symbiosis.c b/test/battle/ability/symbiosis.c index 5c55370fe4..932008d9d7 100644 --- a/test/battle/ability/symbiosis.c +++ b/test/battle/ability/symbiosis.c @@ -21,7 +21,7 @@ DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes an MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); // end of turn, wobb gets poisoned MESSAGE("Wobbuffet was badly poisoned!"); - STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); } THEN { EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); EXPECT_EQ(playerRight->item, ITEM_NONE); @@ -111,3 +111,24 @@ DOUBLE_BATTLE_TEST("Symbiosis triggers after partner flings its item") EXPECT_EQ(playerRight->item, ITEM_NONE); } } + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes a weakness berry") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHILAN_BERRY].holdEffect == HOLD_EFFECT_RESIST_BERRY); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHILAN_BERRY); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} From b6b6e91959504233d742c7d1b89d7b23f23b7e08 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 7 May 2025 16:50:12 +0200 Subject: [PATCH 06/15] A lot of tests (#6734) Co-authored-by: Hedara --- include/config/test.h | 2 + include/constants/flags.h | 2 +- include/constants/generational_changes.h | 1 + include/generational_changes.h | 1 + src/battle_util.c | 4 +- test/battle/ability/anticipation.c | 361 ++++++++++++++++++++++- test/battle/ability/drought.c | 82 ++++- test/battle/ability/liquid_ooze.c | 26 +- test/battle/ability/sand_force.c | 63 +++- test/battle/ability/sand_rush.c | 51 +++- test/battle/ability/sand_veil.c | 16 +- test/battle/ability/slush_rush.c | 65 +++- test/battle/ability/solar_power.c | 71 ++++- test/battle/weather/hail.c | 14 + test/battle/weather/sandstorm.c | 17 ++ 15 files changed, 738 insertions(+), 38 deletions(-) diff --git a/include/config/test.h b/include/config/test.h index a8e2f4ab11..4363a5f82a 100644 --- a/include/config/test.h +++ b/include/config/test.h @@ -1136,5 +1136,7 @@ // Flags #undef B_FLAG_SLEEP_CLAUSE #define B_FLAG_SLEEP_CLAUSE TESTING_FLAG_SLEEP_CLAUSE +#undef B_FLAG_INVERSE_BATTLE +#define B_FLAG_INVERSE_BATTLE TESTING_FLAG_INVERSE_BATTLE #endif // GUARD_CONFIG_TEST_H diff --git a/include/constants/flags.h b/include/constants/flags.h index aec9ebbd92..47e96c528f 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1663,7 +1663,7 @@ #if TESTING #define TESTING_FLAGS_START 0x5000 #define TESTING_FLAG_SLEEP_CLAUSE (TESTING_FLAGS_START + 0x0) -#define TESTING_FLAG_UNUSED_1 (TESTING_FLAGS_START + 0x1) +#define TESTING_FLAG_INVERSE_BATTLE (TESTING_FLAGS_START + 0x1) #define TESTING_FLAG_UNUSED_2 (TESTING_FLAGS_START + 0x2) #define TESTING_FLAG_UNUSED_3 (TESTING_FLAGS_START + 0x3) #define TESTING_FLAG_UNUSED_4 (TESTING_FLAGS_START + 0x4) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 5e93679643..e2f40ea328 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -12,6 +12,7 @@ enum GenConfigTag GEN_CONFIG_GALE_WINGS, GEN_CONFIG_HEAL_BELL_SOUNDPROOF, GEN_CONFIG_TELEPORT_BEHAVIOR, + GEN_CONFIG_ABILITY_WEATHER, GEN_CONFIG_MOODY_STATS, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index 4f555b9368..bbd12445e4 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -15,6 +15,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_GALE_WINGS] = B_GALE_WINGS, [GEN_CONFIG_HEAL_BELL_SOUNDPROOF] = B_HEAL_BELL_SOUNDPROOF, [GEN_CONFIG_TELEPORT_BEHAVIOR] = B_TELEPORT_BEHAVIOR, + [GEN_CONFIG_ABILITY_WEATHER] = B_ABILITY_WEATHER, [GEN_CONFIG_MOODY_STATS] = B_MOODY_ACC_EVASION, }; diff --git a/src/battle_util.c b/src/battle_util.c index b03718f4b9..afa9b5b94a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4050,7 +4050,7 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbilit { return FALSE; } - else if (B_ABILITY_WEATHER < GEN_6 && viaAbility) + else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && viaAbility) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; return TRUE; @@ -4900,7 +4900,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { move = gBattleMons[i].moves[j]; moveType = GetBattleMoveType(move); - if (CalcTypeEffectivenessMultiplier(move, moveType, i, battler, ABILITY_ANTICIPATION, FALSE) >= UQ_4_12(2.0)) + if (CalcTypeEffectivenessMultiplier(move, moveType, i, battler, ABILITY_ANTICIPATION, FALSE) >= UQ_4_12(2.0) || GetMoveEffect(move) == EFFECT_OHKO) { effect++; break; diff --git a/test/battle/ability/anticipation.c b/test/battle/ability/anticipation.c index a15ff153d9..7a45537296 100644 --- a/test/battle/ability/anticipation.c +++ b/test/battle/ability/anticipation.c @@ -1,24 +1,353 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effective move"); -TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move"); -TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a Self-Destruct or Explosion (Gen4)"); -TO_DO_BATTLE_TEST("Anticipation treats Self-Destruct and Explosion like all other Normal types (Gen5+)"); +SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effective move") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CLOSE_COMBAT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} +SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FISSURE, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats Self-Destruct and Explosion like all other Normal types (Gen5+)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EXPLOSION, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Normalize into their effectiveness (Gen5+)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_DELCATTY) { Ability(ABILITY_NORMALIZE); Moves(MOVE_CLOSE_COMBAT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Scrappy into their effectiveness (Gen5+)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_TRICK_OR_TREAT, MOVE_SKILL_SWAP, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK_OR_TREAT); MOVE(player, MOVE_SKILL_SWAP); } + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_OR_TREAT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+)") +{ + KNOWN_FAILING; + GIVEN { + PLAYER(SPECIES_SKARMORY); + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_EARTHQUAKE, MOVE_GRAVITY, MOVE_SCRATCH, MOVE_POUND); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRAVITY); MOVE(player, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRAVITY, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation counts Counter, Metal Burst or Mirror Coat as attacking moves of their types (Gen5+)") +{ + u32 move, species, typeAtk, typeDef; + PARAMETRIZE { move = MOVE_COUNTER; species = SPECIES_RATICATE; typeAtk = TYPE_FIGHTING; typeDef = TYPE_NORMAL; } + PARAMETRIZE { move = MOVE_METAL_BURST; species = SPECIES_ROGGENROLA; typeAtk = TYPE_STEEL; typeDef = TYPE_ROCK; } + PARAMETRIZE { move = MOVE_MIRROR_COAT; species = SPECIES_NIDORINO; typeAtk = TYPE_PSYCHIC; typeDef = TYPE_POISON; } + GIVEN { + ASSUME(GetMoveType(move) == typeAtk); + ASSUME(gSpeciesInfo[species].types[0] == typeDef); + ASSUME(gSpeciesInfo[species].types[1] == typeDef); + PLAYER(species); + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(move, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Synchronoise as an ordinary Psychic-type move") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SYNCHRONOISE) == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_NIDORINO].types[0] == TYPE_POISON); + ASSUME(gSpeciesInfo[SPECIES_NIDORINO].types[1] == TYPE_POISON); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] != TYPE_POISON); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] != TYPE_POISON); + PLAYER(SPECIES_NIDORINO); + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_SYNCHRONOISE, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Freeze-Dry as an ordinary Ice-type move") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetMoveType(MOVE_FREEZE_DRY) == TYPE_ICE); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_SQUIRTLE].types[1] == TYPE_WATER); + PLAYER(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_FREEZE_DRY, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Flying Press as an ordinary Fighting-type move") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetMoveType(MOVE_FLYING_PRESS) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[1] == TYPE_GRASS); + PLAYER(SPECIES_TANGELA); + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_FLYING_PRESS, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Aura Wheel as an ordinary Electric-type move") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_AURA_WHEEL) == TYPE_ELECTRIC); + ASSUME(gSpeciesInfo[SPECIES_PONYTA_GALAR].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_PONYTA_GALAR].types[1] == TYPE_PSYCHIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_MORPEKO) { Ability(ABILITY_HUNGER_SWITCH); Moves(MOVE_AURA_WHEEL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Judgment") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_ARCEUS) { Item(ITEM_FIST_PLATE); Moves(MOVE_JUDGMENT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Weather Ball") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[1] == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ANTICIPATION); Speed(2); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); Moves(MOVE_WEATHER_BALL, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); Speed(4); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_SKILL_SWAP); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Natural Gift") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_NATURAL_GIFT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Techno Blast") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[1] == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_GENESECT) { Item(ITEM_BURN_DRIVE); Moves(MOVE_TECHNO_BLAST, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Revelation Dance") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_REVELATION_DANCE) == EFFECT_REVELATION_DANCE); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[1] == TYPE_STEEL); + ASSUME(gSpeciesInfo[SPECIES_ORICORIO_BAILE].types[0] == TYPE_FIRE); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_ORICORIO_BAILE) { Moves(MOVE_REVELATION_DANCE, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Multi-Attack") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_SILVALLY) { Item(ITEM_FIGHTING_MEMORY); Moves(MOVE_MULTI_ATTACK, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation does not consider Strong Winds on type matchups") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_RAYQUAZA_MEGA].types[0] == TYPE_DRAGON); + ASSUME(gSpeciesInfo[SPECIES_RAYQUAZA_MEGA].types[1] == TYPE_FLYING); + PLAYER(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_ROCK_SLIDE, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DELTA_STREAM); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation does not consider ate-abilities") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_WORMADAM_PLANT].types[0] == TYPE_BUG); + ASSUME(gSpeciesInfo[SPECIES_WORMADAM_PLANT].types[1] == TYPE_GRASS); + PLAYER(SPECIES_WORMADAM_PLANT) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_AURORUS) { Ability(ABILITY_REFRIGERATE); Moves(MOVE_GROWL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats Hidden Power as its dynamic type (Gen6+)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[0] == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_EEVEE].types[1] == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Item(ITEM_CHOPLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HIDDEN_POWER, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); HPIV(30); AttackIV(2); DefenseIV(31); SpAttackIV(30); SpDefenseIV(30); SpeedIV(30); } + } WHEN { + TURN { MOVE(opponent, MOVE_HIDDEN_POWER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); // Check that the item is triggered + ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, opponent); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Inverse Battle types") +{ + GIVEN { + FLAG_SET(B_FLAG_INVERSE_BATTLE); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_FERROTHORN].types[1] == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GROWL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a Self-Destruct or Explosion (Gen4)"); TO_DO_BATTLE_TEST("Anticipation considers Scrappy and Normalize into their effectiveness (Gen4)"); -TO_DO_BATTLE_TEST("Anticipation doesn't consider Scrappy and Normalize into their effectiveness (Gen5+)"); TO_DO_BATTLE_TEST("Anticipation considers Gravity into their effectiveness (Gen4)"); -TO_DO_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+)"); TO_DO_BATTLE_TEST("Anticipation doesn't trigger from Counter, Metal Burst or Mirror Coat (Gen4)"); -TO_DO_BATTLE_TEST("Anticipation counts Counter, Metal Burst or Mirror Coat as attacking moves of their types (Gen5+)"); -TO_DO_BATTLE_TEST("Anticipation considers Synchronoise as an ordinary Psychic-type move"); -TO_DO_BATTLE_TEST("Anticipation considers Freeze-Dry as an ordinary Ice-type move"); -TO_DO_BATTLE_TEST("Anticipation considers Flying Press as an ordinary Fighting-type move"); -TO_DO_BATTLE_TEST("Anticipation considers Aura Wheel as an ordinary Electric-type move"); -TO_DO_BATTLE_TEST("Anticipation considers Inverse Battle types"); //Check with Normal-type moves -TO_DO_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal)"); // Judgment, Weather Ball, Natural Gift, Techno Blast, Revelation Dance, Multi Attack TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as Normal Type (Gen4-5)"); -TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as its dynamic type (Gen6+)"); -TO_DO_BATTLE_TEST("Anticipation does not consider Strong Winds on type matchups"); -TO_DO_BATTLE_TEST("Anticipation does not consider ate-abilities"); diff --git a/test/battle/ability/drought.c b/test/battle/ability/drought.c index bbe4dbe853..c28b0895b8 100644 --- a/test/battle/ability/drought.c +++ b/test/battle/ability/drought.c @@ -1,5 +1,83 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Drought sets up sun for 5 turns (Gen6+)"); -TO_DO_BATTLE_TEST("Drought sets up permanent sun (Gen3-5)"); +SINGLE_BATTLE_TEST("Drought sets up sun for 5 turns (Gen6+)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} + +SINGLE_BATTLE_TEST("Drought sets up sun for 8 turns with Heat Rock (Gen6+)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_DROUGHT); Item(ITEM_HEAT_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} + +SINGLE_BATTLE_TEST("Drought sets up permanent sun (Gen3-5)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_ABILITY_WEATHER, GEN_3); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + NOT MESSAGE("The sunlight faded."); + } +} diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index fb20522937..bb2fc81afa 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -138,5 +138,27 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder" } } -TO_DO_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP instead of heal (Gen 3-4"); -TO_DO_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+"); +SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+") +{ + s16 damage; + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_DREAM_EATER) == EFFECT_DREAM_EATER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACRUEL) { Ability(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_LT(damage, 0); + } +} + +TO_DO_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP instead of heal (Gen 3-4") diff --git a/test/battle/ability/sand_force.c b/test/battle/ability/sand_force.c index e17722a885..959d437980 100644 --- a/test/battle/ability/sand_force.c +++ b/test/battle/ability/sand_force.c @@ -1,6 +1,63 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Sand Force prevents damage from sandstorm"); -TO_DO_BATTLE_TEST("Sand Force increases the power of Rock-, Ground- and Steel-type moves by 30% in sandstorm"); -TO_DO_BATTLE_TEST("Sand Force increases move power if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Sand Force prevents damage from sandstorm") +{ + u32 type1 = gSpeciesInfo[SPECIES_SHELLOS].types[0]; + u32 type2 = gSpeciesInfo[SPECIES_SHELLOS].types[1]; + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Sand Force increases the power of Rock-, Ground- and Steel-type moves by 30% in sandstorm", s16 damage) +{ + u32 moveOpponent, movePlayer; + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_ROCK_THROW; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_ROCK_THROW; } + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_EARTHQUAKE; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_EARTHQUAKE; } + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_IRON_HEAD; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_IRON_HEAD; } + GIVEN { + ASSUME(GetMoveType(MOVE_ROCK_THROW) == TYPE_ROCK); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + ASSUME(GetMoveType(MOVE_IRON_HEAD) == TYPE_STEEL); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, moveOpponent); MOVE(player, movePlayer); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.3), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.3), results[5].damage); + } +} + +SINGLE_BATTLE_TEST("Sand Force don't increase move power if Cloud Nine/Air Lock is on the field", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + GIVEN { + ASSUME(GetMoveType(MOVE_ROCK_THROW) == TYPE_ROCK); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_ROCK_THROW); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/ability/sand_rush.c b/test/battle/ability/sand_rush.c index fa7695c129..08dd22ef57 100644 --- a/test/battle/ability/sand_rush.c +++ b/test/battle/ability/sand_rush.c @@ -1,6 +1,51 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Sand Rush prevents damage from sandstorm"); -TO_DO_BATTLE_TEST("Sand Rush doubles speed from sandstorm"); -TO_DO_BATTLE_TEST("Sand Rush doesn't double speed if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Sand Rush prevents damage from sandstorm") +{ + u32 type1 = gSpeciesInfo[SPECIES_STOUTLAND].types[0]; + u32 type2 = gSpeciesInfo[SPECIES_STOUTLAND].types[1]; + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_STOUTLAND) { Ability(ABILITY_SAND_RUSH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Sand Rush doubles speed from sandstorm") +{ + GIVEN { + PLAYER(SPECIES_SANDSLASH) { Ability(ABILITY_SAND_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SANDSTORM); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SANDSTORM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Sand Rush doesn't double speed if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + PLAYER(SPECIES_SANDSLASH) { Ability(ABILITY_SAND_RUSH); Speed(100); } + OPPONENT(SPECIES_GOLDUCK) { Speed(199); Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SANDSTORM); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SANDSTORM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} diff --git a/test/battle/ability/sand_veil.c b/test/battle/ability/sand_veil.c index 7622d18763..e2bfdd02f8 100644 --- a/test/battle/ability/sand_veil.c +++ b/test/battle/ability/sand_veil.c @@ -29,5 +29,17 @@ SINGLE_BATTLE_TEST("Sand Veil increases evasion during sandstorm") } } -TO_DO_BATTLE_TEST("Sand Veil doesn't prevent Sandstorm damage if Cloud Nine/Air Lock is on the field"); -TO_DO_BATTLE_TEST("Sand Veil doesn't increase evasion if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Sand Veil doesn't increase evasion if Cloud Nine/Air Lock is on the field") +{ + PASSES_RANDOMLY(5, 5, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_POUND) == 100); + PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + TURN { MOVE(opponent, MOVE_POUND); } + } SCENE { + HP_BAR(player); + } +} diff --git a/test/battle/ability/slush_rush.c b/test/battle/ability/slush_rush.c index 8ae62454bb..d7508c2cb4 100644 --- a/test/battle/ability/slush_rush.c +++ b/test/battle/ability/slush_rush.c @@ -1,6 +1,65 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Slush Rush doubles speed from hail"); -TO_DO_BATTLE_TEST("Slush Rush doubles speed from snow"); -TO_DO_BATTLE_TEST("Slush Rush doesn't double speed if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Slush Rush doubles speed from hail") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_HAIL); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doubles speed from snow") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SNOWSCAPE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doesn't double speed if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_GOLDUCK) { Speed(199); Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SNOWSCAPE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doesn't prevent non-Ice types from taking damage in Hail") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_ICE); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_ICE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, player); + HP_BAR(player); + } +} diff --git a/test/battle/ability/solar_power.c b/test/battle/ability/solar_power.c index f14ea11ee1..1fdccad5be 100644 --- a/test/battle/ability/solar_power.c +++ b/test/battle/ability/solar_power.c @@ -1,7 +1,70 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Solar Power increases a Sp. Attack by x1.5 in Sun"); -TO_DO_BATTLE_TEST("Solar Power doesn't increases a Sp. Attack if Cloud Nine/Air Lock is on the field"); -TO_DO_BATTLE_TEST("Solar Power causes the Pokémon to lose 1/8 max HP in Sun"); -TO_DO_BATTLE_TEST("Solar Power doesn't cause the Pokémon to lose 1/8 max HP if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Solar Power increases a Sp. Attack by x1.5 in Sun", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + GIVEN { + ASSUME(GetMovePower(MOVE_HYPER_VOICE) > 0); + ASSUME(GetMoveCategory(MOVE_HYPER_VOICE) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_SOLAR_POWER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + HP_BAR(player); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Power doesn't increases a Sp. Attack if Cloud Nine/Air Lock is on the field", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + GIVEN { + ASSUME(GetMovePower(MOVE_HYPER_VOICE) > 0); + ASSUME(GetMoveCategory(MOVE_HYPER_VOICE) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_SOLAR_POWER); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Power causes the Pokémon to lose 1/8 max HP in Sun") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_SOLAR_POWER); MaxHP(80); HP(80); } + OPPONENT(SPECIES_WOBBUFFET); + } SCENE { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP - player->maxHP/8); + } +} + +SINGLE_BATTLE_TEST("Solar Power doesn't cause the Pokémon to lose 1/8 max HP if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_SOLAR_POWER); MaxHP(80); HP(80); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} diff --git a/test/battle/weather/hail.c b/test/battle/weather/hail.c index 6c7f7bcdec..100692de20 100644 --- a/test/battle/weather/hail.c +++ b/test/battle/weather/hail.c @@ -82,3 +82,17 @@ SINGLE_BATTLE_TEST("Hail damage rounds properly when maxHP < 16") HP_BAR(player, damage: 1); } } + +SINGLE_BATTLE_TEST("Hail doesn't do damage when weather is negated") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_ICE); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_ICE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); } + } SCENE { + NOT HP_BAR(player); + } +} diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index dcac3f71c3..2ff0697597 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -94,3 +94,20 @@ SINGLE_BATTLE_TEST("Sandstorm damage rounds properly when maxHP < 16") HP_BAR(player, damage: 1); } } + +SINGLE_BATTLE_TEST("Sandstorm doesn't do damage when weather is negated") +{ + u32 type1 = gSpeciesInfo[SPECIES_STOUTLAND].types[0]; + u32 type2 = gSpeciesInfo[SPECIES_STOUTLAND].types[1]; + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} From 8cc289ef35137eaf879935d4d2e17c9fe919604b Mon Sep 17 00:00:00 2001 From: i0brendan0 <19826742+i0brendan0@users.noreply.github.com> Date: Thu, 8 May 2025 04:28:00 -0500 Subject: [PATCH 07/15] Fix rerolls overwriting Fixed Personality (#6774) --- src/pokemon.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pokemon.c b/src/pokemon.c index 9cb9430690..a317ccdc37 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1105,7 +1105,7 @@ void CreateMon(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 hasFix void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, u8 hasFixedPersonality, u32 fixedPersonality, u8 otIdType, u32 fixedOtId) { u8 speciesName[POKEMON_NAME_LENGTH + 1]; - u32 personality; + u32 personality = Random32(); u32 value; u16 checksum; u8 i; @@ -1115,11 +1115,6 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, ZeroBoxMonData(boxMon); - if (hasFixedPersonality) - personality = fixedPersonality; - else - personality = Random32(); - // Determine original trainer ID if (otIdType == OT_ID_RANDOM_NO_SHINY) { @@ -1129,7 +1124,7 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, else if (otIdType == OT_ID_PRESET) { value = fixedOtId; - isShiny = GET_SHINY_VALUE(value, personality) < SHINY_ODDS; + isShiny = GET_SHINY_VALUE(value, hasFixedPersonality ? fixedPersonality : personality) < SHINY_ODDS; } else // Player is the OT { @@ -1175,6 +1170,9 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, isShiny = GET_SHINY_VALUE(value, personality) < SHINY_ODDS; } } + + if (hasFixedPersonality) + personality = fixedPersonality; SetBoxMonData(boxMon, MON_DATA_PERSONALITY, &personality); SetBoxMonData(boxMon, MON_DATA_OT_ID, &value); From 5e190d07822dc372b6d203d52df4e4e0a635a13f Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Thu, 8 May 2025 20:17:32 +0200 Subject: [PATCH 08/15] Fix gems activating for moves that don't deal type damage (#6789) --- src/battle_main.c | 5 ++++- src/battle_script_commands.c | 1 - test/battle/hold_effect/gems.c | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/battle_main.c b/src/battle_main.c index 671a31737b..3f6c5e8617 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -6074,7 +6074,10 @@ void SetTypeBeforeUsingMove(u32 move, u32 battler) gBattleStruct->dynamicMoveType = TYPE_ELECTRIC | F_DYNAMIC_TYPE_SET; // Check if a gem should activate. - if (holdEffect == HOLD_EFFECT_GEMS && GetBattleMoveType(move) == ItemId_GetSecondaryId(heldItem)) + if (holdEffect == HOLD_EFFECT_GEMS + && GetBattleMoveType(move) == ItemId_GetSecondaryId(heldItem) + && GetMoveEffect(move) != EFFECT_PLEDGE + && GetMovePower(move) > 1) { gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler); gSpecialStatuses[battler].gemBoost = TRUE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index bd9e19b035..39e23021fa 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2202,7 +2202,6 @@ static void Cmd_adjustdamage(void) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && gBattleMons[gBattlerAttacker].item - && moveEffect != EFFECT_PLEDGE && gCurrentMove != MOVE_STRUGGLE) { BattleScriptPushCursor(); diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c index 52c85d7fb5..5486443808 100644 --- a/test/battle/hold_effect/gems.c +++ b/test/battle/hold_effect/gems.c @@ -87,3 +87,19 @@ SINGLE_BATTLE_TEST("Gem is consumed if the move type is changed") ANIMATION(ANIM_TYPE_MOVE, MOVE_FEINT_ATTACK, player); } } + +SINGLE_BATTLE_TEST("Gem is not consumed if a no type damage move is used") //ie. Counter, Psywave, Super Fang. All these moves have 1 base power. +{ + ASSUME(GetMovePower(MOVE_PSYWAVE) == 1); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PSYCHIC_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYWAVE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Psychic Gem strengthened Wobbuffet's power!"); + } + } +} From 3b91a964c931e834060111b324d89c95305bcd97 Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Fri, 9 May 2025 22:23:44 +0200 Subject: [PATCH 09/15] Fix Iron Ball type effectiveness check (#6794) --- include/config/battle.h | 1 + src/battle_util.c | 35 ++++++++++++++++++++++++----- test/battle/hold_effect/iron_ball.c | 22 ++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 test/battle/hold_effect/iron_ball.c diff --git a/include/config/battle.h b/include/config/battle.h index 8ef347cdcd..478210101e 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -186,6 +186,7 @@ #define B_SAFARI_BALL_MODIFIER GEN_LATEST // In Gen8+, Safari Ball's catch multiplier was reduced from x1.5 to x1. #define B_FRIEND_BALL_MODIFIER GEN_LATEST // In Gen8+, Friend Ball's friendship boost was reduced from 200 to 150. #define B_SERENE_GRACE_BOOST GEN_LATEST // In Gen5+, Serene Grace boosts the added flinch chance of King's Rock and Razor Fang. +#define B_IRON_BALL GEN_LATEST // In Gen5+, Flying-type Pokemon holding Iron Ball take x1 damage from Ground-type moves regardless of their other types, except during Inverse Battles or if the Pokemon is grounded by any other effect. // Flag settings // To use the following features in scripting, replace the 0s with the flag ID you're assigning it to. diff --git a/src/battle_util.c b/src/battle_util.c index afa9b5b94a..ccd4d6fb2d 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8816,12 +8816,24 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) return isProtected; } -// Only called directly when calculating damage type effectiveness -static bool32 IsBattlerGroundedInverseCheck(u32 battler, bool32 considerInverse) +enum InverseBattleCheck +{ + INVERSE_BATTLE, + NOT_INVERSE_BATTLE +}; + +enum IronBallCheck +{ + CHECK_IRON_BALL, + IGNORE_IRON_BALL +}; + +// Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics +static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum InverseBattleCheck checkInverse, enum IronBallCheck checkIronBall) { u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); - if (holdEffect == HOLD_EFFECT_IRON_BALL) + if (!(checkIronBall == IGNORE_IRON_BALL) && holdEffect == HOLD_EFFECT_IRON_BALL) return TRUE; if (gFieldStatuses & STATUS_FIELD_GRAVITY) return TRUE; @@ -8837,14 +8849,14 @@ static bool32 IsBattlerGroundedInverseCheck(u32 battler, bool32 considerInverse) return FALSE; if ((AI_DATA->aiCalcInProgress ? AI_DATA->abilities[battler] : GetBattlerAbility(battler)) == ABILITY_LEVITATE) return FALSE; - if (IS_BATTLER_OF_TYPE(battler, TYPE_FLYING) && (!considerInverse || !FlagGet(B_FLAG_INVERSE_BATTLE))) + if (IS_BATTLER_OF_TYPE(battler, TYPE_FLYING) && (!(checkInverse == INVERSE_BATTLE) || !FlagGet(B_FLAG_INVERSE_BATTLE))) return FALSE; return TRUE; } bool32 IsBattlerGrounded(u32 battler) { - return IsBattlerGroundedInverseCheck(battler, FALSE); + return IsBattlerGroundedInverseCheck(battler, NOT_INVERSE_BATTLE, CHECK_IRON_BALL); } u32 GetMoveSlot(u16 *moves, u32 move) @@ -10685,7 +10697,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov if (B_GLARE_GHOST < GEN_4 && move == MOVE_GLARE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST)) modifier = UQ_4_12(0.0); } - else if (moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(battlerDef, TRUE) && !(MoveIgnoresTypeIfFlyingAndUngrounded(move))) + else if (moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(battlerDef, INVERSE_BATTLE, CHECK_IRON_BALL) && !(MoveIgnoresTypeIfFlyingAndUngrounded(move))) { modifier = UQ_4_12(0.0); if (recordAbilities && defAbility == ABILITY_LEVITATE) @@ -10710,6 +10722,17 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov modifier = UQ_4_12(1.0); } + // Iron Ball ignores type modifiers for flying-type mons if it is the only source of grounding + if (B_IRON_BALL >= GEN_5 + && moveType == TYPE_GROUND + && IS_BATTLER_OF_TYPE(battlerDef, TYPE_FLYING) + && GetBattlerHoldEffect(battlerDef, TRUE) == HOLD_EFFECT_IRON_BALL + && !IsBattlerGroundedInverseCheck(battlerDef, NOT_INVERSE_BATTLE, IGNORE_IRON_BALL) + && !FlagGet(B_FLAG_INVERSE_BATTLE)) + { + modifier = UQ_4_12(1.0); + } + if (((defAbility == ABILITY_WONDER_GUARD && modifier <= UQ_4_12(1.0)) || (defAbility == ABILITY_TELEPATHY && battlerDef == BATTLE_PARTNER(battlerAtk))) && GetMovePower(move) != 0) diff --git a/test/battle/hold_effect/iron_ball.c b/test/battle/hold_effect/iron_ball.c new file mode 100644 index 0000000000..134c47d9af --- /dev/null +++ b/test/battle/hold_effect/iron_ball.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS{ + ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); +} + +SINGLE_BATTLE_TEST("Ground-type moves do neutral damage to non-grounded Flying types holding Iron Ball regardless of other typings") //gen5+ only +{ + ASSUME(B_IRON_BALL >= GEN_5); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SKARMORY) { Item(ITEM_IRON_BALL); }; + } WHEN { + TURN { MOVE(player, MOVE_EARTHQUAKE); }; + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + NONE_OF { + MESSAGE("It's super effective!"); + } + } +} From 8d5d7c7fcafd6d0b546cd846d24513a825fb07ed Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 10 May 2025 19:38:48 +0200 Subject: [PATCH 10/15] Fixes ai moves being recorded without correct index (#6803) Co-authored-by: Bassoonian --- src/battle_ai_util.c | 21 +++++++++++---------- test/battle/ai/ai.c | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 6478950a85..9a3f95674c 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -128,19 +128,20 @@ void RecordLastUsedMoveBy(u32 battlerId, u32 move) BATTLE_HISTORY->moveHistory[battlerId][*index] = move; } -void RecordKnownMove(u32 battlerId, u32 move) +void RecordKnownMove(u32 battler, u32 move) { - s32 i; - for (i = 0; i < MAX_MON_MOVES; i++) + s32 moveIndex; + + for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (BATTLE_HISTORY->usedMoves[battlerId][i] == move) + if (gBattleMons[battler].moves[moveIndex] == move) break; - if (BATTLE_HISTORY->usedMoves[battlerId][i] == MOVE_NONE) - { - BATTLE_HISTORY->usedMoves[battlerId][i] = move; - AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].moves[i] = move; - break; - } + } + + if (moveIndex < MAX_MON_MOVES && BATTLE_HISTORY->usedMoves[battler][moveIndex] == MOVE_NONE) + { + BATTLE_HISTORY->usedMoves[battler][moveIndex] = move; + AI_PARTY->mons[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].moves[moveIndex] = move; } } diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index a23052b25c..6b837a543c 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -891,3 +891,26 @@ AI_SINGLE_BATTLE_TEST("AI sees popped Air Balloon after Air Balloon mon switches TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); SEND_OUT(player, 1); } } } + +SINGLE_BATTLE_TEST("AI correctly records used moves") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_GROWL, MOVE_FLOWER_TRICK, MOVE_TORCH_SONG); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_RAGE_FIST, MOVE_PSYCHIC, MOVE_SCRATCH, MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { MOVE(player, MOVE_FLOWER_TRICK); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_TORCH_SONG); MOVE(opponent, MOVE_PSYCHIC); } + TURN { MOVE(player, MOVE_GROWL); MOVE(opponent, MOVE_RAGE_FIST); } + } THEN { + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_PLAYER_LEFT][0], MOVE_TACKLE); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_PLAYER_LEFT][1], MOVE_GROWL); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_PLAYER_LEFT][2], MOVE_FLOWER_TRICK); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_PLAYER_LEFT][3], MOVE_TORCH_SONG); + + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_OPPONENT_LEFT][0], MOVE_RAGE_FIST); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_OPPONENT_LEFT][1], MOVE_PSYCHIC); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_OPPONENT_LEFT][2], MOVE_SCRATCH); + EXPECT_EQ(BATTLE_HISTORY->usedMoves[B_POSITION_OPPONENT_LEFT][3], MOVE_EARTHQUAKE); + } +} From f4f82f23948a14629864ba0a94f08f56c47aac1f Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Sat, 10 May 2025 22:55:28 +0200 Subject: [PATCH 11/15] Toxic Spikes print whether the target is poisoned or badly poisoned (#6814) --- data/battle_scripts_1.s | 8 +++++++ include/battle_scripts.h | 1 + include/constants/battle_string_ids.h | 3 ++- src/battle_message.c | 1 + src/battle_script_commands.c | 10 +++++++-- test/battle/move_effect/toxic_spikes.c | 31 ++++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 53ed13f9b6..7a47fff5b5 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6223,6 +6223,14 @@ BattleScript_ToxicSpikesPoisoned:: waitstate return +BattleScript_ToxicSpikesBadlyPoisoned:: + printstring STRINGID_TOXICSPIKESBADLYPOISONED + waitmessage B_WAIT_TIME_LONG + statusanimation BS_SCRIPTING + updatestatusicon BS_SCRIPTING + waitstate + return + BattleScript_StickyWebOnSwitchIn:: savetarget saveattacker diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 20ea1ccd49..d840ab5666 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -286,6 +286,7 @@ extern const u8 BattleScript_BadDreamsActivates[]; extern const u8 BattleScript_SwitchInAbilityMsg[]; extern const u8 BattleScript_SwitchInAbilityMsgRet[]; extern const u8 BattleScript_ToxicSpikesPoisoned[]; +extern const u8 BattleScript_ToxicSpikesBadlyPoisoned[]; extern const u8 BattleScript_ToxicSpikesAbsorbed[]; extern const u8 BattleScript_StickyWebOnSwitchIn[]; extern const u8 BattleScript_SolarPowerActivates[]; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 3ba637c690..7c7e023fa3 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -729,8 +729,9 @@ #define STRINGID_ITDOESNTAFFECTTWOFOES 727 #define STRINGID_SENDCAUGHTMONPARTYORBOX 728 #define STRINGID_PKMNSENTTOPCAFTERCATCH 729 +#define STRINGID_TOXICSPIKESBADLYPOISONED 730 -#define BATTLESTRINGS_COUNT 730 +#define BATTLESTRINGS_COUNT 731 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_message.c b/src/battle_message.c index 7b02496ac7..ff887d7f38 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -638,6 +638,7 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_STEALTHROCKDMG] = COMPOUND_STRING("Pointed stones dug into {B_SCR_NAME_WITH_PREFIX2}!"), [STRINGID_TOXICSPIKESABSORBED] = COMPOUND_STRING("The poison spikes disappeared from the ground around {B_SCR_TEAM2} team!"), [STRINGID_TOXICSPIKESPOISONED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was poisoned!"), + [STRINGID_TOXICSPIKESBADLYPOISONED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was badly poisoned!"), [STRINGID_STICKYWEBSWITCHIN] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} was caught in a sticky web!"), [STRINGID_HEALINGWISHCAMETRUE] = COMPOUND_STRING("The healing wish came true for {B_ATK_NAME_WITH_PREFIX2}!"), [STRINGID_HEALINGWISHHEALED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} regained health!"), diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 39e23021fa..cd3ef5a232 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8253,8 +8253,11 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) i = GetBattlerAbility(battler); if (CanBePoisoned(gBattlerAttacker, battler, i)) { - if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2) + u32 tspikes = 0; + if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2) { + tspikes = 1; gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON; + } else gBattleMons[battler].status1 |= STATUS1_POISON; @@ -8262,7 +8265,10 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) MarkBattlerForControllerExec(battler); gBattleScripting.battler = battler; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned; + if (tspikes == 0) + gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned; + else + gBattlescriptCurrInstr = BattleScript_ToxicSpikesBadlyPoisoned; } } } diff --git a/test/battle/move_effect/toxic_spikes.c b/test/battle/move_effect/toxic_spikes.c index 641fc0520c..5452f66a8b 100644 --- a/test/battle/move_effect/toxic_spikes.c +++ b/test/battle/move_effect/toxic_spikes.c @@ -235,3 +235,34 @@ SINGLE_BATTLE_TEST("Toxic Spikes inflicts poison on switch in after Primal Rever STATUS_ICON(player, poison: TRUE); } } + +SINGLE_BATTLE_TEST("Toxic Spikes print normal poison for 1 layer") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + MESSAGE("The opposing Wynaut was poisoned!"); + } +} + +SINGLE_BATTLE_TEST("Toxic Spikes print bad poison for 2 layers") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + MESSAGE("The opposing Wynaut was badly poisoned!"); + } +} From 0ad3f929abb96561eeb1de439cd5f4dd9adf97be Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 11 May 2025 11:34:16 +0200 Subject: [PATCH 12/15] Fixes Berserk Gene infinite loop (#6813) --- data/battle_scripts_1.s | 14 ++++-- include/battle_scripts.h | 1 + src/battle_util.c | 64 ++++++++++---------------- test/battle/hold_effect/berserk_gene.c | 16 +++++++ 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 7a47fff5b5..11797e72ed 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -10041,15 +10041,16 @@ BattleScript_CouldntFullyProtect:: return BattleScript_BerserkGeneRet:: + saveattacker savetarget copybyte gBattlerTarget, sBATTLER statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_BerserkGeneRet_TryConfuse setgraphicalstatchangevalues - playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 + playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 setbyte cMULTISTRING_CHOOSER, B_MSG_STAT_ROSE_ITEM call BattleScript_StatUp BattleScript_BerserkGeneRet_TryConfuse: - jumpifability BS_SCRIPTING, ABILITY_OWN_TEMPO, BattleScript_BerserkGeneRet_OwnTempoPrevents + jumpifability BS_ATTACKER, ABILITY_OWN_TEMPO, BattleScript_BerserkGeneRet_OwnTempoPrevents jumpifsafeguard BattleScript_BerserkGeneRet_SafeguardProtected seteffectprimary MOVE_EFFECT_CONFUSION goto BattleScript_BerserkGeneRet_End @@ -10064,9 +10065,14 @@ BattleScript_BerserkGeneRet_OwnTempoPrevents: printstring STRINGID_PKMNPREVENTSCONFUSIONWITH waitmessage B_WAIT_TIME_LONG BattleScript_BerserkGeneRet_End: + restoreattacker restoretarget - removeitem BS_SCRIPTING - end3 + removeitem BS_ATTACKER + return + +BattleScript_BerserkGeneRetEnd2:: + call BattleScript_BerserkGeneRet + end2 BattleScript_BoosterEnergyEnd2:: call BattleScript_BoosterEnergyRet diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d840ab5666..958cd71892 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -492,6 +492,7 @@ extern const u8 BattleScript_MoveEffectStockpileWoreOff[]; extern const u8 BattleScript_StealthRockActivates[]; extern const u8 BattleScript_SpikesActivates[]; extern const u8 BattleScript_BerserkGeneRet[]; +extern const u8 BattleScript_BerserkGeneRetEnd2[]; extern const u8 BattleScript_TargetFormChangeWithStringNoPopup[]; extern const u8 BattleScript_DefDown[]; extern const u8 BattleScript_UltraBurst[]; diff --git a/src/battle_util.c b/src/battle_util.c index ccd4d6fb2d..3b6818b8dd 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7163,6 +7163,28 @@ static enum ItemEffect TryEjectPack(u32 battler, enum ItemCaseId caseID) return ITEM_NO_EFFECT; } +static enum ItemEffect ConsumeBerserkGene(u32 battler, enum ItemCaseId caseID) +{ + if (CanBeInfinitelyConfused(battler)) + gStatuses4[battler] |= STATUS4_INFINITE_CONFUSION; + + BufferStatChange(battler, STAT_ATK, STRINGID_STATROSE); + gBattlerAttacker = gEffectBattler = battler; + SET_STATCHANGER(STAT_ATK, 2, FALSE); + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; + gBattleScripting.animArg2 = 0; + if (caseID == ITEMEFFECT_ON_SWITCH_IN_FIRST_TURN || caseID == ITEMEFFECT_NORMAL) + { + BattleScriptExecute(BattleScript_BerserkGeneRetEnd2); + } + else + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_BerserkGeneRet; + } + return ITEM_STATS_CHANGE; +} + static u32 ItemRestorePp(u32 battler, u32 itemId, enum ItemCaseId caseID) { struct Pokemon *party = GetBattlerParty(battler); @@ -7577,19 +7599,7 @@ static u8 ItemEffectMoveEnd(u32 battler, u16 holdEffect) } break; case HOLD_EFFECT_BERSERK_GENE: - BufferStatChange(battler, STAT_ATK, STRINGID_STATROSE); - gEffectBattler = battler; - if (CanBeInfinitelyConfused(gEffectBattler)) - { - gStatuses4[gEffectBattler] |= STATUS4_INFINITE_CONFUSION; - } - SET_STATCHANGER(STAT_ATK, 2, FALSE); - - gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; - gBattleScripting.animArg2 = 0; - - BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); - effect = ITEM_STATS_CHANGE; + effect = ConsumeBerserkGene(battler, ITEMEFFECT_NONE); break; case HOLD_EFFECT_MIRROR_HERB: effect = TryConsumeMirrorHerb(battler, ITEMEFFECT_NONE); @@ -7860,19 +7870,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) effect = TryEjectPack(battler, caseID); break; case HOLD_EFFECT_BERSERK_GENE: - BufferStatChange(battler, STAT_ATK, STRINGID_STATROSE); - gEffectBattler = battler; - if (CanBeInfinitelyConfused(gEffectBattler)) - { - gStatuses4[gEffectBattler] |= STATUS4_INFINITE_CONFUSION; - } - SET_STATCHANGER(STAT_ATK, 2, FALSE); - - gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; - gBattleScripting.animArg2 = 0; - - BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); - effect = ITEM_STATS_CHANGE; + effect = ConsumeBerserkGene(battler, caseID); break; case HOLD_EFFECT_MIRROR_HERB: effect = TryConsumeMirrorHerb(battler, caseID); @@ -8072,19 +8070,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) effect = TrySetMicleBerry(battler, gLastUsedItem, caseID); break; case HOLD_EFFECT_BERSERK_GENE: - BufferStatChange(battler, STAT_ATK, STRINGID_STATROSE); - gEffectBattler = battler; - if (CanBeInfinitelyConfused(gEffectBattler)) - { - gStatuses4[gEffectBattler] |= STATUS4_INFINITE_CONFUSION; - } - SET_STATCHANGER(STAT_ATK, 2, FALSE); - - gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; - gBattleScripting.animArg2 = 0; - - BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); - effect = ITEM_STATS_CHANGE; + effect = ConsumeBerserkGene(battler, caseID); break; case HOLD_EFFECT_MIRROR_HERB: effect = TryConsumeMirrorHerb(battler, caseID); diff --git a/test/battle/hold_effect/berserk_gene.c b/test/battle/hold_effect/berserk_gene.c index 164830ad52..640198c992 100644 --- a/test/battle/hold_effect/berserk_gene.c +++ b/test/battle/hold_effect/berserk_gene.c @@ -236,3 +236,19 @@ SINGLE_BATTLE_TEST("Berserk Gene causes confusion timer to not tick down", u32 s EXPECT_EQ(results[0].status2, results[1].status2); } } + +SINGLE_BATTLE_TEST("Berserk Gene does not cause an infinite loop") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BESTOW) == EFFECT_BESTOW); + PLAYER(SPECIES_TOXEL) { Item(ITEM_BERSERK_GENE); Ability(ABILITY_KLUTZ); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); + } +} From 2bc82a886a596d444a6f76e1d86966c57b4146ad Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sun, 11 May 2025 22:37:11 +0200 Subject: [PATCH 13/15] Add AUTO_GEN_TARGETS for .party files and map_groups_count, but on master (#6824) Co-authored-by: Hedara --- Makefile | 3 +++ map_data_rules.mk | 1 + 2 files changed, 4 insertions(+) diff --git a/Makefile b/Makefile index f2128cc25d..52b4c94b9e 100644 --- a/Makefile +++ b/Makefile @@ -350,6 +350,9 @@ clean-generated: COMPETITIVE_PARTY_SYNTAX := $(shell PATH="$(PATH)"; echo 'COMPETITIVE_PARTY_SYNTAX' | $(CPP) $(CPPFLAGS) -imacros include/gba/defines.h -imacros include/config/general.h | tail -n1) ifeq ($(COMPETITIVE_PARTY_SYNTAX),1) %.h: %.party ; $(CPP) $(CPPFLAGS) -traditional-cpp - < $< | $(TRAINERPROC) -o $@ -i $< - + +AUTO_GEN_TARGETS += $(DATA_SRC_SUBDIR)/trainers.h +AUTO_GEN_TARGETS += $(DATA_SRC_SUBDIR)/battle_partners.h endif $(C_BUILDDIR)/librfu_intr.o: CFLAGS := -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -fno-toplevel-reorder -Wno-pointer-to-int-cast diff --git a/map_data_rules.mk b/map_data_rules.mk index 6712698272..f3e8d5dc89 100755 --- a/map_data_rules.mk +++ b/map_data_rules.mk @@ -11,6 +11,7 @@ INCLUDECONSTS_OUTDIR := include/constants AUTO_GEN_TARGETS += $(INCLUDECONSTS_OUTDIR)/map_groups.h AUTO_GEN_TARGETS += $(INCLUDECONSTS_OUTDIR)/layouts.h +AUTO_GEN_TARGETS += $(DATA_SRC_SUBDIR)/map_group_count.h MAP_DIRS := $(dir $(wildcard $(MAPS_DIR)/*/map.json)) MAP_CONNECTIONS := $(patsubst $(MAPS_DIR)/%/,$(MAPS_DIR)/%/connections.inc,$(MAP_DIRS)) From a6b02b00c07112d6fc4dd38172e1c3e7d9e0c123 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Mon, 12 May 2025 14:16:40 +0100 Subject: [PATCH 14/15] Fixes restoretarget on empty stack when using G-Max Gravitas (#6827) --- data/battle_scripts_1.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 11797e72ed..4d278a6179 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2713,12 +2713,12 @@ BattleScript_EffectGravity:: attackstring ppreduce setgravity BattleScript_ButItFailed - savetarget attackanimation waitanimation BattleScript_EffectGravitySuccess:: printstring STRINGID_GRAVITYINTENSIFIED waitmessage B_WAIT_TIME_LONG + savetarget selectfirstvalidtarget BattleScript_GravityLoop: movevaluescleanup From 5f86fd7de6912c857534ca97fa3768d88af295eb Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Mon, 12 May 2025 17:38:03 +0200 Subject: [PATCH 15/15] Changed github CI to use arm-none-eabi-gcc (#6829) Co-authored-by: Hedara --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 449eaa8c38..c3040f55c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,6 @@ on: jobs: build: runs-on: ubuntu-latest - container: devkitpro/devkitarm env: GAME_VERSION: EMERALD GAME_REVISION: 0 @@ -24,10 +23,8 @@ jobs: - name: Install binutils run: | sudo apt update - sudo apt install -y build-essential libpng-dev libelf-dev + sudo apt install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3 # build-essential and git are already installed - # gcc-arm-none-eabi is only needed for the modern build - # as an alternative to dkP - name: ROM env: