diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a61a0e3eded3..6581f5ca3855 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -441,7 +441,7 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectHit @ EFFECT_IVY_CUDGEL .4byte BattleScript_EffectMaxMove @ EFFECT_MAX_MOVE .4byte BattleScript_EffectGlaiveRush @ EFFECT_GLAIVE_RUSH - .4byte BattleScript_EffectBrickBreak @ EFFECT_RAGING_BULL + .4byte BattleScript_EffectBrickBreak @ EFFECT_RAGING_BULL BattleScript_EffectGlaiveRush:: call BattleScript_EffectHit_Ret diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index c83c47e58396..9b30d103e4b9 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -64,6 +64,7 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability); bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u32 move); u32 AI_GetBattlerMoveTargetType(u32 battlerId, u32 move); bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove); +u32 AI_CalcSecondaryEffectChance(u32 battler, u32 secondaryEffectChance); // stat stage checks bool32 AnyStatIsRaised(u32 battlerId); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 0a9e0a269aec..3be0c354103a 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3211,13 +3211,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score struct AiLogicData *aiData = AI_DATA; u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex]; + u32 secondaryEffectChance = AI_CalcSecondaryEffectChance(battlerAtk, gBattleMoves[move].secondaryEffectChance); s8 atkPriority = GetMovePriority(battlerAtk, move); u32 predictedMove = aiData->predictedMoves[battlerDef]; u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk); u32 i; - // We only check for moves that have a 20% chance or more for their secondary effect to happen because moves with a smaller chance are rather worthless. We don't want the AI to use those. - bool32 sereneGraceBoost = (aiData->abilities[battlerAtk] == ABILITY_SERENE_GRACE && (gBattleMoves[move].secondaryEffectChance >= 20 && gBattleMoves[move].secondaryEffectChance < 100)); // The AI should understand that while Dynamaxed, status moves function like Protect. if (IsDynamaxed(battlerAtk) && gBattleMoves[move].split == SPLIT_STATUS) @@ -3646,24 +3645,18 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case EFFECT_PARALYZE: IncreaseParalyzeScore(battlerAtk, battlerDef, move, &score); break; + case EFFECT_SPEED_DOWN_HIT: + if (!ShouldLowerSpeed(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + break; case EFFECT_ATTACK_DOWN_HIT: case EFFECT_DEFENSE_DOWN_HIT: case EFFECT_SPECIAL_ATTACK_DOWN_HIT: case EFFECT_SPECIAL_DEFENSE_DOWN_HIT: case EFFECT_ACCURACY_DOWN_HIT: case EFFECT_EVASION_DOWN_HIT: - if (sereneGraceBoost && aiData->abilities[battlerDef] != ABILITY_CONTRARY) + if (secondaryEffectChance >= 100 && aiData->abilities[battlerDef] != ABILITY_CONTRARY) ADJUST_SCORE(2); break; - case EFFECT_SPEED_DOWN_HIT: - if (ShouldLowerSpeed(battlerAtk, battlerDef, aiData->abilities[battlerDef])) - { - if (sereneGraceBoost && aiData->abilities[battlerDef] != ABILITY_CONTRARY) - ADJUST_SCORE(5); - else - ADJUST_SCORE(2); - } - break; case EFFECT_SUBSTITUTE: if (gStatuses3[battlerDef] & STATUS3_PERISH_SONG) ADJUST_SCORE(3); @@ -3789,7 +3782,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(1); break; case EFFECT_SPEED_UP_HIT: - if (sereneGraceBoost && aiData->abilities[battlerDef] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + if (secondaryEffectChance >= 100 && aiData->abilities[battlerDef] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) ADJUST_SCORE(3); break; case EFFECT_DESTINY_BOND: @@ -4048,11 +4041,11 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score } break; case EFFECT_ATTACK_UP_HIT: - if (sereneGraceBoost) + if (secondaryEffectChance >= 100) IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score); break; case EFFECT_SPECIAL_ATTACK_UP_HIT: - if (sereneGraceBoost) + if (secondaryEffectChance >= 100) IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score); break; case EFFECT_FELL_STINGER: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index b1cdcd583b5e..255da22cd2ff 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3806,3 +3806,11 @@ bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId) { return (gBattleMons[battlerId].status1 & STATUS1_SLEEP) || AI_DATA->abilities[battlerId] == ABILITY_COMATOSE; } + +u32 AI_CalcSecondaryEffectChance(u32 battler, u32 secondaryEffectChance) +{ + if (AI_DATA->abilities[battler] == ABILITY_SERENE_GRACE) + secondaryEffectChance *= 2; + + return secondaryEffectChance; +} diff --git a/test/battle/ai_check_viability.c b/test/battle/ai_check_viability.c index fa46e2a9f0ea..4666eca7cb05 100644 --- a/test/battle/ai_check_viability.c +++ b/test/battle/ai_check_viability.c @@ -165,3 +165,27 @@ AI_SINGLE_BATTLE_TEST("AI can choose Counter or Mirror Coat if the predicted mov MESSAGE("Foe Wobbuffet fainted!"); } } + +AI_SINGLE_BATTLE_TEST("AI chooses moves with secondary effect that have a 100% chance to trigger") +{ + u16 ability; + + PARAMETRIZE { ability = ABILITY_NONE; } + PARAMETRIZE { ability = ABILITY_SERENE_GRACE; } + + GIVEN { + AI_LOG; + ASSUME(gBattleMoves[MOVE_SHADOW_BALL].effect == EFFECT_SPECIAL_DEFENSE_DOWN_HIT); + ASSUME(gBattleMoves[MOVE_SHADOW_BALL].secondaryEffectChance == 20); + ASSUME(gBattleMoves[MOVE_LUSTER_PURGE].effect == EFFECT_SPECIAL_DEFENSE_DOWN_HIT); + ASSUME(gBattleMoves[MOVE_LUSTER_PURGE].secondaryEffectChance == 50); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_REGICE); + OPPONENT(SPECIES_REGIROCK) { Ability(ability); Moves(MOVE_SHADOW_BALL, MOVE_LUSTER_PURGE); } + } WHEN { + if (ability == ABILITY_NONE) + TURN { EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + else + TURN { EXPECT_MOVES(opponent, MOVE_LUSTER_PURGE); } + } +} diff --git a/test/battle/move_effect/relic_song.c b/test/battle/move_effect/relic_song.c index 7b14a57e7210..408a1630f7a8 100644 --- a/test/battle/move_effect/relic_song.c +++ b/test/battle/move_effect/relic_song.c @@ -160,3 +160,23 @@ SINGLE_BATTLE_TEST("Relic Song loses the form-changing effect with Sheer Force") EXPECT_EQ(player->species, SPECIES_MELOETTA_ARIA); } } + +SINGLE_BATTLE_TEST("Relic Song transforms Meloetta after Magician was activated") +{ + GIVEN { + ASSUME(P_GEN_6_POKEMON == TRUE); + PLAYER(SPECIES_MELOETTA_ARIA); + OPPONENT(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); Item(ITEM_POTION); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Meloetta stole Foe Delphox's Potion!"); + MESSAGE("Meloetta transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MELOETTA_PIROUETTE); + } +}