From 9b1689451a50302a9dff0b090b8e0c072ccb5bd5 Mon Sep 17 00:00:00 2001 From: muscode13 Date: Mon, 4 Nov 2024 20:09:49 -0600 Subject: [PATCH 1/2] implement corrosive gas --- src/data/move.ts | 10 ++- src/test/moves/corrosive_gas.test.ts | 110 +++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/test/moves/corrosive_gas.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 6e350315e65..0f251c723b8 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2374,8 +2374,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { * @returns {boolean} True if an item was removed */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) - return false; + if (move.id !== Moves.CORROSIVE_GAS) { + if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) + return false; + } } if (move.hitsSubstitute(user, target)) { @@ -2407,6 +2409,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { if (this.berriesOnly) { user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + } else if (move.id === Moves.CORROSIVE_GAS) { + user.scene.queueMessage(i18next.t("moveTriggers:corrosiveGasItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } else { user.scene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } @@ -10034,7 +10038,7 @@ export function initMoves() { .makesContact(false), new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) .target(MoveTarget.ALL_NEAR_OTHERS) - .unimplemented(), + .attr(RemoveHeldItemAttr, false), new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1) .target(MoveTarget.NEAR_ALLY), diff --git a/src/test/moves/corrosive_gas.test.ts b/src/test/moves/corrosive_gas.test.ts new file mode 100644 index 00000000000..aa410cf6ab8 --- /dev/null +++ b/src/test/moves/corrosive_gas.test.ts @@ -0,0 +1,110 @@ +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Corrosive Gas", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.CORROSIVE_GAS ]) + .battleType("double") + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove enemy and player items", async () => { + game.override + .enemyHeldItems([ + { name: "SOUL_DEW", count: 1 }, + { name: "LUCKY_EGG", count: 1 }, + { name: "LEFTOVERS", count: 1 }, + { name: "GRIP_CLAW", count: 1 }, + { name: "MULTI_LENS", count: 1 }, + ]) + .startingHeldItems( + [ + { name: "SOUL_DEW", count: 1 }, + { name: "LUCKY_EGG", count: 1 }, + { name: "LEFTOVERS", count: 1 }, + { name: "GRIP_CLAW", count: 1 }, + { name: "MULTI_LENS", count: 1 }, + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); + const playerPokemon = game.scene.getPlayerField()!; + const enemyPokemon = game.scene.getEnemyField()!; + + const staka = playerPokemon[0]; + const salazzle = playerPokemon[1]; + const karp1 = enemyPokemon[0]; + const karp2 = enemyPokemon[1]; + + const stakaHeldItemCt = staka.getHeldItems().length; + const salazzleHeldItemCt = salazzle.getHeldItems().length; + const magikarpItemCt1 = karp1.getHeldItems().length; + const magikarpItemCt2 = karp2.getHeldItems().length; + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("BerryPhase"); + + expect(salazzle.getHeldItems().length).toEqual(salazzleHeldItemCt); + expect(staka.getHeldItems().length).toBeLessThan(stakaHeldItemCt); + expect(karp1.getHeldItems().length).toBeLessThan(magikarpItemCt1); + expect(karp2.getHeldItems().length).toBeLessThan(magikarpItemCt2); + }); + + it("should not remove untransferrable items", async () => { + game.override + .enemyMoveset(Moves.CORROSIVE_GAS) + .enemyHeldItems([ + { name: "BASE_STAT_BOOSTER", count: 1 }, + { name: "TEMP_STAT_STAGE_BOOSTER", count: 1 } + ]) + .startingHeldItems( + [ + { name: "FORM_CHANGE_ITEM", count: 1 }, + { name: "BASE_STAT_BOOSTER", count: 1 }, + { name: "TEMP_STAT_STAGE_BOOSTER", count: 1 } + ] + ); + await game.classicMode.startBattle([ Species.GIRATINA, Species.AGGRON ]); + const playerPokemon = game.scene.getPlayerField()!; + const enemyPokemon = game.scene.getEnemyField()!; + + const giratina = playerPokemon[0]; + const aggron = playerPokemon[1]; + const karp1 = enemyPokemon[0]; + const karp2 = enemyPokemon[1]; + + const giratinaHeldItemCt = giratina.getHeldItems().length; + const aggronHeldItemCt = aggron.getHeldItems().length; + const magikarpItemCt1 = karp1.getHeldItems().length; + const magikarpItemCt2 = karp2.getHeldItems().length; + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + + expect(giratina.getHeldItems().length).toEqual(giratinaHeldItemCt); + expect(aggron.getHeldItems().length).toEqual(aggronHeldItemCt); + expect(karp1.getHeldItems().length).toEqual(magikarpItemCt1); + expect(karp2.getHeldItems().length).toEqual(magikarpItemCt2); + }); + +}); From 81534e3ac031f0bdceaa04f43609006573dd3326 Mon Sep 17 00:00:00 2001 From: muscode13 Date: Tue, 5 Nov 2024 00:37:14 -0600 Subject: [PATCH 2/2] updated corrosive gas to give item back --- src/battle-scene.ts | 5 +- src/data/move.ts | 63 ++++++- src/modifier/modifier.ts | 13 +- src/phases/battle-end-phase.ts | 11 +- src/test/moves/corrosive_gas.test.ts | 272 +++++++++++++++++++++++---- 5 files changed, 314 insertions(+), 50 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d82acec1c20..c01942e428b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2760,9 +2760,12 @@ export default class BattleScene extends SceneBase { modifiers.splice(modifiers.indexOf(modifier), 1); } } + const nullifiedModifiers = modifiers.filter(modifier => + !(modifier instanceof PokemonHeldItemModifier) || !modifier.isNullified + ); this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant).then(() => { - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); + (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(nullifiedModifiers); if (!player) { this.updateUIPositions(); } diff --git a/src/data/move.ts b/src/data/move.ts index 0f251c723b8..ac64a0f4115 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2374,10 +2374,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { * @returns {boolean} True if an item was removed */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (move.id !== Moves.CORROSIVE_GAS) { - if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) - return false; - } + if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) + return false; } if (move.hitsSubstitute(user, target)) { @@ -2409,8 +2407,6 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { if (this.berriesOnly) { user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); - } else if (move.id === Moves.CORROSIVE_GAS) { - user.scene.queueMessage(i18next.t("moveTriggers:corrosiveGasItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } else { user.scene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } @@ -7506,6 +7502,59 @@ export class ExposedMoveAttr extends AddBattlerTagAttr { } } +/** + * Nullifies a Pokemon's held item until the battle ends. + * Simulates the item being removed, but it just neutralizes it until the next battle + * Used by: {@linkcode Moves.CORROSIVE_GAS | Corrosive Gas} + * + * @extends MoveEffectAttr + * @see {@linkcode apply} + */ +export class NullifyHeldItemAttr extends MoveEffectAttr { + constructor() { + super(false, { trigger: MoveEffectTrigger.HIT }); + } + + /** + * + * @param user {@linkcode Pokemon} that used the move + * @param target Target {@linkcode Pokemon} that the moves applies to + * @param move {@linkcode Move} that is used + * @param args N/A + * @returns {boolean} True if an item was nullified + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (move.hitsSubstitute(user, target)) { + return false; + } + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft + if (cancelled.value === true) { + return false; + } + + const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); + + if (heldItems.length) { + const nullifiedItem = heldItems[user.randSeedInt(heldItems.length)]; + + nullifiedItem.nullify(); + nullifiedItem.isTransferable = false; + target.scene.updateModifiers(target.isPlayer()); + + user.scene.queueMessage(i18next.t("moveTriggers:corrosiveGasItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: nullifiedItem.type.name })); + + } + applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); + return true; + } + + getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { + return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + } +} + const unknownTypeCondition: MoveConditionFunc = (user, target, move) => !user.getTypes().includes(Type.UNKNOWN); @@ -10038,7 +10087,7 @@ export function initMoves() { .makesContact(false), new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) .target(MoveTarget.ALL_NEAR_OTHERS) - .attr(RemoveHeldItemAttr, false), + .attr(NullifyHeldItemAttr), new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1) .target(MoveTarget.NEAR_ALLY), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0891262649c..de9e52cdab5 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -645,6 +645,7 @@ export class TerastallizeAccessModifier extends PersistentModifier { export abstract class PokemonHeldItemModifier extends PersistentModifier { public pokemonId: number; public isTransferable: boolean = true; + public isNullified: boolean = false; constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, stackCount); @@ -655,13 +656,21 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { abstract matchType(_modifier: Modifier): boolean; match(modifier: Modifier) { - return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId; + return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId && !this.isNullified; } getArgs(): any[] { return [ this.pokemonId ]; } + nullify() { + this.isNullified = true; + } + + removeNullification() { + this.isNullified = false; + } + /** * Applies the {@linkcode PokemonHeldItemModifier} to the given {@linkcode Pokemon}. * @param pokemon The {@linkcode Pokemon} that holds the held item @@ -676,7 +685,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { * @returns if {@linkcode PokemonHeldItemModifier} should be applied */ override shouldApply(pokemon?: Pokemon, ..._args: unknown[]): boolean { - return !!pokemon && (this.pokemonId === -1 || pokemon.id === this.pokemonId); + return !this.isNullified && !!pokemon && (this.pokemonId === -1 || pokemon.id === this.pokemonId); } isIconVisible(scene: BattleScene): boolean { diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 3b9ca012ef7..57fa020a41a 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -1,6 +1,6 @@ import BattleScene from "#app/battle-scene"; import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/ability"; -import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; +import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; import { GameOverPhase } from "./game-over-phase"; @@ -43,6 +43,15 @@ export class BattleEndPhase extends BattlePhase { for (const pokemon of this.scene.getPokemonAllowedInBattle()) { applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + const heldItems = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier) as PokemonHeldItemModifier[]; + for (const item of heldItems) { + if (item.isNullified) { + item.removeNullification(); + if (item.isTransferable === false) { + item.isTransferable = true; + } + } + } } if (this.scene.currentBattle.moneyScattered) { diff --git a/src/test/moves/corrosive_gas.test.ts b/src/test/moves/corrosive_gas.test.ts index aa410cf6ab8..a8ead57c5f6 100644 --- a/src/test/moves/corrosive_gas.test.ts +++ b/src/test/moves/corrosive_gas.test.ts @@ -1,3 +1,6 @@ +import { Abilities } from "#app/enums/abilities"; +import { BerryType } from "#app/enums/berry-type"; +import { WeatherType } from "#app/enums/weather-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -27,22 +30,14 @@ describe("Moves - Corrosive Gas", () => { .enemyMoveset(Moves.SPLASH); }); - it("should remove enemy and player items", async () => { + it("Corrosive Gas should nullify enemy and ally items", async () => { game.override .enemyHeldItems([ - { name: "SOUL_DEW", count: 1 }, - { name: "LUCKY_EGG", count: 1 }, - { name: "LEFTOVERS", count: 1 }, - { name: "GRIP_CLAW", count: 1 }, - { name: "MULTI_LENS", count: 1 }, + { name: "LEFTOVERS", count: 1 } ]) .startingHeldItems( [ - { name: "SOUL_DEW", count: 1 }, - { name: "LUCKY_EGG", count: 1 }, - { name: "LEFTOVERS", count: 1 }, - { name: "GRIP_CLAW", count: 1 }, - { name: "MULTI_LENS", count: 1 }, + { name: "LEFTOVERS", count: 1 } ] ); await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); @@ -50,61 +45,260 @@ describe("Moves - Corrosive Gas", () => { const enemyPokemon = game.scene.getEnemyField()!; const staka = playerPokemon[0]; - const salazzle = playerPokemon[1]; const karp1 = enemyPokemon[0]; const karp2 = enemyPokemon[1]; - const stakaHeldItemCt = staka.getHeldItems().length; - const salazzleHeldItemCt = salazzle.getHeldItems().length; - const magikarpItemCt1 = karp1.getHeldItems().length; - const magikarpItemCt2 = karp2.getHeldItems().length; + staka.hp *= 0.5; + karp1.hp *= 0.5; + karp2.hp *= 0.5; + + const stakaHeldItems = staka.getHeldItems(); + const magikarpItems1 = karp1.getHeldItems(); + const magikarpItems2 = karp2.getHeldItems(); game.move.select(Moves.SPLASH); game.move.select(Moves.CORROSIVE_GAS); - await game.phaseInterceptor.to("BerryPhase"); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + expect(stakaHeldItems[0].isNullified).toEqual(true); + expect(magikarpItems1[0].isNullified).toEqual(true); + expect(magikarpItems2[0].isNullified).toEqual(true); - expect(salazzle.getHeldItems().length).toEqual(salazzleHeldItemCt); - expect(staka.getHeldItems().length).toBeLessThan(stakaHeldItemCt); - expect(karp1.getHeldItems().length).toBeLessThan(magikarpItemCt1); - expect(karp2.getHeldItems().length).toBeLessThan(magikarpItemCt2); + expect(staka.getHpRatio()).toBeCloseTo(0.5); + expect(karp1.getHpRatio()).toBeCloseTo(0.5); + expect(karp2.getHpRatio()).toBeCloseTo(0.5); }); - it("should not remove untransferrable items", async () => { + it("Items should not be nullified the following battle", async () => { game.override - .enemyMoveset(Moves.CORROSIVE_GAS) .enemyHeldItems([ - { name: "BASE_STAT_BOOSTER", count: 1 }, - { name: "TEMP_STAT_STAGE_BOOSTER", count: 1 } + { name: "LEFTOVERS", count: 1 } ]) .startingHeldItems( [ - { name: "FORM_CHANGE_ITEM", count: 1 }, - { name: "BASE_STAT_BOOSTER", count: 1 }, - { name: "TEMP_STAT_STAGE_BOOSTER", count: 1 } + { name: "LEFTOVERS", count: 1 } + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); + const playerPokemon = game.scene.getPlayerField()!; + const staka = playerPokemon[0]; + staka.hp *= 0.5; + const stakaHeldItems = staka.getHeldItems(); + + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + expect(stakaHeldItems[0].isNullified).toEqual(true); + expect(staka.getHpRatio()).toBeCloseTo(0.5); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + game.doSelectModifier(); + await game.phaseInterceptor.to("TurnInitPhase"); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(stakaHeldItems[0].isNullified).toEqual(false); + expect(staka.getHpRatio()).toBeGreaterThan(0.5); + }); + + it("Corrosive Gas should not remove untransferrable items", async () => { + game.override + .enemyMoveset(Moves.CORROSIVE_GAS) + .startingHeldItems( + [ + { name: "MYSTERY_ENCOUNTER_MACHO_BRACE", count: 1 } ] ); await game.classicMode.startBattle([ Species.GIRATINA, Species.AGGRON ]); const playerPokemon = game.scene.getPlayerField()!; + const giratina = playerPokemon[0]; + const giratinaHeldItems = giratina.getHeldItems(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(giratinaHeldItems[0].isNullified).toEqual(false); + }); + + it("Corrosive Gas should not nullify items from sticky hold users", async () => { + game.override + .enemyAbility(Abilities.STICKY_HOLD) + .enemyHeldItems([ + { name: "LEFTOVERS", count: 1 } + ]) + .startingHeldItems( + [ + { name: "LEFTOVERS", count: 1 } + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); + const playerPokemon = game.scene.getPlayerField()!; const enemyPokemon = game.scene.getEnemyField()!; - const giratina = playerPokemon[0]; - const aggron = playerPokemon[1]; + const staka = playerPokemon[0]; + const karp1 = enemyPokemon[0]; + const karp2 = enemyPokemon[1]; + + staka.hp *= 0.5; + karp1.hp *= 0.5; + karp2.hp *= 0.5; + + const stakaHeldItems = staka.getHeldItems(); + const magikarpItems1 = karp1.getHeldItems(); + const magikarpItems2 = karp2.getHeldItems(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + expect(stakaHeldItems[0].isNullified).toEqual(true); + expect(magikarpItems1[0].isNullified).toEqual(false); + expect(magikarpItems2[0].isNullified).toEqual(false); + + expect(staka.getHpRatio()).toBeCloseTo(0.5); + expect(karp1.getHpRatio()).toBeGreaterThan(0.5); + expect(karp2.getHpRatio()).toBeGreaterThan(0.5); + }); + + it("Corrosive Gas should not nullify items if it hits a substitute", async () => { + game.override + .moveset([ Moves.SPLASH, Moves.CORROSIVE_GAS, Moves.SUBSTITUTE ]) + .enemyHeldItems([ + { name: "LEFTOVERS", count: 1 } + ]) + .startingHeldItems( + [ + { name: "LEFTOVERS", count: 1 } + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); + const playerPokemon = game.scene.getPlayerField()!; + const staka = playerPokemon[0]; + staka.hp *= 0.5; + + const stakaHeldItems = staka.getHeldItems(); + + game.move.select(Moves.SUBSTITUTE); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + expect(stakaHeldItems[0].isNullified).toEqual(false); + + expect(staka.getHpRatio()).toBeGreaterThan(0.25); + }); + + it("Items nullified cannot be reobtained with Harvest", async () => { + game.override + .weather(WeatherType.SUNNY) + .ability(Abilities.HARVEST) + .enemyAbility(Abilities.HARVEST) + .enemyHeldItems([ + { name: "BERRY", type: BerryType.SITRUS, count: 1 }, + ]) + .startingHeldItems( + [ + { name: "BERRY", type: BerryType.SITRUS, count: 1 }, + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE ]); + const playerPokemon = game.scene.getPlayerField()!; + const enemyPokemon = game.scene.getEnemyField()!; + + const staka = playerPokemon[0]; const karp1 = enemyPokemon[0]; const karp2 = enemyPokemon[1]; - const giratinaHeldItemCt = giratina.getHeldItems().length; - const aggronHeldItemCt = aggron.getHeldItems().length; - const magikarpItemCt1 = karp1.getHeldItems().length; - const magikarpItemCt2 = karp2.getHeldItems().length; + staka.hp *= 0.1; + karp1.hp *= 0.1; + karp2.hp *= 0.1; + + const stakaHeldItems = staka.getHeldItems(); + const magikarpItems1 = karp1.getHeldItems(); + const magikarpItems2 = karp2.getHeldItems(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.phaseInterceptor.to("BerryPhase"); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + + expect(stakaHeldItems[0].isNullified).toEqual(true); + expect(magikarpItems1[0].isNullified).toEqual(true); + expect(magikarpItems2[0].isNullified).toEqual(true); - expect(giratina.getHeldItems().length).toEqual(giratinaHeldItemCt); - expect(aggron.getHeldItems().length).toEqual(aggronHeldItemCt); - expect(karp1.getHeldItems().length).toEqual(magikarpItemCt1); - expect(karp2.getHeldItems().length).toEqual(magikarpItemCt2); + expect(staka.getHpRatio()).toBeCloseTo(0.1); + expect(karp1.getHpRatio()).toBeCloseTo(0.1); + expect(karp2.getHpRatio()).toBeCloseTo(0.1); }); + it("Items stay nullified on switch out", async () => { + game.override + .enemyHeldItems([ + { name: "LEFTOVERS", count: 1 } + ]) + .startingHeldItems( + [ + { name: "LEFTOVERS", count: 1 } + ] + ); + await game.classicMode.startBattle([ Species.STAKATAKA, Species.SALAZZLE, Species.SALANDIT ]); + const playerPokemon = game.scene.getPlayerField()!; + const enemyPokemon = game.scene.getEnemyField()!; + + const staka = playerPokemon[0]; + const karp1 = enemyPokemon[0]; + const karp2 = enemyPokemon[1]; + + staka.hp *= 0.5; + karp1.hp *= 0.5; + karp2.hp *= 0.5; + + const stakaHeldItems = staka.getHeldItems(); + const magikarpItems1 = karp1.getHeldItems(); + const magikarpItems2 = karp2.getHeldItems(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.CORROSIVE_GAS); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); + + expect(stakaHeldItems[0].isNullified).toEqual(true); + expect(magikarpItems1[0].isNullified).toEqual(true); + expect(magikarpItems2[0].isNullified).toEqual(true); + + expect(staka.getHpRatio()).toBeCloseTo(0.5); + expect(karp1.getHpRatio()).toBeCloseTo(0.5); + expect(karp2.getHpRatio()).toBeCloseTo(0.5); + }); });