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:
parent
06cbc9a162
commit
a0006d8dfb
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
91
test/battle/ai_flag_risky.c
Normal file
91
test/battle/ai_flag_risky.c
Normal 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); }
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user