From 341fa3692cc858414c1c1829285269e97fe2650b Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Wed, 12 Jun 2024 08:43:51 +0200 Subject: [PATCH 01/14] Make Tandemaus only evolve at the end of battles (#4759) --- include/constants/pokemon.h | 5 +++-- src/battle_main.c | 2 +- src/pokemon.c | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h index 186ef5de59..424919cc44 100644 --- a/include/constants/pokemon.h +++ b/include/constants/pokemon.h @@ -289,8 +289,8 @@ #define EVO_LEVEL_FOG 42 // Pokémon reaches the specified level during fog in the overworld #define EVO_MOVE_TWO_SEGMENT 43 // Pokémon levels up, knows specified move, has a personality value with a modulus of 0 #define EVO_MOVE_THREE_SEGMENT 44 // Pokémon levels up, knows specified move, has a personality value with a modulus of 1-99 -#define EVO_LEVEL_FAMILY_OF_THREE 45 // Pokémon reaches the specified level with a personality value with a modulus of 0 -#define EVO_LEVEL_FAMILY_OF_FOUR 46 // Pokémon reaches the specified level with a personality value with a modulus of 1-99 +#define EVO_LEVEL_FAMILY_OF_THREE 45 // Pokémon reaches the specified level in battle with a personality value with a modulus of 0 +#define EVO_LEVEL_FAMILY_OF_FOUR 46 // Pokémon reaches the specified level in battle with a personality value with a modulus of 1-99 #define EVO_LEVEL_MOVE_TWENTY_TIMES 47 // Pokémon levels up after having used a move for at least 20 times #define EVO_LEVEL_RECOIL_DAMAGE_MALE 48 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a male #define EVO_LEVEL_RECOIL_DAMAGE_FEMALE 49 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a female @@ -302,6 +302,7 @@ #define EVO_MODE_ITEM_CHECK 3 // If an Everstone is being held, still want to show that the stone *could* be used on that Pokémon to evolve #define EVO_MODE_BATTLE_SPECIAL 4 #define EVO_MODE_OVERWORLD_SPECIAL 5 +#define EVO_MODE_BATTLE_ONLY 6 // This mode is only used in battles to support Tandemaus' unique requirement #define MON_PIC_WIDTH 64 #define MON_PIC_HEIGHT 64 diff --git a/src/battle_main.c b/src/battle_main.c index 4d64ed46b2..af073cafb6 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5837,7 +5837,7 @@ static void TryEvolvePokemon(void) if (species == SPECIES_NONE && (gLeveledUpInBattle & gBitTable[i])) { gLeveledUpInBattle &= ~(gBitTable[i]); - species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_NORMAL, gLeveledUpInBattle, NULL); + species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL); } if (species != SPECIES_NONE) diff --git a/src/pokemon.c b/src/pokemon.c index 763c044c76..e33f55472a 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -4176,6 +4176,7 @@ u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 mode, u16 evolutionItem, s switch (mode) { case EVO_MODE_NORMAL: + case EVO_MODE_BATTLE_ONLY: level = GetMonData(mon, MON_DATA_LEVEL, 0); friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, 0); @@ -4264,11 +4265,11 @@ u16 GetEvolutionTargetSpecies(struct Pokemon *mon, u8 mode, u16 evolutionItem, s targetSpecies = evolutions[i].targetSpecies; break; case EVO_LEVEL_FAMILY_OF_FOUR: - if (evolutions[i].param <= level && (personality % 100) != 0) + if (mode == EVO_MODE_BATTLE_ONLY && evolutions[i].param <= level && (personality % 100) != 0) targetSpecies = evolutions[i].targetSpecies; break; case EVO_LEVEL_FAMILY_OF_THREE: - if (evolutions[i].param <= level && (personality % 100) == 0) + if (mode == EVO_MODE_BATTLE_ONLY && evolutions[i].param <= level && (personality % 100) == 0) targetSpecies = evolutions[i].targetSpecies; break; case EVO_BEAUTY: From 4724b5dfd62ab9a168b96761e57551a84ffc7914 Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Wed, 12 Jun 2024 09:09:15 +0200 Subject: [PATCH 02/14] Improved Surf/Thunderbolt test (#4764) * improved Surf/Thunderbolt test that previously relied on correct AI guess * remove unnecessary mod * fixed infinite rng loop * wrong ability num and readability * Update src/battle_ai_util.c --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/random.h | 1 + src/battle_ai_util.c | 17 ++++++++--------- test/battle/ai.c | 24 +++++++++++++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/include/random.h b/include/random.h index d15aa28bfb..37649cedf5 100644 --- a/include/random.h +++ b/include/random.h @@ -188,6 +188,7 @@ enum RandomTag RNG_QUICK_CLAW, RNG_TRACE, RNG_FICKLE_BEAM, + RNG_AI_ABILITY, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index f8275d417a..27215563b9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1084,6 +1084,8 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability) // does NOT include ability suppression checks s32 AI_DecideKnownAbilityForTurn(u32 battlerId) { + u32 validAbilities[NUM_ABILITY_SLOTS]; + u8 i, numValidAbilities = 0; u32 knownAbility = GetBattlerAbility(battlerId); // We've had ability overwritten by e.g. Worry Seed. It is not part of AI_PARTY in case of switching @@ -1105,18 +1107,15 @@ s32 AI_DecideKnownAbilityForTurn(u32 battlerId) if (knownAbility == ABILITY_SHADOW_TAG || knownAbility == ABILITY_MAGNET_PULL || knownAbility == ABILITY_ARENA_TRAP) return knownAbility; - // Else, guess the ability - if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) + for (i = 0; i < NUM_ABILITY_SLOTS; i++) { - u32 abilityGuess = ABILITY_NONE; - while (abilityGuess == ABILITY_NONE) - { - abilityGuess = gSpeciesInfo[gBattleMons[battlerId].species].abilities[Random() % NUM_ABILITY_SLOTS]; - } - - return abilityGuess; + if (gSpeciesInfo[gBattleMons[battlerId].species].abilities[i] != ABILITY_NONE) + validAbilities[numValidAbilities++] = gSpeciesInfo[gBattleMons[battlerId].species].abilities[i]; } + if (numValidAbilities > 0) + return validAbilities[RandomUniform(RNG_AI_ABILITY, 0, numValidAbilities - 1)]; + return ABILITY_NONE; // Unknown. } diff --git a/test/battle/ai.c b/test/battle/ai.c index 819eb2edaa..59acdf966c 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -819,8 +819,9 @@ AI_SINGLE_BATTLE_TEST("AI will not choose Burn Up if the user lost the Fire typi } } -AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the opposing mon has Volt Absorb") +AI_SINGLE_BATTLE_TEST("AI will only choose Surf 1/3 times if the opposing mon has Volt Absorb") { + PASSES_RANDOMLY(1, 3, RNG_AI_ABILITY); GIVEN { ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); @@ -828,6 +829,27 @@ AI_SINGLE_BATTLE_TEST("AI will choose Surf over Thunderbolt and Ice Beam if the OPPONENT(SPECIES_LANTURN) { Moves(MOVE_THUNDERBOLT, MOVE_ICE_BEAM, MOVE_SURF); } } WHEN { TURN { EXPECT_MOVE(opponent, MOVE_SURF); } + TURN { EXPECT_MOVE(opponent, MOVE_SURF); } + } SCENE { + MESSAGE("Foe Lanturn used Surf!"); + MESSAGE("Foe Lanturn used Surf!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI will choose Thunderbolt then Surf 2/3 times if the opposing mon has Volt Absorb") +{ + PASSES_RANDOMLY(2, 3, RNG_AI_ABILITY); + GIVEN { + ASSUME(gMovesInfo[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); }; + OPPONENT(SPECIES_LANTURN) { Moves(MOVE_THUNDERBOLT, MOVE_ICE_BEAM, MOVE_SURF); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_THUNDERBOLT); } + TURN { EXPECT_MOVE(opponent, MOVE_SURF); } + } SCENE { + MESSAGE("Foe Lanturn used Thunderbolt!"); + MESSAGE("Foe Lanturn used Surf!"); } } From 5ebdcdc9b03ab95caaa2a1d2d16f7fbb75dd08a8 Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Wed, 12 Jun 2024 02:34:36 -0500 Subject: [PATCH 03/14] Fix Pokemon in tests not being male by default (#4766) --- test/battle/ability/prankster.c | 2 +- test/test_runner_battle.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/battle/ability/prankster.c b/test/battle/ability/prankster.c index d6c5a92462..8c5feaee43 100644 --- a/test/battle/ability/prankster.c +++ b/test/battle/ability/prankster.c @@ -136,7 +136,7 @@ DOUBLE_BATTLE_TEST("Prankster-affected moves that target all Pokémon are succes { GIVEN { ASSUME(gMovesInfo[MOVE_CAPTIVATE].target == MOVE_TARGET_BOTH); - PLAYER(SPECIES_VOLBEAT) { Ability(ABILITY_PRANKSTER); } + PLAYER(SPECIES_ILLUMISE) { Ability(ABILITY_PRANKSTER); } PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_UMBREON); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 30183a6dc8..376c3be8a5 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -1553,7 +1553,7 @@ void OpenPokemon(u32 sourceLine, u32 side, u32 species) DATA.currentSide = side; DATA.currentPartyIndex = *partySize; DATA.currentMon = &party[DATA.currentPartyIndex]; - DATA.gender = MON_MALE; + DATA.gender = 0xFF; // Male DATA.nature = NATURE_HARDY; (*partySize)++; From 9c72392891e9ecf9f5daf5a596773abf23270cd0 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:21:44 +0200 Subject: [PATCH 04/14] Fixes Shell Side Arm (#4753) * Fixes Shell Side Arm * Fixes to random call * hardcoded the effect to the move * minor change * minor change 2 * applied reviews --- asm/macros/battle_script.inc | 8 +- data/battle_scripts_1.s | 4 - include/battle.h | 1 + include/battle_scripts.h | 1 - include/battle_util.h | 3 +- include/constants/battle_move_effects.h | 1 - include/constants/battle_script_commands.h | 57 ++++++------ include/random.h | 1 + src/battle_ai_util.c | 5 +- src/battle_main.c | 8 +- src/battle_script_commands.c | 56 +++--------- src/battle_util.c | 88 +++++++++++++----- src/data/battle_move_effects.h | 6 -- src/data/moves_info.h | 2 +- test/battle/move_effect/shell_side_arm.c | 100 +++++++++++++++++++++ 15 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 test/battle/move_effect/shell_side_arm.c diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 031c950465..d207716194 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1637,11 +1637,11 @@ .macro trygulpmissile callnative BS_TryGulpMissile .endm - + .macro tryactivategulpmissile callnative BS_TryActivateGulpMissile .endm - + .macro tryquash failInstr:req callnative BS_TryQuash .4byte \failInstr @@ -2211,10 +2211,6 @@ .4byte \failInstr .endm - .macro shellsidearmcheck - various BS_ATTACKER, VARIOUS_SHELL_SIDE_ARM_CHECK - .endm - .macro jumpifteanoberry jumpInstr:req various BS_ATTACKER, VARIOUS_TEATIME_TARGETS .4byte \jumpInstr diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a103fc34a1..be64975390 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -719,10 +719,6 @@ BattleScript_FlingMissed: ppreduce goto BattleScript_MoveMissedPause -BattleScript_EffectShellSideArm:: - shellsidearmcheck - goto BattleScript_EffectHit - BattleScript_EffectPhotonGeyser:: setphotongeysercategory goto BattleScript_EffectHit diff --git a/include/battle.h b/include/battle.h index 51d1a03bc3..ef4611869e 100644 --- a/include/battle.h +++ b/include/battle.h @@ -779,6 +779,7 @@ struct BattleStruct u8 supremeOverlordCounter[MAX_BATTLERS_COUNT]; u8 quickClawRandom[MAX_BATTLERS_COUNT]; u8 quickDrawRandom[MAX_BATTLERS_COUNT]; + u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d7b58ba11a..1fd77449f6 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -796,7 +796,6 @@ extern const u8 BattleScript_EffectPlasmaFists[]; extern const u8 BattleScript_EffectHyperspaceFury[]; extern const u8 BattleScript_EffectAuraWheel[]; extern const u8 BattleScript_EffectPhotonGeyser[]; -extern const u8 BattleScript_EffectShellSideArm[]; extern const u8 BattleScript_EffectNoRetreat[]; extern const u8 BattleScript_EffectTarShot[]; extern const u8 BattleScript_EffectPoltergeist[]; diff --git a/include/battle_util.h b/include/battle_util.h index 6c4724c1d3..da47b3444b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -155,7 +155,7 @@ u32 IsAbilityOnOpposingSide(u32 battler, u32 ability); u32 IsAbilityOnField(u32 ability); u32 IsAbilityOnFieldExcept(u32 battler, u32 ability); u32 IsAbilityPreventingEscape(u32 battler); -bool32 IsBattlerProtected(u32 battler, u32 move); +bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move); bool32 CanBattlerEscape(u32 battler); // no ability check void BattleScriptExecute(const u8 *BS_ptr); void BattleScriptPushCursorAndCallback(const u8 *BS_ptr); @@ -206,6 +206,7 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move); bool32 HasEnoughHpToEatBerry(u32 battler, u32 hpFraction, u32 itemId); bool32 IsPartnerMonFromSameTrainer(u32 battler); u8 GetCategoryBasedOnStats(u32 battler); +void SetShellSideArmCategory(void); bool32 MoveIsAffectedBySheerForce(u32 move); bool32 TestIfSheerForceAffected(u32 battler, u16 move); void TryRestoreHeldItems(void); diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index b19bc0f7b1..13a300f064 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -302,7 +302,6 @@ enum { EFFECT_HYPERSPACE_FURY, EFFECT_AURA_WHEEL, EFFECT_PHOTON_GEYSER, - EFFECT_SHELL_SIDE_ARM, EFFECT_TERRAIN_PULSE, EFFECT_NO_RETREAT, EFFECT_TAR_SHOT, diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 7228954f21..6fdbcc9e96 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -208,35 +208,34 @@ #define VARIOUS_JUMP_IF_WEATHER_AFFECTED 116 #define VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED 117 #define VARIOUS_SET_ATTACKER_STICKY_WEB_USER 118 -#define VARIOUS_SHELL_SIDE_ARM_CHECK 119 -#define VARIOUS_TRY_NO_RETREAT 120 -#define VARIOUS_TRY_TAR_SHOT 121 -#define VARIOUS_CAN_TAR_SHOT_WORK 122 -#define VARIOUS_CHECK_POLTERGEIST 123 -#define VARIOUS_CUT_1_3_HP_RAISE_STATS 124 -#define VARIOUS_TRY_END_NEUTRALIZING_GAS 125 -#define VARIOUS_JUMP_IF_UNDER_200 126 -#define VARIOUS_SET_SKY_DROP 127 -#define VARIOUS_CLEAR_SKY_DROP 128 -#define VARIOUS_SKY_DROP_YAWN 129 -#define VARIOUS_CURE_CERTAIN_STATUSES 130 -#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 131 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 132 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 133 -#define VARIOUS_SAVE_BATTLER_ITEM 134 -#define VARIOUS_RESTORE_BATTLER_ITEM 135 -#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 136 -#define VARIOUS_SET_BEAK_BLAST 137 -#define VARIOUS_SWAP_SIDE_STATUSES 138 -#define VARIOUS_SWAP_STATS 139 -#define VARIOUS_TEATIME_INVUL 140 -#define VARIOUS_TEATIME_TARGETS 141 -#define VARIOUS_TRY_WIND_RIDER_POWER 142 -#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 143 -#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 144 -#define VARIOUS_STORE_HEALING_WISH 145 -#define VARIOUS_HIT_SWITCH_TARGET_FAILED 146 -#define VARIOUS_TRY_REVIVAL_BLESSING 147 +#define VARIOUS_TRY_NO_RETREAT 119 +#define VARIOUS_TRY_TAR_SHOT 120 +#define VARIOUS_CAN_TAR_SHOT_WORK 121 +#define VARIOUS_CHECK_POLTERGEIST 122 +#define VARIOUS_CUT_1_3_HP_RAISE_STATS 123 +#define VARIOUS_TRY_END_NEUTRALIZING_GAS 124 +#define VARIOUS_JUMP_IF_UNDER_200 125 +#define VARIOUS_SET_SKY_DROP 126 +#define VARIOUS_CLEAR_SKY_DROP 127 +#define VARIOUS_SKY_DROP_YAWN 128 +#define VARIOUS_CURE_CERTAIN_STATUSES 129 +#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 130 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 131 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 132 +#define VARIOUS_SAVE_BATTLER_ITEM 133 +#define VARIOUS_RESTORE_BATTLER_ITEM 134 +#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 135 +#define VARIOUS_SET_BEAK_BLAST 136 +#define VARIOUS_SWAP_SIDE_STATUSES 137 +#define VARIOUS_SWAP_STATS 138 +#define VARIOUS_TEATIME_INVUL 139 +#define VARIOUS_TEATIME_TARGETS 140 +#define VARIOUS_TRY_WIND_RIDER_POWER 141 +#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 142 +#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 143 +#define VARIOUS_STORE_HEALING_WISH 144 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 145 +#define VARIOUS_TRY_REVIVAL_BLESSING 146 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/include/random.h b/include/random.h index 37649cedf5..3f47dc1e16 100644 --- a/include/random.h +++ b/include/random.h @@ -189,6 +189,7 @@ enum RandomTag RNG_TRACE, RNG_FICKLE_BEAM, RNG_AI_ABILITY, + RNG_SHELL_SIDE_ARM, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 27215563b9..dcf223070b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -477,8 +477,9 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes } else if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); - - if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) + else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL) + gBattleStruct->swapDamageCategory = TRUE; + else if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) move = GetNaturePowerMove(); gBattleStruct->dynamicMoveType = 0; diff --git a/src/battle_main.c b/src/battle_main.c index af073cafb6..fe36bde552 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3677,8 +3677,8 @@ const u8* FaintClearSetData(u32 battler) gBattleStruct->zmove.toBeUsed[battler] = MOVE_NONE; gBattleStruct->zmove.effect = EFFECT_HIT; // Clear Dynamax data - UndoDynamax(battler); - + UndoDynamax(battler); + return result; } @@ -4162,6 +4162,7 @@ static void TryDoEventsBeforeFirstTurn(void) memset(gQueuedStatBoosts, 0, sizeof(gQueuedStatBoosts)); // erase all totem boosts just to be safe + SetShellSideArmCategory(); SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers if (gBattleTypeFlags & BATTLE_TYPE_ARENA) @@ -4254,6 +4255,7 @@ void BattleTurnPassed(void) *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); + SetShellSideArmCategory(); SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers gBattleMainFunc = HandleTurnActionSelectionState; @@ -5835,7 +5837,7 @@ static void TryEvolvePokemon(void) sTriedEvolving |= gBitTable[i]; if (species == SPECIES_NONE && (gLeveledUpInBattle & gBitTable[i])) - { + { gLeveledUpInBattle &= ~(gBitTable[i]); species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL); } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d227456224..8cef961abe 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1479,7 +1479,7 @@ static void Cmd_attackcanceler(void) gBattlescriptCurrInstr = BattleScript_TookAttack; RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); } - else if (IsBattlerProtected(gBattlerTarget, gCurrentMove) + else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) && (gCurrentMove != MOVE_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) && (!gBattleMoveEffects[gMovesInfo[gCurrentMove].effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) && gMovesInfo[gCurrentMove].effect != EFFECT_SUCKER_PUNCH @@ -1535,7 +1535,7 @@ static void Cmd_unused5(void) { CMD_ARGS(const u8 *failInstr); - if (IsBattlerProtected(gBattlerTarget, gCurrentMove)) + if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove)) { gMoveResultFlags |= MOVE_RESULT_MISSED; JumpIfMoveFailed(sizeof(*cmd), MOVE_NONE); @@ -1550,7 +1550,7 @@ static void Cmd_unused5(void) static bool8 JumpIfMoveAffectedByProtect(u16 move) { bool8 affected = FALSE; - if (IsBattlerProtected(gBattlerTarget, move)) + if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, move)) { gMoveResultFlags |= MOVE_RESULT_MISSED; JumpIfMoveFailed(7, move); @@ -2009,6 +2009,8 @@ static void Cmd_damagecalc(void) u8 moveType; GET_MOVE_TYPE(gCurrentMove, moveType); + if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) + gBattleStruct->swapDamageCategory = TRUE; gBattleMoveDamage = CalculateMoveDamage(gCurrentMove, gBattlerAttacker, gBattlerTarget, moveType, 0, gIsCriticalHit, TRUE, TRUE); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -2106,7 +2108,7 @@ static void Cmd_adjustdamage(void) gLastUsedItem = gBattleMons[gBattlerTarget].item; gSpecialStatuses[gBattlerTarget].focusBanded = FALSE; gSpecialStatuses[gBattlerTarget].focusSashed = FALSE; - + } else if (gSpecialStatuses[gBattlerTarget].sturdied) { @@ -3225,8 +3227,8 @@ void SetMoveEffect(bool32 primary, bool32 certain) { gBattleMons[gEffectBattler].status2 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; gBattlescriptCurrInstr++; - } - else + } + else { gBattlescriptCurrInstr++; } @@ -6285,7 +6287,7 @@ static void Cmd_moveend(void) break; } } - + if (!(gBattleStruct->lastMoveFailed & gBitTable[gBattlerAttacker] || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove && gBattleStruct->bouncedMoveIsUsed))) @@ -10487,44 +10489,6 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->nextInstr; return; } - case VARIOUS_SHELL_SIDE_ARM_CHECK: // 0% chance GameFreak actually checks this way according to DaWobblefet, but this is the only functional explanation at the moment - { - VARIOUS_ARGS(); - - u32 attackerAtkStat = gBattleMons[gBattlerAttacker].attack; - u32 targetDefStat = gBattleMons[gBattlerTarget].defense; - u32 attackerSpAtkStat = gBattleMons[gBattlerAttacker].spAttack; - u32 targetSpDefStat = gBattleMons[gBattlerTarget].spDefense; - u8 statStage; - u32 physical; - u32 special; - - gBattleStruct->swapDamageCategory = FALSE; - - statStage = gBattleMons[gBattlerAttacker].statStages[STAT_ATK]; - attackerAtkStat *= gStatStageRatios[statStage][0]; - attackerAtkStat /= gStatStageRatios[statStage][1]; - - statStage = gBattleMons[gBattlerTarget].statStages[STAT_DEF]; - targetDefStat *= gStatStageRatios[statStage][0]; - targetDefStat /= gStatStageRatios[statStage][1]; - - physical = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerAtkStat) / targetDefStat) / 50); - - statStage = gBattleMons[gBattlerAttacker].statStages[STAT_SPATK]; - attackerSpAtkStat *= gStatStageRatios[statStage][0]; - attackerSpAtkStat /= gStatStageRatios[statStage][1]; - - statStage = gBattleMons[gBattlerTarget].statStages[STAT_SPDEF]; - targetSpDefStat *= gStatStageRatios[statStage][0]; - targetSpDefStat /= gStatStageRatios[statStage][1]; - - special = ((((2 * gBattleMons[gBattlerAttacker].level / 5 + 2) * gMovesInfo[gCurrentMove].power * attackerSpAtkStat) / targetSpDefStat) / 50); - - if (((physical > special) || (physical == special && (Random() % 2) == 0))) - gBattleStruct->swapDamageCategory = TRUE; - break; - } case VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED: { VARIOUS_ARGS(const u8 *jumpInstr); @@ -13903,7 +13867,7 @@ static void Cmd_trymemento(void) if (B_MEMENTO_FAIL >= GEN_4 && (gBattleCommunication[MISS_TYPE] == B_MSG_PROTECTED || gStatuses3[gBattlerTarget] & STATUS3_SEMI_INVULNERABLE - || IsBattlerProtected(gBattlerTarget, gCurrentMove) + || IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) || DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))) { // Failed, target was protected. diff --git a/src/battle_util.c b/src/battle_util.c index 8a077ffb4e..9202db6f35 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8264,7 +8264,7 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) if (!gMovesInfo[move].makesContact) { - if (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory) + if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL) return TRUE; else return FALSE; @@ -8280,59 +8280,59 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) } } -bool32 IsBattlerProtected(u32 battler, u32 move) +bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) { // Decorate bypasses protect and detect, but not crafty shield if (move == MOVE_DECORATE) { - if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD) + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD) return TRUE; - else if (gProtectStructs[battler].protected) + else if (gProtectStructs[battlerDef].protected) return FALSE; } // Z-Moves and Max Moves bypass protection (except Max Guard). if ((IsMaxMove(move) || gBattleStruct->zmove.active) - && (!gProtectStructs[battler].maxGuarded + && (!gProtectStructs[battlerDef].maxGuarded || gMovesInfo[move].argument == MAX_EFFECT_BYPASS_PROTECT)) return FALSE; // Max Guard is silly about the moves it blocks, including Teatime. - if (gProtectStructs[battler].maxGuarded && IsMoveBlockedByMaxGuard(move)) + if (gProtectStructs[battlerDef].maxGuarded && IsMoveBlockedByMaxGuard(move)) return TRUE; // Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here. // This means extra logic is needed to handle Shell Side Arm. if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST - && (gMovesInfo[move].makesContact || (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->swapDamageCategory)) - && !gProtectStructs[battler].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist + && (gMovesInfo[move].makesContact || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL)) + && !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist return FALSE; else if (gMovesInfo[move].ignoresProtect) return FALSE; - else if (gProtectStructs[battler].protected) + else if (gProtectStructs[battlerDef].protected) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_WIDE_GUARD - && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_WIDE_GUARD + && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) return TRUE; - else if (gProtectStructs[battler].banefulBunkered) + else if (gProtectStructs[battlerDef].banefulBunkered) return TRUE; - else if (gProtectStructs[battler].burningBulwarked) + else if (gProtectStructs[battlerDef].burningBulwarked) return TRUE; - else if ((gProtectStructs[battler].obstructed || gProtectStructs[battler].silkTrapped) && !IS_MOVE_STATUS(move)) + else if ((gProtectStructs[battlerDef].obstructed || gProtectStructs[battlerDef].silkTrapped) && !IS_MOVE_STATUS(move)) return TRUE; - else if (gProtectStructs[battler].spikyShielded) + else if (gProtectStructs[battlerDef].spikyShielded) return TRUE; - else if (gProtectStructs[battler].kingsShielded && gMovesInfo[move].power != 0) + else if (gProtectStructs[battlerDef].kingsShielded && gMovesInfo[move].power != 0) return TRUE; - else if (gProtectStructs[battler].maxGuarded) + else if (gProtectStructs[battlerDef].maxGuarded) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_QUICK_GUARD + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_QUICK_GUARD && GetChosenMovePriority(gBattlerAttacker) > 0) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_CRAFTY_SHIELD + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD && IS_MOVE_STATUS(move)) return TRUE; - else if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_MAT_BLOCK + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MAT_BLOCK && !IS_MOVE_STATUS(move)) return TRUE; else @@ -11382,3 +11382,51 @@ void RemoveBattlerType(u32 battler, u8 type) *(u8 *)(&gBattleMons[battler].type1 + i) = TYPE_MYSTERY; } } + +void SetShellSideArmCategory(void) +{ + u32 battlerAtk, battlerDef; + u32 attackerAtkStat; + u32 targetDefStat; + u32 attackerSpAtkStat; + u32 targetSpDefStat; + u8 statStage; + u32 physical; + u32 special; + + for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++) + { + attackerAtkStat = gBattleMons[battlerAtk].attack; + statStage = gBattleMons[battlerAtk].statStages[STAT_ATK]; + attackerAtkStat *= gStatStageRatios[statStage][0]; + attackerAtkStat /= gStatStageRatios[statStage][1]; + + attackerSpAtkStat = gBattleMons[battlerAtk].spAttack; + statStage = gBattleMons[battlerAtk].statStages[STAT_SPATK]; + attackerSpAtkStat *= gStatStageRatios[statStage][0]; + attackerSpAtkStat /= gStatStageRatios[statStage][1]; + + for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) + { + if (battlerAtk == battlerDef) + continue; + + targetDefStat = gBattleMons[battlerDef].defense; + statStage = gBattleMons[battlerDef].statStages[STAT_DEF]; + targetDefStat *= gStatStageRatios[statStage][0]; + targetDefStat /= gStatStageRatios[statStage][1]; + + physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerAtkStat) / targetDefStat) / 50); + + targetSpDefStat = gBattleMons[battlerDef].spDefense; + statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF]; + targetSpDefStat *= gStatStageRatios[statStage][0]; + targetSpDefStat /= gStatStageRatios[statStage][1]; + + special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerSpAtkStat) / targetSpDefStat) / 50); + + if (((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50)))) + gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL; + } + } +} diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 9c53f50396..5a15361553 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1934,12 +1934,6 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 0, // TODO: Assign points }, - [EFFECT_SHELL_SIDE_ARM] = - { - .battleScript = BattleScript_EffectShellSideArm, - .battleTvScore = 0, // TODO: Assign points - }, - [EFFECT_TERRAIN_PULSE] = { .battleScript = BattleScript_EffectHit, diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 35672537a6..8351392613 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -17350,7 +17350,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .description = COMPOUND_STRING( "Deals better of physical and\n" "special damage. May poison."), - .effect = EFFECT_SHELL_SIDE_ARM, + .effect = EFFECT_HIT, // The effect is hardcoded to the move since SetShellSideArmCategory() can't be used with anything but Shell Side Arm because of the BP requirement .power = 90, .type = TYPE_POISON, .accuracy = 100, diff --git a/test/battle/move_effect/shell_side_arm.c b/test/battle/move_effect/shell_side_arm.c new file mode 100644 index 0000000000..f0b3dd74dc --- /dev/null +++ b/test/battle/move_effect/shell_side_arm.c @@ -0,0 +1,100 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Shell Side Arm can be countered if it is physical") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_REGICE) { Defense(100); SpDefense(200); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Side Arm can be mirror coated if it is special") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_REGIROCK) { Defense(200); SpDefense(100); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SIDE_ARM); MOVE(opponent, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Side Arm does not change catogory mid-turn") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SHELL_SIDE_ARM); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(100); SpDefense(120); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); } + TURN { MOVE(opponent, MOVE_LIGHT_SCREEN); MOVE(player, MOVE_SHELL_SIDE_ARM); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SHELL_SIDE_ARM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +} + +DOUBLE_BATTLE_TEST("Shell Side Arm is choosing it's type for each battler on the field") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); } + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_REGIROCK) { Speed(30); Defense(200); SpDefense(100); } + OPPONENT(SPECIES_REGICE) { Speed(30); Defense(100); SpDefense(200); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentRight); MOVE(opponentRight, MOVE_COUNTER); } + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, opponentRight); + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft); + HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Shell Side Arm does not change category mid-turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Moves(MOVE_SHELL_SIDE_ARM); } + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); Defense(200); SpDefense(190); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(40); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); + MOVE(opponentRight, MOVE_LIGHT_SCREEN); + MOVE(opponentLeft, MOVE_MIRROR_COAT); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft); + HP_BAR(playerLeft); + } +} From 251019d63de19f1169705bbbfb757d1206feccaf Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Wed, 12 Jun 2024 04:33:03 -0500 Subject: [PATCH 05/14] Update wild held items to Gen 9 (#4769) * Update wild held items to Gen 9 * Update gen_1_families.h * Update gen_5_families.h --- .../pokemon/species_info/gen_2_families.h | 1 + .../pokemon/species_info/gen_5_families.h | 1 + .../pokemon/species_info/gen_7_families.h | 8 +++---- .../pokemon/species_info/gen_9_families.h | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/data/pokemon/species_info/gen_2_families.h b/src/data/pokemon/species_info/gen_2_families.h index 492efdd048..5c42d99ef9 100644 --- a/src/data/pokemon/species_info/gen_2_families.h +++ b/src/data/pokemon/species_info/gen_2_families.h @@ -4330,6 +4330,7 @@ const struct SpeciesInfo gSpeciesInfoGen2[] = .catchRate = 120, .expYield = (P_UPDATED_EXP_YIELDS >= GEN_5) ? 66 : 124, .evYield_Attack = 1, + .itemCommon = ITEM_HONEY, .genderRatio = PERCENT_FEMALE(50), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, diff --git a/src/data/pokemon/species_info/gen_5_families.h b/src/data/pokemon/species_info/gen_5_families.h index ee8ead9858..b25aae6546 100644 --- a/src/data/pokemon/species_info/gen_5_families.h +++ b/src/data/pokemon/species_info/gen_5_families.h @@ -8503,6 +8503,7 @@ const struct SpeciesInfo gSpeciesInfoGen5[] = .catchRate = 45, .expYield = 172, .evYield_Attack = 2, + .itemRare = ITEM_LEADERS_CREST, .genderRatio = PERCENT_FEMALE(50), .eggCycles = 20, .friendship = 35, diff --git a/src/data/pokemon/species_info/gen_7_families.h b/src/data/pokemon/species_info/gen_7_families.h index fef7f30e2e..fdb038b4c6 100644 --- a/src/data/pokemon/species_info/gen_7_families.h +++ b/src/data/pokemon/species_info/gen_7_families.h @@ -1197,7 +1197,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] = .catchRate = 45, .expYield = 167, .evYield_SpAttack = 2, - .itemRare = ITEM_HONEY, + .itemRare = ITEM_RED_NECTAR, .genderRatio = PERCENT_FEMALE(75), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, @@ -1252,7 +1252,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] = .catchRate = 45, .expYield = 167, .evYield_SpAttack = 2, - .itemRare = ITEM_HONEY, + .itemRare = ITEM_YELLOW_NECTAR, .genderRatio = PERCENT_FEMALE(75), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, @@ -1307,7 +1307,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] = .catchRate = 45, .expYield = 167, .evYield_SpAttack = 2, - .itemRare = ITEM_HONEY, + .itemRare = ITEM_PINK_NECTAR, .genderRatio = PERCENT_FEMALE(75), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, @@ -1362,7 +1362,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] = .catchRate = 45, .expYield = 167, .evYield_SpAttack = 2, - .itemRare = ITEM_HONEY, + .itemRare = ITEM_PURPLE_NECTAR, .genderRatio = PERCENT_FEMALE(75), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, diff --git a/src/data/pokemon/species_info/gen_9_families.h b/src/data/pokemon/species_info/gen_9_families.h index da82e9bc5a..8110770ffd 100644 --- a/src/data/pokemon/species_info/gen_9_families.h +++ b/src/data/pokemon/species_info/gen_9_families.h @@ -2529,6 +2529,8 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 190, .expYield = 67, .evYield_SpDefense = 1, + .itemCommon = ITEM_TINY_MUSHROOM, + .itemRare = ITEM_BIG_MUSHROOM, .genderRatio = PERCENT_FEMALE(50), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, @@ -2582,6 +2584,8 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 90, .expYield = 180, .evYield_SpDefense = 2, + .itemCommon = ITEM_TINY_MUSHROOM, + .itemRare = ITEM_BIG_MUSHROOM, .genderRatio = PERCENT_FEMALE(50), .eggCycles = 20, .friendship = STANDARD_FRIENDSHIP, @@ -4354,6 +4358,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4409,6 +4414,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_HP = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4464,6 +4470,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4521,6 +4528,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .evYield_Speed = 1, .evYield_SpAttack = 1, .evYield_SpDefense = 1, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4577,6 +4585,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4631,6 +4640,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4686,6 +4696,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Defense = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4741,6 +4752,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Speed = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4796,6 +4808,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4851,6 +4864,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4907,6 +4921,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4963,6 +4978,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5561,6 +5577,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5617,6 +5634,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5783,6 +5801,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 5, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5837,6 +5856,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 5, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6335,6 +6355,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Defense = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6390,6 +6411,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6445,6 +6467,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Speed = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6499,6 +6522,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, From a17259763c37011f4df0a4a61942c198bfdff28f Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 12 Jun 2024 13:17:18 +0200 Subject: [PATCH 06/14] Fix Baton Pass breaking on Memento (#4773) * Fix Baton Pass breaking on Memento * doubled headers --- src/battle_script_commands.c | 8 ++++++-- test/battle/move_effect/baton_pass.c | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8cef961abe..44825a54f0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7032,8 +7032,12 @@ static void Cmd_openpartyscreen(void) if (gAbsentBattlerFlags & gBitTable[battlerOpposite]) battlerOpposite ^= BIT_FLANK; - BtlController_EmitLinkStandbyMsg(battlerOpposite, BUFFER_A, LINK_STANDBY_MSG_ONLY, FALSE); - MarkBattlerForControllerExec(battlerOpposite); + // Make sure we're checking a valid battler. In edge case scenarios - battler could be absent and battlerOpposite would become a non-existent one softlocking the game. + if (battlerOpposite < gBattlersCount) + { + BtlController_EmitLinkStandbyMsg(battlerOpposite, BUFFER_A, LINK_STANDBY_MSG_ONLY, FALSE); + MarkBattlerForControllerExec(battlerOpposite); + } } } } diff --git a/test/battle/move_effect/baton_pass.c b/test/battle/move_effect/baton_pass.c index 6fad1c1605..02d8a8839e 100644 --- a/test/battle/move_effect/baton_pass.c +++ b/test/battle/move_effect/baton_pass.c @@ -1,6 +1,30 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); +} + +// This softlocked the game before. +SINGLE_BATTLE_TEST("Baton Pass used after Memento works correctly") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_CATERPIE); + } WHEN { + TURN { MOVE(player, MOVE_MEMENTO); SEND_OUT(player, 1); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + MESSAGE("Wobbuffet used Memento!"); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Foe Wynaut used Baton Pass!"); + MESSAGE("2 sent out Caterpie!"); + MESSAGE("Go! Wobbuffet!"); + } +} + TO_DO_BATTLE_TEST("Baton Pass switches out the user"); TO_DO_BATTLE_TEST("Baton Pass fails if there's no valid party Pokémon left"); TO_DO_BATTLE_TEST("Baton Pass passes both positive and negative stat changes"); From 000f144465c1a29b91c8d8d2b575ad843ecfb015 Mon Sep 17 00:00:00 2001 From: sneed <56992013+Sneed69@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:54:34 +0300 Subject: [PATCH 07/14] AI actually avoids contact vs rocky helmet/rough skin (#4779) --- src/battle_ai_util.c | 11 ++++++----- test/battle/ai.c | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index dcf223070b..515c28d85c 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -820,17 +820,18 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo) { bool32 effect1, effect2; - s32 defAbility = AI_DATA->abilities[battlerDef]; + u32 defAbility = AI_DATA->abilities[battlerDef]; + u32 atkAbility = AI_DATA->abilities[battlerAtk]; // Check if physical moves hurt. - if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS + if (AI_DATA->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS && atkAbility != ABILITY_LONG_REACH && (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET || defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN)) { - if (IS_MOVE_PHYSICAL(move1) && !IS_MOVE_PHYSICAL(move2)) + if (gMovesInfo[move1].makesContact && !gMovesInfo[move2].makesContact) + return -1; + if (gMovesInfo[move2].makesContact && !gMovesInfo[move1].makesContact) return 1; - if (IS_MOVE_PHYSICAL(move2) && !IS_MOVE_PHYSICAL(move1)) - return 0; } // Check additional effects. diff --git a/test/battle/ai.c b/test/battle/ai.c index 59acdf966c..f69a748bc9 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -1000,3 +1000,27 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); } } } + +AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet") +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_ROCKY_HELMET; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_BRANCH_POKE].makesContact); + ASSUME(!gMovesInfo[MOVE_LEAFAGE].makesContact); + ASSUME(gMovesInfo[MOVE_BRANCH_POKE].power == gMovesInfo[MOVE_LEAFAGE].power); + ASSUME(gMovesInfo[MOVE_BRANCH_POKE].type == gMovesInfo[MOVE_LEAFAGE].type); + ASSUME(gMovesInfo[MOVE_BRANCH_POKE].category == gMovesInfo[MOVE_LEAFAGE].category); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_BRANCH_POKE, MOVE_LEAFAGE); } + } WHEN { + if (item == ITEM_ROCKY_HELMET) + TURN { EXPECT_MOVE(opponent, MOVE_LEAFAGE); } + else + TURN { EXPECT_MOVES(opponent, MOVE_LEAFAGE, MOVE_BRANCH_POKE); } + } +} From 416519220d54006eae2ca448c2315c6e84432efa Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Wed, 12 Jun 2024 21:52:04 +0200 Subject: [PATCH 08/14] Add evolution tracker check tests (#4771) --- test/species.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/species.c b/test/species.c index 02827ba9c7..3ad7495d23 100644 --- a/test/species.c +++ b/test/species.c @@ -85,3 +85,47 @@ TEST("Form change targets have the appropriate species flags") } } } + +TEST("No species has two evolutions that use the evolution tracker") +{ + u32 i; + u32 species = SPECIES_NONE; + u32 evolutionTrackerEvolutions; + bool32 hasGenderBasedRecoil; + const struct Evolution *evolutions; + + for (i = 0; i < NUM_SPECIES; i++) + { + if (GetSpeciesEvolutions(i) != NULL) PARAMETRIZE { species = i; } + } + + evolutionTrackerEvolutions = 0; + hasGenderBasedRecoil = FALSE; + evolutions = GetSpeciesEvolutions(species); + + for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++) + { + if (evolutions[i].method == EVO_LEVEL_MOVE_TWENTY_TIMES + #ifdef EVO_DEFEAT_WITH_ITEM + || evolutions[i].method == EVO_DEFEAT_WITH_ITEM + #endif //EVO_DEFEAT_WITH_ITEM + #ifdef EVO_OVERWORLD_STEPS + || evolutions[i].method == EVO_OVERWORLD_STEPS + #endif //EVO_OVERWORLD_STEPS + ) + evolutionTrackerEvolutions++; + + if (evolutions[i].method == EVO_LEVEL_RECOIL_DAMAGE_MALE + || evolutions[i].method == EVO_LEVEL_RECOIL_DAMAGE_FEMALE) + { + // Special handling for these since they can be combined as the evolution tracker field is used for the same purpose + if (!hasGenderBasedRecoil) + { + hasGenderBasedRecoil = TRUE; + evolutionTrackerEvolutions++; + } + } + } + + EXPECT(evolutionTrackerEvolutions < 2); +} From 21339cf27220da942d0c31eb5b9c2911fba00a2e Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 12 Jun 2024 23:02:45 +0200 Subject: [PATCH 09/14] Fix AI not recognising Volt Absorb/FlashFire (#4781) --- src/battle_ai_main.c | 15 ++++++++++++++- test/battle/ai.c | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 71817646b1..5dc9ab23ae 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -943,6 +943,19 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && IsNonVolatileStatusMoveEffect(moveEffect)) RETURN_SCORE_MINUS(10); break; + case ABILITY_VOLT_ABSORB: + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_FLASH_FIRE: + if (moveType == TYPE_FIRE) + RETURN_SCORE_MINUS(20); + break; } // def ability checks // target partner ability checks & not attacking partner @@ -3386,7 +3399,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); break; case EFFECT_ROAR: - if ((gMovesInfo[move].soundMove && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) + if ((gMovesInfo[move].soundMove && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) || aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) break; else if (IsDynamaxed(battlerDef)) diff --git a/test/battle/ai.c b/test/battle/ai.c index f69a748bc9..e5c5a5e76d 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -1001,6 +1001,21 @@ AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical i } } +AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_MAGNETON); + PLAYER(SPECIES_GARDEVOIR) { Ability(ABILITY_TRACE); } + OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } + OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } + } WHEN { + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); } + } THEN { + EXPECT(gBattleResources->aiData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB); + } +} + AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet") { u32 item; From 8e41b9857fbe288a555372c96c1f940b6cd3faa3 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 13 Jun 2024 10:41:17 +0200 Subject: [PATCH 10/14] Fix Revive in double battles (#4784) --- src/battle_script_commands.c | 1 + test/battle/item_effect/revive.c | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 44825a54f0..470229d14f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -16073,6 +16073,7 @@ void BS_ItemRestoreHP(void) if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && battler != MAX_BATTLERS_COUNT) { gAbsentBattlerFlags &= ~gBitTable[battler]; + gBattleMons[battler].hp = hp; gBattleCommunication[MULTIUSE_STATE] = TRUE; } gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/battle/item_effect/revive.c b/test/battle/item_effect/revive.c index eac16899a6..e113b6ae33 100644 --- a/test/battle/item_effect/revive.c +++ b/test/battle/item_effect/revive.c @@ -73,4 +73,30 @@ SINGLE_BATTLE_TEST("Max Honey restores a fainted battler's HP fully") } } +// Note: this test is oddly specific with implicit moves/speeds, because I had errors/invalids without them. +DOUBLE_BATTLE_TEST("Revive works for a partner in a double battle") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(200); Moves(MOVE_IRON_DEFENSE, MOVE_CELEBRATE); Speed(5); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(4); } + OPPONENT(SPECIES_ABRA) { Speed(3); Moves(MOVE_TACKLE, MOVE_PSYCHIC, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KADABRA) { Speed(2); Moves(MOVE_TACKLE, MOVE_PSYCHIC, MOVE_CELEBRATE, MOVE_EXPLOSION); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_PSYCHIC, target:playerLeft); MOVE(playerLeft, MOVE_CELEBRATE); } // Wynaut faints + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); MOVE(opponentRight, MOVE_PSYCHIC, target:playerRight); } // Wynaut gets revived, Wobb faints + // Wynaut is functionally back + TURN { MOVE(opponentLeft, MOVE_TACKLE, target:playerLeft); } + TURN { MOVE(opponentRight, MOVE_TACKLE, target:playerLeft); } + TURN { MOVE(opponentRight, MOVE_EXPLOSION); } // Everyone dies, the test can finish. + } SCENE { + MESSAGE("Wynaut fainted!"); + MESSAGE("You used Revive!"); + // Switch-in animation + MESSAGE("Wobbuffet fainted!"); + HP_BAR(playerLeft); + HP_BAR(playerLeft); + } +} + TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted") From 349b402f1b4bf6b2c11e5495ad0020d8b2acd39a Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 13 Jun 2024 10:57:20 +0200 Subject: [PATCH 11/14] Fix Soul-Heart changing stats of wrong battler (#4785) --- data/battle_scripts_1.s | 2 +- test/battle/hold_effect/mirror_herb.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index be64975390..93a59090d0 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8337,7 +8337,7 @@ BattleScript_ScriptingAbilityStatRaise:: call BattleScript_AbilityPopUp copybyte sSAVED_DMG, gBattlerAttacker copybyte gBattlerAttacker, sBATTLER - statbuffchange STAT_CHANGE_NOT_PROTECT_AFFECTED | MOVE_EFFECT_CERTAIN, NULL + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_NOT_PROTECT_AFFECTED | MOVE_EFFECT_CERTAIN, NULL setgraphicalstatchangevalues playanimation BS_SCRIPTING, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1 waitanimation diff --git a/test/battle/hold_effect/mirror_herb.c b/test/battle/hold_effect/mirror_herb.c index da335b7ac5..135ec270b3 100644 --- a/test/battle/hold_effect/mirror_herb.c +++ b/test/battle/hold_effect/mirror_herb.c @@ -48,3 +48,28 @@ SINGLE_BATTLE_TEST("Mirror Herb copies all of Stuff Cheeks' stat boosts") EXPECT_EQ(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]); } } + +DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat raise") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); } + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_SOUL_HEART); } // Raises Sp. Atk after fainting am on + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_TACKLE, target:opponentLeft); } + } SCENE { + MESSAGE("Wynaut used Tackle!"); + MESSAGE("Foe Wobbuffet fainted!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet used its Mirror Herb to mirror its opponent's stat changes!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } + } + THEN { + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} From 2b64546a252e6cf8e06f7780d8ea72fb82ea5342 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 13 Jun 2024 13:45:11 +0200 Subject: [PATCH 12/14] fix tectonic rage anim not clearing mon bg (#4787) --- data/battle_anim_scripts.s | 1 + 1 file changed, 1 insertion(+) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 8861dc514d..61e5d94650 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -29113,6 +29113,7 @@ Move_TECTONIC_RAGE: waitforvisualfinish call UnsetPsychicBg waitbgfadein + clearmonbg_static ANIM_ATTACKER createvisualtask AnimTask_AllBattlersVisible, 0xA waitforvisualfinish end From 8ff1cbed2a3784f1460c1e07fc4af33b23b88bf2 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 13 Jun 2024 13:45:56 +0200 Subject: [PATCH 13/14] update shiny data for battle mons (#4788) --- src/battle_controllers.c | 1 + src/battle_gfx_sfx_util.c | 1 + src/pokemon.c | 1 + 3 files changed, 3 insertions(+) diff --git a/src/battle_controllers.c b/src/battle_controllers.c index b2ae3039df..c4acc1691a 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1601,6 +1601,7 @@ static u32 GetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId, u8 * battleMon.abilityNum = GetMonData(&party[monId], MON_DATA_ABILITY_NUM); battleMon.otId = GetMonData(&party[monId], MON_DATA_OT_ID); battleMon.metLevel = GetMonData(&party[monId], MON_DATA_MET_LEVEL); + battleMon.isShiny = GetMonData(&party[monId], MON_DATA_IS_SHINY); GetMonData(&party[monId], MON_DATA_NICKNAME, nickname); StringCopy_Nickname(battleMon.nickname, nickname); GetMonData(&party[monId], MON_DATA_OT_NAME, battleMon.otName); diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 801e2f5fc5..9970be2e65 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -595,6 +595,7 @@ void BattleLoadMonSpriteGfx(struct Pokemon *mon, u32 battler) if (B_TRANSFORM_SHINY >= GEN_4) { currentPersonality = gTransformedPersonalities[battler]; + isShiny = gTransformedShininess[battler]; } else { diff --git a/src/pokemon.c b/src/pokemon.c index e33f55472a..240dab1322 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3394,6 +3394,7 @@ void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst) dst->type1 = gSpeciesInfo[dst->species].types[0]; dst->type2 = gSpeciesInfo[dst->species].types[1]; dst->type3 = TYPE_MYSTERY; + dst->isShiny = IsMonShiny(src); dst->ability = GetAbilityBySpecies(dst->species, dst->abilityNum); GetMonData(src, MON_DATA_NICKNAME, nickname); StringCopy_Nickname(dst->nickname, nickname); From c2ca22d4740ef2b5a838d75d5665468d2eccb84f Mon Sep 17 00:00:00 2001 From: sneed <56992013+Sneed69@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:52:13 +0300 Subject: [PATCH 14/14] Various small doubles AI fixes (#4786) * fix AI thinking defiant procs off ally moves * AI see's partner's ability for swagger and flatter --- src/battle_ai_main.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 5dc9ab23ae..2aa8f71fa7 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2891,20 +2891,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_PLUS(DECENT_EFFECT); } break; - case ABILITY_DEFIANT: - if (IsStatLoweringEffect(effect) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)) - { - RETURN_SCORE_PLUS(WEAK_EFFECT); - } - break; - case ABILITY_COMPETITIVE: - if (IsStatLoweringEffect(effect) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_SPATK)) - { - RETURN_SCORE_PLUS(WEAK_EFFECT); - } - break; } } // ability checks @@ -2922,7 +2908,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SWAGGER: if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] < MAX_STAT_STAGE && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) - && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility) || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) { @@ -2932,7 +2918,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FLATTER: if (gBattleMons[battlerAtkPartner].statStages[STAT_SPATK] < MAX_STAT_STAGE && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL) - && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, TRUE) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility) || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) {