Renaming to AI_ASSUME_STATUS_MOVES and making a config for universal status move guessing + disabling the custom tuning.

This commit is contained in:
surskitty 2025-07-12 18:50:08 -04:00
parent 9ccb1fbc3a
commit f6b0a15841
6 changed files with 98 additions and 103 deletions

View File

@ -147,10 +147,12 @@ AI has full knowledge of player moves, abilities, and hold items, and can use th
## `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_ASSUME_POWERFUL_STATUS`
A more restricted version of `AI_FLAG_OMNISCIENT`. The AI has a _chance_ to know if the player has certain strong status moves, plus also Fake Out and fixed percentage moves like Super Fang. The intention is so that if the AI has a counterplay implemented, it will seem to have guessed if the player's pokemon has a move, without giving the AI perfect information. For example, with Omniscient set, the AI will not put a pokemon to sleep if it has Sleep Talk; with neither Assume Powerful Status nor Omniscient set, the AI will always assume the pokemon does not have Sleep Talk.
## `AI_FLAG_ASSUME_STATUS_MOVES`
A more restricted version of `AI_FLAG_OMNISCIENT`. The AI has a _chance_ to know what status moves the player has, plus additionally Fake Out and fixed percentage moves like Super Fang. The intention is so that if the AI has a counterplay implemented, it will seem to have guessed if the player's pokemon has a move, without giving the AI perfect information. For example, with Omniscient set, the AI will not usually put a pokemon to sleep if it has Sleep Talk; with neither Assume Powerful Status nor Omniscient set, the AI will always assume the pokemon does not have Sleep Talk.
Percentages for the three groupings (high odds, medium odds, and low odds) are defined in `include/config/ai.h` under `ASSUME_POWERFUL_STATUS_HIGH_ODDS`, `ASSUME_POWERFUL_STATUS_MEDIUM_ODDS`, and `ASSUME_POWERFUL_STATUS_LOW_ODDS`. Moves are sorted in `src/battle_ai_util.c` within `ShouldRecordStatusMove()`
By default, there are three groups of higher likelihood status moves defined in `include/config/ai.h` under `ASSUME_STATUS_HIGH_ODDS`, `ASSUME_STATUS_MEDIUM_ODDS`, and `ASSUME_STATUS_LOW_ODDS`. Moves are sorted in `src/battle_ai_util.c` within `ShouldRecordStatusMove()`.
Any move that is not special cased is then potentially caught by `ASSUME_ALL_STATUS_ODDS`.
## `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.

View File

@ -76,10 +76,13 @@
// 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_ASSUME_POWERFUL_STATUS settings
#define ASSUME_POWERFUL_STATUS_HIGH_ODDS 90 // Chance for AI to see extremely likely moves for a pokemon to have, like Spore
#define ASSUME_POWERFUL_STATUS_MEDIUM_ODDS 70 // Chance for AI to see moderately likely moves for a pokemon to have, like Protect
#define ASSUME_POWERFUL_STATUS_LOW_ODDS 40 // Chance for AI to see niche moves a pokemon may have but probably won't, like Trick Room or Speed Swap
// AI_FLAG_ASSUME_STATUS_MOVES settings
#define ASSUME_STATUS_MOVES_HAS_TUNING TRUE // Flag has varying rates for different kinds of status move.
// Setting to false also means it will not alert on Fake Out or Super Fang.
#define ASSUME_STATUS_HIGH_ODDS 90 // Chance for AI to see extremely likely moves for a pokemon to have, like Spore
#define ASSUME_STATUS_MEDIUM_ODDS 70 // Chance for AI to see moderately likely moves for a pokemon to have, like Protect
#define ASSUME_STATUS_LOW_ODDS 40 // Chance for AI to see niche moves a pokemon may have but probably won't, like Entrainment
#define ASSUME_ALL_STATUS_ODDS 25 // Chance for the AI to see any kind of status move.
// AI_FLAG_SMART_SWITCHING settings
#define SMART_SWITCHING_OMNISCIENT FALSE // AI will use omniscience for switching calcs, regardless of omniscience setting otherwise

