diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index a3b800aa71..a28613e32b 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1654,11 +1654,11 @@ .macro trygulpmissile callnative BS_TryGulpMissile .endm - + .macro tryactivategulpmissile callnative BS_TryActivateGulpMissile .endm - + .macro tryquash failInstr:req callnative BS_TryQuash .4byte \failInstr @@ -2228,10 +2228,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_anim_scripts.s b/data/battle_anim_scripts.s index 536c7854cb..0c0ede09e3 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -28263,6 +28263,7 @@ Move_TECTONIC_RAGE:: waitforvisualfinish call UnsetPsychicBg waitbgfadein + clearmonbg_static ANIM_ATTACKER createvisualtask AnimTask_AllBattlersVisible, 0xA waitforvisualfinish end diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index fe7c5b6349..87fd56fef8 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -804,10 +804,6 @@ BattleScript_FlingMissed: ppreduce goto BattleScript_MoveMissedPause -BattleScript_EffectShellSideArm:: - shellsidearmcheck - goto BattleScript_EffectHit - BattleScript_EffectPhotonGeyser:: setphotongeysercategory goto BattleScript_EffectHit @@ -8468,7 +8464,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/include/battle.h b/include/battle.h index 4e7840ce05..6f105fa54c 100644 --- a/include/battle.h +++ b/include/battle.h @@ -826,6 +826,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]; u8 boosterEnergyActivates; u8 distortedTypeMatchups; }; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 6982b0c339..071084fb5d 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -808,7 +808,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 543cfdaecc..9ab5275d88 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -164,7 +164,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); @@ -216,6 +216,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 34a6d9bb4d..e69987b442 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 3435537681..18d1108431 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -206,35 +206,34 @@ #define VARIOUS_JUMP_IF_WEATHER_AFFECTED 114 #define VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED 115 #define VARIOUS_SET_ATTACKER_STICKY_WEB_USER 116 -#define VARIOUS_SHELL_SIDE_ARM_CHECK 117 -#define VARIOUS_TRY_NO_RETREAT 118 -#define VARIOUS_TRY_TAR_SHOT 119 -#define VARIOUS_CAN_TAR_SHOT_WORK 120 -#define VARIOUS_CHECK_POLTERGEIST 121 -#define VARIOUS_CUT_1_3_HP_RAISE_STATS 122 -#define VARIOUS_TRY_END_NEUTRALIZING_GAS 123 -#define VARIOUS_JUMP_IF_UNDER_200 124 -#define VARIOUS_SET_SKY_DROP 125 -#define VARIOUS_CLEAR_SKY_DROP 126 -#define VARIOUS_SKY_DROP_YAWN 127 -#define VARIOUS_CURE_CERTAIN_STATUSES 128 -#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 129 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 130 -#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 131 -#define VARIOUS_SAVE_BATTLER_ITEM 132 -#define VARIOUS_RESTORE_BATTLER_ITEM 133 -#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 134 -#define VARIOUS_SET_BEAK_BLAST 135 -#define VARIOUS_SWAP_SIDE_STATUSES 136 -#define VARIOUS_SWAP_STATS 137 -#define VARIOUS_TEATIME_INVUL 138 -#define VARIOUS_TEATIME_TARGETS 139 -#define VARIOUS_TRY_WIND_RIDER_POWER 140 -#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 141 -#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 142 -#define VARIOUS_STORE_HEALING_WISH 143 -#define VARIOUS_HIT_SWITCH_TARGET_FAILED 144 -#define VARIOUS_TRY_REVIVAL_BLESSING 145 +#define VARIOUS_TRY_NO_RETREAT 117 +#define VARIOUS_TRY_TAR_SHOT 118 +#define VARIOUS_CAN_TAR_SHOT_WORK 119 +#define VARIOUS_CHECK_POLTERGEIST 120 +#define VARIOUS_CUT_1_3_HP_RAISE_STATS 121 +#define VARIOUS_TRY_END_NEUTRALIZING_GAS 122 +#define VARIOUS_JUMP_IF_UNDER_200 123 +#define VARIOUS_SET_SKY_DROP 124 +#define VARIOUS_CLEAR_SKY_DROP 125 +#define VARIOUS_SKY_DROP_YAWN 126 +#define VARIOUS_CURE_CERTAIN_STATUSES 127 +#define VARIOUS_TRY_RESET_NEGATIVE_STAT_STAGES 128 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY 129 +#define VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT 130 +#define VARIOUS_SAVE_BATTLER_ITEM 131 +#define VARIOUS_RESTORE_BATTLER_ITEM 132 +#define VARIOUS_BATTLER_ITEM_TO_LAST_USED_ITEM 133 +#define VARIOUS_SET_BEAK_BLAST 134 +#define VARIOUS_SWAP_SIDE_STATUSES 135 +#define VARIOUS_SWAP_STATS 136 +#define VARIOUS_TEATIME_INVUL 137 +#define VARIOUS_TEATIME_TARGETS 138 +#define VARIOUS_TRY_WIND_RIDER_POWER 139 +#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 140 +#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 141 +#define VARIOUS_STORE_HEALING_WISH 142 +#define VARIOUS_HIT_SWITCH_TARGET_FAILED 143 +#define VARIOUS_TRY_REVIVAL_BLESSING 144 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h index b91045fc48..bebda44834 100644 --- a/include/constants/pokemon.h +++ b/include/constants/pokemon.h @@ -290,8 +290,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_USE_MOVE_TWENTY_TIMES 47 // Pokémon levels up after having used a move for at least 20 times #define EVO_RECOIL_DAMAGE_MALE 48 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a male #define EVO_RECOIL_DAMAGE_FEMALE 49 // Pokémon levels up after having suffered specified amount of non-fainting recoil damage as a female @@ -307,6 +307,7 @@ #define EVO_MODE_ITEM_CHECK 4 // 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 5 #define EVO_MODE_OVERWORLD_SPECIAL 6 +#define EVO_MODE_BATTLE_ONLY 7 // 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/include/random.h b/include/random.h index c399ae3cf2..24b5b7bdf2 100644 --- a/include/random.h +++ b/include/random.h @@ -188,6 +188,8 @@ enum RandomTag RNG_QUICK_CLAW, RNG_TRACE, RNG_FICKLE_BEAM, + RNG_AI_ABILITY, + RNG_SHELL_SIDE_ARM, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 09010c847f..c88825211d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -972,6 +972,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 @@ -2913,20 +2926,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 @@ -2950,7 +2949,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)) { @@ -2960,7 +2959,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)) { diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index f589197bc3..731084731b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -521,8 +521,9 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u } 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(); // Temporarily enable other gimmicks for damage calcs if planned @@ -917,17 +918,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. @@ -1198,6 +1200,8 @@ u32 AI_GetBattlerAbility(u32 battler) // does NOT include ability suppression checks s32 AI_DecideKnownAbilityForTurn(u32 battlerId) { + u32 validAbilities[NUM_ABILITY_SLOTS]; + u8 i, numValidAbilities = 0; u32 knownAbility = AI_GetBattlerAbility(battlerId); // We've had ability overwritten by e.g. Worry Seed. It is not part of AI_PARTY in case of switching @@ -1219,18 +1223,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/src/battle_controllers.c b/src/battle_controllers.c index 2022b013fe..1f9cf0d7f6 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1602,6 +1602,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 4a3c3c50de..586473781f 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -585,6 +585,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/battle_main.c b/src/battle_main.c index 3168b95b3a..4b0cc44dac 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3420,7 +3420,7 @@ 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; } @@ -3905,6 +3905,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) @@ -3997,6 +3998,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; @@ -5600,9 +5602,9 @@ static void TryEvolvePokemon(void) sTriedEvolving |= gBitTable[i]; 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/battle_script_commands.c b/src/battle_script_commands.c index d576f4c15e..a44690e85f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1400,7 +1400,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 @@ -1456,7 +1456,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); @@ -1471,7 +1471,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); @@ -1961,6 +1961,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; } @@ -7023,8 +7025,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); + } } } } @@ -10500,44 +10506,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); @@ -13916,7 +13884,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. @@ -16187,6 +16155,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/src/battle_util.c b/src/battle_util.c index 845ff657d3..3d724a81b8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8343,7 +8343,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; @@ -8359,59 +8359,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 @@ -11566,6 +11566,54 @@ void RemoveBattlerType(u32 battler, u8 type) } } +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; + } + } +} + bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) { return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 6f503e4f42..0b23c30817 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 a1fd2f18f8..f627e01396 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -18079,7 +18079,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/src/data/pokemon/species_info/gen_2_families.h b/src/data/pokemon/species_info/gen_2_families.h index 67177d85b1..d9b9339d0f 100644 --- a/src/data/pokemon/species_info/gen_2_families.h +++ b/src/data/pokemon/species_info/gen_2_families.h @@ -4907,6 +4907,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 997ae5f5f5..0592d5b5fe 100644 --- a/src/data/pokemon/species_info/gen_5_families.h +++ b/src/data/pokemon/species_info/gen_5_families.h @@ -9768,6 +9768,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 216e25cc74..df0b4be2bd 100644 --- a/src/data/pokemon/species_info/gen_7_families.h +++ b/src/data/pokemon/species_info/gen_7_families.h @@ -1380,7 +1380,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, @@ -1444,7 +1444,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, @@ -1500,7 +1500,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, @@ -1556,7 +1556,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 b623c4bb3b..6eabb6b0e9 100644 --- a/src/data/pokemon/species_info/gen_9_families.h +++ b/src/data/pokemon/species_info/gen_9_families.h @@ -2550,6 +2550,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, @@ -2604,6 +2606,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, @@ -4396,6 +4400,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4451,6 +4456,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_HP = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4506,6 +4512,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4563,6 +4570,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, @@ -4619,6 +4627,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4673,6 +4682,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4728,6 +4738,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Defense = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4783,6 +4794,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Speed = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4838,6 +4850,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 50, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4893,6 +4906,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -4949,6 +4963,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5005,6 +5020,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 30, .expYield = 285, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5604,6 +5620,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5660,6 +5677,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5826,6 +5844,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 5, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -5880,6 +5899,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 5, .expYield = 295, .evYield_Attack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6380,6 +6400,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Defense = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6435,6 +6456,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6490,6 +6512,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_Speed = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, @@ -6544,6 +6567,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .catchRate = 10, .expYield = 295, .evYield_SpAttack = 3, + .itemRare = ITEM_BOOSTER_ENERGY, .genderRatio = MON_GENDERLESS, .eggCycles = 50, .friendship = 0, diff --git a/src/pokemon.c b/src/pokemon.c index 36d44eded2..4f90201602 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3666,6 +3666,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); @@ -4448,6 +4449,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); @@ -4536,11 +4538,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: 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/battle/ai.c b/test/battle/ai.c index 7eaf88f629..48eee5c051 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -555,8 +555,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); @@ -564,6 +565,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!"); } } @@ -715,6 +737,45 @@ 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; + + 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); } + } +} + AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the highest expected damage") { u32 flags; 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); + } +} 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") 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"); 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); + } +} diff --git a/test/species.c b/test/species.c index 3755f97f5c..68f13c0917 100644 --- a/test/species.c +++ b/test/species.c @@ -94,3 +94,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); +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index b2cc4725ce..23433502fe 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)++;