diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 276904da191..1168cb1e59f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2759,8 +2759,9 @@ export default class BattleScene extends SceneBase { } const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); + const data = source.heldItemManager[heldItemId].data; source.heldItemManager.remove(heldItemId, countTaken); - target.heldItemManager.add(heldItemId, countTaken); + target.heldItemManager.add(heldItemId, countTaken, data); if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 84a1ee49df9..94c9da520fe 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -70,7 +70,6 @@ import { allAbilities, allMoves } from "../data-lists"; import { BerryModifier, PokemonHeldItemModifier, - PokemonMultiHitModifier, PreserveBerryModifier, } from "../../modifier/modifier"; import type { BattlerIndex } from "../../battle"; @@ -122,8 +121,10 @@ import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; import { TrainerVariant } from "#app/field/trainer"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; -import { applyHeldItems } from "#app/items/all-held-items"; +import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; import { ITEM_EFFECT } from "#app/items/held-item"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; +import { HeldItemId } from "#enums/held-item-id"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -816,7 +817,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)) { power.value = 60; } @@ -919,7 +920,7 @@ export default class Move implements Localizable { * Returns `true` if this move can be given additional strikes * by enhancing effects. * Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} - * and {@linkcode PokemonMultiHitModifier | Multi-Lens}. + * and {@linkcode MultiHitHeldItem | Multi-Lens}. * @param user The {@linkcode Pokemon} using the move * @param restrictSpread `true` if the enhancing effect * should not affect multi-target moves (default `false`) @@ -1506,7 +1507,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // first, determine if the hit is coming from multi lens or not - const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; + const lensCount = user.heldItemManager.getStack(HeldItemId.MULTI_LENS); if (lensCount <= 0) { // no multi lenses; we can just halve the target's hp and call it a day (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); @@ -2574,7 +2575,12 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", + { pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + itemName: allHeldItems[stolenItem].name + } + )); return true; } @@ -2630,10 +2636,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Considers entire transferrable item pool by default (Knock Off). // Otherwise only consider berries (Incinerate). - let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); + let heldItems = target.heldItemManager.getTransferableHeldItems(); if (this.berriesOnly) { - heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer()); + heldItems = heldItems.filter(m => m in Object.values(berryTypeToHeldItem)); } if (!heldItems.length) { @@ -2647,9 +2653,11 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { globalScene.updateModifiers(target.isPlayer()); if (this.berriesOnly) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", + { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } else { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", + { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } return true; @@ -7932,14 +7940,14 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST); -const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; +const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.heldItemManager.getTransferableHeldItems().length > 0; const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { - const heldItems = target.getHeldItems().filter(i => i.isTransferable); + const heldItems = target.heldItemManager.getTransferableHeldItems(); if (heldItems.length === 0) { return ""; } - const itemName = heldItems[0]?.type?.name ?? "item"; + const itemName = allHeldItems[heldItems[0]].name ?? "item"; const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName }); return message; }; @@ -9123,7 +9131,7 @@ export function initMoves() { .condition((user, target, move) => !target.status && !target.isSafeguarded(user)) .reflectable(), new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => target.heldItemManager.getTransferableHeldItems().length > 0 ? 1.5 : 1) .attr(RemoveHeldItemAttr, false) .edgeCase(), // Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc. @@ -9838,7 +9846,7 @@ export function initMoves() { .condition((user, target, move) => !target.turnData.acted) .attr(ForceLastAttr), new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))), + .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.heldItemManager.getTransferableHeldItems().reduce((v, m) => v + user.heldItemManager.getStack(m), 0))), new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5) .ignoresSubstitute() .attr(CopyTypeAttr), diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 88e025599d5..a07e3ee2911 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -4,11 +4,13 @@ import type { FormChangeItem } from "#app/data/pokemon-forms"; import type { BASE_STAT_TOTAL_DATA } from "#app/items/held-items/base-stat-total"; import type { BASE_STAT_FLAT_DATA } from "#app/items/held-items/base-stat-flat"; +type HELD_ITEM_DATA = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; + interface HeldItemProperties { stack: number; disabled: boolean; cooldown?: number; - data?: BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; + data?: HELD_ITEM_DATA; } type HeldItemPropertyMap = { @@ -66,14 +68,14 @@ export class PokemonItemManager { return itemType in this.heldItems ? this.heldItems[itemType].stack : 0; } - add(itemType: HeldItemId, addStack = 1) { + add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA) { const maxStack = allHeldItems[itemType].getMaxStackCount(); if (this.hasItem(itemType)) { // TODO: We may want an error message of some kind instead this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack); } else { - this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false }; + this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data }; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5c92f83f7f7..051175ede3f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -92,7 +92,6 @@ import { ShinyRateBoosterModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, - EvoTrackerModifier, } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; @@ -242,7 +241,7 @@ import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { PokemonItemManager } from "./pokemon-held-item-manager"; import { applyHeldItems } from "#app/items/all-held-items"; import { ITEM_EFFECT } from "#app/items/held-item"; -import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItemId } from "#enums/held-item-id"; export enum LearnMoveSituation { MISC, @@ -4375,7 +4374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(this.isPlayer(), true); + globalScene.updateModifiers(this.isPlayer()); Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); }); }); @@ -5801,9 +5800,9 @@ export class PlayerPokemon extends Pokemon { }); }; if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) { - const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; + const evotracker = this.heldItemManager.hasItem(HeldItemId.GIMMIGHOUL_EVO_TRACKER); if (evotracker) { - globalScene.removeModifier(evotracker); + this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true); } } if (!globalScene.gameMode.isDaily || this.metBiome > -1) { @@ -5911,7 +5910,7 @@ export class PlayerPokemon extends Pokemon { const updateAndResolve = () => { this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(true, true); + globalScene.updateModifiers(true); this.updateInfo(true).then(() => resolve()); }); }; @@ -5983,7 +5982,7 @@ export class PlayerPokemon extends Pokemon { true, ) as PokemonHeldItemModifier[]; for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); + globalScene.tryTransferHeldItem(modifier, pokemon, this, false, modifier.getStackCount(), true, false); } globalScene.updateModifiers(true); globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];