Improved item transfer

This commit is contained in:
Wlowscha 2025-06-09 18:14:26 +02:00
parent 928d8a8f97
commit ff73c5b038
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
4 changed files with 36 additions and 26 deletions

View File

@ -2759,8 +2759,9 @@ export default class BattleScene extends SceneBase {
} }
const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack);
const data = source.heldItemManager[heldItemId].data;
source.heldItemManager.remove(heldItemId, countTaken); source.heldItemManager.remove(heldItemId, countTaken);
target.heldItemManager.add(heldItemId, countTaken); target.heldItemManager.add(heldItemId, countTaken, data);
if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);

View File

@ -70,7 +70,6 @@ import { allAbilities, allMoves } from "../data-lists";
import { import {
BerryModifier, BerryModifier,
PokemonHeldItemModifier, PokemonHeldItemModifier,
PokemonMultiHitModifier,
PreserveBerryModifier, PreserveBerryModifier,
} from "../../modifier/modifier"; } from "../../modifier/modifier";
import type { BattlerIndex } from "../../battle"; import type { BattlerIndex } from "../../battle";
@ -122,8 +121,10 @@ import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
import { TrainerVariant } from "#app/field/trainer"; import { TrainerVariant } from "#app/field/trainer";
import { SelectBiomePhase } from "#app/phases/select-biome-phase"; 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 { 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 MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: 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); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); 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; power.value = 60;
} }
@ -919,7 +920,7 @@ export default class Move implements Localizable {
* Returns `true` if this move can be given additional strikes * Returns `true` if this move can be given additional strikes
* by enhancing effects. * by enhancing effects.
* Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} * 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 user The {@linkcode Pokemon} using the move
* @param restrictSpread `true` if the enhancing effect * @param restrictSpread `true` if the enhancing effect
* should not affect multi-target moves (default `false`) * 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 { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// first, determine if the hit is coming from multi lens or not // 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) { if (lensCount <= 0) {
// no multi lenses; we can just halve the target's hp and call it a day // 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); (args[0] as NumberHolder).value = toDmgValue(target.hp / 2);
@ -2574,7 +2575,12 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
return false; 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; return true;
} }
@ -2630,10 +2636,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
// Considers entire transferrable item pool by default (Knock Off). // Considers entire transferrable item pool by default (Knock Off).
// Otherwise only consider berries (Incinerate). // Otherwise only consider berries (Incinerate).
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); let heldItems = target.heldItemManager.getTransferableHeldItems();
if (this.berriesOnly) { 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) { if (!heldItems.length) {
@ -2647,9 +2653,11 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
globalScene.updateModifiers(target.isPlayer()); globalScene.updateModifiers(target.isPlayer());
if (this.berriesOnly) { 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 { } 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; 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 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 attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
const heldItems = target.getHeldItems().filter(i => i.isTransferable); const heldItems = target.heldItemManager.getTransferableHeldItems();
if (heldItems.length === 0) { if (heldItems.length === 0) {
return ""; 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 }); const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName });
return message; return message;
}; };
@ -9123,7 +9131,7 @@ export function initMoves() {
.condition((user, target, move) => !target.status && !target.isSafeguarded(user)) .condition((user, target, move) => !target.status && !target.isSafeguarded(user))
.reflectable(), .reflectable(),
new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) 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) .attr(RemoveHeldItemAttr, false)
.edgeCase(), .edgeCase(),
// Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc. // 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) .condition((user, target, move) => !target.turnData.acted)
.attr(ForceLastAttr), .attr(ForceLastAttr),
new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) 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) new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5)
.ignoresSubstitute() .ignoresSubstitute()
.attr(CopyTypeAttr), .attr(CopyTypeAttr),

View File

@ -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_TOTAL_DATA } from "#app/items/held-items/base-stat-total";
import type { BASE_STAT_FLAT_DATA } from "#app/items/held-items/base-stat-flat"; 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 { interface HeldItemProperties {
stack: number; stack: number;
disabled: boolean; disabled: boolean;
cooldown?: number; cooldown?: number;
data?: BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; data?: HELD_ITEM_DATA;
} }
type HeldItemPropertyMap = { type HeldItemPropertyMap = {
@ -66,14 +68,14 @@ export class PokemonItemManager {
return itemType in this.heldItems ? this.heldItems[itemType].stack : 0; 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(); const maxStack = allHeldItems[itemType].getMaxStackCount();
if (this.hasItem(itemType)) { if (this.hasItem(itemType)) {
// TODO: We may want an error message of some kind instead // TODO: We may want an error message of some kind instead
this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack); this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack);
} else { } else {
this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false }; this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data };
} }
} }

View File

@ -92,7 +92,6 @@ import {
ShinyRateBoosterModifier, ShinyRateBoosterModifier,
TempStatStageBoosterModifier, TempStatStageBoosterModifier,
TempCritBoosterModifier, TempCritBoosterModifier,
EvoTrackerModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; 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 { PokemonItemManager } from "./pokemon-held-item-manager";
import { applyHeldItems } from "#app/items/all-held-items"; import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item"; 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 { export enum LearnMoveSituation {
MISC, MISC,
@ -4375,7 +4374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.setScale(this.getSpriteScale()); this.setScale(this.getSpriteScale());
this.loadAssets().then(() => { this.loadAssets().then(() => {
this.calculateStats(); this.calculateStats();
globalScene.updateModifiers(this.isPlayer(), true); globalScene.updateModifiers(this.isPlayer());
Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve());
}); });
}); });
@ -5801,9 +5800,9 @@ export class PlayerPokemon extends Pokemon {
}); });
}; };
if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) { 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) { if (evotracker) {
globalScene.removeModifier(evotracker); this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true);
} }
} }
if (!globalScene.gameMode.isDaily || this.metBiome > -1) { if (!globalScene.gameMode.isDaily || this.metBiome > -1) {
@ -5911,7 +5910,7 @@ export class PlayerPokemon extends Pokemon {
const updateAndResolve = () => { const updateAndResolve = () => {
this.loadAssets().then(() => { this.loadAssets().then(() => {
this.calculateStats(); this.calculateStats();
globalScene.updateModifiers(true, true); globalScene.updateModifiers(true);
this.updateInfo(true).then(() => resolve()); this.updateInfo(true).then(() => resolve());
}); });
}; };
@ -5983,7 +5982,7 @@ export class PlayerPokemon extends Pokemon {
true, true,
) as PokemonHeldItemModifier[]; ) as PokemonHeldItemModifier[];
for (const modifier of fusedPartyMemberHeldModifiers) { 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.updateModifiers(true);
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];