Refactored parameters passed to .apply() methods; introduced generic .applyHeldItems() function; all HeldItems classes specify an ITEM_EFFECT

This commit is contained in:
Wlowscha 2025-06-01 23:00:15 +02:00
parent db580a6735
commit 7ba7c9c529
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
10 changed files with 110 additions and 86 deletions

View File

@ -124,7 +124,8 @@ 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 { applyAttackTypeBoosterHeldItem } from "#app/items/held-items/attack-type-booster";
import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
@ -853,7 +854,11 @@ export default class Move implements Localizable {
if (!this.hasAttr(TypelessAttr)) {
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
applyAttackTypeBoosterHeldItem(source, typeChangeHolder.value, power);
applyHeldItems(ITEM_EFFECT.ATTACK_TYPE_BOOST, {
pokemon: source,
moveType: typeChangeHolder.value,
movePower: power,
});
}
if (source.getTag(HelpingHandTag)) {

View File

@ -1,8 +1,15 @@
import { HeldItems } from "#enums/held-items";
import type { PokemonType } from "#enums/pokemon-type";
import { AttackTypeBoosterHeldItem, attackTypeToHeldItem } from "./held-items/attack-type-booster";
import { HitHealHeldItem } from "./held-items/hit-heal";
import { TurnHealHeldItem } from "./held-items/turn-heal";
import { ITEM_EFFECT } from "./held-item";
import {
type ATTACK_TYPE_BOOST_PARAMS,
AttackTypeBoosterHeldItem,
attackTypeToHeldItem,
} from "./held-items/attack-type-booster";
import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal";
import type { RESET_NEGATIVE_STAT_STAGE_PARAMS } from "./held-items/reset-negative-stat-stage";
import type { TURN_END_HEAL_PARAMS } from "./held-items/turn-end-heal";
import { TurnEndHealHeldItem } from "./held-items/turn-end-heal";
export const allHeldItems = {};
@ -12,7 +19,25 @@ export function initHeldItems() {
const pokemonType = Number(typeKey) as PokemonType;
allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2);
}
allHeldItems[HeldItems.LEFTOVERS] = new TurnHealHeldItem(HeldItems.LEFTOVERS, 4);
allHeldItems[HeldItems.LEFTOVERS] = new TurnEndHealHeldItem(HeldItems.LEFTOVERS, 4);
allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.LEFTOVERS, 4);
console.log(allHeldItems);
}
type APPLY_HELD_ITEMS_PARAMS = {
[ITEM_EFFECT.ATTACK_TYPE_BOOST]: ATTACK_TYPE_BOOST_PARAMS;
[ITEM_EFFECT.TURN_END_HEAL]: TURN_END_HEAL_PARAMS;
[ITEM_EFFECT.HIT_HEAL]: HIT_HEAL_PARAMS;
[ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS;
};
export function applyHeldItems<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) {
const pokemon = params.pokemon;
if (pokemon) {
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
if (allHeldItems[item].effects.includes(effect)) {
allHeldItems[item].apply(params);
}
}
}
}

View File

@ -2,6 +2,15 @@ import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import type { HeldItems } from "#enums/held-items";
export const ITEM_EFFECT = {
ATTACK_TYPE_BOOST: 1,
TURN_END_HEAL: 2,
HIT_HEAL: 3,
RESET_NEGATIVE_STAT_STAGE: 4,
} as const;
export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT];
export class HeldItem {
// public pokemonId: number;
public type: HeldItems;
@ -109,20 +118,10 @@ export class HeldItem {
}
export class ConsumableHeldItem extends HeldItem {
applyConsumable(_pokemon: Pokemon): boolean {
consume(pokemon: Pokemon, isPlayer: boolean): boolean {
pokemon.heldItemManager.remove(this.type, 1);
// TODO: Turn this into updateItemBar or something
globalScene.updateModifiers(isPlayer);
return true;
}
apply(pokemon: Pokemon, isPlayer: boolean): boolean {
const consumed = this.applyConsumable(pokemon);
if (consumed) {
pokemon.heldItemManager.remove(this.type, 1);
// TODO: Turn this into updateItemBar or something
globalScene.updateModifiers(isPlayer);
return true;
}
return false;
}
}

View File

@ -3,8 +3,16 @@ import { PokemonType } from "#enums/pokemon-type";
import i18next from "i18next";
import type { NumberHolder } from "#app/utils/common";
import type Pokemon from "#app/field/pokemon";
import { HeldItem } from "#app/items/held-item";
import { allHeldItems } from "../all-held-items";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
export interface ATTACK_TYPE_BOOST_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
/** The resolved type of the move */
moveType: PokemonType;
/** Holder for the damage value */
movePower: NumberHolder;
}
interface AttackTypeToHeldItemMap {
[key: number]: HeldItems;
@ -32,6 +40,7 @@ export const attackTypeToHeldItem: AttackTypeToHeldItemMap = {
};
export class AttackTypeBoosterHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_HEAL];
public moveType: PokemonType;
public powerBoost: number;
@ -55,20 +64,13 @@ export class AttackTypeBoosterHeldItem extends HeldItem {
return `${HeldItemNames[this.type]?.toLowerCase()}`;
}
apply(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder): void {
apply(params: ATTACK_TYPE_BOOST_PARAMS): void {
const pokemon = params.pokemon;
const moveType = params.moveType;
const movePower = params.movePower;
const stackCount = pokemon.heldItemManager.getStack(this.type);
if (moveType === this.moveType && movePower.value >= 1) {
movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost));
}
}
}
export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) {
if (pokemon) {
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) {
allHeldItems[item].apply(pokemon, moveType, movePower);
}
}
}
}

View File

@ -1,13 +1,19 @@
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import i18next from "i18next";
import { HeldItem } from "#app/items/held-item";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { toDmgValue } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages";
import { allHeldItems } from "../all-held-items";
export interface HIT_HEAL_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
}
export class HitHealHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_HEAL];
get name(): string {
return i18next.t("modifierType:ModifierType.SHELL_BELL.name");
}
@ -25,7 +31,8 @@ export class HitHealHeldItem extends HeldItem {
* @param pokemon The {@linkcode Pokemon} that holds the item
* @returns `true` if the {@linkcode Pokemon} was healed
*/
apply(pokemon: Pokemon): boolean {
apply(params: HIT_HEAL_PARAMS): boolean {
const pokemon = params.pokemon;
const stackCount = pokemon.heldItemManager.getStack(this.type);
if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) {
// TODO: this shouldn't be undefined AFAIK
@ -44,13 +51,3 @@ export class HitHealHeldItem extends HeldItem {
return true;
}
}
export function applyHitHealHeldItem(pokemon: Pokemon) {
if (pokemon) {
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
if (allHeldItems[item] instanceof HitHealHeldItem) {
allHeldItems[item].apply(pokemon);
}
}
}
}

View File

@ -2,9 +2,15 @@ import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import { BATTLE_STATS } from "#enums/stat";
import i18next from "i18next";
import { ConsumableHeldItem } from "../held-item";
import { ConsumableHeldItem, ITEM_EFFECT } from "../held-item";
import { getPokemonNameWithAffix } from "#app/messages";
import { allHeldItems } from "../all-held-items";
export interface RESET_NEGATIVE_STAT_STAGE_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
/** Whether the move was used by a player pokemon */
isPlayer: boolean;
}
/**
* Modifier used for held items, namely White Herb, that restore adverse stat
@ -13,6 +19,8 @@ import { allHeldItems } from "../all-held-items";
* @see {@linkcode apply}
*/
export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE];
get name(): string {
return i18next.t("modifierType:ModifierType.WHITE_HERB.name");
}
@ -30,7 +38,9 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem {
* @param pokemon {@linkcode Pokemon} that holds the item
* @returns `true` if any stat stages were reset, false otherwise
*/
applyConsumable(pokemon: Pokemon): boolean {
apply(params: RESET_NEGATIVE_STAT_STAGE_PARAMS): boolean {
const pokemon = params.pokemon;
const isPlayer = params.isPlayer;
let statRestored = false;
for (const s of BATTLE_STATS) {
@ -47,24 +57,10 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem {
typeName: this.name,
}),
);
this.consume(pokemon, isPlayer);
}
return statRestored;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 2;
}
}
// TODO: Do we need this to return true/false?
export function applyResetNegativeStatStageHeldItem(pokemon: Pokemon): boolean {
let applied = false;
if (pokemon) {
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
if (allHeldItems[item] instanceof ResetNegativeStatStageHeldItem) {
applied ||= allHeldItems[item].apply(pokemon);
}
}
}
return applied;
}

