AI respects partner when using spread moves in double battles (#4518)

* Fixes Earthquake AI in double battles

* earthquake_ai_fix

* Use CompareDamagingMoves to fix spread damage issue
This commit is contained in:
Alex 2024-05-17 00:41:55 +02:00 committed by GitHub
parent 6f12da0a67
commit 635db6312c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 101 additions and 31 deletions

View File

@ -191,7 +191,6 @@ enum {
EFFECT_ROUND,
EFFECT_BRINE,
EFFECT_RETALIATE,
EFFECT_BULLDOZE,
EFFECT_FOUL_PLAY,
EFFECT_PSYSHOCK,
EFFECT_ROOST,

View File

@ -423,7 +423,7 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3
// Simulate dmg for both ai controlled mons and for player controlled mons.
for (battlerDef = 0; battlerDef < battlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
if (battlerAtk == battlerDef || !IsBattlerAlive(battlerDef))
continue;
SaveBattlerData(battlerDef);
@ -3028,21 +3028,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 0) || IsAbilityOfRating(aiData->abilities[battlerDef], 10))
ADJUST_SCORE(DECENT_EFFECT); // we want to transfer our bad ability or take their awesome ability
break;
case EFFECT_EARTHQUAKE:
case EFFECT_MAGNITUDE:
if (!IsBattlerGrounded(battlerAtkPartner)
|| (IsBattlerGrounded(battlerAtkPartner)
&& AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner, move) == AI_IS_SLOWER
&& IsUngroundingEffect(gMovesInfo[aiData->partnerMove].effect)))
ADJUST_SCORE(DECENT_EFFECT);
else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE)
|| IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ELECTRIC)
|| IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_POISON)
|| IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ROCK))
ADJUST_SCORE(-10); // partner will be hit by earthquake and is weak to it
else if (IsBattlerAlive(battlerAtkPartner))
ADJUST_SCORE(-3);
break;
}
// lightning rod, flash fire against enemy handled in AI_CheckBadMove
@ -3082,6 +3067,18 @@ static s32 CompareMoveAccuracies(u32 battlerAtk, u32 battlerDef, u32 moveSlot1,
return 0;
}
static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 moveIndex, u32 hitsToFaintOpposingBattler)
{
u32 partnerBattler = BATTLE_PARTNER(battlerAtk);
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex);
return (IsDoubleBattle()
&& noOfHitsToFaintPartner != 0 // Immunity check
&& IsBattlerAlive(partnerBattler)
&& gMovesInfo[move].target == MOVE_TARGET_FOES_AND_ALLY
&& !(noOfHitsToFaintPartner < 4 && hitsToFaintOpposingBattler == 1)
&& noOfHitsToFaintPartner < 7);
}
static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
{
u32 i;
@ -3099,7 +3096,13 @@ static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
if (moves[i] != MOVE_NONE && gMovesInfo[moves[i]].power)
{
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i);
if (noOfHits[i] < leastHits && noOfHits[i] != 0)
if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i]))
{
noOfHits[i] = -1;
viableMoveScores[i] = 0;
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
}
else if (noOfHits[i] < leastHits && noOfHits[i] != 0)
{
leastHits = noOfHits[i];
}

View File

@ -371,14 +371,17 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
s32 moveType;
struct AiLogicData *aiData = AI_DATA;
u32 battlerDefAbility;
GET_MOVE_TYPE(move, moveType);
if (DoesBattlerIgnoreAbilityChecks(aiData->abilities[battlerAtk], move))
battlerDefAbility = ABILITY_NONE;
else
battlerDefAbility = aiData->abilities[battlerDef];
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
// Battler doesn't see partners Ability for some reason.
// This is a small hack to avoid the issue but should be investigated
if (battlerDef == BATTLE_PARTNER(battlerAtk))
battlerDefAbility = GetBattlerAbility(battlerDef);
switch (battlerDefAbility)
{
@ -472,14 +475,13 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
gBattleStruct->dynamicMoveType = 0;
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
effectivenessMultiplier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, aiData->abilities[battlerDef], FALSE);
if (gMovesInfo[move].power)
isDamageMoveUnusable = IsDamageMoveUnusable(move, battlerAtk, battlerDef);
effectivenessMultiplier = CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, aiData->abilities[battlerDef], FALSE);
if (gMovesInfo[move].power && !isDamageMoveUnusable)
{
s32 critChanceIndex, normalDmg, fixedBasePower, n;

View File

@ -8846,7 +8846,6 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32
if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk])
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_BULLDOZE:
case EFFECT_MAGNITUDE:
case EFFECT_EARTHQUAKE:
if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE))

View File

@ -1228,12 +1228,6 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] =
.battleTvScore = 0, // TODO: Assign points
},
[EFFECT_BULLDOZE] =
{
.battleScript = BattleScript_EffectHit,
.battleTvScore = 0, // TODO: Assign points
},
[EFFECT_FOUL_PLAY] =
{
.battleScript = BattleScript_EffectHit,

View File

@ -12504,7 +12504,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.description = COMPOUND_STRING(
"Stomps down on the ground.\n"
"Lowers Speed."),
.effect = EFFECT_BULLDOZE,
.effect = EFFECT_EARTHQUAKE,
.power = 60,
.type = TYPE_GROUND,
.accuracy = 100,

View File

@ -882,3 +882,76 @@ AI_SINGLE_BATTLE_TEST("AI will choose Superpower over Outrage with Contrary")
}
}
}
AI_DOUBLE_BATTLE_TEST("AI will not choose Earthquake if it damages the partner")
{
u32 species;
PARAMETRIZE { species = SPECIES_CHARIZARD; }
PARAMETRIZE { species = SPECIES_CHARMANDER; }
PARAMETRIZE { species = SPECIES_CHIKORITA; }
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(species) { Moves(MOVE_CELEBRATE); }
} WHEN {
if (species == SPECIES_CHARIZARD)
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if partner is not alive")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(SPECIES_PIKACHU) { HP(1); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentRight); }
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will choose Earthquake if it kill an opposing mon and does 1/3 of damage to AI")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_TACKLE); }
OPPONENT(SPECIES_PARAS) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will the see a corresponding absorbing ability on partner to one of its moves")
{
u32 ability;
PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; }
PARAMETRIZE { ability = ABILITY_STATIC; }
GIVEN {
ASSUME(gMovesInfo[MOVE_DISCHARGE].target == MOVE_TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_DISCHARGE, MOVE_TACKLE); }
OPPONENT(SPECIES_PIKACHU) { HP(1); Ability(ability); Moves(MOVE_CELEBRATE); }
} WHEN {
if (ability == ABILITY_LIGHTNING_ROD)
TURN { EXPECT_MOVE(opponentLeft, MOVE_DISCHARGE); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}

View File

@ -68,7 +68,7 @@ SINGLE_BATTLE_TEST("Grassy Terrain decreases power of Earthquake and Bulldoze by
PARAMETRIZE { terrain = TRUE; move = MOVE_BULLDOZE; } // 3
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].effect == EFFECT_EARTHQUAKE);
ASSUME(gMovesInfo[MOVE_BULLDOZE].effect == EFFECT_BULLDOZE);
ASSUME(gMovesInfo[MOVE_BULLDOZE].effect == EFFECT_EARTHQUAKE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {