From 0ca588943b47606b6abd91c757b0b27e9001f518 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:45:35 +0100 Subject: [PATCH] Fixes choiced moves not locked in after ability block (#5738) --- include/battle.h | 2 +- include/battle_util.h | 1 + src/battle_script_commands.c | 40 -------------------------- src/battle_util.c | 56 ++++++++++++++++++++++++++++++++---- test/battle/ai/ai.c | 25 ++++++++++++++++ 5 files changed, 77 insertions(+), 47 deletions(-) diff --git a/include/battle.h b/include/battle.h index 20afe876ca3a..d32481ee4d5c 100644 --- a/include/battle.h +++ b/include/battle.h @@ -802,8 +802,8 @@ struct BattleStruct u8 categoryOverride; // for Z-Moves and Max Moves u32 stellarBoostFlags[NUM_BATTLE_SIDES]; // stored as a bitfield of flags for all types for each side u8 fickleBeamBoosted:1; - u8 obedienceResult:3; u8 redCardActivates:1; + u8 padding:6; u8 usedMicleBerry; }; diff --git a/include/battle_util.h b/include/battle_util.h index 71d81ce3bb18..0d73a0a9ddd6 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -82,6 +82,7 @@ enum CANCELLER_SKY_DROP, CANCELLER_ASLEEP, CANCELLER_FROZEN, + CANCELLER_OBEDIENCE, CANCELLER_TRUANT, CANCELLER_RECHARGE, CANCELLER_FLINCH, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 0375cfeed016..ede9c0733236 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1282,46 +1282,6 @@ static void Cmd_attackcanceler(void) gHitMarker &= ~HITMARKER_ALLOW_NO_PP; - if (!(gHitMarker & HITMARKER_OBEYS) && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) - { - switch (gBattleStruct->obedienceResult) - { - case OBEYS: - break; - case DISOBEYS_LOAFS: - // Randomly select, then print a disobedient string - // B_MSG_LOAFING, B_MSG_WONT_OBEY, B_MSG_TURNED_AWAY, or B_MSG_PRETEND_NOT_NOTICE - gBattleCommunication[MULTISTRING_CHOOSER] = MOD(Random(), NUM_LOAF_STRINGS); - gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround; - gMoveResultFlags |= MOVE_RESULT_MISSED; - return; - case DISOBEYS_HITS_SELF: - gBattlerTarget = gBattlerAttacker; - gBattleMoveDamage = CalculateMoveDamage(MOVE_NONE, gBattlerAttacker, gBattlerAttacker, TYPE_MYSTERY, 40, FALSE, FALSE, TRUE); - gBattlescriptCurrInstr = BattleScript_IgnoresAndHitsItself; - gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; - gHitMarker |= HITMARKER_OBEYS; - return; - case DISOBEYS_FALL_ASLEEP: - gBattlescriptCurrInstr = BattleScript_IgnoresAndFallsAsleep; - gMoveResultFlags |= MOVE_RESULT_MISSED; - return; - case DISOBEYS_WHILE_ASLEEP: - gBattlescriptCurrInstr = BattleScript_IgnoresWhileAsleep; - gMoveResultFlags |= MOVE_RESULT_MISSED; - return; - case DISOBEYS_RANDOM_MOVE: - gCalledMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos]; - SetAtkCancellerForCalledMove(); - gBattlescriptCurrInstr = BattleScript_IgnoresAndUsesRandomMove; - gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); - gHitMarker |= HITMARKER_DISOBEDIENT_MOVE; - gHitMarker |= HITMARKER_OBEYS; - return; - } - } - - gHitMarker |= HITMARKER_OBEYS; // Check if no available target present on the field or if Sky Battles ban the move if ((NoTargetPresent(gBattlerAttacker, gCurrentMove) && (!gBattleMoveEffects[gMovesInfo[gCurrentMove].effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS))) diff --git a/src/battle_util.c b/src/battle_util.c index f95b8f990197..6f0b2af880fa 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -145,8 +145,6 @@ void HandleAction_UseMove(void) gBattleScripting.savedMoveEffect = 0; gCurrMovePos = gChosenMovePos = *(gBattleStruct->chosenMovePositions + gBattlerAttacker); - gBattleStruct->obedienceResult = GetAttackerObedienceForAction(); - // choose move if (gProtectStructs[gBattlerAttacker].noValidMoves) { @@ -3222,7 +3220,8 @@ void SetAtkCancellerForCalledMove(void) u8 AtkCanceller_UnableToUseMove(u32 moveType) { - u8 effect = 0; + u32 effect = 0; + u32 obedienceResult; do { switch (gBattleStruct->atkCancellerTracker) @@ -3306,6 +3305,53 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) } gBattleStruct->atkCancellerTracker++; break; + case CANCELLER_OBEDIENCE: + obedienceResult = GetAttackerObedienceForAction(); + if (obedienceResult != OBEYS + && !(gHitMarker & HITMARKER_NO_PPDEDUCT) // Don't check obedience after first hit of multi target move or multi hit moves + && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) + { + switch (obedienceResult) + { + case DISOBEYS_LOAFS: + // Randomly select, then print a disobedient string + // B_MSG_LOAFING, B_MSG_WONT_OBEY, B_MSG_TURNED_AWAY, or B_MSG_PRETEND_NOT_NOTICE + gBattleCommunication[MULTISTRING_CHOOSER] = MOD(Random(), NUM_LOAF_STRINGS); + gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround; + gMoveResultFlags |= MOVE_RESULT_MISSED; + break; + case DISOBEYS_HITS_SELF: + gBattlerTarget = gBattlerAttacker; + gBattleMoveDamage = CalculateMoveDamage(MOVE_NONE, gBattlerAttacker, gBattlerAttacker, TYPE_MYSTERY, 40, FALSE, FALSE, TRUE); + gBattlescriptCurrInstr = BattleScript_IgnoresAndHitsItself; + gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; + gHitMarker |= HITMARKER_OBEYS; + break; + case DISOBEYS_FALL_ASLEEP: + gBattlescriptCurrInstr = BattleScript_IgnoresAndFallsAsleep; + gMoveResultFlags |= MOVE_RESULT_MISSED; + break; + case DISOBEYS_WHILE_ASLEEP: + gBattlescriptCurrInstr = BattleScript_IgnoresWhileAsleep; + gMoveResultFlags |= MOVE_RESULT_MISSED; + break; + case DISOBEYS_RANDOM_MOVE: + gCalledMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos]; + SetAtkCancellerForCalledMove(); + gBattlescriptCurrInstr = BattleScript_IgnoresAndUsesRandomMove; + gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); + gHitMarker |= HITMARKER_DISOBEDIENT_MOVE; + gHitMarker |= HITMARKER_OBEYS; + break; + } + effect = 1; + } + else + { + gHitMarker |= HITMARKER_OBEYS; + } + gBattleStruct->atkCancellerTracker++; + break; case CANCELLER_TRUANT: // truant if (GetBattlerAbility(gBattlerAttacker) == ABILITY_TRUANT && gDisableStructs[gBattlerAttacker].truantCounter) { @@ -3554,7 +3600,6 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE - || gBattleStruct->obedienceResult != OBEYS || HasTrainerUsedGimmick(gBattlerAttacker, GIMMICK_Z_MOVE)) gBattlescriptCurrInstr = BattleScript_MoveUsedPowder; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; @@ -3575,8 +3620,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_Z_MOVES: - if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE - && gBattleStruct->obedienceResult == OBEYS) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) { // For Z-Mirror Move, so it doesn't play the animation twice. bool32 alreadyUsed = HasTrainerUsedGimmick(gBattlerAttacker, GIMMICK_Z_MOVE); diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index a19481c7ed3a..b52e64262dd1 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -805,3 +805,28 @@ AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the NOT MESSAGE("Wobbuffet fainted!"); } } + +AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player's ability disabling them") +{ + u32 playerMon, ability, aiMove; + PARAMETRIZE { ability = ABILITY_DAZZLING; playerMon = SPECIES_BRUXISH; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_QUEENLY_MAJESTY; playerMon = SPECIES_TSAREENA; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_ARMOR_TAIL; playerMon = SPECIES_FARIGIRAF; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; playerMon = SPECIES_EXPLOUD; aiMove = MOVE_BOOMBURST; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; playerMon = SPECIES_CHESNAUGHT; aiMove = MOVE_BULLET_SEED; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1); + ASSUME(gMovesInfo[MOVE_BOOMBURST].soundMove == TRUE); + ASSUME(gMovesInfo[MOVE_BULLET_SEED].ballisticMove == TRUE); + ASSUME(gMovesInfo[MOVE_TAIL_WHIP].category == DAMAGE_CATEGORY_STATUS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(playerMon) { Ability(ability); } + OPPONENT(SPECIES_SMEARGLE) { Item(ITEM_CHOICE_BAND); Moves(aiMove, MOVE_TACKLE); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, aiMove); } + TURN { EXPECT_MOVE(opponent, aiMove); } + } +}