View File

@ -1,13 +1,19 @@
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import i18next from "i18next";
import { HeldItem } from "#app/items/held-item";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { toDmgValue } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages";
import { allHeldItems } from "../all-held-items";
export class TurnHealHeldItem extends HeldItem {
export interface TURN_END_HEAL_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
}
export class TurnEndHealHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_HEAL];
get name(): string {
return i18next.t("modifierType:ModifierType.LEFTOVERS.name") + " (new)";
}
@ -20,7 +26,8 @@ export class TurnHealHeldItem extends HeldItem {
return "leftovers";
}
apply(pokemon: Pokemon): boolean {
apply(params: TURN_END_HEAL_PARAMS): boolean {
const pokemon = params.pokemon;
const stackCount = pokemon.heldItemManager.getStack(this.type);
if (pokemon.isFullHp()) {
return false;
@ -39,13 +46,3 @@ export class TurnHealHeldItem extends HeldItem {
return true;
}
}
export function applyTurnHealHeldItem(pokemon: Pokemon) {
if (pokemon) {
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
if (allHeldItems[item] instanceof TurnHealHeldItem) {
allHeldItems[item].apply(pokemon);
}
}
}
}

View File

@ -78,7 +78,8 @@ import type Move from "#app/data/moves/move";
import { isFieldTargeted } from "#app/data/moves/move-utils";
import { FaintPhase } from "./faint-phase";
import { DamageAchv } from "#app/system/achv";
import { applyHitHealHeldItem } from "#app/items/held-items/hit-heal";
import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item";
type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
@ -417,7 +418,7 @@ export class MoveEffectPhase extends PokemonPhase {
// If there are multiple hits, or if there are hits of the multi-hit move left
globalScene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
}
applyHitHealHeldItem(user);
applyHeldItems(ITEM_EFFECT.HIT_HEAL, { pokemon: user });
this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null));
}
}

View File

@ -22,7 +22,8 @@ import { PokemonPhase } from "./pokemon-phase";
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
import { OctolockTag } from "#app/data/battler-tags";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import { applyResetNegativeStatStageHeldItem } from "#app/items/held-items/reset-negative-stat-stage";
import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item";
export type StatStageChangeCallback = (
target: Pokemon | null,
@ -239,7 +240,7 @@ export class StatStageChangePhase extends PokemonPhase {
);
if (!(existingPhase instanceof StatStageChangePhase)) {
// Apply White Herb if needed
applyResetNegativeStatStageHeldItem(pokemon);
applyHeldItems(ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE, { pokemon: pokemon, isPlayer: this.player });
}
pokemon.updateInfo();

View File

@ -15,7 +15,8 @@ import i18next from "i18next";
import { FieldPhase } from "./field-phase";
import { PokemonHealPhase } from "./pokemon-heal-phase";
import { globalScene } from "#app/global-scene";
import { applyTurnHealHeldItem } from "#app/items/held-items/turn-heal";
import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item";
export class TurnEndPhase extends FieldPhase {
start() {
@ -30,7 +31,7 @@ export class TurnEndPhase extends FieldPhase {
if (!pokemon.switchOutStatus) {
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
applyTurnHealHeldItem(pokemon);
applyHeldItems(ITEM_EFFECT.TURN_END_HEAL, { pokemon: pokemon });
if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
globalScene.unshiftPhase(