AI_FLAG_RISKY Improvements (#4648)

* #defines for damage roll bounds

* Risky AI behavior implemented

* Ignore score penalty to EFFECT_RECOIL_IF_MISS moves if accuracy has been lowered

* Adjust score defines

* EFFECT_MIND_BLOWN

* Use GetBestDmgMoveFromBattler instead of AI_CompareDamagingMoves
This commit is contained in:
Pawkkie 2024-05-29 13:54:18 -04:00 committed by GitHub
parent 06cbc9a162
commit a0006d8dfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 146 additions and 24 deletions

View File

@ -32,7 +32,7 @@
#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect
#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target
// Temporary scores that are added together to determine a final score at the at of AI_CalcMoveEffectScore
// Temporary scores that are added together to determine a final score at the end of AI_CalcMoveEffectScore
#define WEAK_EFFECT 1
#define DECENT_EFFECT 2
#define GOOD_EFFECT 4
@ -49,6 +49,10 @@
#define SLOW_KILL 4 // AI is slower and faints target
#define LAST_CHANCE 2 // AI faints to target. It should try and do damage with a priority move
// AI_Risky
#define STRONG_RISKY_EFFECT 3
#define AVERAGE_RISKY_EFFECT 2
#include "test_runner.h"
// Logs for debugging AI tests.

View File

@ -5,6 +5,10 @@
#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER))
// Roll boundaries used by AI when scoring. Doesn't affect actual damage dealt.
#define MAX_ROLL_PERCENTAGE 100
#define MIN_ROLL_PERCENTAGE 85
enum
{
DMG_ROLL_LOWEST,

View File

@ -1434,8 +1434,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_MIRROR_COAT:
if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) || gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION))
ADJUST_SCORE(-1);
if (predictedMove == MOVE_NONE || GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_STATUS
|| DoesSubstituteBlockMove(battlerAtk, BATTLE_PARTNER(battlerDef), predictedMove))
if ((predictedMove == MOVE_NONE || GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_STATUS
|| DoesSubstituteBlockMove(battlerAtk, BATTLE_PARTNER(battlerDef), predictedMove))
&& !(predictedMove == MOVE_NONE && (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY))) // Let Risky AI predict blindly based on stats
ADJUST_SCORE(-10);
break;
@ -1911,7 +1912,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-8); //No point in healing, but should at least do it if nothing better
break;
case EFFECT_RECOIL_IF_MISS:
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && AI_DATA->moveAccuracy[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] < 75)
if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && AI_DATA->moveAccuracy[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] < 75
&& !(AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY))
ADJUST_SCORE(-6);
break;
case EFFECT_TRANSFORM:
@ -4742,7 +4744,12 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 0)
ADJUST_SCORE(-20);
else
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex);
{
if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef) == move)
score += 1;
else
score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex);
}
}
score += AI_CalcMoveEffectScore(battlerAtk, battlerDef, move);
@ -4888,27 +4895,37 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (gMovesInfo[move].criticalHitStage > 0)
ADJUST_SCORE(DECENT_EFFECT);
// +3 Score
switch (gMovesInfo[move].effect)
{
case EFFECT_SLEEP:
case EFFECT_EXPLOSION:
case EFFECT_MIRROR_MOVE:
case EFFECT_OHKO:
case EFFECT_CONFUSE:
case EFFECT_METRONOME:
case EFFECT_PSYWAVE:
case EFFECT_COUNTER:
case EFFECT_DESTINY_BOND:
case EFFECT_SWAGGER:
case EFFECT_ATTRACT:
case EFFECT_PRESENT:
case EFFECT_BELLY_DRUM:
if (gSpeciesInfo[gBattleMons[battlerDef].species].baseAttack >= gSpeciesInfo[gBattleMons[battlerDef].species].baseSpAttack + 10)
ADJUST_SCORE(STRONG_RISKY_EFFECT);
break;
case EFFECT_MIRROR_COAT:
case EFFECT_FOCUS_PUNCH:
if (gSpeciesInfo[gBattleMons[battlerDef].species].baseSpAttack >= gSpeciesInfo[gBattleMons[battlerDef].species].baseAttack + 10)
ADJUST_SCORE(STRONG_RISKY_EFFECT);
break;
case EFFECT_EXPLOSION:
ADJUST_SCORE(STRONG_RISKY_EFFECT);
break;
// +2 Score
case EFFECT_REVENGE:
case EFFECT_FILLET_AWAY:
if (Random() & 1)
ADJUST_SCORE(DECENT_EFFECT);
if (gSpeciesInfo[gBattleMons[battlerDef].species].baseSpeed >= gSpeciesInfo[gBattleMons[battlerAtk].species].baseSpeed + 10)
ADJUST_SCORE(AVERAGE_RISKY_EFFECT);
break;
case EFFECT_BELLY_DRUM:
if (gBattleMons[battlerAtk].hp >= gBattleMons[battlerAtk].maxHP * 90 / 100)
ADJUST_SCORE(AVERAGE_RISKY_EFFECT);
break;
case EFFECT_MAX_HP_50_RECOIL:
case EFFECT_MIND_BLOWN:
case EFFECT_SWAGGER:
case EFFECT_FLATTER:
case EFFECT_ATTRACT:
case EFFECT_OHKO:
ADJUST_SCORE(AVERAGE_RISKY_EFFECT);
break;
case EFFECT_HIT:
{
@ -4920,7 +4937,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
case MOVE_EFFECT_ALL_STATS_UP:
if (Random() & 1)
ADJUST_SCORE(DECENT_EFFECT);
ADJUST_SCORE(AVERAGE_RISKY_EFFECT);
break;
default:
break;

View File

@ -369,13 +369,15 @@ s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *type
static inline s32 LowestRollDmg(s32 dmg)
{
dmg *= 100 - 15;
dmg *= MIN_ROLL_PERCENTAGE;
dmg /= 100;
return dmg;
}
static inline s32 HighestRollDmg(s32 dmg)
{
dmg *= MAX_ROLL_PERCENTAGE;
dmg /= 100;
return dmg;
}

View File

@ -54,6 +54,7 @@
#include "wild_encounter.h"
#include "window.h"
#include "constants/abilities.h"
#include "constants/battle_ai.h"
#include "constants/battle_move_effects.h"
#include "constants/battle_string_ids.h"
#include "constants/battle_partner.h"
@ -4438,7 +4439,10 @@ static void HandleTurnActionSelectionState(void)
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart())
&& (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)))
{
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE);
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY) // Risky AI switches aggressively even mid battle
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, TRUE);
else
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE);
gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler);
}
// fallthrough

