Add AI_FLAG_ASSUME_STAB (#6797)
This commit is contained in:
parent
0d09e5cce8
commit
419219bb31
@ -144,6 +144,9 @@ Marks the last two Pokémon in the party as Ace Pokémon, with the same behaviou
|
||||
## `AI_FLAG_OMNISCIENT`
|
||||
AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions.
|
||||
|
||||
## `AI_FLAG_ASSUME_STAB`
|
||||
A significantly more restricted version of `AI_FLAG_OMNISCIENT`, the AI only knows the player's STAB moves, as their existence would be reasonable to assume in almost any case.
|
||||
|
||||
## `AI_FLAG_SMART_MON_CHOICES`
|
||||
Affects what the AI chooses to send out after a switch. AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are handled separately. Automatically included when `AI_FLAG_SMART_SWITCHING` is enabled.
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcC
|
||||
bool32 IsAiVsAiBattle(void);
|
||||
bool32 BattlerHasAi(u32 battlerId);
|
||||
bool32 IsAiBattlerAware(u32 battlerId);
|
||||
bool32 IsAiBattlerAssumingStab(void);
|
||||
void ClearBattlerMoveHistory(u32 battlerId);
|
||||
void RecordLastUsedMoveBy(u32 battlerId, u32 move);
|
||||
void RecordAllMoves(u32 battler);
|
||||
|
||||
@ -69,10 +69,15 @@
|
||||
#define AI_CONSERVE_TERA_CHANCE_PER_MON 10 // Chance for AI with smart tera flag to decide not to tera before considering defensive benefit is this*(X-1), where X is the number of alive pokemon that could tera
|
||||
#define AI_TERA_PREDICT_CHANCE 40 // Chance for AI with smart tera flag to tera in the situation where tera would save it from a KO, but could be punished by a KO from a different move.
|
||||
|
||||
// AI PP Stall detection chance per roll
|
||||
#define PP_STALL_DISREGARD_MOVE_PERCENTAGE 50
|
||||
// Score reduction if any roll for PP stall detection passes
|
||||
#define PP_STALL_SCORE_REDUCTION 20
|
||||
// AI_FLAG_PP_STALL_PREVENTION settings
|
||||
#define PP_STALL_DISREGARD_MOVE_PERCENTAGE 50 // Detection chance per roll
|
||||
#define PP_STALL_SCORE_REDUCTION 20 // Score reduction if any roll for PP stall detection passes
|
||||
|
||||
// AI_FLAG_ASSUME_STAB settings
|
||||
#define ASSUME_STAB_SEES_ABILITY FALSE // Flag also gives omniscience for player's ability. Can use AI_FLAG_WEIGH_ABILITY_PREDICTION instead for smarter prediction without omniscience.
|
||||
|
||||
// AI_FLAG_SMART_SWITCHING settings
|
||||
#define SMART_SWITCHING_OMNISCIENT FALSE // AI will use omniscience for switching calcs, regardless of omniscience setting otherwise
|
||||
|
||||
// AI's acceptable number of hits to KO the partner via friendly fire in a double battle.
|
||||
#define FRIENDLY_FIRE_RISKY_THRESHOLD 2
|
||||
|
||||
@ -33,8 +33,9 @@
|
||||
#define AI_FLAG_PP_STALL_PREVENTION (1 << 25) // AI keeps track of the player's switches where the incoming mon is immune to the chosen move
|
||||
#define AI_FLAG_PREDICT_MOVE (1 << 26) // AI will predict the player's move based on what move it would use in the same situation. Recommend using AI_FLAG_OMNISCIENT
|
||||
#define AI_FLAG_SMART_TERA (1 << 27) // AI will make smarter decisions when choosing whether to terrastalize (default is to always tera whenever available).
|
||||
#define AI_FLAG_ASSUME_STAB (1 << 28) // AI knows player's STAB moves, but nothing else. Restricted version of AI_FLAG_OMNISCIENT.
|
||||
|
||||
#define AI_FLAG_COUNT 28
|
||||
#define AI_FLAG_COUNT 29
|
||||
|
||||
// Flags at and after 32 need different formatting, as in
|
||||
// #define AI_FLAG_PLACEHOLDER ((u64)1 << 32)
|
||||
|
||||
@ -881,5 +881,6 @@ u32 GetTeraTypeFromPersonality(struct Pokemon *mon);
|
||||
struct Pokemon *GetSavedPlayerPartyMon(u32 index);
|
||||
u8 *GetSavedPlayerPartyCount(void);
|
||||
void SavePlayerPartyMon(u32 index, struct Pokemon *mon);
|
||||
u32 IsSpeciesOfType(u32 species, u32 type);
|
||||
|
||||
#endif // GUARD_POKEMON_H
|
||||
|
||||
@ -533,6 +533,17 @@ void Ai_UpdateFaintData(u32 battler)
|
||||
aiMon->isFainted = TRUE;
|
||||
}
|
||||
|
||||
void RecordMovesBasedOnStab(u32 battler)
|
||||
{
|
||||
u32 i;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
u32 playerMove = gBattleMons[battler].moves[i];
|
||||
if (IsSpeciesOfType(gBattleMons[battler].species, GetMoveType(playerMove)) && GetMovePower(playerMove != 0))
|
||||
RecordKnownMove(battler, playerMove);
|
||||
}
|
||||
}
|
||||
|
||||
void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
||||
{
|
||||
u32 ability, holdEffect;
|
||||
@ -545,6 +556,9 @@ void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
|
||||
aiData->hpPercents[battler] = GetHealthPercentage(battler);
|
||||
aiData->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL);
|
||||
aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect);
|
||||
|
||||
if (IsAiBattlerAssumingStab())
|
||||
RecordMovesBasedOnStab(battler);
|
||||
}
|
||||
|
||||
#define BYPASSES_ACCURACY_CALC 101 // 101 indicates for ai that the move will always hit
|
||||
|
||||
@ -192,6 +192,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
||||
|
||||
opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler));
|
||||
opposingBattler = GetBattlerAtPosition(opposingPosition);
|
||||
u16 *playerMoves = GetMovesArray(opposingBattler);
|
||||
|
||||
// Gets types of player (opposingBattler) and computer (battler)
|
||||
atkType1 = gBattleMons[opposingBattler].types[0];
|
||||
@ -255,7 +256,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
||||
// Get max damage mon could take
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
playerMove = gBattleMons[opposingBattler].moves[i];
|
||||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
|
||||
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
|
||||
{
|
||||
damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData);
|
||||
@ -1941,11 +1942,12 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
|
||||
{
|
||||
int i = 0;
|
||||
u32 playerMove;
|
||||
u16 *playerMoves = GetMovesArray(opposingBattler);
|
||||
s32 damageTaken = 0, maxDamageTaken = 0;
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
playerMove = gBattleMons[opposingBattler].moves[i];
|
||||
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i];
|
||||
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH)
|
||||
{
|
||||
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, AI_DEFENDING);
|
||||
|
||||
@ -132,6 +132,15 @@ bool32 IsAiBattlerAware(u32 battlerId)
|
||||
return BattlerHasAi(battlerId);
|
||||
}
|
||||
|
||||
bool32 IsAiBattlerAssumingStab()
|
||||
{
|
||||
if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_ASSUME_STAB
|
||||
|| gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_ASSUME_STAB)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool32 IsAiBattlerPredictingAbility(u32 battlerId)
|
||||
{
|
||||
if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_WEIGH_ABILITY_PREDICTION
|
||||
@ -1423,8 +1432,8 @@ s32 AI_DecideKnownAbilityForTurn(u32 battlerId)
|
||||
if (gDisableStructs[battlerId].overwrittenAbility)
|
||||
return gDisableStructs[battlerId].overwrittenAbility;
|
||||
|
||||
// The AI knows its own ability.
|
||||
if (IsAiBattlerAware(battlerId))
|
||||
// The AI knows its own ability, and omniscience handling
|
||||
if (IsAiBattlerAware(battlerId) || (IsAiBattlerAssumingStab() && ASSUME_STAB_SEES_ABILITY))
|
||||
return knownAbility;
|
||||
|
||||
// Check neutralizing gas, gastro acid
|
||||
|
||||
@ -7154,3 +7154,11 @@ void SavePlayerPartyMon(u32 index, struct Pokemon *mon)
|
||||
{
|
||||
gSaveBlock1Ptr->playerParty[index] = *mon;
|
||||
}
|
||||
|
||||
u32 IsSpeciesOfType(u32 species, u32 type)
|
||||
{
|
||||
if (gSpeciesInfo[species].types[0] == type
|
||||
|| gSpeciesInfo[species].types[1] == type)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
85
test/battle/ai/ai_assume_stab.c
Normal file
85
test/battle/ai/ai_assume_stab.c
Normal file
@ -0,0 +1,85 @@
|
||||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
#include "battle_ai_util.h"
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_ASSUME_STAB sees the player's STAB moves")
|
||||
{
|
||||
u32 aiFlag = 0;
|
||||
PARAMETRIZE { aiFlag = AI_FLAG_ASSUME_STAB; }
|
||||
PARAMETRIZE { aiFlag = AI_FLAG_OMNISCIENT; }
|
||||
PARAMETRIZE { aiFlag = 0; }
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiFlag);
|
||||
PLAYER(SPECIES_TYPHLOSION) { Speed(5); Moves(MOVE_TACKLE, MOVE_FLAMETHROWER); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); Moves(MOVE_TACKLE); Level(1); }
|
||||
OPPONENT(SPECIES_SCIZOR) { Speed(4); Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_BLISSEY) { Speed(4); Moves(MOVE_TACKLE); }
|
||||
} WHEN {
|
||||
if (aiFlag == AI_FLAG_ASSUME_STAB || aiFlag == AI_FLAG_OMNISCIENT)
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); }
|
||||
else
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_ASSUME_STAB does not see the player's non-STAB moves")
|
||||
{
|
||||
u32 aiFlag = 0;
|
||||
PARAMETRIZE { aiFlag = AI_FLAG_ASSUME_STAB; }
|
||||
PARAMETRIZE { aiFlag = AI_FLAG_OMNISCIENT; }
|
||||
PARAMETRIZE { aiFlag = 0; }
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiFlag);
|
||||
PLAYER(SPECIES_GOREBYSS) { Speed(5); Moves(MOVE_TACKLE, MOVE_FLAMETHROWER); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); Moves(MOVE_TACKLE); Level(1); }
|
||||
OPPONENT(SPECIES_SCIZOR) { Speed(4); Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_BLISSEY) { Speed(4); Moves(MOVE_TACKLE); }
|
||||
} WHEN {
|
||||
if (aiFlag == AI_FLAG_OMNISCIENT)
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); }
|
||||
else
|
||||
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_DOUBLE_BATTLE_TEST("AI correctly records assumed STAB moves")
|
||||
{
|
||||
u32 aiFlag = 0;
|
||||
// Not checking Omniscient here because it doesn't change the battle history
|
||||
PARAMETRIZE { aiFlag = AI_FLAG_ASSUME_STAB; }
|
||||
PARAMETRIZE { aiFlag = 0; }
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiFlag);
|
||||
PLAYER(SPECIES_TYPHLOSION) { Moves(MOVE_TACKLE, MOVE_FLAMETHROWER, MOVE_SMOG); }
|
||||
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE, MOVE_HEADBUTT, MOVE_THUNDERBOLT); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_TACKLE, target:opponentLeft); MOVE(playerRight, MOVE_THUNDERBOLT, target:opponentRight); }
|
||||
} THEN {
|
||||
if (aiFlag == AI_FLAG_ASSUME_STAB)
|
||||
{
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][0], MOVE_TACKLE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][1], MOVE_FLAMETHROWER);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][2], MOVE_NONE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][3], MOVE_NONE);
|
||||
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][0], MOVE_TACKLE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][1], MOVE_HEADBUTT);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][2], MOVE_THUNDERBOLT);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][3], MOVE_NONE);
|
||||
}
|
||||
else if (aiFlag == 0)
|
||||
{
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][0], MOVE_TACKLE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][1], MOVE_NONE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][2], MOVE_NONE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_LEFT][3], MOVE_NONE);
|
||||
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][0], MOVE_NONE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][1], MOVE_NONE);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][2], MOVE_THUNDERBOLT);
|
||||
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][3], MOVE_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive o
|
||||
PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; }
|
||||
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiRiskyFlag);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT | aiRiskyFlag);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); SpDefense(41); } // Mid battle, AI sends out Aron
|
||||
|
||||
@ -85,7 +85,7 @@ AI_SINGLE_BATTLE_TEST("AI will switch out if it has no move that affects the pla
|
||||
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 {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL, MOVE_CELEBRATE); }
|
||||
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
|
||||
@ -314,7 +314,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize defensive options")
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } // Mid battle, AI sends out Aron
|
||||
@ -373,7 +373,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize
|
||||
GIVEN {
|
||||
ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK);
|
||||
ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
|
||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); }
|
||||
OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout
|
||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user