Fix AI handling of Trick/Bestow and add comprehensive tests (#8516)

This commit is contained in:
GGbond 2025-12-15 01:27:28 +08:00 committed by GitHub
parent 5700fb08a2
commit ded6a0fe18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 300 additions and 5 deletions

View File

@ -2057,6 +2057,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-20);
break;
case EFFECT_TRICK:
if ((gBattleMons[battlerAtk].item == ITEM_NONE && aiData->items[battlerDef] == ITEM_NONE)
|| !CanBattlerGetOrLoseItem(battlerAtk, gBattleMons[battlerAtk].item)
|| !CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef])
|| !CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef])
|| !CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerAtk].item)
|| aiData->abilities[battlerAtk] == ABILITY_STICKY_HOLD
|| aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move))
ADJUST_SCORE(-10);
case EFFECT_KNOCK_OFF:
case EFFECT_CORROSIVE_GAS:
if (aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD)
@ -2450,8 +2459,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_BESTOW:
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_NONE
|| !CanBattlerGetOrLoseItem(battlerAtk, gBattleMons[battlerAtk].item)) // AI knows its own item
if (gBattleMons[battlerAtk].item == ITEM_NONE
|| aiData->items[battlerDef] != ITEM_NONE
|| !CanBattlerGetOrLoseItem(battlerAtk, gBattleMons[battlerAtk].item) // AI knows its own item
|| !CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerAtk].item)
|| aiData->abilities[battlerAtk] == ABILITY_STICKY_HOLD
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move))
ADJUST_SCORE(-10);
break;
case EFFECT_WISH:

View File