View File

@ -0,0 +1,91 @@
#include "global.h"
#include "test/battle.h"
AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Mirror Coat against special attackers")
{
u32 aiRiskyFlag = 0;
PARAMETRIZE{ aiRiskyFlag = 0; }
PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_MIRROR_COAT].effect == EFFECT_MIRROR_COAT);
ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseSpAttack == 85);
ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseAttack == 65);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag);
PLAYER(SPECIES_GROVYLE) { Level(20); Moves(MOVE_ENERGY_BALL); }
OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_MIRROR_COAT); }
} WHEN {
TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_MIRROR_COAT : MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Counter against physical attackers")
{
u32 aiRiskyFlag = 0;
PARAMETRIZE{ aiRiskyFlag = 0; }
PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_COUNTER].effect == EFFECT_COUNTER);
ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseAttack == 85);
ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseSpAttack == 60);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag);
PLAYER(SPECIES_MARSHTOMP) { Level(20); Moves(MOVE_WATERFALL); }
OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_COUNTER); }
} WHEN {
TURN { MOVE(player, MOVE_WATERFALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_COUNTER : MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will prioritize Revenge if slower")
{
u32 aiRiskyFlag = 0;
PARAMETRIZE{ aiRiskyFlag = 0; }
PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; }
GIVEN {
ASSUME(gMovesInfo[MOVE_REVENGE].effect == EFFECT_REVENGE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag);
PLAYER(SPECIES_GROVYLE) { Level(20); Speed(4); Moves(MOVE_ENERGY_BALL); }
OPPONENT(SPECIES_CASTFORM) { Level(19); Speed(3); Moves(MOVE_TACKLE, MOVE_REVENGE); }
} WHEN {
TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_REVENGE : MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive options")
{
u32 aiRiskyFlag = 0;
PARAMETRIZE{ aiRiskyFlag = 0; }
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);
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
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
} WHEN {
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, aiRiskyFlag? 2 : 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI prefers high damage moves at the expense of accuracy regardless of KO thresholds")
{
u32 aiRiskyFlag = 0;
PARAMETRIZE{ aiRiskyFlag = 0; }
PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag);
PLAYER(SPECIES_GOLDEEN) { Level(5); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_THUNDER, MOVE_THUNDERBOLT); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_THUNDER : MOVE_THUNDERBOLT); }
}
}