View File

@ -34,7 +34,7 @@
#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_ASSUME_POWERFUL_STATUS (1 << 29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ASSUME_STATUS_MOVES (1 << 29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_COUNT 30

View File

@ -202,11 +202,12 @@ enum RandomTag
RNG_AI_BOOST_INTO_HAZE,
RNG_HEALER,
RNG_DEXNAV_ENCOUNTER_LEVEL,
RNG_AI_ASSUME_POWERFUL_STATUS_SLEEP,
RNG_AI_ASSUME_POWERFUL_STATUS_NONVOLATILE,
RNG_AI_ASSUME_POWERFUL_STATUS_HIGH_ODDS,
RNG_AI_ASSUME_POWERFUL_STATUS_MEDIUM_ODDS,
RNG_AI_ASSUME_POWERFUL_STATUS_LOW_ODDS,
RNG_AI_ASSUME_STATUS_SLEEP,
RNG_AI_ASSUME_STATUS_NONVOLATILE,
RNG_AI_ASSUME_STATUS_HIGH_ODDS,
RNG_AI_ASSUME_STATUS_MEDIUM_ODDS,
RNG_AI_ASSUME_STATUS_LOW_ODDS,
RNG_AI_ASSUME_ALL_STATUS,
};
#define RandomWeighted(tag, ...) \

View File

