diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c47127c651..95fffd0513 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7754,6 +7754,15 @@ BattleScript_CheekPouchActivates:: copybyte gBattlerAttacker, sSAVED_BATTLER return +BattleScript_PickupActivates:: + pause 5 + tryrecycleitem BattleScript_PickupActivatesEnd + call BattleScript_AbilityPopUp + printstring STRINGID_XFOUNDONEY + waitmessage B_WAIT_TIME_LONG +BattleScript_PickupActivatesEnd: + end3 + BattleScript_HarvestActivates:: pause 5 tryrecycleitem BattleScript_HarvestActivatesEnd diff --git a/include/battle.h b/include/battle.h index ace178c0e7..0d54f2a614 100644 --- a/include/battle.h +++ b/include/battle.h @@ -676,6 +676,7 @@ struct BattleStruct u16 chosenItem[MAX_BATTLERS_COUNT]; u16 choicedMove[MAX_BATTLERS_COUNT]; u16 changedItems[MAX_BATTLERS_COUNT]; + u8 canPickupItem; u8 switchInBattlerCounter; u8 arenaTurnCounter; u8 turnSideTracker; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 6bdb88b28f..cd35e1de82 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -295,6 +295,7 @@ extern const u8 BattleScript_WeakArmorActivates[]; extern const u8 BattleScript_FellStingerRaisesStat[]; extern const u8 BattleScript_SnowWarningActivatesHail[]; extern const u8 BattleScript_SnowWarningActivatesSnow[]; +extern const u8 BattleScript_PickupActivates[]; extern const u8 BattleScript_HarvestActivates[]; extern const u8 BattleScript_ImposterActivates[]; extern const u8 BattleScript_SelectingNotAllowedMoveAssaultVest[]; diff --git a/include/battle_util.h b/include/battle_util.h index 8eadf47c98..de05242902 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -231,6 +231,8 @@ bool32 TryRoomService(u32 battler); void BufferStatChange(u32 battler, u8 statId, u8 stringId); bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget); u16 GetUsedHeldItem(u32 battler); +bool32 PickupHasValidTarget(u32 battler); +bool32 CantPickupItem(u32 battler); bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags); u32 GetBattlerMoveTargetType(u32 battler, u32 move); bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move); diff --git a/include/random.h b/include/random.h index cd7e26b589..d254a08f03 100644 --- a/include/random.h +++ b/include/random.h @@ -173,12 +173,14 @@ enum RandomTag RNG_G_MAX_BEFUDDLE, RNG_G_MAX_REPLENISH, RNG_G_MAX_SNOOZE, + RNG_HARVEST, RNG_HITS, RNG_HOLD_EFFECT_FLINCH, RNG_INFATUATION, RNG_LOADED_DICE, RNG_METRONOME, RNG_PARALYSIS, + RNG_PICKUP, RNG_POISON_POINT, RNG_POISON_TOUCH, RNG_RAMPAGE_TURNS, diff --git a/src/battle_main.c b/src/battle_main.c index 55337a045e..10f27e921f 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3219,6 +3219,7 @@ void SwitchInClearSetData(u32 battler) gBattleStruct->lastMoveFailed &= ~(gBitTable[battler]); gBattleStruct->palaceFlags &= ~(gBitTable[battler]); gBattleStruct->boosterEnergyActivates &= ~(gBitTable[battler]); + gBattleStruct->canPickupItem &= ~(1u << battler); for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) { @@ -5112,6 +5113,7 @@ static void TurnValuesCleanUp(bool8 var0) if (gDisableStructs[i].rechargeTimer == 0) gBattleMons[i].status2 &= ~STATUS2_RECHARGE; } + gBattleStruct->canPickupItem &= ~(1u << i); } if (gDisableStructs[i].substituteHP == 0) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index afc63fed9b..6ba28f5027 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8071,6 +8071,7 @@ static void Cmd_removeitem(void) gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = itemId; // Remember if switched out gBattleMons[battler].item = ITEM_NONE; + gBattleStruct->canPickupItem |= (1u << battler); CheckSetUnburden(battler); BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battler].item), &gBattleMons[battler].item); @@ -14853,7 +14854,10 @@ static void Cmd_tryrecycleitem(void) u16 *usedHeldItem; - usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; + if (gCurrentMove == MOVE_NONE && GetBattlerAbility(gBattlerAttacker) == ABILITY_PICKUP) + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerTarget]][GetBattlerSide(gBattlerTarget)]; + else + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; if (*usedHeldItem != ITEM_NONE && gBattleMons[gBattlerAttacker].item == ITEM_NONE) { gLastUsedItem = *usedHeldItem; diff --git a/src/battle_util.c b/src/battle_util.c index c18b0544a0..8729d0cf1a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4950,8 +4950,19 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattlerAttacker = battler; switch (gLastUsedAbility) { + case ABILITY_PICKUP: + if (gBattleMons[battler].item == ITEM_NONE + && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item + && PickupHasValidTarget(battler)) + { + gBattlerTarget = RandomUniformExcept(RNG_PICKUP, 0, gBattlersCount - 1, CantPickupItem); + gLastUsedItem = GetUsedHeldItem(gBattlerTarget); + BattleScriptPushCursorAndCallback(BattleScript_PickupActivates); + effect++; + } + break; case ABILITY_HARVEST: - if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || Random() % 2 == 0) + if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || RandomPercentage(RNG_HARVEST, 50)) && gBattleMons[battler].item == ITEM_NONE && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item && ItemId_GetPocket(GetUsedHeldItem(battler)) == POCKET_BERRIES) @@ -11398,6 +11409,25 @@ u16 GetUsedHeldItem(u32 battler) return gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]; } +bool32 CantPickupItem(u32 battler) +{ + // Used by RandomUniformExcept() for RNG_PICKUP + if (battler == gBattlerAttacker && gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)) + return TRUE; + return !(IsBattlerAlive(battler) && GetUsedHeldItem(battler) && gBattleStruct->canPickupItem & (1u << battler)); +} + +bool32 PickupHasValidTarget(u32 battler) +{ + u32 i; + for (i = 0; i < gBattlersCount; i++) + { + if (!CantPickupItem(i)) + return TRUE; + } + return FALSE; +} + bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags) { if (gBattleWeather & weatherFlags && WEATHER_HAS_EFFECT) diff --git a/test/battle/ability/harvest.c b/test/battle/ability/harvest.c index b4783c4542..f19e0715de 100644 --- a/test/battle/ability/harvest.c +++ b/test/battle/ability/harvest.c @@ -1,18 +1,264 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn"); -TO_DO_BATTLE_TEST("Harvest always restores a Berry in Sunlight"); -TO_DO_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field"); -TO_DO_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Fling"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift"); +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); + ASSUME(gMovesInfo[MOVE_SUNNY_DAY].effect == EFFECT_SUNNY_DAY); +} + +SINGLE_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest always restores a Berry in Sunlight") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Fling") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + TO_DO_BATTLE_TEST("Harvest only works once per turn"); // Check for berries that are consumed immediately, like Pecha Berry -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup"); -TO_DO_BATTLE_TEST("Harvest order is affected by speed"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry"); + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Harvest order is affected by speed") +{ + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Speed(2); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerRight, MOVE_BULLDOZE); MOVE(playerLeft, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_HARVEST); + ABILITY_POPUP(playerLeft, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponentLeft->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} + +SINGLE_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + ASSUME(gItemsInfo[ITEM_APICOT_BERRY].holdEffect == HOLD_EFFECT_SP_DEFENSE_UP); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); Item(ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} diff --git a/test/battle/ability/pickup.c b/test/battle/ability/pickup.c new file mode 100644 index 0000000000..57f6429feb --- /dev/null +++ b/test/battle/ability/pickup.c @@ -0,0 +1,295 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); +} + +SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant the user their item") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant another Pokémon's popped Air Balloon") +{ + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Air Balloon!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item not used that turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item after its holder faints") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if holder is replaced") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it destroyed the item with Incinerate") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it knocked off that item") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if the user eats it with Bug Bite/Pluck") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if its user already restored it") +{ + ASSUME(gMovesInfo[MOVE_RECYCLE].effect == EFFECT_RECYCLE); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that has been Flinged") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that was used by Natural Gift") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup triggers based on Speed order") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(1); Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(opponentRight->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickup grants a random item used by another Pokémon") +{ + PASSES_RANDOMLY(1, 3, RNG_PICKUP); + ASSUME(gItemsInfo[ITEM_WHITE_HERB].holdEffect == HOLD_EFFECT_RESTORE_STATS); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup doesn't trigger more than once per turn") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { HP(1); Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_GT(playerLeft->hp, 1); + EXPECT_LT(playerLeft->hp, playerLeft->maxHP/2); + } +} diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index dca175c637..b865ef2f1c 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1234,7 +1234,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Replenish recycles allies' berries 50\% of t GIVEN { ASSUME(gMovesInfo[MOVE_G_MAX_REPLENISH].argument == MAX_EFFECT_RECYCLE_BERRIES); PLAYER(SPECIES_SNORLAX) { Item(ITEM_APICOT_BERRY); GigantamaxFactor(TRUE); } - PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); } + PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); Ability(ABILITY_THICK_FAT); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } } WHEN { diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 92789710f7..6f318bbead 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -410,7 +410,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 if (STATE->trials == 1) { u32 n = 0, i; - for (i = lo; i < hi; i++) + for (i = lo; i <= hi; i++) if (!reject(i)) n++; STATE->trials = n;