updated corrosive gas to give item back

This commit is contained in:
muscode13 2024-11-05 00:37:14 -06:00
parent 9b1689451a
commit 81534e3ac0
5 changed files with 315 additions and 51 deletions

View File

@ -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();
}

View File

@ -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),

View File

@ -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 {

View File

@ -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) {

View File

@ -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(salazzle.getHeldItems().length).toEqual(salazzleHeldItemCt);
expect(staka.getHeldItems().length).toBeLessThan(stakaHeldItemCt);
expect(karp1.getHeldItems().length).toBeLessThan(magikarpItemCt1);
expect(karp2.getHeldItems().length).toBeLessThan(magikarpItemCt2);
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);
});
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];
const giratinaHeldItemCt = giratina.getHeldItems().length;
const aggronHeldItemCt = aggron.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.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
game.move.select(Moves.CORROSIVE_GAS);
await game.phaseInterceptor.to("TurnEndPhase");
await game.toNextTurn();
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(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];
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("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.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);
});
});