@ -143,8 +143,8 @@ bool32 IsAiBattlerAssumingStab()
bool32 IsAiBattlerAssumingPowerfulStatus()
{
if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_ASSUME_POWERFUL_STATUS
|| gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_ASSUME_POWERFUL_STATUS)
if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_ASSUME_STATUS_MOVES
|| gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_ASSUME_STATUS_MOVES)
return TRUE;
return FALSE;
@ -260,92 +260,62 @@ void SaveBattlerData(u32 battlerId)
bool32 ShouldRecordStatusMove(u32 move)
{
switch (GetMoveEffect(move))
if (ASSUME_STATUS_MOVES_HAS_TUNING)
{
// variable odds by additional effect
case EFFECT_NON_VOLATILE_STATUS:
if (GetMoveNonVolatileStatus(move) == MOVE_EFFECT_SLEEP && RandomPercentage(RNG_AI_ASSUME_POWERFUL_STATUS_SLEEP, ASSUME_POWERFUL_STATUS_HIGH_ODDS))
return TRUE;
else if (RandomPercentage(RNG_AI_ASSUME_POWERFUL_STATUS_NONVOLATILE, ASSUME_POWERFUL_STATUS_MEDIUM_ODDS))
return TRUE;
break;
// High odds
case EFFECT_AURORA_VEIL:
case EFFECT_CHILLY_RECEPTION:
case EFFECT_CONVERSION:
case EFFECT_FIRST_TURN_ONLY:
case EFFECT_FOLLOW_ME:
case EFFECT_INSTRUCT:
case EFFECT_JUNGLE_HEALING:
case EFFECT_REVIVAL_BLESSING:
case EFFECT_SHED_TAIL:
case EFFECT_STICKY_WEB:
return RandomPercentage(RNG_AI_ASSUME_POWERFUL_STATUS_HIGH_ODDS, ASSUME_POWERFUL_STATUS_HIGH_ODDS);
// Medium odds
case EFFECT_AFTER_YOU:
case EFFECT_DEFOG:
case EFFECT_ENCORE:
case EFFECT_HAZE:
case EFFECT_HEAL_BELL:
case EFFECT_HEALING_WISH:
case EFFECT_HELPING_HAND:
case EFFECT_LIFE_DEW:
case EFFECT_MEMENTO:
case EFFECT_MOONLIGHT:
case EFFECT_MORNING_SUN:
case EFFECT_PARTING_SHOT:
case EFFECT_PROTECT:
case EFFECT_REST:
case EFFECT_RESTORE_HP:
case EFFECT_ROAR:
case EFFECT_ROOST:
case EFFECT_SHORE_UP:
case EFFECT_SLEEP_TALK:
case EFFECT_SOFTBOILED:
case EFFECT_SYNTHESIS:
case EFFECT_TAUNT:
case EFFECT_TAILWIND:
case EFFECT_TELEPORT:
case EFFECT_TRICK:
// defoggables / screens and hazards
case EFFECT_LIGHT_SCREEN:
case EFFECT_REFLECT:
case EFFECT_SPIKES:
case EFFECT_STEALTH_ROCK:
case EFFECT_TOXIC_SPIKES:
// field status
case EFFECT_HAIL:
case EFFECT_RAIN_DANCE:
case EFFECT_SANDSTORM:
case EFFECT_SNOWSCAPE:
case EFFECT_SUNNY_DAY:
case EFFECT_TRICK_ROOM:
case EFFECT_ELECTRIC_TERRAIN:
case EFFECT_GRASSY_TERRAIN:
case EFFECT_MISTY_TERRAIN:
case EFFECT_PSYCHIC_TERRAIN:
return RandomPercentage(RNG_AI_ASSUME_POWERFUL_STATUS_MEDIUM_ODDS, ASSUME_POWERFUL_STATUS_MEDIUM_ODDS);
// Low odds
case EFFECT_COURT_CHANGE:
case EFFECT_DOODLE:
case EFFECT_ENTRAINMENT:
case EFFECT_FIXED_PERCENT_DAMAGE:
case EFFECT_GASTRO_ACID:
case EFFECT_GUARD_SPLIT:
case EFFECT_IMPRISON:
case EFFECT_PERISH_SONG:
case EFFECT_POWER_SPLIT:
case EFFECT_QUASH:
case EFFECT_ROLE_PLAY:
case EFFECT_SKILL_SWAP:
case EFFECT_SPEED_SWAP:
case EFFECT_WORRY_SEED:
return RandomPercentage(RNG_AI_ASSUME_POWERFUL_STATUS_LOW_ODDS, ASSUME_POWERFUL_STATUS_LOW_ODDS);
default:
break;
switch (GetMoveEffect(move))
{
// variable odds by additional effect
case EFFECT_NON_VOLATILE_STATUS:
if (GetMoveNonVolatileStatus(move) == MOVE_EFFECT_SLEEP && RandomPercentage(RNG_AI_ASSUME_STATUS_SLEEP, ASSUME_STATUS_HIGH_ODDS))
return TRUE;
else if (RandomPercentage(RNG_AI_ASSUME_STATUS_NONVOLATILE, ASSUME_STATUS_MEDIUM_ODDS))
return TRUE;
break;
// High odds
case EFFECT_AURORA_VEIL:
case EFFECT_CHILLY_RECEPTION:
case EFFECT_FIRST_TURN_ONLY:
case EFFECT_FOLLOW_ME:
case EFFECT_INSTRUCT:
case EFFECT_JUNGLE_HEALING:
case EFFECT_SHED_TAIL:
return RandomPercentage(RNG_AI_ASSUME_STATUS_HIGH_ODDS, ASSUME_STATUS_HIGH_ODDS);
// Medium odds
case EFFECT_AFTER_YOU:
case EFFECT_DOODLE:
case EFFECT_ENCORE:
case EFFECT_HAZE:
case EFFECT_PARTING_SHOT:
case EFFECT_PROTECT:
case EFFECT_REST:
case EFFECT_ROAR:
case EFFECT_ROOST:
case EFFECT_SLEEP_TALK:
case EFFECT_TAUNT:
case EFFECT_TAILWIND:
case EFFECT_TRICK:
case EFFECT_TRICK_ROOM:
// defoggables / screens and hazards
case EFFECT_LIGHT_SCREEN:
case EFFECT_REFLECT:
case EFFECT_SPIKES:
case EFFECT_STEALTH_ROCK:
case EFFECT_STICKY_WEB:
case EFFECT_TOXIC_SPIKES:
return RandomPercentage(RNG_AI_ASSUME_STATUS_MEDIUM_ODDS, ASSUME_STATUS_MEDIUM_ODDS);
// Low odds
case EFFECT_ENTRAINMENT:
case EFFECT_FIXED_PERCENT_DAMAGE:
case EFFECT_GASTRO_ACID:
case EFFECT_IMPRISON:
case EFFECT_TELEPORT:
return RandomPercentage(RNG_AI_ASSUME_STATUS_LOW_ODDS, ASSUME_STATUS_LOW_ODDS);
default:
break;
}
}
return FALSE;
return RandomPercentage(RNG_AI_ASSUME_ALL_STATUS, ASSUME_ALL_STATUS_ODDS) && IsBattleMoveStatus(move);
}
static bool32 ShouldFailForIllusion(u32 illusionSpecies, u32 battlerId)

