Smarter Choice AI for Status Moves (#4872)
* Smarter choice item usage * Clarify test name / line ending * Review feedback * Review feedback pt. 2
This commit is contained in:
parent
55c13a80bc
commit
d1ca1f667f
3 changed files with 221 additions and 2 deletions
|
@ -2654,6 +2654,22 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
||||||
return 0; // cannot even select
|
return 0; // cannot even select
|
||||||
} // move effect checks
|
} // move effect checks
|
||||||
|
|
||||||
|
// Choice items
|
||||||
|
if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && gBattleMons[battlerAtk].ability != ABILITY_KLUTZ)
|
||||||
|
{
|
||||||
|
// Don't use user-target moves ie. Swords Dance, with exceptions
|
||||||
|
if ((moveTarget & MOVE_TARGET_USER)
|
||||||
|
&& moveEffect != EFFECT_DESTINY_BOND && moveEffect != EFFECT_WISH && moveEffect != EFFECT_HEALING_WISH
|
||||||
|
&& !(moveEffect == EFFECT_AURORA_VEIL && (AI_GetWeather(aiData) & (B_WEATHER_SNOW | B_WEATHER_HAIL))))
|
||||||
|
ADJUST_SCORE(-30);
|
||||||
|
// Don't use a status move if the mon is the last one in the party, has no good switchin, or is trapped
|
||||||
|
else if (GetBattleMoveCategory(move) == DAMAGE_CATEGORY_STATUS
|
||||||
|
&& (CountUsablePartyMons(battlerAtk) < 1
|
||||||
|
|| AI_DATA->mostSuitableMonId[battlerAtk] == PARTY_SIZE
|
||||||
|
|| IsBattlerTrapped(battlerAtk, TRUE)))
|
||||||
|
ADJUST_SCORE(-30);
|
||||||
|
}
|
||||||
|
|
||||||
if (score < 0)
|
if (score < 0)
|
||||||
score = 0;
|
score = 0;
|
||||||
|
|
||||||
|
|
|
@ -919,6 +919,24 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool32 ShouldSwitchIfBadChoiceLock(u32 battler, bool32 emitResult)
|
||||||
|
{
|
||||||
|
u32 holdEffect = GetBattlerHoldEffect(battler, FALSE);
|
||||||
|
|
||||||
|
if (HOLD_EFFECT_CHOICE(holdEffect) && gBattleMons[battler].ability != ABILITY_KLUTZ)
|
||||||
|
{
|
||||||
|
if (gMovesInfo[gLastUsedMove].category == DAMAGE_CATEGORY_STATUS)
|
||||||
|
{
|
||||||
|
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||||
|
if (emitResult)
|
||||||
|
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
// AI should switch if it's become setup fodder and has something better to switch to
|
// AI should switch if it's become setup fodder and has something better to switch to
|
||||||
static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
||||||
{
|
{
|
||||||
|
@ -941,7 +959,8 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
||||||
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
|
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
|
||||||
{
|
{
|
||||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
if (emitResult)
|
||||||
|
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -949,7 +968,8 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
|
||||||
else if (attackingStage < DEFAULT_STAT_STAGE - 2)
|
else if (attackingStage < DEFAULT_STAT_STAGE - 2)
|
||||||
{
|
{
|
||||||
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
|
||||||
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
if (emitResult)
|
||||||
|
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1076,6 +1096,8 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
if (ShouldSwitchIfEncored(battler, emitResult))
|
if (ShouldSwitchIfEncored(battler, emitResult))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
if (ShouldSwitchIfBadChoiceLock(battler, emitResult))
|
||||||
|
return TRUE;
|
||||||
if (AreAttackingStatsLowered(battler, emitResult))
|
if (AreAttackingStatsLowered(battler, emitResult))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
|
|
181
test/battle/ai_choice.c
Normal file
181
test/battle/ai_choice.c
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#include "global.h"
|
||||||
|
#include "test/battle.h"
|
||||||
|
|
||||||
|
ASSUMPTIONS
|
||||||
|
{
|
||||||
|
ASSUME(gItemsInfo[ITEM_CHOICE_SPECS].holdEffect == HOLD_EFFECT_CHOICE_SPECS);
|
||||||
|
ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND);
|
||||||
|
ASSUME(gItemsInfo[ITEM_CHOICE_SCARF].holdEffect == HOLD_EFFECT_CHOICE_SCARF);
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_SINGLE_BATTLE_TEST("Choiced Pokémon switch out after using a status move once")
|
||||||
|
{
|
||||||
|
u32 j, ability = ABILITY_NONE, heldItem = ITEM_NONE;
|
||||||
|
|
||||||
|
static const u32 choiceItems[] = {
|
||||||
|
ITEM_CHOICE_SPECS,
|
||||||
|
ITEM_CHOICE_BAND,
|
||||||
|
ITEM_CHOICE_SCARF,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_COUNT(choiceItems); j++)
|
||||||
|
{
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS);
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
|
||||||
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||||
|
PLAYER(SPECIES_RHYDON)
|
||||||
|
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); }
|
||||||
|
OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); }
|
||||||
|
} WHEN {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_YAWN); }
|
||||||
|
if (ability == ABILITY_KLUTZ) { // Klutz ignores item
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TURN { EXPECT_SWITCH(opponent, 1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use stat boosting moves")
|
||||||
|
{
|
||||||
|
// Moves defined by MOVE_TARGET_USER (with exceptions?)
|
||||||
|
u32 j, ability = ABILITY_NONE, heldItem = ITEM_NONE;
|
||||||
|
|
||||||
|
static const u32 choiceItems[] = {
|
||||||
|
ITEM_CHOICE_SPECS,
|
||||||
|
ITEM_CHOICE_BAND,
|
||||||
|
ITEM_CHOICE_SCARF,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_COUNT(choiceItems); j++)
|
||||||
|
{
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
ASSUME(gMovesInfo[MOVE_SWORDS_DANCE].target == MOVE_TARGET_USER);
|
||||||
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||||
|
PLAYER(SPECIES_RHYDON)
|
||||||
|
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_SWORDS_DANCE, MOVE_TACKLE); Item(heldItem); Ability(ability); }
|
||||||
|
OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); }
|
||||||
|
} WHEN {
|
||||||
|
if (ability == ABILITY_KLUTZ) { // Klutz ignores item
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_SWORDS_DANCE); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are the only party member")
|
||||||
|
{
|
||||||
|
u32 j, ability = ABILITY_NONE, isAlive = 0, heldItem = ITEM_NONE;
|
||||||
|
|
||||||
|
static const u32 choiceItems[] = {
|
||||||
|
ITEM_CHOICE_SPECS,
|
||||||
|
ITEM_CHOICE_BAND,
|
||||||
|
ITEM_CHOICE_SCARF,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_COUNT(choiceItems); j++)
|
||||||
|
{
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 0; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 0; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 1; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS);
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
|
||||||
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||||
|
PLAYER(SPECIES_RHYDON)
|
||||||
|
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); }
|
||||||
|
OPPONENT(SPECIES_SWAMPERT) { HP(isAlive); Moves(MOVE_WATERFALL); }
|
||||||
|
} WHEN {
|
||||||
|
if (isAlive == 1 || ability == ABILITY_KLUTZ) {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_YAWN); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they don't have a good switchin")
|
||||||
|
{
|
||||||
|
u32 j, ability = ABILITY_NONE, move = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE;
|
||||||
|
|
||||||
|
static const u32 choiceItems[] = {
|
||||||
|
ITEM_CHOICE_SPECS,
|
||||||
|
ITEM_CHOICE_BAND,
|
||||||
|
ITEM_CHOICE_SCARF,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_COUNT(choiceItems); j++)
|
||||||
|
{
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; }
|
||||||
|
PARAMETRIZE{ ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; }
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS);
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
|
||||||
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||||
|
PLAYER(SPECIES_RHYDON)
|
||||||
|
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); }
|
||||||
|
OPPONENT(species) { Moves(move); }
|
||||||
|
} WHEN {
|
||||||
|
if (species == SPECIES_SWAMPERT || ability == ABILITY_KLUTZ) {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_YAWN); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trapped")
|
||||||
|
{
|
||||||
|
u32 j, aiAbility = ABILITY_NONE, playerAbility = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE;
|
||||||
|
|
||||||
|
static const u32 choiceItems[] = {
|
||||||
|
ITEM_CHOICE_SPECS,
|
||||||
|
ITEM_CHOICE_BAND,
|
||||||
|
ITEM_CHOICE_SCARF,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_COUNT(choiceItems); j++)
|
||||||
|
{
|
||||||
|
PARAMETRIZE{ aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; }
|
||||||
|
PARAMETRIZE{ aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; }
|
||||||
|
PARAMETRIZE{ aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; }
|
||||||
|
PARAMETRIZE{ aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; }
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN {
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS);
|
||||||
|
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
|
||||||
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||||
|
PLAYER(species) { Ability(playerAbility); }
|
||||||
|
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(aiAbility); }
|
||||||
|
OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); }
|
||||||
|
} WHEN {
|
||||||
|
if (playerAbility != ABILITY_ARENA_TRAP || aiAbility == ABILITY_KLUTZ) {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_YAWN); }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue