Fix Pickpocket moveend target checks and Thief/Covet handoff (#9037)

This commit is contained in:
GGbond 2026-01-30 02:02:35 +08:00 committed by GitHub
parent 08dd7da30c
commit 6928d54d46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 332 additions and 6 deletions

View File

@ -7003,11 +7003,21 @@ static void Cmd_moveend(void)
gBattleScripting.moveendState++;
break;
case MOVEEND_PICKPOCKET:
{
u16 attackerItem = gBattleMons[gBattlerAttacker].item;
bool32 hasPendingStolenItem = FALSE;
if (attackerItem == ITEM_NONE
&& GetMoveEffect(gCurrentMove) == EFFECT_STEAL_ITEM
&& gBattleStruct->changedItems[gBattlerAttacker] != ITEM_NONE)
{
attackerItem = gBattleStruct->changedItems[gBattlerAttacker];
hasPendingStolenItem = TRUE;
}
if (IsBattlerAlive(gBattlerAttacker)
&& gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item
&& !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker])) // But not knocked off
&& IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Obviously attack needs to have worked
&& attackerItem != ITEM_NONE // Attacker must have an item (including pending stolen item)
&& !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker]))) // But not knocked off
{
u8 battlers[4] = {0, 1, 2, 3};
SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item
@ -7018,12 +7028,19 @@ static void Cmd_moveend(void)
if (battler != gBattlerAttacker // Cannot pickpocket yourself
&& GetBattlerAbility(battler) == ABILITY_PICKPOCKET // Target must have pickpocket ability
&& IsBattlerTurnDamaged(battler) // Target needs to have been damaged
&& IsMoveMakingContact(gBattlerAttacker, battler, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact
&& !(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) // Move needs to have affected this battler
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) // Subsitute unaffected
&& IsBattlerAlive(battler) // Battler must be alive to pickpocket
&& gBattleMons[battler].item == ITEM_NONE // Pickpocketer can't have an item already
&& CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // Cannot steal plates, mega stones, etc
&& CanStealItem(battler, gBattlerAttacker, attackerItem)) // Cannot steal plates, mega stones, etc
{
gBattlerTarget = gBattlerAbility = battler;
if (hasPendingStolenItem)
{
gBattleMons[gBattlerAttacker].item = attackerItem;
gBattleStruct->changedItems[gBattlerAttacker] = ITEM_NONE;
}
// Battle scripting is super brittle so we shall do the item exchange now (if possible)
if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD)
StealTargetItem(gBattlerTarget, gBattlerAttacker); // Target takes attacker's item
@ -7037,6 +7054,7 @@ static void Cmd_moveend(void)
}
gBattleScripting.moveendState++;
break;
}
case MOVEEND_THIRD_MOVE_BLOCK:
switch (moveEffect)
{

View File

@ -1,4 +1,312 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Pickpocket (Ability) test titles")
ASSUMPTIONS
{
ASSUME(MoveMakesContact(MOVE_BREAKING_SWIPE));
ASSUME(MoveMakesContact(MOVE_SCRATCH));
}
DOUBLE_BATTLE_TEST("Pickpocket checks contact/effect per target for spread moves")
{
GIVEN {
ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY);
ASSUME(GetMoveType(MOVE_BREAKING_SWIPE) == TYPE_DRAGON);
ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
OPPONENT(SPECIES_CLEFAIRY);
} WHEN {
TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); }
} SCENE {
ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY);
EXPECT(playerLeft->item == ITEM_NONE);
}
}
DOUBLE_BATTLE_TEST("Pickpocket activates for the fastest itemless target when both are hit by a contact spread move")
{
GIVEN {
ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH);
PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_MAGOST_BERRY); }
PLAYER(SPECIES_WYNAUT) { Speed(10); }
OPPONENT(SPECIES_SNEASEL) { Speed(40); Ability(ABILITY_PICKPOCKET); }
OPPONENT(SPECIES_SNEASEL) { Speed(30); Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); }
} SCENE {
ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY);
EXPECT(opponentRight->item == ITEM_NONE);
EXPECT(playerLeft->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals the attacker's item unless it already has one")
{
bool32 targetHasItem;
PARAMETRIZE { targetHasItem = FALSE; }
PARAMETRIZE { targetHasItem = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(targetHasItem ? ITEM_EVIOLITE : ITEM_NONE); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
if (targetHasItem) {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
} else {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
} THEN {
if (targetHasItem) {
EXPECT(opponent->item == ITEM_EVIOLITE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
} else {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
}
SINGLE_BATTLE_TEST("Pickpocket does not activate if the user faints")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); HP(1); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player);
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
}
MESSAGE("The opposing Sneasel fainted!");
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
}
}
SINGLE_BATTLE_TEST("Pickpocket cannot steal from Sticky Hold")
{
GIVEN {
PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
ABILITY_POPUP(player, ABILITY_STICKY_HOLD);
MESSAGE("Grimer's item cannot be removed!");
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_MAGOST_BERRY);
}
}
SINGLE_BATTLE_TEST("Pickpocket cannot steal restricted held items")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_NORMALIUM_Z].holdEffect == HOLD_EFFECT_Z_CRYSTAL);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
}
} THEN {
EXPECT(opponent->item == ITEM_NONE);
EXPECT(player->item == ITEM_NORMALIUM_Z);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after the final hit of a multi-strike move")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_FURY_SWIPES) == EFFECT_MULTI_HIT);
ASSUME(MoveMakesContact(MOVE_FURY_SWIPES));
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_FURY_SWIPES, WITH_RNG(RNG_HITS, 3)); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player);
MESSAGE("The Pokémon was hit 3 time(s)!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Magician steals an item")
{
GIVEN {
PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(player, ABILITY_MAGICIAN);
MESSAGE("Delphox stole the opposing Sneasel's Magost Berry!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Delphox's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Sticky Barb transfers")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_STICKY_BARB].holdEffect == HOLD_EFFECT_STICKY_BARB);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_STICKY_BARB); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
MESSAGE("The Sticky Barb attached itself to Wobbuffet!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Sticky Barb!");
} THEN {
EXPECT(opponent->item == ITEM_STICKY_BARB);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Thief or Covet steals an item")
{
u16 move;
PARAMETRIZE { move = MOVE_THIEF; }
PARAMETRIZE { move = MOVE_COVET; }
GIVEN {
ASSUME(GetMoveEffect(move) == EFFECT_STEAL_ITEM);
ASSUME(MoveMakesContact(move));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
MESSAGE("Wobbuffet stole the opposing Sneasel's Magost Berry!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Focus Sash is consumed")
{
GIVEN {
ASSUME(MoveMakesContact(MOVE_SEISMIC_TOSS));
ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); Level(100); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_FOCUS_SASH); MaxHP(6); HP(6); }
} WHEN {
TURN { MOVE(player, MOVE_SEISMIC_TOSS); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player);
MESSAGE("The opposing Sneasel hung on using its Focus Sash!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket activates after Knock Off, Bug Bite, or Pluck")
{
u16 move;
PARAMETRIZE { move = MOVE_KNOCK_OFF; }
PARAMETRIZE { move = MOVE_BUG_BITE; }
PARAMETRIZE { move = MOVE_PLUCK; }
GIVEN {
ASSUME(MoveMakesContact(move));
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Item(ITEM_ORAN_BERRY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!");
} THEN {
EXPECT(opponent->item == ITEM_MAGOST_BERRY);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals Life Orb after it activates")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
MESSAGE("Wobbuffet was hurt by the Life Orb!");
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Life Orb!");
} THEN {
EXPECT(opponent->item == ITEM_LIFE_ORB);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket steals Shell Bell after it heals the user")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_SHELL_BELL].holdEffect == HOLD_EFFECT_SHELL_BELL);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHELL_BELL); MaxHP(100); HP(66); }
OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player);
HP_BAR(opponent);
HP_BAR(player);
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's Shell Bell!");
} THEN {
EXPECT(opponent->item == ITEM_SHELL_BELL);
EXPECT(player->item == ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinches")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH);
PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_KINGS_ROCK); }
OPPONENT(SPECIES_SNEASEL) { Speed(10); Ability(ABILITY_PICKPOCKET); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_HOLD_EFFECT_FLINCH, 1)); MOVE(opponent, MOVE_SCRATCH); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PICKPOCKET);
MESSAGE("The opposing Sneasel stole Wobbuffet's King's Rock!");
MESSAGE("The opposing Sneasel flinched and couldn't move!");
} THEN {
EXPECT(opponent->item == ITEM_KINGS_ROCK);
EXPECT(player->item == ITEM_NONE);
}
}