Reworked various effects that steal items

This commit is contained in:
Wlowscha 2025-06-09 14:43:00 +02:00
parent c607a73ebc
commit b271dc724b
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
5 changed files with 50 additions and 53 deletions

View File

@ -2717,11 +2717,12 @@ export default class BattleScene extends SceneBase {
} }
/** /**
* Try to transfer a held item to another pokemon. * Try to transfer a held item from source to target.
* If the recepient already has the maximum amount allowed for this item, the transfer is cancelled. * If the recepient already has the maximum amount allowed for this item, the transfer is cancelled.
* The quantity to transfer is automatically capped at how much the recepient can take before reaching the maximum stack size for the item. * The quantity to transfer is automatically capped at how much the recepient can take before reaching the maximum stack size for the item.
* A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful. * A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful.
* @param heldItemId {@linkcode HeldItemId} item to transfer * @param heldItemId {@linkcode HeldItemId} item to transfer
* @param source {@linkcode Pokemon} giver in this transfer
* @param target {@linkcode Pokemon} recepient in this transfer * @param target {@linkcode Pokemon} recepient in this transfer
* @param playSound `true` to play a sound when transferring the item * @param playSound `true` to play a sound when transferring the item
* @param transferQuantity How many items of the stack to transfer. Optional, defaults to `1` * @param transferQuantity How many items of the stack to transfer. Optional, defaults to `1`

View File

@ -35,7 +35,7 @@ import {
} from "#app/data/moves/move"; } from "#app/data/moves/move";
import { allMoves } from "../data-lists"; import { allMoves } from "../data-lists";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag";
import { BerryModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { BerryModifier, type PokemonHeldItemModifier } from "#app/modifier/modifier";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { import {
SpeciesFormChangeAbilityTrigger, SpeciesFormChangeAbilityTrigger,
@ -91,6 +91,7 @@ import type Move from "#app/data/moves/move";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves";
import { HeldItemId } from "#enums/held-item-id"; import { HeldItemId } from "#enums/held-item-id";
import { allHeldItems } from "#app/items/all-held-items";
export class BlockRecoilDamageAttr extends AbAttr { export class BlockRecoilDamageAttr extends AbAttr {
constructor() { constructor() {
@ -2540,17 +2541,11 @@ export class AllyStatMultiplierAbAttr extends AbAttr {
* @extends AbAttr * @extends AbAttr
*/ */
export class ExecutedMoveAbAttr extends AbAttr { export class ExecutedMoveAbAttr extends AbAttr {
canApplyExecutedMove( canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean {
_pokemon: Pokemon,
_simulated: boolean,
): boolean {
return true; return true;
} }
applyExecutedMove( applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {}
_pokemon: Pokemon,
_simulated: boolean,
): void {}
} }
/** /**
@ -2558,7 +2553,7 @@ export class ExecutedMoveAbAttr extends AbAttr {
* @extends ExecutedMoveAbAttr * @extends ExecutedMoveAbAttr
*/ */
export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
constructor(showAbility: boolean = false) { constructor(showAbility = false) {
super(showAbility); super(showAbility);
} }
@ -2575,7 +2570,7 @@ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
private stealCondition: PokemonAttackCondition | null; private stealCondition: PokemonAttackCondition | null;
private stolenItem?: PokemonHeldItemModifier; private stolenItem?: HeldItemId;
constructor(stealCondition?: PokemonAttackCondition) { constructor(stealCondition?: PokemonAttackCondition) {
super(); super();
@ -2598,11 +2593,11 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
hitResult < HitResult.NO_EFFECT && hitResult < HitResult.NO_EFFECT &&
(!this.stealCondition || this.stealCondition(pokemon, defender, move)) (!this.stealCondition || this.stealCondition(pokemon, defender, move))
) { ) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); const heldItems = defender.heldItemManager.getTransferableHeldItems();
if (heldItems.length) { if (heldItems.length) {
// Ensure that the stolen item in testing is the same as when the effect is applied // Ensure that the stolen item in testing is the same as when the effect is applied
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { if (globalScene.canTransferHeldItem(this.stolenItem, defender, pokemon)) {
return true; return true;
} }
} }
@ -2620,28 +2615,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
_hitResult: HitResult, _hitResult: HitResult,
_args: any[], _args: any[],
): void { ): void {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); const heldItems = defender.heldItemManager.getTransferableHeldItems();
if (!this.stolenItem) { if (!this.stolenItem) {
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
} }
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { if (globalScene.tryTransferHeldItem(this.stolenItem, defender, pokemon, false)) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:postAttackStealHeldItem", { i18next.t("abilityTriggers:postAttackStealHeldItem", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
defenderName: defender.name, defenderName: defender.name,
stolenItemType: this.stolenItem.type.name, stolenItemType: allHeldItems[this.stolenItem].name,
}), }),
); );
} }
this.stolenItem = undefined; this.stolenItem = undefined;
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
target.isPlayer(),
) as PokemonHeldItemModifier[];
}
} }
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
@ -2762,7 +2750,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition?: PokemonDefendCondition; private condition?: PokemonDefendCondition;
private stolenItem?: PokemonHeldItemModifier; private stolenItem?: HeldItemId;
constructor(condition?: PokemonDefendCondition) { constructor(condition?: PokemonDefendCondition) {
super(); super();
@ -2780,10 +2768,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
_args: any[], _args: any[],
): boolean { ): boolean {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); const heldItems = attacker.heldItemManager.getTransferableHeldItems();
if (heldItems.length) { if (heldItems.length) {
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { if (globalScene.canTransferHeldItem(this.stolenItem, attacker, pokemon)) {
return true; return true;
} }
} }
@ -2800,28 +2788,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
_hitResult: HitResult, _hitResult: HitResult,
_args: any[], _args: any[],
): void { ): void {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); const heldItems = attacker.heldItemManager.getTransferableHeldItems();
if (!this.stolenItem) { if (!this.stolenItem) {
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
} }
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { if (globalScene.tryTransferHeldItem(this.stolenItem, attacker, pokemon, false)) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:postDefendStealHeldItem", { i18next.t("abilityTriggers:postDefendStealHeldItem", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
attackerName: attacker.name, attackerName: attacker.name,
stolenItemType: this.stolenItem.type.name, stolenItemType: allHeldItems[this.stolenItem].name,
}), }),
); );
} }
this.stolenItem = undefined; this.stolenItem = undefined;
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
target.isPlayer(),
) as PokemonHeldItemModifier[];
}
} }
/** /**
@ -7782,7 +7763,7 @@ export function applyPreAttackAbAttrs(
export function applyExecutedMoveAbAttrs( export function applyExecutedMoveAbAttrs(
attrType: Constructor<ExecutedMoveAbAttr>, attrType: Constructor<ExecutedMoveAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<ExecutedMoveAbAttr>( applyAbAttrsInternal<ExecutedMoveAbAttr>(

View File

@ -2559,16 +2559,18 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
return false; return false;
} }
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); const heldItems = target.heldItemManager.getTransferableHeldItems();
if (!heldItems.length) { if (!heldItems.length) {
return false; return false;
} }
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)];
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier); // const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; // const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) { // const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
// const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)];
if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) {
return false; return false;
} }
@ -2576,18 +2578,13 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
return true; return true;
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target); const heldItems = target.heldItemManager.getTransferableHeldItems();
return heldItems.length ? 5 : 0; return heldItems.length ? 5 : 0;
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target); const heldItems = target.heldItemManager.getTransferableHeldItems();
return heldItems.length ? -5 : 0; return heldItems.length ? -5 : 0;
} }
} }

View File

@ -35,6 +35,24 @@ export class PokemonItemManager {
return Object.keys(this.heldItems).map(k => Number(k)); return Object.keys(this.heldItems).map(k => Number(k));
} }
getTransferableHeldItems(): number[] {
return Object.keys(this.heldItems)
.filter(k => allHeldItems[k].isTransferable)
.map(k => Number(k));
}
getStealableHeldItems(): number[] {
return Object.keys(this.heldItems)
.filter(k => allHeldItems[k].isStealable)
.map(k => Number(k));
}
getSuppressableHeldItems(): number[] {
return Object.keys(this.heldItems)
.filter(k => allHeldItems[k].isSuppressable)
.map(k => Number(k));
}
hasItem(itemType: HeldItemId): boolean { hasItem(itemType: HeldItemId): boolean {
return itemType in this.heldItems; return itemType in this.heldItems;
} }

View File

@ -47,7 +47,7 @@ export abstract class ItemTransferHeldItem extends HeldItem {
// TODO: Change this logic to use held items // TODO: Change this logic to use held items
const transferredModifierTypes: HeldItemId[] = []; const transferredModifierTypes: HeldItemId[] = [];
const heldItems = targetPokemon.heldItemManager.getHeldItemKeys(); const heldItems = targetPokemon.heldItemManager.getTransferableHeldItems();
for (let i = 0; i < transferredItemCount; i++) { for (let i = 0; i < transferredItemCount; i++) {
if (!heldItems.length) { if (!heldItems.length) {
@ -56,7 +56,7 @@ export abstract class ItemTransferHeldItem extends HeldItem {
const randItemIndex = pokemon.randBattleSeedInt(heldItems.length); const randItemIndex = pokemon.randBattleSeedInt(heldItems.length);
const randItem = heldItems[randItemIndex]; const randItem = heldItems[randItemIndex];
// TODO: Fix this after updating the various methods in battle-scene.ts // TODO: Fix this after updating the various methods in battle-scene.ts
if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) { if (globalScene.tryTransferHeldItem(randItem, targetPokemon, pokemon, false)) {
transferredModifierTypes.push(randItem); transferredModifierTypes.push(randItem);
heldItems.splice(randItemIndex, 1); heldItems.splice(randItemIndex, 1);
} }