Add AI_FLAG_PP_STALL_PREVENTION (#6743)
Co-authored-by: Hedara <hedara90@gmail.com>
This commit is contained in:
parent
1a2cd5645a
commit
ccda2308a3
@ -816,6 +816,7 @@ struct BattleStruct
|
||||
struct AiBattleData
|
||||
{
|
||||
s32 finalScore[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // AI, target, moves to make debugging easier
|
||||
u8 playerStallMons[PARTY_SIZE];
|
||||
u8 chosenMoveIndex[MAX_BATTLERS_COUNT];
|
||||
u8 chosenTarget[MAX_BATTLERS_COUNT];
|
||||
u8 actionFlee:1;
|
||||
|
||||
@ -377,5 +377,6 @@ bool32 HasWeatherEffect(void);
|
||||
u32 RestoreWhiteHerbStats(u32 battler);
|
||||
bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef);
|
||||
bool32 HadMoreThanHalfHpNowDoesnt(u32 battler);
|
||||
void UpdateStallMons(void);
|
||||
|
||||
#endif // GUARD_BATTLE_UTIL_H
|
||||
|
||||
@ -60,4 +60,9 @@
|
||||
// AI prediction chances
|
||||
#define PREDICT_SWITCH_CHANCE 50
|
||||
|
||||
// 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
|
||||
|
||||
#endif // GUARD_CONFIG_AI_H
|
||||
|
||||
@ -30,12 +30,13 @@
|
||||
#define AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE (1 << 22) // AI adds score to highest damage move regardless of accuracy or secondary effect
|
||||
#define AI_FLAG_PREDICT_SWITCH (1 << 23) // AI will predict the player's switches and switchins based on how it would handle the situation. Recommend using AI_FLAG_OMNISCIENT
|
||||
#define AI_FLAG_PREDICT_INCOMING_MON (1 << 24) // AI will score against the predicting incoming mon if it predicts the player to switch. Requires AI_FLAG_PREDICT_SWITCH
|
||||
#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_COUNT 25
|
||||
#define AI_FLAG_COUNT 26
|
||||
|
||||
// The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag
|
||||
#define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY)
|
||||
#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_WEIGH_ABILITY_PREDICTION)
|
||||
#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_WEIGH_ABILITY_PREDICTION | AI_FLAG_PP_STALL_PREVENTION)
|
||||
#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON)
|
||||
|
||||
// 'other' ai logic flags
|
||||
|
||||
@ -187,6 +187,7 @@ enum RandomTag
|
||||
RNG_AI_SWITCH_TRAPPER,
|
||||
RNG_AI_SWITCH_FREE_TURN,
|
||||
RNG_AI_SWITCH_ALL_MOVES_BAD,
|
||||
RNG_AI_PP_STALL_DISREGARD_MOVE,
|
||||
RNG_SHELL_SIDE_ARM,
|
||||
RNG_RANDOM_TARGET,
|
||||
RNG_AI_PREDICT_ABILITY,
|
||||
|
||||
@ -58,6 +58,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
|
||||
static s32 AI_PowerfulStatus(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
|
||||
static s32 AI_DynamicFunc(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
|
||||
static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
|
||||
static s32 AI_CheckPpStall(u32 battlerAtk, u32 battlerDef, u32 move, s32 score);
|
||||
|
||||
static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) =
|
||||
{
|
||||
@ -86,7 +87,7 @@ static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) =
|
||||
[22] = NULL, // Unused
|
||||
[23] = AI_PredictSwitch, // AI_FLAG_PREDICT_SWITCH
|
||||
[24] = NULL, // Unused
|
||||
[25] = NULL, // Unused
|
||||
[25] = AI_CheckPpStall, // AI_FLAG_PP_STALL_PREVENTION
|
||||
[26] = NULL, // Unused
|
||||
[27] = NULL, // Unused
|
||||
[28] = AI_DynamicFunc, // AI_FLAG_DYNAMIC_FUNC
|
||||
@ -554,6 +555,52 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
|
||||
AI_DATA->aiCalcInProgress = FALSE;
|
||||
}
|
||||
|
||||
u32 GetPartyMonAbility(struct Pokemon *mon)
|
||||
{
|
||||
// Doesn't have any special handling yet
|
||||
u32 species = GetMonData(mon, MON_DATA_SPECIES);
|
||||
u32 ability = gSpeciesInfo[species].abilities[GetMonData(mon, MON_DATA_ABILITY_NUM)];
|
||||
return ability;
|
||||
}
|
||||
|
||||
static u32 PpStallReduction(u32 move, u32 battlerAtk)
|
||||
{
|
||||
if (move == MOVE_NONE)
|
||||
return 0;
|
||||
u32 tempBattleMonIndex = 0;
|
||||
u32 totalStallValue = 0;
|
||||
u32 returnValue = 0;
|
||||
struct BattlePokemon backupBattleMon;
|
||||
memcpy(&backupBattleMon, &gBattleMons[tempBattleMonIndex], sizeof(struct BattlePokemon));
|
||||
for (u32 partyIndex = 0; partyIndex < PARTY_SIZE; partyIndex++)
|
||||
{
|
||||
u32 currentStallValue = gAiBattleData->playerStallMons[partyIndex];
|
||||
if (currentStallValue == 0 || GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP) == 0)
|
||||
continue;
|
||||
PokemonToBattleMon(&gPlayerParty[partyIndex], &gBattleMons[tempBattleMonIndex]);
|
||||
u32 species = GetMonData(&gPlayerParty[partyIndex], MON_DATA_SPECIES);
|
||||
u32 abilityAtk = ABILITY_NONE;
|
||||
u32 abilityDef = GetPartyMonAbility(&gPlayerParty[partyIndex]);
|
||||
u32 moveType = GetBattleMoveType(move); // Probably doesn't handle dynamic types right now
|
||||
if (CanAbilityAbsorbMove(battlerAtk, tempBattleMonIndex, abilityDef, move, moveType, ABILITY_CHECK_TRIGGER)
|
||||
|| CanAbilityBlockMove(battlerAtk, tempBattleMonIndex, abilityAtk, abilityDef, move, ABILITY_CHECK_TRIGGER)
|
||||
|| (CalcPartyMonTypeEffectivenessMultiplier(move, species, abilityDef) == 0))
|
||||
{
|
||||
totalStallValue += currentStallValue;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; returnValue == 0 && i < totalStallValue; i++)
|
||||
{
|
||||
if (RandomPercentage(RNG_AI_PP_STALL_DISREGARD_MOVE, (100 - PP_STALL_DISREGARD_MOVE_PERCENTAGE)))
|
||||
returnValue = PP_STALL_SCORE_REDUCTION;
|
||||
}
|
||||
|
||||
memcpy(&gBattleMons[tempBattleMonIndex], &backupBattleMon, sizeof(struct BattlePokemon));
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
static u32 ChooseMoveOrAction_Singles(u32 battlerAi)
|
||||
{
|
||||
u8 currentMoveArray[MAX_MON_MOVES];
|
||||
@ -5637,6 +5684,13 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
return score;
|
||||
}
|
||||
|
||||
static s32 AI_CheckPpStall(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||
{
|
||||
if (GetBattlerSide(battlerAtk) == B_SIDE_OPPONENT)
|
||||
score -= PpStallReduction(move, battlerAtk);
|
||||
return score;
|
||||
}
|
||||
|
||||
static void AI_Flee(void)
|
||||
{
|
||||
AI_THINKING_STRUCT->aiAction |= (AI_ACTION_DONE | AI_ACTION_FLEE | AI_ACTION_DO_NOT_ATTACK);
|
||||
|
||||
@ -6841,6 +6841,8 @@ static void Cmd_moveend(void)
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_UPDATE_LAST_MOVES:
|
||||
if (GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT)
|
||||
UpdateStallMons();
|
||||
if ((gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE))
|
||||
|| (gBattleMons[gBattlerAttacker].status2 & (STATUS2_FLINCHED))
|
||||
|| gProtectStructs[gBattlerAttacker].nonVolatileStatusImmobility)
|
||||
|
||||
@ -11400,3 +11400,28 @@ static bool32 IsAnyTargetAffected(u32 battlerAtk)
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void UpdateStallMons(void)
|
||||
{
|
||||
if (IsBattlerTurnDamaged(gBattlerTarget) || IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) || gMovesInfo[gCurrentMove].category == DAMAGE_CATEGORY_STATUS)
|
||||
return;
|
||||
if (!IsDoubleBattle() || gMovesInfo[gCurrentMove].target == MOVE_TARGET_SELECTED)
|
||||
{
|
||||
u32 moveType = GetBattleMoveType(gCurrentMove); // Probably doesn't handle dynamic move types right now
|
||||
u32 abilityAtk = GetBattlerAbility(gBattlerAttacker);
|
||||
u32 abilityDef = GetBattlerAbility(gBattlerTarget);
|
||||
if (CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, abilityDef, gCurrentMove, moveType, ABILITY_CHECK_TRIGGER))
|
||||
{
|
||||
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
||||
}
|
||||
else if (CanAbilityBlockMove(gBattlerAttacker, gBattlerTarget, abilityAtk, abilityDef, gCurrentMove, ABILITY_CHECK_TRIGGER))
|
||||
{
|
||||
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
||||
}
|
||||
else if (AI_GetMoveEffectiveness(gCurrentMove, gBattlerAttacker, gBattlerTarget) == 0)
|
||||
{
|
||||
gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++;
|
||||
}
|
||||
}
|
||||
// Handling for moves that target multiple opponents in doubles not handled currently
|
||||
}
|
||||
|
||||
18
test/battle/ai/ai_pp_stall_prevention.c
Normal file
18
test/battle/ai/ai_pp_stall_prevention.c
Normal file
@ -0,0 +1,18 @@
|
||||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
#include "battle_ai_util.h"
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_PP_STALL_PREVENTION: AI will stop using moves that has hit into immunities due to switches sometimes")
|
||||
{
|
||||
PASSES_RANDOMLY(PP_STALL_DISREGARD_MOVE_PERCENTAGE, 100, RNG_AI_PP_STALL_DISREGARD_MOVE);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_PP_STALL_PREVENTION);
|
||||
PLAYER(SPECIES_RATICATE);
|
||||
PLAYER(SPECIES_GENGAR);
|
||||
OPPONENT(SPECIES_KARTANA) { Moves(MOVE_SHADOW_CLAW, MOVE_SACRED_SWORD, MOVE_ROCK_SLIDE); }
|
||||
} WHEN {
|
||||
TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_SACRED_SWORD); }
|
||||
TURN { SWITCH(player, 0); EXPECT_MOVE(opponent, MOVE_SHADOW_CLAW); }
|
||||
TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_SACRED_SWORD); }
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,6 @@
|
||||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI gets baited by Protect Switch tactics") // This behavior is to be fixed.
|
||||
{
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
|
||||
PLAYER(SPECIES_STUNFISK);
|
||||
PLAYER(SPECIES_PELIPPER);
|
||||
OPPONENT(SPECIES_DARKRAI) { Moves(MOVE_TACKLE, MOVE_PECK, MOVE_EARTHQUAKE, MOVE_THUNDERBOLT); }
|
||||
OPPONENT(SPECIES_SCIZOR) { Moves(MOVE_HYPER_BEAM, MOVE_FACADE, MOVE_GIGA_IMPACT, MOVE_EXTREME_SPEED); }
|
||||
} WHEN {
|
||||
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } // E-quake
|
||||
TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } // E-quake
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_THUNDERBOLT); } // T-Bolt
|
||||
TURN { SWITCH(player, 0); EXPECT_MOVE(opponent, MOVE_THUNDERBOLT); } // T-Bolt
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } // E-quake
|
||||
TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE);} // E-quake
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_THUNDERBOLT); } // T-Bolt
|
||||
}
|
||||
}
|
||||
|
||||
// General switching behaviour
|
||||
AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill")
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user