@ -12698,7 +12698,7 @@ static void Cmd_trysethelpinghand(void)
}
}
// Trick // TODO: Sticky Hold
// Trick
static void Cmd_tryswapitems(void)
{
CMD_ARGS(const u8 *failInstr);
@ -12743,6 +12743,10 @@ static void Cmd_tryswapitems(void)
gBattlescriptCurrInstr = cmd->failInstr;
}
// check if ability prevents swapping
else if (GetBattlerAbility(gBattlerAttacker) == ABILITY_STICKY_HOLD)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD)
{
gBattlescriptCurrInstr = BattleScript_StickyHoldActivates;
@ -17475,6 +17479,7 @@ void BS_TryBestow(void)
|| gBattleMons[gBattlerTarget].item != ITEM_NONE
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerAttacker].item)
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item)
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_STICKY_HOLD
|| gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & (1u << gBattlerPartyIndexes[gBattlerTarget]))
{
gBattlescriptCurrInstr = cmd->failInstr;

View File

@ -56,6 +56,88 @@ AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any
}
}
AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow when items are missing or target already holds one")
{
u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE;
PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_NONE; targetItem = ITEM_NONE; }
PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_NONE; targetItem = ITEM_NONE; }
PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_LEFTOVERS; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Item(targetItem); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Item(atkItem); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, move); }
}
}
AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow with unexchangeable items")
{
u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE;
PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORANGE_MAIL; targetItem = ITEM_NONE; }
PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_ORANGE_MAIL; }
PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORANGE_MAIL; targetItem = ITEM_NONE; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Item(targetItem); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Item(atkItem); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, move); }
}
}
AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow around Sticky Hold")
{
u16 move = MOVE_NONE, atkItem = ITEM_ORAN_BERRY, targetItem = ITEM_NONE;
enum Ability atkAbility = ABILITY_PRESSURE, targetAbility = ABILITY_PRESSURE;
PARAMETRIZE { move = MOVE_TRICK; atkAbility = ABILITY_STICKY_HOLD; targetAbility = ABILITY_PRESSURE; targetItem = ITEM_LEFTOVERS; }
PARAMETRIZE { move = MOVE_TRICK; atkAbility = ABILITY_PRESSURE; targetAbility = ABILITY_STICKY_HOLD; targetItem = ITEM_LEFTOVERS; }
PARAMETRIZE { move = MOVE_BESTOW; atkAbility = ABILITY_STICKY_HOLD; targetAbility = ABILITY_PRESSURE; targetItem = ITEM_NONE; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Ability(targetAbility); Item(targetItem); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Ability(atkAbility); Item(atkItem); Moves(move, MOVE_SCRATCH); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { NOT_EXPECT_MOVE(opponentLeft, move); }
}
}
AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow if the target has a Substitute")
{
ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE);
u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE;
PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_LEFTOVERS; }
PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_NONE; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SUBSTITUTE, MOVE_CELEBRATE); Item(targetItem); Speed(20); }
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(20); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Item(atkItem); Speed(1); Attack(1); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); }
} WHEN {
TURN {
MOVE(playerLeft, MOVE_SUBSTITUTE);
MOVE(playerRight, MOVE_CELEBRATE);
}
TURN { NOT_EXPECT_MOVE(opponentLeft, move); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose Helping Hand")
{
s32 j;

View File

@ -92,6 +92,21 @@ SINGLE_BATTLE_TEST("Bestow fails if the user's held item is a Z-Crystal")
}
}
SINGLE_BATTLE_TEST("Bestow fails if the user has Sticky Hold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_BESTOW); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == ITEM_SITRUS_BERRY);
EXPECT(opponent->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Bestow fails if the target is behind a Substitute (Gen 6+)")
{
GIVEN {
@ -130,4 +145,3 @@ SINGLE_BATTLE_TEST("Bestow fails if the user's held item changes its form")
EXPECT(opponent->item == ITEM_NONE);
}
}

View File

@ -1,4 +1,185 @@
#include "global.h"
#include "test/battle.h"
#include "mail.h"
TO_DO_BATTLE_TEST("TODO: Write Trick (Move Effect) test titles")
SINGLE_BATTLE_TEST("Trick swaps held items")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} THEN {
EXPECT(player->item == ITEM_LUM_BERRY);
EXPECT(opponent->item == ITEM_SITRUS_BERRY);
}
}
SINGLE_BATTLE_TEST("Trick succeeds if only the user has an item")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} THEN {
EXPECT(player->item == ITEM_NONE);
EXPECT(opponent->item == ITEM_SITRUS_BERRY);
}
}
SINGLE_BATTLE_TEST("Trick succeeds if only the target has an item")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} THEN {
EXPECT(player->item == ITEM_LUM_BERRY);
EXPECT(opponent->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Trick fails if both battlers have no held item")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == ITEM_NONE);
EXPECT(opponent->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Trick fails if either item is Mail")
{
u16 atkItem = ITEM_NONE, defItem = ITEM_NONE;
ASSUME(ItemIsMail(ITEM_ORANGE_MAIL));
PARAMETRIZE { atkItem = ITEM_ORANGE_MAIL; defItem = ITEM_NONE; }
PARAMETRIZE { atkItem = ITEM_ORAN_BERRY; defItem = ITEM_ORANGE_MAIL; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(atkItem); }
OPPONENT(SPECIES_WOBBUFFET) { Item(defItem); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == atkItem);
EXPECT(opponent->item == defItem);
}
}
SINGLE_BATTLE_TEST("Trick fails if either item is a Z-Crystal")
{
u16 atkItem = ITEM_NONE, defItem = ITEM_NONE;
ASSUME(GetItemHoldEffect(ITEM_FIGHTINIUM_Z) == HOLD_EFFECT_Z_CRYSTAL);
PARAMETRIZE { atkItem = ITEM_FIGHTINIUM_Z; defItem = ITEM_NONE; }
PARAMETRIZE { atkItem = ITEM_SITRUS_BERRY; defItem = ITEM_FIGHTINIUM_Z; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(atkItem); }
OPPONENT(SPECIES_WOBBUFFET) { Item(defItem); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == atkItem);
EXPECT(opponent->item == defItem);
}
}
SINGLE_BATTLE_TEST("Trick fails if either battler holds a Mega Stone")
{
u16 atkItem = ITEM_NONE, defItem = ITEM_NONE;
u16 atkSpecies = SPECIES_WOBBUFFET, defSpecies = SPECIES_WOBBUFFET;
PARAMETRIZE { atkSpecies = SPECIES_BLAZIKEN; atkItem = ITEM_BLAZIKENITE; defSpecies = SPECIES_WOBBUFFET; defItem = ITEM_SITRUS_BERRY; }
PARAMETRIZE { atkSpecies = SPECIES_WOBBUFFET; atkItem = ITEM_SITRUS_BERRY; defSpecies = SPECIES_BLAZIKEN; defItem = ITEM_BLAZIKENITE; }
GIVEN {
PLAYER(atkSpecies) { Item(atkItem); }
OPPONENT(defSpecies) { Item(defItem); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == atkItem);
EXPECT(opponent->item == defItem);
}
}
SINGLE_BATTLE_TEST("Trick fails if an item changes the holder's form")
{
u16 atkItem = ITEM_NONE, defItem = ITEM_NONE;
PARAMETRIZE { atkItem = ITEM_GRISEOUS_CORE; defItem = ITEM_SITRUS_BERRY; }
PARAMETRIZE { atkItem = ITEM_SITRUS_BERRY; defItem = ITEM_GRISEOUS_CORE; }
GIVEN {
PLAYER(SPECIES_GIRATINA_ORIGIN) { Item(atkItem); }
OPPONENT(SPECIES_WOBBUFFET) { Item(defItem); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == atkItem);
EXPECT(opponent->item == defItem);
}
}
SINGLE_BATTLE_TEST("Trick fails if the user has Sticky Hold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == ITEM_SITRUS_BERRY);
EXPECT(opponent->item == ITEM_LUM_BERRY);
}
}
SINGLE_BATTLE_TEST("Trick fails against Sticky Hold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_LUM_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("The opposing Wobbuffet's Sticky Hold made Trick ineffective!");
} THEN {
EXPECT(player->item == ITEM_SITRUS_BERRY);
EXPECT(opponent->item == ITEM_LUM_BERRY);
}
}
SINGLE_BATTLE_TEST("Trick fails if the target is behind a Substitute")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); Speed(50); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); Speed(100); }
} WHEN {
TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_TRICK); }
} SCENE {
MESSAGE("But it failed!");
} THEN {
EXPECT(player->item == ITEM_SITRUS_BERRY);
EXPECT(opponent->item == ITEM_LUM_BERRY);
}
}