Add AI_FLAG_SEQUENCE_SWITCHING (#4878)
* AI_FLAG_BAD_SWITCHING * Cleanup and tests, U-Turn / Baton Pass / Parting Shot aren't working properly * Fix U-Turn etc. tests * Test overhaul with Egg's suggestions * Add EFFECT_HIT_SWITCH_TARGET to tests * Review feedback * Re-review feedback
This commit is contained in:
parent
c3634db0ea
commit
7e167f0b3c
@ -3,7 +3,7 @@
|
||||
|
||||
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId);
|
||||
void AI_TrySwitchOrUseItem(u32 battler);
|
||||
u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd);
|
||||
u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd);
|
||||
bool32 ShouldSwitch(u32 battler, bool32 emitResult);
|
||||
|
||||
#endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items
|
||||
#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
|
||||
#define AI_FLAG_CONSERVATIVE (1 << 19) // AI assumes all moves will low roll damage
|
||||
#define AI_FLAG_SEQUENCE_SWITCHING (1 << 20) // AI switches in mons in exactly party order, and never switches mid-battle
|
||||
|
||||
#define AI_FLAG_COUNT 20
|
||||
|
||||
|
||||
@ -511,7 +511,8 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
|
||||
if (CountUsablePartyMons(battler) > 0
|
||||
&& !IsBattlerTrapped(battler, TRUE)
|
||||
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
|
||||
&& AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
|
||||
&& AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)
|
||||
&& !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING))
|
||||
{
|
||||
// Consider switching if all moves are worthless to use.
|
||||
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
|
||||
|
||||
@ -1020,6 +1020,10 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||||
return FALSE;
|
||||
|
||||
// Sequence Switching AI never switches mid-battle
|
||||
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)
|
||||
return FALSE;
|
||||
|
||||
availableToSwitch = 0;
|
||||
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
|
||||
@ -1787,7 +1791,7 @@ static bool32 CanAbilityTrapOpponent(u16 ability, u32 opponent)
|
||||
// the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup
|
||||
// Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you!
|
||||
|
||||
static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u8 battlerIn1, u8 battlerIn2, bool32 isSwitchAfterKO)
|
||||
static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, bool32 isSwitchAfterKO)
|
||||
{
|
||||
int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE;
|
||||
int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE;
|
||||
@ -1993,17 +1997,37 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
||||
|
||||
// If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon.
|
||||
else if (aceMonId != PARTY_SIZE
|
||||
&& (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT))
|
||||
&& (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS))
|
||||
return aceMonId;
|
||||
}
|
||||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
|
||||
static u32 GetNextMonInParty(struct Pokemon *party, int firstId, int lastId, u32 battlerIn1, u32 battlerIn2)
|
||||
{
|
||||
u32 i;
|
||||
// Iterate through mons
|
||||
for (i = firstId; i < lastId; i++)
|
||||
{
|
||||
// Check mon validity
|
||||
if (!IsValidForBattle(&party[i])
|
||||
|| gBattlerPartyIndexes[battlerIn1] == i
|
||||
|| gBattlerPartyIndexes[battlerIn2] == i
|
||||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn1]
|
||||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn2])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
|
||||
{
|
||||
u32 opposingBattler = 0;
|
||||
u32 bestMonId = PARTY_SIZE;
|
||||
u8 battlerIn1 = 0, battlerIn2 = 0;
|
||||
u32 battlerIn1 = 0, battlerIn2 = 0;
|
||||
s32 firstId = 0;
|
||||
s32 lastId = 0; // + 1
|
||||
struct Pokemon *party;
|
||||
@ -2039,6 +2063,12 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
|
||||
else
|
||||
party = gEnemyParty;
|
||||
|
||||
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)
|
||||
{
|
||||
bestMonId = GetNextMonInParty(party, firstId, lastId, battlerIn1, battlerIn2);
|
||||
return bestMonId;
|
||||
}
|
||||
|
||||
// Split ideal mon decision between after previous mon KO'd (prioritize offensive options) and after switching active mon out (prioritize defensive options), and expand the scope of both.
|
||||
// Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer.
|
||||
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic
|
||||
|
||||
129
test/battle/ai_flag_sequence_switching.c
Normal file
129
test/battle/ai_flag_sequence_switching.c
Normal file
@ -0,0 +1,129 @@
|
||||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch after a KO in exactly party order")
|
||||
{
|
||||
u32 aiSequenceSwitchingFlag = 0;
|
||||
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = 0; }
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag);
|
||||
PLAYER(SPECIES_SWELLOW) { Level (50); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(5); }
|
||||
OPPONENT(SPECIES_MACHOKE) { Level(5); }
|
||||
OPPONENT(SPECIES_MACHAMP) { Level(5); }
|
||||
OPPONENT(SPECIES_MANKEY) { Level(5); }
|
||||
OPPONENT(SPECIES_PRIMEAPE) { Level(5); }
|
||||
OPPONENT(SPECIES_MAGNEZONE) { Level(100); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); }
|
||||
if (aiSequenceSwitchingFlag) {
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); }
|
||||
TURN { MOVE(player, MOVE_WING_ATTACK); }
|
||||
}
|
||||
} SCENE {
|
||||
if (aiSequenceSwitchingFlag) {
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Machoke!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Machamp!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Mankey!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Primeape!");
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!");
|
||||
}
|
||||
else {
|
||||
MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: Roar and Dragon Tail still force switch to random party member")
|
||||
{
|
||||
u32 move;
|
||||
|
||||
PARAMETRIZE { move = MOVE_ROAR; }
|
||||
PARAMETRIZE {move = MOVE_DRAGON_TAIL; }
|
||||
|
||||
PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH);
|
||||
GIVEN {
|
||||
ASSUME(gMovesInfo[MOVE_ROAR].effect == EFFECT_ROAR);
|
||||
ASSUME(gMovesInfo[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET);
|
||||
AI_FLAGS(AI_FLAG_SEQUENCE_SWITCHING);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_BULBASAUR);
|
||||
OPPONENT(SPECIES_CHARMANDER);
|
||||
OPPONENT(SPECIES_SQUIRTLE) { HP(0); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, move); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, move, player);
|
||||
MESSAGE("Foe Bulbasaur was dragged out!");
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lowest party index after U-Turn, Parting Shot, and Baton Pass")
|
||||
{
|
||||
u32 j, aiSequenceSwitchingFlag = 0, move = MOVE_NONE;
|
||||
|
||||
static const u32 switchMoves[] = {
|
||||
MOVE_U_TURN,
|
||||
MOVE_PARTING_SHOT,
|
||||
MOVE_BATON_PASS,
|
||||
};
|
||||
|
||||
for (j = 0; j < ARRAY_COUNT(switchMoves); j++)
|
||||
{
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = 0; move = switchMoves[j]; }
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; move = switchMoves[j]; }
|
||||
}
|
||||
|
||||
GIVEN {
|
||||
ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE);
|
||||
ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT);
|
||||
ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag);
|
||||
PLAYER(SPECIES_SWELLOW) { Level (50); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(1); Moves(move); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(2); Moves(move); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(3); Moves(move); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(4); Moves(move); }
|
||||
OPPONENT(SPECIES_MACHOP) { Level(5); Moves(move); }
|
||||
OPPONENT(SPECIES_MAGNEZONE) { Level(100); Moves(move); }
|
||||
} WHEN {
|
||||
if (aiSequenceSwitchingFlag) {
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); }
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); }
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); }
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); }
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
else {
|
||||
TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 5); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will not switch mid-battle")
|
||||
{
|
||||
u32 aiSequenceSwitchingFlag = 0;
|
||||
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = 0; }
|
||||
PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag);
|
||||
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_LICK); }
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_SHADOW_BALL); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_KARATE_CHOP); }
|
||||
} WHEN {
|
||||
if (aiSequenceSwitchingFlag == AI_FLAG_SEQUENCE_SWITCHING) {
|
||||
TURN { MOVE(player, MOVE_LICK) ; EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); }
|
||||
}
|
||||
else {
|
||||
TURN { MOVE(player, MOVE_LICK) ; EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user