View File

@ -2,14 +2,15 @@
#include "test/battle.h"
#include "battle_ai_util.h"
AI_DOUBLE_BATTLE_TEST("AI_FLAG_ASSUME_POWERFUL_STATUS correctly records assumed status moves")
AI_DOUBLE_BATTLE_TEST("AI_FLAG_ASSUME_STATUS_MOVES correctly records assumed status moves")
{
PASSES_RANDOMLY(ASSUME_POWERFUL_STATUS_HIGH_ODDS, 100, RNG_AI_ASSUME_POWERFUL_STATUS_HIGH_ODDS);
PASSES_RANDOMLY(ASSUME_POWERFUL_STATUS_MEDIUM_ODDS, 100, RNG_AI_ASSUME_POWERFUL_STATUS_MEDIUM_ODDS);
PASSES_RANDOMLY(ASSUME_POWERFUL_STATUS_LOW_ODDS, 100, RNG_AI_ASSUME_POWERFUL_STATUS_LOW_ODDS);
PASSES_RANDOMLY(ASSUME_STATUS_HIGH_ODDS, 100, RNG_AI_ASSUME_STATUS_HIGH_ODDS);
PASSES_RANDOMLY(ASSUME_STATUS_MEDIUM_ODDS, 100, RNG_AI_ASSUME_STATUS_MEDIUM_ODDS);
PASSES_RANDOMLY(ASSUME_STATUS_LOW_ODDS, 100, RNG_AI_ASSUME_STATUS_LOW_ODDS);
PASSES_RANDOMLY(ASSUME_ALL_STATUS_ODDS, 100, RNG_AI_ASSUME_ALL_STATUS);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_ASSUME_POWERFUL_STATUS);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_ASSUME_STATUS_MOVES);
PLAYER(SPECIES_TYPHLOSION) { Moves(MOVE_TACKLE, MOVE_COURT_CHANGE, MOVE_FAKE_OUT); }
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_HAIL, MOVE_SHED_TAIL, MOVE_THUNDERBOLT); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
@ -28,3 +29,21 @@ AI_DOUBLE_BATTLE_TEST("AI_FLAG_ASSUME_POWERFUL_STATUS correctly records assumed
EXPECT_EQ(gBattleHistory->usedMoves[B_POSITION_PLAYER_RIGHT][3], MOVE_NONE);
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_ASSUME_STATUS_MOVES changes behavior")
{
if (ASSUME_STATUS_MOVES_HAS_TUNING)
PASSES_RANDOMLY(ASSUME_STATUS_MEDIUM_ODDS, 100, RNG_AI_ASSUME_STATUS_MEDIUM_ODDS);
else
PASSES_RANDOMLY(ASSUME_ALL_STATUS_ODDS, 100, RNG_AI_ASSUME_ALL_STATUS);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_ASSUME_STATUS_MOVES);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_REST, MOVE_HEADBUTT); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_WORRY_SEED, MOVE_HEADBUTT); }
} WHEN {
TURN { MOVE(player, MOVE_HEADBUTT); EXPECT_MOVE(opponent, MOVE_WORRY_SEED); }
}
}