mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-04-26 10:25:49 -05:00
Fix doubles moves bad / choice lock bad switch AI (#9078)
This commit is contained in:
parent
109a3cd9e7
commit
3c08fac37b
|
|
@ -441,43 +441,66 @@ static u32 FindMonWithMoveOfEffectiveness(enum BattlerId battler, enum BattlerId
|
|||
return FALSE; // There is not a single Pokémon in the party that has a move with this effectiveness threshold
|
||||
}
|
||||
|
||||
static bool32 CanMoveAffectTarget(struct BattleContext *ctx, u32 moveIndex)
|
||||
{
|
||||
if (ctx->move != MOVE_NONE
|
||||
&& gAiLogicData->effectiveness[ctx->battlerAtk][ctx->battlerDef][moveIndex] > UQ_4_12(0.0)
|
||||
&& !AI_CanMoveBeBlockedByTarget(ctx))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 IsMoveBad(struct BattleContext *ctx, u32 moveIndex)
|
||||
{
|
||||
if (CanMoveAffectTarget(ctx, moveIndex))
|
||||
return FALSE;
|
||||
if (!ALL_MOVES_BAD_STATUS_MOVES_BAD || GetMovePower(ctx->move) != 0) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static bool32 ShouldSwitchIfAllMovesBad(enum BattlerId battler)
|
||||
{
|
||||
u32 moveIndex;
|
||||
enum BattlerId opposingBattler = GetOppositeBattler(battler);
|
||||
enum Move aiMove;
|
||||
struct BattleContext ctx = {0};
|
||||
ctx.battlerAtk = battler;
|
||||
ctx.battlerDef = opposingBattler;
|
||||
ctx.abilityAtk = gAiLogicData->abilities[ctx.battlerAtk];
|
||||
ctx.abilityDef = gAiLogicData->abilities[ctx.battlerDef];
|
||||
ctx.holdEffectAtk = gAiLogicData->holdEffects[ctx.battlerAtk];
|
||||
ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef];
|
||||
|
||||
// Switch if no moves affect opponents
|
||||
if (IsDoubleBattle())
|
||||
if (HasTwoOpponents(battler))
|
||||
{
|
||||
enum BattlerId opposingPartner = BATTLE_PARTNER(opposingBattler);
|
||||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
aiMove = gBattleMons[battler].moves[moveIndex];
|
||||
if (aiMove == MOVE_NONE)
|
||||
continue;
|
||||
if (gAiLogicData->effectiveness[battler][opposingBattler][moveIndex] > UQ_4_12(0.0)
|
||||
|| gAiLogicData->effectiveness[battler][opposingPartner][moveIndex] > UQ_4_12(0.0))
|
||||
ctx.move = ctx.chosenMove = gBattleMons[battler].moves[moveIndex];
|
||||
ctx.moveType = GetBattleMoveType(ctx.move);
|
||||
// Check if move is bad in the context of both opposing battlers
|
||||
if (!IsMoveBad(&ctx, moveIndex))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set partner data in ctx
|
||||
ctx.battlerDef = opposingPartner;
|
||||
ctx.abilityDef = gAiLogicData->abilities[ctx.battlerDef];
|
||||
ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef];
|
||||
if (!IsMoveBad(&ctx, moveIndex))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct BattleContext ctx = {0};
|
||||
ctx.battlerAtk = battler;
|
||||
ctx.battlerDef = opposingBattler;
|
||||
ctx.abilityAtk = gAiLogicData->abilities[ctx.battlerAtk];
|
||||
ctx.abilityDef = gAiLogicData->abilities[ctx.battlerDef];
|
||||
ctx.holdEffectAtk = gAiLogicData->holdEffects[ctx.battlerAtk];
|
||||
ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef];
|
||||
|
||||
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
aiMove = gBattleMons[battler].moves[moveIndex];
|
||||
if (aiMove != MOVE_NONE
|
||||
&& gAiLogicData->effectiveness[battler][opposingBattler][moveIndex] > UQ_4_12(0.0)
|
||||
&& !AI_CanMoveBeBlockedByTarget(&ctx)
|
||||
&& (!ALL_MOVES_BAD_STATUS_MOVES_BAD || GetMovePower(aiMove) != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero
|
||||
ctx.move = ctx.chosenMove = gBattleMons[battler].moves[moveIndex];
|
||||
ctx.moveType = GetBattleMoveType(ctx.move);
|
||||
if (!IsMoveBad(&ctx, moveIndex))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -975,7 +998,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(enum BattlerId battler)
|
|||
if (CanUseSuperEffectiveMoveAgainstOpponent(battler, opposingBattler))
|
||||
return TRUE;
|
||||
|
||||
if (IsDoubleBattle() && CanUseSuperEffectiveMoveAgainstOpponent(battler, BATTLE_PARTNER(BATTLE_OPPOSITE(battler))))
|
||||
if (HasTwoOpponents(battler) && CanUseSuperEffectiveMoveAgainstOpponent(battler, BATTLE_PARTNER(BATTLE_OPPOSITE(battler))))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
|
|
@ -1117,27 +1140,44 @@ static bool32 ShouldSwitchIfEncored(enum BattlerId battler)
|
|||
|
||||
static bool32 ShouldSwitchIfBadChoiceLock(enum BattlerId battler)
|
||||
{
|
||||
enum Move lastUsedMove = gAiLogicData->lastUsedMove[battler];
|
||||
enum Move choicedMove = gBattleStruct->choicedMove[battler];
|
||||
enum BattlerId opposingBattler = GetOppositeBattler(battler);
|
||||
bool32 moveAffectsTarget = TRUE;
|
||||
|
||||
struct BattleContext ctx = {0};
|
||||
ctx.battlerAtk = battler;
|
||||
ctx.battlerDef = opposingBattler;
|
||||
ctx.move = ctx.chosenMove = lastUsedMove;
|
||||
ctx.moveType = GetBattleMoveType(lastUsedMove);
|
||||
ctx.move = ctx.chosenMove = choicedMove;
|
||||
ctx.moveType = GetBattleMoveType(choicedMove);
|
||||
ctx.abilityAtk = gAiLogicData->abilities[ctx.battlerAtk];
|
||||
ctx.abilityDef = gAiLogicData->abilities[ctx.battlerDef];
|
||||
ctx.holdEffectAtk = gAiLogicData->holdEffects[ctx.battlerAtk];
|
||||
ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef];
|
||||
|
||||
if (lastUsedMove != MOVE_NONE
|
||||
&& (AI_GetMoveEffectiveness(lastUsedMove, battler, opposingBattler) == UQ_4_12(0.0) || AI_CanMoveBeBlockedByTarget(&ctx)))
|
||||
moveAffectsTarget = FALSE;
|
||||
// Not locked in to anything yet, or not choiced
|
||||
if (choicedMove == MOVE_NONE)
|
||||
return FALSE;
|
||||
|
||||
if (IsHoldEffectChoice(ctx.holdEffectAtk) && IsBattlerItemEnabled(battler))
|
||||
u32 moveIndex = GetMoveIndex(battler, choicedMove);
|
||||
|
||||
if (HasTwoOpponents(battler))
|
||||
{
|
||||
if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || !moveAffectsTarget) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED)))
|
||||
enum BattlerId opposingPartner = BATTLE_PARTNER(opposingBattler);
|
||||
if (IsHoldEffectChoice(ctx.holdEffectAtk) && IsBattlerItemEnabled(battler))
|
||||
{
|
||||
if (GetMoveCategory(choicedMove) == DAMAGE_CATEGORY_STATUS || !CanMoveAffectTarget(&ctx, moveIndex))
|
||||
{
|
||||
// Set partner data in ctx
|
||||
ctx.battlerDef = opposingPartner;
|
||||
ctx.abilityDef = gAiLogicData->abilities[ctx.battlerDef];
|
||||
ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef];
|
||||
if (!CanMoveAffectTarget(&ctx, moveIndex) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED)))
|
||||
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (IsHoldEffectChoice(ctx.holdEffectAtk) && IsBattlerItemEnabled(battler))
|
||||
{
|
||||
if ((GetMoveCategory(choicedMove) == DAMAGE_CATEGORY_STATUS || !CanMoveAffectTarget(&ctx, moveIndex)) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED)))
|
||||
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
|
||||
}
|
||||
|
||||
|
|
@ -1319,9 +1359,19 @@ bool32 ShouldSwitchIfAllScoresBad(enum BattlerId battler)
|
|||
|
||||
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
|
||||
{
|
||||
score = gAiBattleData->finalScore[battler][opposingBattler][moveIndex];
|
||||
if (score > AI_BAD_SCORE_THRESHOLD)
|
||||
return FALSE;
|
||||
if (HasTwoOpponents(battler))
|
||||
{
|
||||
u32 score1 = gAiBattleData->finalScore[battler][opposingBattler][moveIndex];
|
||||
u32 score2 = gAiBattleData->finalScore[battler][BATTLE_PARTNER(opposingBattler)][moveIndex];
|
||||
if (score1 > AI_BAD_SCORE_THRESHOLD || score2 > AI_BAD_SCORE_THRESHOLD)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
score = gAiBattleData->finalScore[battler][opposingBattler][moveIndex];
|
||||
if (score > AI_BAD_SCORE_THRESHOLD)
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if (RandomPercentage(RNG_AI_SWITCH_ALL_SCORES_BAD, GetSwitchChance(SHOULD_SWITCH_ALL_SCORES_BAD))
|
||||
&& (gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE || !ALL_SCORES_BAD_NEEDS_GOOD_SWITCHIN))
|
||||
|
|
@ -1347,7 +1397,8 @@ bool32 ShouldStayInToUseMove(enum BattlerId battler)
|
|||
&& !GetHitEscapeTransformState(battler, aiMove))
|
||||
continue;
|
||||
|
||||
if (gAiBattleData->finalScore[battler][opposingBattler][moveIndex] > AI_GOOD_SCORE_THRESHOLD)
|
||||
if (gAiBattleData->finalScore[battler][opposingBattler][moveIndex] > AI_GOOD_SCORE_THRESHOLD
|
||||
|| (HasTwoOpponents(battler) && gAiBattleData->finalScore[battler][BATTLE_PARTNER(opposingBattler)][moveIndex] > AI_GOOD_SCORE_THRESHOLD))
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,3 +256,30 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon will only see choiced moves when conside
|
|||
TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("Choiced Pokémon won't switch out if they can still affect one opposing Pokémon in doubles")
|
||||
{
|
||||
u32 defendingSpecies = SPECIES_NONE;
|
||||
enum Ability defendingAbility = ABILITY_NONE;
|
||||
PARAMETRIZE { defendingSpecies = SPECIES_VAPOREON; defendingAbility = ABILITY_WATER_ABSORB; }
|
||||
PARAMETRIZE { defendingSpecies = SPECIES_ZIGZAGOON; defendingAbility = SPECIES_ZIGZAGOON; }
|
||||
|
||||
PASSES_RANDOMLY(SHOULD_SWITCH_CHOICE_LOCKED_PERCENTAGE, 100, RNG_AI_SWITCH_CHOICE_LOCKED);
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_RISKY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES);
|
||||
PLAYER(SPECIES_CHARMANDER) { Level(5); Moves(MOVE_CELEBRATE); }
|
||||
PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_CELEBRATE); }
|
||||
PLAYER(defendingSpecies) { Ability(defendingAbility); SpDefense(500); Moves(MOVE_CELEBRATE); }
|
||||
OPPONENT(SPECIES_VAPOREON) { Moves(MOVE_SCALD); Item(ITEM_CHOICE_SPECS); }
|
||||
OPPONENT(SPECIES_VAPOREON) { Moves(MOVE_SCALD); Item(ITEM_CHOICE_SPECS); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON);
|
||||
OPPONENT(SPECIES_ZIGZAGOON);
|
||||
} WHEN {
|
||||
TURN { SWITCH(playerLeft, 2); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_SCALD, target:playerLeft); EXPECT_MOVE(opponentRight, MOVE_SCALD, target:playerLeft); }
|
||||
if (defendingSpecies == SPECIES_VAPOREON)
|
||||
TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_SWITCH(opponentLeft, 3); EXPECT_MOVE(opponentRight, MOVE_SCALD); }
|
||||
else
|
||||
TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_SCALD, target:playerLeft); EXPECT_MOVE(opponentRight, MOVE_SCALD, target:playerLeft); SEND_OUT(playerLeft, 0); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,6 +403,36 @@ AI_SINGLE_BATTLE_TEST("AI will switch out if it has no move that affects the pla
|
|||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will switch out if it has no moves that affect either of the player's battlers")
|
||||
{
|
||||
PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_RATTATA);
|
||||
PLAYER(SPECIES_RATTATA);
|
||||
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
|
||||
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_SCRATCH);}
|
||||
} WHEN {
|
||||
TURN { EXPECT_SWITCH(opponentLeft, 2); EXPECT_MOVE(opponentRight, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI will not switch out if it's moves can still affect one of the player's battlers")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_GASTLY);
|
||||
PLAYER(SPECIES_RATTATA);
|
||||
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
|
||||
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_SCRATCH);}
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponentLeft, MOVE_SHADOW_BALL); EXPECT_MOVE(opponentRight, MOVE_SCRATCH); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects the player, AI will send in a mon that can hit the player, even if not ideal")
|
||||
{
|
||||
GIVEN {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user