Partial implementation of item steal items

This commit is contained in:
Wlowscha 2025-06-08 20:10:32 +02:00
parent 2024281580
commit 1cb956e872
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
4 changed files with 173 additions and 176 deletions

View File

@ -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<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) {

View File

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

View File

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

View File

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