From 1cb956e872168482de2b6656e9aec7c525b73e7b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:10:32 +0200 Subject: [PATCH] Partial implementation of item steal items --- src/items/all-held-items.ts | 9 ++ src/items/held-item.ts | 22 ++-- src/items/held-items/item-steal.ts | 155 +++++++++++++++++++++++++++ src/modifier/modifier.ts | 163 +---------------------------- 4 files changed, 173 insertions(+), 176 deletions(-) create mode 100644 src/items/held-items/item-steal.ts diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 0efe7a7b0a3..d799874f67e 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -28,6 +28,11 @@ import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/fl import { type FRIENDSHIP_BOOST_PARAMS, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; +import { + ContactItemStealChanceHeldItem, + type ITEM_STEAL_PARAMS, + TurnEndItemStealHeldItem, +} from "./held-items/item-steal"; import { type MULTI_HIT_PARAMS, MultiHitHeldItem } from "./held-items/multi-hit"; import { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; import { @@ -123,6 +128,8 @@ export function initHeldItems() { allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2); allHeldItems[HeldItemId.GOLDEN_PUNCH] = new DamageMoneyRewardHeldItem(HeldItemId.GOLDEN_PUNCH, 5); allHeldItems[HeldItemId.BATON] = new BatonHeldItem(HeldItemId.BATON, 1); + allHeldItems[HeldItemId.GRIP_CLAW] = new ContactItemStealChanceHeldItem(HeldItemId.GRIP_CLAW, 5, 10); + allHeldItems[HeldItemId.MINI_BLACK_HOLE] = new TurnEndItemStealHeldItem(HeldItemId.MINI_BLACK_HOLE, 1); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -156,6 +163,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_PARAMS; [ITEM_EFFECT.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; [ITEM_EFFECT.BATON]: BATON_PARAMS; + [ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; + [ITEM_EFFECT.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; }; export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 01c1210de47..ac6b3929a60 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -27,6 +27,10 @@ export const ITEM_EFFECT = { MULTI_HIT: 19, DAMAGE_MONEY_REWARD: 20, BATON: 21, + TURN_END_ITEM_STEAL: 22, + CONTACT_ITEM_STEAL_CHANCE: 23, + // EVO_TRACKER: 40, + // BASE_STAT_TOTAL: 50, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; @@ -48,28 +52,16 @@ export class HeldItem { this.isSuppressable = true; } - getName(): string { + get name(): string { return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.name`) + " (new)"; } - getDescription(): string { + get description(): string { return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.description`); } - getIcon(): string { - return `${HeldItemNames[this.type]?.toLowerCase()}`; - } - - get name(): string { - return ""; - } - - get description(): string { - return ""; - } - get iconName(): string { - return ""; + return `${HeldItemNames[this.type]?.toLowerCase()}`; } // TODO: Aren't these fine as just properties to set in the subclass definition? diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts new file mode 100644 index 00000000000..1e338c98d9a --- /dev/null +++ b/src/items/held-items/item-steal.ts @@ -0,0 +1,155 @@ +import Pokemon from "#app/field/pokemon"; +import { randSeedFloat } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { allHeldItems } from "../all-held-items"; +import { globalScene } from "#app/global-scene"; + +export interface ITEM_STEAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The pokemon to steal from (optional) */ + target?: Pokemon; +} + +// constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { + +/** + * Abstract class for held items that steal other Pokemon's items. + * @see {@linkcode TurnHeldItemTransferModifier} + * @see {@linkcode ContactHeldItemTransferChanceModifier} + */ +export abstract class ItemTransferHeldItem extends HeldItem { + /** + * Steals an item, chosen randomly, from a set of target Pokemon. + * @param pokemon The {@linkcode Pokemon} holding this item + * @param target The {@linkcode Pokemon} to steal from (optional) + * @param _args N/A + * @returns `true` if an item was stolen; false otherwise. + */ + apply(params: ITEM_STEAL_PARAMS): boolean { + const opponents = this.getTargets(params); + + if (!opponents.length) { + return false; + } + + const pokemon = params.pokemon; + //TODO: Simplify this logic here + const targetPokemon = opponents[pokemon.randBattleSeedInt(opponents.length)]; + + const transferredItemCount = this.getTransferredItemCount(params); + if (!transferredItemCount) { + return false; + } + + // TODO: Change this logic to use held items + const transferredModifierTypes: HeldItemId[] = []; + const heldItems = targetPokemon.heldItemManager.getHeldItemKeys(); + + for (let i = 0; i < transferredItemCount; i++) { + if (!heldItems.length) { + break; + } + const randItemIndex = pokemon.randBattleSeedInt(heldItems.length); + const randItem = heldItems[randItemIndex]; + // TODO: Fix this after updating the various methods in battle-scene.ts + if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) { + transferredModifierTypes.push(randItem); + heldItems.splice(randItemIndex, 1); + } + } + + for (const mt of transferredModifierTypes) { + globalScene.phaseManager.queueMessage(this.getTransferMessage(params, mt)); + } + + return !!transferredModifierTypes.length; + } + + abstract getTargets(params: ITEM_STEAL_PARAMS): Pokemon[]; + + abstract getTransferredItemCount(params: ITEM_STEAL_PARAMS): number; + + abstract getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string; +} + +/** + * Modifier for held items that steal items from the enemy at the end of + * each turn. + * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} + */ +export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_ITEM_STEAL]; + isTransferable = true; + + /** + * Determines the targets to transfer items from when this applies. + * @param pokemon the {@linkcode Pokemon} holding this item + * @param _args N/A + * @returns the opponents of the source {@linkcode Pokemon} + */ + getTargets(params: ITEM_STEAL_PARAMS): Pokemon[] { + return params.pokemon instanceof Pokemon ? params.pokemon.getOpponents() : []; + } + + getTransferredItemCount(_params: ITEM_STEAL_PARAMS): number { + return 1; + } + + getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + return i18next.t("modifier:turnHeldItemTransferApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(params.target), + itemName: allHeldItems[itemId].name, + pokemonName: params.pokemon.getNameToRender(), + typeName: this.name, + }); + } + + setTransferrableFalse(): void { + this.isTransferable = false; + } +} + +/** + * Modifier for held items that add a chance to steal items from the target of a + * successful attack. + * @see {@linkcode modifierTypes[GRIP_CLAW]} + * @see {@linkcode HeldItemTransferModifier} + */ +export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]; + public readonly chance: number; + + constructor(type: HeldItemId, maxStackCount = 1, chancePercent: number) { + super(type, maxStackCount); + + this.chance = chancePercent / 100; + } + + /** + * Determines the target to steal items from when this applies. + * @param _holderPokemon The {@linkcode Pokemon} holding this item + * @param targetPokemon The {@linkcode Pokemon} the holder is targeting with an attack + * @returns The target {@linkcode Pokemon} as array for further use in `apply` implementations + */ + getTargets(params: ITEM_STEAL_PARAMS): Pokemon[] { + return params.target ? [params.target] : []; + } + + getTransferredItemCount(params: ITEM_STEAL_PARAMS): number { + const stackCount = params.pokemon.heldItemManager.getStack(this.type); + return randSeedFloat() <= this.chance * stackCount ? 1 : 0; + } + + getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + return i18next.t("modifier:contactHeldItemTransferApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(params.target), + itemName: allHeldItems[itemId].name, + pokemonName: params.pokemon.getNameToRender(), + typeName: this.name, + }); + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 8d076995a5d..dc187f9334b 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -4,7 +4,8 @@ import { getLevelTotalExp } from "#app/data/exp"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; -import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { LearnMoveType } from "#app/phases/learn-move-phase"; @@ -2005,166 +2006,6 @@ export class BoostBugSpawnModifier extends PersistentModifier { } } -/** - * Abstract class for held items that steal other Pokemon's items. - * @see {@linkcode TurnHeldItemTransferModifier} - * @see {@linkcode ContactHeldItemTransferChanceModifier} - */ -export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { - /** - * Determines the targets to transfer items from when this applies. - * @param pokemon the {@linkcode Pokemon} holding this item - * @param _args N/A - * @returns the opponents of the source {@linkcode Pokemon} - */ - getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] { - return pokemon instanceof Pokemon ? pokemon.getOpponents() : []; - } - - /** - * Steals an item, chosen randomly, from a set of target Pokemon. - * @param pokemon The {@linkcode Pokemon} holding this item - * @param target The {@linkcode Pokemon} to steal from (optional) - * @param _args N/A - * @returns `true` if an item was stolen; false otherwise. - */ - override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean { - const opponents = this.getTargets(pokemon, target); - - if (!opponents.length) { - return false; - } - - const targetPokemon = opponents[pokemon.randBattleSeedInt(opponents.length)]; - - const transferredItemCount = this.getTransferredItemCount(); - if (!transferredItemCount) { - return false; - } - - const transferredModifierTypes: ModifierType[] = []; - const itemModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, - targetPokemon.isPlayer(), - ) as PokemonHeldItemModifier[]; - - for (let i = 0; i < transferredItemCount; i++) { - if (!itemModifiers.length) { - break; - } - const randItemIndex = pokemon.randBattleSeedInt(itemModifiers.length); - const randItem = itemModifiers[randItemIndex]; - if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) { - transferredModifierTypes.push(randItem.type); - itemModifiers.splice(randItemIndex, 1); - } - } - - for (const mt of transferredModifierTypes) { - globalScene.phaseManager.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); - } - - return !!transferredModifierTypes.length; - } - - abstract getTransferredItemCount(): number; - - abstract getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string; -} - -/** - * Modifier for held items that steal items from the enemy at the end of - * each turn. - * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} - */ -export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { - isTransferable = true; - - matchType(modifier: Modifier): boolean { - return modifier instanceof TurnHeldItemTransferModifier; - } - - clone(): TurnHeldItemTransferModifier { - return new TurnHeldItemTransferModifier(this.type, this.pokemonId, this.stackCount); - } - - getTransferredItemCount(): number { - return this.getStackCount(); - } - - getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { - return i18next.t("modifier:turnHeldItemTransferApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), - itemName: item.name, - pokemonName: pokemon.getNameToRender(), - typeName: this.type.name, - }); - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } - - setTransferrableFalse(): void { - this.isTransferable = false; - } -} - -/** - * Modifier for held items that add a chance to steal items from the target of a - * successful attack. - * @see {@linkcode modifierTypes[GRIP_CLAW]} - * @see {@linkcode HeldItemTransferModifier} - */ -export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { - public readonly chance: number; - - constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.chance = chancePercent / 100; - } - - /** - * Determines the target to steal items from when this applies. - * @param _holderPokemon The {@linkcode Pokemon} holding this item - * @param targetPokemon The {@linkcode Pokemon} the holder is targeting with an attack - * @returns The target {@linkcode Pokemon} as array for further use in `apply` implementations - */ - override getTargets(_holderPokemon: Pokemon, targetPokemon: Pokemon): Pokemon[] { - return targetPokemon ? [targetPokemon] : []; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof ContactHeldItemTransferChanceModifier; - } - - clone(): ContactHeldItemTransferChanceModifier { - return new ContactHeldItemTransferChanceModifier(this.type, this.pokemonId, this.chance * 100, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.chance * 100); - } - - getTransferredItemCount(): number { - return randSeedFloat() <= this.chance * this.getStackCount() ? 1 : 0; - } - - getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { - return i18next.t("modifier:contactHeldItemTransferApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), - itemName: item.name, - pokemonName: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }); - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 5; - } -} - export class IvScannerModifier extends PersistentModifier { constructor(type: ModifierType, _stackCount?: number) { super(type);