Prevent double Dynamax for single-trainer 2v1 multi battles (#8323)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
moostoet 2025-11-30 12:44:55 +01:00 committed by GitHub
parent 6daab57bd5
commit bcf90b71a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 45 additions and 21 deletions

View File

@ -640,6 +640,7 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
u32 battlerAtk, battlersCount, weather;
memset(aiData, 0, sizeof(struct AiLogicData));
gAiBattleData->aiUsingGimmick = 0;
if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart())
return;

View File

@ -1351,6 +1351,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
if (gAiLogicData->shouldSwitch & (1u << battler) && IsSwitchinValid(battler))
{
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_SWITCH, 0);
SetAIUsingGimmick(battler, NO_GIMMICK);
if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE)
{
s32 monToSwitchId = gAiLogicData->mostSuitableMonId[battler];
@ -1397,6 +1398,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
}
else if (ShouldUseItem(battler))
{
SetAIUsingGimmick(battler, NO_GIMMICK);
return;
}
}

View File

@ -462,12 +462,15 @@ static void OpponentHandleChooseMove(u32 battler)
gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT);
}
// If opponent can and should use a gimmick (considering trainer data), do it
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE && IsAIUsingGimmick(battler))
enum Gimmick usableGimmick = gBattleStruct->gimmick.usableGimmick[battler];
if (usableGimmick != GIMMICK_NONE && IsAIUsingGimmick(battler) && !HasTrainerUsedGimmick(battler, usableGimmick))
{
gBattleStruct->gimmick.toActivate |= 1u << battler;
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (RET_GIMMICK) | (gBattlerTarget << 8));
}
else
{
SetAIUsingGimmick(battler, NO_GIMMICK);
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (gBattlerTarget << 8));
}
}

View File

@ -269,12 +269,15 @@ static void PlayerPartnerHandleChooseMove(u32 battler)
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
// If partner can and should use a gimmick (considering trainer data), do it
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE && IsAIUsingGimmick(battler))
enum Gimmick usableGimmick = gBattleStruct->gimmick.usableGimmick[battler];
if (usableGimmick != GIMMICK_NONE && IsAIUsingGimmick(battler) && !HasTrainerUsedGimmick(battler, usableGimmick))
{
gBattleStruct->gimmick.toActivate |= 1u << battler;
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (RET_GIMMICK) | (gBattlerTarget << 8));
}
else
{
SetAIUsingGimmick(battler, NO_GIMMICK);
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (gBattlerTarget << 8));
}

View File

@ -91,20 +91,15 @@ bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick)
// Returns whether a trainer has used a gimmick during a battle.
bool32 HasTrainerUsedGimmick(u32 battler, enum Gimmick gimmick)
{
// Check whether partner battler has used gimmick or plans to during turn.
if (IsDoubleBattle()
&& IsPartnerMonFromSameTrainer(battler)
&& (gBattleStruct->gimmick.activated[BATTLE_PARTNER(battler)][gimmick]
|| ((gBattleStruct->gimmick.toActivate & (1u << BATTLE_PARTNER(battler))
&& gBattleStruct->gimmick.usableGimmick[BATTLE_PARTNER(battler)] == gimmick))))
if (IsDoubleBattle() && IsPartnerMonFromSameTrainer(battler))
{
return TRUE;
}
// Otherwise, return whether current battler has used gimmick.
else
{
return gBattleStruct->gimmick.activated[battler][gimmick];
u32 partner = BATTLE_PARTNER(battler);
if (gBattleStruct->gimmick.activated[partner][gimmick]
|| ((gBattleStruct->gimmick.toActivate & (1u << partner)) && gBattleStruct->gimmick.usableGimmick[partner] == gimmick))
return TRUE;
}
return gBattleStruct->gimmick.activated[battler][gimmick];
}
// Sets a gimmick as used by a trainer with checks for Multi Battles.

View File

@ -8938,14 +8938,10 @@ s32 GetStealthHazardDamage(enum TypeSideHazard hazardType, u32 battler)
bool32 IsPartnerMonFromSameTrainer(u32 battler)
{
if (!IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
return FALSE;
else if (IsOnPlayerSide(battler) && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
return FALSE;
else if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
return FALSE;
if (!IsOnPlayerSide(battler))
return !(gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS);
else
return TRUE;
return !(gBattleTypeFlags & BATTLE_TYPE_MULTI);
}
bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId)

View File

@ -37,5 +37,29 @@ AI_SINGLE_BATTLE_TEST("AI uses Dynamax -- AI does not dynamax before using a uti
}
}
AI_TWO_VS_ONE_BATTLE_TEST("AI only Dynamaxes once per trainer in 2v1 multi battles")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
MULTI_PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH); }
MULTI_PARTNER(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH); }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH); DynamaxLevel(10); }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH); DynamaxLevel(10); }
} WHEN {
TURN {
MOVE(playerLeft, MOVE_SPLASH);
MOVE(playerRight, MOVE_SPLASH);
EXPECT_MOVE(opponentLeft, MOVE_SPLASH, gimmick: GIMMICK_DYNAMAX);
EXPECT_MOVE(opponentRight, MOVE_SPLASH, gimmick: GIMMICK_NONE);
}
TURN {
MOVE(playerLeft, MOVE_SPLASH);
MOVE(playerRight, MOVE_SPLASH);
EXPECT_MOVE(opponentLeft, MOVE_SPLASH, gimmick: GIMMICK_NONE);
EXPECT_MOVE(opponentRight, MOVE_SPLASH, gimmick: GIMMICK_NONE);
}
}
}
// Copycatting an ally's Max Guard rendition of Trick Room was a notable strategy.
TO_DO_BATTLE_TEST("TODO: AI uses Dynamax -- AI uses Copycat against a Dynamaxed Pokemon intelligently")