From 167c5f163516a62f1e7a750fd0f3cc777396cf1d Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Apr 2025 19:50:08 +0200 Subject: [PATCH 001/114] Introducing held items --- src/modifier/held-items.ts | 230 +++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/modifier/held-items.ts diff --git a/src/modifier/held-items.ts b/src/modifier/held-items.ts new file mode 100644 index 00000000000..284365bb02d --- /dev/null +++ b/src/modifier/held-items.ts @@ -0,0 +1,230 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type { Localizable } from "#app/interfaces/locales"; +import type { Constructor, NumberHolder } from "#app/utils"; +import { PokemonType } from "#enums/pokemon-type"; +import i18next from "i18next"; + +export enum HeldItemType { + NONE, + + SITRUS_BERRY = 1, + LEPPA_BERRY, + + SILK_SCARF = 101, + BLACK_BELT, + SHARP_BEAK, + POISON_BARB, + SOFT_SAND, + HARD_STONE, + SILVER_POWDER, + SPELL_TAG, + METAL_COAT, + CHARCOAL, + MYSTIC_WATER, + MIRACLE_SEED, + MAGNET, + TWISTED_SPOON, + NEVER_MELT_ICE, + DRAGON_FANG, + BLACK_GLASSES, + FAIRY_FEATHER, +} + +export class HeldItem implements Localizable { + // public pokemonId: number; + public type: HeldItemType; + public maxStackCount: number; + public isTransferable = true; + public isStealable = true; + public isSuppressable = true; + public attrs: HeldItemAttr[]; + + public name: string; + public description: string; + public icon: string; + + constructor(type: HeldItemType, maxStackCount = 1, name, description, icon) { + this.type = type; + this.maxStackCount = maxStackCount; + + this.isTransferable = true; + this.isStealable = true; + this.isSuppressable = true; + + this.name = name; + this.description = description; + this.icon = icon; + } + + //TODO: we might want to change things to make this work... otherwise it's pointless + // to derive from Localizable. + localize(): void {} + + // get name(): string { + // return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); + // } + + // getDescription(): string { + // return + // } + + untransferable(): HeldItem { + this.isTransferable = false; + return this; + } + + unstealable(): HeldItem { + this.isStealable = false; + return this; + } + + unsuppressable(): HeldItem { + this.isSuppressable = false; + return this; + } + + attr>(AttrType: T, ...args: ConstructorParameters): HeldItem { + const attr = new AttrType(...args); + this.attrs.push(attr); + + return this; + } + + /** + * Get all ability attributes that match `attrType` + * @param attrType any attribute that extends {@linkcode AbAttr} + * @returns Array of attributes that match `attrType`, Empty Array if none match. + */ + getAttrs(attrType: Constructor): T[] { + return this.attrs.filter((a): a is T => a instanceof attrType); + } + + hasAttr(attrType: Constructor): boolean { + return this.getAttrs(attrType).length > 0; + } + + getMaxStackCount(): number { + return this.maxStackCount; + } + + getIcon(stackCount: number, _forSummary?: boolean): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items"); + item.setFrame(this.icon); + item.setOrigin(0, 0.5); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + + return container; + } + + getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1) { + return null; + } + + const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (stackCount >= this.getMaxStackCount()) { + text.setTint(0xf89890); + } + text.setOrigin(0, 0); + + return text; + } + + getScoreMultiplier(): number { + return 1; + } +} + +export abstract class HeldItemAttr { + public showItem: boolean; + + constructor(showItem = false) { + this.showItem = showItem; + } + + /** + * Applies ability effects without checking conditions + * @param pokemon - The pokemon to apply this ability to + * @param itemType - Whether or not the ability is a passive + * @param args - Extra args passed to the function. Handled by child classes. + * @see {@linkcode canApply} + */ + apply(..._args: any[]): void {} + + getTriggerMessage(_pokemon: Pokemon, _itemType: HeldItemType, ..._args: any[]): string | null { + return null; + } + + //TODO: May need to add back some condition logic... we'll deal with that later on +} + +export class AttackTypeBoosterHeldItemAttr extends HeldItemAttr { + public moveType: PokemonType; + public powerBoost: number; + + constructor(moveType: PokemonType, powerBoost: number) { + super(); + this.moveType = moveType; + this.powerBoost = powerBoost; + } + + override apply(stackCount: number, args: any[]): void { + const moveType = args[0]; + const movePower = args[1]; + + if (moveType === this.moveType && movePower.value >= 1) { + (movePower as NumberHolder).value = Math.floor( + (movePower as NumberHolder).value * (1 + stackCount * this.powerBoost), + ); + } + } +} + +export function applyHeldItemAttrs(attrType: Constructor, pokemon: Pokemon, ...args: any[]) { + if (pokemon) { + for (const [item, stackCount] of pokemon.getHeldItems2()) { + if (allHeldItems[item].hasAttr(attrType)) { + attrType.apply(stackCount, ...args); + } + } + } +} + +export const allHeldItems = [new HeldItem(HeldItemType.NONE, 0, "", "", "")]; + +export function initHeldItems() { + allHeldItems.push( + new HeldItem( + HeldItemType.SILK_SCARF, + 99, + i18next.t("modifierType:AttackTypeBoosterItem.silk_scarf"), + i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t("pokemonInfo:Type.NORMAL"), + }), + "silk_scarf", + ).attr(AttackTypeBoosterHeldItemAttr, PokemonType.NORMAL, 0.2), + new HeldItem( + HeldItemType.BLACK_BELT, + 99, + i18next.t("modifierType:AttackTypeBoosterItem.black_belt"), + i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t("pokemonInfo:Type.FIGHTING"), + }), + "black_belt", + ).attr(AttackTypeBoosterHeldItemAttr, PokemonType.FIGHTING, 0.2), + ); +} + +//TODO: I hate this. Can we just make it an interface? +export function getHeldItem(itemType: HeldItemType): HeldItem { + return allHeldItems.find(item => item.type === itemType)!; // TODO: is this bang correct? +} From 662d7d3a6ab503e9302abe3641291c716ad4f571 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Apr 2025 19:50:41 +0200 Subject: [PATCH 002/114] Pokemon class can add held items --- src/field/pokemon.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b59b7ba01fe..6d99aa91193 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -259,6 +259,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; +import { allHeldItems, HeldItemType } from "#app/modifier/held-items"; export enum LearnMoveSituation { MISC, @@ -341,6 +342,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; + private heldItems: [HeldItemType, number][] = []; + constructor( x: number, y: number, @@ -1186,6 +1189,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) as PokemonHeldItemModifier[]; } + getHeldItems2(): [HeldItemType, number][] { + return this.heldItems; + } + + addHeldItem(itemType: HeldItemType, stack: number) { + const maxStack = allHeldItems[itemType].getMaxStackCount(); + + const existing = this.heldItems.find(([type]) => type === itemType); + + if (existing) { + existing[1] = Math.min(existing[1] + stack, maxStack); + } else { + this.heldItems.push([itemType, Math.min(stack, maxStack)]); + } + } + updateScale(): void { this.setScale(this.getSpriteScale()); } From 24845bcf8204b99d0547868a900db46839197075 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Apr 2025 19:51:04 +0200 Subject: [PATCH 003/114] Example of applyHeldItemAttrs --- src/data/moves/move.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 962a13bb840..026ee0ca656 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -121,6 +121,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; +import { applyHeldItemAttrs, AttackTypeBoosterHeldItemAttr } from "#app/modifier/held-items"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -837,7 +838,7 @@ export default class Move implements Localizable { if (!this.hasAttr(TypelessAttr)) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); - globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); + applyHeldItemAttrs(AttackTypeBoosterHeldItemAttr, source, this.type, power) } if (source.getTag(HelpingHandTag)) { From dfc0d29ec99a61f34757446a692f3e51e9cebf61 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Apr 2025 21:36:32 +0200 Subject: [PATCH 004/114] Introducing a PokemonItemManager class --- src/field/pokemon-item-manager.ts | 26 ++++++++++++++++++++++++++ src/field/pokemon.ts | 22 ++++------------------ src/modifier/held-items.ts | 2 +- 3 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 src/field/pokemon-item-manager.ts diff --git a/src/field/pokemon-item-manager.ts b/src/field/pokemon-item-manager.ts new file mode 100644 index 00000000000..f00cf63f763 --- /dev/null +++ b/src/field/pokemon-item-manager.ts @@ -0,0 +1,26 @@ +import { allHeldItems } from "#app/modifier/held-items"; +import type { HeldItemType } from "#app/modifier/held-items"; + +export class PokemonItemManager { + private heldItems: [HeldItemType, number][]; + + constructor() { + this.heldItems = []; + } + + getHeldItems(): [HeldItemType, number][] { + return this.heldItems; + } + + addHeldItem(itemType: HeldItemType, stack: number) { + const maxStack = allHeldItems[itemType].getMaxStackCount(); + + const existing = this.heldItems.find(([type]) => type === itemType); + + if (existing) { + existing[1] = Math.min(existing[1] + stack, maxStack); + } else { + this.heldItems.push([itemType, Math.min(stack, maxStack)]); + } + } +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6d99aa91193..f2c11de396a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -259,7 +259,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; -import { allHeldItems, HeldItemType } from "#app/modifier/held-items"; +import { PokemonItemManager } from "./pokemon-item-manager"; export enum LearnMoveSituation { MISC, @@ -342,7 +342,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - private heldItems: [HeldItemType, number][] = []; + public heldItemManager: PokemonItemManager; constructor( x: number, @@ -537,6 +537,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!dataSource) { this.calculateStats(); } + + this.heldItemManager = new PokemonItemManager(); } /** @@ -1189,22 +1191,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) as PokemonHeldItemModifier[]; } - getHeldItems2(): [HeldItemType, number][] { - return this.heldItems; - } - - addHeldItem(itemType: HeldItemType, stack: number) { - const maxStack = allHeldItems[itemType].getMaxStackCount(); - - const existing = this.heldItems.find(([type]) => type === itemType); - - if (existing) { - existing[1] = Math.min(existing[1] + stack, maxStack); - } else { - this.heldItems.push([itemType, Math.min(stack, maxStack)]); - } - } - updateScale(): void { this.setScale(this.getSpriteScale()); } diff --git a/src/modifier/held-items.ts b/src/modifier/held-items.ts index 284365bb02d..10e03a533b3 100644 --- a/src/modifier/held-items.ts +++ b/src/modifier/held-items.ts @@ -191,7 +191,7 @@ export class AttackTypeBoosterHeldItemAttr extends HeldItemAttr { export function applyHeldItemAttrs(attrType: Constructor, pokemon: Pokemon, ...args: any[]) { if (pokemon) { - for (const [item, stackCount] of pokemon.getHeldItems2()) { + for (const [item, stackCount] of pokemon.heldItemManager.getHeldItems()) { if (allHeldItems[item].hasAttr(attrType)) { attrType.apply(stackCount, ...args); } From 09e217b4ccaa9612e0c89ad4339f7e18786f24ba Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 18 Apr 2025 00:26:05 +0200 Subject: [PATCH 005/114] Moving away from HeldItemAttr --- src/data/moves/move.ts | 4 +- src/modifier/held-items.ts | 174 ++++++++++++++++--------------------- 2 files changed, 78 insertions(+), 100 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 192e51214c3..64939d6f873 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -121,7 +121,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; -import { applyHeldItemAttrs, AttackTypeBoosterHeldItemAttr } from "#app/modifier/held-items"; +import { applyAttackTypeBoosterHeldItem } from "#app/modifier/held-items"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -838,7 +838,7 @@ export default class Move implements Localizable { if (!this.hasAttr(TypelessAttr)) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); - applyHeldItemAttrs(AttackTypeBoosterHeldItemAttr, source, this.type, power) + applyAttackTypeBoosterHeldItem(source, this.type, power); } if (source.getTag(HelpingHandTag)) { diff --git a/src/modifier/held-items.ts b/src/modifier/held-items.ts index 10e03a533b3..2c9f9a77c20 100644 --- a/src/modifier/held-items.ts +++ b/src/modifier/held-items.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { Localizable } from "#app/interfaces/locales"; -import type { Constructor, NumberHolder } from "#app/utils"; +import type { NumberHolder } from "#app/utils"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; @@ -38,36 +38,37 @@ export class HeldItem implements Localizable { public isTransferable = true; public isStealable = true; public isSuppressable = true; - public attrs: HeldItemAttr[]; - public name: string; - public description: string; - public icon: string; + public name = ""; + public description = ""; + public icon = ""; - constructor(type: HeldItemType, maxStackCount = 1, name, description, icon) { + constructor(type: HeldItemType, maxStackCount = 1) { this.type = type; this.maxStackCount = maxStackCount; this.isTransferable = true; this.isStealable = true; this.isSuppressable = true; - - this.name = name; - this.description = description; - this.icon = icon; } - //TODO: we might want to change things to make this work... otherwise it's pointless - // to derive from Localizable. - localize(): void {} + localize(): void { + this.name = this.getName(); + this.description = this.getDescription(); + this.icon = this.getIcon(); + } - // get name(): string { - // return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); - // } + getName(): string { + return ""; + } - // getDescription(): string { - // return - // } + getDescription(): string { + return ""; + } + + getIcon(): string { + return ""; + } untransferable(): HeldItem { this.isTransferable = false; @@ -84,31 +85,11 @@ export class HeldItem implements Localizable { return this; } - attr>(AttrType: T, ...args: ConstructorParameters): HeldItem { - const attr = new AttrType(...args); - this.attrs.push(attr); - - return this; - } - - /** - * Get all ability attributes that match `attrType` - * @param attrType any attribute that extends {@linkcode AbAttr} - * @returns Array of attributes that match `attrType`, Empty Array if none match. - */ - getAttrs(attrType: Constructor): T[] { - return this.attrs.filter((a): a is T => a instanceof attrType); - } - - hasAttr(attrType: Constructor): boolean { - return this.getAttrs(attrType).length > 0; - } - getMaxStackCount(): number { return this.maxStackCount; } - getIcon(stackCount: number, _forSummary?: boolean): Phaser.GameObjects.Container { + createIcon(stackCount: number, _forSummary?: boolean): Phaser.GameObjects.Container { const container = globalScene.add.container(0, 0); const item = globalScene.add.sprite(0, 12, "items"); @@ -144,87 +125,84 @@ export class HeldItem implements Localizable { } } -export abstract class HeldItemAttr { - public showItem: boolean; - - constructor(showItem = false) { - this.showItem = showItem; - } - - /** - * Applies ability effects without checking conditions - * @param pokemon - The pokemon to apply this ability to - * @param itemType - Whether or not the ability is a passive - * @param args - Extra args passed to the function. Handled by child classes. - * @see {@linkcode canApply} - */ - apply(..._args: any[]): void {} - - getTriggerMessage(_pokemon: Pokemon, _itemType: HeldItemType, ..._args: any[]): string | null { - return null; - } - - //TODO: May need to add back some condition logic... we'll deal with that later on +interface AttackTypeToHeldItemTypeMap { + [key: number]: HeldItemType; } -export class AttackTypeBoosterHeldItemAttr extends HeldItemAttr { +export const attackTypeToHeldItemTypeMap: AttackTypeToHeldItemTypeMap = { + [PokemonType.NORMAL]: HeldItemType.SILK_SCARF, + [PokemonType.FIGHTING]: HeldItemType.BLACK_BELT, + [PokemonType.FLYING]: HeldItemType.SHARP_BEAK, + [PokemonType.POISON]: HeldItemType.POISON_BARB, + [PokemonType.GROUND]: HeldItemType.SOFT_SAND, + [PokemonType.ROCK]: HeldItemType.HARD_STONE, + [PokemonType.BUG]: HeldItemType.SILVER_POWDER, + [PokemonType.GHOST]: HeldItemType.SPELL_TAG, + [PokemonType.STEEL]: HeldItemType.METAL_COAT, + [PokemonType.FIRE]: HeldItemType.CHARCOAL, + [PokemonType.WATER]: HeldItemType.MYSTIC_WATER, + [PokemonType.GRASS]: HeldItemType.MIRACLE_SEED, + [PokemonType.ELECTRIC]: HeldItemType.MAGNET, + [PokemonType.PSYCHIC]: HeldItemType.TWISTED_SPOON, + [PokemonType.ICE]: HeldItemType.NEVER_MELT_ICE, + [PokemonType.DRAGON]: HeldItemType.DRAGON_FANG, + [PokemonType.DARK]: HeldItemType.BLACK_GLASSES, + [PokemonType.FAIRY]: HeldItemType.FAIRY_FEATHER, +}; + +export class AttackTypeBoosterHeldItem extends HeldItem { public moveType: PokemonType; public powerBoost: number; - constructor(moveType: PokemonType, powerBoost: number) { - super(); + constructor(type: HeldItemType, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + super(type, maxStackCount); this.moveType = moveType; this.powerBoost = powerBoost; + this.localize(); } - override apply(stackCount: number, args: any[]): void { - const moveType = args[0]; - const movePower = args[1]; + getName(): string { + return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemType[this.type]?.toLowerCase()}`); + } + getDescription(): string { + return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), + }); + } + + getIcon(): string { + return `${HeldItemType[this.type]?.toLowerCase()}`; + } + + apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { if (moveType === this.moveType && movePower.value >= 1) { - (movePower as NumberHolder).value = Math.floor( - (movePower as NumberHolder).value * (1 + stackCount * this.powerBoost), - ); + movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost)); } } } -export function applyHeldItemAttrs(attrType: Constructor, pokemon: Pokemon, ...args: any[]) { +export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) { if (pokemon) { for (const [item, stackCount] of pokemon.heldItemManager.getHeldItems()) { - if (allHeldItems[item].hasAttr(attrType)) { - attrType.apply(stackCount, ...args); + if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { + allHeldItems[item].apply(stackCount, moveType, movePower); } } } } -export const allHeldItems = [new HeldItem(HeldItemType.NONE, 0, "", "", "")]; +type HeldItemMap = { + [key in HeldItemType]: HeldItem; +}; + +export const allHeldItems = {} as HeldItemMap; export function initHeldItems() { - allHeldItems.push( - new HeldItem( - HeldItemType.SILK_SCARF, - 99, - i18next.t("modifierType:AttackTypeBoosterItem.silk_scarf"), - i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t("pokemonInfo:Type.NORMAL"), - }), - "silk_scarf", - ).attr(AttackTypeBoosterHeldItemAttr, PokemonType.NORMAL, 0.2), - new HeldItem( - HeldItemType.BLACK_BELT, - 99, - i18next.t("modifierType:AttackTypeBoosterItem.black_belt"), - i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t("pokemonInfo:Type.FIGHTING"), - }), - "black_belt", - ).attr(AttackTypeBoosterHeldItemAttr, PokemonType.FIGHTING, 0.2), - ); -} + // SILK_SCARF, BLACK_BELT, etc... + for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItemTypeMap)) { + const pokemonType = Number(typeKey) as PokemonType; -//TODO: I hate this. Can we just make it an interface? -export function getHeldItem(itemType: HeldItemType): HeldItem { - return allHeldItems.find(item => item.type === itemType)!; // TODO: is this bang correct? + allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); + } } From d4db552e68eccd85622139aeadda06c3467c819e Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 23 Apr 2025 19:24:09 +0200 Subject: [PATCH 006/114] Attempt at incorporating the new framework in modifier-type --- src/modifier/modifier-type.ts | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 8feb60c7778..96240c1a784 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -128,6 +128,8 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; +import type { HeldItemType } from "./held-items"; +import { allHeldItems, attackTypeToHeldItemTypeMap } from "./held-items"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -804,54 +806,33 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge } } -enum AttackTypeBoosterItem { - SILK_SCARF, - BLACK_BELT, - SHARP_BEAK, - POISON_BARB, - SOFT_SAND, - HARD_STONE, - SILVER_POWDER, - SPELL_TAG, - METAL_COAT, - CHARCOAL, - MYSTIC_WATER, - MIRACLE_SEED, - MAGNET, - TWISTED_SPOON, - NEVER_MELT_ICE, - DRAGON_FANG, - BLACK_GLASSES, - FAIRY_FEATHER, -} - export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { public moveType: PokemonType; public boostPercent: number; + public heldItemId: HeldItemType; constructor(moveType: PokemonType, boostPercent: number) { + const heldItemId = attackTypeToHeldItemTypeMap[moveType]; super( "", - `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`, + allHeldItems[heldItemId].getIcon(), (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), ); this.moveType = moveType; this.boostPercent = boostPercent; + this.heldItemId = heldItemId; // Not good, but temporary } get name(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); + return allHeldItems[this.heldItemId].getName(); } getDescription(): string { - // TODO: Need getTypeName? - return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), - }); + return allHeldItems[this.heldItemId].getDescription(); } getPregenArgs(): any[] { From 8613dadad92bf5c8eb52f45cf1a90e65286ffdb0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 4 May 2025 17:50:27 +0200 Subject: [PATCH 007/114] Changes --- src/modifier/held-items.ts | 4 +- src/modifier/reward-tier.ts | 8 ++ src/modifier/reward.ts | 173 ++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/modifier/reward-tier.ts create mode 100644 src/modifier/reward.ts diff --git a/src/modifier/held-items.ts b/src/modifier/held-items.ts index 2c9f9a77c20..8200b946011 100644 --- a/src/modifier/held-items.ts +++ b/src/modifier/held-items.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { Localizable } from "#app/interfaces/locales"; -import type { NumberHolder } from "#app/utils"; +import type { NumberHolder } from "#app/utils/common"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; @@ -93,7 +93,7 @@ export class HeldItem implements Localizable { const container = globalScene.add.container(0, 0); const item = globalScene.add.sprite(0, 12, "items"); - item.setFrame(this.icon); + item.setFrame(this.getIcon()); item.setOrigin(0, 0.5); container.add(item); diff --git a/src/modifier/reward-tier.ts b/src/modifier/reward-tier.ts new file mode 100644 index 00000000000..e7ccc1d9166 --- /dev/null +++ b/src/modifier/reward-tier.ts @@ -0,0 +1,8 @@ +export enum RewardTier { + COMMON, + GREAT, + ULTRA, + ROGUE, + MASTER, + LUXURY, +} diff --git a/src/modifier/reward.ts b/src/modifier/reward.ts new file mode 100644 index 00000000000..01ab667a1b1 --- /dev/null +++ b/src/modifier/reward.ts @@ -0,0 +1,173 @@ +/** +import { globalScene } from "#app/global-scene"; +import type { PokeballType } from "#enums/pokeball"; +import i18next from "i18next"; +import { allHeldItems, type HeldItem } from "./held-items"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; +import type Pokemon from "#app/field/pokemon"; + +export class Reward { + + getName(): string { + return ""; + } + + getDescription(): string { + return ""; + } + + getIcon(): string { + return ""; + } + + createIcon(): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items"); + item.setFrame(this.getIcon()); + item.setOrigin(0, 0.5); + container.add(item); + return container; + } +} + + +export class PokeballReward extends Reward { + private pokeballType: PokeballType; + private count: number; + + constructor(pokeballType: PokeballType, count: number) { + super(); + this.pokeballType = pokeballType; + this.count = count; + } + + getName(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + catchRate: + getPokeballCatchMultiplier(this.pokeballType) > -1 + ? `${getPokeballCatchMultiplier(this.pokeballType)}x` + : "100%", + pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, + }); + } + + apply(): boolean { + const pokeballCounts = globalScene.pokeballCounts; + pokeballCounts[this.pokeballType] = Math.min( + pokeballCounts[this.pokeballType] + this.count, + MAX_PER_TYPE_POKEBALLS, + ); + return true; + } +} + +export class PartySelectReward extends Reward { + apply(): { + } +} + +export class HeldItemReward extends PartySelectReward { + private itemId; + constructor(itemId: HeldItem) { + super(); + this.itemId = itemId; + } + + getName(): string { + return allHeldItems[this.itemId].getName(); + } + + getDescription(): string { + return allHeldItems[this.itemId].getDescription(); + } + + getIcon(): string { + return allHeldItems[this.itemId].getIcon(); + } + + apply(): { + } +} + + + + + +export class RewardGenerator { + options: number[]; + optionWeights: number[]; + + constructor(options: number[]) { + this.options = options; + } +} + + +export class PokeballRewardGenerator extends RewardGenerator{ + + constructor( + options: PokeballType[], + condition?: (party: Pokemon[], option: number) => boolean, + getOptionWeight?: (party: Pokemon[], option: number) => number, + ) { + super(options); + + this.isAvailable = isAvailable; + this.getOptionWeight = getOptionWeight; + } + + isAvailable(): boolean { + + } + + optionWeights() { + + } + +} + + + + +export interface RewardInfo { + options: number[]; + rewardType: ; + condition?: (party: Pokemon[], option: number) => void; + optionWeight?: (party: Pokemon[], option: number) => void; +} + + + + +interface RewardPool { + [rewardTier: number]: RewardGenerator[]; +} + + +export class RewardManager { + + private rewardPool: RewardPool; + + constructor(rewardPool: RewardPool) { + this.rewardPool = rewardPool; + } + + + + + + + + +} + * */ From 560b6fd36932a4c501a1477f933dcc841677a3d2 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 11 May 2025 23:11:32 +0200 Subject: [PATCH 008/114] More changes --- src/field/pokemon-item-manager.ts | 8 +- src/loading-scene.ts | 2 + src/modifier/held-item-pool.ts | 69 ++++++++++++++++ src/modifier/held-items.ts | 130 +++++++++++++++++------------- src/modifier/modifier-type.ts | 8 +- src/modifier/reward-generator.ts | 120 +++++++++++++++++++++++++++ src/modifier/reward.ts | 41 ++-------- 7 files changed, 279 insertions(+), 99 deletions(-) create mode 100644 src/modifier/held-item-pool.ts create mode 100644 src/modifier/reward-generator.ts diff --git a/src/field/pokemon-item-manager.ts b/src/field/pokemon-item-manager.ts index f00cf63f763..58fb6ba4d40 100644 --- a/src/field/pokemon-item-manager.ts +++ b/src/field/pokemon-item-manager.ts @@ -1,18 +1,18 @@ import { allHeldItems } from "#app/modifier/held-items"; -import type { HeldItemType } from "#app/modifier/held-items"; +import type { HeldItems } from "#app/modifier/held-items"; export class PokemonItemManager { - private heldItems: [HeldItemType, number][]; + private heldItems: [HeldItems, number][]; constructor() { this.heldItems = []; } - getHeldItems(): [HeldItemType, number][] { + getHeldItems(): [HeldItems, number][] { return this.heldItems; } - addHeldItem(itemType: HeldItemType, stack: number) { + addHeldItem(itemType: HeldItems, stack: number) { const maxStack = allHeldItems[itemType].getMaxStackCount(); const existing = this.heldItems.find(([type]) => type === itemType); diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 914e6e961e2..f801061b665 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,6 +21,7 @@ import { initVouchers } from "#app/system/voucher"; import { Biome } from "#enums/biome"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; +import { initHeldItems } from "./modifier/held-items"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -375,6 +376,7 @@ export class LoadingScene extends SceneBase { initSpecies(); initMoves(); initAbilities(); + initHeldItems(); initChallenges(); initMysteryEncounters(); } diff --git a/src/modifier/held-item-pool.ts b/src/modifier/held-item-pool.ts new file mode 100644 index 00000000000..6d54e3561b9 --- /dev/null +++ b/src/modifier/held-item-pool.ts @@ -0,0 +1,69 @@ +/** +import { PlayerPokemon } from "#app/field/pokemon"; +import { randSeedInt } from "#app/utils/common"; +import { HeldItemCategories, HeldItems } from "./held-items"; +import { ModifierTier } from "./modifier-tier"; + +interface HeldItemPool { + [tier: string]: [HeldItems | HeldItemCategories, number][]; +} + +const dailyStarterHeldItemPool: HeldItemPool = { + [ModifierTier.COMMON]: [ + [HeldItemCategories.BASE_STAT_BOOSTER, 1], + [HeldItemCategories.BERRY, 3], + ], + [ModifierTier.GREAT]: [ + [HeldItemCategories.ATTACK_TYPE_BOOSTER, 5], + ], + [ModifierTier.ULTRA]: [ + [HeldItems.REVIVER_SEED, 4], + [HeldItems.SOOTHE_BELL, 1], + [HeldItems.SOUL_DEW, 1], + [HeldItems.GOLDEN_PUNCH, 1], + ], + [ModifierTier.ROGUE]: [ + [HeldItems.GRIP_CLAW, 5], + [HeldItems.BATON, 2], + [HeldItems.FOCUS_BAND, 5], + [HeldItems.QUICK_CLAW, 3], + [HeldItems.KINGS_ROCK, 3], + ], + [ModifierTier.MASTER]: [ + [HeldItems.LEFTOVERS, 1], + [HeldItems.SHELL_BELL, 1], + ], +}; + + + + +export function getDailyRunStarterModifiers(party: PlayerPokemon[]): HeldItems[] { + const ret: HeldItems[] = []; + for (const p of party) { + for (let m = 0; m < 3; m++) { + const tierValue = randSeedInt(64); + + let tier: ModifierTier; + if (tierValue > 25) { + tier = ModifierTier.COMMON; + } else if (tierValue > 12) { + tier = ModifierTier.GREAT; + } else if (tierValue > 4) { + tier = ModifierTier.ULTRA; + } else if (tierValue) { + tier = ModifierTier.ROGUE; + } else { + tier = ModifierTier.MASTER; + } + + const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier( + p, + ); + ret.push(modifier); + } + } + + return ret; +} +*/ diff --git a/src/modifier/held-items.ts b/src/modifier/held-items.ts index 8200b946011..170004aa024 100644 --- a/src/modifier/held-items.ts +++ b/src/modifier/held-items.ts @@ -5,35 +5,58 @@ import type { NumberHolder } from "#app/utils/common"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; -export enum HeldItemType { - NONE, +export const HeldItems = { + NONE: 0x0000, - SITRUS_BERRY = 1, - LEPPA_BERRY, + SITRUS_BERRY: 0x0101, + LEPPA_BERRY: 0x0102, - SILK_SCARF = 101, - BLACK_BELT, - SHARP_BEAK, - POISON_BARB, - SOFT_SAND, - HARD_STONE, - SILVER_POWDER, - SPELL_TAG, - METAL_COAT, - CHARCOAL, - MYSTIC_WATER, - MIRACLE_SEED, - MAGNET, - TWISTED_SPOON, - NEVER_MELT_ICE, - DRAGON_FANG, - BLACK_GLASSES, - FAIRY_FEATHER, -} + SILK_SCARF: 0x0201, + BLACK_BELT: 0x0202, + SHARP_BEAK: 0x0203, + POISON_BARB: 0x0204, + SOFT_SAND: 0x0205, + HARD_STONE: 0x0206, + SILVER_POWDER: 0x0207, + SPELL_TAG: 0x0208, + METAL_COAT: 0x0209, + CHARCOAL: 0x020a, + MYSTIC_WATER: 0x020b, + MIRACLE_SEED: 0x020c, + MAGNET: 0x020d, + TWISTED_SPOON: 0x020e, + NEVER_MELT_ICE: 0x020f, + DRAGON_FANG: 0x0210, + BLACK_GLASSES: 0x0211, + FAIRY_FEATHER: 0x0212, + + REVIVER_SEED: 0x0301, + SOOTHE_BELL: 0x0302, + SOUL_DEW: 0x0303, + GOLDEN_PUNCH: 0x0304, + GRIP_CLAW: 0x0305, + BATON: 0x0306, + FOCUS_BAND: 0x0307, + QUICK_CLAW: 0x0308, + KINGS_ROCK: 0x0309, + LEFTOVERS: 0x030a, + SHELL_BELL: 0x030b, +}; + +export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; + +export const HeldItemCategories = { + NONE: 0x0000, + BERRY: 0x0100, + ATTACK_TYPE_BOOSTER: 0x0200, + BASE_STAT_BOOSTER: 0x0400, +}; + +export type HeldItemCategories = (typeof HeldItemCategories)[keyof typeof HeldItemCategories]; export class HeldItem implements Localizable { // public pokemonId: number; - public type: HeldItemType; + public type: HeldItems; public maxStackCount: number; public isTransferable = true; public isStealable = true; @@ -43,7 +66,7 @@ export class HeldItem implements Localizable { public description = ""; public icon = ""; - constructor(type: HeldItemType, maxStackCount = 1) { + constructor(type: HeldItems, maxStackCount = 1) { this.type = type; this.maxStackCount = maxStackCount; @@ -125,36 +148,36 @@ export class HeldItem implements Localizable { } } -interface AttackTypeToHeldItemTypeMap { - [key: number]: HeldItemType; +interface AttackTypeToHeldItemMap { + [key: number]: HeldItems; } -export const attackTypeToHeldItemTypeMap: AttackTypeToHeldItemTypeMap = { - [PokemonType.NORMAL]: HeldItemType.SILK_SCARF, - [PokemonType.FIGHTING]: HeldItemType.BLACK_BELT, - [PokemonType.FLYING]: HeldItemType.SHARP_BEAK, - [PokemonType.POISON]: HeldItemType.POISON_BARB, - [PokemonType.GROUND]: HeldItemType.SOFT_SAND, - [PokemonType.ROCK]: HeldItemType.HARD_STONE, - [PokemonType.BUG]: HeldItemType.SILVER_POWDER, - [PokemonType.GHOST]: HeldItemType.SPELL_TAG, - [PokemonType.STEEL]: HeldItemType.METAL_COAT, - [PokemonType.FIRE]: HeldItemType.CHARCOAL, - [PokemonType.WATER]: HeldItemType.MYSTIC_WATER, - [PokemonType.GRASS]: HeldItemType.MIRACLE_SEED, - [PokemonType.ELECTRIC]: HeldItemType.MAGNET, - [PokemonType.PSYCHIC]: HeldItemType.TWISTED_SPOON, - [PokemonType.ICE]: HeldItemType.NEVER_MELT_ICE, - [PokemonType.DRAGON]: HeldItemType.DRAGON_FANG, - [PokemonType.DARK]: HeldItemType.BLACK_GLASSES, - [PokemonType.FAIRY]: HeldItemType.FAIRY_FEATHER, +export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { + [PokemonType.NORMAL]: HeldItems.SILK_SCARF, + [PokemonType.FIGHTING]: HeldItems.BLACK_BELT, + [PokemonType.FLYING]: HeldItems.SHARP_BEAK, + [PokemonType.POISON]: HeldItems.POISON_BARB, + [PokemonType.GROUND]: HeldItems.SOFT_SAND, + [PokemonType.ROCK]: HeldItems.HARD_STONE, + [PokemonType.BUG]: HeldItems.SILVER_POWDER, + [PokemonType.GHOST]: HeldItems.SPELL_TAG, + [PokemonType.STEEL]: HeldItems.METAL_COAT, + [PokemonType.FIRE]: HeldItems.CHARCOAL, + [PokemonType.WATER]: HeldItems.MYSTIC_WATER, + [PokemonType.GRASS]: HeldItems.MIRACLE_SEED, + [PokemonType.ELECTRIC]: HeldItems.MAGNET, + [PokemonType.PSYCHIC]: HeldItems.TWISTED_SPOON, + [PokemonType.ICE]: HeldItems.NEVER_MELT_ICE, + [PokemonType.DRAGON]: HeldItems.DRAGON_FANG, + [PokemonType.DARK]: HeldItems.BLACK_GLASSES, + [PokemonType.FAIRY]: HeldItems.FAIRY_FEATHER, }; export class AttackTypeBoosterHeldItem extends HeldItem { public moveType: PokemonType; public powerBoost: number; - constructor(type: HeldItemType, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + constructor(type: HeldItems, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { super(type, maxStackCount); this.moveType = moveType; this.powerBoost = powerBoost; @@ -162,7 +185,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { } getName(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemType[this.type]?.toLowerCase()}`); + return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItems[this.type]?.toLowerCase()}`); } getDescription(): string { @@ -172,7 +195,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { } getIcon(): string { - return `${HeldItemType[this.type]?.toLowerCase()}`; + return `${HeldItems[this.type]?.toLowerCase()}`; } apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { @@ -192,17 +215,12 @@ export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: Pokem } } -type HeldItemMap = { - [key in HeldItemType]: HeldItem; -}; - -export const allHeldItems = {} as HeldItemMap; +export const allHeldItems = {}; export function initHeldItems() { // SILK_SCARF, BLACK_BELT, etc... - for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItemTypeMap)) { + for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) { const pokemonType = Number(typeKey) as PokemonType; - allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 9a88d04bf3c..a6e0889679a 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -128,8 +128,8 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; -import type { HeldItemType } from "./held-items"; -import { allHeldItems, attackTypeToHeldItemTypeMap } from "./held-items"; +import type { HeldItems } from "./held-items"; +import { allHeldItems, attackTypeToHeldItem } from "./held-items"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -812,10 +812,10 @@ export class AttackTypeBoosterModifierType { public moveType: PokemonType; public boostPercent: number; - public heldItemId: HeldItemType; + public heldItemId: HeldItems; constructor(moveType: PokemonType, boostPercent: number) { - const heldItemId = attackTypeToHeldItemTypeMap[moveType]; + const heldItemId = attackTypeToHeldItem[moveType]; super( "", allHeldItems[heldItemId].getIcon(), diff --git a/src/modifier/reward-generator.ts b/src/modifier/reward-generator.ts new file mode 100644 index 00000000000..cedecd923bb --- /dev/null +++ b/src/modifier/reward-generator.ts @@ -0,0 +1,120 @@ +import { AttackMove } from "#app/data/moves/move"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonType } from "#enums/pokemon-type"; +import { attackTypeToHeldItem } from "./held-items"; +import { HeldItemReward, type Reward } from "./reward"; + +function getRandomWeightedSelection(weights: Map): T | null { + const totalWeight = Array.from(weights.values()).reduce((sum, weight) => sum + weight, 0); + + if (totalWeight === 0) { + return null; + } + + const randInt = Math.floor(Math.random() * totalWeight); + + let accumulatedWeight = 0; + for (const [item, weight] of weights.entries()) { + accumulatedWeight += weight; + if (randInt < accumulatedWeight) { + return item; + } + } + + return null; +} + +export class RewardGenerator { + options: T[]; + tempWeights: Map; + + constructor(options: T[]) { + this.options = options; + this.tempWeights = new Map(this.options.map(option => [option, 1])); + } + + generate(party: Pokemon[], overrideWeightFunction?: Function) { + const weights = overrideWeightFunction ? overrideWeightFunction(party) : this.weightFunction(party); + + for (const [option, tempWeight] of this.tempWeights.entries()) { + if (tempWeight === 0 && weights.has(option)) { + weights.set(option, 0); + } + } + + const value: T | null = getRandomWeightedSelection(weights); + + if (value) { + this.tempWeights.set(value, 0); + return this.generateReward(value); + } + + return null; + } + + weightFunction(_party: Pokemon[]): Map { + const defaultWeightMap = new Map(); + + this.options.forEach(option => { + defaultWeightMap.set(option, 1); + }); + + return defaultWeightMap; + } + + generateReward(_value: T): Reward | null { + return null; + } +} + +export class AttackTypeBoosterHeldItemRewardGenerator extends RewardGenerator { + constructor() { + //TODO: we can also construct this, but then have to handle options being null + const options = [ + PokemonType.NORMAL, + PokemonType.FIGHTING, + PokemonType.FLYING, + PokemonType.POISON, + PokemonType.GROUND, + PokemonType.ROCK, + PokemonType.BUG, + PokemonType.GHOST, + PokemonType.STEEL, + PokemonType.FIRE, + PokemonType.WATER, + PokemonType.GRASS, + PokemonType.ELECTRIC, + PokemonType.PSYCHIC, + PokemonType.ICE, + PokemonType.DRAGON, + PokemonType.DARK, + PokemonType.FAIRY, + ]; + super(options); + } + + weightFunction(party: Pokemon[]): Map { + const attackMoveTypes = party.flatMap(p => + p + .getMoveset() + .map(m => m.getMove()) + .filter(m => m instanceof AttackMove) + .map(m => m.type), + ); + + const attackMoveTypeWeights = new Map(); + + for (const type of attackMoveTypes) { + const currentWeight = attackMoveTypeWeights.get(type) ?? 0; + if (currentWeight < 3) { + attackMoveTypeWeights.set(type, currentWeight + 1); + } + } + + return attackMoveTypeWeights; + } + + generateReward(value: PokemonType) { + return new HeldItemReward(attackTypeToHeldItem[value]); + } +} diff --git a/src/modifier/reward.ts b/src/modifier/reward.ts index 01ab667a1b1..917e7909cb5 100644 --- a/src/modifier/reward.ts +++ b/src/modifier/reward.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import type { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; -import { allHeldItems, type HeldItem } from "./held-items"; +import { allHeldItems, type HeldItems } from "./held-items"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import type Pokemon from "#app/field/pokemon"; @@ -77,8 +77,9 @@ export class PartySelectReward extends Reward { } export class HeldItemReward extends PartySelectReward { - private itemId; - constructor(itemId: HeldItem) { + private itemId: HeldItems; + + constructor(itemId: HeldItems) { super(); this.itemId = itemId; } @@ -103,38 +104,14 @@ export class HeldItemReward extends PartySelectReward { -export class RewardGenerator { - options: number[]; - optionWeights: number[]; - - constructor(options: number[]) { - this.options = options; - } -} -export class PokeballRewardGenerator extends RewardGenerator{ - constructor( - options: PokeballType[], - condition?: (party: Pokemon[], option: number) => boolean, - getOptionWeight?: (party: Pokemon[], option: number) => number, - ) { - super(options); - this.isAvailable = isAvailable; - this.getOptionWeight = getOptionWeight; - } - isAvailable(): boolean { - - } - optionWeights() { - } -} @@ -162,12 +139,6 @@ export class RewardManager { this.rewardPool = rewardPool; } - - - - - - - } - * */ + +*/ From 82e24bf4bcc8d00e52377b749a319ad852a2fbe6 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 28 May 2025 00:14:51 +0200 Subject: [PATCH 009/114] Splitting up methods in select-modifier-phase.ts --- src/phases/select-modifier-phase.ts | 528 ++++++++++++++-------------- 1 file changed, 264 insertions(+), 264 deletions(-) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5f11441333b..6a84bf692a9 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -66,6 +66,267 @@ export class SelectModifierPhase extends BattlePhase { if (!this.isCopy) { regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); } + const modifierCount = this.getModifierCount(); + + this.typeOptions = this.getModifierTypeOptions(modifierCount.value); + + const modifierSelectCallback = (rowCursor: number, cursor: number) => { + if (rowCursor < 0 || cursor < 0) { + globalScene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { + globalScene.ui.setOverlayMode( + UiMode.CONFIRM, + () => { + globalScene.ui.revertMode(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + }, + () => this.resetModifierSelect(modifierSelectCallback), + ); + }); + return false; + } + let modifierType: ModifierType; + let cost = 0; + switch (rowCursor) { + // Execute option from the bottom row + case 0: + return this.playBottomRowOption(cursor, modifierSelectCallback); + // Pick an option from the rewards + case 1: + if (this.typeOptions.length === 0) { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + return true; + } + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; + } + break; + // Pick an option from the shop + default: { + const shopOptions = getPlayerShopModifierTypeOptionsForWave( + globalScene.currentBattle.waveIndex, + globalScene.getWaveMoneyAmount(1), + ); + const shopOption = + shopOptions[ + rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT + ]; + if (shopOption.type) { + modifierType = shopOption.type; + } + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); + cost = healingItemCost.value; + break; + } + } + + if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.ui.playError(); + return false; + } + + if (modifierType! instanceof PokemonModifierType) { + //TODO: is the bang correct? + if (modifierType instanceof FusePokemonModifierType) { + this.openFusionMenu(modifierType, cost, modifierSelectCallback); + } else { + this.openModifierMenu(modifierType, cost, modifierSelectCallback); + } + } else { + this.applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? + } + + return !cost!; // TODO: is the bang correct? + }; + this.resetModifierSelect(modifierSelectCallback); + } + + private playBottomRowOption(cursor: number, modifierSelectCallback): boolean { + const party = globalScene.getPlayerParty(); + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + switch (cursor) { + // Reroll rewards + case 0: + if (rerollCost < 0 || globalScene.money < rerollCost) { + globalScene.ui.playError(); + return false; + } + globalScene.reroll = true; + globalScene.unshiftPhase( + new SelectModifierPhase( + this.rerollCount + 1, + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], + ), + ); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.money -= rerollCost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); + } + globalScene.playSound("se/buy"); + break; + // Transfer modifiers among party pokemon + case 1: + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.MODIFIER_TRANSFER, + -1, + (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { + if ( + toSlotIndex !== undefined && + fromSlotIndex < 6 && + toSlotIndex < 6 && + fromSlotIndex !== toSlotIndex && + itemIndex > -1 + ) { + const itemModifiers = globalScene.findModifiers( + m => + m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, + ) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + globalScene.tryTransferHeldItemModifier( + itemModifier, + party[toSlotIndex], + true, + itemQuantity, + undefined, + undefined, + false, + ); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + PartyUiHandler.FilterItemMaxStacks, + ); + break; + // Check the party, pass a callback to restore the modifier select screen. + case 2: + globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { + this.resetModifierSelect(modifierSelectCallback); + }); + break; + // Toggle reroll lock + case 3: + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + globalScene.ui.playError(); + return false; + } + globalScene.lockModifierTiers = !globalScene.lockModifierTiers; + const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; + } + return true; + } + + private applyModifier(modifier: Modifier, cost = 0, playSound = false) { + const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); + // Queue a copy of this phase when applying a TM or Memory Mushroom. + // If the player selects either of these, then escapes out of consuming them, + // they are returned to a shop in the same state. + if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { + globalScene.unshiftPhase(this.copy()); + } + + if (cost && !(modifier.type instanceof RememberMoveModifierType)) { + if (result) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.money -= cost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); + } + globalScene.playSound("se/buy"); + (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + } else { + globalScene.ui.playError(); + } + } else { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + } + } + + private openFusionMenu(modifierType, cost, modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.SPLICE, + -1, + (fromSlotIndex: number, spliceSlotIndex: number) => { + if ( + spliceSlotIndex !== undefined && + fromSlotIndex < 6 && + spliceSlotIndex < 6 && + fromSlotIndex !== spliceSlotIndex + ) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? + this.applyModifier(modifier, cost, true); + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + modifierType.selectFilter, + ); + } + + private openModifierMenu(modifierType, cost, modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + const pokemonModifierType = modifierType as PokemonModifierType; + const isMoveModifier = modifierType instanceof PokemonMoveModifierType; + const isTmModifier = modifierType instanceof TmModifierType; + const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; + const isPpRestoreModifier = + modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType; + const partyUiMode = isMoveModifier + ? PartyUiMode.MOVE_MODIFIER + : isTmModifier + ? PartyUiMode.TM_MODIFIER + : isRememberMoveModifier + ? PartyUiMode.REMEMBER_MOVE_MODIFIER + : PartyUiMode.MODIFIER; + const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = !isMoveModifier + ? !isRememberMoveModifier + ? modifierType.newModifier(party[slotIndex]) + : modifierType.newModifier(party[slotIndex], option as number) + : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + this.applyModifier(modifier!, cost, true); // TODO: is the bang correct? + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + pokemonModifierType.selectFilter, + modifierType instanceof PokemonMoveModifierType + ? (modifierType as PokemonMoveModifierType).moveSelectFilter + : undefined, + tmMoveId, + isPpRestoreModifier, + ); + } + + // Function that determines how many reward slots are available + private getModifierCount() { const modifierCount = new NumberHolder(3); if (this.isPlayer()) { globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); @@ -86,271 +347,10 @@ export class SelectModifierPhase extends BattlePhase { } } - this.typeOptions = this.getModifierTypeOptions(modifierCount.value); + return modifierCount; + } - const modifierSelectCallback = (rowCursor: number, cursor: number) => { - if (rowCursor < 0 || cursor < 0) { - globalScene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { - globalScene.ui.setOverlayMode( - UiMode.CONFIRM, - () => { - globalScene.ui.revertMode(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - }, - () => - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ), - ); - }); - return false; - } - let modifierType: ModifierType; - let cost: number; - const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); - switch (rowCursor) { - case 0: - switch (cursor) { - case 0: - if (rerollCost < 0 || globalScene.money < rerollCost) { - globalScene.ui.playError(); - return false; - } - globalScene.reroll = true; - globalScene.unshiftPhase( - new SelectModifierPhase( - this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], - ), - ); - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.money -= rerollCost; - globalScene.updateMoneyText(); - globalScene.animateMoneyChanged(false); - } - globalScene.playSound("se/buy"); - break; - case 1: - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.MODIFIER_TRANSFER, - -1, - (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { - if ( - toSlotIndex !== undefined && - fromSlotIndex < 6 && - toSlotIndex < 6 && - fromSlotIndex !== toSlotIndex && - itemIndex > -1 - ) { - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - globalScene.tryTransferHeldItemModifier( - itemModifier, - party[toSlotIndex], - true, - itemQuantity, - undefined, - undefined, - false, - ); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - PartyUiHandler.FilterItemMaxStacks, - ); - break; - case 2: - globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - }); - break; - case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - globalScene.ui.playError(); - return false; - } - globalScene.lockModifierTiers = !globalScene.lockModifierTiers; - const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (this.typeOptions.length === 0) { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - return true; - } - if (this.typeOptions[cursor].type) { - modifierType = this.typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave( - globalScene.currentBattle.waveIndex, - globalScene.getWaveMoneyAmount(1), - ); - const shopOption = - shopOptions[ - rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT - ]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; - } - - if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - // TODO: is the bang on cost correct? - globalScene.ui.playError(); - return false; - } - - const applyModifier = (modifier: Modifier, playSound = false) => { - const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); - // Queue a copy of this phase when applying a TM or Memory Mushroom. - // If the player selects either of these, then escapes out of consuming them, - // they are returned to a shop in the same state. - if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { - globalScene.unshiftPhase(this.copy()); - } - - if (cost && !(modifier.type instanceof RememberMoveModifierType)) { - if (result) { - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.money -= cost; - globalScene.updateMoneyText(); - globalScene.animateMoneyChanged(false); - } - globalScene.playSound("se/buy"); - (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); - } else { - globalScene.ui.playError(); - } - } else { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - } - }; - - if (modifierType! instanceof PokemonModifierType) { - //TODO: is the bang correct? - if (modifierType instanceof FusePokemonModifierType) { - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.SPLICE, - -1, - (fromSlotIndex: number, spliceSlotIndex: number) => { - if ( - spliceSlotIndex !== undefined && - fromSlotIndex < 6 && - spliceSlotIndex < 6 && - fromSlotIndex !== spliceSlotIndex - ) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? - applyModifier(modifier, true); - }); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - modifierType.selectFilter, - ); - } else { - const pokemonModifierType = modifierType as PokemonModifierType; - const isMoveModifier = modifierType instanceof PokemonMoveModifierType; - const isTmModifier = modifierType instanceof TmModifierType; - const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; - const isPpRestoreModifier = - modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType; - const partyUiMode = isMoveModifier - ? PartyUiMode.MOVE_MODIFIER - : isTmModifier - ? PartyUiMode.TM_MODIFIER - : isRememberMoveModifier - ? PartyUiMode.REMEMBER_MOVE_MODIFIER - : PartyUiMode.MODIFIER; - const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - partyUiMode, - -1, - (slotIndex: number, option: PartyOption) => { - if (slotIndex < 6) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = !isMoveModifier - ? !isRememberMoveModifier - ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as number) - : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - applyModifier(modifier!, true); // TODO: is the bang correct? - }); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - pokemonModifierType.selectFilter, - modifierType instanceof PokemonMoveModifierType - ? (modifierType as PokemonMoveModifierType).moveSelectFilter - : undefined, - tmMoveId, - isPpRestoreModifier, - ); - } - } else { - applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? - } - - return !cost!; // TODO: is the bang correct? - }; + private resetModifierSelect(modifierSelectCallback) { globalScene.ui.setMode( UiMode.MODIFIER_SELECT, this.isPlayer(), From f849c0c8e784db9a045bfc69ab050a0148b11086 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 11:54:39 +0200 Subject: [PATCH 010/114] Newrefactors of reward-pool-manager.ts --- src/modifier/reward-pool-manager.ts | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/modifier/reward-pool-manager.ts diff --git a/src/modifier/reward-pool-manager.ts b/src/modifier/reward-pool-manager.ts new file mode 100644 index 00000000000..ad7f81de810 --- /dev/null +++ b/src/modifier/reward-pool-manager.ts @@ -0,0 +1,92 @@ +import { globalScene } from "#app/global-scene"; +import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; +import type { RewardGenerator } from "./reward-generator"; +import type { RewardTier } from "./reward-tier"; +import Overrides from "#app/overrides"; + +interface RewardPool { + [rewardTier: number]: RewardGenerator[]; +} + +export interface CustomRewardSettings { + guaranteedModifierTiers?: RewardTier[]; + guaranteedModifierTypeOptions?: ModifierTypeOption[]; + guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; + fillRemaining?: boolean; + /** Set to negative value to disable rerolls completely in shop */ + rerollMultiplier?: number; + allowLuckUpgrades?: boolean; +} + +export class RewardPoolManager { + public rerollCount: number; + private rewardPool: RewardPool; + private customRewardSettings?: CustomRewardSettings; //TODO: have a better scheme than just this + + constructor(rewardPool: RewardPool) { + this.rewardPool = rewardPool; + } + + getRerollCost(lockRarities: boolean): number { + let baseValue = 0; + if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + return baseValue; + } + if (lockRarities) { + const tierValues = [50, 125, 300, 750, 2000]; + for (const opt of this.typeOptions) { + baseValue += tierValues[opt.type.tier ?? 0]; + } + } else { + baseValue = 250; + } + + let multiplier = 1; + if (!isNullOrUndefined(this.customRewardSettings?.rerollMultiplier)) { + if (this.customRewardSettings.rerollMultiplier < 0) { + // Completely overrides reroll cost to -1 and early exits + return -1; + } + + // Otherwise, continue with custom multiplier + multiplier = this.customRewardSettings.rerollMultiplier; + } + + const baseMultiplier = Math.min( + Math.ceil(globalScene.currentBattle.waveIndex / 10) * baseValue * 2 ** this.rerollCount * multiplier, + Number.MAX_SAFE_INTEGER, + ); + + // Apply Black Sludge to reroll cost + const modifiedRerollCost = new NumberHolder(baseMultiplier); + globalScene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); + return modifiedRerollCost.value; + } + + getRewardCount(): NumberHolder { + const modifierCount = new NumberHolder(3); + + // TODO: This code is used by golden and silver pokéball to increase the number of item slots + // They will become a trainer item, so there will be no .applyModifiers + globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); + globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); + + // If custom rewards are specified, overrides default item count + // TODO: Figure out exactly how and when that would happen + // Presumably in MEs, but possibly also after rerolls? And at specific waves... + if (this.customRewardSettings) { + const newItemCount = + (this.customRewardSettings.guaranteedModifierTiers?.length || 0) + + (this.customRewardSettings.guaranteedModifierTypeOptions?.length || 0) + + (this.customRewardSettings.guaranteedModifierTypeFuncs?.length || 0); + if (this.customRewardSettings.fillRemaining) { + const originalCount = modifierCount.value; + modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount; + } else { + modifierCount.value = newItemCount; + } + } + + return modifierCount; + } +} From 2a13e2ac5aa209ea78ee14dbc9fb8fe3b91008ed Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 11:56:45 +0200 Subject: [PATCH 011/114] New refactors of select-modifier-phase.ts --- src/modifier/reward-pool-manager.ts | 4 +- src/modifier/reward.ts | 1 - src/phases/select-modifier-phase.ts | 174 ++++++++++++++++------------ 3 files changed, 101 insertions(+), 78 deletions(-) diff --git a/src/modifier/reward-pool-manager.ts b/src/modifier/reward-pool-manager.ts index ad7f81de810..c49075505d6 100644 --- a/src/modifier/reward-pool-manager.ts +++ b/src/modifier/reward-pool-manager.ts @@ -1,3 +1,4 @@ +/** import { globalScene } from "#app/global-scene"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; import type { RewardGenerator } from "./reward-generator"; @@ -13,7 +14,7 @@ export interface CustomRewardSettings { guaranteedModifierTypeOptions?: ModifierTypeOption[]; guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; fillRemaining?: boolean; - /** Set to negative value to disable rerolls completely in shop */ + //Set to negative value to disable rerolls completely in shop rerollMultiplier?: number; allowLuckUpgrades?: boolean; } @@ -90,3 +91,4 @@ export class RewardPoolManager { return modifierCount; } } +*/ diff --git a/src/modifier/reward.ts b/src/modifier/reward.ts index 917e7909cb5..351ebc3a861 100644 --- a/src/modifier/reward.ts +++ b/src/modifier/reward.ts @@ -140,5 +140,4 @@ export class RewardManager { } } - */ diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 6a84bf692a9..d0eab4ba86f 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -68,7 +68,7 @@ export class SelectModifierPhase extends BattlePhase { } const modifierCount = this.getModifierCount(); - this.typeOptions = this.getModifierTypeOptions(modifierCount.value); + this.typeOptions = this.getModifierTypeOptions(modifierCount); const modifierSelectCallback = (rowCursor: number, cursor: number) => { if (rowCursor < 0 || cursor < 0) { @@ -146,89 +146,107 @@ export class SelectModifierPhase extends BattlePhase { } private playBottomRowOption(cursor: number, modifierSelectCallback): boolean { - const party = globalScene.getPlayerParty(); - const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + let success = false; switch (cursor) { - // Reroll rewards case 0: - if (rerollCost < 0 || globalScene.money < rerollCost) { - globalScene.ui.playError(); - return false; - } - globalScene.reroll = true; - globalScene.unshiftPhase( - new SelectModifierPhase( - this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], - ), - ); - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.money -= rerollCost; - globalScene.updateMoneyText(); - globalScene.animateMoneyChanged(false); - } - globalScene.playSound("se/buy"); + success = this.rerollModifiers(); break; - // Transfer modifiers among party pokemon case 1: - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.MODIFIER_TRANSFER, - -1, - (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { - if ( - toSlotIndex !== undefined && - fromSlotIndex < 6 && - toSlotIndex < 6 && - fromSlotIndex !== toSlotIndex && - itemIndex > -1 - ) { - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - globalScene.tryTransferHeldItemModifier( - itemModifier, - party[toSlotIndex], - true, - itemQuantity, - undefined, - undefined, - false, - ); - } else { - this.resetModifierSelect(modifierSelectCallback); - } - }, - PartyUiHandler.FilterItemMaxStacks, - ); + success = this.openModifierTransferScreen(modifierSelectCallback); break; // Check the party, pass a callback to restore the modifier select screen. case 2: globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { this.resetModifierSelect(modifierSelectCallback); }); + success = true; break; - // Toggle reroll lock case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - globalScene.ui.playError(); - return false; - } - globalScene.lockModifierTiers = !globalScene.lockModifierTiers; - const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; + success = this.toggleRerollLock(); + break; } + return success; + } + + // Reroll rewards + private rerollModifiers() { + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + if (rerollCost < 0 || globalScene.money < rerollCost) { + globalScene.ui.playError(); + return false; + } + globalScene.reroll = true; + globalScene.unshiftPhase( + new SelectModifierPhase( + this.rerollCount + 1, + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], + ), + ); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.money -= rerollCost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); + } + globalScene.playSound("se/buy"); return true; } + // Transfer modifiers among party pokemon + private openModifierTransferScreen(modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.MODIFIER_TRANSFER, + -1, + (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { + if ( + toSlotIndex !== undefined && + fromSlotIndex < 6 && + toSlotIndex < 6 && + fromSlotIndex !== toSlotIndex && + itemIndex > -1 + ) { + const itemModifiers = globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, + ) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + globalScene.tryTransferHeldItemModifier( + itemModifier, + party[toSlotIndex], + true, + itemQuantity, + undefined, + undefined, + false, + ); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + PartyUiHandler.FilterItemMaxStacks, + ); + return true; + } + + // Toggle reroll lock + private toggleRerollLock() { + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + globalScene.ui.playError(); + return false; + } + globalScene.lockModifierTiers = !globalScene.lockModifierTiers; + const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; + } + + // Applies the effects of the chosen modifier private applyModifier(modifier: Modifier, cost = 0, playSound = false) { const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); // Queue a copy of this phase when applying a TM or Memory Mushroom. @@ -257,6 +275,7 @@ export class SelectModifierPhase extends BattlePhase { } } + // Opens the party menu specifically for fusions private openFusionMenu(modifierType, cost, modifierSelectCallback) { const party = globalScene.getPlayerParty(); globalScene.ui.setModeWithoutClear( @@ -282,6 +301,7 @@ export class SelectModifierPhase extends BattlePhase { ); } + // Opens the party menu to apply one of various modifiers private openModifierMenu(modifierType, cost, modifierSelectCallback) { const party = globalScene.getPlayerParty(); const pokemonModifierType = modifierType as PokemonModifierType; @@ -326,11 +346,11 @@ export class SelectModifierPhase extends BattlePhase { } // Function that determines how many reward slots are available - private getModifierCount() { - const modifierCount = new NumberHolder(3); + private getModifierCount(): number { + const modifierCountHolder = new NumberHolder(3); if (this.isPlayer()) { - globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); - globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); + globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder); + globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder); } // If custom modifiers are specified, overrides default item count @@ -340,16 +360,18 @@ export class SelectModifierPhase extends BattlePhase { (this.customModifierSettings.guaranteedModifierTypeOptions?.length || 0) + (this.customModifierSettings.guaranteedModifierTypeFuncs?.length || 0); if (this.customModifierSettings.fillRemaining) { - const originalCount = modifierCount.value; - modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount; + const originalCount = modifierCountHolder.value; + modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; } else { - modifierCount.value = newItemCount; + modifierCountHolder.value = newItemCount; } } - return modifierCount; + return modifierCountHolder.value; } + // Function that resets the reward selection screen, + // e.g. after pressing cancel in the party ui or while learning a move private resetModifierSelect(modifierSelectCallback) { globalScene.ui.setMode( UiMode.MODIFIER_SELECT, From d769bd7f11ad88a5ee9f2ceb0869d0877cfb56bc Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 12:59:19 +0200 Subject: [PATCH 012/114] Extracted logic from modifierSelectCallback --- src/phases/select-modifier-phase.ts | 137 ++++++++++++++-------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index d0eab4ba86f..9bd75ac85c4 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -85,87 +85,88 @@ export class SelectModifierPhase extends BattlePhase { }); return false; } - let modifierType: ModifierType; - let cost = 0; + switch (rowCursor) { - // Execute option from the bottom row + // Execute one of the options from the bottom row case 0: - return this.playBottomRowOption(cursor, modifierSelectCallback); + switch (cursor) { + case 0: + return this.rerollModifiers(); + case 1: + return this.openModifierTransferScreen(modifierSelectCallback); + // Check the party, pass a callback to restore the modifier select screen. + case 2: + globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { + this.resetModifierSelect(modifierSelectCallback); + }); + return true; + case 3: + return this.toggleRerollLock(); + default: + return false; + } // Pick an option from the rewards case 1: - if (this.typeOptions.length === 0) { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - return true; - } - if (this.typeOptions[cursor].type) { - modifierType = this.typeOptions[cursor].type; - } - break; + return this.selectRewardModifierOption(cursor, modifierSelectCallback); // Pick an option from the shop default: { - const shopOptions = getPlayerShopModifierTypeOptionsForWave( - globalScene.currentBattle.waveIndex, - globalScene.getWaveMoneyAmount(1), - ); - const shopOption = - shopOptions[ - rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT - ]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; + return this.selectShopModifierOption(rowCursor, cursor, modifierSelectCallback); } } - - if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.ui.playError(); - return false; - } - - if (modifierType! instanceof PokemonModifierType) { - //TODO: is the bang correct? - if (modifierType instanceof FusePokemonModifierType) { - this.openFusionMenu(modifierType, cost, modifierSelectCallback); - } else { - this.openModifierMenu(modifierType, cost, modifierSelectCallback); - } - } else { - this.applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? - } - - return !cost!; // TODO: is the bang correct? }; + this.resetModifierSelect(modifierSelectCallback); } - private playBottomRowOption(cursor: number, modifierSelectCallback): boolean { - let success = false; - switch (cursor) { - case 0: - success = this.rerollModifiers(); - break; - case 1: - success = this.openModifierTransferScreen(modifierSelectCallback); - break; - // Check the party, pass a callback to restore the modifier select screen. - case 2: - globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { - this.resetModifierSelect(modifierSelectCallback); - }); - success = true; - break; - case 3: - success = this.toggleRerollLock(); - break; + // Pick a modifier from among the rewards and apply it + private selectRewardModifierOption(cursor: number, modifierSelectCallback): boolean { + if (this.typeOptions.length === 0) { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + return true; } - return success; + const modifierType = this.typeOptions[cursor].type; + return this.applyChosenModifier(modifierType, 0, modifierSelectCallback); + } + + // Pick a modifier from the shop and apply it + private selectShopModifierOption(rowCursor: number, cursor: number, modifierSelectCallback): boolean { + const shopOptions = getPlayerShopModifierTypeOptionsForWave( + globalScene.currentBattle.waveIndex, + globalScene.getWaveMoneyAmount(1), + ); + const shopOption = + shopOptions[ + rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT + ]; + const modifierType = shopOption.type; + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); + const cost = healingItemCost.value; + + if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.ui.playError(); + return false; + } + + return this.applyChosenModifier(modifierType, cost, modifierSelectCallback); + } + + // Apply a chosen modifier: do an effect or open the party menu + private applyChosenModifier(modifierType: ModifierType, cost: number, modifierSelectCallback): boolean { + if (modifierType! instanceof PokemonModifierType) { + //TODO: is the bang correct? + if (modifierType instanceof FusePokemonModifierType) { + this.openFusionMenu(modifierType, cost, modifierSelectCallback); + } else { + this.openModifierMenu(modifierType, cost, modifierSelectCallback); + } + } else { + this.applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? + } + return !cost; } // Reroll rewards From ba01c505d949a0b01d6c3a86c8470ffc40f8ec3a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:26:50 +0200 Subject: [PATCH 013/114] Moved some files around, added many held item ids --- src/enums/held-items.ts | 76 ++++++++++ src/field/pokemon-held-item-manager.ts | 44 ++++++ src/field/pokemon-item-manager.ts | 26 ---- src/field/pokemon.ts | 2 +- src/loading-scene.ts | 2 +- .../{held-items.ts => all-held-items.ts} | 54 +------ src/modifier/reward-generator.ts | 120 --------------- src/modifier/reward-pool-manager.ts | 94 ------------ src/modifier/reward-tier.ts | 8 - src/modifier/reward.ts | 143 ------------------ 10 files changed, 125 insertions(+), 444 deletions(-) create mode 100644 src/enums/held-items.ts create mode 100644 src/field/pokemon-held-item-manager.ts delete mode 100644 src/field/pokemon-item-manager.ts rename src/modifier/{held-items.ts => all-held-items.ts} (80%) delete mode 100644 src/modifier/reward-generator.ts delete mode 100644 src/modifier/reward-pool-manager.ts delete mode 100644 src/modifier/reward-tier.ts delete mode 100644 src/modifier/reward.ts diff --git a/src/enums/held-items.ts b/src/enums/held-items.ts new file mode 100644 index 00000000000..9a15559ac14 --- /dev/null +++ b/src/enums/held-items.ts @@ -0,0 +1,76 @@ +export const HeldItems = { + NONE: 0x0000, + + // Berries + SITRUS_BERRY: 0x0101, + LUM_BERRY: 0x0102, + ENIGMA_BERRY: 0x0103, + LIECHI_BERRY: 0x0104, + GANLON_BERRY: 0x0105, + PETAYA_BERRY: 0x0106, + APICOT_BERRY: 0x0107, + SALAC_BERRY: 0x0108, + LANSAT_BERRY: 0x0109, + STARF_BERRY: 0x010A, + LEPPA_BERRY: 0x010B, + + // Other items that are consumed + REVIVER_SEED: 0x0201, + WHITE_HERB: 0x0202, + + // Type Boosters + SILK_SCARF: 0x0301, + BLACK_BELT: 0x0302, + SHARP_BEAK: 0x0303, + POISON_BARB: 0x0304, + SOFT_SAND: 0x0305, + HARD_STONE: 0x0306, + SILVER_POWDER: 0x0307, + SPELL_TAG: 0x0308, + METAL_COAT: 0x0309, + CHARCOAL: 0x030A, + MYSTIC_WATER: 0x030B, + MIRACLE_SEED: 0x030C, + MAGNET: 0x030D, + TWISTED_SPOON: 0x030E, + NEVER_MELT_ICE: 0x030F, + DRAGON_FANG: 0x0310, + BLACK_GLASSES: 0x0311, + FAIRY_FEATHER: 0x0312, + + // Stat Boosters + EVIOLITE: 0x0401, + LIGHT_BALL: 0x0402, + THICK_CLUB: 0x0403, + METAL_POWDER: 0x0404, + QUICK_POWDER: 0x0405, + DEEP_SEA_SCALE: 0x0406, + DEEP_SEA_TOOTH: 0x0407, + + // Crit Boosters + SCOPE_LENS: 0x0501, + LEEK: 0x0502, + + // Items increasing gains + LUCKY_EGG: 0x0601, + GOLDEN_EGG: 0x0602, + SOOTHE_BELL: 0x0603, + + // Unique items + FOCUS_BAND: 0x0701, + QUICK_CLAW: 0x0702, + KINGS_ROCK: 0x0703, + LEFTOVERS: 0x0704, + SHELL_BELL: 0x0705, + MYSTICAL_ROCK: 0x0706, + WIDE_LENS: 0x0707, + MULTI_LENS: 0x0708, + GOLDEN_PUNCH: 0x0709, + GRIP_CLAW: 0x070A, + TOXIC_ORB: 0x070B, + FLAME_ORB: 0x070C, + SOUL_DEW: 0x070D, + BATON: 0x070E, +}; + +export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts new file mode 100644 index 00000000000..08f8416861c --- /dev/null +++ b/src/field/pokemon-held-item-manager.ts @@ -0,0 +1,44 @@ +import { allHeldItems } from "#app/modifier/all-held-items"; +import type { HeldItems } from "#app/enums/held-items"; + +interface HeldItemProperties { + stack: number; + disabled: boolean; + cooldown?: number; +} + +type HeldItemPropertyMap = { + [key in HeldItems]: HeldItemProperties; +}; + +export class PokemonItemManager { + private heldItems: HeldItemPropertyMap; + + constructor() { + this.heldItems = {}; + } + + getHeldItems(): HeldItemPropertyMap { + return this.heldItems; + } + + hasItem(itemType: HeldItems): boolean { + return itemType in this.getHeldItems(); + } + + getItem(itemType: HeldItems): HeldItemProperties { + // TODO: Not very safe + return this.heldItems[itemType]; + } + + addHeldItem(itemType: HeldItems, addStack = 1) { + const maxStack = allHeldItems[itemType].getMaxStackCount(); + + if (this.hasItem(itemType)) { + // TODO: We may want an error message of some kind instead + this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack); + } else { + this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false }; + } + } +} diff --git a/src/field/pokemon-item-manager.ts b/src/field/pokemon-item-manager.ts deleted file mode 100644 index 58fb6ba4d40..00000000000 --- a/src/field/pokemon-item-manager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { allHeldItems } from "#app/modifier/held-items"; -import type { HeldItems } from "#app/modifier/held-items"; - -export class PokemonItemManager { - private heldItems: [HeldItems, number][]; - - constructor() { - this.heldItems = []; - } - - getHeldItems(): [HeldItems, number][] { - return this.heldItems; - } - - addHeldItem(itemType: HeldItems, stack: number) { - const maxStack = allHeldItems[itemType].getMaxStackCount(); - - const existing = this.heldItems.find(([type]) => type === itemType); - - if (existing) { - existing[1] = Math.min(existing[1] + stack, maxStack); - } else { - this.heldItems.push([itemType, Math.min(stack, maxStack)]); - } - } -} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index c7d7a4a456c..1c8d1e2cf0f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -255,7 +255,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; -import { PokemonItemManager } from "./pokemon-item-manager"; +import { PokemonItemManager } from "./pokemon-held-item-manager"; export enum LearnMoveSituation { MISC, diff --git a/src/loading-scene.ts b/src/loading-scene.ts index f801061b665..38581e8ad0b 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,7 +21,7 @@ import { initVouchers } from "#app/system/voucher"; import { Biome } from "#enums/biome"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; -import { initHeldItems } from "./modifier/held-items"; +import { initHeldItems } from "./modifier/all-held-items"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; diff --git a/src/modifier/held-items.ts b/src/modifier/all-held-items.ts similarity index 80% rename from src/modifier/held-items.ts rename to src/modifier/all-held-items.ts index 170004aa024..293a91d9207 100644 --- a/src/modifier/held-items.ts +++ b/src/modifier/all-held-items.ts @@ -2,58 +2,10 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { Localizable } from "#app/interfaces/locales"; import type { NumberHolder } from "#app/utils/common"; +import { HeldItems } from "#enums/held-items"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; -export const HeldItems = { - NONE: 0x0000, - - SITRUS_BERRY: 0x0101, - LEPPA_BERRY: 0x0102, - - SILK_SCARF: 0x0201, - BLACK_BELT: 0x0202, - SHARP_BEAK: 0x0203, - POISON_BARB: 0x0204, - SOFT_SAND: 0x0205, - HARD_STONE: 0x0206, - SILVER_POWDER: 0x0207, - SPELL_TAG: 0x0208, - METAL_COAT: 0x0209, - CHARCOAL: 0x020a, - MYSTIC_WATER: 0x020b, - MIRACLE_SEED: 0x020c, - MAGNET: 0x020d, - TWISTED_SPOON: 0x020e, - NEVER_MELT_ICE: 0x020f, - DRAGON_FANG: 0x0210, - BLACK_GLASSES: 0x0211, - FAIRY_FEATHER: 0x0212, - - REVIVER_SEED: 0x0301, - SOOTHE_BELL: 0x0302, - SOUL_DEW: 0x0303, - GOLDEN_PUNCH: 0x0304, - GRIP_CLAW: 0x0305, - BATON: 0x0306, - FOCUS_BAND: 0x0307, - QUICK_CLAW: 0x0308, - KINGS_ROCK: 0x0309, - LEFTOVERS: 0x030a, - SHELL_BELL: 0x030b, -}; - -export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; - -export const HeldItemCategories = { - NONE: 0x0000, - BERRY: 0x0100, - ATTACK_TYPE_BOOSTER: 0x0200, - BASE_STAT_BOOSTER: 0x0400, -}; - -export type HeldItemCategories = (typeof HeldItemCategories)[keyof typeof HeldItemCategories]; - export class HeldItem implements Localizable { // public pokemonId: number; public type: HeldItems; @@ -207,9 +159,9 @@ export class AttackTypeBoosterHeldItem extends HeldItem { export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) { if (pokemon) { - for (const [item, stackCount] of pokemon.heldItemManager.getHeldItems()) { + for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { - allHeldItems[item].apply(stackCount, moveType, movePower); + allHeldItems[item].apply(props.stack, moveType, movePower); } } } diff --git a/src/modifier/reward-generator.ts b/src/modifier/reward-generator.ts deleted file mode 100644 index cedecd923bb..00000000000 --- a/src/modifier/reward-generator.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { AttackMove } from "#app/data/moves/move"; -import type Pokemon from "#app/field/pokemon"; -import { PokemonType } from "#enums/pokemon-type"; -import { attackTypeToHeldItem } from "./held-items"; -import { HeldItemReward, type Reward } from "./reward"; - -function getRandomWeightedSelection(weights: Map): T | null { - const totalWeight = Array.from(weights.values()).reduce((sum, weight) => sum + weight, 0); - - if (totalWeight === 0) { - return null; - } - - const randInt = Math.floor(Math.random() * totalWeight); - - let accumulatedWeight = 0; - for (const [item, weight] of weights.entries()) { - accumulatedWeight += weight; - if (randInt < accumulatedWeight) { - return item; - } - } - - return null; -} - -export class RewardGenerator { - options: T[]; - tempWeights: Map; - - constructor(options: T[]) { - this.options = options; - this.tempWeights = new Map(this.options.map(option => [option, 1])); - } - - generate(party: Pokemon[], overrideWeightFunction?: Function) { - const weights = overrideWeightFunction ? overrideWeightFunction(party) : this.weightFunction(party); - - for (const [option, tempWeight] of this.tempWeights.entries()) { - if (tempWeight === 0 && weights.has(option)) { - weights.set(option, 0); - } - } - - const value: T | null = getRandomWeightedSelection(weights); - - if (value) { - this.tempWeights.set(value, 0); - return this.generateReward(value); - } - - return null; - } - - weightFunction(_party: Pokemon[]): Map { - const defaultWeightMap = new Map(); - - this.options.forEach(option => { - defaultWeightMap.set(option, 1); - }); - - return defaultWeightMap; - } - - generateReward(_value: T): Reward | null { - return null; - } -} - -export class AttackTypeBoosterHeldItemRewardGenerator extends RewardGenerator { - constructor() { - //TODO: we can also construct this, but then have to handle options being null - const options = [ - PokemonType.NORMAL, - PokemonType.FIGHTING, - PokemonType.FLYING, - PokemonType.POISON, - PokemonType.GROUND, - PokemonType.ROCK, - PokemonType.BUG, - PokemonType.GHOST, - PokemonType.STEEL, - PokemonType.FIRE, - PokemonType.WATER, - PokemonType.GRASS, - PokemonType.ELECTRIC, - PokemonType.PSYCHIC, - PokemonType.ICE, - PokemonType.DRAGON, - PokemonType.DARK, - PokemonType.FAIRY, - ]; - super(options); - } - - weightFunction(party: Pokemon[]): Map { - const attackMoveTypes = party.flatMap(p => - p - .getMoveset() - .map(m => m.getMove()) - .filter(m => m instanceof AttackMove) - .map(m => m.type), - ); - - const attackMoveTypeWeights = new Map(); - - for (const type of attackMoveTypes) { - const currentWeight = attackMoveTypeWeights.get(type) ?? 0; - if (currentWeight < 3) { - attackMoveTypeWeights.set(type, currentWeight + 1); - } - } - - return attackMoveTypeWeights; - } - - generateReward(value: PokemonType) { - return new HeldItemReward(attackTypeToHeldItem[value]); - } -} diff --git a/src/modifier/reward-pool-manager.ts b/src/modifier/reward-pool-manager.ts deleted file mode 100644 index c49075505d6..00000000000 --- a/src/modifier/reward-pool-manager.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** -import { globalScene } from "#app/global-scene"; -import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; -import type { RewardGenerator } from "./reward-generator"; -import type { RewardTier } from "./reward-tier"; -import Overrides from "#app/overrides"; - -interface RewardPool { - [rewardTier: number]: RewardGenerator[]; -} - -export interface CustomRewardSettings { - guaranteedModifierTiers?: RewardTier[]; - guaranteedModifierTypeOptions?: ModifierTypeOption[]; - guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; - fillRemaining?: boolean; - //Set to negative value to disable rerolls completely in shop - rerollMultiplier?: number; - allowLuckUpgrades?: boolean; -} - -export class RewardPoolManager { - public rerollCount: number; - private rewardPool: RewardPool; - private customRewardSettings?: CustomRewardSettings; //TODO: have a better scheme than just this - - constructor(rewardPool: RewardPool) { - this.rewardPool = rewardPool; - } - - getRerollCost(lockRarities: boolean): number { - let baseValue = 0; - if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - return baseValue; - } - if (lockRarities) { - const tierValues = [50, 125, 300, 750, 2000]; - for (const opt of this.typeOptions) { - baseValue += tierValues[opt.type.tier ?? 0]; - } - } else { - baseValue = 250; - } - - let multiplier = 1; - if (!isNullOrUndefined(this.customRewardSettings?.rerollMultiplier)) { - if (this.customRewardSettings.rerollMultiplier < 0) { - // Completely overrides reroll cost to -1 and early exits - return -1; - } - - // Otherwise, continue with custom multiplier - multiplier = this.customRewardSettings.rerollMultiplier; - } - - const baseMultiplier = Math.min( - Math.ceil(globalScene.currentBattle.waveIndex / 10) * baseValue * 2 ** this.rerollCount * multiplier, - Number.MAX_SAFE_INTEGER, - ); - - // Apply Black Sludge to reroll cost - const modifiedRerollCost = new NumberHolder(baseMultiplier); - globalScene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); - return modifiedRerollCost.value; - } - - getRewardCount(): NumberHolder { - const modifierCount = new NumberHolder(3); - - // TODO: This code is used by golden and silver pokéball to increase the number of item slots - // They will become a trainer item, so there will be no .applyModifiers - globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); - globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); - - // If custom rewards are specified, overrides default item count - // TODO: Figure out exactly how and when that would happen - // Presumably in MEs, but possibly also after rerolls? And at specific waves... - if (this.customRewardSettings) { - const newItemCount = - (this.customRewardSettings.guaranteedModifierTiers?.length || 0) + - (this.customRewardSettings.guaranteedModifierTypeOptions?.length || 0) + - (this.customRewardSettings.guaranteedModifierTypeFuncs?.length || 0); - if (this.customRewardSettings.fillRemaining) { - const originalCount = modifierCount.value; - modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount; - } else { - modifierCount.value = newItemCount; - } - } - - return modifierCount; - } -} -*/ diff --git a/src/modifier/reward-tier.ts b/src/modifier/reward-tier.ts deleted file mode 100644 index e7ccc1d9166..00000000000 --- a/src/modifier/reward-tier.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum RewardTier { - COMMON, - GREAT, - ULTRA, - ROGUE, - MASTER, - LUXURY, -} diff --git a/src/modifier/reward.ts b/src/modifier/reward.ts deleted file mode 100644 index 351ebc3a861..00000000000 --- a/src/modifier/reward.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** -import { globalScene } from "#app/global-scene"; -import type { PokeballType } from "#enums/pokeball"; -import i18next from "i18next"; -import { allHeldItems, type HeldItems } from "./held-items"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import type Pokemon from "#app/field/pokemon"; - -export class Reward { - - getName(): string { - return ""; - } - - getDescription(): string { - return ""; - } - - getIcon(): string { - return ""; - } - - createIcon(): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); - - const item = globalScene.add.sprite(0, 12, "items"); - item.setFrame(this.getIcon()); - item.setOrigin(0, 0.5); - container.add(item); - return container; - } -} - - -export class PokeballReward extends Reward { - private pokeballType: PokeballType; - private count: number; - - constructor(pokeballType: PokeballType, count: number) { - super(); - this.pokeballType = pokeballType; - this.count = count; - } - - getName(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - catchRate: - getPokeballCatchMultiplier(this.pokeballType) > -1 - ? `${getPokeballCatchMultiplier(this.pokeballType)}x` - : "100%", - pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, - }); - } - - apply(): boolean { - const pokeballCounts = globalScene.pokeballCounts; - pokeballCounts[this.pokeballType] = Math.min( - pokeballCounts[this.pokeballType] + this.count, - MAX_PER_TYPE_POKEBALLS, - ); - return true; - } -} - -export class PartySelectReward extends Reward { - apply(): { - } -} - -export class HeldItemReward extends PartySelectReward { - private itemId: HeldItems; - - constructor(itemId: HeldItems) { - super(); - this.itemId = itemId; - } - - getName(): string { - return allHeldItems[this.itemId].getName(); - } - - getDescription(): string { - return allHeldItems[this.itemId].getDescription(); - } - - getIcon(): string { - return allHeldItems[this.itemId].getIcon(); - } - - apply(): { - } -} - - - - - - - - - - - - - - - - - -export interface RewardInfo { - options: number[]; - rewardType: ; - condition?: (party: Pokemon[], option: number) => void; - optionWeight?: (party: Pokemon[], option: number) => void; -} - - - - -interface RewardPool { - [rewardTier: number]: RewardGenerator[]; -} - - -export class RewardManager { - - private rewardPool: RewardPool; - - constructor(rewardPool: RewardPool) { - this.rewardPool = rewardPool; - } - -} -*/ From 517dec2f7e88cdc754be908c08f7e51cee18de92 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:29:42 +0200 Subject: [PATCH 014/114] Introduced HeldItemReward class --- src/modifier/modifier-type.ts | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 79db06c4760..ac569be5792 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -129,8 +129,8 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; -import type { HeldItems } from "./held-items"; -import { allHeldItems, attackTypeToHeldItem } from "./held-items"; +import type { HeldItems } from "#enums/held-items"; +import { allHeldItems, attackTypeToHeldItem } from "./all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; const outputModifierData = false; @@ -421,6 +421,55 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { } } +export class PokemonHeldItemReward extends PokemonModifierType { + public itemId: HeldItems; + constructor( + itemId: HeldItems, + localeKey: string, + iconImage: string, + newModifierFunc: NewModifierFunc, + group?: string, + soundName?: string, + ) { + super( + localeKey, + iconImage, + newModifierFunc, + (pokemon: PlayerPokemon) => { + const hasItem = pokemon.heldItemManager.hasItem(this.itemId); + const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); + if (!maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + if (hasItem && pokemon.heldItemManager.getItem(this.itemId).stack === maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + return null; + }, + group, + soundName, + ); + this.itemId = itemId; + } + + newModifier(...args: any[]): PokemonHeldItemModifier { + return super.newModifier(...args) as PokemonHeldItemModifier; + } + + get name(): string { + return allHeldItems[this.itemId].getName(); + } + + getDescription(): string { + // TODO: Need getTypeName? + return allHeldItems[this.itemId].getDescription(); + } +} + export class TerastallizeModifierType extends PokemonModifierType { private teraType: PokemonType; From 9d726e47d36b54516fbf68638fec7446391812e7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:31:17 +0200 Subject: [PATCH 015/114] Introduced AttackBoosterReward --- src/modifier/modifier-type.ts | 54 +++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ac569be5792..53c91579732 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -858,33 +858,75 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge } } +export class AttackTypeBoosterReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { + public moveType: PokemonType; + public boostPercent: number; + + constructor(moveType: PokemonType, boostPercent: number) { + const itemId = attackTypeToHeldItem[moveType]; + super( + itemId, + "", + allHeldItems[itemId].getIcon(), + (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), + ); + this.moveType = moveType; + this.boostPercent = boostPercent; + } + + getPregenArgs(): any[] { + return [this.moveType]; + } +} + +enum AttackTypeBoosterItem { + SILK_SCARF, + BLACK_BELT, + SHARP_BEAK, + POISON_BARB, + SOFT_SAND, + HARD_STONE, + SILVER_POWDER, + SPELL_TAG, + METAL_COAT, + CHARCOAL, + MYSTIC_WATER, + MIRACLE_SEED, + MAGNET, + TWISTED_SPOON, + NEVER_MELT_ICE, + DRAGON_FANG, + BLACK_GLASSES, + FAIRY_FEATHER, +} + export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { public moveType: PokemonType; public boostPercent: number; - public heldItemId: HeldItems; constructor(moveType: PokemonType, boostPercent: number) { - const heldItemId = attackTypeToHeldItem[moveType]; super( "", - allHeldItems[heldItemId].getIcon(), + `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`, (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), ); this.moveType = moveType; this.boostPercent = boostPercent; - this.heldItemId = heldItemId; // Not good, but temporary } get name(): string { - return allHeldItems[this.heldItemId].getName(); + return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); } getDescription(): string { - return allHeldItems[this.heldItemId].getDescription(); + // TODO: Need getTypeName? + return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), + }); } getPregenArgs(): any[] { From 827c462d3f622b7e8c4e93ab3aff31bfe763a666 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:33:30 +0200 Subject: [PATCH 016/114] Introduced AttackBoosterRewardGenerator --- src/modifier/modifier-type.ts | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 53c91579732..145d9264b8d 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1400,6 +1400,63 @@ export class FusePokemonModifierType extends PokemonModifierType { } } +class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { + return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); + } + + const attackMoveTypes = party.flatMap(p => + p + .getMoveset() + .map(m => m.getMove()) + .filter(m => m instanceof AttackMove) + .map(m => m.type), + ); + if (!attackMoveTypes.length) { + return null; + } + + const attackMoveTypeWeights = new Map(); + let totalWeight = 0; + for (const t of attackMoveTypes) { + if (attackMoveTypeWeights.has(t)) { + if (attackMoveTypeWeights.get(t)! < 3) { + // attackMoveTypeWeights.has(t) was checked before + attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1); + } else { + continue; + } + } else { + attackMoveTypeWeights.set(t, 1); + } + totalWeight++; + } + + if (!totalWeight) { + return null; + } + + let type: PokemonType; + + const randInt = randSeedInt(totalWeight); + let weight = 0; + + for (const t of attackMoveTypeWeights.keys()) { + const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined + if (randInt <= weight + typeWeight) { + type = t; + break; + } + weight += typeWeight; + } + + return new AttackTypeBoosterReward(type!, TYPE_BOOST_ITEM_BOOST_PERCENT); + }); + } +} + class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { @@ -2091,6 +2148,8 @@ export const modifierTypes = { BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), + ATTACK_TYPE_BOOSTER_REWARD: () => new AttackTypeBoosterRewardGenerator(), + ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), MINT: () => @@ -2975,7 +3034,7 @@ const modifierPool: ModifierPool = { ), new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 9), new WeightedModifierType(modifierTypes.TM_ULTRA, 11), new WeightedModifierType(modifierTypes.RARER_CANDY, 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), From b32400e81a0cd6e27ea61bf333a4dae8078997ad Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:42:55 +0200 Subject: [PATCH 017/114] Removed unused file --- src/modifier/held-item-pool.ts | 69 ---------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 src/modifier/held-item-pool.ts diff --git a/src/modifier/held-item-pool.ts b/src/modifier/held-item-pool.ts deleted file mode 100644 index 6d54e3561b9..00000000000 --- a/src/modifier/held-item-pool.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** -import { PlayerPokemon } from "#app/field/pokemon"; -import { randSeedInt } from "#app/utils/common"; -import { HeldItemCategories, HeldItems } from "./held-items"; -import { ModifierTier } from "./modifier-tier"; - -interface HeldItemPool { - [tier: string]: [HeldItems | HeldItemCategories, number][]; -} - -const dailyStarterHeldItemPool: HeldItemPool = { - [ModifierTier.COMMON]: [ - [HeldItemCategories.BASE_STAT_BOOSTER, 1], - [HeldItemCategories.BERRY, 3], - ], - [ModifierTier.GREAT]: [ - [HeldItemCategories.ATTACK_TYPE_BOOSTER, 5], - ], - [ModifierTier.ULTRA]: [ - [HeldItems.REVIVER_SEED, 4], - [HeldItems.SOOTHE_BELL, 1], - [HeldItems.SOUL_DEW, 1], - [HeldItems.GOLDEN_PUNCH, 1], - ], - [ModifierTier.ROGUE]: [ - [HeldItems.GRIP_CLAW, 5], - [HeldItems.BATON, 2], - [HeldItems.FOCUS_BAND, 5], - [HeldItems.QUICK_CLAW, 3], - [HeldItems.KINGS_ROCK, 3], - ], - [ModifierTier.MASTER]: [ - [HeldItems.LEFTOVERS, 1], - [HeldItems.SHELL_BELL, 1], - ], -}; - - - - -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): HeldItems[] { - const ret: HeldItems[] = []; - for (const p of party) { - for (let m = 0; m < 3; m++) { - const tierValue = randSeedInt(64); - - let tier: ModifierTier; - if (tierValue > 25) { - tier = ModifierTier.COMMON; - } else if (tierValue > 12) { - tier = ModifierTier.GREAT; - } else if (tierValue > 4) { - tier = ModifierTier.ULTRA; - } else if (tierValue) { - tier = ModifierTier.ROGUE; - } else { - tier = ModifierTier.MASTER; - } - - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier( - p, - ); - ret.push(modifier); - } - } - - return ret; -} -*/ From 77f8cd96438f9f897359114bda2f2047c9b70c4b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 29 May 2025 23:46:35 +0200 Subject: [PATCH 018/114] Select modifier phase tentatively working with new held items --- src/phases/select-modifier-phase.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 9bd75ac85c4..bf5cad31cdd 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -13,6 +13,7 @@ import { PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions, + PokemonHeldItemReward, } from "#app/modifier/modifier-type"; import type { Modifier } from "#app/modifier/modifier"; import { @@ -158,6 +159,9 @@ export class SelectModifierPhase extends BattlePhase { private applyChosenModifier(modifierType: ModifierType, cost: number, modifierSelectCallback): boolean { if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? + if (modifierType instanceof PokemonHeldItemReward) { + this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); + } if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { @@ -231,6 +235,26 @@ export class SelectModifierPhase extends BattlePhase { return true; } + private openGiveHeldItemMenu(reward, modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + const partyUiMode = PartyUiMode.MODIFIER; + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, _option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + party[slotIndex].heldItemManager.addHeldItem(reward.itemId); + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + reward.selectFilter, + ); + } + // Toggle reroll lock private toggleRerollLock() { const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); From 3f045ec7a6086e4ff78b1b0a4b30e5da7dcd785f Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 30 May 2025 01:50:15 +0200 Subject: [PATCH 019/114] Working leftovers in new style --- src/data/moves/move.ts | 2 +- src/enums/held-items.ts | 12 +++++++ src/modifier/all-held-items.ts | 54 +++++++++++++++++++++++++--- src/modifier/modifier-type.ts | 34 +++++++++--------- src/modifier/modifier.ts | 2 +- src/phases/select-modifier-phase.ts | 46 ++++++++++++------------ src/phases/turn-end-phase.ts | 4 +-- src/ui/modifier-select-ui-handler.ts | 2 +- 8 files changed, 109 insertions(+), 47 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f458dc6053c..7eedc81c282 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -122,7 +122,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; -import { applyAttackTypeBoosterHeldItem } from "#app/modifier/held-items"; +import { applyAttackTypeBoosterHeldItem } from "#app/modifier/all-held-items"; import { TrainerVariant } from "#app/field/trainer"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; diff --git a/src/enums/held-items.ts b/src/enums/held-items.ts index 9a15559ac14..7871eeedc74 100644 --- a/src/enums/held-items.ts +++ b/src/enums/held-items.ts @@ -74,3 +74,15 @@ export const HeldItems = { }; export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; + +type HeldItemName = keyof typeof HeldItems; +type HeldItemValue = typeof HeldItems[HeldItemName]; + +// Use a type-safe reducer to force number keys and values +export const HeldItemNames: Record = Object.entries(HeldItems).reduce( + (acc, [key, value]) => { + acc[value as HeldItemValue] = key as HeldItemName; + return acc; + }, + {} as Record +); \ No newline at end of file diff --git a/src/modifier/all-held-items.ts b/src/modifier/all-held-items.ts index 293a91d9207..8417c97a581 100644 --- a/src/modifier/all-held-items.ts +++ b/src/modifier/all-held-items.ts @@ -1,8 +1,10 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { Localizable } from "#app/interfaces/locales"; -import type { NumberHolder } from "#app/utils/common"; -import { HeldItems } from "#enums/held-items"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { toDmgValue, type NumberHolder } from "#app/utils/common"; +import { HeldItemNames, HeldItems } from "#enums/held-items"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; @@ -137,7 +139,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { } getName(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItems[this.type]?.toLowerCase()}`); + return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`); } getDescription(): string { @@ -147,7 +149,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { } getIcon(): string { - return `${HeldItems[this.type]?.toLowerCase()}`; + return `${HeldItemNames[this.type]?.toLowerCase()}`; } apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { @@ -167,6 +169,48 @@ export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: Pokem } } +export class TurnHealHeldItem extends HeldItem { + getName(): string { + return i18next.t("modifierType:ModifierType.LEFTOVERS.name") + " (new)"; + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.LEFTOVERS.description"); + } + + getIcon(): string { + return "leftovers"; + } + + apply(stackCount: number, pokemon: Pokemon): boolean { + if (!pokemon.isFullHp()) { + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16) * stackCount, + i18next.t("modifier:turnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + return true; + } + return false; + } +} + +export function applyTurnHealHeldItem(pokemon: Pokemon) { + if (pokemon) { + for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + if (allHeldItems[item] instanceof TurnHealHeldItem) { + allHeldItems[item].apply(props.stack, pokemon); + } + } + } +} + export const allHeldItems = {}; export function initHeldItems() { @@ -175,4 +219,6 @@ 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); + console.log(allHeldItems); } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 145d9264b8d..15689f79d6e 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -129,7 +129,7 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; -import type { HeldItems } from "#enums/held-items"; +import { HeldItems } from "#enums/held-items"; import { allHeldItems, attackTypeToHeldItem } from "./all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; @@ -177,6 +177,10 @@ export class ModifierType { return i18next.t(`${this.localeKey}.description` as any); } + getIcon(): string { + return this.iconImage; + } + setTier(tier: ModifierTier): void { this.tier = tier; } @@ -423,17 +427,10 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { export class PokemonHeldItemReward extends PokemonModifierType { public itemId: HeldItems; - constructor( - itemId: HeldItems, - localeKey: string, - iconImage: string, - newModifierFunc: NewModifierFunc, - group?: string, - soundName?: string, - ) { + constructor(itemId: HeldItems, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { super( - localeKey, - iconImage, + "", + "", newModifierFunc, (pokemon: PlayerPokemon) => { const hasItem = pokemon.heldItemManager.hasItem(this.itemId); @@ -465,9 +462,12 @@ export class PokemonHeldItemReward extends PokemonModifierType { } getDescription(): string { - // TODO: Need getTypeName? return allHeldItems[this.itemId].getDescription(); } + + getIcon(): string { + return allHeldItems[this.itemId].getIcon(); + } } export class TerastallizeModifierType extends PokemonModifierType { @@ -866,8 +866,7 @@ export class AttackTypeBoosterReward extends PokemonHeldItemReward implements Ge const itemId = attackTypeToHeldItem[moveType]; super( itemId, - "", - allHeldItems[itemId].getIcon(), + // Next argument is useless (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), ); this.moveType = moveType; @@ -2016,7 +2015,7 @@ export type GeneratorModifierOverride = { type?: Nature; } | { - name: keyof Pick; + name: keyof Pick; type?: PokemonType; } | { @@ -2371,6 +2370,9 @@ export const modifierTypes = { (type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id), ), + LEFTOVERS_REWARD: () => + new PokemonHeldItemReward(HeldItems.LEFTOVERS, (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id)), + LEFTOVERS: () => new PokemonHeldItemModifierType( "modifierType:ModifierType.LEFTOVERS", @@ -3058,7 +3060,7 @@ const modifierPool: ModifierPool = { [ModifierTier.ROGUE]: [ new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.LEFTOVERS, 3), + new WeightedModifierType(modifierTypes.LEFTOVERS_REWARD, 3), new WeightedModifierType(modifierTypes.SHELL_BELL, 3), new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 42e0155bdd8..2ec849be799 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -718,7 +718,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { const item = globalScene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); item.setScale(0.5); item.setOrigin(0, 0.5); - item.setTexture("items", this.type.iconImage); + item.setTexture("items", this.type.getIcon()); container.add(item); const stackText = this.getIconStackText(); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index bf5cad31cdd..9c62444d77c 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -161,8 +161,7 @@ export class SelectModifierPhase extends BattlePhase { //TODO: is the bang correct? if (modifierType instanceof PokemonHeldItemReward) { this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); - } - if (modifierType instanceof FusePokemonModifierType) { + } else if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { this.openModifierMenu(modifierType, cost, modifierSelectCallback); @@ -235,26 +234,6 @@ export class SelectModifierPhase extends BattlePhase { return true; } - private openGiveHeldItemMenu(reward, modifierSelectCallback) { - const party = globalScene.getPlayerParty(); - const partyUiMode = PartyUiMode.MODIFIER; - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - partyUiMode, - -1, - (slotIndex: number, _option: PartyOption) => { - if (slotIndex < 6) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - party[slotIndex].heldItemManager.addHeldItem(reward.itemId); - }); - } else { - this.resetModifierSelect(modifierSelectCallback); - } - }, - reward.selectFilter, - ); - } - // Toggle reroll lock private toggleRerollLock() { const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); @@ -370,6 +349,29 @@ export class SelectModifierPhase extends BattlePhase { ); } + private openGiveHeldItemMenu(reward, modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + const partyUiMode = PartyUiMode.MODIFIER; + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, _option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + party[slotIndex].heldItemManager.addHeldItem(reward.itemId); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + reward.selectFilter, + ); + } + // Function that determines how many reward slots are available private getModifierCount(): number { const modifierCountHolder = new NumberHolder(3); diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 756c497802b..1838f4cb8ae 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -6,7 +6,6 @@ import { TurnEndEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { - TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, @@ -16,6 +15,7 @@ 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/modifier/all-held-items"; export class TurnEndPhase extends FieldPhase { start() { @@ -30,7 +30,7 @@ export class TurnEndPhase extends FieldPhase { if (!pokemon.switchOutStatus) { pokemon.lapseTags(BattlerTagLapseType.TURN_END); - globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + applyTurnHealHeldItem(pokemon); if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { globalScene.unshiftPhase( diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 7f5bf997f88..e70ef4b4e55 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -763,7 +763,7 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.itemContainer); const getItem = () => { - const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.iconImage); + const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.getIcon()); return item; }; From d728052c2842caa8e5974f3513000c221a96a893 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 00:30:36 +0200 Subject: [PATCH 020/114] Moved modifier-bar to its own file --- src/battle-scene.ts | 2 +- src/modifier/modifier-bar.ts | 105 +++++++++++++++++++++++++++++ src/modifier/modifier.ts | 103 ---------------------------- src/ui/run-info-ui-handler.ts | 3 +- src/ui/summary-ui-handler.ts | 2 +- src/ui/target-select-ui-handler.ts | 2 +- 6 files changed, 110 insertions(+), 107 deletions(-) create mode 100644 src/modifier/modifier-bar.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cbaf07d579c..7a55a7ed571 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -28,7 +28,6 @@ import { ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, - ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, @@ -186,6 +185,7 @@ import { hasExpSprite } from "./sprites/sprite-utils"; import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; +import { ModifierBar } from "./modifier/modifier-bar"; const DEBUG_RNG = false; diff --git a/src/modifier/modifier-bar.ts b/src/modifier/modifier-bar.ts new file mode 100644 index 00000000000..994c6960c63 --- /dev/null +++ b/src/modifier/modifier-bar.ts @@ -0,0 +1,105 @@ +import { globalScene } from "#app/global-scene"; +import { type Modifier, type PersistentModifier, PokemonHeldItemModifier } from "./modifier"; + +const iconOverflowIndex = 24; + +export const modifierSortFunc = (a: Modifier, b: Modifier): number => { + const itemNameMatch = a.type.name.localeCompare(b.type.name); + const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); + const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; + const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; + + //First sort by pokemonID + if (aId < bId) { + return 1; + } + if (aId > bId) { + return -1; + } + if (aId === bId) { + //Then sort by item type + if (typeNameMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } + return typeNameMatch; + } + return 0; +}; + +export class ModifierBar extends Phaser.GameObjects.Container { + private player: boolean; + private modifierCache: PersistentModifier[]; + + constructor(enemy?: boolean) { + super(globalScene, 1 + (enemy ? 302 : 0), 2); + + this.player = !enemy; + this.setScale(0.5); + } + + /** + * Method to update content displayed in {@linkcode ModifierBar} + * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} + * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed + */ + updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) { + this.removeAll(true); + + const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); + const nonPokemonSpecificModifiers = visibleIconModifiers + .filter(m => !(m as PokemonHeldItemModifier).pokemonId) + .sort(modifierSortFunc); + const pokemonSpecificModifiers = visibleIconModifiers + .filter(m => (m as PokemonHeldItemModifier).pokemonId) + .sort(modifierSortFunc); + + const sortedVisibleIconModifiers = hideHeldItems + ? nonPokemonSpecificModifiers + : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); + + sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { + const icon = modifier.getIcon(); + if (i >= iconOverflowIndex) { + icon.setVisible(false); + } + this.add(icon); + this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => { + globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); + if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { + this.updateModifierOverflowVisibility(true); + } + }); + icon.on("pointerout", () => { + globalScene.ui.hideTooltip(); + if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { + this.updateModifierOverflowVisibility(false); + } + }); + }); + + for (const icon of this.getAll()) { + this.sendToBack(icon); + } + + this.modifierCache = modifiers; + } + + updateModifierOverflowVisibility(ignoreLimit: boolean) { + const modifierIcons = this.getAll().reverse(); + for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) { + modifier.setVisible(ignoreLimit); + } + } + + setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: number) { + const rowIcons: number = 12 + 6 * Math.max(Math.ceil(Math.min(modifierCount, 24) / 12) - 2, 0); + + const x = ((this.getIndex(icon) % rowIcons) * 26) / (rowIcons / 12); + const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; + + icon.setPosition(this.player ? x : -x, y); + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2ec849be799..cf0bdec0ee0 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -57,109 +57,6 @@ import { globalScene } from "#app/global-scene"; export type ModifierPredicate = (modifier: Modifier) => boolean; -const iconOverflowIndex = 24; - -export const modifierSortFunc = (a: Modifier, b: Modifier): number => { - const itemNameMatch = a.type.name.localeCompare(b.type.name); - const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; - const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; - - //First sort by pokemonID - if (aId < bId) { - return 1; - } - if (aId > bId) { - return -1; - } - if (aId === bId) { - //Then sort by item type - if (typeNameMatch === 0) { - return itemNameMatch; - //Finally sort by item name - } - return typeNameMatch; - } - return 0; -}; - -export class ModifierBar extends Phaser.GameObjects.Container { - private player: boolean; - private modifierCache: PersistentModifier[]; - - constructor(enemy?: boolean) { - super(globalScene, 1 + (enemy ? 302 : 0), 2); - - this.player = !enemy; - this.setScale(0.5); - } - - /** - * Method to update content displayed in {@linkcode ModifierBar} - * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} - * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed - */ - updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) { - this.removeAll(true); - - const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); - const nonPokemonSpecificModifiers = visibleIconModifiers - .filter(m => !(m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - const pokemonSpecificModifiers = visibleIconModifiers - .filter(m => (m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - - const sortedVisibleIconModifiers = hideHeldItems - ? nonPokemonSpecificModifiers - : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); - - sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { - const icon = modifier.getIcon(); - if (i >= iconOverflowIndex) { - icon.setVisible(false); - } - this.add(icon); - this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => { - globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(true); - } - }); - icon.on("pointerout", () => { - globalScene.ui.hideTooltip(); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(false); - } - }); - }); - - for (const icon of this.getAll()) { - this.sendToBack(icon); - } - - this.modifierCache = modifiers; - } - - updateModifierOverflowVisibility(ignoreLimit: boolean) { - const modifierIcons = this.getAll().reverse(); - for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) { - modifier.setVisible(ignoreLimit); - } - } - - setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: number) { - const rowIcons: number = 12 + 6 * Math.max(Math.ceil(Math.min(modifierCount, 24) / 12) - 2, 0); - - const x = ((this.getIndex(icon) % rowIcons) * 26) / (rowIcons / 12); - const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; - - icon.setPosition(this.player ? x : -x, y); - } -} - export abstract class Modifier { public type: ModifierType; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 8487533f465..ae2b949de84 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -27,6 +27,7 @@ import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { getBiomeName } from "#app/data/balance/biomes"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; +import { modifierSortFunc } from "#app/modifier/modifier-bar"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -891,7 +892,7 @@ export default class RunInfoUiHandler extends UiHandler { } } if (heldItemsList.length > 0) { - (heldItemsList as Modifier.PokemonHeldItemModifier[]).sort(Modifier.modifierSortFunc); + (heldItemsList as Modifier.PokemonHeldItemModifier[]).sort(modifierSortFunc); let row = 0; for (const [index, item] of heldItemsList.entries()) { if (index > 36) { diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index f93a1826b3e..3f1e726ca0c 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -33,7 +33,7 @@ import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import type { Ability } from "#app/data/abilities/ability-class"; import i18next from "i18next"; -import { modifierSortFunc } from "#app/modifier/modifier"; +import { modifierSortFunc } from "#app/modifier/modifier-bar"; import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; import { Nature } from "#enums/nature"; diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 5e14e5f7771..ba55d63ada7 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -6,9 +6,9 @@ import { getMoveTargets } from "../data/moves/move"; import { Button } from "#enums/buttons"; import type { Moves } from "#enums/moves"; import type Pokemon from "#app/field/pokemon"; -import type { ModifierBar } from "#app/modifier/modifier"; import { SubstituteTag } from "#app/data/battler-tags"; import { globalScene } from "#app/global-scene"; +import type { ModifierBar } from "#app/modifier/modifier-bar"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; From 28c04877db56ec389f82a25c2541d39410601313 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 01:16:15 +0200 Subject: [PATCH 021/114] Held items now show up in summary --- src/field/pokemon-held-item-manager.ts | 8 ++++++++ src/modifier/all-held-items.ts | 25 ++++++++++++++++++++++++- src/ui/summary-ui-handler.ts | 21 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 08f8416861c..ac3fe1f0afc 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -22,6 +22,10 @@ export class PokemonItemManager { return this.heldItems; } + getHeldItemKeys(): number[] { + return Object.keys(this.heldItems).map(k => Number(k)); + } + hasItem(itemType: HeldItems): boolean { return itemType in this.getHeldItems(); } @@ -31,6 +35,10 @@ export class PokemonItemManager { return this.heldItems[itemType]; } + getStack(itemType: HeldItems): number { + return itemType in this.getHeldItems() ? this.heldItems[itemType].stack : 0; + } + addHeldItem(itemType: HeldItems, addStack = 1) { const maxStack = allHeldItems[itemType].getMaxStackCount(); diff --git a/src/modifier/all-held-items.ts b/src/modifier/all-held-items.ts index 8417c97a581..5144e326182 100644 --- a/src/modifier/all-held-items.ts +++ b/src/modifier/all-held-items.ts @@ -66,7 +66,7 @@ export class HeldItem implements Localizable { return this.maxStackCount; } - createIcon(stackCount: number, _forSummary?: boolean): Phaser.GameObjects.Container { + createSummaryIcon(stackCount: number): Phaser.GameObjects.Container { const container = globalScene.add.container(0, 0); const item = globalScene.add.sprite(0, 12, "items"); @@ -79,6 +79,29 @@ export class HeldItem implements Localizable { container.add(stackText); } + container.setScale(0.5); + + return container; + } + + createPokemonIcon(stackCount: number, pokemon: Pokemon): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); + container.add(pokemonIcon); + container.setName(pokemon.id.toString()); + + const item = globalScene.add.sprite(16, 16, "items"); + item.setScale(0.5); + item.setOrigin(0, 0.5); + item.setTexture("items", this.getIcon()); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + return container; } diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 3f1e726ca0c..2b19e9c9561 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -38,6 +38,7 @@ import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; import { Nature } from "#enums/nature"; import { achvs } from "#app/system/achv"; +import { allHeldItems } from "#app/modifier/all-held-items"; enum Page { PROFILE, @@ -1047,6 +1048,26 @@ export default class SummaryUiHandler extends UiHandler { icon.on("pointerout", () => globalScene.ui.hideTooltip()); }); + const heldItemKeys = this.pokemon?.heldItemManager.getHeldItemKeys(); + // TODO: Sort them + //.sort(heldItemSortFunc); + + console.log(heldItemKeys); + + heldItemKeys?.forEach((itemKey, i) => { + const stack = this.pokemon?.heldItemManager.getStack(itemKey); + const heldItem = allHeldItems[itemKey]; + const icon = heldItem.createSummaryIcon(stack); + + console.log(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + this.statsContainer.add(icon); + + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => globalScene.ui.showTooltip(heldItem.getName(), heldItem.getDescription(), true)); + icon.on("pointerout", () => globalScene.ui.hideTooltip()); + }); + const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? From c3b303414ee55f4dc3340aa83774d17a9e59fea9 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 01:33:28 +0200 Subject: [PATCH 022/114] Added some suggestions --- src/modifier/all-held-items.ts | 30 +++++++++++++++--------------- src/modifier/modifier-type.ts | 29 +++++++++-------------------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/modifier/all-held-items.ts b/src/modifier/all-held-items.ts index 5144e326182..d8f207ec7fc 100644 --- a/src/modifier/all-held-items.ts +++ b/src/modifier/all-held-items.ts @@ -115,7 +115,7 @@ export class HeldItem implements Localizable { if (stackCount >= this.getMaxStackCount()) { text.setTint(0xf89890); } - text.setOrigin(0, 0); + text.setOrigin(0); return text; } @@ -206,21 +206,21 @@ export class TurnHealHeldItem extends HeldItem { } apply(stackCount: number, pokemon: Pokemon): boolean { - if (!pokemon.isFullHp()) { - globalScene.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16) * stackCount, - i18next.t("modifier:turnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.name, - }), - true, - ), - ); - return true; + if (pokemon.isFullHp()) { + return false; } - return false; + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16) * stackCount, + i18next.t("modifier:turnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + return true; } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 15689f79d6e..b7626a022a3 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1406,6 +1406,7 @@ class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); } + // TODO: make this consider moves or abilities that change types const attackMoveTypes = party.flatMap(p => p .getMoveset() @@ -1420,17 +1421,11 @@ class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { const attackMoveTypeWeights = new Map(); let totalWeight = 0; for (const t of attackMoveTypes) { - if (attackMoveTypeWeights.has(t)) { - if (attackMoveTypeWeights.get(t)! < 3) { - // attackMoveTypeWeights.has(t) was checked before - attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1); - } else { - continue; - } - } else { - attackMoveTypeWeights.set(t, 1); + const weight = attackMoveTypeWeights.get(t) ?? 0; + if (weight < 3) { + attackMoveTypeWeights.set(t, weight + 1); + totalWeight++; } - totalWeight++; } if (!totalWeight) { @@ -1477,17 +1472,11 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { const attackMoveTypeWeights = new Map(); let totalWeight = 0; for (const t of attackMoveTypes) { - if (attackMoveTypeWeights.has(t)) { - if (attackMoveTypeWeights.get(t)! < 3) { - // attackMoveTypeWeights.has(t) was checked before - attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1); - } else { - continue; - } - } else { - attackMoveTypeWeights.set(t, 1); + const weight = attackMoveTypeWeights.get(t) ?? 0; + if (weight < 3) { + attackMoveTypeWeights.set(t, weight + 1); + totalWeight++; } - totalWeight++; } if (!totalWeight) { From 8ae08982297dfa937759de30f778d66790a4f44d Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 01:49:31 +0200 Subject: [PATCH 023/114] Implemented more suggestions --- src/modifier/all-held-items.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modifier/all-held-items.ts b/src/modifier/all-held-items.ts index d8f207ec7fc..6f25af3055f 100644 --- a/src/modifier/all-held-items.ts +++ b/src/modifier/all-held-items.ts @@ -69,9 +69,7 @@ export class HeldItem implements Localizable { createSummaryIcon(stackCount: number): Phaser.GameObjects.Container { const container = globalScene.add.container(0, 0); - const item = globalScene.add.sprite(0, 12, "items"); - item.setFrame(this.getIcon()); - item.setOrigin(0, 0.5); + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.getIcon()).setOrigin(0, 0.5); container.add(item); const stackText = this.getIconStackText(stackCount); @@ -91,10 +89,11 @@ export class HeldItem implements Localizable { container.add(pokemonIcon); container.setName(pokemon.id.toString()); - const item = globalScene.add.sprite(16, 16, "items"); - item.setScale(0.5); - item.setOrigin(0, 0.5); - item.setTexture("items", this.getIcon()); + const item = globalScene.add + .sprite(16, 16, "items") + .setScale(0.5) + .setOrigin(0, 0.5) + .setTexture("items", this.getIcon()); container.add(item); const stackText = this.getIconStackText(stackCount); From fc790b77c5cb594233d3183e25a288821d7383ca Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 22:31:33 +0200 Subject: [PATCH 024/114] Splitting up held-item file --- src/modifier/all-held-items.ts | 238 +----------------- src/modifier/held-item.ts | 109 ++++++++ .../held-items/attack-type-booster.ts | 73 ++++++ src/modifier/held-items/turn-heal.ts | 50 ++++ 4 files changed, 236 insertions(+), 234 deletions(-) create mode 100644 src/modifier/held-item.ts create mode 100644 src/modifier/held-items/attack-type-booster.ts create mode 100644 src/modifier/held-items/turn-heal.ts diff --git a/src/modifier/all-held-items.ts b/src/modifier/all-held-items.ts index 6f25af3055f..7464a2e3a93 100644 --- a/src/modifier/all-held-items.ts +++ b/src/modifier/all-held-items.ts @@ -1,237 +1,7 @@ -import type Pokemon from "#app/field/pokemon"; -import { globalScene } from "#app/global-scene"; -import type { Localizable } from "#app/interfaces/locales"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { toDmgValue, type NumberHolder } from "#app/utils/common"; -import { HeldItemNames, HeldItems } from "#enums/held-items"; -import { PokemonType } from "#enums/pokemon-type"; -import i18next from "i18next"; - -export class HeldItem implements Localizable { - // public pokemonId: number; - public type: HeldItems; - public maxStackCount: number; - public isTransferable = true; - public isStealable = true; - public isSuppressable = true; - - public name = ""; - public description = ""; - public icon = ""; - - constructor(type: HeldItems, maxStackCount = 1) { - this.type = type; - this.maxStackCount = maxStackCount; - - this.isTransferable = true; - this.isStealable = true; - this.isSuppressable = true; - } - - localize(): void { - this.name = this.getName(); - this.description = this.getDescription(); - this.icon = this.getIcon(); - } - - getName(): string { - return ""; - } - - getDescription(): string { - return ""; - } - - getIcon(): string { - return ""; - } - - untransferable(): HeldItem { - this.isTransferable = false; - return this; - } - - unstealable(): HeldItem { - this.isStealable = false; - return this; - } - - unsuppressable(): HeldItem { - this.isSuppressable = false; - return this; - } - - getMaxStackCount(): number { - return this.maxStackCount; - } - - createSummaryIcon(stackCount: number): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); - - const item = globalScene.add.sprite(0, 12, "items").setFrame(this.getIcon()).setOrigin(0, 0.5); - container.add(item); - - const stackText = this.getIconStackText(stackCount); - if (stackText) { - container.add(stackText); - } - - container.setScale(0.5); - - return container; - } - - createPokemonIcon(stackCount: number, pokemon: Pokemon): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); - - const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); - container.add(pokemonIcon); - container.setName(pokemon.id.toString()); - - const item = globalScene.add - .sprite(16, 16, "items") - .setScale(0.5) - .setOrigin(0, 0.5) - .setTexture("items", this.getIcon()); - container.add(item); - - const stackText = this.getIconStackText(stackCount); - if (stackText) { - container.add(stackText); - } - - return container; - } - - getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount() === 1) { - return null; - } - - const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); - text.letterSpacing = -0.5; - if (stackCount >= this.getMaxStackCount()) { - text.setTint(0xf89890); - } - text.setOrigin(0); - - return text; - } - - getScoreMultiplier(): number { - return 1; - } -} - -interface AttackTypeToHeldItemMap { - [key: number]: HeldItems; -} - -export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { - [PokemonType.NORMAL]: HeldItems.SILK_SCARF, - [PokemonType.FIGHTING]: HeldItems.BLACK_BELT, - [PokemonType.FLYING]: HeldItems.SHARP_BEAK, - [PokemonType.POISON]: HeldItems.POISON_BARB, - [PokemonType.GROUND]: HeldItems.SOFT_SAND, - [PokemonType.ROCK]: HeldItems.HARD_STONE, - [PokemonType.BUG]: HeldItems.SILVER_POWDER, - [PokemonType.GHOST]: HeldItems.SPELL_TAG, - [PokemonType.STEEL]: HeldItems.METAL_COAT, - [PokemonType.FIRE]: HeldItems.CHARCOAL, - [PokemonType.WATER]: HeldItems.MYSTIC_WATER, - [PokemonType.GRASS]: HeldItems.MIRACLE_SEED, - [PokemonType.ELECTRIC]: HeldItems.MAGNET, - [PokemonType.PSYCHIC]: HeldItems.TWISTED_SPOON, - [PokemonType.ICE]: HeldItems.NEVER_MELT_ICE, - [PokemonType.DRAGON]: HeldItems.DRAGON_FANG, - [PokemonType.DARK]: HeldItems.BLACK_GLASSES, - [PokemonType.FAIRY]: HeldItems.FAIRY_FEATHER, -}; - -export class AttackTypeBoosterHeldItem extends HeldItem { - public moveType: PokemonType; - public powerBoost: number; - - constructor(type: HeldItems, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { - super(type, maxStackCount); - this.moveType = moveType; - this.powerBoost = powerBoost; - this.localize(); - } - - getName(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), - }); - } - - getIcon(): string { - return `${HeldItemNames[this.type]?.toLowerCase()}`; - } - - apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { - 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, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { - if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { - allHeldItems[item].apply(props.stack, moveType, movePower); - } - } - } -} - -export class TurnHealHeldItem extends HeldItem { - getName(): string { - return i18next.t("modifierType:ModifierType.LEFTOVERS.name") + " (new)"; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.LEFTOVERS.description"); - } - - getIcon(): string { - return "leftovers"; - } - - apply(stackCount: number, pokemon: Pokemon): boolean { - if (pokemon.isFullHp()) { - return false; - } - globalScene.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16) * stackCount, - i18next.t("modifier:turnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.name, - }), - true, - ), - ); - return true; - } -} - -export function applyTurnHealHeldItem(pokemon: Pokemon) { - if (pokemon) { - for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { - if (allHeldItems[item] instanceof TurnHealHeldItem) { - allHeldItems[item].apply(props.stack, pokemon); - } - } - } -} +import { HeldItems } from "#enums/held-items"; +import type { PokemonType } from "#enums/pokemon-type"; +import { AttackTypeBoosterHeldItem, attackTypeToHeldItem } from "./held-items/attack-type-booster"; +import { TurnHealHeldItem } from "./held-items/turn-heal"; export const allHeldItems = {}; diff --git a/src/modifier/held-item.ts b/src/modifier/held-item.ts new file mode 100644 index 00000000000..8580d516172 --- /dev/null +++ b/src/modifier/held-item.ts @@ -0,0 +1,109 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type { HeldItems } from "#enums/held-items"; + +export class HeldItem { + // public pokemonId: number; + public type: HeldItems; + public maxStackCount: number; + public isTransferable = true; + public isStealable = true; + public isSuppressable = true; + + constructor(type: HeldItems, maxStackCount = 1) { + this.type = type; + this.maxStackCount = maxStackCount; + + this.isTransferable = true; + this.isStealable = true; + this.isSuppressable = true; + } + + get name(): string { + return ""; + } + + get description(): string { + return ""; + } + + get iconName(): string { + return ""; + } + + untransferable(): HeldItem { + this.isTransferable = false; + return this; + } + + unstealable(): HeldItem { + this.isStealable = false; + return this; + } + + unsuppressable(): HeldItem { + this.isSuppressable = false; + return this; + } + + getMaxStackCount(): number { + return this.maxStackCount; + } + + createSummaryIcon(stackCount: number): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + + container.setScale(0.5); + + return container; + } + + createPokemonIcon(stackCount: number, pokemon: Pokemon): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); + container.add(pokemonIcon); + container.setName(pokemon.id.toString()); + + const item = globalScene.add + .sprite(16, 16, "items") + .setScale(0.5) + .setOrigin(0, 0.5) + .setTexture("items", this.iconName); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + + return container; + } + + getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1) { + return null; + } + + const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (stackCount >= this.getMaxStackCount()) { + text.setTint(0xf89890); + } + text.setOrigin(0); + + return text; + } + + getScoreMultiplier(): number { + return 1; + } +} diff --git a/src/modifier/held-items/attack-type-booster.ts b/src/modifier/held-items/attack-type-booster.ts new file mode 100644 index 00000000000..a02c97beb72 --- /dev/null +++ b/src/modifier/held-items/attack-type-booster.ts @@ -0,0 +1,73 @@ +import { HeldItemNames, HeldItems } from "#enums/held-items"; +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 "../held-item"; +import { allHeldItems } from "../all-held-items"; + +interface AttackTypeToHeldItemMap { + [key: number]: HeldItems; +} + +export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { + [PokemonType.NORMAL]: HeldItems.SILK_SCARF, + [PokemonType.FIGHTING]: HeldItems.BLACK_BELT, + [PokemonType.FLYING]: HeldItems.SHARP_BEAK, + [PokemonType.POISON]: HeldItems.POISON_BARB, + [PokemonType.GROUND]: HeldItems.SOFT_SAND, + [PokemonType.ROCK]: HeldItems.HARD_STONE, + [PokemonType.BUG]: HeldItems.SILVER_POWDER, + [PokemonType.GHOST]: HeldItems.SPELL_TAG, + [PokemonType.STEEL]: HeldItems.METAL_COAT, + [PokemonType.FIRE]: HeldItems.CHARCOAL, + [PokemonType.WATER]: HeldItems.MYSTIC_WATER, + [PokemonType.GRASS]: HeldItems.MIRACLE_SEED, + [PokemonType.ELECTRIC]: HeldItems.MAGNET, + [PokemonType.PSYCHIC]: HeldItems.TWISTED_SPOON, + [PokemonType.ICE]: HeldItems.NEVER_MELT_ICE, + [PokemonType.DRAGON]: HeldItems.DRAGON_FANG, + [PokemonType.DARK]: HeldItems.BLACK_GLASSES, + [PokemonType.FAIRY]: HeldItems.FAIRY_FEATHER, +}; + +export class AttackTypeBoosterHeldItem extends HeldItem { + public moveType: PokemonType; + public powerBoost: number; + + constructor(type: HeldItems, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + super(type, maxStackCount); + this.moveType = moveType; + this.powerBoost = powerBoost; + } + + getName(): string { + return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), + }); + } + + getIcon(): string { + return `${HeldItemNames[this.type]?.toLowerCase()}`; + } + + apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { + 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, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { + allHeldItems[item].apply(props.stack, moveType, movePower); + } + } + } +} diff --git a/src/modifier/held-items/turn-heal.ts b/src/modifier/held-items/turn-heal.ts new file mode 100644 index 00000000000..f2f9ec2f08b --- /dev/null +++ b/src/modifier/held-items/turn-heal.ts @@ -0,0 +1,50 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { HeldItem } from "../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 { + get name(): string { + return i18next.t("modifierType:ModifierType.LEFTOVERS.name") + " (new)"; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.LEFTOVERS.description"); + } + + get icon(): string { + return "leftovers"; + } + + apply(stackCount: number, pokemon: Pokemon): boolean { + if (pokemon.isFullHp()) { + return false; + } + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16) * stackCount, + i18next.t("modifier:turnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + return true; + } +} + +export function applyTurnHealHeldItem(pokemon: Pokemon) { + if (pokemon) { + for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + if (allHeldItems[item] instanceof TurnHealHeldItem) { + allHeldItems[item].apply(props.stack, pokemon); + } + } + } +} From 318f326e3b33f90a4c5a7949c4e031d90598a1d1 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 22:37:39 +0200 Subject: [PATCH 025/114] Fixing various imports --- src/data/moves/move.ts | 2 +- src/modifier/modifier-type.ts | 3 ++- src/phases/turn-end-phase.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index c8ee64b3d7c..4847508ec70 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -122,9 +122,9 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; -import { applyAttackTypeBoosterHeldItem } from "#app/modifier/all-held-items"; import { TrainerVariant } from "#app/field/trainer"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import { applyAttackTypeBoosterHeldItem } from "#app/modifier/held-items/attack-type-booster"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index b7626a022a3..c7b064e7dfa 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -130,8 +130,9 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; import { HeldItems } from "#enums/held-items"; -import { allHeldItems, attackTypeToHeldItem } from "./all-held-items"; +import { allHeldItems } from "./all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; +import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; const outputModifierData = false; const useMaxWeightForOutput = false; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 1838f4cb8ae..d54a578f9cd 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -15,7 +15,7 @@ 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/modifier/all-held-items"; +import { applyTurnHealHeldItem } from "#app/modifier/held-items/turn-heal"; export class TurnEndPhase extends FieldPhase { start() { From 096c3e018e9d4d2d66571f57e919e50d28ccf015 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 31 May 2025 22:57:26 +0200 Subject: [PATCH 026/114] Created items folder --- src/data/moves/move.ts | 2 +- src/field/pokemon-held-item-manager.ts | 2 +- src/{modifier => items}/all-held-items.ts | 0 src/{modifier => items}/held-item.ts | 0 src/{modifier => items}/held-items/attack-type-booster.ts | 2 +- src/{modifier => items}/held-items/turn-heal.ts | 2 +- src/loading-scene.ts | 2 +- src/modifier/modifier-type.ts | 4 ++-- src/phases/turn-end-phase.ts | 2 +- src/ui/summary-ui-handler.ts | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename src/{modifier => items}/all-held-items.ts (100%) rename src/{modifier => items}/held-item.ts (100%) rename src/{modifier => items}/held-items/attack-type-booster.ts (98%) rename src/{modifier => items}/held-items/turn-heal.ts (96%) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 4847508ec70..08d64a6d9fd 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -124,7 +124,7 @@ 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/modifier/held-items/attack-type-booster"; +import { applyAttackTypeBoosterHeldItem } from "#app/items/held-items/attack-type-booster"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index ac3fe1f0afc..06bdc55124d 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,4 +1,4 @@ -import { allHeldItems } from "#app/modifier/all-held-items"; +import { allHeldItems } from "#app/items/all-held-items"; import type { HeldItems } from "#app/enums/held-items"; interface HeldItemProperties { diff --git a/src/modifier/all-held-items.ts b/src/items/all-held-items.ts similarity index 100% rename from src/modifier/all-held-items.ts rename to src/items/all-held-items.ts diff --git a/src/modifier/held-item.ts b/src/items/held-item.ts similarity index 100% rename from src/modifier/held-item.ts rename to src/items/held-item.ts diff --git a/src/modifier/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts similarity index 98% rename from src/modifier/held-items/attack-type-booster.ts rename to src/items/held-items/attack-type-booster.ts index a02c97beb72..9324bec3229 100644 --- a/src/modifier/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -3,7 +3,7 @@ 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 "../held-item"; +import { HeldItem } from "#app/items/held-item"; import { allHeldItems } from "../all-held-items"; interface AttackTypeToHeldItemMap { diff --git a/src/modifier/held-items/turn-heal.ts b/src/items/held-items/turn-heal.ts similarity index 96% rename from src/modifier/held-items/turn-heal.ts rename to src/items/held-items/turn-heal.ts index f2f9ec2f08b..a3e1120abe2 100644 --- a/src/modifier/held-items/turn-heal.ts +++ b/src/items/held-items/turn-heal.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; -import { HeldItem } from "../held-item"; +import { HeldItem } from "#app/items/held-item"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 38581e8ad0b..fd0c3f96e33 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,7 +21,7 @@ import { initVouchers } from "#app/system/voucher"; import { Biome } from "#enums/biome"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; -import { initHeldItems } from "./modifier/all-held-items"; +import { initHeldItems } from "./items/all-held-items"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index c7b064e7dfa..dcd692462eb 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -130,9 +130,9 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; import { HeldItems } from "#enums/held-items"; -import { allHeldItems } from "./all-held-items"; +import { allHeldItems } from "#app/items/all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; -import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; +import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; const outputModifierData = false; const useMaxWeightForOutput = false; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index d54a578f9cd..2f6b46d190c 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -15,7 +15,7 @@ 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/modifier/held-items/turn-heal"; +import { applyTurnHealHeldItem } from "#app/items/held-items/turn-heal"; export class TurnEndPhase extends FieldPhase { start() { diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 6fcf2e766f4..6b21e4d9c92 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -39,7 +39,7 @@ import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; import { Nature } from "#enums/nature"; import { achvs } from "#app/system/achv"; -import { allHeldItems } from "#app/modifier/all-held-items"; +import { allHeldItems } from "#app/items/all-held-items"; enum Page { PROFILE, From cf19a01c3752444922d6e21d50cde48031850d7b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 04:03:04 +0200 Subject: [PATCH 027/114] Shell bell --- src/items/all-held-items.ts | 2 ++ src/items/held-items/hit-heal.ts | 55 ++++++++++++++++++++++++++++++++ src/modifier/modifier-type.ts | 26 ++++++++++++++- src/phases/move-effect-phase.ts | 3 +- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/items/held-items/hit-heal.ts diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 7464a2e3a93..de745f65004 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -1,6 +1,7 @@ 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"; export const allHeldItems = {}; @@ -12,5 +13,6 @@ export function initHeldItems() { allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } allHeldItems[HeldItems.LEFTOVERS] = new TurnHealHeldItem(HeldItems.LEFTOVERS, 4); + allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.LEFTOVERS, 4); console.log(allHeldItems); } diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts new file mode 100644 index 00000000000..8e710f3359c --- /dev/null +++ b/src/items/held-items/hit-heal.ts @@ -0,0 +1,55 @@ +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 { 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 HitHealHeldItem extends HeldItem { + get name(): string { + return i18next.t("modifierType:ModifierType.SHELL_BELL.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.SHELL_BELL.description"); + } + + get icon(): string { + return "shell_bell"; + } + + /** + * Applies {@linkcode HitHealModifier} + * @param pokemon The {@linkcode Pokemon} that holds the item + * @returns `true` if the {@linkcode Pokemon} was healed + */ + apply(stackCount: number, pokemon: Pokemon): boolean { + if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { + // TODO: this shouldn't be undefined AFAIK + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.turnData.totalDamageDealt / 8) * stackCount, + i18next.t("modifier:hitHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + } + return true; + } +} + +export function applyHitHealHeldItem(pokemon: Pokemon) { + if (pokemon) { + for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + if (allHeldItems[item] instanceof HitHealHeldItem) { + allHeldItems[item].apply(props.stack, pokemon); + } + } + } +} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index dcd692462eb..c32364f99ba 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2363,6 +2363,9 @@ export const modifierTypes = { LEFTOVERS_REWARD: () => new PokemonHeldItemReward(HeldItems.LEFTOVERS, (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id)), + SHELL_BELL_REWARD: () => + new PokemonHeldItemReward(HeldItems.SHELL_BELL, (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id)), + LEFTOVERS: () => new PokemonHeldItemModifierType( "modifierType:ModifierType.LEFTOVERS", @@ -3051,7 +3054,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LEFTOVERS_REWARD, 3), - new WeightedModifierType(modifierTypes.SHELL_BELL, 3), + new WeightedModifierType(modifierTypes.SHELL_BELL_REWARD, 3), new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), @@ -3675,6 +3678,27 @@ export function getEnemyModifierTypesForWave( return ret; } +export function getEnemyHeldItemsForWave( + waveIndex: number, + count: number, + party: EnemyPokemon[], + poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, + upgradeChance = 0, +): PokemonHeldItemReward[] { + const ret = new Array(count) + .fill(0) + .map( + () => + getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0) + ?.type as PokemonHeldItemReward, + ); + if (!(waveIndex % 1000)) { + // TODO: Change this line with the actual held item when implemented + ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemReward); + } + return ret; +} + export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeldItemModifier[] { const ret: PokemonHeldItemModifier[] = []; for (const p of party) { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e3773952214..15df6e35355 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -78,6 +78,7 @@ 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"; type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -416,7 +417,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 })); } - globalScene.applyModifiers(HitHealModifier, this.player, user); + applyHitHealHeldItem(user); this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null)); } } From 41a4c9ec2d93aa53c2d91bc898f6a895fc7b3996 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:14:10 +0200 Subject: [PATCH 028/114] Converted White Herb --- src/field/pokemon-held-item-manager.ts | 10 +- src/items/held-item.ts | 17 +++ .../held-items/reset-negative-stat-stage.ts | 70 +++++++++++ src/modifier/modifier-type.ts | 111 +++++++++++------- src/phases/select-modifier-phase.ts | 2 +- src/phases/stat-stage-change-phase.ts | 11 +- 6 files changed, 168 insertions(+), 53 deletions(-) create mode 100644 src/items/held-items/reset-negative-stat-stage.ts diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 06bdc55124d..71890f03247 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -39,7 +39,7 @@ export class PokemonItemManager { return itemType in this.getHeldItems() ? this.heldItems[itemType].stack : 0; } - addHeldItem(itemType: HeldItems, addStack = 1) { + add(itemType: HeldItems, addStack = 1) { const maxStack = allHeldItems[itemType].getMaxStackCount(); if (this.hasItem(itemType)) { @@ -49,4 +49,12 @@ export class PokemonItemManager { this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false }; } } + + remove(itemType: HeldItems, removeStack = 1) { + this.heldItems[itemType].stack -= removeStack; + + if (this.heldItems[itemType].stack <= 0) { + delete this.heldItems[itemType]; + } + } } diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 8580d516172..0182479727a 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -107,3 +107,20 @@ export class HeldItem { return 1; } } + +export class ConsumableHeldItem extends HeldItem { + applyConsumable(_pokemon: Pokemon): boolean { + return true; + } + + apply(pokemon: Pokemon): boolean { + const consumed = this.applyConsumable(pokemon); + + if (consumed) { + pokemon.heldItemManager.remove(this.type, 1); + return true; + } + + return false; + } +} diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts new file mode 100644 index 00000000000..c84690148d1 --- /dev/null +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -0,0 +1,70 @@ +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 { getPokemonNameWithAffix } from "#app/messages"; +import { allHeldItems } from "../all-held-items"; + +/** + * Modifier used for held items, namely White Herb, that restore adverse stat + * stages in battle. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { + get name(): string { + return i18next.t("modifierType:ModifierType.WHITE_HERB.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.WHITE_HERB.description"); + } + + get icon(): string { + return "shell_bell"; + } + /** + * Goes through the holder's stat stages and, if any are negative, resets that + * stat stage back to 0. + * @param pokemon {@linkcode Pokemon} that holds the item + * @returns `true` if any stat stages were reset, false otherwise + */ + applyConsumable(pokemon: Pokemon): boolean { + let statRestored = false; + + for (const s of BATTLE_STATS) { + if (pokemon.getStatStage(s) < 0) { + pokemon.setStatStage(s, 0); + statRestored = true; + } + } + + if (statRestored) { + globalScene.queueMessage( + i18next.t("modifier:resetNegativeStatStageApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + ); + } + 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.getHeldItems())) { + if (allHeldItems[item] instanceof ResetNegativeStatStageHeldItem) { + applied ||= allHeldItems[item].apply(pokemon); + } + } + } + return applied; +} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index c32364f99ba..8274ef1f31f 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2096,6 +2096,14 @@ export const modifierTypes = { "reviver_seed", (type, args) => new PokemonInstantReviveModifier(type, (args[0] as Pokemon).id), ), + + WHITE_HERB_REWARD: () => + new PokemonHeldItemReward( + HeldItems.WHITE_HERB, + (type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id), + ), + + // TODO: Remove the old one WHITE_HERB: () => new PokemonHeldItemModifierType( "modifierType:ModifierType.WHITE_HERB", @@ -3140,7 +3148,7 @@ const wildModifierPool: ModifierPool = { }), [ModifierTier.ULTRA]: [ new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + new WeightedModifierType(modifierTypes.WHITE_HERB_REWARD, 0), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; @@ -3685,13 +3693,12 @@ export function getEnemyHeldItemsForWave( poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance = 0, ): PokemonHeldItemReward[] { - const ret = new Array(count) - .fill(0) - .map( - () => - getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0) - ?.type as PokemonHeldItemReward, - ); + const ret = new Array(count).fill(0).map( + () => + // TODO: Change this to get held items (this function really could just return a list of ids honestly) + getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0) + ?.type as PokemonHeldItemReward, + ); if (!(waveIndex % 1000)) { // TODO: Change this line with the actual held item when implemented ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemReward); @@ -3740,13 +3747,53 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeld function getNewModifierTypeOption( party: Pokemon[], poolType: ModifierPoolType, - tier?: ModifierTier, + baseTier?: ModifierTier, upgradeCount?: number, retryCount = 0, allowLuckUpgrades = true, ): ModifierTypeOption | null { const player = !poolType; const pool = getModifierPoolForType(poolType); + const thresholds = getPoolThresholds(poolType); + + const tier = determineTier(party, player, baseTier, upgradeCount, retryCount, allowLuckUpgrades); + + const tierThresholds = Object.keys(thresholds[tier]); + const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]); + const value = randSeedInt(totalWeight); + let index: number | undefined; + for (const t of tierThresholds) { + const threshold = Number.parseInt(t); + if (value < threshold) { + index = thresholds[tier][threshold]; + break; + } + } + + if (index === undefined) { + return null; + } + + if (player) { + console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); + } + let modifierType: ModifierType | null = pool[tier][index].modifierType; + if (modifierType instanceof ModifierTypeGenerator) { + modifierType = (modifierType as ModifierTypeGenerator).generateType(party); + if (modifierType === null) { + if (player) { + console.log(ModifierTier[tier], upgradeCount); + } + return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); + } + } + + console.log(modifierType, !player ? "(enemy)" : ""); + + return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct? +} + +function getPoolThresholds(poolType: ModifierPoolType) { let thresholds: object; switch (poolType) { case ModifierPoolType.PLAYER: @@ -3765,6 +3812,17 @@ function getNewModifierTypeOption( thresholds = dailyStarterModifierPoolThresholds; break; } + return thresholds; +} + +function determineTier( + party: Pokemon[], + player: boolean, + tier?: ModifierTier, + upgradeCount?: number, + retryCount = 0, + allowLuckUpgrades = true, +): ModifierTier { if (tier === undefined) { const tierValue = randSeedInt(1024); if (!upgradeCount) { @@ -3819,40 +3877,7 @@ function getNewModifierTypeOption( retryCount = 0; tier--; } - - const tierThresholds = Object.keys(thresholds[tier]); - const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]); - const value = randSeedInt(totalWeight); - let index: number | undefined; - for (const t of tierThresholds) { - const threshold = Number.parseInt(t); - if (value < threshold) { - index = thresholds[tier][threshold]; - break; - } - } - - if (index === undefined) { - return null; - } - - if (player) { - console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); - } - let modifierType: ModifierType | null = pool[tier][index].modifierType; - if (modifierType instanceof ModifierTypeGenerator) { - modifierType = (modifierType as ModifierTypeGenerator).generateType(party); - if (modifierType === null) { - if (player) { - console.log(ModifierTier[tier], upgradeCount); - } - return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); - } - } - - console.log(modifierType, !player ? "(enemy)" : ""); - - return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct? + return tier; } export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 9c62444d77c..7afac410918 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -359,7 +359,7 @@ export class SelectModifierPhase extends BattlePhase { (slotIndex: number, _option: PartyOption) => { if (slotIndex < 6) { globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - party[slotIndex].heldItemManager.addHeldItem(reward.itemId); + party[slotIndex].heldItemManager.add(reward.itemId); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE); super.end(); diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 6731e45025c..1b63e5af7db 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -15,7 +15,6 @@ import { ArenaTagSide, MistTag } from "#app/data/arena-tag"; import type { ArenaTag } from "#app/data/arena-tag"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { ResetNegativeStatStageModifier } from "#app/modifier/modifier"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { NumberHolder, BooleanHolder, isNullOrUndefined } from "#app/utils/common"; import i18next from "i18next"; @@ -23,6 +22,7 @@ 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"; export type StatStageChangeCallback = ( target: Pokemon | null, @@ -239,14 +239,9 @@ export class StatStageChangePhase extends PokemonPhase { ); if (!(existingPhase instanceof StatStageChangePhase)) { // Apply White Herb if needed - const whiteHerb = globalScene.applyModifier( - ResetNegativeStatStageModifier, - this.player, - pokemon, - ) as ResetNegativeStatStageModifier; - // If the White Herb was applied, consume it + const whiteHerb = applyResetNegativeStatStageHeldItem(pokemon); + // If the White Herb was applied, update scene modifiers if (whiteHerb) { - pokemon.loseHeldItem(whiteHerb); globalScene.updateModifiers(this.player); } } From da18bf6ea97339b8043a0483fcb67f48be47c880 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:21:33 +0200 Subject: [PATCH 029/114] HeldItem .apply() methods do not really need the stack as input, just the pokemon --- src/items/held-items/attack-type-booster.ts | 7 ++++--- src/items/held-items/hit-heal.ts | 7 ++++--- src/items/held-items/turn-heal.ts | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 9324bec3229..cb8966fbb99 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -55,7 +55,8 @@ export class AttackTypeBoosterHeldItem extends HeldItem { return `${HeldItemNames[this.type]?.toLowerCase()}`; } - apply(stackCount: number, moveType: PokemonType, movePower: NumberHolder): void { + apply(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder): void { + const stackCount = pokemon.heldItemManager.getStack(this.type); if (moveType === this.moveType && movePower.value >= 1) { movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost)); } @@ -64,9 +65,9 @@ export class AttackTypeBoosterHeldItem extends HeldItem { export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) { if (pokemon) { - for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { - allHeldItems[item].apply(props.stack, moveType, movePower); + allHeldItems[item].apply(pokemon, moveType, movePower); } } } diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 8e710f3359c..8ea06d6cc6f 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -25,7 +25,8 @@ export class HitHealHeldItem extends HeldItem { * @param pokemon The {@linkcode Pokemon} that holds the item * @returns `true` if the {@linkcode Pokemon} was healed */ - apply(stackCount: number, pokemon: Pokemon): boolean { + apply(pokemon: Pokemon): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { // TODO: this shouldn't be undefined AFAIK globalScene.unshiftPhase( @@ -46,9 +47,9 @@ export class HitHealHeldItem extends HeldItem { export function applyHitHealHeldItem(pokemon: Pokemon) { if (pokemon) { - for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { if (allHeldItems[item] instanceof HitHealHeldItem) { - allHeldItems[item].apply(props.stack, pokemon); + allHeldItems[item].apply(pokemon); } } } diff --git a/src/items/held-items/turn-heal.ts b/src/items/held-items/turn-heal.ts index a3e1120abe2..348d03c243c 100644 --- a/src/items/held-items/turn-heal.ts +++ b/src/items/held-items/turn-heal.ts @@ -20,7 +20,8 @@ export class TurnHealHeldItem extends HeldItem { return "leftovers"; } - apply(stackCount: number, pokemon: Pokemon): boolean { + apply(pokemon: Pokemon): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.isFullHp()) { return false; } @@ -41,9 +42,9 @@ export class TurnHealHeldItem extends HeldItem { export function applyTurnHealHeldItem(pokemon: Pokemon) { if (pokemon) { - for (const [item, props] of Object.entries(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { if (allHeldItems[item] instanceof TurnHealHeldItem) { - allHeldItems[item].apply(props.stack, pokemon); + allHeldItems[item].apply(pokemon); } } } From b885b9c62cce4895c4c6a3b40a892518d561ed65 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:37:22 +0200 Subject: [PATCH 030/114] Made heldItems public in heldItemManager --- src/field/pokemon-held-item-manager.ts | 10 +++------- src/items/held-items/attack-type-booster.ts | 2 +- src/items/held-items/hit-heal.ts | 2 +- src/items/held-items/reset-negative-stat-stage.ts | 2 +- src/items/held-items/turn-heal.ts | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 71890f03247..52eabc9b50a 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -12,22 +12,18 @@ type HeldItemPropertyMap = { }; export class PokemonItemManager { - private heldItems: HeldItemPropertyMap; + public heldItems: HeldItemPropertyMap; constructor() { this.heldItems = {}; } - getHeldItems(): HeldItemPropertyMap { - return this.heldItems; - } - getHeldItemKeys(): number[] { return Object.keys(this.heldItems).map(k => Number(k)); } hasItem(itemType: HeldItems): boolean { - return itemType in this.getHeldItems(); + return itemType in this.heldItems; } getItem(itemType: HeldItems): HeldItemProperties { @@ -36,7 +32,7 @@ export class PokemonItemManager { } getStack(itemType: HeldItems): number { - return itemType in this.getHeldItems() ? this.heldItems[itemType].stack : 0; + return itemType in this.heldItems ? this.heldItems[itemType].stack : 0; } add(itemType: HeldItems, addStack = 1) { diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index cb8966fbb99..275d1d5b8d5 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -65,7 +65,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { export function applyAttackTypeBoosterHeldItem(pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder) { if (pokemon) { - for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { if (allHeldItems[item] instanceof AttackTypeBoosterHeldItem) { allHeldItems[item].apply(pokemon, moveType, movePower); } diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 8ea06d6cc6f..eb32dccf104 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -47,7 +47,7 @@ export class HitHealHeldItem extends HeldItem { export function applyHitHealHeldItem(pokemon: Pokemon) { if (pokemon) { - for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { if (allHeldItems[item] instanceof HitHealHeldItem) { allHeldItems[item].apply(pokemon); } diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index c84690148d1..d27514ec888 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -60,7 +60,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { export function applyResetNegativeStatStageHeldItem(pokemon: Pokemon): boolean { let applied = false; if (pokemon) { - for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { if (allHeldItems[item] instanceof ResetNegativeStatStageHeldItem) { applied ||= allHeldItems[item].apply(pokemon); } diff --git a/src/items/held-items/turn-heal.ts b/src/items/held-items/turn-heal.ts index 348d03c243c..c48205022e6 100644 --- a/src/items/held-items/turn-heal.ts +++ b/src/items/held-items/turn-heal.ts @@ -42,7 +42,7 @@ export class TurnHealHeldItem extends HeldItem { export function applyTurnHealHeldItem(pokemon: Pokemon) { if (pokemon) { - for (const item of Object.keys(pokemon.heldItemManager.getHeldItems())) { + for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { if (allHeldItems[item] instanceof TurnHealHeldItem) { allHeldItems[item].apply(pokemon); } From db580a673540564f312756c01e129ef0bf5252d2 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:24:16 +0200 Subject: [PATCH 031/114] Update modifier bar is now called in the apply of consumable held items --- src/items/held-item.ts | 4 +++- src/phases/stat-stage-change-phase.ts | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 0182479727a..db0aa70ce20 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -113,11 +113,13 @@ export class ConsumableHeldItem extends HeldItem { return true; } - apply(pokemon: Pokemon): boolean { + 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; } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 1b63e5af7db..77ed54979db 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -239,11 +239,7 @@ export class StatStageChangePhase extends PokemonPhase { ); if (!(existingPhase instanceof StatStageChangePhase)) { // Apply White Herb if needed - const whiteHerb = applyResetNegativeStatStageHeldItem(pokemon); - // If the White Herb was applied, update scene modifiers - if (whiteHerb) { - globalScene.updateModifiers(this.player); - } + applyResetNegativeStatStageHeldItem(pokemon); } pokemon.updateInfo(); From 7ba7c9c529646d7762b7c1d1fd1feacb4318e7ad Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:00:15 +0200 Subject: [PATCH 032/114] Refactored parameters passed to .apply() methods; introduced generic .applyHeldItems() function; all HeldItems classes specify an ITEM_EFFECT --- src/data/moves/move.ts | 9 +++-- src/items/all-held-items.ts | 33 ++++++++++++++--- src/items/held-item.ts | 27 +++++++------- src/items/held-items/attack-type-booster.ts | 28 ++++++++------- src/items/held-items/hit-heal.ts | 23 ++++++------ .../held-items/reset-negative-stat-stage.ts | 36 +++++++++---------- .../{turn-heal.ts => turn-end-heal.ts} | 25 ++++++------- src/phases/move-effect-phase.ts | 5 +-- src/phases/stat-stage-change-phase.ts | 5 +-- src/phases/turn-end-phase.ts | 5 +-- 10 files changed, 110 insertions(+), 86 deletions(-) rename src/items/held-items/{turn-heal.ts => turn-end-heal.ts} (69%) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 08d64a6d9fd..685fc087e3e 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -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)) { diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index de745f65004..4b382f2992d 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -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(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); + } + } + } +} diff --git a/src/items/held-item.ts b/src/items/held-item.ts index db0aa70ce20..c2b5d10bb25 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -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; - } } diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 275d1d5b8d5..4d1df7af8ff 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -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); - } - } - } -} diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index eb32dccf104..dd2f8c73eeb 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -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); - } - } - } -} diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index d27514ec888..90a2e2cd8d6 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -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; } diff --git a/src/items/held-items/turn-heal.ts b/src/items/held-items/turn-end-heal.ts similarity index 69% rename from src/items/held-items/turn-heal.ts rename to src/items/held-items/turn-end-heal.ts index c48205022e6..1bad2e42e7d 100644 --- a/src/items/held-items/turn-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -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); - } - } - } -} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 15df6e35355..6eec1be2216 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -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)); } } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 77ed54979db..f8a9c7124a4 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -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(); diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 2f6b46d190c..184fbc491e1 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -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( From 95684881618c690a14c6a4acd28936bed21d2e8f Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:48:14 +0200 Subject: [PATCH 033/114] Lucky egg and Golden egg --- src/battle-scene.ts | 5 ++- src/items/all-held-items.ts | 8 +++- src/items/held-item.ts | 1 + src/items/held-items/exp-booster.ts | 64 +++++++++++++++++++++++++++++ src/modifier/modifier-type.ts | 18 ++++++-- 5 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 src/items/held-items/exp-booster.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 7a55a7ed571..40818a8eaaf 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -30,7 +30,6 @@ import { HealingBoosterModifier, MultipleParticipantExpBonusModifier, PersistentModifier, - PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, @@ -186,6 +185,8 @@ import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; import { ModifierBar } from "./modifier/modifier-bar"; +import { applyHeldItems } from "./items/all-held-items"; +import { ITEM_EFFECT } from "./items/held-item"; const DEBUG_RNG = false; @@ -3722,7 +3723,7 @@ export default class BattleScene extends SceneBase { expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; } const pokemonExp = new NumberHolder(expValue * expMultiplier); - this.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + applyHeldItems(ITEM_EFFECT.EXP_BOOSTER, { pokemon: partyMember, expAmount: pokemonExp }); partyMemberExp.push(Math.floor(pokemonExp.value)); } diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 4b382f2992d..ec054f493a5 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -6,6 +6,7 @@ import { AttackTypeBoosterHeldItem, attackTypeToHeldItem, } from "./held-items/attack-type-booster"; +import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-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"; @@ -20,7 +21,11 @@ export function initHeldItems() { allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } allHeldItems[HeldItems.LEFTOVERS] = new TurnEndHealHeldItem(HeldItems.LEFTOVERS, 4); - allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.LEFTOVERS, 4); + allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.SHELL_BELL, 4); + + allHeldItems[HeldItems.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItems.LUCKY_EGG, 99, 40); + allHeldItems[HeldItems.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItems.GOLDEN_EGG, 99, 100); + console.log(allHeldItems); } @@ -29,6 +34,7 @@ type APPLY_HELD_ITEMS_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; + [ITEM_EFFECT.EXP_BOOSTER]: EXP_BOOST_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 c2b5d10bb25..013593a738b 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -7,6 +7,7 @@ export const ITEM_EFFECT = { TURN_END_HEAL: 2, HIT_HEAL: 3, RESET_NEGATIVE_STAT_STAGE: 4, + EXP_BOOSTER: 5, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts new file mode 100644 index 00000000000..e9b5090f972 --- /dev/null +++ b/src/items/held-items/exp-booster.ts @@ -0,0 +1,64 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItems } from "#enums/held-items"; +import i18next from "i18next"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface EXP_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + expAmount: NumberHolder; +} + +export class ExpBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.EXP_BOOSTER]; + private boostMultiplier: number; + + constructor(type: HeldItems, maxStackCount = 1, boostPercent: number) { + super(type, maxStackCount); + this.boostMultiplier = boostPercent * 0.01; + } + + get name(): string { + return this.type === HeldItems.LUCKY_EGG + ? i18next.t("modifierType:ModifierType.LUCKY_EGG.name") + : i18next.t("modifierType:ModifierType.GOLDEN_EGG.name"); + } + + get description(): string { + return this.type === HeldItems.LUCKY_EGG + ? i18next.t("modifierType:ModifierType.LUCKY_EGG.description") + : i18next.t("modifierType:ModifierType.GOLDEN_EGG.description"); + } + + get icon(): string { + return this.type === HeldItems.LUCKY_EGG ? "lucky_egg" : "golden_egg"; + } + + // TODO: What do we do with this? Need to look up all the shouldApply + /** + * Checks if {@linkcode PokemonExpBoosterModifier} should be applied + * @param pokemon The {@linkcode Pokemon} to apply the exp boost to + * @param boost {@linkcode NumberHolder} holding the exp boost value + * @returns `true` if {@linkcode PokemonExpBoosterModifier} should be applied + */ + // override shouldApply(pokemon: Pokemon, boost: NumberHolder): boolean { + // return super.shouldApply(pokemon, boost) && !!boost; + // } + + /** + * Applies {@linkcode PokemonExpBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the exp boost to + * @param boost {@linkcode NumberHolder} holding the exp boost value + * @returns always `true` + */ + apply(params: EXP_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const expAmount = params.expAmount; + const stackCount = pokemon.heldItemManager.getStack(this.type); + expAmount.value = Math.floor(expAmount.value * (1 + stackCount * this.boostMultiplier)); + + return true; + } +} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 8274ef1f31f..2ee938d7f96 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2236,6 +2236,18 @@ export const modifierTypes = { GOLDEN_EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EXP_CHARM", "golden_exp_charm", 100), + LUCKY_EGG_REWARD: () => + new PokemonHeldItemReward( + HeldItems.LUCKY_EGG, + (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), + ), + GOLDEN_EGG_REWARD: () => + new PokemonHeldItemReward( + HeldItems.GOLDEN_EGG, + (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), + ), + + // TODO: Remove these when refactor is done LUCKY_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.LUCKY_EGG", "lucky_egg", 40), GOLDEN_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EGG", "golden_egg", 100), @@ -3147,17 +3159,17 @@ const wildModifierPool: ModifierPool = { return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 10), new WeightedModifierType(modifierTypes.WHITE_HERB_REWARD, 0), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), - [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { + [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG_REWARD, 4)].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), - [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { + [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG_REWARD, 1)].map(m => { m.setTier(ModifierTier.MASTER); return m; }), From 9e1bbee58f41780ffe7eacceb89b4c8657f959fe Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 3 Jun 2025 23:19:44 +0200 Subject: [PATCH 034/114] Converted wild pokemon pool to held item rewards --- src/battle-scene.ts | 6 +- src/enums/held-items.ts | 11 ++ src/items/all-held-items.ts | 28 +++++ src/items/held-item.ts | 20 +++- src/items/held-items/attack-type-booster.ts | 1 + src/items/held-items/base-stat-booster.ts | 81 +++++++++++++ src/items/held-items/berry.ts | 99 ++++++++++++++++ src/items/held-items/hit-heal.ts | 2 +- .../held-items/reset-negative-stat-stage.ts | 2 +- src/modifier/modifier-type.ts | 109 +++++++++++++++--- 10 files changed, 334 insertions(+), 25 deletions(-) create mode 100644 src/items/held-items/base-stat-booster.ts create mode 100644 src/items/held-items/berry.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 40818a8eaaf..4be9247f18c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -53,7 +53,7 @@ import { allMoves } from "./data/data-lists"; import { MusicPreference } from "#app/system/settings/settings"; import { getDefaultModifierTypeForTier, - getEnemyModifierTypesForWave, + getEnemyHeldItemsForWave, getLuckString, getLuckTextTint, getModifierPoolForType, @@ -3223,13 +3223,13 @@ export default class BattleScene extends SceneBase { if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyModifierTypesForWave( + getEnemyHeldItemsForWave( difficultyWaveIndex, count, [enemyPokemon], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance, - ).map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false)); + ).map(itemId => enemyPokemon.heldItemManager.add(itemId)); } return true; }); diff --git a/src/enums/held-items.ts b/src/enums/held-items.ts index 7871eeedc74..727dc4f7f0e 100644 --- a/src/enums/held-items.ts +++ b/src/enums/held-items.ts @@ -71,6 +71,17 @@ export const HeldItems = { FLAME_ORB: 0x070C, SOUL_DEW: 0x070D, BATON: 0x070E, + + // Mini Black Hole + MINI_BLACK_HOLE: 0x0801, + + // Vitamins + HP_UP: 0x0901, + PROTEIN: 0x0902, + IRON: 0x0903, + CALCIUM: 0x0904, + ZINC: 0x0905, + CARBOS: 0x0906, }; export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index ec054f493a5..273cf8221b8 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -1,11 +1,20 @@ +import { getEnumValues } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; import { HeldItems } from "#enums/held-items"; import type { PokemonType } from "#enums/pokemon-type"; +import type { PermanentStat } from "#enums/stat"; import { ITEM_EFFECT } from "./held-item"; import { type ATTACK_TYPE_BOOST_PARAMS, AttackTypeBoosterHeldItem, attackTypeToHeldItem, } from "./held-items/attack-type-booster"; +import { + type BASE_STAT_BOOSTER_PARAMS, + BaseStatBoosterHeldItem, + permanentStatToHeldItem, +} from "./held-items/base-stat-booster"; +import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-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"; @@ -20,12 +29,29 @@ export function initHeldItems() { const pokemonType = Number(typeKey) as PokemonType; allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } + + // vitamins + for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { + const stat = Number(statKey) as PermanentStat; + allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); + } + allHeldItems[HeldItems.LEFTOVERS] = new TurnEndHealHeldItem(HeldItems.LEFTOVERS, 4); allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.SHELL_BELL, 4); allHeldItems[HeldItems.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItems.LUCKY_EGG, 99, 40); allHeldItems[HeldItems.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItems.GOLDEN_EGG, 99, 100); + for (const berry of getEnumValues(BerryType)) { + let maxStackCount: number; + if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry)) { + maxStackCount = 2; + } else { + maxStackCount = 3; + } + const berryId = berryTypeToHeldItem[berry]; + allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount); + } console.log(allHeldItems); } @@ -35,6 +61,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.HIT_HEAL]: HIT_HEAL_PARAMS; [ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS; [ITEM_EFFECT.EXP_BOOSTER]: EXP_BOOST_PARAMS; + [ITEM_EFFECT.BERRY]: BERRY_PARAMS; + [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_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 013593a738b..8d1f5137637 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,3 +1,4 @@ +import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { HeldItems } from "#enums/held-items"; @@ -8,6 +9,9 @@ export const ITEM_EFFECT = { HIT_HEAL: 3, RESET_NEGATIVE_STAT_STAGE: 4, EXP_BOOSTER: 5, + // Should we actually distinguish different berry effects? + BERRY: 6, + BASE_STAT_BOOSTER: 7, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; @@ -41,6 +45,7 @@ export class HeldItem { return ""; } + // TODO: Aren't these fine as just properties to set in the subclass definition? untransferable(): HeldItem { this.isTransferable = false; return this; @@ -119,10 +124,15 @@ export class HeldItem { } export class ConsumableHeldItem extends HeldItem { - consume(pokemon: Pokemon, isPlayer: boolean): boolean { - pokemon.heldItemManager.remove(this.type, 1); - // TODO: Turn this into updateItemBar or something - globalScene.updateModifiers(isPlayer); - return true; + // Sometimes berries are not eaten, some stuff may not proc unburden... + consume(pokemon: Pokemon, isPlayer: boolean, remove = true, unburden = true): void { + if (remove) { + pokemon.heldItemManager.remove(this.type, 1); + // TODO: Turn this into updateItemBar or something + globalScene.updateModifiers(isPlayer); + } + if (unburden) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + } } } diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 4d1df7af8ff..f9048480e3f 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -44,6 +44,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { public moveType: PokemonType; public powerBoost: number; + // This constructor may need a revision constructor(type: HeldItems, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { super(type, maxStackCount); this.moveType = moveType; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts new file mode 100644 index 00000000000..d65675a35c9 --- /dev/null +++ b/src/items/held-items/base-stat-booster.ts @@ -0,0 +1,81 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItems } from "#enums/held-items"; +import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; +import i18next from "i18next"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface BASE_STAT_BOOSTER_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + baseStats: number[]; +} + +interface PermanentStatToHeldItemMap { + [key: number]: HeldItems; +} + +export const permanentStatToHeldItem: PermanentStatToHeldItemMap = { + [Stat.HP]: HeldItems.HP_UP, + [Stat.ATK]: HeldItems.PROTEIN, + [Stat.DEF]: HeldItems.IRON, + [Stat.SPATK]: HeldItems.CALCIUM, + [Stat.SPDEF]: HeldItems.ZINC, + [Stat.SPD]: HeldItems.CARBOS, +}; + +export const statBoostItems: Record = { + [Stat.HP]: "hp_up", + [Stat.ATK]: "protein", + [Stat.DEF]: "iron", + [Stat.SPATK]: "calcium", + [Stat.SPDEF]: "zinc", + [Stat.SPD]: "carbos", +}; + +export class BaseStatBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BASE_STAT_BOOSTER]; + public stat: PermanentStat; + + constructor(type: HeldItems, maxStackCount = 1, stat: PermanentStat) { + super(type, maxStackCount); + this.stat = stat; + } + + get name(): string { + return i18next.t(`modifierType:BaseStatBoosterItem.${statBoostItems[this.stat]}`); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { + stat: i18next.t(getStatKey(this.stat)), + }); + } + + get iconName(): string { + return statBoostItems[this.stat]; + } + + /** + * Checks if {@linkcode BaseStatModifier} should be applied to the specified {@linkcode Pokemon}. + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode Pokemon} should be modified + */ + // override shouldApply(_pokemon?: Pokemon, baseStats?: number[]): boolean { + // return super.shouldApply(_pokemon, baseStats) && Array.isArray(baseStats); + // } + + /** + * Applies the {@linkcode BaseStatModifier} to the specified {@linkcode Pokemon}. + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + apply(params: BASE_STAT_BOOSTER_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const baseStats = params.baseStats; + baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1)); + return true; + } +} diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts new file mode 100644 index 00000000000..28f453d664c --- /dev/null +++ b/src/items/held-items/berry.ts @@ -0,0 +1,99 @@ +import { getBerryEffectDescription, getBerryEffectFunc, getBerryName } from "#app/data/berry"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ConsumableHeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import { PreserveBerryModifier } from "#app/modifier/modifier"; +import { BooleanHolder } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; +import { HeldItems } from "#enums/held-items"; + +interface BerryTypeToHeldItemMap { + [key: number]: HeldItems; +} + +export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { + [BerryType.SITRUS]: HeldItems.SITRUS_BERRY, + [BerryType.LUM]: HeldItems.LUM_BERRY, + [BerryType.ENIGMA]: HeldItems.ENIGMA_BERRY, + [BerryType.LIECHI]: HeldItems.LIECHI_BERRY, + [BerryType.GANLON]: HeldItems.GANLON_BERRY, + [BerryType.PETAYA]: HeldItems.PETAYA_BERRY, + [BerryType.APICOT]: HeldItems.APICOT_BERRY, + [BerryType.SALAC]: HeldItems.SALAC_BERRY, + [BerryType.LANSAT]: HeldItems.LANSAT_BERRY, + [BerryType.STARF]: HeldItems.STARF_BERRY, + [BerryType.LEPPA]: HeldItems.LEPPA_BERRY, +}; + +export interface BERRY_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** Whether the move was used by a player pokemon */ + isPlayer: boolean; +} + +// TODO: Maybe split up into subclasses? +export class BerryHeldItem extends ConsumableHeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BERRY]; + public berryType: BerryType; + + constructor(berryType: BerryType, maxStackCount = 1) { + const type = berryTypeToHeldItem[berryType]; + super(type, maxStackCount); + + this.berryType = berryType; + } + + get name(): string { + return getBerryName(this.berryType); + } + + get description(): string { + return getBerryEffectDescription(this.berryType); + } + + get iconName(): string { + return `${BerryType[this.berryType].toLowerCase()}_berry`; + } + + /** + * Checks if {@linkcode BerryModifier} should be applied + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns `true` if {@linkcode BerryModifier} should be applied + */ + // override shouldApply(pokemon: Pokemon): boolean { + // return !this.consumed && super.shouldApply(pokemon) && getBerryPredicate(this.berryType)(pokemon); + // } + + /** + * Applies {@linkcode BerryHeldItem} + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns always `true` + */ + apply(params: BERRY_PARAMS): boolean { + const pokemon = params.pokemon; + const isPlayer = params.isPlayer; + + const preserve = new BooleanHolder(false); + globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); + const consumed = !preserve.value; + + // munch the berry and trigger unburden-like effects + getBerryEffectFunc(this.berryType)(pokemon); + this.consume(pokemon, isPlayer, consumed); + + // TODO: Update this method to work with held items + // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. + // Don't recover it if we proc berry pouch (no item duplication) + pokemon.recordEatenBerry(this.berryType, consumed); + + return true; + } + + getMaxHeldItemCount(_pokemon: Pokemon): number { + if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { + return 2; + } + return 3; + } +} diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index dd2f8c73eeb..2117572e6cf 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -22,7 +22,7 @@ export class HitHealHeldItem extends HeldItem { return i18next.t("modifierType:ModifierType.SHELL_BELL.description"); } - get icon(): string { + get iconName(): string { return "shell_bell"; } diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 90a2e2cd8d6..9e236a8f458 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -58,7 +58,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { }), ); - this.consume(pokemon, isPlayer); + this.consume(pokemon, isPlayer, true, false); } return statRestored; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2ee938d7f96..e707804512b 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -133,6 +133,8 @@ import { HeldItems } from "#enums/held-items"; import { allHeldItems } from "#app/items/all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; +import { permanentStatToHeldItem, statBoostItems } from "#app/items/held-items/base-stat-booster"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -859,6 +861,26 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge } } +export class BerryReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { + private berryType: BerryType; + + constructor(berryType: BerryType) { + const itemId = berryTypeToHeldItem[berryType]; + super( + itemId, + // Next argument is useless + (type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType), + ); + + this.berryType = berryType; + this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework + } + + getPregenArgs(): any[] { + return [this.berryType]; + } +} + export class AttackTypeBoosterReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { public moveType: PokemonType; public boostPercent: number; @@ -1029,6 +1051,24 @@ export class BaseStatBoosterModifierType } } +export class BaseStatBoosterReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { + private stat: PermanentStat; + private key: string; + + constructor(stat: PermanentStat) { + const key = statBoostItems[stat]; + const itemId = permanentStatToHeldItem[stat]; + super(itemId, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); + + this.stat = stat; + this.key = key; + } + + getPregenArgs(): any[] { + return [this.stat]; + } +} + /** * Shuckle Juice item */ @@ -1524,6 +1564,18 @@ class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { } } +class BaseStatBoosterRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + return new BaseStatBoosterReward(pregenArgs[0]); + } + const randStat: PermanentStat = randSeedInt(Stat.SPD + 1); + return new BaseStatBoosterReward(randStat); + }); + } +} + class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { public static readonly items: Record = { [Stat.ATK]: "x_attack", @@ -2143,6 +2195,8 @@ export const modifierTypes = { } })("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new TempCritBoosterModifier(type, 5)), + BASE_STAT_BOOSTER_REWARD: () => new BaseStatBoosterRewardGenerator(), + BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), ATTACK_TYPE_BOOSTER_REWARD: () => new AttackTypeBoosterRewardGenerator(), @@ -2189,6 +2243,26 @@ export const modifierTypes = { return new TerastallizeModifierType(shardType); }), + BERRY_REWARD: () => + new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { + return new BerryModifierType(pregenArgs[0] as BerryType); + } + const berryTypes = getEnumValues(BerryType); + let randBerryType: BerryType; + const rand = randSeedInt(12); + if (rand < 2) { + randBerryType = BerryType.SITRUS; + } else if (rand < 4) { + randBerryType = BerryType.LUM; + } else if (rand < 6) { + randBerryType = BerryType.LEPPA; + } else { + randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; + } + return new BerryReward(randBerryType); + }), + BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { @@ -3150,11 +3224,11 @@ const modifierPool: ModifierPool = { }; const wildModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { + [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY_REWARD, 1)].map(m => { m.setTier(ModifierTier.COMMON); return m; }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { + [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 1)].map(m => { m.setTier(ModifierTier.GREAT); return m; }), @@ -3177,19 +3251,19 @@ const wildModifierPool: ModifierPool = { const trainerModifierPool: ModifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BERRY, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), + new WeightedModifierType(modifierTypes.BERRY_REWARD, 8), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 3), ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { + [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 3)].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB_REWARD, 0), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; @@ -3698,22 +3772,27 @@ export function getEnemyModifierTypesForWave( return ret; } +// TODO: Add proper documentation to this function (once it fully works...) +// TODO: Convert trainer pool to HeldItems too export function getEnemyHeldItemsForWave( waveIndex: number, count: number, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance = 0, -): PokemonHeldItemReward[] { - const ret = new Array(count).fill(0).map( - () => - // TODO: Change this to get held items (this function really could just return a list of ids honestly) - getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0) - ?.type as PokemonHeldItemReward, - ); +): HeldItems[] { + const ret = new Array(count).fill(0).map(() => { + const reward = getNewModifierTypeOption( + party, + poolType, + undefined, + upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, + )?.type as PokemonHeldItemReward; + return reward.itemId; + }); if (!(waveIndex % 1000)) { // TODO: Change this line with the actual held item when implemented - ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemReward); + ret.push(HeldItems.MINI_BLACK_HOLE); } return ret; } From 064bebb2050c135493522abbcaf3b29003aa3e50 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 10:26:35 +0200 Subject: [PATCH 035/114] Temporary stopgap on maxUpgradeCount to avoid game crashing on modifier select ui handler --- src/ui/modifier-select-ui-handler.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index e70ef4b4e55..fceb6a8d175 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -216,6 +216,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { : []; const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; + console.log("ui_shop_options", shopTypeOptions); for (let m = 0; m < typeOptions.length; m++) { const sliceWidth = globalScene.game.canvas.width / 6 / (typeOptions.length + 2); @@ -229,6 +230,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.modifierContainer.add(option); this.options.push(option); } + console.log("ui_options", this.options); // Set "Continue" button height based on number of rows in healing items shop const continueButton = this.continueButtonContainer.getAt(0); @@ -258,7 +260,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.shopOptionsRows[row].push(option); } - const maxUpgradeCount = typeOptions.map(to => to.upgradeCount).reduce((max, current) => Math.max(current, max), 0); + //TODO: temporary stopgap so the game does not crash, will have to fix this later + // console.log(typeOptions.map(to => to.upgradeCount)) + // const maxUpgradeCount = typeOptions.map(to => to.upgradeCount).reduce((max, current) => Math.max(current, max), 0); + const maxUpgradeCount = 0; /* Force updateModifiers without pokemonSpecificModifiers */ globalScene.getModifierBar().updateModifiers(globalScene.modifiers, true); @@ -288,14 +293,20 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { }, }); + console.log("maxUpgradeCount", maxUpgradeCount); + globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => { + console.log("delayed1", partyHasHeldItem); for (const shopOption of this.shopOptionsRows.flat()) { + console.log("uishop", shopOption); shopOption.show(0, 0); } }); globalScene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => { + console.log("delayed2", partyHasHeldItem); if (partyHasHeldItem) { + console.log("uihelditem", partyHasHeldItem); this.transferButtonContainer.setAlpha(0); this.transferButtonContainer.setVisible(true); globalScene.tweens.add({ @@ -350,6 +361,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { }); }); + console.log("ui_end"); + return true; } @@ -798,6 +811,7 @@ class ModifierOption extends Phaser.GameObjects.Container { } show(remainingDuration: number, upgradeCountOffset: number) { + console.log("mo1"); if (!this.modifierTypeOption.cost) { globalScene.tweens.add({ targets: this.pb, @@ -805,6 +819,7 @@ class ModifierOption extends Phaser.GameObjects.Container { duration: 1250, ease: "Bounce.Out", }); + console.log("mo2"); let lastValue = 1; let bounceCount = 0; @@ -831,6 +846,7 @@ class ModifierOption extends Phaser.GameObjects.Container { lastValue = value; }, }); + console.log("mo3"); for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) { const upgradeIndex = u; @@ -868,12 +884,14 @@ class ModifierOption extends Phaser.GameObjects.Container { }, ); } + console.log("mo4"); } globalScene.time.delayedCall(remainingDuration + 2000, () => { if (!globalScene) { return; } + console.log("mo5"); if (!this.modifierTypeOption.cost) { this.pb.setTexture("pb", `${this.getPbAtlasKey(0)}_open`); @@ -888,6 +906,7 @@ class ModifierOption extends Phaser.GameObjects.Container { onComplete: () => this.pb.destroy(), }); } + console.log("mo6"); globalScene.tweens.add({ targets: this.itemContainer, @@ -922,6 +941,7 @@ class ModifierOption extends Phaser.GameObjects.Container { }); } }); + console.log("mo_end"); } getPbAtlasKey(tierOffset = 0) { From d109bc202fd6989c24edf7e996042685670e2924 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 10:48:30 +0200 Subject: [PATCH 036/114] Changed held-items.ts to held-item-id.ts and renamed id object accoridngly --- src/enums/{held-items.ts => held-item-id.ts} | 10 ++--- src/field/pokemon-held-item-manager.ts | 14 +++---- src/items/all-held-items.ts | 10 ++--- src/items/held-item.ts | 6 +-- src/items/held-items/attack-type-booster.ts | 42 ++++++++++---------- src/items/held-items/base-stat-booster.ts | 18 ++++----- src/items/held-items/berry.ts | 26 ++++++------ src/items/held-items/exp-booster.ts | 10 ++--- src/modifier/modifier-type.ts | 28 ++++++++----- 9 files changed, 85 insertions(+), 79 deletions(-) rename src/enums/{held-items.ts => held-item-id.ts} (89%) diff --git a/src/enums/held-items.ts b/src/enums/held-item-id.ts similarity index 89% rename from src/enums/held-items.ts rename to src/enums/held-item-id.ts index 727dc4f7f0e..1a27a73d9fe 100644 --- a/src/enums/held-items.ts +++ b/src/enums/held-item-id.ts @@ -1,4 +1,4 @@ -export const HeldItems = { +export const HeldItemId = { NONE: 0x0000, // Berries @@ -84,13 +84,13 @@ export const HeldItems = { CARBOS: 0x0906, }; -export type HeldItems = (typeof HeldItems)[keyof typeof HeldItems]; +export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; -type HeldItemName = keyof typeof HeldItems; -type HeldItemValue = typeof HeldItems[HeldItemName]; +type HeldItemName = keyof typeof HeldItemId; +type HeldItemValue = typeof HeldItemId[HeldItemName]; // Use a type-safe reducer to force number keys and values -export const HeldItemNames: Record = Object.entries(HeldItems).reduce( +export const HeldItemNames: Record = Object.entries(HeldItemId).reduce( (acc, [key, value]) => { acc[value as HeldItemValue] = key as HeldItemName; return acc; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 52eabc9b50a..9c6c02a92ee 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,5 +1,5 @@ import { allHeldItems } from "#app/items/all-held-items"; -import type { HeldItems } from "#app/enums/held-items"; +import type { HeldItemId } from "#app/enums/held-item-id"; interface HeldItemProperties { stack: number; @@ -8,7 +8,7 @@ interface HeldItemProperties { } type HeldItemPropertyMap = { - [key in HeldItems]: HeldItemProperties; + [key in HeldItemId]: HeldItemProperties; }; export class PokemonItemManager { @@ -22,20 +22,20 @@ export class PokemonItemManager { return Object.keys(this.heldItems).map(k => Number(k)); } - hasItem(itemType: HeldItems): boolean { + hasItem(itemType: HeldItemId): boolean { return itemType in this.heldItems; } - getItem(itemType: HeldItems): HeldItemProperties { + getItem(itemType: HeldItemId): HeldItemProperties { // TODO: Not very safe return this.heldItems[itemType]; } - getStack(itemType: HeldItems): number { + getStack(itemType: HeldItemId): number { return itemType in this.heldItems ? this.heldItems[itemType].stack : 0; } - add(itemType: HeldItems, addStack = 1) { + add(itemType: HeldItemId, addStack = 1) { const maxStack = allHeldItems[itemType].getMaxStackCount(); if (this.hasItem(itemType)) { @@ -46,7 +46,7 @@ export class PokemonItemManager { } } - remove(itemType: HeldItems, removeStack = 1) { + remove(itemType: HeldItemId, removeStack = 1) { this.heldItems[itemType].stack -= removeStack; if (this.heldItems[itemType].stack <= 0) { diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 273cf8221b8..396e681a616 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -1,6 +1,6 @@ import { getEnumValues } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; -import { HeldItems } from "#enums/held-items"; +import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import type { PermanentStat } from "#enums/stat"; import { ITEM_EFFECT } from "./held-item"; @@ -36,11 +36,11 @@ export function initHeldItems() { allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); } - allHeldItems[HeldItems.LEFTOVERS] = new TurnEndHealHeldItem(HeldItems.LEFTOVERS, 4); - allHeldItems[HeldItems.SHELL_BELL] = new HitHealHeldItem(HeldItems.SHELL_BELL, 4); + allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); + allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); - allHeldItems[HeldItems.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItems.LUCKY_EGG, 99, 40); - allHeldItems[HeldItems.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItems.GOLDEN_EGG, 99, 100); + allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); + allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); for (const berry of getEnumValues(BerryType)) { let maxStackCount: number; diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 8d1f5137637..31d734f686f 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,7 +1,7 @@ import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import type { HeldItems } from "#enums/held-items"; +import type { HeldItemId } from "#enums/held-item-id"; export const ITEM_EFFECT = { ATTACK_TYPE_BOOST: 1, @@ -18,13 +18,13 @@ export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; export class HeldItem { // public pokemonId: number; - public type: HeldItems; + public type: HeldItemId; public maxStackCount: number; public isTransferable = true; public isStealable = true; public isSuppressable = true; - constructor(type: HeldItems, maxStackCount = 1) { + constructor(type: HeldItemId, maxStackCount = 1) { this.type = type; this.maxStackCount = maxStackCount; diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index f9048480e3f..69015a0412e 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -1,4 +1,4 @@ -import { HeldItemNames, HeldItems } from "#enums/held-items"; +import { HeldItemNames, HeldItemId } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; import type { NumberHolder } from "#app/utils/common"; @@ -15,28 +15,28 @@ export interface ATTACK_TYPE_BOOST_PARAMS { } interface AttackTypeToHeldItemMap { - [key: number]: HeldItems; + [key: number]: HeldItemId; } export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { - [PokemonType.NORMAL]: HeldItems.SILK_SCARF, - [PokemonType.FIGHTING]: HeldItems.BLACK_BELT, - [PokemonType.FLYING]: HeldItems.SHARP_BEAK, - [PokemonType.POISON]: HeldItems.POISON_BARB, - [PokemonType.GROUND]: HeldItems.SOFT_SAND, - [PokemonType.ROCK]: HeldItems.HARD_STONE, - [PokemonType.BUG]: HeldItems.SILVER_POWDER, - [PokemonType.GHOST]: HeldItems.SPELL_TAG, - [PokemonType.STEEL]: HeldItems.METAL_COAT, - [PokemonType.FIRE]: HeldItems.CHARCOAL, - [PokemonType.WATER]: HeldItems.MYSTIC_WATER, - [PokemonType.GRASS]: HeldItems.MIRACLE_SEED, - [PokemonType.ELECTRIC]: HeldItems.MAGNET, - [PokemonType.PSYCHIC]: HeldItems.TWISTED_SPOON, - [PokemonType.ICE]: HeldItems.NEVER_MELT_ICE, - [PokemonType.DRAGON]: HeldItems.DRAGON_FANG, - [PokemonType.DARK]: HeldItems.BLACK_GLASSES, - [PokemonType.FAIRY]: HeldItems.FAIRY_FEATHER, + [PokemonType.NORMAL]: HeldItemId.SILK_SCARF, + [PokemonType.FIGHTING]: HeldItemId.BLACK_BELT, + [PokemonType.FLYING]: HeldItemId.SHARP_BEAK, + [PokemonType.POISON]: HeldItemId.POISON_BARB, + [PokemonType.GROUND]: HeldItemId.SOFT_SAND, + [PokemonType.ROCK]: HeldItemId.HARD_STONE, + [PokemonType.BUG]: HeldItemId.SILVER_POWDER, + [PokemonType.GHOST]: HeldItemId.SPELL_TAG, + [PokemonType.STEEL]: HeldItemId.METAL_COAT, + [PokemonType.FIRE]: HeldItemId.CHARCOAL, + [PokemonType.WATER]: HeldItemId.MYSTIC_WATER, + [PokemonType.GRASS]: HeldItemId.MIRACLE_SEED, + [PokemonType.ELECTRIC]: HeldItemId.MAGNET, + [PokemonType.PSYCHIC]: HeldItemId.TWISTED_SPOON, + [PokemonType.ICE]: HeldItemId.NEVER_MELT_ICE, + [PokemonType.DRAGON]: HeldItemId.DRAGON_FANG, + [PokemonType.DARK]: HeldItemId.BLACK_GLASSES, + [PokemonType.FAIRY]: HeldItemId.FAIRY_FEATHER, }; export class AttackTypeBoosterHeldItem extends HeldItem { @@ -45,7 +45,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { public powerBoost: number; // This constructor may need a revision - constructor(type: HeldItems, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + constructor(type: HeldItemId, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { super(type, maxStackCount); this.moveType = moveType; this.powerBoost = powerBoost; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index d65675a35c9..f71a1b6ef5b 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItems } from "#enums/held-items"; +import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; @@ -11,16 +11,16 @@ export interface BASE_STAT_BOOSTER_PARAMS { } interface PermanentStatToHeldItemMap { - [key: number]: HeldItems; + [key: number]: HeldItemId; } export const permanentStatToHeldItem: PermanentStatToHeldItemMap = { - [Stat.HP]: HeldItems.HP_UP, - [Stat.ATK]: HeldItems.PROTEIN, - [Stat.DEF]: HeldItems.IRON, - [Stat.SPATK]: HeldItems.CALCIUM, - [Stat.SPDEF]: HeldItems.ZINC, - [Stat.SPD]: HeldItems.CARBOS, + [Stat.HP]: HeldItemId.HP_UP, + [Stat.ATK]: HeldItemId.PROTEIN, + [Stat.DEF]: HeldItemId.IRON, + [Stat.SPATK]: HeldItemId.CALCIUM, + [Stat.SPDEF]: HeldItemId.ZINC, + [Stat.SPD]: HeldItemId.CARBOS, }; export const statBoostItems: Record = { @@ -36,7 +36,7 @@ export class BaseStatBoosterHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BASE_STAT_BOOSTER]; public stat: PermanentStat; - constructor(type: HeldItems, maxStackCount = 1, stat: PermanentStat) { + constructor(type: HeldItemId, maxStackCount = 1, stat: PermanentStat) { super(type, maxStackCount); this.stat = stat; } diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 28f453d664c..eb6da1092bd 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -5,24 +5,24 @@ import { ConsumableHeldItem, ITEM_EFFECT } from "#app/items/held-item"; import { PreserveBerryModifier } from "#app/modifier/modifier"; import { BooleanHolder } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; -import { HeldItems } from "#enums/held-items"; +import { HeldItemId } from "#enums/held-item-id"; interface BerryTypeToHeldItemMap { - [key: number]: HeldItems; + [key: number]: HeldItemId; } export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { - [BerryType.SITRUS]: HeldItems.SITRUS_BERRY, - [BerryType.LUM]: HeldItems.LUM_BERRY, - [BerryType.ENIGMA]: HeldItems.ENIGMA_BERRY, - [BerryType.LIECHI]: HeldItems.LIECHI_BERRY, - [BerryType.GANLON]: HeldItems.GANLON_BERRY, - [BerryType.PETAYA]: HeldItems.PETAYA_BERRY, - [BerryType.APICOT]: HeldItems.APICOT_BERRY, - [BerryType.SALAC]: HeldItems.SALAC_BERRY, - [BerryType.LANSAT]: HeldItems.LANSAT_BERRY, - [BerryType.STARF]: HeldItems.STARF_BERRY, - [BerryType.LEPPA]: HeldItems.LEPPA_BERRY, + [BerryType.SITRUS]: HeldItemId.SITRUS_BERRY, + [BerryType.LUM]: HeldItemId.LUM_BERRY, + [BerryType.ENIGMA]: HeldItemId.ENIGMA_BERRY, + [BerryType.LIECHI]: HeldItemId.LIECHI_BERRY, + [BerryType.GANLON]: HeldItemId.GANLON_BERRY, + [BerryType.PETAYA]: HeldItemId.PETAYA_BERRY, + [BerryType.APICOT]: HeldItemId.APICOT_BERRY, + [BerryType.SALAC]: HeldItemId.SALAC_BERRY, + [BerryType.LANSAT]: HeldItemId.LANSAT_BERRY, + [BerryType.STARF]: HeldItemId.STARF_BERRY, + [BerryType.LEPPA]: HeldItemId.LEPPA_BERRY, }; export interface BERRY_PARAMS { diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index e9b5090f972..79ffc4ba8bc 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItems } from "#enums/held-items"; +import { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; @@ -15,25 +15,25 @@ export class ExpBoosterHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.EXP_BOOSTER]; private boostMultiplier: number; - constructor(type: HeldItems, maxStackCount = 1, boostPercent: number) { + constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { super(type, maxStackCount); this.boostMultiplier = boostPercent * 0.01; } get name(): string { - return this.type === HeldItems.LUCKY_EGG + return this.type === HeldItemId.LUCKY_EGG ? i18next.t("modifierType:ModifierType.LUCKY_EGG.name") : i18next.t("modifierType:ModifierType.GOLDEN_EGG.name"); } get description(): string { - return this.type === HeldItems.LUCKY_EGG + return this.type === HeldItemId.LUCKY_EGG ? i18next.t("modifierType:ModifierType.LUCKY_EGG.description") : i18next.t("modifierType:ModifierType.GOLDEN_EGG.description"); } get icon(): string { - return this.type === HeldItems.LUCKY_EGG ? "lucky_egg" : "golden_egg"; + return this.type === HeldItemId.LUCKY_EGG ? "lucky_egg" : "golden_egg"; } // TODO: What do we do with this? Need to look up all the shouldApply diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 35e2fb8a33e..2ffa9242852 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -129,7 +129,7 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; -import { HeldItems } from "#enums/held-items"; +import { HeldItemId } from "#enums/held-item-id"; import { allHeldItems } from "#app/items/all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; @@ -429,8 +429,8 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { } export class PokemonHeldItemReward extends PokemonModifierType { - public itemId: HeldItems; - constructor(itemId: HeldItems, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { + public itemId: HeldItemId; + constructor(itemId: HeldItemId, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { super( "", "", @@ -2153,7 +2153,7 @@ export const modifierTypes = { WHITE_HERB_REWARD: () => new PokemonHeldItemReward( - HeldItems.WHITE_HERB, + HeldItemId.WHITE_HERB, (type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id), ), @@ -2316,12 +2316,12 @@ export const modifierTypes = { LUCKY_EGG_REWARD: () => new PokemonHeldItemReward( - HeldItems.LUCKY_EGG, + HeldItemId.LUCKY_EGG, (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), ), GOLDEN_EGG_REWARD: () => new PokemonHeldItemReward( - HeldItems.GOLDEN_EGG, + HeldItemId.GOLDEN_EGG, (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), ), @@ -2459,10 +2459,16 @@ export const modifierTypes = { ), LEFTOVERS_REWARD: () => - new PokemonHeldItemReward(HeldItems.LEFTOVERS, (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id)), + new PokemonHeldItemReward( + HeldItemId.LEFTOVERS, + (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id), + ), SHELL_BELL_REWARD: () => - new PokemonHeldItemReward(HeldItems.SHELL_BELL, (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id)), + new PokemonHeldItemReward( + HeldItemId.SHELL_BELL, + (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id), + ), LEFTOVERS: () => new PokemonHeldItemModifierType( @@ -3778,14 +3784,14 @@ export function getEnemyModifierTypesForWave( } // TODO: Add proper documentation to this function (once it fully works...) -// TODO: Convert trainer pool to HeldItems too +// TODO: Convert trainer pool to HeldItemId too export function getEnemyHeldItemsForWave( waveIndex: number, count: number, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance = 0, -): HeldItems[] { +): HeldItemId[] { const ret = new Array(count).fill(0).map(() => { const reward = getNewModifierTypeOption( party, @@ -3797,7 +3803,7 @@ export function getEnemyHeldItemsForWave( }); if (!(waveIndex % 1000)) { // TODO: Change this line with the actual held item when implemented - ret.push(HeldItems.MINI_BLACK_HOLE); + ret.push(HeldItemId.MINI_BLACK_HOLE); } return ret; } From 298325f7261d0e92ffaf704a26c3022ef3dc83e0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:13:01 +0200 Subject: [PATCH 037/114] Added reviver seed --- src/items/all-held-items.ts | 46 +++++++------ src/items/held-item.ts | 1 + src/items/held-items/instant-revive.ts | 68 +++++++++++++++++++ .../held-items/reset-negative-stat-stage.ts | 2 +- 4 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 src/items/held-items/instant-revive.ts diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 396e681a616..6830bcbf7f1 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -17,31 +17,17 @@ import { import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-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 { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; +import { + ResetNegativeStatStageHeldItem, + 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 = {}; export function initHeldItems() { - // SILK_SCARF, BLACK_BELT, etc... - for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) { - const pokemonType = Number(typeKey) as PokemonType; - allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); - } - - // vitamins - for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { - const stat = Number(statKey) as PermanentStat; - allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); - } - - allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); - allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); - - allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); - allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); - for (const berry of getEnumValues(BerryType)) { let maxStackCount: number; if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry)) { @@ -53,6 +39,27 @@ export function initHeldItems() { allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount); } console.log(allHeldItems); + + allHeldItems[HeldItemId.REVIVER_SEED] = new InstantReviveHeldItem(HeldItemId.REVIVER_SEED, 1); + allHeldItems[HeldItemId.WHITE_HERB] = new ResetNegativeStatStageHeldItem(HeldItemId.WHITE_HERB, 2); + + // SILK_SCARF, BLACK_BELT, etc... + for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) { + const pokemonType = Number(typeKey) as PokemonType; + allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); + } + + allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); + allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); + + allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); + allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); + + // vitamins + for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { + const stat = Number(statKey) as PermanentStat; + allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); + } } type APPLY_HELD_ITEMS_PARAMS = { @@ -63,6 +70,7 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.EXP_BOOSTER]: EXP_BOOST_PARAMS; [ITEM_EFFECT.BERRY]: BERRY_PARAMS; [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; + [ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_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 31d734f686f..b634aecff15 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -12,6 +12,7 @@ export const ITEM_EFFECT = { // Should we actually distinguish different berry effects? BERRY: 6, BASE_STAT_BOOSTER: 7, + INSTANT_REVIVE: 8, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts new file mode 100644 index 00000000000..de395ecd5ff --- /dev/null +++ b/src/items/held-items/instant-revive.ts @@ -0,0 +1,68 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { ConsumableHeldItem, ITEM_EFFECT } from "../held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { toDmgValue } from "#app/utils/common"; +import { applyAbAttrs, CommanderAbAttr } from "#app/data/abilities/ability"; + +export interface INSTANT_REVIVE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +/** + * Modifier used for held items, namely White Herb, that restore adverse stat + * stages in battle. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class InstantReviveHeldItem extends ConsumableHeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.INSTANT_REVIVE]; + + get name(): string { + return i18next.t("modifierType:ModifierType.REVIVER_SEED.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.REVIVER_SEED.description"); + } + + get icon(): string { + return "reviver_seed"; + } + /** + * Goes through the holder's stat stages and, if any are negative, resets that + * stat stage back to 0. + * @param pokemon {@linkcode Pokemon} that holds the item + * @returns `true` if any stat stages were reset, false otherwise + */ + apply(params: INSTANT_REVIVE_PARAMS): boolean { + const pokemon = params.pokemon; + // Restore the Pokemon to half HP + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 2), + i18next.t("modifier:pokemonInstantReviveApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + false, + false, + true, + ), + ); + + // Remove the Pokemon's FAINT status + pokemon.resetStatus(true, false, true, false); + + // Reapply Commander on the Pokemon's side of the field, if applicable + const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); + for (const p of field) { + applyAbAttrs(CommanderAbAttr, p, null, false); + } + return true; + } +} diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 9e236a8f458..3dbbf51d404 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -30,7 +30,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { } get icon(): string { - return "shell_bell"; + return "white_herb"; } /** * Goes through the holder's stat stages and, if any are negative, resets that From 5c93a0c9b05c774313e5abdec2f4458a3b2fe87c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:19:07 +0200 Subject: [PATCH 038/114] Simplified HeldItemReward --- src/modifier/modifier-type.ts | 52 +++++++++-------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2ffa9242852..f9a94c97c47 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -147,7 +147,7 @@ export enum ModifierPoolType { DAILY_STARTER, } -type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier; +type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier | null; export class ModifierType { public id: string; @@ -430,11 +430,11 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { export class PokemonHeldItemReward extends PokemonModifierType { public itemId: HeldItemId; - constructor(itemId: HeldItemId, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { + constructor(itemId: HeldItemId, group?: string, soundName?: string) { super( "", "", - newModifierFunc, + () => null, (pokemon: PlayerPokemon) => { const hasItem = pokemon.heldItemManager.hasItem(this.itemId); const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); @@ -866,11 +866,7 @@ export class BerryReward extends PokemonHeldItemReward implements GeneratedPersi constructor(berryType: BerryType) { const itemId = berryTypeToHeldItem[berryType]; - super( - itemId, - // Next argument is useless - (type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType), - ); + super(itemId); this.berryType = berryType; this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework @@ -887,11 +883,7 @@ export class AttackTypeBoosterReward extends PokemonHeldItemReward implements Ge constructor(moveType: PokemonType, boostPercent: number) { const itemId = attackTypeToHeldItem[moveType]; - super( - itemId, - // Next argument is useless - (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), - ); + super(itemId); this.moveType = moveType; this.boostPercent = boostPercent; } @@ -1058,7 +1050,7 @@ export class BaseStatBoosterReward extends PokemonHeldItemReward implements Gene constructor(stat: PermanentStat) { const key = statBoostItems[stat]; const itemId = permanentStatToHeldItem[stat]; - super(itemId, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); + super(itemId); this.stat = stat; this.key = key; @@ -2144,6 +2136,8 @@ export const modifierTypes = { SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), + REVIVER_SEED_REWARD: () => new PokemonHeldItemReward(HeldItemId.REVIVER_SEED), + REVIVER_SEED: () => new PokemonHeldItemModifierType( "modifierType:ModifierType.REVIVER_SEED", @@ -2151,11 +2145,7 @@ export const modifierTypes = { (type, args) => new PokemonInstantReviveModifier(type, (args[0] as Pokemon).id), ), - WHITE_HERB_REWARD: () => - new PokemonHeldItemReward( - HeldItemId.WHITE_HERB, - (type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id), - ), + WHITE_HERB_REWARD: () => new PokemonHeldItemReward(HeldItemId.WHITE_HERB), // TODO: Remove the old one WHITE_HERB: () => @@ -2314,16 +2304,8 @@ export const modifierTypes = { GOLDEN_EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EXP_CHARM", "golden_exp_charm", 100), - LUCKY_EGG_REWARD: () => - new PokemonHeldItemReward( - HeldItemId.LUCKY_EGG, - (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), - ), - GOLDEN_EGG_REWARD: () => - new PokemonHeldItemReward( - HeldItemId.GOLDEN_EGG, - (type, args) => new ExpBoosterModifier(type, (args[0] as Pokemon).id), - ), + LUCKY_EGG_REWARD: () => new PokemonHeldItemReward(HeldItemId.LUCKY_EGG), + GOLDEN_EGG_REWARD: () => new PokemonHeldItemReward(HeldItemId.GOLDEN_EGG), // TODO: Remove these when refactor is done LUCKY_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.LUCKY_EGG", "lucky_egg", 40), @@ -2458,17 +2440,9 @@ export const modifierTypes = { (type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id), ), - LEFTOVERS_REWARD: () => - new PokemonHeldItemReward( - HeldItemId.LEFTOVERS, - (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id), - ), + LEFTOVERS_REWARD: () => new PokemonHeldItemReward(HeldItemId.LEFTOVERS), - SHELL_BELL_REWARD: () => - new PokemonHeldItemReward( - HeldItemId.SHELL_BELL, - (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id), - ), + SHELL_BELL_REWARD: () => new PokemonHeldItemReward(HeldItemId.SHELL_BELL), LEFTOVERS: () => new PokemonHeldItemModifierType( From 15e729e3e8159aa608717487bf915b54e8a1d3d2 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:23:46 +0200 Subject: [PATCH 039/114] Added effect of reviver seed (leveraging consumable logic) --- src/phases/faint-phase.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 7332d6b9462..e2c2055276a 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -20,7 +20,6 @@ import type { EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; import i18next from "i18next"; import { DamageAnimPhase } from "./damage-anim-phase"; @@ -33,6 +32,8 @@ import { VictoryPhase } from "./victory-phase"; import { isNullOrUndefined } from "#app/utils/common"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { ITEM_EFFECT } from "#app/items/held-item"; export class FaintPhase extends PokemonPhase { public readonly phaseName = "FaintPhase"; @@ -66,17 +67,7 @@ export class FaintPhase extends PokemonPhase { faintPokemon.resetSummonData(); if (!this.preventInstantRevive) { - const instantReviveModifier = globalScene.applyModifier( - PokemonInstantReviveModifier, - this.player, - faintPokemon, - ) as PokemonInstantReviveModifier; - - if (instantReviveModifier) { - faintPokemon.loseHeldItem(instantReviveModifier); - globalScene.updateModifiers(this.player); - return this.end(); - } + applyHeldItems(ITEM_EFFECT.INSTANT_REVIVE, { pokemon: faintPokemon }); } /** From d39744041e9c82183543b315dd301580ba95ac47 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:24:42 +0200 Subject: [PATCH 040/114] Remove InstantReviveModifier --- src/modifier/modifier.ts | 53 +--------------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 54923bba8ee..b6f45a8b63f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -46,12 +46,7 @@ import { } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; -import { - applyAbAttrs, - applyPostItemLostAbAttrs, - CommanderAbAttr, - PostItemLostAbAttr, -} from "#app/data/abilities/ability"; +import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import { globalScene } from "#app/global-scene"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -1831,52 +1826,6 @@ export class PreserveBerryModifier extends PersistentModifier { } } -export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof PokemonInstantReviveModifier; - } - - clone() { - return new PokemonInstantReviveModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode PokemonInstantReviveModifier} - * @param pokemon The {@linkcode Pokemon} that holds the item - * @returns always `true` - */ - override apply(pokemon: Pokemon): boolean { - // Restore the Pokemon to half HP - globalScene.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 2), - i18next.t("modifier:pokemonInstantReviveApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - false, - false, - true, - ), - ); - - // Remove the Pokemon's FAINT status - pokemon.resetStatus(true, false, true, false); - - // Reapply Commander on the Pokemon's side of the field, if applicable - const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); - for (const p of field) { - applyAbAttrs(CommanderAbAttr, p, null, false); - } - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - /** * Modifier used for held items, namely White Herb, that restore adverse stat * stages in battle. From 955592bdf624374f7f6d41425ceebb8aef14db55 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:53:47 +0200 Subject: [PATCH 041/114] Added Stat Boost items; generic name and description in HeldItem class --- src/items/all-held-items.ts | 40 +++++- src/items/held-item.ts | 16 ++- src/items/held-items/stat-booster.ts | 168 ++++++++++++++++++++++ src/items/held-items/turn-end-heal.ts | 12 -- src/modifier/modifier.ts | 196 -------------------------- 5 files changed, 222 insertions(+), 210 deletions(-) create mode 100644 src/items/held-items/stat-booster.ts diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 6830bcbf7f1..a016f4880cd 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -2,7 +2,8 @@ import { getEnumValues } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; -import type { PermanentStat } from "#enums/stat"; +import { SpeciesId } from "#enums/species-id"; +import { Stat, type PermanentStat } from "#enums/stat"; import { ITEM_EFFECT } from "./held-item"; import { type ATTACK_TYPE_BOOST_PARAMS, @@ -22,6 +23,11 @@ import { ResetNegativeStatStageHeldItem, type RESET_NEGATIVE_STAT_STAGE_PARAMS, } from "./held-items/reset-negative-stat-stage"; +import { + EvolutionStatBoostHeldItem, + SpeciesStatBoostHeldItem, + type STAT_BOOST_PARAMS, +} from "./held-items/stat-booster"; import type { TURN_END_HEAL_PARAMS } from "./held-items/turn-end-heal"; import { TurnEndHealHeldItem } from "./held-items/turn-end-heal"; @@ -49,6 +55,37 @@ export function initHeldItems() { allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } + allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem( + HeldItemId.EVIOLITE, + 1, + [Stat.DEF, Stat.SPDEF], + 1.5, + ); + allHeldItems[HeldItemId.LIGHT_BALL] = new SpeciesStatBoostHeldItem( + HeldItemId.LIGHT_BALL, + 1, + [Stat.ATK, Stat.SPATK], + 2, + [SpeciesId.PIKACHU], + ); + allHeldItems[HeldItemId.THICK_CLUB] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.ATK], 2, [ + SpeciesId.CUBONE, + SpeciesId.MAROWAK, + SpeciesId.ALOLA_MAROWAK, + ]); + allHeldItems[HeldItemId.METAL_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.DEF], 2, [ + SpeciesId.DITTO, + ]); + allHeldItems[HeldItemId.QUICK_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPD], 2, [ + SpeciesId.DITTO, + ]); + allHeldItems[HeldItemId.DEEP_SEA_SCALE] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPDEF], 2, [ + SpeciesId.CLAMPERL, + ]); + allHeldItems[HeldItemId.DEEP_SEA_TOOTH] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPATK], 2, [ + SpeciesId.CLAMPERL, + ]); + allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); @@ -71,6 +108,7 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.BERRY]: BERRY_PARAMS; [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; [ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; + [ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_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 b634aecff15..f8cbfcec5eb 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,7 +1,8 @@ import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItemNames, type HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; export const ITEM_EFFECT = { ATTACK_TYPE_BOOST: 1, @@ -13,6 +14,7 @@ export const ITEM_EFFECT = { BERRY: 6, BASE_STAT_BOOSTER: 7, INSTANT_REVIVE: 8, + STAT_BOOST: 9, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; @@ -34,6 +36,18 @@ export class HeldItem { this.isSuppressable = true; } + getName(): string { + return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.name`) + " (new)"; + } + + getDescription(): string { + return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.description`); + } + + getIcon(): string { + return `${HeldItemNames[this.type]?.toLowerCase()}`; + } + get name(): string { return ""; } diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts new file mode 100644 index 00000000000..831d645e807 --- /dev/null +++ b/src/items/held-items/stat-booster.ts @@ -0,0 +1,168 @@ +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import type { Stat } from "#enums/stat"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface STAT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + stat: Stat; + statValue: NumberHolder; +} + +/** + * Modifier used for held items that Applies {@linkcode Stat} boost(s) + * using a multiplier. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class StatBoostHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.STAT_BOOST]; + /** The stats that the held item boosts */ + protected stats: Stat[]; + /** The multiplier used to increase the relevant stat(s) */ + protected multiplier: number; + + constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[], multiplier: number) { + super(type, maxStackCount); + + this.stats = stats; + this.multiplier = multiplier; + } + + /** + * Checks if the incoming stat is listed in {@linkcode stats} + * @param _pokemon the {@linkcode Pokemon} that holds the item + * @param _stat the {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat could be boosted, false otherwise + */ + // override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + // return super.shouldApply(pokemon, stat, statValue) && this.stats.includes(stat); + // } + + /** + * Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed + * in {@linkcode stats}. + * @param _pokemon the {@linkcode Pokemon} that holds the item + * @param _stat the {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boost applies successfully, false otherwise + * @see shouldApply + */ + apply(params: STAT_BOOST_PARAMS): boolean { + params.statValue.value *= this.multiplier; + return true; + } + + getMaxHeldItemCount(_pokemon: Pokemon): number { + return 1; + } +} + +/** + * Modifier used for held items, specifically Eviolite, that apply + * {@linkcode Stat} boost(s) using a multiplier if the holder can evolve. + * @extends StatBoosterModifier + * @see {@linkcode apply} + */ +export class EvolutionStatBoostHeldItem extends StatBoostHeldItem { + /** + * Checks if the stat boosts can apply and if the holder is not currently + * Gigantamax'd. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param stat {@linkcode Stat} The {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boosts can be applied, false otherwise + */ + // override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + // return super.shouldApply(pokemon, stat, statValue) && !pokemon.isMax(); + // } + + /** + * Boosts the incoming stat value by a {@linkcode EvolutionStatBoosterModifier.multiplier} if the holder + * can evolve. Note that, if the holder is a fusion, they will receive + * only half of the boost if either of the fused members are fully + * evolved. However, if they are both unevolved, the full boost + * will apply. + * @param pokemon {@linkcode Pokemon} that holds the item + * @param _stat {@linkcode Stat} The {@linkcode Stat} to be boosted + * @param statValue{@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boost applies successfully, false otherwise + * @see shouldApply + */ + override apply(params: STAT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; + + if (pokemon.isFusion() && pokemon.getFusionSpeciesForm(true).speciesId in pokemonEvolutions !== isUnevolved) { + // Half boost applied if pokemon is fused and either part of fusion is fully evolved + params.statValue.value *= 1 + (this.multiplier - 1) / 2; + return true; + } + if (isUnevolved) { + // Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved + return super.apply(params); + } + + return false; + } +} + +/** + * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a + * multiplier if the holder is of a specific {@linkcode SpeciesId}. + * @extends StatBoostHeldItem + * @see {@linkcode apply} + */ +export class SpeciesStatBoostHeldItem extends StatBoostHeldItem { + /** The species that the held item's stat boost(s) apply to */ + private species: SpeciesId[]; + + constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[], multiplier: number, species: SpeciesId[]) { + super(type, maxStackCount, stats, multiplier); + this.species = species; + } + + /** + * Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode SpeciesId} + * (or its fused species) is listed in {@linkcode species}. + * @param pokemon {@linkcode Pokemon} that holds the item + * @param stat {@linkcode Stat} being checked at the time + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat could be boosted, false otherwise + */ + // override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + // return ( + // super.shouldApply(pokemon, stat, statValue) && + // (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + // (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))) + // ); + // } + + apply(params: STAT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const fitsSpecies = + this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)); + + if (fitsSpecies) { + return super.apply(params); + } + + return false; + } + + /** + * Checks if either parameter is included in the corresponding lists + * @param speciesId {@linkcode SpeciesId} being checked + * @param stat {@linkcode Stat} being checked + * @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise + */ + contains(speciesId: SpeciesId, stat: Stat): boolean { + return this.species.includes(speciesId) && this.stats.includes(stat); + } +} diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 1bad2e42e7d..772eeb0c0ca 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -14,18 +14,6 @@ export interface TURN_END_HEAL_PARAMS { 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)"; - } - - get description(): string { - return i18next.t("modifierType:ModifierType.LEFTOVERS.description"); - } - - get icon(): string { - return "leftovers"; - } - apply(params: TURN_END_HEAL_PARAMS): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b6f45a8b63f..1d2db5c5b63 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1029,202 +1029,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { } } -/** - * Modifier used for held items that Applies {@linkcode Stat} boost(s) - * using a multiplier. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class StatBoosterModifier extends PokemonHeldItemModifier { - /** The stats that the held item boosts */ - protected stats: Stat[]; - /** The multiplier used to increase the relevant stat(s) */ - protected multiplier: number; - - constructor(type: ModifierType, pokemonId: number, stats: Stat[], multiplier: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.stats = stats; - this.multiplier = multiplier; - } - - clone() { - return new StatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.stackCount); - } - - getArgs(): any[] { - return [...super.getArgs(), this.stats, this.multiplier]; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof StatBoosterModifier) { - const modifierInstance = modifier as StatBoosterModifier; - if (modifierInstance.multiplier === this.multiplier && modifierInstance.stats.length === this.stats.length) { - return modifierInstance.stats.every((e, i) => e === this.stats[i]); - } - } - - return false; - } - - /** - * Checks if the incoming stat is listed in {@linkcode stats} - * @param _pokemon the {@linkcode Pokemon} that holds the item - * @param _stat the {@linkcode Stat} to be boosted - * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat - * @returns `true` if the stat could be boosted, false otherwise - */ - override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { - return super.shouldApply(pokemon, stat, statValue) && this.stats.includes(stat); - } - - /** - * Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed - * in {@linkcode stats}. - * @param _pokemon the {@linkcode Pokemon} that holds the item - * @param _stat the {@linkcode Stat} to be boosted - * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat - * @returns `true` if the stat boost applies successfully, false otherwise - * @see shouldApply - */ - override apply(_pokemon: Pokemon, _stat: Stat, statValue: NumberHolder): boolean { - statValue.value *= this.multiplier; - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Modifier used for held items, specifically Eviolite, that apply - * {@linkcode Stat} boost(s) using a multiplier if the holder can evolve. - * @extends StatBoosterModifier - * @see {@linkcode apply} - */ -export class EvolutionStatBoosterModifier extends StatBoosterModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof EvolutionStatBoosterModifier; - } - - /** - * Checks if the stat boosts can apply and if the holder is not currently - * Gigantamax'd. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param stat {@linkcode Stat} The {@linkcode Stat} to be boosted - * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat - * @returns `true` if the stat boosts can be applied, false otherwise - */ - override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { - return super.shouldApply(pokemon, stat, statValue) && !pokemon.isMax(); - } - - /** - * Boosts the incoming stat value by a {@linkcode EvolutionStatBoosterModifier.multiplier} if the holder - * can evolve. Note that, if the holder is a fusion, they will receive - * only half of the boost if either of the fused members are fully - * evolved. However, if they are both unevolved, the full boost - * will apply. - * @param pokemon {@linkcode Pokemon} that holds the item - * @param _stat {@linkcode Stat} The {@linkcode Stat} to be boosted - * @param statValue{@linkcode NumberHolder} that holds the resulting value of the stat - * @returns `true` if the stat boost applies successfully, false otherwise - * @see shouldApply - */ - override apply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { - const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; - - if (pokemon.isFusion() && pokemon.getFusionSpeciesForm(true).speciesId in pokemonEvolutions !== isUnevolved) { - // Half boost applied if pokemon is fused and either part of fusion is fully evolved - statValue.value *= 1 + (this.multiplier - 1) / 2; - return true; - } - if (isUnevolved) { - // Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved - return super.apply(pokemon, stat, statValue); - } - - return false; - } -} - -/** - * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a - * multiplier if the holder is of a specific {@linkcode SpeciesId}. - * @extends StatBoosterModifier - * @see {@linkcode apply} - */ -export class SpeciesStatBoosterModifier extends StatBoosterModifier { - /** The species that the held item's stat boost(s) apply to */ - private species: SpeciesId[]; - - constructor( - type: ModifierType, - pokemonId: number, - stats: Stat[], - multiplier: number, - species: SpeciesId[], - stackCount?: number, - ) { - super(type, pokemonId, stats, multiplier, stackCount); - - this.species = species; - } - - clone() { - return new SpeciesStatBoosterModifier( - this.type, - this.pokemonId, - this.stats, - this.multiplier, - this.species, - this.stackCount, - ); - } - - getArgs(): any[] { - return [...super.getArgs(), this.species]; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof SpeciesStatBoosterModifier) { - const modifierInstance = modifier as SpeciesStatBoosterModifier; - if (modifierInstance.species.length === this.species.length) { - return super.matchType(modifier) && modifierInstance.species.every((e, i) => e === this.species[i]); - } - } - - return false; - } - - /** - * Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode SpeciesId} - * (or its fused species) is listed in {@linkcode species}. - * @param pokemon {@linkcode Pokemon} that holds the item - * @param stat {@linkcode Stat} being checked at the time - * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat - * @returns `true` if the stat could be boosted, false otherwise - */ - override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { - return ( - super.shouldApply(pokemon, stat, statValue) && - (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || - (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))) - ); - } - - /** - * Checks if either parameter is included in the corresponding lists - * @param speciesId {@linkcode SpeciesId} being checked - * @param stat {@linkcode Stat} being checked - * @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise - */ - contains(speciesId: SpeciesId, stat: Stat): boolean { - return this.species.includes(speciesId) && this.stats.includes(stat); - } -} - /** * Modifier used for held items that apply critical-hit stage boost(s). * @extends PokemonHeldItemModifier From c323375590c16cd7f4fc3f64eed3cffe22c3191a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:33:48 +0200 Subject: [PATCH 042/114] Added Crit Boost held items, King's Rock, Focus Band and Quick Claw --- .../encounters/bug-type-superfan-encounter.ts | 2 - src/field/pokemon.ts | 11 +- src/items/all-held-items.ts | 30 +- src/items/held-item.ts | 5 + src/items/held-items/bypass-speed-chance.ts | 62 +++ src/items/held-items/crit-booster.ts | 86 ++++ src/items/held-items/exp-booster.ts | 19 +- src/items/held-items/flinch-chance.ts | 57 +++ src/items/held-items/survive-chance.ts | 57 +++ src/items/held-items/turn-end-status.ts | 40 ++ src/modifier/modifier.ts | 434 ------------------ src/phases/move-effect-phase.ts | 4 +- src/phases/turn-end-phase.ts | 3 +- src/phases/turn-start-phase.ts | 5 +- 14 files changed, 346 insertions(+), 469 deletions(-) create mode 100644 src/items/held-items/bypass-speed-chance.ts create mode 100644 src/items/held-items/crit-booster.ts create mode 100644 src/items/held-items/flinch-chance.ts create mode 100644 src/items/held-items/survive-chance.ts create mode 100644 src/items/held-items/turn-end-status.ts diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c080122f922..32d01817ff8 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -42,8 +42,6 @@ import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/mod import { modifierTypes } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { - AttackTypeBoosterModifier, - BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, GigantamaxAccessModifier, MegaEvolutionAccessModifier, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8db0d5fc0e8..322aab4c6b5 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -92,11 +92,8 @@ import { PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, - SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, - StatBoosterModifier, - CritBoosterModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, @@ -257,6 +254,8 @@ import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; import { PokemonItemManager } from "./pokemon-held-item-manager"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { ITEM_EFFECT } from "#app/items/held-item"; export enum LearnMoveSituation { MISC, @@ -1446,7 +1445,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getCritStage(source: Pokemon, move: Move): number { const critStage = new NumberHolder(0); applyMoveAttrs(HighCritAttr, source, this, move, critStage); - globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); + applyHeldItems(ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage }); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); const critBoostTag = source.getTag(CritBoostTag); @@ -1503,7 +1502,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const statValue = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { - globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); + applyHeldItems(ITEM_EFFECT.STAT_BOOST, { pokemon: this, stat: stat, statValue: statValue }); } // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway @@ -3995,7 +3994,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); + applyHeldItems(ITEM_EFFECT.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage }); } if (surviveDamage.value) { damage = this.hp - 1; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index a016f4880cd..9c3f7c91473 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -4,6 +4,7 @@ import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { Stat, type PermanentStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import { ITEM_EFFECT } from "./held-item"; import { type ATTACK_TYPE_BOOST_PARAMS, @@ -16,7 +17,10 @@ import { permanentStatToHeldItem, } from "./held-items/base-stat-booster"; import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; +import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; +import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; +import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; import { @@ -28,8 +32,9 @@ import { SpeciesStatBoostHeldItem, type STAT_BOOST_PARAMS, } from "./held-items/stat-booster"; -import type { TURN_END_HEAL_PARAMS } from "./held-items/turn-end-heal"; -import { TurnEndHealHeldItem } from "./held-items/turn-end-heal"; +import { type SURVIVE_CHANCE_PARAMS, SurviveChanceHeldItem } from "./held-items/survive-chance"; +import { type TURN_END_HEAL_PARAMS, TurnEndHealHeldItem } from "./held-items/turn-end-heal"; +import { type TURN_END_STATUS_PARAMS, TurnEndStatusHeldItem } from "./held-items/turn-end-status"; export const allHeldItems = {}; @@ -55,6 +60,7 @@ export function initHeldItems() { allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); } + // Items that boost specific stats allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem( HeldItemId.EVIOLITE, 1, @@ -86,12 +92,27 @@ export function initHeldItems() { SpeciesId.CLAMPERL, ]); + // Items that boost the crit rate + allHeldItems[HeldItemId.SCOPE_LENS] = new CritBoostHeldItem(HeldItemId.SCOPE_LENS, 1, 1); + allHeldItems[HeldItemId.LEEK] = new SpeciesCritBoostHeldItem(HeldItemId.LEEK, 1, 2, [ + SpeciesId.FARFETCHD, + SpeciesId.GALAR_FARFETCHD, + SpeciesId.SIRFETCHD, + ]); + allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); + allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5); + allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3); + allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); + + allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); + allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); + // vitamins for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { const stat = Number(statKey) as PermanentStat; @@ -109,6 +130,11 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; [ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; [ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS; + [ITEM_EFFECT.CRIT_BOOST]: CRIT_BOOST_PARAMS; + [ITEM_EFFECT.TURN_END_STATUS]: TURN_END_STATUS_PARAMS; + [ITEM_EFFECT.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; + [ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; + [ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_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 f8cbfcec5eb..f8fd3fb592a 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -15,6 +15,11 @@ export const ITEM_EFFECT = { BASE_STAT_BOOSTER: 7, INSTANT_REVIVE: 8, STAT_BOOST: 9, + CRIT_BOOST: 10, + TURN_END_STATUS: 11, + SURVIVE_CHANCE: 12, + BYPASS_SPEED_CHANCE: 13, + FLINCH_CHANCE: 14, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts new file mode 100644 index 00000000000..388e716bc30 --- /dev/null +++ b/src/items/held-items/bypass-speed-chance.ts @@ -0,0 +1,62 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { Command } from "#app/ui/command-ui-handler"; + +export interface BYPASS_SPEED_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + doBypassSpeed: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class BypassSpeedChanceHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BYPASS_SPEED_CHANCE]; + + /** + * Checks if {@linkcode BypassSpeedChanceModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed + * @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean { + // return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed; + // } + + /** + * Applies {@linkcode BypassSpeedChanceModifier} + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed + * @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied + */ + apply(params: BYPASS_SPEED_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const doBypassSpeed = params.doBypassSpeed; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < stackCount) { + doBypassSpeed.value = true; + const isCommandFight = + globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; + + if (isCommandFight) { + globalScene.queueMessage( + i18next.t("modifier:bypassSpeedChanceApply", { + pokemonName: getPokemonNameWithAffix(pokemon), + itemName: this.name, + }), + ); + } + return true; + } + + return false; + } +} diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts new file mode 100644 index 00000000000..90502042844 --- /dev/null +++ b/src/items/held-items/crit-booster.ts @@ -0,0 +1,86 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface CRIT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + critStage: NumberHolder; +} + +/** + * Modifier used for held items that apply critical-hit stage boost(s). + * using a multiplier. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class CritBoostHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.CRIT_BOOST]; + + /** The amount of stages by which the held item increases the current critical-hit stage value */ + protected stageIncrement: number; + + constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number) { + super(type, maxStackCount); + + this.stageIncrement = stageIncrement; + } + + /** + * Increases the current critical-hit stage value by {@linkcode stageIncrement}. + * @param _pokemon {@linkcode Pokemon} N/A + * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns always `true` + */ + apply(params: CRIT_BOOST_PARAMS): boolean { + params.critStage.value += this.stageIncrement; + return true; + } +} + +/** + * Modifier used for held items that apply critical-hit stage boost(s) + * if the holder is of a specific {@linkcode SpeciesId}. + * @extends CritBoosterModifier + * @see {@linkcode shouldApply} + */ +export class SpeciesCritBoostHeldItem extends CritBoostHeldItem { + /** The species that the held item's critical-hit stage boost applies to */ + private species: SpeciesId[]; + + constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number, species: SpeciesId[]) { + super(type, maxStackCount, stageIncrement); + + this.species = species; + } + + /** + * Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed + * in {@linkcode species}. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns `true` if the critical-hit level can be incremented, false otherwise + */ + // override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean { + // return ( + // super.shouldApply(pokemon, critStage) && + // (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + // (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))) + // ); + // } + + apply(params: CRIT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const fitsSpecies = + this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)); + + if (fitsSpecies) { + return super.apply(params); + } + + return false; + } +} diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 79ffc4ba8bc..cf1ecc2d4b0 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,7 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItemId } from "#enums/held-item-id"; -import i18next from "i18next"; +import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem, ITEM_EFFECT } from "../held-item"; export interface EXP_BOOST_PARAMS { @@ -20,22 +19,6 @@ export class ExpBoosterHeldItem extends HeldItem { this.boostMultiplier = boostPercent * 0.01; } - get name(): string { - return this.type === HeldItemId.LUCKY_EGG - ? i18next.t("modifierType:ModifierType.LUCKY_EGG.name") - : i18next.t("modifierType:ModifierType.GOLDEN_EGG.name"); - } - - get description(): string { - return this.type === HeldItemId.LUCKY_EGG - ? i18next.t("modifierType:ModifierType.LUCKY_EGG.description") - : i18next.t("modifierType:ModifierType.GOLDEN_EGG.description"); - } - - get icon(): string { - return this.type === HeldItemId.LUCKY_EGG ? "lucky_egg" : "golden_egg"; - } - // TODO: What do we do with this? Need to look up all the shouldApply /** * Checks if {@linkcode PokemonExpBoosterModifier} should be applied diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts new file mode 100644 index 00000000000..7f4a64bb507 --- /dev/null +++ b/src/items/held-items/flinch-chance.ts @@ -0,0 +1,57 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; + +export interface FLINCH_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + flinched: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class FlinchChanceHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.FLINCH_CHANCE]; + private chance: number; + + constructor(type: HeldItemId, maxStackCount = 1, chance: number) { + super(type, maxStackCount); + + this.chance = chance; // 10 + } + + /** + * Checks if {@linkcode FlinchChanceModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched + * @returns `true` if {@linkcode FlinchChanceModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean { + // return super.shouldApply(pokemon, flinched) && !!flinched; + // } + + /** + * Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit. + * @param pokemon - The {@linkcode Pokemon} that holds the item + * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched + * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully + */ + apply(params: FLINCH_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const flinched = params.flinched; + const stackCount = pokemon.heldItemManager.getStack(this.type); + // The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch + // TODO: Since summonData is always defined now, we can probably remove this + if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < stackCount * this.chance) { + flinched.value = true; + return true; + } + + return false; + } +} diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts new file mode 100644 index 00000000000..245483adf4f --- /dev/null +++ b/src/items/held-items/survive-chance.ts @@ -0,0 +1,57 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; + +export interface SURVIVE_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + surviveDamage: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class SurviveChanceHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.SURVIVE_CHANCE]; + + /** + * Checks if the {@linkcode SurviveDamageModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage + * @returns `true` if the {@linkcode SurviveDamageModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean { + // return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage; + // } + + /** + * Applies {@linkcode SurviveDamageModifier} + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage + * @returns `true` if the survive damage has been applied + */ + apply(params: SURVIVE_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const surviveDamage = params.surviveDamage; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) { + surviveDamage.value = true; + + globalScene.queueMessage( + i18next.t("modifier:surviveDamageApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + ); + return true; + } + + return false; + } +} diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts new file mode 100644 index 00000000000..5352be4ca63 --- /dev/null +++ b/src/items/held-items/turn-end-status.ts @@ -0,0 +1,40 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import type { StatusEffect } from "#enums/status-effect"; +import type { HeldItemId } from "#enums/held-item-id"; + +export interface TURN_END_STATUS_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class TurnEndStatusHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_STATUS]; + /** The status effect to be applied by the held item */ + private effect: StatusEffect; + + constructor(type: HeldItemId, maxStackCount = 1, effect: StatusEffect) { + super(type, maxStackCount); + + this.effect = effect; + } + + /** + * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @returns `true` if the status effect was applied successfully + */ + apply(params: TURN_END_STATUS_PARAMS): boolean { + return params.pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name); + } + + getStatusEffect(): StatusEffect { + return this.effect; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1d2db5c5b63..c3907ef9456 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -13,7 +13,6 @@ import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import type { VoucherType } from "#app/system/voucher"; -import { Command } from "#app/ui/command-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -42,7 +41,6 @@ import { getModifierType, ModifierTypeGenerator, modifierTypes, - PokemonHeldItemModifierType, } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; @@ -1029,438 +1027,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { } } -/** - * Modifier used for held items that apply critical-hit stage boost(s). - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class CritBoosterModifier extends PokemonHeldItemModifier { - /** The amount of stages by which the held item increases the current critical-hit stage value */ - protected stageIncrement: number; - - constructor(type: ModifierType, pokemonId: number, stageIncrement: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.stageIncrement = stageIncrement; - } - - clone() { - return new CritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.stageIncrement); - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof CritBoosterModifier) { - return (modifier as CritBoosterModifier).stageIncrement === this.stageIncrement; - } - - return false; - } - - /** - * Increases the current critical-hit stage value by {@linkcode stageIncrement}. - * @param _pokemon {@linkcode Pokemon} N/A - * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level - * @returns always `true` - */ - override apply(_pokemon: Pokemon, critStage: NumberHolder): boolean { - critStage.value += this.stageIncrement; - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Modifier used for held items that apply critical-hit stage boost(s) - * if the holder is of a specific {@linkcode SpeciesId}. - * @extends CritBoosterModifier - * @see {@linkcode shouldApply} - */ -export class SpeciesCritBoosterModifier extends CritBoosterModifier { - /** The species that the held item's critical-hit stage boost applies to */ - private species: SpeciesId[]; - - constructor( - type: ModifierType, - pokemonId: number, - stageIncrement: number, - species: SpeciesId[], - stackCount?: number, - ) { - super(type, pokemonId, stageIncrement, stackCount); - - this.species = species; - } - - clone() { - return new SpeciesCritBoosterModifier( - this.type, - this.pokemonId, - this.stageIncrement, - this.species, - this.stackCount, - ); - } - - getArgs(): any[] { - return [...super.getArgs(), this.species]; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof SpeciesCritBoosterModifier; - } - - /** - * Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed - * in {@linkcode species}. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level - * @returns `true` if the critical-hit level can be incremented, false otherwise - */ - override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean { - return ( - super.shouldApply(pokemon, critStage) && - (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || - (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))) - ); - } -} - -/** - * Applies Specific Type item boosts (e.g., Magnet) - */ -export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { - public moveType: PokemonType; - private boostMultiplier: number; - - constructor(type: ModifierType, pokemonId: number, moveType: PokemonType, boostPercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.moveType = moveType; - this.boostMultiplier = boostPercent * 0.01; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof AttackTypeBoosterModifier) { - const attackTypeBoosterModifier = modifier as AttackTypeBoosterModifier; - return ( - attackTypeBoosterModifier.moveType === this.moveType && - attackTypeBoosterModifier.boostMultiplier === this.boostMultiplier - ); - } - - return false; - } - - clone() { - return new AttackTypeBoosterModifier( - this.type, - this.pokemonId, - this.moveType, - this.boostMultiplier * 100, - this.stackCount, - ); - } - - getArgs(): any[] { - return super.getArgs().concat([this.moveType, this.boostMultiplier * 100]); - } - - /** - * Checks if {@linkcode AttackTypeBoosterModifier} should be applied - * @param pokemon the {@linkcode Pokemon} that holds the held item - * @param moveType the {@linkcode PokemonType} of the move being used - * @param movePower the {@linkcode NumberHolder} that holds the power of the move - * @returns `true` if boosts should be applied to the move. - */ - override shouldApply(pokemon?: Pokemon, moveType?: PokemonType, movePower?: NumberHolder): boolean { - return ( - super.shouldApply(pokemon, moveType, movePower) && - typeof moveType === "number" && - movePower instanceof NumberHolder && - this.moveType === moveType - ); - } - - /** - * Applies {@linkcode AttackTypeBoosterModifier} - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param moveType {@linkcode PokemonType} of the move being used - * @param movePower {@linkcode NumberHolder} that holds the power of the move - * @returns `true` if boosts have been applied to the move. - */ - override apply(_pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder): boolean { - if (moveType === this.moveType && movePower.value >= 1) { - (movePower as NumberHolder).value = Math.floor( - (movePower as NumberHolder).value * (1 + this.getStackCount() * this.boostMultiplier), - ); - return true; - } - - return false; - } - - getScoreMultiplier(): number { - return 1.2; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 99; - } -} - -export class SurviveDamageModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof SurviveDamageModifier; - } - - clone() { - return new SurviveDamageModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Checks if the {@linkcode SurviveDamageModifier} should be applied - * @param pokemon the {@linkcode Pokemon} that holds the item - * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage - * @returns `true` if the {@linkcode SurviveDamageModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean { - return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage; - } - - /** - * Applies {@linkcode SurviveDamageModifier} - * @param pokemon the {@linkcode Pokemon} that holds the item - * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage - * @returns `true` if the survive damage has been applied - */ - override apply(pokemon: Pokemon, surviveDamage: BooleanHolder): boolean { - if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) { - surviveDamage.value = true; - - globalScene.queueMessage( - i18next.t("modifier:surviveDamageApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - ); - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 5; - } -} - -export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof BypassSpeedChanceModifier; - } - - clone() { - return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Checks if {@linkcode BypassSpeedChanceModifier} should be applied - * @param pokemon the {@linkcode Pokemon} that holds the item - * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed - * @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean { - return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed; - } - - /** - * Applies {@linkcode BypassSpeedChanceModifier} - * @param pokemon the {@linkcode Pokemon} that holds the item - * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed - * @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied - */ - override apply(pokemon: Pokemon, doBypassSpeed: BooleanHolder): boolean { - if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) { - doBypassSpeed.value = true; - const isCommandFight = - globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; - const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; - - if (isCommandFight && hasQuickClaw) { - globalScene.queueMessage( - i18next.t("modifier:bypassSpeedChanceApply", { - pokemonName: getPokemonNameWithAffix(pokemon), - itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"), - }), - ); - } - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -/** - * Class for Pokemon held items like King's Rock - * Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from AbilityId.SERENE_GRACE - */ -export class FlinchChanceModifier extends PokemonHeldItemModifier { - private chance: number; - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.chance = 10; - } - - matchType(modifier: Modifier) { - return modifier instanceof FlinchChanceModifier; - } - - clone() { - return new FlinchChanceModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Checks if {@linkcode FlinchChanceModifier} should be applied - * @param pokemon the {@linkcode Pokemon} that holds the item - * @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched - * @returns `true` if {@linkcode FlinchChanceModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean { - return super.shouldApply(pokemon, flinched) && !!flinched; - } - - /** - * Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit. - * @param pokemon - The {@linkcode Pokemon} that holds the item - * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched - * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully - */ - override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean { - // The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch - // TODO: Since summonData is always defined now, we can probably remove this - if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < this.getStackCount() * this.chance) { - flinched.value = true; - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class TurnHealModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof TurnHealModifier; - } - - clone() { - return new TurnHealModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode TurnHealModifier} - * @param pokemon The {@linkcode Pokemon} that holds the item - * @returns `true` if the {@linkcode Pokemon} was healed - */ - override apply(pokemon: Pokemon): boolean { - if (!pokemon.isFullHp()) { - globalScene.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, - i18next.t("modifier:turnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ), - ); - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 4; - } -} - -/** - * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a - * set {@linkcode StatusEffect} at the end of a turn. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class TurnStatusEffectModifier extends PokemonHeldItemModifier { - /** The status effect to be applied by the held item */ - private effect: StatusEffect; - - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - switch (type.id) { - case "TOXIC_ORB": - this.effect = StatusEffect.TOXIC; - break; - case "FLAME_ORB": - this.effect = StatusEffect.BURN; - break; - } - } - - /** - * Checks if {@linkcode modifier} is an instance of this class, - * intentionally ignoring potentially different {@linkcode effect}s - * to prevent held item stockpiling since the item obtained first - * would be the only item able to {@linkcode apply} successfully. - * @override - * @param modifier {@linkcode Modifier} being type tested - * @return `true` if {@linkcode modifier} is an instance of - * TurnStatusEffectModifier, false otherwise - */ - matchType(modifier: Modifier): boolean { - return modifier instanceof TurnStatusEffectModifier; - } - - clone() { - return new TurnStatusEffectModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @returns `true` if the status effect was applied successfully - */ - override apply(pokemon: Pokemon): boolean { - return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.type.name); - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } - - getStatusEffect(): StatusEffect { - return this.effect; - } -} - export class HitHealModifier extends PokemonHeldItemModifier { matchType(modifier: Modifier) { return modifier instanceof HitHealModifier; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 637953a472c..69e96383a57 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -57,8 +57,6 @@ import { DamageMoneyRewardModifier, EnemyAttackStatusEffectChanceModifier, EnemyEndureChanceModifier, - FlinchChanceModifier, - HitHealModifier, PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; @@ -456,7 +454,7 @@ export class MoveEffectPhase extends PokemonPhase { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) { const flinched = new BooleanHolder(false); - globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); + applyHeldItems(ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched }); if (flinched.value) { target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id); } diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 0ce786966f7..93c91fef34d 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -8,7 +8,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, - TurnStatusEffectModifier, TurnHeldItemTransferModifier, } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -55,7 +54,7 @@ export class TurnEndPhase extends FieldPhase { applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); } - globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); + applyHeldItems(ITEM_EFFECT.TURN_END_STATUS, { pokemon: pokemon }); globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); pokemon.tempSummonData.turnCount++; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 2d009b30bf3..76709f85e94 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -5,7 +5,6 @@ import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; -import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import { randSeedShuffle, BooleanHolder } from "#app/utils/common"; import { AttemptCapturePhase } from "./attempt-capture-phase"; @@ -23,6 +22,8 @@ import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; import { TeraPhase } from "./tera-phase"; +import { ITEM_EFFECT } from "#app/items/held-item"; +import { applyHeldItems } from "#app/items/all-held-items"; export class TurnStartPhase extends FieldPhase { public readonly phaseName = "TurnStartPhase"; @@ -80,7 +81,7 @@ export class TurnStartPhase extends FieldPhase { applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { - globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + applyHeldItems(ITEM_EFFECT.BYPASS_SPEED_CHANCE, { pokemon: p, doBypassSpeed: bypassSpeed }); } battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); From bce7472e3d38c5a672928da436c23394193dea18 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:48:35 +0200 Subject: [PATCH 043/114] Added Mystical Rock --- src/field/arena.ts | 7 +- src/items/all-held-items.ts | 3 + src/items/held-item.ts | 1 + src/items/held-items/field-effect.ts | 33 ++++++++ src/modifier/modifier.ts | 119 +-------------------------- 5 files changed, 42 insertions(+), 121 deletions(-) create mode 100644 src/items/held-items/field-effect.ts diff --git a/src/field/arena.ts b/src/field/arena.ts index 2ec98c53afa..07ac8b9cf1a 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -40,7 +40,8 @@ import { AbilityId } from "#enums/ability-id"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { WeatherType } from "#enums/weather-type"; -import { FieldEffectModifier } from "#app/modifier/modifier"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { ITEM_EFFECT } from "#app/items/held-item"; export class Arena { public biomeType: BiomeId; @@ -339,7 +340,7 @@ export class Arena { if (!isNullOrUndefined(user)) { weatherDuration.value = 5; - globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration); + applyHeldItems(ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration }); } this.weather = weather ? new Weather(weather, weatherDuration.value) : null; @@ -420,7 +421,7 @@ export class Arena { if (!isNullOrUndefined(user)) { terrainDuration.value = 5; - globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration); + applyHeldItems(ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration }); } this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 9c3f7c91473..0381f616116 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -20,6 +20,7 @@ import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-it import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; +import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; @@ -109,6 +110,7 @@ export function initHeldItems() { allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5); allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3); allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); + allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -135,6 +137,7 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; [ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; [ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; + [ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_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 f8fd3fb592a..4acdaa1886e 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -20,6 +20,7 @@ export const ITEM_EFFECT = { SURVIVE_CHANCE: 12, BYPASS_SPEED_CHANCE: 13, FLINCH_CHANCE: 14, + FIELD_EFFECT: 15, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts new file mode 100644 index 00000000000..babea90f4e7 --- /dev/null +++ b/src/items/held-items/field-effect.ts @@ -0,0 +1,33 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import type { NumberHolder } from "#app/utils/common"; + +export interface FIELD_EFFECT_PARAMS { + pokemon: Pokemon; + /** The pokemon with the item */ + fieldDuration: NumberHolder; +} + +/** + * Modifier used for held items, namely Mystical Rock, that extend the + * duration of weather and terrain effects. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class FieldEffectHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.FIELD_EFFECT]; + + /** + * Provides two more turns per stack to any weather or terrain effect caused + * by the holder. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration + * @returns `true` if the field effect extension was applied successfully + */ + apply(params: FIELD_EFFECT_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + params.fieldDuration.value += 2 * stackCount; + return true; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index c3907ef9456..7642e1b6acf 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -21,7 +21,7 @@ import type { MoveId } from "#enums/move-id"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; -import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; +import { type PermanentStat, type TempBattleStat, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import type { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; @@ -1027,44 +1027,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { } } -export class HitHealModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof HitHealModifier; - } - - clone() { - return new HitHealModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode HitHealModifier} - * @param pokemon The {@linkcode Pokemon} that holds the item - * @returns `true` if the {@linkcode Pokemon} was healed - */ - override apply(pokemon: Pokemon): boolean { - if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { - // TODO: this shouldn't be undefined AFAIK - globalScene.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, - i18next.t("modifier:hitHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ), - ); - } - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 4; - } -} - export class LevelIncrementBoosterModifier extends PersistentModifier { match(modifier: Modifier) { return modifier instanceof LevelIncrementBoosterModifier; @@ -1196,85 +1158,6 @@ export class PreserveBerryModifier extends PersistentModifier { } } -/** - * Modifier used for held items, namely White Herb, that restore adverse stat - * stages in battle. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof ResetNegativeStatStageModifier; - } - - clone() { - return new ResetNegativeStatStageModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Goes through the holder's stat stages and, if any are negative, resets that - * stat stage back to 0. - * @param pokemon {@linkcode Pokemon} that holds the item - * @returns `true` if any stat stages were reset, false otherwise - */ - override apply(pokemon: Pokemon): boolean { - let statRestored = false; - - for (const s of BATTLE_STATS) { - if (pokemon.getStatStage(s) < 0) { - pokemon.setStatStage(s, 0); - statRestored = true; - } - } - - if (statRestored) { - globalScene.queueMessage( - i18next.t("modifier:resetNegativeStatStageApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - ); - } - return statRestored; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - -/** - * Modifier used for held items, namely Mystical Rock, that extend the - * duration of weather and terrain effects. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class FieldEffectModifier extends PokemonHeldItemModifier { - /** - * Provides two more turns per stack to any weather or terrain effect caused - * by the holder. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration - * @returns `true` if the field effect extension was applied successfully - */ - override apply(_pokemon: Pokemon, fieldDuration: NumberHolder): boolean { - fieldDuration.value += 2 * this.stackCount; - return true; - } - - override matchType(modifier: Modifier): boolean { - return modifier instanceof FieldEffectModifier; - } - - override clone(): FieldEffectModifier { - return new FieldEffectModifier(this.type, this.pokemonId, this.stackCount); - } - - override getMaxHeldItemCount(_pokemon?: Pokemon): number { - return 2; - } -} - export abstract class ConsumablePokemonModifier extends ConsumableModifier { public pokemonId: number; From ff24aae54ba0563030b462bb2cfea796057f3666 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:06:04 +0200 Subject: [PATCH 044/114] Added Shell Bell, Soul Dew --- src/field/pokemon.ts | 6 +- src/items/all-held-items.ts | 6 + src/items/held-item.ts | 2 + src/items/held-items/friendship-booster.ts | 29 +++++ src/items/held-items/nature-weight-booster.ts | 32 +++++ src/modifier/modifier.ts | 112 ------------------ 6 files changed, 71 insertions(+), 116 deletions(-) create mode 100644 src/items/held-items/friendship-booster.ts create mode 100644 src/items/held-items/nature-weight-booster.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 322aab4c6b5..d684f85e7e6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -88,9 +88,7 @@ import { EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, - PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, - PokemonNatureWeightModifier, ShinyRateBoosterModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, @@ -1611,7 +1609,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { statHolder.value += 5; const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s)); - globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); + applyHeldItems(ITEM_EFFECT.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier }); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max( Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), @@ -5649,7 +5647,7 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); const amount = new NumberHolder(friendship); - globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); + applyHeldItems(ITEM_EFFECT.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount }); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? timedEventManager.getClassicFriendshipMultiplier() : 1; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 0381f616116..e06c3778cd8 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -22,8 +22,10 @@ import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } f import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; +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 { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; import { ResetNegativeStatStageHeldItem, type RESET_NEGATIVE_STAT_STAGE_PARAMS, @@ -103,6 +105,7 @@ export function initHeldItems() { allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); + allHeldItems[HeldItemId.SOOTHE_BELL] = new FriendshipBoosterHeldItem(HeldItemId.SOOTHE_BELL, 3); allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); @@ -111,6 +114,7 @@ export function initHeldItems() { allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3); allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2); + allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -138,6 +142,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; [ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; [ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; + [ITEM_EFFECT.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; + [ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_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 4acdaa1886e..f8afc9553f3 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -21,6 +21,8 @@ export const ITEM_EFFECT = { BYPASS_SPEED_CHANCE: 13, FLINCH_CHANCE: 14, FIELD_EFFECT: 15, + FRIENDSHIP_BOOSTER: 16, + NATURE_WEIGHT_BOOSTER: 17, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts new file mode 100644 index 00000000000..c9f4b5c5ced --- /dev/null +++ b/src/items/held-items/friendship-booster.ts @@ -0,0 +1,29 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface FRIENDSHIP_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + friendship: NumberHolder; +} + +export class FriendshipBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.FRIENDSHIP_BOOSTER]; + + /** + * Applies {@linkcode PokemonFriendshipBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the friendship boost to + * @param friendship {@linkcode NumberHolder} holding the friendship boost value + * @returns always `true` + */ + apply(params: FRIENDSHIP_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const friendship = params.friendship; + const stackCount = pokemon.heldItemManager.getStack(this.type); + friendship.value = Math.floor(friendship.value * (1 + 0.5 * stackCount)); + + return true; + } +} diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts new file mode 100644 index 00000000000..960433f929e --- /dev/null +++ b/src/items/held-items/nature-weight-booster.ts @@ -0,0 +1,32 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface NATURE_WEIGHT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + multiplier: NumberHolder; +} + +export class NatureWeightBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]; + + /** + * Applies {@linkcode PokemonNatureWeightModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the nature weight to + * @param multiplier {@linkcode NumberHolder} holding the nature weight + * @returns `true` if multiplier was applied + */ + apply(params: NATURE_WEIGHT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const multiplier = params.multiplier; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (multiplier.value !== 1) { + multiplier.value += 0.1 * stackCount * (multiplier.value > 1 ? 1 : -1); + return true; + } + + return false; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 7642e1b6acf..630bd0d441a 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -32,8 +32,6 @@ import { type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, - type PokemonExpBoosterModifierType, - type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, @@ -1650,59 +1648,6 @@ export class ExpBoosterModifier extends PersistentModifier { } } -export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonExpBoosterModifierType; - - private boostMultiplier: number; - - constructor(type: PokemonExpBoosterModifierType, pokemonId: number, boostPercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.boostMultiplier = boostPercent * 0.01; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof PokemonExpBoosterModifier) { - const pokemonExpModifier = modifier as PokemonExpBoosterModifier; - return pokemonExpModifier.boostMultiplier === this.boostMultiplier; - } - return false; - } - - clone(): PersistentModifier { - return new PokemonExpBoosterModifier(this.type, this.pokemonId, this.boostMultiplier * 100, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.boostMultiplier * 100); - } - - /** - * Checks if {@linkcode PokemonExpBoosterModifier} should be applied - * @param pokemon The {@linkcode Pokemon} to apply the exp boost to - * @param boost {@linkcode NumberHolder} holding the exp boost value - * @returns `true` if {@linkcode PokemonExpBoosterModifier} should be applied - */ - override shouldApply(pokemon: Pokemon, boost: NumberHolder): boolean { - return super.shouldApply(pokemon, boost) && !!boost; - } - - /** - * Applies {@linkcode PokemonExpBoosterModifier} - * @param _pokemon The {@linkcode Pokemon} to apply the exp boost to - * @param boost {@linkcode NumberHolder} holding the exp boost value - * @returns always `true` - */ - override apply(_pokemon: Pokemon, boost: NumberHolder): boolean { - boost.value = Math.floor(boost.value * (1 + this.getStackCount() * this.boostMultiplier)); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 99; - } -} - export class ExpShareModifier extends PersistentModifier { match(modifier: Modifier): boolean { return modifier instanceof ExpShareModifier; @@ -1747,63 +1692,6 @@ export class ExpBalanceModifier extends PersistentModifier { } } -export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonFriendshipBoosterModifierType; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonFriendshipBoosterModifier; - } - - clone(): PersistentModifier { - return new PokemonFriendshipBoosterModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode PokemonFriendshipBoosterModifier} - * @param _pokemon The {@linkcode Pokemon} to apply the friendship boost to - * @param friendship {@linkcode NumberHolder} holding the friendship boost value - * @returns always `true` - */ - override apply(_pokemon: Pokemon, friendship: NumberHolder): boolean { - friendship.value = Math.floor(friendship.value * (1 + 0.5 * this.getStackCount())); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class PokemonNatureWeightModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonNatureWeightModifier; - } - - clone(): PersistentModifier { - return new PokemonNatureWeightModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode PokemonNatureWeightModifier} - * @param _pokemon The {@linkcode Pokemon} to apply the nature weight to - * @param multiplier {@linkcode NumberHolder} holding the nature weight - * @returns `true` if multiplier was applied - */ - override apply(_pokemon: Pokemon, multiplier: NumberHolder): boolean { - if (multiplier.value !== 1) { - multiplier.value += 0.1 * this.getStackCount() * (multiplier.value > 1 ? 1 : -1); - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 10; - } -} - export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier { public override type: PokemonMoveAccuracyBoosterModifierType; private accuracyAmount: number; From 0b32cfb622775486b8215adf7f1c4e909d691459 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:29:14 +0200 Subject: [PATCH 045/114] Added multi lens and wide lens --- src/data/moves/move.ts | 4 +- src/field/pokemon.ts | 27 ++--- src/items/all-held-items.ts | 6 + src/items/held-item.ts | 2 + src/items/held-items/accuracy-booster.ts | 47 ++++++++ src/items/held-items/multi-hit.ts | 84 ++++++++++++++ src/modifier/modifier.ts | 136 ----------------------- src/phases/move-effect-phase.ts | 3 +- 8 files changed, 151 insertions(+), 158 deletions(-) create mode 100644 src/items/held-items/accuracy-booster.ts create mode 100644 src/items/held-items/multi-hit.ts diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7faf1778367..8bcfb7c0a2c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -68,10 +68,8 @@ import { } from "../abilities/ability"; import { allAbilities, allMoves } from "../data-lists"; import { - AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, - PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier, } from "../../modifier/modifier"; @@ -781,7 +779,7 @@ export default class Move implements Localizable { const isOhko = this.hasAttr(OneHitKOAccuracyAttr); if (!isOhko) { - globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + applyHeldItems(ITEM_EFFECT.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy }); } if (globalScene.arena.weather?.weatherType === WeatherType.FOG) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d684f85e7e6..b32a30b7884 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -96,7 +96,6 @@ import { PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, - PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; @@ -3725,14 +3724,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiLensMultiplier, - ); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiLensMultiplier, + }); fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { @@ -3776,14 +3772,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ const multiStrikeEnhancementMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiStrikeEnhancementMultiplier, - ); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiStrikeEnhancementMultiplier, + }); if (!ignoreSourceAbility) { applyPreAttackAbAttrs( AddSecondStrikeAbAttr, diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index e06c3778cd8..5d8a234213c 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -6,6 +6,7 @@ import { SpeciesId } from "#enums/species-id"; import { Stat, type PermanentStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { ITEM_EFFECT } from "./held-item"; +import { type ACCURACY_BOOST_PARAMS, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; import { type ATTACK_TYPE_BOOST_PARAMS, AttackTypeBoosterHeldItem, @@ -25,6 +26,7 @@ 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 { type MULTI_HIT_PARAMS, MultiHitHeldItem } from "./held-items/multi-hit"; import { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; import { ResetNegativeStatStageHeldItem, @@ -115,6 +117,8 @@ export function initHeldItems() { allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2); allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10); + allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5); + allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -144,6 +148,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; [ITEM_EFFECT.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; [ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; + [ITEM_EFFECT.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; + [ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_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 f8afc9553f3..5c4b39a6b67 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -23,6 +23,8 @@ export const ITEM_EFFECT = { FIELD_EFFECT: 15, FRIENDSHIP_BOOSTER: 16, NATURE_WEIGHT_BOOSTER: 17, + ACCURACY_BOOSTER: 18, + MULTI_HIT: 19, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts new file mode 100644 index 00000000000..90325a18bc8 --- /dev/null +++ b/src/items/held-items/accuracy-booster.ts @@ -0,0 +1,47 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface ACCURACY_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + moveAccuracy: NumberHolder; +} + +export class AccuracyBoosterHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.ACCURACY_BOOSTER]; + + private accuracyAmount: number; + + constructor(type: HeldItemId, maxStackCount = 1, accuracy: number) { + super(type, maxStackCount); + this.accuracyAmount = accuracy; + } + + /** + * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + * @param pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { + // return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; + // } + + /** + * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns always `true` + */ + apply(params: ACCURACY_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const moveAccuracy = params.moveAccuracy; + const stackCount = pokemon.heldItemManager.getStack(this.type); + moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * stackCount; + + return true; + } +} diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts new file mode 100644 index 00000000000..8bb48e190c4 --- /dev/null +++ b/src/items/held-items/multi-hit.ts @@ -0,0 +1,84 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; +import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; +import type { MoveId } from "#enums/move-id"; +import { allMoves } from "#app/data/data-lists"; + +export interface MULTI_HIT_PARAMS { + pokemon: Pokemon; + moveId: MoveId; + count?: NumberHolder; + damageMultiplier?: NumberHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class MultiHitHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.MULTI_HIT]; + + /** + * For each stack, converts 25 percent of attack damage into an additional strike. + * @param pokemon The {@linkcode Pokemon} using the move + * @param moveId The {@linkcode MoveId | identifier} for the move being used + * @param count {@linkcode NumberHolder} holding the move's hit count for this turn + * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move + * @returns always `true` + */ + apply(params: MULTI_HIT_PARAMS): boolean { + const pokemon = params.pokemon; + const move = allMoves[params.moveId]; + /** + * The move must meet Parental Bond's restrictions for this item + * to apply. This means + * - Only attacks are boosted + * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted + * (though Multi-Lens can still affect moves boosted by Parental Bond) + * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon + * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted + */ + if (!move.canBeMultiStrikeEnhanced(pokemon)) { + return false; + } + + if (!isNullOrUndefined(params.count)) { + return this.applyHitCountBoost(pokemon, params.count); + } + if (!isNullOrUndefined(params.damageMultiplier)) { + return this.applyDamageModifier(pokemon, params.damageMultiplier); + } + + return false; + } + + /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ + private applyHitCountBoost(pokemon: Pokemon, count: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + count.value += stackCount; + return true; + } + + /** + * If applied to the first hit of a move, sets the damage multiplier + * equal to (1 - the number of stacked Multi-Lenses). + * Additional strikes beyond that are given a 0.25x damage multiplier + */ + private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { + // Reduce first hit by 25% for each stack count + damageMultiplier.value *= 1 - 0.25 * stackCount; + return true; + } + if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== stackCount + 1) { + // Deal 25% damage for each remaining Multi Lens hit + damageMultiplier.value *= 0.25; + return true; + } + // An extra hit not caused by Multi Lens -- assume it is Parental Bond + return false; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 630bd0d441a..c51d6a99e2c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,7 +1,6 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves } from "#app/data/data-lists"; 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"; @@ -17,7 +16,6 @@ import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; -import type { MoveId } from "#enums/move-id"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; @@ -32,8 +30,6 @@ import { type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, - type PokemonMoveAccuracyBoosterModifierType, - type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, @@ -1692,138 +1688,6 @@ export class ExpBalanceModifier extends PersistentModifier { } } -export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonMoveAccuracyBoosterModifierType; - private accuracyAmount: number; - - constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.accuracyAmount = accuracy; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof PokemonMoveAccuracyBoosterModifier) { - const pokemonAccuracyBoosterModifier = modifier as PokemonMoveAccuracyBoosterModifier; - return pokemonAccuracyBoosterModifier.accuracyAmount === this.accuracyAmount; - } - return false; - } - - clone(): PersistentModifier { - return new PokemonMoveAccuracyBoosterModifier(this.type, this.pokemonId, this.accuracyAmount, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.accuracyAmount); - } - - /** - * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied - * @param pokemon The {@linkcode Pokemon} to apply the move accuracy boost to - * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost - * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { - return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; - } - - /** - * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} - * @param _pokemon The {@linkcode Pokemon} to apply the move accuracy boost to - * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost - * @returns always `true` - */ - override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { - moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount(); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class PokemonMultiHitModifier extends PokemonHeldItemModifier { - public override type: PokemonMultiHitModifierType; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonMultiHitModifier; - } - - clone(): PersistentModifier { - return new PokemonMultiHitModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * For each stack, converts 25 percent of attack damage into an additional strike. - * @param pokemon The {@linkcode Pokemon} using the move - * @param moveId The {@linkcode MoveId | identifier} for the move being used - * @param count {@linkcode NumberHolder} holding the move's hit count for this turn - * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move - * @returns always `true` - */ - override apply( - pokemon: Pokemon, - moveId: MoveId, - count: NumberHolder | null = null, - damageMultiplier: NumberHolder | null = null, - ): boolean { - const move = allMoves[moveId]; - /** - * The move must meet Parental Bond's restrictions for this item - * to apply. This means - * - Only attacks are boosted - * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted - * (though Multi-Lens can still affect moves boosted by Parental Bond) - * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon - * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted - */ - if (!move.canBeMultiStrikeEnhanced(pokemon)) { - return false; - } - - if (!isNullOrUndefined(count)) { - return this.applyHitCountBoost(count); - } - if (!isNullOrUndefined(damageMultiplier)) { - return this.applyDamageModifier(pokemon, damageMultiplier); - } - - return false; - } - - /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ - private applyHitCountBoost(count: NumberHolder): boolean { - count.value += this.getStackCount(); - return true; - } - - /** - * If applied to the first hit of a move, sets the damage multiplier - * equal to (1 - the number of stacked Multi-Lenses). - * Additional strikes beyond that are given a 0.25x damage multiplier - */ - private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { - if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { - // Reduce first hit by 25% for each stack count - damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); - return true; - } - if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { - // Deal 25% damage for each remaining Multi Lens hit - damageMultiplier.value *= 0.25; - return true; - } - // An extra hit not caused by Multi Lens -- assume it is Parental Bond - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { public override type: FormChangeItemModifierType; public formChangeItem: FormChangeItem; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 69e96383a57..2563b8d85ee 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -57,7 +57,6 @@ import { DamageMoneyRewardModifier, EnemyAttackStatusEffectChanceModifier, EnemyEndureChanceModifier, - PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils/common"; @@ -322,7 +321,7 @@ export class MoveEffectPhase extends PokemonPhase { // If Parental Bond is applicable, add another hit applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); + applyHeldItems(ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value; From bac6aa1d3dd3666743bbd0c396738e7f5f3f0dee Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:48:27 +0200 Subject: [PATCH 046/114] Added Baton and Golden Punch --- src/items/all-held-items.ts | 6 +++ src/items/held-item.ts | 2 + src/items/held-items/baton.ts | 22 +++++++++ src/items/held-items/damage-money-reward.ts | 33 ++++++++++++++ src/modifier/modifier.ts | 50 --------------------- src/phases/move-effect-phase.ts | 3 +- 6 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 src/items/held-items/baton.ts create mode 100644 src/items/held-items/damage-money-reward.ts diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 5d8a234213c..0efe7a7b0a3 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -17,9 +17,11 @@ import { BaseStatBoosterHeldItem, permanentStatToHeldItem, } from "./held-items/base-stat-booster"; +import { type BATON_PARAMS, BatonHeldItem } from "./held-items/baton"; import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; +import { type DAMAGE_MONEY_REWARD_PARAMS, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; @@ -119,6 +121,8 @@ export function initHeldItems() { allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10); allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5); 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.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -150,6 +154,8 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; [ITEM_EFFECT.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; [ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_PARAMS; + [ITEM_EFFECT.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; + [ITEM_EFFECT.BATON]: BATON_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 5c4b39a6b67..01c1210de47 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -25,6 +25,8 @@ export const ITEM_EFFECT = { NATURE_WEIGHT_BOOSTER: 17, ACCURACY_BOOSTER: 18, MULTI_HIT: 19, + DAMAGE_MONEY_REWARD: 20, + BATON: 21, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts new file mode 100644 index 00000000000..f8d5a8106ce --- /dev/null +++ b/src/items/held-items/baton.ts @@ -0,0 +1,22 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface BATON_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + expAmount: NumberHolder; +} + +export class BatonHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BATON]; + + /** + * Applies {@linkcode SwitchEffectTransferModifier} + * @returns always `true` + */ + apply(): boolean { + return true; + } +} diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts new file mode 100644 index 00000000000..46b9489f152 --- /dev/null +++ b/src/items/held-items/damage-money-reward.ts @@ -0,0 +1,33 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { MoneyMultiplierModifier } from "#app/modifier/modifier"; +import { NumberHolder } from "#app/utils/common"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface DAMAGE_MONEY_REWARD_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + damage: number; +} + +export class DamageMoneyRewardHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.DAMAGE_MONEY_REWARD]; + + /** + * Applies {@linkcode DamageMoneyRewardModifier} + * @param pokemon The {@linkcode Pokemon} attacking + * @param multiplier {@linkcode NumberHolder} holding the multiplier value + * @returns always `true` + */ + apply(params: DAMAGE_MONEY_REWARD_PARAMS): boolean { + const pokemon = params.pokemon; + const damage = params.damage; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const moneyAmount = new NumberHolder(Math.floor(damage * (0.5 * stackCount))); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.addMoney(moneyAmount.value); + + return true; + } +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index c51d6a99e2c..ef43d573a22 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1812,34 +1812,6 @@ export class MoneyMultiplierModifier extends PersistentModifier { } } -export class DamageMoneyRewardModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof DamageMoneyRewardModifier; - } - - clone(): DamageMoneyRewardModifier { - return new DamageMoneyRewardModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode DamageMoneyRewardModifier} - * @param pokemon The {@linkcode Pokemon} attacking - * @param multiplier {@linkcode NumberHolder} holding the multiplier value - * @returns always `true` - */ - override apply(_pokemon: Pokemon, multiplier: NumberHolder): boolean { - const moneyAmount = new NumberHolder(Math.floor(multiplier.value * (0.5 * this.getStackCount()))); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - globalScene.addMoney(moneyAmount.value); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 5; - } -} - export class MoneyInterestModifier extends PersistentModifier { match(modifier: Modifier): boolean { return modifier instanceof MoneyInterestModifier; @@ -2035,28 +2007,6 @@ export class BoostBugSpawnModifier extends PersistentModifier { } } -export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof SwitchEffectTransferModifier; - } - - clone(): SwitchEffectTransferModifier { - return new SwitchEffectTransferModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode SwitchEffectTransferModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - /** * Abstract class for held items that steal other Pokemon's items. * @see {@linkcode TurnHeldItemTransferModifier} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 2563b8d85ee..b5fec79ffee 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -54,7 +54,6 @@ import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ContactHeldItemTransferChanceModifier, - DamageMoneyRewardModifier, EnemyAttackStatusEffectChanceModifier, EnemyEndureChanceModifier, } from "#app/modifier/modifier"; @@ -887,7 +886,7 @@ export class MoveEffectPhase extends PokemonPhase { }); if (user.isPlayer() && !target.isPlayer()) { - globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage)); + applyHeldItems(ITEM_EFFECT.DAMAGE_MONEY_REWARD, { pokemon: user, damage: damage }); } return result; From 202428158008b8cc6c81a308cc5711de95964b54 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:34:58 +0200 Subject: [PATCH 047/114] Baton switch logic in party ui handler now using held item --- src/ui/party-ui-handler.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 6ce192751df..c8654854b13 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -6,11 +6,7 @@ import { Command } from "#app/ui/command-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; -import { - PokemonFormChangeItemModifier, - PokemonHeldItemModifier, - SwitchEffectTransferModifier, -} from "#app/modifier/modifier"; +import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; @@ -30,6 +26,7 @@ import { SpeciesId } from "#enums/species-id"; import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -1162,10 +1159,7 @@ export default class PartyUiHandler extends MessageUiHandler { private allowBatonModifierSwitch(): boolean { return !!( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && - globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, - ) + globalScene.getPlayerField()[this.fieldIndex].heldItemManager.hasItem(HeldItemId.BATON) ); } 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 048/114] 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); From 2b077151ef8cc93f8b06a0ff7654a7adbf027390 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:15:56 +0200 Subject: [PATCH 049/114] Using held items in some places --- src/phases/move-effect-phase.ts | 8 ++------ src/phases/turn-end-phase.ts | 9 +++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 5978c88ad8b..477d0779d61 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -52,11 +52,7 @@ import { type DamageResult, PokemonMove, type TurnMove } from "#app/field/pokemo import type Pokemon from "#app/field/pokemon"; import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { - ContactHeldItemTransferChanceModifier, - EnemyAttackStatusEffectChanceModifier, - EnemyEndureChanceModifier, -} from "#app/modifier/modifier"; +import { EnemyAttackStatusEffectChanceModifier, EnemyEndureChanceModifier } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils/common"; import type { nil } from "#app/utils/common"; @@ -999,7 +995,7 @@ export class MoveEffectPhase extends PokemonPhase { // Apply Grip Claw's chance to steal an item from the target if (this.move instanceof AttackMove) { - globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + applyHeldItems(ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE, { pokemon: user, target: target }); } } } diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index f5924853601..5d4ac3f5f8c 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -5,11 +5,7 @@ import { WeatherType } from "#app/enums/weather-type"; import { TurnEndEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { - EnemyTurnHealModifier, - EnemyStatusEffectHealChanceModifier, - TurnHeldItemTransferModifier, -} from "#app/modifier/modifier"; +import { EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; @@ -53,7 +49,8 @@ export class TurnEndPhase extends FieldPhase { } applyHeldItems(ITEM_EFFECT.TURN_END_STATUS, { pokemon: pokemon }); - globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + + applyHeldItems(ITEM_EFFECT.TURN_END_ITEM_STEAL, { pokemon: pokemon }); pokemon.tempSummonData.turnCount++; pokemon.tempSummonData.waveTurnCount++; From 12117bb2ac2a07ffa06020f66d9fbbfa69f95de5 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:23:42 +0200 Subject: [PATCH 050/114] Using phaseManager --- src/items/held-items/instant-revive.ts | 2 +- src/items/held-items/turn-end-heal.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index de395ecd5ff..ea8162d7c45 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -41,7 +41,7 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { apply(params: INSTANT_REVIVE_PARAMS): boolean { const pokemon = params.pokemon; // Restore the Pokemon to half HP - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 2), diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 772eeb0c0ca..ee78732a0d6 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -20,7 +20,7 @@ export class TurnEndHealHeldItem extends HeldItem { if (pokemon.isFullHp()) { return false; } - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16) * stackCount, From 76a3e612dd09570b5b9cd980ff8a673b62593a43 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 8 Jun 2025 22:09:55 +0200 Subject: [PATCH 051/114] Tracking forms in held item manager --- src/battle-scene.ts | 12 ++----- src/data/pokemon-forms.ts | 17 ++++----- src/field/pokemon-held-item-manager.ts | 38 ++++++++++++++++++++ src/modifier/modifier-type.ts | 3 +- src/ui/party-ui-handler.ts | 49 ++++++++++++-------------- 5 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 68c59ae7f09..b73a4e30357 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3167,17 +3167,11 @@ export default class BattleScene extends SceneBase { let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === SpeciesId.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. - const formChangeItemModifiers = ( - this.findModifiers( - m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id, - ) as PokemonFormChangeItemModifier[] - ) - .filter(m => m.active) - .map(m => m.formChangeItem); + const formChangeItems = pokemon.heldItemManager.getActiveFormChangeItems(); - matchingFormChange = formChangeItemModifiers.includes(FormChangeItem.N_LUNARIZER) + matchingFormChange = formChangeItems.includes(FormChangeItem.N_LUNARIZER) ? matchingFormChangeOpts[0] - : formChangeItemModifiers.includes(FormChangeItem.N_SOLARIZER) + : formChangeItems.includes(FormChangeItem.N_SOLARIZER) ? matchingFormChangeOpts[1] : null; } else { diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 47eb355c8b6..00c32207bcd 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,4 +1,3 @@ -import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import { allMoves } from "./data-lists"; @@ -135,6 +134,10 @@ export enum FormChangeItem { NORMAL_MEMORY, // TODO: Find a potential use for this } +export function formChangeItemName(id: FormChangeItem) { + return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[id]}`); +} + export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; @@ -277,13 +280,11 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { } canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier( - m => - m instanceof PokemonFormChangeItemModifier && - m.pokemonId === pokemon.id && - m.formChangeItem === this.item && - m.active === this.active, - ); + const matchItem = pokemon.heldItemManager.hasFormChangeItem(this.item); + if (!matchItem) { + return false; + } + return pokemon.heldItemManager.formChangeItems[this.item].active === this.active; } } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 9c6c02a92ee..24c8b1d67c5 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,5 +1,6 @@ import { allHeldItems } from "#app/items/all-held-items"; import type { HeldItemId } from "#app/enums/held-item-id"; +import type { FormChangeItem } from "#app/data/pokemon-forms"; interface HeldItemProperties { stack: number; @@ -11,8 +12,17 @@ type HeldItemPropertyMap = { [key in HeldItemId]: HeldItemProperties; }; +interface FormChangeItemProperties { + active: boolean; +} + +type FormChangeItemPropertyMap = { + [key in FormChangeItem]: FormChangeItemProperties; +}; + export class PokemonItemManager { public heldItems: HeldItemPropertyMap; + public formChangeItems: FormChangeItemPropertyMap; constructor() { this.heldItems = {}; @@ -53,4 +63,32 @@ export class PokemonItemManager { delete this.heldItems[itemType]; } } + + addFormChangeItem(id: FormChangeItem) { + if (!(id in this.formChangeItems)) { + this.formChangeItems[id] = { active: false }; + } + } + + hasFormChangeItem(id: FormChangeItem): boolean { + return id in this.formChangeItems; + } + + hasActiveFormChangeItem(id: FormChangeItem): boolean { + return id in this.formChangeItems && this.formChangeItems[id].active; + } + + getFormChangeItems(): FormChangeItem[] { + return Object.keys(this.formChangeItems).map(k => Number(k)); + } + + getActiveFormChangeItems(): FormChangeItem[] { + return this.getFormChangeItems().filter(m => this.formChangeItems[m].active); + } + + toggleActive(id: FormChangeItem) { + if (id in this.formChangeItems) { + this.formChangeItems[id].active = !this.formChangeItems[id].active; + } + } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f9a94c97c47..a09f633be14 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -8,6 +8,7 @@ import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { FormChangeItem, + formChangeItemName, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger, @@ -1400,7 +1401,7 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G } get name(): string { - return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`); + return formChangeItemName(this.formChangeItem); } getDescription(): string { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index c8654854b13..7bb8af0b0a4 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -6,7 +6,7 @@ import { Command } from "#app/ui/command-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; -import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; @@ -14,7 +14,7 @@ import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; -import { SpeciesFormChangeItemTrigger, FormChangeItem } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger, FormChangeItem, formChangeItemName } from "#app/data/pokemon-forms"; import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; @@ -750,9 +750,9 @@ export default class PartyUiHandler extends MessageUiHandler { globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase") && this.partyUiMode === PartyUiMode.CHECK ) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - modifier.active = !modifier.active; + const formChangeItems = this.getFormChangeItems(pokemon); + const item = formChangeItems[option - PartyOption.FORM_CHANGE_ITEM]; + pokemon.heldItemManager.toggleActive(item); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); } @@ -1332,8 +1332,8 @@ export default class PartyUiHandler extends MessageUiHandler { break; case PartyUiMode.CHECK: if (globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase")) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { + const formChangeItems = this.getFormChangeItems(pokemon); + for (let i = 0; i < formChangeItems.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); } } @@ -1393,10 +1393,10 @@ export default class PartyUiHandler extends MessageUiHandler { } break; default: - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; + const formChangeItems = this.getFormChangeItems(pokemon); + if (formChangeItems && option >= PartyOption.FORM_CHANGE_ITEM) { + const item = formChangeItems[option - PartyOption.FORM_CHANGE_ITEM]; + optionName = `${pokemon.heldItemManager.hasActiveFormChangeItem(item) ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${formChangeItemName(item)}`; } else if (option === PartyOption.UNPAUSE_EVOLUTION) { optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; } else { @@ -1555,29 +1555,24 @@ export default class PartyUiHandler extends MessageUiHandler { }); } - getFormChangeItemsModifiers(pokemon: Pokemon) { - let formChangeItemModifiers = globalScene.findModifiers( - m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id, - ) as PokemonFormChangeItemModifier[]; - const ultraNecrozmaModifiers = formChangeItemModifiers.filter( - m => m.active && m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z, - ); - if (ultraNecrozmaModifiers.length > 0) { + getFormChangeItems(pokemon: Pokemon) { + let formChangeItems = pokemon.heldItemManager.getFormChangeItems(); + const hasActiveFormChangeItems = pokemon.heldItemManager.getFormChangeItems().length; + const ultraNecrozmaActive = pokemon.heldItemManager.hasActiveFormChangeItem(FormChangeItem.ULTRANECROZIUM_Z); + if (ultraNecrozmaActive) { // ULTRANECROZIUM_Z is active and deactivating it should be the only option - return ultraNecrozmaModifiers; + return [FormChangeItem.ULTRANECROZIUM_Z]; } - if (formChangeItemModifiers.find(m => m.active)) { + if (hasActiveFormChangeItems) { // a form is currently active. the user has to disable the form or activate ULTRANECROZIUM_Z - formChangeItemModifiers = formChangeItemModifiers.filter( - m => m.active || m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z, + formChangeItems = formChangeItems.filter( + m => pokemon.heldItemManager.hasActiveFormChangeItem(m) || m === FormChangeItem.ULTRANECROZIUM_Z, ); } else if (pokemon.species.speciesId === SpeciesId.NECROZMA) { // no form is currently active. the user has to activate some form, except ULTRANECROZIUM_Z - formChangeItemModifiers = formChangeItemModifiers.filter( - m => m.formChangeItem !== FormChangeItem.ULTRANECROZIUM_Z, - ); + formChangeItems = formChangeItems.filter(m => m !== FormChangeItem.ULTRANECROZIUM_Z); } - return formChangeItemModifiers; + return formChangeItems; } getOptionsCursorWithScroll(): number { From 69b99887d9943e3495ef1faf452500353532cfe4 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:12:17 +0200 Subject: [PATCH 052/114] Shuckle Juice and Old Gateau --- src/field/pokemon-held-item-manager.ts | 3 + src/items/held-items/base-stat-booster.ts | 7 ++ src/items/held-items/base-stat-flat.ts | 64 ++++++++++++++++++ src/items/held-items/base-stat-total.ts | 81 +++++++++++++++++++++++ src/items/held-items/incrementing-stat.ts | 61 +++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 src/items/held-items/base-stat-flat.ts create mode 100644 src/items/held-items/base-stat-total.ts create mode 100644 src/items/held-items/incrementing-stat.ts diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 24c8b1d67c5..538eabef873 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,11 +1,14 @@ import { allHeldItems } from "#app/items/all-held-items"; import type { HeldItemId } from "#app/enums/held-item-id"; 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_FLAT_DATA } from "#app/items/held-items/base-stat-flat"; interface HeldItemProperties { stack: number; disabled: boolean; cooldown?: number; + data?: BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; } type HeldItemPropertyMap = { diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index f71a1b6ef5b..e024e74fd48 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -3,6 +3,7 @@ import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; +import type { STAT_BOOST_PARAMS } from "./stat-booster"; export interface BASE_STAT_BOOSTER_PARAMS { /** The pokemon with the item */ @@ -78,4 +79,10 @@ export class BaseStatBoosterHeldItem extends HeldItem { baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1)); return true; } + + getMaxStackCount(params: STAT_BOOST_PARAMS): number { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + return stackCount; + } } diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts new file mode 100644 index 00000000000..f48e597cfdf --- /dev/null +++ b/src/items/held-items/base-stat-flat.ts @@ -0,0 +1,64 @@ +import type Pokemon from "#app/field/pokemon"; +import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; +import type { Stat } from "#enums/stat"; + +export interface BASE_STAT_FLAT_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + baseStats: number[]; +} + +export interface BASE_STAT_FLAT_DATA { + statModifier: number; +} + +/** + * Currently used by Old Gateau item + */ +export class BaseStatFlatHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BASE_STAT_FLAT]; + private stats: Stat[]; + public isTransferable = false; + + constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[]) { + super(type, maxStackCount); + this.stats = stats; + } + + /** + * Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that holds the item + * @param baseStats The base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { + // return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); + // } + + /** + * Applies the {@linkcode PokemonBaseStatFlatModifier} + * @param _pokemon The {@linkcode Pokemon} that holds the item + * @param baseStats The base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + apply(params: BASE_STAT_FLAT_PARAMS): boolean { + const pokemon = params.pokemon; + const itemData = pokemon.heldItemManager.heldItems[this.type].data; + if (!itemData) { + return false; + } + const statModifier = itemData.statModifier; + const baseStats = params.baseStats; + // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats + baseStats.forEach((v, i) => { + if (this.stats.includes(i)) { + const newVal = Math.floor(v + statModifier); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); + } + }); + + return true; + } +} diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts new file mode 100644 index 00000000000..3b85a002e65 --- /dev/null +++ b/src/items/held-items/base-stat-total.ts @@ -0,0 +1,81 @@ +import type Pokemon from "#app/field/pokemon"; +import i18next from "i18next"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface BASE_STAT_TOTAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + baseStats: number[]; +} + +export interface BASE_STAT_TOTAL_DATA { + statModifier: number; +} + +/** + * Currently used by Shuckle Juice item + */ +export class BaseStatTotalHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BASE_STAT_TOTAL]; + public isTransferable = false; + + get name(): string { + return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE") + " (new)"; + } + + // TODO: where is this description shown? + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", { + increaseDecrease: i18next.t( + this.statModifier >= 0 + ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase" + : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease", + ), + blessCurse: i18next.t( + this.statModifier >= 0 + ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed" + : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed", + ), + statValue: this.statModifier, + }); + } + + get icon(): string { + return "berry_juice"; + } + + /** + * Checks if {@linkcode PokemonBaseStatTotalModifier} should be applied to the specified {@linkcode Pokemon}. + * @param pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode Pokemon} should be modified + */ + // override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { + // return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); + // } + + /** + * Applies the {@linkcode PokemonBaseStatTotalModifier} + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + apply(params: BASE_STAT_TOTAL_PARAMS): boolean { + const pokemon = params.pokemon; + const itemData = pokemon.heldItemManager.heldItems[this.type].data; + if (!itemData) { + return false; + } + const statModifier = itemData.statModifier; + const baseStats = params.baseStats; + // Modifies the passed in baseStats[] array + baseStats.forEach((v, i) => { + // HP is affected by half as much as other stats + const newVal = i === 0 ? Math.floor(v + statModifier / 2) : Math.floor(v + statModifier); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); + }); + + return true; + } +} diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts new file mode 100644 index 00000000000..fa66ba7d5a1 --- /dev/null +++ b/src/items/held-items/incrementing-stat.ts @@ -0,0 +1,61 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; +import { Stat } from "#enums/stat"; +import type { NumberHolder } from "#app/utils/common"; + +export interface INCREMENTING_STAT_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + stat: Stat; + statHolder: NumberHolder; +} + +/** + * Currently used by Macho Brace item + */ +export class IncrementingStatHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.INCREMENTING_STAT]; + public isTransferable = false; + + /** + * Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that holds the item + * @param stat The affected {@linkcode Stat} + * @param statHolder The {@linkcode NumberHolder} that holds the stat + * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied + */ + // override shouldApply(pokemon?: Pokemon, stat?: Stat, statHolder?: NumberHolder): boolean { + // return super.shouldApply(pokemon, stat, statHolder) && !!statHolder; + // } + + /** + * Applies the {@linkcode PokemonIncrementingStatModifier} + * @param _pokemon The {@linkcode Pokemon} that holds the item + * @param stat The affected {@linkcode Stat} + * @param statHolder The {@linkcode NumberHolder} that holds the stat + * @returns always `true` + */ + apply(params: INCREMENTING_STAT_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const statHolder = params.statHolder; + + // Modifies the passed in stat number holder by +2 per stack for HP, +1 per stack for other stats + // If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats + const isHp = params.stat === Stat.HP; + + if (isHp) { + statHolder.value += 2 * stackCount; + if (stackCount === this.maxStackCount) { + statHolder.value = Math.floor(statHolder.value * 1.1); + } + } else { + statHolder.value += stackCount; + if (stackCount === this.maxStackCount) { + statHolder.value = Math.floor(statHolder.value * 1.05); + } + } + + return true; + } +} From 2f794dbc6ed2f0df9dd1b2e5a0d910b3e2ae58ef Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:12:46 +0200 Subject: [PATCH 053/114] Using phaseManager --- src/items/held-items/bypass-speed-chance.ts | 2 +- src/items/held-items/reset-negative-stat-stage.ts | 2 +- src/items/held-items/survive-chance.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index 388e716bc30..40fc65593ff 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -47,7 +47,7 @@ export class BypassSpeedChanceHeldItem extends HeldItem { globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; if (isCommandFight) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: this.name, diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 3dbbf51d404..c07deee8420 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -51,7 +51,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { } if (statRestored) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.name, diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index 245483adf4f..848b766a72c 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -43,7 +43,7 @@ export class SurviveChanceHeldItem extends HeldItem { if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) { surviveDamage.value = true; - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.name, From 34864b51b829412072cfba9a5721ffb6e304b880 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:13:13 +0200 Subject: [PATCH 054/114] Removed a bunch of modifiers --- src/enums/held-item-id.ts | 5 +- src/field/pokemon.ts | 14 +- src/items/all-held-items.ts | 14 ++ src/items/held-item.ts | 7 +- src/modifier/modifier.ts | 457 +----------------------------------- 5 files changed, 38 insertions(+), 459 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 1a27a73d9fe..6669e81453a 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -75,13 +75,16 @@ export const HeldItemId = { // Mini Black Hole MINI_BLACK_HOLE: 0x0801, - // Vitamins + // Stat boosting items HP_UP: 0x0901, PROTEIN: 0x0902, IRON: 0x0903, CALCIUM: 0x0904, ZINC: 0x0905, CARBOS: 0x0906, + SHUCKLE_JUICE: 0x0907, + OLD_GATEAU: 0x0908, + MACHO_BRACE: 0x0909, }; export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index af374ecb5b7..6f28d55d66b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -88,14 +88,10 @@ import { EnemyDamageReducerModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, - BaseStatModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, - PokemonBaseStatFlatModifier, - PokemonBaseStatTotalModifier, - PokemonIncrementingStatModifier, EvoTrackerModifier, } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; @@ -1588,7 +1584,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + applyHeldItems(ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); if (this.hasAbility(AbilityId.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1610,7 +1606,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 1, ); } - globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + applyHeldItems(ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); } statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); @@ -1623,9 +1619,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); applyChallenges(ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice - globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); + applyHeldItems(ITEM_EFFECT.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats }); // Old Gateau - globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); + applyHeldItems(ITEM_EFFECT.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats }); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats); @@ -1639,7 +1635,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } // Vitamins - globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); + applyHeldItems(ITEM_EFFECT.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats }); return baseStats; } diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index d799874f67e..2b4520f5966 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -17,6 +17,8 @@ import { BaseStatBoosterHeldItem, permanentStatToHeldItem, } from "./held-items/base-stat-booster"; +import { type BASE_STAT_FLAT_PARAMS, BaseStatFlatHeldItem } from "./held-items/base-stat-flat"; +import { type BASE_STAT_TOTAL_PARAMS, BaseStatTotalHeldItem } from "./held-items/base-stat-total"; import { type BATON_PARAMS, BatonHeldItem } from "./held-items/baton"; import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; @@ -27,6 +29,7 @@ import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/fiel import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; import { type FRIENDSHIP_BOOST_PARAMS, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; +import { type INCREMENTING_STAT_PARAMS, IncrementingStatHeldItem } from "./held-items/incrementing-stat"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; import { ContactItemStealChanceHeldItem, @@ -139,6 +142,14 @@ export function initHeldItems() { const stat = Number(statKey) as PermanentStat; allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); } + + allHeldItems[HeldItemId.SHUCKLE_JUICE] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE, 1); + allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1, [ + Stat.HP, + Stat.ATK, + Stat.DEF, + ]); + allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50); } type APPLY_HELD_ITEMS_PARAMS = { @@ -165,6 +176,9 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.BATON]: BATON_PARAMS; [ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; [ITEM_EFFECT.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; + [ITEM_EFFECT.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; + [ITEM_EFFECT.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; + [ITEM_EFFECT.INCREMENTING_STAT]: INCREMENTING_STAT_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 ac6b3929a60..bf5d92653df 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -29,8 +29,11 @@ export const ITEM_EFFECT = { BATON: 21, TURN_END_ITEM_STEAL: 22, CONTACT_ITEM_STEAL_CHANCE: 23, - // EVO_TRACKER: 40, - // BASE_STAT_TOTAL: 50, + EVO_TRACKER: 40, + BASE_STAT_TOTAL: 50, + BASE_STAT_FLAT: 51, + INCREMENTING_STAT: 52, + LAPSING: 60, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index dc187f9334b..2b38e876560 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,5 +1,4 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; @@ -11,13 +10,19 @@ import Overrides from "#app/overrides"; import { LearnMoveType } from "#app/phases/learn-move-phase"; import type { VoucherType } from "#app/system/voucher"; import { addTextObject, TextStyle } from "#app/ui/text"; -import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; +import { + type BooleanHolder, + hslToHex, + isNullOrUndefined, + NumberHolder, + randSeedFloat, + toDmgValue, +} from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; -import { type PermanentStat, type TempBattleStat, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; +import { type TempBattleStat, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import type { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; @@ -27,16 +32,13 @@ import { type FormChangeItemModifierType, type ModifierOverride, type ModifierType, - type PokemonBaseStatTotalModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierTypeGenerator, modifierTypes, } from "./modifier-type"; -import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; -import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import { globalScene } from "#app/global-scene"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -643,382 +645,6 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { abstract getMaxHeldItemCount(pokemon?: Pokemon): number; } -export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier { - protected battlesLeft: number; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, battlesLeft?: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.battlesLeft = battlesLeft!; // TODO: is this bang correct? - } - - /** - * Lapse the {@linkcode battlesLeft} counter (reduce it by 1) - * @param _args arguments passed (not used here) - * @returns `true` if {@linkcode battlesLeft} is not null - */ - public lapse(..._args: unknown[]): boolean { - return !!--this.battlesLeft; - } - - /** - * Retrieve the {@linkcode Modifier | Modifiers} icon as a {@linkcode Phaser.GameObjects.Container | Container} - * @param forSummary `true` if the icon is for the summary screen - * @returns the icon as a {@linkcode Phaser.GameObjects.Container | Container} - */ - public getIcon(forSummary?: boolean): Phaser.GameObjects.Container { - const container = super.getIcon(forSummary); - - if (this.getPokemon()?.isPlayer()) { - const battleCountText = addTextObject(27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { - fontSize: "66px", - color: Color.PINK, - }); - battleCountText.setShadow(0, 0); - battleCountText.setStroke(ShadowColor.RED, 16); - battleCountText.setOrigin(1, 0); - container.add(battleCountText); - } - - return container; - } - - getBattlesLeft(): number { - return this.battlesLeft; - } - - getMaxStackCount(_forThreshold?: boolean): number { - return 1; - } -} - -/** - * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that - * increase the value of a given {@linkcode PermanentStat}. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class BaseStatModifier extends PokemonHeldItemModifier { - protected stat: PermanentStat; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, stat: PermanentStat, stackCount?: number) { - super(type, pokemonId, stackCount); - this.stat = stat; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof BaseStatModifier) { - return (modifier as BaseStatModifier).stat === this.stat; - } - return false; - } - - clone(): PersistentModifier { - return new BaseStatModifier(this.type, this.pokemonId, this.stat, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.stat); - } - - /** - * Checks if {@linkcode BaseStatModifier} should be applied to the specified {@linkcode Pokemon}. - * @param _pokemon the {@linkcode Pokemon} to be modified - * @param baseStats the base stats of the {@linkcode Pokemon} - * @returns `true` if the {@linkcode Pokemon} should be modified - */ - override shouldApply(_pokemon?: Pokemon, baseStats?: number[]): boolean { - return super.shouldApply(_pokemon, baseStats) && Array.isArray(baseStats); - } - - /** - * Applies the {@linkcode BaseStatModifier} to the specified {@linkcode Pokemon}. - * @param _pokemon the {@linkcode Pokemon} to be modified - * @param baseStats the base stats of the {@linkcode Pokemon} - * @returns always `true` - */ - override apply(_pokemon: Pokemon, baseStats: number[]): boolean { - baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + this.getStackCount() * 0.1)); - return true; - } - - getScoreMultiplier(): number { - return 1.1; - } - - getMaxHeldItemCount(pokemon: Pokemon): number { - return pokemon.ivs[this.stat]; - } -} - -export class EvoTrackerModifier extends PokemonHeldItemModifier { - protected species: SpeciesId; - protected required: number; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, species: SpeciesId, required: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.species = species; - this.required = required; - } - - matchType(modifier: Modifier): boolean { - return ( - modifier instanceof EvoTrackerModifier && modifier.species === this.species && modifier.required === this.required - ); - } - - clone(): PersistentModifier { - return new EvoTrackerModifier(this.type, this.pokemonId, this.species, this.required, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat([this.species, this.required]); - } - - /** - * Applies the {@linkcode EvoTrackerModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { - return null; - } - - const pokemon = globalScene.getPokemonById(this.pokemonId); - - this.stackCount = pokemon - ? pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length - : this.stackCount; - - const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); - text.letterSpacing = -0.5; - if (this.getStackCount() >= this.required) { - text.setTint(0xf89890); - } - text.setOrigin(0, 0); - - return text; - } - - getMaxHeldItemCount(pokemon: Pokemon): number { - this.stackCount = - pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length; - return 999; - } -} - -/** - * Currently used by Shuckle Juice item - */ -export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { - public override type: PokemonBaseStatTotalModifierType; - public isTransferable = false; - - private statModifier: number; - - constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.statModifier = statModifier; - } - - override matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonBaseStatTotalModifier && this.statModifier === modifier.statModifier; - } - - override clone(): PersistentModifier { - return new PokemonBaseStatTotalModifier(this.type, this.pokemonId, this.statModifier, this.stackCount); - } - - override getArgs(): any[] { - return super.getArgs().concat(this.statModifier); - } - - /** - * Checks if {@linkcode PokemonBaseStatTotalModifier} should be applied to the specified {@linkcode Pokemon}. - * @param pokemon the {@linkcode Pokemon} to be modified - * @param baseStats the base stats of the {@linkcode Pokemon} - * @returns `true` if the {@linkcode Pokemon} should be modified - */ - override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { - return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); - } - - /** - * Applies the {@linkcode PokemonBaseStatTotalModifier} - * @param _pokemon the {@linkcode Pokemon} to be modified - * @param baseStats the base stats of the {@linkcode Pokemon} - * @returns always `true` - */ - override apply(_pokemon: Pokemon, baseStats: number[]): boolean { - // Modifies the passed in baseStats[] array - baseStats.forEach((v, i) => { - // HP is affected by half as much as other stats - const newVal = i === 0 ? Math.floor(v + this.statModifier / 2) : Math.floor(v + this.statModifier); - baseStats[i] = Math.min(Math.max(newVal, 1), 999999); - }); - - return true; - } - - override getScoreMultiplier(): number { - return 1.2; - } - - override getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - -/** - * Currently used by Old Gateau item - */ -export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { - private statModifier: number; - private stats: Stat[]; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, statModifier: number, stats: Stat[], stackCount?: number) { - super(type, pokemonId, stackCount); - - this.statModifier = statModifier; - this.stats = stats; - } - - override matchType(modifier: Modifier): boolean { - return ( - modifier instanceof PokemonBaseStatFlatModifier && - modifier.statModifier === this.statModifier && - this.stats.every(s => modifier.stats.some(stat => s === stat)) - ); - } - - override clone(): PersistentModifier { - return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount); - } - - override getArgs(): any[] { - return [...super.getArgs(), this.statModifier, this.stats]; - } - - /** - * Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}. - * @param pokemon The {@linkcode Pokemon} that holds the item - * @param baseStats The base stats of the {@linkcode Pokemon} - * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { - return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); - } - - /** - * Applies the {@linkcode PokemonBaseStatFlatModifier} - * @param _pokemon The {@linkcode Pokemon} that holds the item - * @param baseStats The base stats of the {@linkcode Pokemon} - * @returns always `true` - */ - override apply(_pokemon: Pokemon, baseStats: number[]): boolean { - // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats - baseStats.forEach((v, i) => { - if (this.stats.includes(i)) { - const newVal = Math.floor(v + this.statModifier); - baseStats[i] = Math.min(Math.max(newVal, 1), 999999); - } - }); - - return true; - } - - override getScoreMultiplier(): number { - return 1.1; - } - - override getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Currently used by Macho Brace item - */ -export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { - public isTransferable = false; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonIncrementingStatModifier; - } - - clone(): PokemonIncrementingStatModifier { - return new PokemonIncrementingStatModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}. - * @param pokemon The {@linkcode Pokemon} that holds the item - * @param stat The affected {@linkcode Stat} - * @param statHolder The {@linkcode NumberHolder} that holds the stat - * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, stat?: Stat, statHolder?: NumberHolder): boolean { - return super.shouldApply(pokemon, stat, statHolder) && !!statHolder; - } - - /** - * Applies the {@linkcode PokemonIncrementingStatModifier} - * @param _pokemon The {@linkcode Pokemon} that holds the item - * @param stat The affected {@linkcode Stat} - * @param statHolder The {@linkcode NumberHolder} that holds the stat - * @returns always `true` - */ - override apply(_pokemon: Pokemon, stat: Stat, statHolder: NumberHolder): boolean { - // Modifies the passed in stat number holder by +2 per stack for HP, +1 per stack for other stats - // If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats - const isHp = stat === Stat.HP; - - if (isHp) { - statHolder.value += 2 * this.stackCount; - if (this.stackCount === this.getMaxHeldItemCount()) { - statHolder.value = Math.floor(statHolder.value * 1.1); - } - } else { - statHolder.value += this.stackCount; - if (this.stackCount === this.getMaxHeldItemCount()) { - statHolder.value = Math.floor(statHolder.value * 1.05); - } - } - - return true; - } - - getScoreMultiplier(): number { - return 1.2; - } - - getMaxHeldItemCount(_pokemon?: Pokemon): number { - return 50; - } -} - export class LevelIncrementBoosterModifier extends PersistentModifier { match(modifier: Modifier) { return modifier instanceof LevelIncrementBoosterModifier; @@ -1053,67 +679,6 @@ export class LevelIncrementBoosterModifier extends PersistentModifier { } } -export class BerryModifier extends PokemonHeldItemModifier { - public berryType: BerryType; - public consumed: boolean; - - constructor(type: ModifierType, pokemonId: number, berryType: BerryType, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.berryType = berryType; - this.consumed = false; - } - - matchType(modifier: Modifier) { - return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType; - } - - clone() { - return new BerryModifier(this.type, this.pokemonId, this.berryType, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.berryType); - } - - /** - * Checks if {@linkcode BerryModifier} should be applied - * @param pokemon The {@linkcode Pokemon} that holds the berry - * @returns `true` if {@linkcode BerryModifier} should be applied - */ - override shouldApply(pokemon: Pokemon): boolean { - return !this.consumed && super.shouldApply(pokemon) && getBerryPredicate(this.berryType)(pokemon); - } - - /** - * Applies {@linkcode BerryModifier} - * @param pokemon The {@linkcode Pokemon} that holds the berry - * @returns always `true` - */ - override apply(pokemon: Pokemon): boolean { - const preserve = new BooleanHolder(false); - globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); - this.consumed = !preserve.value; - - // munch the berry and trigger unburden-like effects - getBerryEffectFunc(this.berryType)(pokemon); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); - - // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. - // Don't recover it if we proc berry pouch (no item duplication) - pokemon.recordEatenBerry(this.berryType, this.consumed); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { - return 2; - } - return 3; - } -} - export class PreserveBerryModifier extends PersistentModifier { match(modifier: Modifier) { return modifier instanceof PreserveBerryModifier; @@ -1775,9 +1340,7 @@ export class MoneyRewardModifier extends ConsumableModifier { p.evoCounter ? (p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3)) : (p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3)); - const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( - p, - ) as EvoTrackerModifier; + const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(p); // as EvoTrackerModifier; globalScene.addModifier(modifier); } }); From 565d75225aa4cbbf461123f1ef92c49d0547cdfe Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:42:11 +0200 Subject: [PATCH 055/114] Fixed shell bell in ability.ts (why is it here?) --- src/data/abilities/ability.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index a79e2206348..66b2f6a39d6 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -35,7 +35,7 @@ import { } from "#app/data/moves/move"; import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { BerryModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; import { SpeciesFormChangeAbilityTrigger, @@ -90,6 +90,7 @@ import type { BattlerIndex } from "#app/battle"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; +import { HeldItemId } from "#enums/held-item-id"; export class BlockRecoilDamageAttr extends AbAttr { constructor() { @@ -7413,11 +7414,9 @@ class ForceSwitchOutHelper { * @returns The amount of health recovered by Shell Bell. */ function calculateShellBellRecovery(pokemon: Pokemon): number { - const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier); - if (shellBellModifier) { - return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount; - } - return 0; + // Returns 0 if no Shell Bell is present + const shellBellStack = pokemon.heldItemManager.getStack(HeldItemId.SHELL_BELL); + return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellStack; } /** From c41ae9936595493b6f46c0bca6b408885f904712 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:42:55 +0200 Subject: [PATCH 056/114] Changed BattleScene.removeModifier and pokemon.loseHeldItem --- src/battle-scene.ts | 15 +++++---------- src/field/pokemon-held-item-manager.ts | 4 ++-- src/field/pokemon.ts | 11 +++++------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index b73a4e30357..d2a236ba728 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -33,7 +33,6 @@ import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, - PokemonIncrementingStatModifier, RememberMoveModifier, } from "./modifier/modifier"; import { PokeballType } from "#enums/pokeball"; @@ -169,6 +168,7 @@ import { ModifierBar } from "./modifier/modifier-bar"; import { applyHeldItems } from "./items/all-held-items"; import { ITEM_EFFECT } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; +import { HeldItemId } from "#enums/held-item-id"; const DEBUG_RNG = false; @@ -3019,12 +3019,6 @@ export default class BattleScene extends SceneBase { const modifierIndex = modifiers.indexOf(modifier); if (modifierIndex > -1) { modifiers.splice(modifierIndex, 1); - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - modifier.apply(pokemon, false); - } - } return true; } @@ -3177,6 +3171,7 @@ export default class BattleScene extends SceneBase { } else { matchingFormChange = matchingFormChangeOpts[0]; } + if (matchingFormChange) { let phase: Phase; if (pokemon.isPlayer() && !matchingFormChange.quiet) { @@ -3374,9 +3369,9 @@ export default class BattleScene extends SceneBase { const participated = participantIds.has(pId); if (participated && pokemonDefeated) { partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); - const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); - if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount()) { - machoBraceModifier.stackCount++; + const hasMachoBrace = partyMember.heldItemManager.hasItem(HeldItemId.MACHO_BRACE); + if (hasMachoBrace) { + partyMember.heldItemManager.add(HeldItemId.MACHO_BRACE); this.updateModifiers(true, true); partyMember.updateInfo(); } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 538eabef873..71385cda254 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -59,10 +59,10 @@ export class PokemonItemManager { } } - remove(itemType: HeldItemId, removeStack = 1) { + remove(itemType: HeldItemId, removeStack = 1, all = false) { this.heldItems[itemType].stack -= removeStack; - if (this.heldItems[itemType].stack <= 0) { + if (all || this.heldItems[itemType].stack <= 0) { delete this.heldItems[itemType]; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6f28d55d66b..1acbd37a94a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -242,6 +242,7 @@ import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { PokemonItemManager } from "./pokemon-held-item-manager"; import { applyHeldItems } from "#app/items/all-held-items"; import { ITEM_EFFECT } from "#app/items/held-item"; +import type { HeldItemId } from "#enums/held-item-id"; export enum LearnMoveSituation { MISC, @@ -5462,15 +5463,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. * @returns `true` if the item was removed successfully, `false` otherwise. */ - public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean { - if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { + public loseHeldItem(heldItemId: HeldItemId, forBattle = true): boolean { + if (!this.heldItemManager.hasItem(heldItemId)) { return false; } - heldItem.stackCount--; - if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, this.isEnemy()); - } + this.heldItemManager.remove(heldItemId); + if (forBattle) { applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); } From c1469c08fa1d0cdc594104be072411500a4b4fdb Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:51:07 +0200 Subject: [PATCH 057/114] Making some held items unstealable and unsuppressable --- src/items/all-held-items.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 2b4520f5966..99e442250be 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -132,7 +132,9 @@ export function initHeldItems() { 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.MINI_BLACK_HOLE] = new TurnEndItemStealHeldItem(HeldItemId.MINI_BLACK_HOLE, 1) + .unstealable() + .untransferable(); allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); @@ -140,16 +142,28 @@ export function initHeldItems() { // vitamins for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { const stat = Number(statKey) as PermanentStat; - allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat); + allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat) + .unstealable() + .untransferable() + .unsuppressable(); } - allHeldItems[HeldItemId.SHUCKLE_JUICE] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE, 1); + allHeldItems[HeldItemId.SHUCKLE_JUICE] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE, 1) + .unstealable() + .untransferable() + .unsuppressable(); allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1, [ Stat.HP, Stat.ATK, Stat.DEF, - ]); - allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50); + ]) + .unstealable() + .untransferable() + .unsuppressable(); + allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50) + .unstealable() + .untransferable() + .unsuppressable(); } type APPLY_HELD_ITEMS_PARAMS = { From 51f6fd2c1ac592da592234aba29836adb22f35d6 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:15:59 +0200 Subject: [PATCH 058/114] Refactored most of battle-scene.ts with held items --- src/battle-scene.ts | 245 +++++++++------------------- src/data/abilities/ability.ts | 2 - src/data/moves/move.ts | 3 - src/field/pokemon.ts | 3 +- src/phases/attempt-capture-phase.ts | 1 - src/phases/attempt-run-phase.ts | 2 - src/phases/battle-end-phase.ts | 1 - src/ui/party-ui-handler.ts | 1 - test/testUtils/gameManager.ts | 1 - 9 files changed, 82 insertions(+), 177 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d2a236ba728..19eca6fd4cc 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -19,7 +19,7 @@ import { type Constructor, } from "#app/utils/common"; import { deepMergeSpriteData } from "#app/utils/data"; -import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import type { Modifier, ModifierPredicate } from "./modifier/modifier"; import { ConsumableModifier, ConsumablePokemonModifier, @@ -30,7 +30,6 @@ import { HealingBoosterModifier, MultipleParticipantExpBonusModifier, PersistentModifier, - PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, RememberMoveModifier, @@ -56,10 +55,8 @@ import { getLuckString, getLuckTextTint, getModifierPoolForType, - getModifierType, getPartyLuckValue, ModifierPoolType, - modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; import AbilityBar from "#app/ui/ability-bar"; @@ -102,6 +99,7 @@ import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/poke import { FormChangeItem, pokemonFormChanges, + SpeciesFormChangeItemTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, } from "#app/data/pokemon-forms"; @@ -165,7 +163,7 @@ import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; import { ModifierBar } from "./modifier/modifier-bar"; -import { applyHeldItems } from "./items/all-held-items"; +import { allHeldItems, applyHeldItems } from "./items/all-held-items"; import { ITEM_EFFECT } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; import { HeldItemId } from "#enums/held-item-id"; @@ -2620,15 +2618,8 @@ export default class BattleScene extends SceneBase { let success = false; const soundName = modifier.type.soundName; this.validateAchvs(ModifierAchv, modifier); - const modifiersToRemove: PersistentModifier[] = []; if (modifier instanceof PersistentModifier) { if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - success = modifier.apply(pokemon, true); - } - } if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } @@ -2646,12 +2637,8 @@ export default class BattleScene extends SceneBase { return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant); } - for (const rm of modifiersToRemove) { - this.removeModifier(rm); - } - if (!ignoreUpdate && !virtual) { - this.updateModifiers(true, instant); + this.updateModifiers(true); } } else if (modifier instanceof ConsumableModifier) { if (playSound && !this.sound.get(soundName)) { @@ -2695,27 +2682,38 @@ export default class BattleScene extends SceneBase { return success; } - addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { - return new Promise(resolve => { - const modifiersToRemove: PersistentModifier[] = []; - if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - modifier.apply(pokemon, true); - } - } - for (const rm of modifiersToRemove) { - this.removeModifier(rm, true); - } - } - if (!ignoreUpdate) { - this.updateModifiers(false, instant); - resolve(); - } else { - resolve(); - } - }); + addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean) { + (modifier as PersistentModifier).add(this.enemyModifiers, false); + if (!ignoreUpdate) { + this.updateModifiers(false); + } + } + + addHeldItem(heldItemId: HeldItemId, pokemon: Pokemon, amount = 1, playSound?: boolean, ignoreUpdate?: boolean) { + pokemon.heldItemManager.add(heldItemId, amount); + if (!ignoreUpdate) { + this.updateModifiers(pokemon.isPlayer()); + } + const soundName = modifier.type.soundName; + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + } + + addFormChangeItem(itemId: FormChangeItem, pokemon: Pokemon, ignoreUpdate?: boolean) { + if (pokemon.heldItemManager.hasFormChangeItem(itemId)) { + return; + } + + pokemon.heldItemManager.addFormChangeItem(itemId); + + this.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); + + pokemon.heldItemManager.formChangeItems[itemId].active = true; + + if (!ignoreUpdate) { + this.updateModifiers(false); + } } /** @@ -2723,7 +2721,7 @@ export default class BattleScene extends SceneBase { * 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. * A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful. - * @param itemModifier {@linkcode PokemonHeldItemModifier} item to transfer (represents the whole stack) + * @param heldItemId {@linkcode HeldItemId} item to transfer * @param target {@linkcode Pokemon} recepient in this transfer * @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` @@ -2732,16 +2730,15 @@ export default class BattleScene extends SceneBase { * @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`. * @returns `true` if the transfer was successful */ - tryTransferHeldItemModifier( - itemModifier: PokemonHeldItemModifier, + tryTransferHeldItem( + heldItemId: HeldItemId, + source: Pokemon, target: Pokemon, playSound: boolean, transferQuantity = 1, - instant?: boolean, ignoreUpdate?: boolean, itemLost = true, ): boolean { - const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { @@ -2752,65 +2749,35 @@ export default class BattleScene extends SceneBase { return false; } - const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; - newItemModifier.pokemonId = target.id; - const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier; + const itemStack = source.heldItemManager.getStack(heldItemId); + const matchingItemStack = target.heldItemManager.getStack(heldItemId); - if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(); - if (matchingModifier.stackCount >= maxStackCount) { - return false; - } - const countTaken = Math.min( - transferQuantity, - itemModifier.stackCount, - maxStackCount - matchingModifier.stackCount, - ); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = matchingModifier.stackCount + countTaken; - } else { - const countTaken = Math.min(transferQuantity, itemModifier.stackCount); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = countTaken; + const maxStackCount = allHeldItems[heldItemId].getMaxStackCount(); + if (matchingItemStack >= maxStackCount) { + return false; + } + const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); + + source.heldItemManager.remove(heldItemId, countTaken); + target.heldItemManager.add(heldItemId, countTaken); + + if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); } - const removeOld = itemModifier.stackCount === 0; - - if (!removeOld || !source || this.removeModifier(itemModifier, source.isEnemy())) { - const addModifier = () => { - if (!matchingModifier || this.removeModifier(matchingModifier, target.isEnemy())) { - if (target.isPlayer()) { - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); - if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); - } - return true; - } - this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); - if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); - } - return true; - } - return false; - }; - if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { - this.updateModifiers(source.isPlayer(), instant); - addModifier(); - } else { - addModifier(); - } - return true; + if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { + this.updateModifiers(source.isPlayer()); } - return false; + + const soundName = modifier.type.soundName; + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + + return true; } - canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { - const mod = itemModifier.clone() as PokemonHeldItemModifier; - const source = mod.pokemonId ? mod.getPokemon() : null; + canTransferHeldItem(heldItemId: HeldItemId, source: Pokemon, target: Pokemon, transferQuantity = 1): boolean { const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { @@ -2821,40 +2788,16 @@ export default class BattleScene extends SceneBase { return false; } - const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier; + const itemStack = source.heldItemManager.getStack(heldItemId); + const matchingItemStack = target.heldItemManager.getStack(heldItemId); - if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(); - if (matchingModifier.stackCount >= maxStackCount) { - return false; - } - const countTaken = Math.min(transferQuantity, mod.stackCount, maxStackCount - matchingModifier.stackCount); - mod.stackCount -= countTaken; - } else { - const countTaken = Math.min(transferQuantity, mod.stackCount); - mod.stackCount -= countTaken; + const maxStackCount = allHeldItems[heldItemId].getMaxStackCount(); + if (matchingItemStack >= maxStackCount) { + return false; } + const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); - const removeOld = mod.stackCount === 0; - - return !removeOld || !source || this.hasModifier(itemModifier, !source.isPlayer()); - } - - removePartyMemberModifiers(partyMemberIndex: number): Promise { - return new Promise(resolve => { - const pokemonId = this.getPlayerParty()[partyMemberIndex].id; - const modifiersToRemove = this.modifiers.filter( - m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, - ); - for (const m of modifiersToRemove) { - this.modifiers.splice(this.modifiers.indexOf(m), 1); - } - this.updateModifiers(); - resolve(); - }); + return countTaken > 0; } generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise { @@ -2874,7 +2817,7 @@ export default class BattleScene extends SceneBase { if (this.currentBattle.trainer) { const modifiers = this.currentBattle.trainer.genModifiers(party); for (const modifier of modifiers) { - this.addEnemyModifier(modifier, true, true); + this.addEnemyModifier(modifier, true); } } @@ -2939,43 +2882,21 @@ export default class BattleScene extends SceneBase { this.updateUIPositions(); } - /** - * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type - * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} - */ - clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { - const modifiersToRemove = this.enemyModifiers.filter( - m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon() === pokemon), - ); - for (const m of modifiersToRemove) { - this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); - } - this.updateModifiers(false); - this.updateUIPositions(); - } - setModifiersVisible(visible: boolean) { [this.modifierBar, this.enemyModifierBar].map(m => m.setVisible(visible)); } // TODO: Document this - updateModifiers(player = true, instant?: boolean): void { + updateModifiers(player = true): void { const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); - for (let m = 0; m < modifiers.length; m++) { - const modifier = modifiers[m]; - if ( - modifier instanceof PokemonHeldItemModifier && - !this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId) - ) { - modifiers.splice(m--, 1); - } - } + for (const modifier of modifiers) { if (modifier instanceof PersistentModifier) { (modifier as PersistentModifier).virtualStackCount = 0; } } + // Removing modifiers with a stack count of 0, if they somehow got to this point const modifiersClone = modifiers.slice(0); for (const modifier of modifiersClone) { if (!modifier.getStackCount()) { @@ -2983,14 +2904,14 @@ export default class BattleScene extends SceneBase { } } - this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant); + this.updateParty(player ? this.getPlayerParty() : this.getEnemyParty(), true); (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); if (!player) { this.updateUIPositions(); } } - updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { + updateParty(party: Pokemon[], instant?: boolean): Promise { return new Promise(resolve => { Promise.allSettled( party.map(p => { @@ -3161,11 +3082,11 @@ export default class BattleScene extends SceneBase { let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === SpeciesId.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. - const formChangeItems = pokemon.heldItemManager.getActiveFormChangeItems(); + const activeFormChangeItems = pokemon.heldItemManager.getActiveFormChangeItems(); - matchingFormChange = formChangeItems.includes(FormChangeItem.N_LUNARIZER) + matchingFormChange = activeFormChangeItems.includes(FormChangeItem.N_LUNARIZER) ? matchingFormChangeOpts[0] - : formChangeItems.includes(FormChangeItem.N_SOLARIZER) + : activeFormChangeItems.includes(FormChangeItem.N_SOLARIZER) ? matchingFormChangeOpts[1] : null; } else { @@ -3301,11 +3222,7 @@ export default class BattleScene extends SceneBase { pokemon.species.name, undefined, () => { - const finalBossMBH = getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier( - pokemon, - ) as TurnHeldItemTransferModifier; - finalBossMBH.setTransferrableFalse(); - this.addEnemyModifier(finalBossMBH, false, true); + pokemon.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE); pokemon.generateAndPopulateMoveset(1); this.setFieldScale(0.75); this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); @@ -3372,7 +3289,7 @@ export default class BattleScene extends SceneBase { const hasMachoBrace = partyMember.heldItemManager.hasItem(HeldItemId.MACHO_BRACE); if (hasMachoBrace) { partyMember.heldItemManager.add(HeldItemId.MACHO_BRACE); - this.updateModifiers(true, true); + this.updateModifiers(true); partyMember.updateInfo(); } } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 66b2f6a39d6..b33b38a0f78 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -7329,8 +7329,6 @@ class ForceSwitchOutHelper { } if (!allyPokemon?.isActive(true)) { - globalScene.clearEnemyHeldItemModifiers(); - if (switchOutTarget.hp) { globalScene.phaseManager.pushNew("BattleEndPhase", false); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index e1cf68dad55..f670da69fd0 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -6347,9 +6347,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } } - // clear out enemy held item modifiers of the switch out target - globalScene.clearEnemyHeldItemModifiers(switchOutTarget); - if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { globalScene.phaseManager.pushNew("BattleEndPhase", false); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 1acbd37a94a..a29b44bd03f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5991,8 +5991,7 @@ export class PlayerPokemon extends Pokemon { for (const modifier of fusedPartyMemberHeldModifiers) { globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); } - globalScene.updateModifiers(true, true); - globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); + globalScene.updateModifiers(true); globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); pokemon diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 4f3f54a7e5b..82c0cdec922 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -265,7 +265,6 @@ export class AttemptCapturePhase extends PokemonPhase { globalScene.addFaintedEnemyScore(pokemon); pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); - globalScene.clearEnemyHeldItemModifiers(); pokemon.leaveField(true, true, true); }; const addToParty = (slotIndex?: number) => { diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 525be8c21ab..0579b3b30c4 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -48,8 +48,6 @@ export class AttemptRunPhase extends PokemonPhase { enemyField.forEach(enemyPokemon => enemyPokemon.destroy()), }); - globalScene.clearEnemyHeldItemModifiers(); - // biome-ignore lint/complexity/noForEach: TODO enemyField.forEach(enemyPokemon => { enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index e169de58cb3..0a4928af44c 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -72,7 +72,6 @@ export class BattleEndPhase extends BattlePhase { globalScene.currentBattle.pickUpScatteredMoney(); } - globalScene.clearEnemyHeldItemModifiers(); for (const p of globalScene.getEnemyParty()) { try { p.destroy(); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 7bb8af0b0a4..d2681d919cc 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1494,7 +1494,6 @@ export default class PartyUiHandler extends MessageUiHandler { null, () => { this.clearPartySlots(); - globalScene.removePartyMemberModifiers(slotIndex); const releasedPokemon = globalScene.getPlayerParty().splice(slotIndex, 1)[0]; releasedPokemon.destroy(); this.populatePartySlots(); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 437c8d9f083..65b20f77a36 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -536,7 +536,6 @@ export default class GameManager { * Removes all held items from enemy pokemon. */ removeEnemyHeldItems(): void { - this.scene.clearEnemyHeldItemModifiers(); this.scene.clearEnemyModifiers(); console.log("Enemy held items removed"); } From c607a73ebce1abb22af959096fbc008a34cb2941 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:39:25 +0200 Subject: [PATCH 059/114] Added soundName to HeldItem (possibly useless) --- src/battle-scene.ts | 4 ++-- src/items/held-item.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 19eca6fd4cc..bf6bd4833dc 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2694,7 +2694,7 @@ export default class BattleScene extends SceneBase { if (!ignoreUpdate) { this.updateModifiers(pokemon.isPlayer()); } - const soundName = modifier.type.soundName; + const soundName = allHeldItems[heldItemId].soundName; if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } @@ -2769,7 +2769,7 @@ export default class BattleScene extends SceneBase { this.updateModifiers(source.isPlayer()); } - const soundName = modifier.type.soundName; + const soundName = allHeldItems[heldItemId].soundName; if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } diff --git a/src/items/held-item.ts b/src/items/held-item.ts index bf5d92653df..96ff712a0c6 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -46,6 +46,9 @@ export class HeldItem { public isStealable = true; public isSuppressable = true; + //TODO: If this is actually never changed by any subclass, perhaps it should not be here + public soundName = "se/restore"; + constructor(type: HeldItemId, maxStackCount = 1) { this.type = type; this.maxStackCount = maxStackCount; From b271dc724b1ec83a658073a4db51f0768ac07579 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:43:00 +0200 Subject: [PATCH 060/114] Reworked various effects that steal items --- src/battle-scene.ts | 3 +- src/data/abilities/ability.ts | 55 +++++++++----------------- src/data/moves/move.ts | 23 +++++------ src/field/pokemon-held-item-manager.ts | 18 +++++++++ src/items/held-items/item-steal.ts | 4 +- 5 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index bf6bd4833dc..276904da191 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -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. * 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. * @param heldItemId {@linkcode HeldItemId} item to transfer + * @param source {@linkcode Pokemon} giver in this transfer * @param target {@linkcode Pokemon} recepient in this transfer * @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` diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 4b461e7e9fa..cbeb66aea56 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -35,7 +35,7 @@ import { } from "#app/data/moves/move"; import { allMoves } from "../data-lists"; 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 { SpeciesFormChangeAbilityTrigger, @@ -91,6 +91,7 @@ import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; export class BlockRecoilDamageAttr extends AbAttr { constructor() { @@ -2540,17 +2541,11 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @extends AbAttr */ export class ExecutedMoveAbAttr extends AbAttr { - canApplyExecutedMove( - _pokemon: Pokemon, - _simulated: boolean, - ): boolean { + canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean { return true; } - applyExecutedMove( - _pokemon: Pokemon, - _simulated: boolean, - ): void {} + applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {} } /** @@ -2558,7 +2553,7 @@ export class ExecutedMoveAbAttr extends AbAttr { * @extends ExecutedMoveAbAttr */ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { - constructor(showAbility: boolean = false) { + constructor(showAbility = false) { super(showAbility); } @@ -2575,7 +2570,7 @@ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { private stealCondition: PokemonAttackCondition | null; - private stolenItem?: PokemonHeldItemModifier; + private stolenItem?: HeldItemId; constructor(stealCondition?: PokemonAttackCondition) { super(); @@ -2598,11 +2593,11 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move)) ) { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); + const heldItems = defender.heldItemManager.getTransferableHeldItems(); if (heldItems.length) { // Ensure that the stolen item in testing is the same as when the effect is applied this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; - if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + if (globalScene.canTransferHeldItem(this.stolenItem, defender, pokemon)) { return true; } } @@ -2620,28 +2615,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { _hitResult: HitResult, _args: any[], ): void { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); + const heldItems = defender.heldItemManager.getTransferableHeldItems(); if (!this.stolenItem) { 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( i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, - stolenItemType: this.stolenItem.type.name, + stolenItemType: allHeldItems[this.stolenItem].name, }), ); } 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 { @@ -2762,7 +2750,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { private condition?: PokemonDefendCondition; - private stolenItem?: PokemonHeldItemModifier; + private stolenItem?: HeldItemId; constructor(condition?: PokemonDefendCondition) { super(); @@ -2780,10 +2768,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { _args: any[], ): boolean { 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) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; - if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + if (globalScene.canTransferHeldItem(this.stolenItem, attacker, pokemon)) { return true; } } @@ -2800,28 +2788,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { _hitResult: HitResult, _args: any[], ): void { - const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); + const heldItems = attacker.heldItemManager.getTransferableHeldItems(); if (!this.stolenItem) { 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( i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, - stolenItemType: this.stolenItem.type.name, + stolenItemType: allHeldItems[this.stolenItem].name, }), ); } 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( attrType: Constructor, pokemon: Pokemon, - simulated: boolean = false, + simulated = false, ...args: any[] ): void { applyAbAttrsInternal( diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f670da69fd0..84a1ee49df9 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2559,16 +2559,18 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { return false; } - const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); + const heldItems = target.heldItemManager.getTransferableHeldItems(); if (!heldItems.length) { return false; } - const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; - 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 stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; - if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) { + const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)]; + +// const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; +// 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 stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; + if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) { return false; } @@ -2576,18 +2578,13 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { 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 { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.heldItemManager.getTransferableHeldItems(); return heldItems.length ? 5 : 0; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.heldItemManager.getTransferableHeldItems(); return heldItems.length ? -5 : 0; } } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 71385cda254..564b01e3a41 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -35,6 +35,24 @@ export class PokemonItemManager { 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 { return itemType in this.heldItems; } diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 1e338c98d9a..d5712187b2e 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -47,7 +47,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { // TODO: Change this logic to use held items const transferredModifierTypes: HeldItemId[] = []; - const heldItems = targetPokemon.heldItemManager.getHeldItemKeys(); + const heldItems = targetPokemon.heldItemManager.getTransferableHeldItems(); for (let i = 0; i < transferredItemCount; i++) { if (!heldItems.length) { @@ -56,7 +56,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { 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)) { + if (globalScene.tryTransferHeldItem(randItem, targetPokemon, pokemon, false)) { transferredModifierTypes.push(randItem); heldItems.splice(randItemIndex, 1); } From faa387a67ef4751cf3870100223c7c71cad105c6 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:46:31 +0200 Subject: [PATCH 061/114] Refactored Baton logic --- src/phases/switch-summon-phase.ts | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 103af3db275..5cb25388641 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -13,12 +13,12 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { TrainerSlot } from "#enums/trainer-slot"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import i18next from "i18next"; import { SummonPhase } from "./summon-phase"; import { SubstituteTag } from "#app/data/battler-tags"; import { SwitchType } from "#enums/switch-type"; +import { HeldItemId } from "#enums/held-item-id"; export class SwitchSummonPhase extends SummonPhase { public readonly phaseName: "SwitchSummonPhase" | "ReturnPhase" = "SwitchSummonPhase"; @@ -145,29 +145,11 @@ export class SwitchSummonPhase extends SummonPhase { ); // If the recipient pokemon lacks a baton, give our baton to it during the swap - if ( - !globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id, - ) - ) { - const batonPassModifier = globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id, - ) as SwitchEffectTransferModifier; + if (!switchedInPokemon.heldItemManager.hasItem(HeldItemId.BATON)) { + const batonPassModifier = this.lastPokemon.heldItemManager.hasItem(HeldItemId.BATON); if (batonPassModifier) { - globalScene.tryTransferHeldItemModifier( - batonPassModifier, - switchedInPokemon, - false, - undefined, - undefined, - undefined, - false, - ); + globalScene.tryTransferHeldItem(HeldItemId.BATON, this.lastPokemon, switchedInPokemon, false); } } } From 87b0035463657426d49d132383d02aa57920ce39 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:02:59 +0200 Subject: [PATCH 062/114] Reworked most entries in Modifier Types --- src/items/held-items/base-stat-flat.ts | 10 +- src/items/held-items/exp-booster.ts | 9 + src/items/held-items/friendship-booster.ts | 5 + src/items/held-items/incrementing-stat.ts | 9 + src/items/held-items/item-steal.ts | 12 + src/items/held-items/multi-hit.ts | 5 + src/modifier/modifier-type.ts | 780 +++------------------ src/phases/select-modifier-phase.ts | 29 +- 8 files changed, 156 insertions(+), 703 deletions(-) diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index f48e597cfdf..6111263d3ef 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,7 +1,8 @@ import type Pokemon from "#app/field/pokemon"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem, ITEM_EFFECT } from "../held-item"; -import type { Stat } from "#enums/stat"; +import { getStatKey, type Stat } from "#enums/stat"; +import i18next from "i18next"; export interface BASE_STAT_FLAT_PARAMS { /** The pokemon with the item */ @@ -27,6 +28,13 @@ export class BaseStatFlatHeldItem extends HeldItem { this.stats = stats; } + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", { + stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"), + statValue: this.statModifier, + }); + } + /** * Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}. * @param pokemon The {@linkcode Pokemon} that holds the item diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index cf1ecc2d4b0..ce65f68571d 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,6 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; export interface EXP_BOOST_PARAMS { @@ -12,13 +13,21 @@ export interface EXP_BOOST_PARAMS { export class ExpBoosterHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.EXP_BOOSTER]; + private boostPercent: number; private boostMultiplier: number; constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { super(type, maxStackCount); + this.boostPercent = boostPercent; this.boostMultiplier = boostPercent * 0.01; } + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { + boostPercent: this.boostPercent, + }); + } + // TODO: What do we do with this? Need to look up all the shouldApply /** * Checks if {@linkcode PokemonExpBoosterModifier} should be applied diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index c9f4b5c5ced..9820430d224 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,5 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; +import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; export interface FRIENDSHIP_BOOST_PARAMS { @@ -12,6 +13,10 @@ export interface FRIENDSHIP_BOOST_PARAMS { export class FriendshipBoosterHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.FRIENDSHIP_BOOSTER]; + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); + } + /** * Applies {@linkcode PokemonFriendshipBoosterModifier} * @param _pokemon The {@linkcode Pokemon} to apply the friendship boost to diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index fa66ba7d5a1..e4ac0979c74 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -2,6 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import { HeldItem, ITEM_EFFECT } from "../held-item"; import { Stat } from "#enums/stat"; import type { NumberHolder } from "#app/utils/common"; +import i18next from "i18next"; export interface INCREMENTING_STAT_PARAMS { /** The pokemon with the item */ @@ -28,6 +29,14 @@ export class IncrementingStatHeldItem extends HeldItem { // return super.shouldApply(pokemon, stat, statHolder) && !!statHolder; // } + get name(): string { + return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.name") + " (new)"; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.description"); + } + /** * Applies the {@linkcode PokemonIncrementingStatModifier} * @param _pokemon The {@linkcode Pokemon} that holds the item diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index d5712187b2e..9f2d292a713 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -85,6 +85,10 @@ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_ITEM_STEAL]; isTransferable = true; + get description(): string { + return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description"); + } + /** * Determines the targets to transfer items from when this applies. * @param pokemon the {@linkcode Pokemon} holding this item @@ -121,14 +125,22 @@ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { */ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]; + public readonly chancePercent: number; public readonly chance: number; constructor(type: HeldItemId, maxStackCount = 1, chancePercent: number) { super(type, maxStackCount); + this.chancePercent = chancePercent; this.chance = chancePercent / 100; } + get description(): string { + return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", { + chancePercent: this.chancePercent, + }); + } + /** * Determines the target to steal items from when this applies. * @param _holderPokemon The {@linkcode Pokemon} holding this item diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 8bb48e190c4..244afd9a04e 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -3,6 +3,7 @@ import { HeldItem, ITEM_EFFECT } from "#app/items/held-item"; import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; import type { MoveId } from "#enums/move-id"; import { allMoves } from "#app/data/data-lists"; +import i18next from "i18next"; export interface MULTI_HIT_PARAMS { pokemon: Pokemon; @@ -20,6 +21,10 @@ export interface MULTI_HIT_PARAMS { export class MultiHitHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.MULTI_HIT]; + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); + } + /** * For each stack, converts 25 percent of attack damage into an additional strike. * @param pokemon The {@linkcode Pokemon} using the move diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a09f633be14..943a5b7126a 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; -import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { AttackMove } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; @@ -21,14 +20,8 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { AddPokeballModifier, AddVoucherModifier, - AttackTypeBoosterModifier, - BaseStatModifier, BerryModifier, BoostBugSpawnModifier, - BypassSpeedChanceModifier, - ContactHeldItemTransferChanceModifier, - CritBoosterModifier, - DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, @@ -38,19 +31,16 @@ import { EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, - EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, - FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, - HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, @@ -61,45 +51,29 @@ import { MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, - PokemonBaseStatFlatModifier, - PokemonBaseStatTotalModifier, - PokemonExpBoosterModifier, PokemonFormChangeItemModifier, - PokemonFriendshipBoosterModifier, - PokemonHeldItemModifier, + type PokemonHeldItemModifier, PokemonHpRestoreModifier, - PokemonIncrementingStatModifier, - PokemonInstantReviveModifier, PokemonLevelIncrementModifier, - PokemonMoveAccuracyBoosterModifier, - PokemonMultiHitModifier, PokemonNatureChangeModifier, - PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, - ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, - SpeciesStatBoosterModifier, - SurviveDamageModifier, - SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerrastalizeModifier, TmModifier, - TurnHealModifier, - TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier, - FieldEffectModifier, } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; @@ -389,47 +363,7 @@ export class PokemonModifierType extends ModifierType { } } -export class PokemonHeldItemModifierType extends PokemonModifierType { - constructor( - localeKey: string, - iconImage: string, - newModifierFunc: NewModifierFunc, - group?: string, - soundName?: string, - ) { - super( - localeKey, - iconImage, - newModifierFunc, - (pokemon: PlayerPokemon) => { - const dummyModifier = this.newModifier(pokemon); - const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier), - ) as PokemonHeldItemModifier; - const maxStackCount = dummyModifier.getMaxStackCount(); - if (!maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - if (matchingModifier && matchingModifier.stackCount === maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - return null; - }, - group, - soundName, - ); - } - - newModifier(...args: any[]): PokemonHeldItemModifier { - return super.newModifier(...args) as PokemonHeldItemModifier; - } -} - -export class PokemonHeldItemReward extends PokemonModifierType { +export class HeldItemReward extends PokemonModifierType { public itemId: HeldItemId; constructor(itemId: HeldItemId, group?: string, soundName?: string) { super( @@ -457,20 +391,20 @@ export class PokemonHeldItemReward extends PokemonModifierType { this.itemId = itemId; } - newModifier(...args: any[]): PokemonHeldItemModifier { - return super.newModifier(...args) as PokemonHeldItemModifier; - } - get name(): string { - return allHeldItems[this.itemId].getName(); + return allHeldItems[this.itemId].name; } - getDescription(): string { - return allHeldItems[this.itemId].getDescription(); + get description(): string { + return allHeldItems[this.itemId].name; } - getIcon(): string { - return allHeldItems[this.itemId].getIcon(); + get icon(): string { + return allHeldItems[this.itemId].name; + } + + apply(pokemon: Pokemon) { + pokemon.heldItemManager.add(this.itemId); } } @@ -834,35 +768,7 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge } } -export class BerryModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { - private berryType: BerryType; - - constructor(berryType: BerryType) { - super( - "", - `${BerryType[berryType].toLowerCase()}_berry`, - (type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType), - "berry", - ); - - this.berryType = berryType; - this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework - } - - get name(): string { - return getBerryName(this.berryType); - } - - getDescription(): string { - return getBerryEffectDescription(this.berryType); - } - - getPregenArgs(): any[] { - return [this.berryType]; - } -} - -export class BerryReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { +export class BerryReward extends HeldItemReward implements GeneratedPersistentModifierType { private berryType: BerryType; constructor(berryType: BerryType) { @@ -878,7 +784,30 @@ export class BerryReward extends PokemonHeldItemReward implements GeneratedPersi } } -export class AttackTypeBoosterReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { +class BerryRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { + return new BerryReward(pregenArgs[0] as BerryType); + } + const berryTypes = getEnumValues(BerryType); + let randBerryType: BerryType; + const rand = randSeedInt(12); + if (rand < 2) { + randBerryType = BerryType.SITRUS; + } else if (rand < 4) { + randBerryType = BerryType.LUM; + } else if (rand < 6) { + randBerryType = BerryType.LEPPA; + } else { + randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; + } + return new BerryReward(randBerryType); + }); + } +} + +export class AttackTypeBoosterReward extends HeldItemReward implements GeneratedPersistentModifierType { public moveType: PokemonType; public boostPercent: number; @@ -894,91 +823,6 @@ export class AttackTypeBoosterReward extends PokemonHeldItemReward implements Ge } } -enum AttackTypeBoosterItem { - SILK_SCARF, - BLACK_BELT, - SHARP_BEAK, - POISON_BARB, - SOFT_SAND, - HARD_STONE, - SILVER_POWDER, - SPELL_TAG, - METAL_COAT, - CHARCOAL, - MYSTIC_WATER, - MIRACLE_SEED, - MAGNET, - TWISTED_SPOON, - NEVER_MELT_ICE, - DRAGON_FANG, - BLACK_GLASSES, - FAIRY_FEATHER, -} - -export class AttackTypeBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ - public moveType: PokemonType; - public boostPercent: number; - - constructor(moveType: PokemonType, boostPercent: number) { - super( - "", - `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`, - (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), - ); - - this.moveType = moveType; - this.boostPercent = boostPercent; - } - - get name(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); - } - - getDescription(): string { - // TODO: Need getTypeName? - return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), - }); - } - - getPregenArgs(): any[] { - return [this.moveType]; - } -} - -export type SpeciesStatBoosterItem = keyof typeof SpeciesStatBoosterModifierTypeGenerator.items; - -/** - * Modifier type for {@linkcode SpeciesStatBoosterModifier} - * @extends PokemonHeldItemModifierType - * @implements GeneratedPersistentModifierType - */ -export class SpeciesStatBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ - public key: SpeciesStatBoosterItem; - - constructor(key: SpeciesStatBoosterItem) { - const item = SpeciesStatBoosterModifierTypeGenerator.items[key]; - super( - `modifierType:SpeciesBoosterItem.${key}`, - key.toLowerCase(), - (type, args) => - new SpeciesStatBoosterModifier(type, (args[0] as Pokemon).id, item.stats, item.multiplier, item.species), - ); - - this.key = key; - } - - getPregenArgs(): any[] { - return [this.key]; - } -} - export class PokemonLevelIncrementModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { super( @@ -1014,37 +858,7 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType { } } -export class BaseStatBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ - private stat: PermanentStat; - private key: string; - - constructor(stat: PermanentStat) { - const key = BaseStatBoosterModifierTypeGenerator.items[stat]; - super("", key, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); - - this.stat = stat; - this.key = key; - } - - get name(): string { - return i18next.t(`modifierType:BaseStatBoosterItem.${this.key}`); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { - stat: i18next.t(getStatKey(this.stat)), - }); - } - - getPregenArgs(): any[] { - return [this.stat]; - } -} - -export class BaseStatBoosterReward extends PokemonHeldItemReward implements GeneratedPersistentModifierType { +export class BaseStatBoosterReward extends HeldItemReward { private stat: PermanentStat; private key: string; @@ -1056,80 +870,39 @@ export class BaseStatBoosterReward extends PokemonHeldItemReward implements Gene this.stat = stat; this.key = key; } - - getPregenArgs(): any[] { - return [this.stat]; - } } /** * Shuckle Juice item */ -export class PokemonBaseStatTotalModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ +export class BaseStatTotalHeldItemReward extends HeldItemReward { private readonly statModifier: number; - constructor(statModifier: number) { - super( - "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE", - "berry_juice", - (_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier), - ); + constructor(itemId: HeldItemId, statModifier: number) { + super(itemId); this.statModifier = statModifier; } - override getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", { - increaseDecrease: i18next.t( - this.statModifier >= 0 - ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase" - : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease", - ), - blessCurse: i18next.t( - this.statModifier >= 0 - ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed" - : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed", - ), - statValue: this.statModifier, - }); - } - - public getPregenArgs(): any[] { - return [this.statModifier]; + apply(pokemon: Pokemon) { + super.apply(pokemon); + pokemon.heldItemManager[this.itemId].data.statModifier = this.statModifier; } } /** * Old Gateau item */ -export class PokemonBaseStatFlatModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ +export class BaseStatFlatHeldItemReward extends HeldItemReward { private readonly statModifier: number; - private readonly stats: Stat[]; - constructor(statModifier: number, stats: Stat[]) { - super( - "modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", - "old_gateau", - (_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats), - ); + constructor(itemId: HeldItemId, statModifier: number) { + super(itemId); this.statModifier = statModifier; - this.stats = stats; } - override getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", { - stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"), - statValue: this.statModifier, - }); - } - - public getPregenArgs(): any[] { - return [this.statModifier, this.stats]; + apply(pokemon: Pokemon) { + super.apply(pokemon); + pokemon.heldItemManager[this.itemId].data.statModifier = this.statModifier; } } @@ -1203,72 +976,6 @@ export class ExpBoosterModifierType extends ModifierType { } } -export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { - private boostPercent: number; - - constructor(localeKey: string, iconImage: string, boostPercent: number) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent), - ); - - this.boostPercent = boostPercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { - boostPercent: this.boostPercent, - }); - } -} - -export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, args) => new PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); - } -} - -export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModifierType { - private amount: number; - - constructor(localeKey: string, iconImage: string, amount: number, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount), - group, - soundName, - ); - - this.amount = amount; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { - accuracyAmount: this.amount, - }); - } -} - -export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string) { - super( - localeKey, - iconImage, - (type, args) => new PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id), - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); - } -} - export class TmModifierType extends PokemonModifierType { public moveId: MoveId; @@ -1485,78 +1192,6 @@ class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { } } -class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { - constructor() { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); - } - - const attackMoveTypes = party.flatMap(p => - p - .getMoveset() - .map(m => m.getMove()) - .filter(m => m instanceof AttackMove) - .map(m => m.type), - ); - if (!attackMoveTypes.length) { - return null; - } - - const attackMoveTypeWeights = new Map(); - let totalWeight = 0; - for (const t of attackMoveTypes) { - const weight = attackMoveTypeWeights.get(t) ?? 0; - if (weight < 3) { - attackMoveTypeWeights.set(t, weight + 1); - totalWeight++; - } - } - - if (!totalWeight) { - return null; - } - - let type: PokemonType; - - const randInt = randSeedInt(totalWeight); - let weight = 0; - - for (const t of attackMoveTypeWeights.keys()) { - const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined - if (randInt <= weight + typeWeight) { - type = t; - break; - } - weight += typeWeight; - } - - return new AttackTypeBoosterModifierType(type!, TYPE_BOOST_ITEM_BOOST_PERCENT); - }); - } -} - -class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { - public static readonly items: Record = { - [Stat.HP]: "hp_up", - [Stat.ATK]: "protein", - [Stat.DEF]: "iron", - [Stat.SPATK]: "calcium", - [Stat.SPDEF]: "zinc", - [Stat.SPD]: "carbos", - }; - - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new BaseStatBoosterModifierType(pregenArgs[0]); - } - const randStat: PermanentStat = randSeedInt(Stat.SPD + 1); - return new BaseStatBoosterModifierType(randStat); - }); - } -} - class BaseStatBoosterRewardGenerator extends ModifierTypeGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { @@ -1598,64 +1233,28 @@ class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { */ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { /** Object comprised of the currently available species-based stat boosting held items */ - public static readonly items = { - LIGHT_BALL: { - stats: [Stat.ATK, Stat.SPATK], - multiplier: 2, - species: [SpeciesId.PIKACHU], - rare: true, - }, - THICK_CLUB: { - stats: [Stat.ATK], - multiplier: 2, - species: [SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.ALOLA_MAROWAK], - rare: true, - }, - METAL_POWDER: { - stats: [Stat.DEF], - multiplier: 2, - species: [SpeciesId.DITTO], - rare: true, - }, - QUICK_POWDER: { - stats: [Stat.SPD], - multiplier: 2, - species: [SpeciesId.DITTO], - rare: true, - }, - DEEP_SEA_SCALE: { - stats: [Stat.SPDEF], - multiplier: 2, - species: [SpeciesId.CLAMPERL], - rare: false, - }, - DEEP_SEA_TOOTH: { - stats: [Stat.SPATK], - multiplier: 2, - species: [SpeciesId.CLAMPERL], - rare: false, - }, - }; + public static readonly items = [ + HeldItemId.LIGHT_BALL, + HeldItemId.THICK_CLUB, + HeldItemId.METAL_POWDER, + HeldItemId.QUICK_POWDER, + HeldItemId.DEEP_SEA_SCALE, + HeldItemId.DEEP_SEA_TOOTH, + ]; constructor(rare: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { const items = SpeciesStatBoosterModifierTypeGenerator.items; if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in items) { - return new SpeciesStatBoosterModifierType(pregenArgs[0] as SpeciesStatBoosterItem); + return new HeldItemReward(pregenArgs[0] as HeldItemId); } // Get a pool of items based on the rarity. - const keys: (keyof SpeciesStatBoosterItem)[] = []; - const values: (typeof items)[keyof typeof items][] = []; - const weights: number[] = []; - for (const [key, val] of Object.entries(SpeciesStatBoosterModifierTypeGenerator.items)) { - if (val.rare !== rare) { - continue; - } - values.push(val); - keys.push(key as keyof SpeciesStatBoosterItem); - weights.push(0); - } + const tierItems = rare + ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] + : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; + + const weights = new Array(tierItems.length).fill(0); for (const p of party) { const speciesId = p.getSpeciesForm(true).speciesId; @@ -1663,18 +1262,11 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { // TODO: Use commented boolean when Fling is implemented const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */ - for (const i in values) { - const checkedSpecies = values[i].species; - const checkedStats = values[i].stats; + for (const i in tierItems) { + const checkedSpecies = allHeldItems[tierItems[i]].species; // If party member already has the item being weighted currently, skip to the next item - const hasItem = p - .getHeldItems() - .some( - m => - m instanceof SpeciesStatBoosterModifier && - (m as SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0]), - ); + const hasItem = p.heldItemManager.hasItem(tierItems[i]); if (!hasItem) { if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) { @@ -1688,6 +1280,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { } } + // TODO: Replace this with a helper function let totalWeight = 0; for (const weight of weights) { totalWeight += weight; @@ -1701,7 +1294,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { if (weights[i] !== 0) { const curWeight = weight + weights[i]; if (randInt <= weight + weights[i]) { - return new SpeciesStatBoosterModifierType(keys[i] as SpeciesStatBoosterItem); + return new HeldItemReward(tierItems[i]); } weight = curWeight; } @@ -1882,44 +1475,6 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { } } -export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { - private chancePercent: number; - - constructor(localeKey: string, iconImage: string, chancePercent: number, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (type, args) => new ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent), - group, - soundName, - ); - - this.chancePercent = chancePercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", { - chancePercent: this.chancePercent, - }); - } -} - -export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (type, args) => new TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id), - group, - soundName, - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description"); - } -} - export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { private chancePercent: number; private effect: StatusEffect; @@ -2137,24 +1692,9 @@ export const modifierTypes = { SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - REVIVER_SEED_REWARD: () => new PokemonHeldItemReward(HeldItemId.REVIVER_SEED), + REVIVER_SEED_REWARD: () => new HeldItemReward(HeldItemId.REVIVER_SEED), - REVIVER_SEED: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.REVIVER_SEED", - "reviver_seed", - (type, args) => new PokemonInstantReviveModifier(type, (args[0] as Pokemon).id), - ), - - WHITE_HERB_REWARD: () => new PokemonHeldItemReward(HeldItemId.WHITE_HERB), - - // TODO: Remove the old one - WHITE_HERB: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.WHITE_HERB", - "white_herb", - (type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id), - ), + WHITE_HERB_REWARD: () => new HeldItemReward(HeldItemId.WHITE_HERB), ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10), MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1), @@ -2190,12 +1730,8 @@ export const modifierTypes = { BASE_STAT_BOOSTER_REWARD: () => new BaseStatBoosterRewardGenerator(), - BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), - ATTACK_TYPE_BOOSTER_REWARD: () => new AttackTypeBoosterRewardGenerator(), - ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), - MINT: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { @@ -2204,12 +1740,7 @@ export const modifierTypes = { return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature); }), - MYSTICAL_ROCK: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.MYSTICAL_ROCK", - "mystical_rock", - (type, args) => new FieldEffectModifier(type, (args[0] as Pokemon).id), - ), + MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK), TERA_SHARD: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { @@ -2238,45 +1769,7 @@ export const modifierTypes = { return new TerastallizeModifierType(shardType); }), - BERRY_REWARD: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - return new BerryModifierType(pregenArgs[0] as BerryType); - } - const berryTypes = getEnumValues(BerryType); - let randBerryType: BerryType; - const rand = randSeedInt(12); - if (rand < 2) { - randBerryType = BerryType.SITRUS; - } else if (rand < 4) { - randBerryType = BerryType.LUM; - } else if (rand < 6) { - randBerryType = BerryType.LEPPA; - } else { - randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; - } - return new BerryReward(randBerryType); - }), - - BERRY: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - return new BerryModifierType(pregenArgs[0] as BerryType); - } - const berryTypes = getEnumValues(BerryType); - let randBerryType: BerryType; - const rand = randSeedInt(12); - if (rand < 2) { - randBerryType = BerryType.SITRUS; - } else if (rand < 4) { - randBerryType = BerryType.LUM; - } else if (rand < 6) { - randBerryType = BerryType.LEPPA; - } else { - randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; - } - return new BerryModifierType(randBerryType); - }), + BERRY: () => new BerryRewardGenerator(), TM_COMMON: () => new TmModifierTypeGenerator(ModifierTier.COMMON), TM_GREAT: () => new TmModifierTypeGenerator(ModifierTier.GREAT), @@ -2305,46 +1798,17 @@ export const modifierTypes = { GOLDEN_EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EXP_CHARM", "golden_exp_charm", 100), - LUCKY_EGG_REWARD: () => new PokemonHeldItemReward(HeldItemId.LUCKY_EGG), - GOLDEN_EGG_REWARD: () => new PokemonHeldItemReward(HeldItemId.GOLDEN_EGG), + LUCKY_EGG: () => new HeldItemReward(HeldItemId.LUCKY_EGG), + GOLDEN_EGG: () => new HeldItemReward(HeldItemId.GOLDEN_EGG), - // TODO: Remove these when refactor is done - LUCKY_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.LUCKY_EGG", "lucky_egg", 40), - GOLDEN_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EGG", "golden_egg", 100), + SOOTHE_BELL: () => new HeldItemReward(HeldItemId.SOOTHE_BELL), - SOOTHE_BELL: () => new PokemonFriendshipBoosterModifierType("modifierType:ModifierType.SOOTHE_BELL", "soothe_bell"), + SCOPE_LENS: () => new HeldItemReward(HeldItemId.SCOPE_LENS), + LEEK: () => new HeldItemReward(HeldItemId.LEEK), - SCOPE_LENS: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.SCOPE_LENS", - "scope_lens", - (type, args) => new CritBoosterModifier(type, (args[0] as Pokemon).id, 1), - ), - LEEK: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.LEEK", - "leek", - (type, args) => - new SpeciesCritBoosterModifier(type, (args[0] as Pokemon).id, 2, [ - SpeciesId.FARFETCHD, - SpeciesId.GALAR_FARFETCHD, - SpeciesId.SIRFETCHD, - ]), - ), + EVIOLITE: () => new HeldItemReward(HeldItemId.EVIOLITE), - EVIOLITE: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.EVIOLITE", - "eviolite", - (type, args) => new EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5), - ), - - SOUL_DEW: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.SOUL_DEW", - "soul_dew", - (type, args) => new PokemonNatureWeightModifier(type, (args[0] as Pokemon).id), - ), + SOUL_DEW: () => new HeldItemReward(HeldItemId.SOUL_DEW), NUGGET: () => new MoneyRewardModifierType( @@ -2374,12 +1838,8 @@ export const modifierTypes = { "amulet_coin", (type, _args) => new MoneyMultiplierModifier(type), ), - GOLDEN_PUNCH: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.GOLDEN_PUNCH", - "golden_punch", - (type, args) => new DamageMoneyRewardModifier(type, (args[0] as Pokemon).id), - ), + GOLDEN_PUNCH: () => new HeldItemReward(HeldItemId.GOLDEN_PUNCH), + COIN_CASE: () => new ModifierType( "modifierType:ModifierType.COIN_CASE", @@ -2394,11 +1854,10 @@ export const modifierTypes = { (type, _args) => new LockModifierTiersModifier(type), ), - GRIP_CLAW: () => - new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10), - WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5), + GRIP_CLAW: () => new HeldItemReward(HeldItemId.GRIP_CLAW), + WIDE_LENS: () => new HeldItemReward(HeldItemId.WIDE_LENS), - MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"), + MULTI_LENS: () => new HeldItemReward(HeldItemId.MULTI_LENS), HEALING_CHARM: () => new ModifierType( @@ -2420,63 +1879,21 @@ export const modifierTypes = { (type, _args) => new PreserveBerryModifier(type), ), - FOCUS_BAND: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.FOCUS_BAND", - "focus_band", - (type, args) => new SurviveDamageModifier(type, (args[0] as Pokemon).id), - ), + FOCUS_BAND: () => new HeldItemReward(HeldItemId.FOCUS_BAND), - QUICK_CLAW: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.QUICK_CLAW", - "quick_claw", - (type, args) => new BypassSpeedChanceModifier(type, (args[0] as Pokemon).id), - ), + QUICK_CLAW: () => new HeldItemReward(HeldItemId.QUICK_CLAW), - KINGS_ROCK: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.KINGS_ROCK", - "kings_rock", - (type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id), - ), + KINGS_ROCK: () => new HeldItemReward(HeldItemId.KINGS_ROCK), - LEFTOVERS_REWARD: () => new PokemonHeldItemReward(HeldItemId.LEFTOVERS), + LEFTOVERS: () => new HeldItemReward(HeldItemId.LEFTOVERS), - SHELL_BELL_REWARD: () => new PokemonHeldItemReward(HeldItemId.SHELL_BELL), + SHELL_BELL: () => new HeldItemReward(HeldItemId.SHELL_BELL), - LEFTOVERS: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.LEFTOVERS", - "leftovers", - (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id), - ), - SHELL_BELL: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.SHELL_BELL", - "shell_bell", - (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id), - ), + TOXIC_ORB: () => new HeldItemReward(HeldItemId.TOXIC_ORB), - TOXIC_ORB: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.TOXIC_ORB", - "toxic_orb", - (type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id), - ), - FLAME_ORB: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.FLAME_ORB", - "flame_orb", - (type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id), - ), + FLAME_ORB: () => new HeldItemReward(HeldItemId.FLAME_ORB), - BATON: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.BATON", - "baton", - (type, args) => new SwitchEffectTransferModifier(type, (args[0] as Pokemon).id), - ), + BATON: () => new HeldItemReward(HeldItemId.BATON), SHINY_CHARM: () => new ModifierType( @@ -2502,8 +1919,7 @@ export const modifierTypes = { DNA_SPLICERS: () => new FusePokemonModifierType("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), - MINI_BLACK_HOLE: () => - new TurnHeldItemTransferModifierType("modifierType:ModifierType.MINI_BLACK_HOLE", "mini_black_hole"), + MINI_BLACK_HOLE: () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), VOUCHER: () => new AddVoucherModifierType(VoucherType.REGULAR, 1), VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1), @@ -2613,12 +2029,8 @@ export const modifierTypes = { (type, _args) => new HealShopCostModifier(type, 2.5), ); }), - MYSTERY_ENCOUNTER_MACHO_BRACE: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", - "macho_brace", - (type, args) => new PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id), - ), + MYSTERY_ENCOUNTER_MACHO_BRACE: () => new HeldItemReward(HeldItemId.MACHO_BRACE), + MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType( "modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", @@ -3773,7 +3185,7 @@ export function getEnemyHeldItemsForWave( poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, - )?.type as PokemonHeldItemReward; + )?.type as HeldItemReward; return reward.itemId; }); if (!(waveIndex % 1000)) { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 329c8df3dc0..cab93e92c19 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -16,12 +16,7 @@ import { PokemonHeldItemReward, } from "#app/modifier/modifier-type"; import type { Modifier } from "#app/modifier/modifier"; -import { - ExtraModifierModifier, - HealShopCostModifier, - PokemonHeldItemModifier, - TempExtraModifierModifier, -} from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; @@ -76,9 +71,6 @@ export class SelectModifierPhase extends BattlePhase { } const modifierCount = this.getModifierCount(); - this.typeOptions = this.getModifierTypeOptions(modifierCount); - const modifierCount = this.getModifierCount(); - this.typeOptions = this.getModifierTypeOptions(modifierCount); const modifierSelectCallback = (rowCursor: number, cursor: number) => { @@ -176,6 +168,9 @@ export class SelectModifierPhase extends BattlePhase { modifierSelectCallback: ModifierSelectCallback, ): boolean { if (modifierType instanceof PokemonModifierType) { + if (modifierType instanceof PokemonHeldItemReward) { + this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); + } if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { @@ -226,17 +221,15 @@ export class SelectModifierPhase extends BattlePhase { fromSlotIndex !== toSlotIndex && itemIndex > -1 ) { - const itemModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - globalScene.tryTransferHeldItemModifier( - itemModifier, + const items = party[fromSlotIndex].heldItemManager.getTransferableHeldItems(); + const item = items[itemIndex]; + globalScene.tryTransferHeldItem( + item, + party[fromSlotIndex], party[toSlotIndex], true, itemQuantity, - undefined, - undefined, + true, false, ); } else { @@ -373,7 +366,7 @@ export class SelectModifierPhase extends BattlePhase { (slotIndex: number, _option: PartyOption) => { if (slotIndex < 6) { globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - party[slotIndex].heldItemManager.add(reward.itemId); + reward.apply(party[slotIndex]); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE); super.end(); From 5278a96f24275fc41e4da2136991ac07d42c6dbd Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:20:21 +0200 Subject: [PATCH 063/114] pokemon.getHeldItems now uses heldItemManager --- src/field/pokemon-held-item-manager.ts | 2 +- src/field/pokemon.ts | 10 ++-------- src/ui/summary-ui-handler.ts | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 564b01e3a41..88e025599d5 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -31,7 +31,7 @@ export class PokemonItemManager { this.heldItems = {}; } - getHeldItemKeys(): number[] { + getHeldItems(): number[] { return Object.keys(this.heldItems).map(k => Number(k)); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a29b44bd03f..5c92f83f7f7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1198,14 +1198,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); } - getHeldItems(): PokemonHeldItemModifier[] { - if (!globalScene) { - return []; - } - return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, - this.isPlayer(), - ) as PokemonHeldItemModifier[]; + getHeldItems(): HeldItemId[] { + return this.heldItemManager.getHeldItems(); } updateScale(): void { diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index a6d3d805a48..dfd12c56252 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -1050,7 +1050,7 @@ export default class SummaryUiHandler extends UiHandler { icon.on("pointerout", () => globalScene.ui.hideTooltip()); }); - const heldItemKeys = this.pokemon?.heldItemManager.getHeldItemKeys(); + const heldItemKeys = this.pokemon?.heldItemManager.getHeldItems(); // TODO: Sort them //.sort(heldItemSortFunc); From a0041eb18a443daf5d7654a4dccabae9dd128be0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:48:56 +0200 Subject: [PATCH 064/114] Added Evolution Tracker as held item --- src/enums/held-item-id.ts | 3 + src/items/all-held-items.ts | 9 ++ src/items/held-item.ts | 12 +-- src/items/held-items/evo-tracker.ts | 86 ++++++++++++++++++ src/modifier/modifier-type.ts | 136 ++++++++++++++-------------- 5 files changed, 170 insertions(+), 76 deletions(-) create mode 100644 src/items/held-items/evo-tracker.ts diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 6669e81453a..cff6151ada7 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -85,6 +85,9 @@ export const HeldItemId = { SHUCKLE_JUICE: 0x0907, OLD_GATEAU: 0x0908, MACHO_BRACE: 0x0909, + + // Evo trackers + GIMMIGHOUL_EVO_TRACKER: 0x0a01, }; export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 99e442250be..fab4c4bb5cc 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -24,6 +24,7 @@ import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-it import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; import { type DAMAGE_MONEY_REWARD_PARAMS, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; +import { type EVO_TRACKER_PARAMS, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; @@ -164,6 +165,13 @@ export function initHeldItems() { .unstealable() .untransferable() .unsuppressable(); + + allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER] = new GimmighoulEvoTrackerHeldItem( + HeldItemId.GIMMIGHOUL_EVO_TRACKER, + 999, + SpeciesId.GIMMIGHOUL, + 10, + ); } type APPLY_HELD_ITEMS_PARAMS = { @@ -193,6 +201,7 @@ type APPLY_HELD_ITEMS_PARAMS = { [ITEM_EFFECT.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; [ITEM_EFFECT.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; [ITEM_EFFECT.INCREMENTING_STAT]: INCREMENTING_STAT_PARAMS; + [ITEM_EFFECT.EVO_TRACKER]: EVO_TRACKER_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 96ff712a0c6..0249e8ec40e 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -33,7 +33,6 @@ export const ITEM_EFFECT = { BASE_STAT_TOTAL: 50, BASE_STAT_FLAT: 51, INCREMENTING_STAT: 52, - LAPSING: 60, } as const; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; @@ -90,13 +89,13 @@ export class HeldItem { return this.maxStackCount; } - createSummaryIcon(stackCount: number): Phaser.GameObjects.Container { + createSummaryIcon(pokemon: Pokemon): Phaser.GameObjects.Container { const container = globalScene.add.container(0, 0); const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); container.add(item); - const stackText = this.getIconStackText(stackCount); + const stackText = this.getIconStackText(pokemon); if (stackText) { container.add(stackText); } @@ -106,7 +105,7 @@ export class HeldItem { return container; } - createPokemonIcon(stackCount: number, pokemon: Pokemon): Phaser.GameObjects.Container { + createPokemonIcon(pokemon: Pokemon): Phaser.GameObjects.Container { const container = globalScene.add.container(0, 0); const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); @@ -120,7 +119,7 @@ export class HeldItem { .setTexture("items", this.iconName); container.add(item); - const stackText = this.getIconStackText(stackCount); + const stackText = this.getIconStackText(pokemon); if (stackText) { container.add(stackText); } @@ -128,11 +127,12 @@ export class HeldItem { return container; } - getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { + getIconStackText(pokemon: Pokemon): Phaser.GameObjects.BitmapText | null { if (this.getMaxStackCount() === 1) { return null; } + const stackCount = pokemon.heldItemManager.getStack(this.type); const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); text.letterSpacing = -0.5; if (stackCount >= this.getMaxStackCount()) { diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts new file mode 100644 index 00000000000..6bd4937b2ee --- /dev/null +++ b/src/items/held-items/evo-tracker.ts @@ -0,0 +1,86 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import i18next from "i18next"; +import { HeldItem, ITEM_EFFECT } from "../held-item"; + +export interface EVO_TRACKER_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + multiplier: NumberHolder; +} + +//TODO: Possibly replace with this +export interface EVO_TRACKER_DATA { + evoCounter: number; +} + +export class EvoTrackerHeldItem extends HeldItem { + public effects: ITEM_EFFECT[] = [ITEM_EFFECT.EVO_TRACKER]; + + protected species: SpeciesId; + protected required: number; + public isTransferable = false; + + constructor(type: HeldItemId, maxStackCount = 1, species: SpeciesId, required: number) { + super(type, maxStackCount); + this.species = species; + this.required = required; + } + + /** + * Applies the {@linkcode EvoTrackerModifier} + * @returns always `true` + */ + apply(): boolean { + return true; + } + + getIconStackText(pokemon: Pokemon): Phaser.GameObjects.BitmapText | null { + const stackCount = this.getStackCount(pokemon); + + const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (stackCount >= this.required) { + text.setTint(0xf89890); + } + text.setOrigin(0, 0); + + return text; + } + + getStackCount(_pokemon: Pokemon): number { + return 0; + } +} + +export class GimmighoulEvoTrackerHeldItem extends EvoTrackerHeldItem { + get name(): string { + return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.description"); + } + + get iconName(): string { + return "relic_gold"; + } + + getStackCount(pokemon: Pokemon): number { + const stackCount = + pokemon.evoCounter + + pokemon.heldItemManager.getStack(HeldItemId.GOLDEN_PUNCH) + + globalScene.findModifiers( + m => + m instanceof MoneyMultiplierModifier || + m instanceof ExtraModifierModifier || + m instanceof TempExtraModifierModifier, + ).length; + return stackCount; + } +} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 943a5b7126a..9c068399095 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -20,7 +20,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { AddPokeballModifier, AddVoucherModifier, - BerryModifier, BoostBugSpawnModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, @@ -31,7 +30,6 @@ import { EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, - EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, @@ -62,13 +60,11 @@ import { PreserveBerryModifier, RememberMoveModifier, ShinyRateBoosterModifier, - SpeciesCritBoosterModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerrastalizeModifier, TmModifier, - TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, @@ -889,15 +885,28 @@ export class BaseStatTotalHeldItemReward extends HeldItemReward { } } +class BaseStatTotalHeldItemRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + return new BaseStatTotalHeldItemReward(HeldItemId.SHUCKLE_JUICE, pregenArgs[0] as number); + } + return new BaseStatTotalHeldItemReward(HeldItemId.SHUCKLE_JUICE, randSeedInt(20, 1)); + }); + } +} + /** * Old Gateau item */ export class BaseStatFlatHeldItemReward extends HeldItemReward { private readonly statModifier: number; + private readonly stats: Stat[]; - constructor(itemId: HeldItemId, statModifier: number) { + constructor(itemId: HeldItemId, statModifier: number, stats: Stat[]) { super(itemId); this.statModifier = statModifier; + this.stats = stats; } apply(pokemon: Pokemon) { @@ -906,6 +915,17 @@ export class BaseStatFlatHeldItemReward extends HeldItemReward { } } +class BaseStatFlatHeldItemRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + return new BaseStatFlatHeldItemReward(HeldItemId.OLD_GATEAU, pregenArgs[0] as number, pregenArgs[1] as Stat[]); + } + return new BaseStatFlatHeldItemReward(HeldItemId.OLD_GATEAU, randSeedInt(20, 1), [Stat.HP, Stat.ATK, Stat.DEF]); + }); + } +} + class AllPokemonFullHpRestoreModifierType extends ModifierType { private descriptionKey: string; @@ -1648,12 +1668,7 @@ export const modifierTypes = { FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(false), RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true), - EVOLUTION_TRACKER_GIMMIGHOUL: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", - "relic_gold", - (type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10), - ), + EVOLUTION_TRACKER_GIMMIGHOUL: () => new HeldItemReward(HeldItemId.GIMMIGHOUL_EVO_TRACKER), MEGA_BRACELET: () => new ModifierType( @@ -1692,9 +1707,9 @@ export const modifierTypes = { SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - REVIVER_SEED_REWARD: () => new HeldItemReward(HeldItemId.REVIVER_SEED), + REVIVER_SEED: () => new HeldItemReward(HeldItemId.REVIVER_SEED), - WHITE_HERB_REWARD: () => new HeldItemReward(HeldItemId.WHITE_HERB), + WHITE_HERB: () => new HeldItemReward(HeldItemId.WHITE_HERB), ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10), MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1), @@ -1728,9 +1743,9 @@ export const modifierTypes = { } })("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new TempCritBoosterModifier(type, 5)), - BASE_STAT_BOOSTER_REWARD: () => new BaseStatBoosterRewardGenerator(), + BASE_STAT_BOOSTER: () => new BaseStatBoosterRewardGenerator(), - ATTACK_TYPE_BOOSTER_REWARD: () => new AttackTypeBoosterRewardGenerator(), + ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterRewardGenerator(), MINT: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { @@ -2000,20 +2015,10 @@ export const modifierTypes = { (type, _args) => new EnemyFusionChanceModifier(type, 1), ), - MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); - } - return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1)); - }), - MYSTERY_ENCOUNTER_OLD_GATEAU: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); - } - return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [Stat.HP, Stat.ATK, Stat.DEF]); - }), + MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new BaseStatTotalHeldItemRewardGenerator(), + + MYSTERY_ENCOUNTER_OLD_GATEAU: () => new BaseStatFlatHeldItemRewardGenerator(), + MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { @@ -2085,7 +2090,7 @@ const modifierPool: ModifierPool = { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -2104,7 +2109,7 @@ const modifierPool: ModifierPool = { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -2135,12 +2140,10 @@ const modifierPool: ModifierPool = { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => allHeldItems[i].effect === p.status?.effect), ).length, 3, ); @@ -2201,12 +2204,10 @@ const modifierPool: ModifierPool = { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => allHeldItems[i].effect === p.status?.effect), ).length, 3, ); @@ -2226,7 +2227,7 @@ const modifierPool: ModifierPool = { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -2245,7 +2246,7 @@ const modifierPool: ModifierPool = { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -2349,7 +2350,7 @@ const modifierPool: ModifierPool = { (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) ) { // Check if Pokemon is already holding an Eviolite - return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + return !p.heldItemManager.hasItem(HeldItemId.EVIOLITE); } return false; }) @@ -2366,7 +2367,7 @@ const modifierPool: ModifierPool = { // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear return party.some( p => - !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && + !p.heldItemManager.hasItem(HeldItemId.LEEK) && (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), ) @@ -2379,7 +2380,7 @@ const modifierPool: ModifierPool = { modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -2425,7 +2426,7 @@ const modifierPool: ModifierPool = { modifierTypes.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -2471,13 +2472,8 @@ const modifierPool: ModifierPool = { modifierTypes.MYSTICAL_ROCK, (party: Pokemon[]) => { return party.some(p => { - let isHoldingMax = false; - for (const i of p.getHeldItems()) { - if (i.type.id === "MYSTICAL_ROCK") { - isHoldingMax = i.getStackCount() === i.getMaxStackCount(); - break; - } - } + const stack = p.heldItemManager.getStack(HeldItemId.MYSTICAL_ROCK); + const isHoldingMax = stack === allHeldItems[HeldItemId.MYSTICAL_ROCK].maxStackCount; if (!isHoldingMax) { const moveset = p.getMoveset(true).map(m => m.moveId); @@ -2521,7 +2517,7 @@ const modifierPool: ModifierPool = { ), new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 9), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), new WeightedModifierType(modifierTypes.TM_ULTRA, 11), new WeightedModifierType(modifierTypes.RARER_CANDY, 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), @@ -2545,8 +2541,8 @@ const modifierPool: ModifierPool = { [ModifierTier.ROGUE]: [ new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.LEFTOVERS_REWARD, 3), - new WeightedModifierType(modifierTypes.SHELL_BELL_REWARD, 3), + new WeightedModifierType(modifierTypes.LEFTOVERS, 3), + new WeightedModifierType(modifierTypes.SHELL_BELL, 3), new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), @@ -2622,26 +2618,26 @@ const modifierPool: ModifierPool = { }; const wildModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY_REWARD, 1)].map(m => { + [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { m.setTier(ModifierTier.COMMON); return m; }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 1)].map(m => { + [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB_REWARD, 0), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), - [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG_REWARD, 4)].map(m => { + [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), - [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG_REWARD, 1)].map(m => { + [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { m.setTier(ModifierTier.MASTER); return m; }), @@ -2649,19 +2645,19 @@ const wildModifierPool: ModifierPool = { const trainerModifierPool: ModifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BERRY_REWARD, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 3), + new WeightedModifierType(modifierTypes.BERRY, 8), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER_REWARD, 3)].map(m => { + [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER_REWARD, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB_REWARD, 0), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; From 928d8a8f97db41c039e09a2e7e06a2b76fd78398 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:12:41 +0200 Subject: [PATCH 065/114] MBE is always untransferable --- src/phases/encounter-phase.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index e3b33122ac2..5687a5ad026 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -19,7 +19,7 @@ import { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; +import { BoostBugSpawnModifier, IvScannerModifier } from "#app/modifier/modifier"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; @@ -547,22 +547,6 @@ export class EncounterPhase extends BattlePhase { if (enemyPokemon.isShiny(true)) { globalScene.phaseManager.unshiftNew("ShinySparklePhase", BattlerIndex.ENEMY + e); } - /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ - if ( - enemyPokemon.species.speciesId === SpeciesId.ETERNATUS && - (globalScene.gameMode.isBattleClassicFinalBoss(globalScene.currentBattle.waveIndex) || - globalScene.gameMode.isEndlessMajorBoss(globalScene.currentBattle.waveIndex)) - ) { - const enemyMBH = globalScene.findModifier( - m => m instanceof TurnHeldItemTransferModifier, - false, - ) as TurnHeldItemTransferModifier; - if (enemyMBH) { - globalScene.removeModifier(enemyMBH, true); - enemyMBH.setTransferrableFalse(); - globalScene.addEnemyModifier(enemyMBH); - } - } }); if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { From ff73c5b0383fbc0e56c86dc058aa24f6d62f22e1 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:14:26 +0200 Subject: [PATCH 066/114] Improved item transfer --- src/battle-scene.ts | 3 +- src/data/moves/move.ts | 38 ++++++++++++++++---------- src/field/pokemon-held-item-manager.ts | 8 ++++-- src/field/pokemon.ts | 13 ++++----- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 276904da191..1168cb1e59f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2759,8 +2759,9 @@ export default class BattleScene extends SceneBase { } const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); + const data = source.heldItemManager[heldItemId].data; source.heldItemManager.remove(heldItemId, countTaken); - target.heldItemManager.add(heldItemId, countTaken); + target.heldItemManager.add(heldItemId, countTaken, data); if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 84a1ee49df9..94c9da520fe 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -70,7 +70,6 @@ import { allAbilities, allMoves } from "../data-lists"; import { BerryModifier, PokemonHeldItemModifier, - PokemonMultiHitModifier, PreserveBerryModifier, } from "../../modifier/modifier"; import type { BattlerIndex } from "../../battle"; @@ -122,8 +121,10 @@ 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 { 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 { berryTypeToHeldItem } from "#app/items/held-items/berry"; +import { HeldItemId } from "#enums/held-item-id"; type MoveConditionFunc = (user: Pokemon, target: 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); 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; } @@ -919,7 +920,7 @@ export default class Move implements Localizable { * Returns `true` if this move can be given additional strikes * by enhancing effects. * 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 restrictSpread `true` if the enhancing effect * 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 { // 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) { // 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); @@ -2574,7 +2575,12 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { 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; } @@ -2630,10 +2636,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Considers entire transferrable item pool by default (Knock Off). // Otherwise only consider berries (Incinerate). - let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); + let heldItems = target.heldItemManager.getTransferableHeldItems(); 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) { @@ -2647,9 +2653,11 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { globalScene.updateModifiers(target.isPlayer()); 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 { - 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; @@ -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 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 heldItems = target.getHeldItems().filter(i => i.isTransferable); + const heldItems = target.heldItemManager.getTransferableHeldItems(); if (heldItems.length === 0) { 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 }); return message; }; @@ -9123,7 +9131,7 @@ export function initMoves() { .condition((user, target, move) => !target.status && !target.isSafeguarded(user)) .reflectable(), 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) .edgeCase(), // 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) .attr(ForceLastAttr), 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) .ignoresSubstitute() .attr(CopyTypeAttr), diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 88e025599d5..a07e3ee2911 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -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_FLAT_DATA } from "#app/items/held-items/base-stat-flat"; +type HELD_ITEM_DATA = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; + interface HeldItemProperties { stack: number; disabled: boolean; cooldown?: number; - data?: BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; + data?: HELD_ITEM_DATA; } type HeldItemPropertyMap = { @@ -66,14 +68,14 @@ export class PokemonItemManager { 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(); if (this.hasItem(itemType)) { // TODO: We may want an error message of some kind instead this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack); } else { - this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false }; + this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data }; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5c92f83f7f7..051175ede3f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -92,7 +92,6 @@ import { ShinyRateBoosterModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, - EvoTrackerModifier, } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; 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 { applyHeldItems } from "#app/items/all-held-items"; 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 { MISC, @@ -4375,7 +4374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(this.isPlayer(), true); + globalScene.updateModifiers(this.isPlayer()); Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); }); }); @@ -5801,9 +5800,9 @@ export class PlayerPokemon extends Pokemon { }); }; 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) { - globalScene.removeModifier(evotracker); + this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true); } } if (!globalScene.gameMode.isDaily || this.metBiome > -1) { @@ -5911,7 +5910,7 @@ export class PlayerPokemon extends Pokemon { const updateAndResolve = () => { this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(true, true); + globalScene.updateModifiers(true); this.updateInfo(true).then(() => resolve()); }); }; @@ -5983,7 +5982,7 @@ export class PlayerPokemon extends Pokemon { true, ) as PokemonHeldItemModifier[]; 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.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; From 20d4d3aa05baa9aaca90be4e143615fd51c70568 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:29:13 +0200 Subject: [PATCH 067/114] Fixed types in held item manager --- src/field/pokemon-held-item-manager.ts | 48 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index a07e3ee2911..59423e83c1e 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -14,7 +14,7 @@ interface HeldItemProperties { } type HeldItemPropertyMap = { - [key in HeldItemId]: HeldItemProperties; + [key in HeldItemId]?: HeldItemProperties; }; interface FormChangeItemProperties { @@ -22,7 +22,7 @@ interface FormChangeItemProperties { } type FormChangeItemPropertyMap = { - [key in FormChangeItem]: FormChangeItemProperties; + [key in FormChangeItem]?: FormChangeItemProperties; }; export class PokemonItemManager { @@ -31,6 +31,7 @@ export class PokemonItemManager { constructor() { this.heldItems = {}; + this.formChangeItems = {}; } getHeldItems(): number[] { @@ -59,31 +60,40 @@ export class PokemonItemManager { return itemType in this.heldItems; } + /* getItem(itemType: HeldItemId): HeldItemProperties { - // TODO: Not very safe - return this.heldItems[itemType]; + if (itemType in this.heldItems) { + return this.heldItems[itemType]; + } } +*/ getStack(itemType: HeldItemId): number { - return itemType in this.heldItems ? this.heldItems[itemType].stack : 0; + const item = this.heldItems[itemType]; + return item ? item.stack : 0; } add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA) { const maxStack = allHeldItems[itemType].getMaxStackCount(); + const item = this.heldItems[itemType]; - if (this.hasItem(itemType)) { + if (item) { // TODO: We may want an error message of some kind instead - this.heldItems[itemType].stack = Math.min(this.heldItems[itemType].stack + addStack, maxStack); + item.stack = Math.min(item.stack + addStack, maxStack); } else { this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data }; } } remove(itemType: HeldItemId, removeStack = 1, all = false) { - this.heldItems[itemType].stack -= removeStack; + const item = this.heldItems[itemType]; - if (all || this.heldItems[itemType].stack <= 0) { - delete this.heldItems[itemType]; + if (item) { + item.stack -= removeStack; + + if (all || item.stack <= 0) { + delete this.heldItems[itemType]; + } } } @@ -98,7 +108,11 @@ export class PokemonItemManager { } hasActiveFormChangeItem(id: FormChangeItem): boolean { - return id in this.formChangeItems && this.formChangeItems[id].active; + const item = this.formChangeItems[id]; + if (item) { + return item.active; + } + return false; } getFormChangeItems(): FormChangeItem[] { @@ -106,12 +120,18 @@ export class PokemonItemManager { } getActiveFormChangeItems(): FormChangeItem[] { - return this.getFormChangeItems().filter(m => this.formChangeItems[m].active); + return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active); } toggleActive(id: FormChangeItem) { - if (id in this.formChangeItems) { - this.formChangeItems[id].active = !this.formChangeItems[id].active; + const item = this.formChangeItems[id]; + if (item) { + item.active = !item.active; } } + + clearItems() { + this.heldItems = {}; + this.formChangeItems = {}; + } } From 2678535befd216ce418352aa30ce9e5138e24ec7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:35:06 +0200 Subject: [PATCH 068/114] Various fixes --- src/battle-scene.ts | 2 +- src/field/pokemon.ts | 9 ++-- src/modifier/modifier-type.ts | 77 +++++++---------------------- src/phases/select-modifier-phase.ts | 4 +- 4 files changed, 24 insertions(+), 68 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 1168cb1e59f..140774474a6 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2709,7 +2709,7 @@ export default class BattleScene extends SceneBase { this.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); - pokemon.heldItemManager.formChangeItems[itemId].active = true; + pokemon.heldItemManager.toggleActive(itemId); if (!ignoreUpdate) { this.updateModifiers(false); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 051175ede3f..a293f853079 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5977,12 +5977,9 @@ export class PlayerPokemon extends Pokemon { } // combine the two mons' held items - const fusedPartyMemberHeldModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItem(modifier, pokemon, this, false, modifier.getStackCount(), true, false); + const fusedPartyMemberHeldItems = pokemon.getHeldItems(); + for (const item of fusedPartyMemberHeldItems) { + globalScene.tryTransferHeldItem(item, pokemon, this, false, pokemon.heldItemManager.getStack(item), true, false); } globalScene.updateModifiers(true); globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 9c068399095..c53fcc059fd 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -106,6 +106,7 @@ import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; import { berryTypeToHeldItem } from "#app/items/held-items/berry"; import { permanentStatToHeldItem, statBoostItems } from "#app/items/held-items/base-stat-booster"; +import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoosterItemId } from "#app/items/held-items/stat-booster"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -374,7 +375,7 @@ export class HeldItemReward extends PokemonModifierType { pokemonName: getPokemonNameWithAffix(pokemon), }); } - if (hasItem && pokemon.heldItemManager.getItem(this.itemId).stack === maxStackCount) { + if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { pokemonName: getPokemonNameWithAffix(pokemon), }); @@ -1095,7 +1096,7 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge /** * Class that represents form changing items */ -export class FormChangeItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType { +export class FormChangeItemReward extends PokemonModifierType { public formChangeItem: FormChangeItem; constructor(formChangeItem: FormChangeItem) { @@ -1134,10 +1135,6 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G getDescription(): string { return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); } - - getPregenArgs(): any[] { - return [this.formChangeItem]; - } } export class FusePokemonModifierType extends PokemonModifierType { @@ -1251,21 +1248,12 @@ class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { * the current list of {@linkcode items}. * @extends ModifierTypeGenerator */ -class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { +class SpeciesStatBoosterRewardGenerator extends ModifierTypeGenerator { /** Object comprised of the currently available species-based stat boosting held items */ - public static readonly items = [ - HeldItemId.LIGHT_BALL, - HeldItemId.THICK_CLUB, - HeldItemId.METAL_POWDER, - HeldItemId.QUICK_POWDER, - HeldItemId.DEEP_SEA_SCALE, - HeldItemId.DEEP_SEA_TOOTH, - ]; constructor(rare: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { - const items = SpeciesStatBoosterModifierTypeGenerator.items; - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in items) { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { return new HeldItemReward(pregenArgs[0] as HeldItemId); } @@ -1410,11 +1398,11 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } } -class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { +class FormChangeItemRewardGenerator extends ModifierTypeGenerator { constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { - return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); + return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); } const formChangeItemPool = [ @@ -1438,16 +1426,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { fc.preFormKey === p.getFormKey(), ) .map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) - .filter( - t => - t?.active && - !globalScene.findModifier( - m => - m instanceof PokemonFormChangeItemModifier && - m.pokemonId === p.id && - m.formChangeItem === t.item, - ), - ); + .filter(t => t?.active && !p.heldItemManager.hasFormChangeItem(t.item)); if (p.species.speciesId === SpeciesId.NECROZMA) { // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... @@ -1490,7 +1469,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { return null; } - return new FormChangeItemModifierType(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); + return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); }); } } @@ -1612,7 +1591,7 @@ export type GeneratorModifierOverride = { } & ( | { name: keyof Pick; - type?: SpeciesStatBoosterItem; + type?: SpeciesStatBoosterItemId; } | { name: keyof Pick; @@ -1627,7 +1606,7 @@ export type GeneratorModifierOverride = { type?: Nature; } | { - name: keyof Pick; + name: keyof Pick; type?: PokemonType; } | { @@ -1665,8 +1644,9 @@ export const modifierTypes = { EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(false), RARE_EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(true), - FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(false), - RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true), + + FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(false), + RARE_FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(true), EVOLUTION_TRACKER_GIMMIGHOUL: () => new HeldItemReward(HeldItemId.GIMMIGHOUL_EVO_TRACKER), @@ -1728,8 +1708,8 @@ export const modifierTypes = { SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 15), MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 30), - SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(false), - RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(true), + SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(false), + RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(true), TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterModifierTypeGenerator(), @@ -2847,8 +2827,8 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod : weightedModifierType.modifierType; const weight = !existingModifiers.length || - itemModifierType instanceof PokemonHeldItemModifierType || - itemModifierType instanceof FormChangeItemModifierType || + itemModifierType instanceof HeldItemReward || + itemModifierType instanceof FormChangeItemReward || existingModifiers.find(m => m.stackCount < m.getMaxStackCount(true)) ? weightedModifierType.weight instanceof Function ? // biome-ignore lint/complexity/noBannedTypes: TODO: refactor to not use Function type @@ -3146,28 +3126,7 @@ export function getEnemyBuffModifierForWave( return modifier; } -export function getEnemyModifierTypesForWave( - waveIndex: number, - count: number, - party: EnemyPokemon[], - poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, - upgradeChance = 0, -): PokemonHeldItemModifierType[] { - const ret = new Array(count) - .fill(0) - .map( - () => - getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0) - ?.type as PokemonHeldItemModifierType, - ); - if (!(waveIndex % 1000)) { - ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); - } - return ret; -} - // TODO: Add proper documentation to this function (once it fully works...) -// TODO: Convert trainer pool to HeldItemId too export function getEnemyHeldItemsForWave( waveIndex: number, count: number, diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index cab93e92c19..2180b4dca17 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -13,7 +13,7 @@ import { PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions, - PokemonHeldItemReward, + HeldItemReward, } from "#app/modifier/modifier-type"; import type { Modifier } from "#app/modifier/modifier"; import { ExtraModifierModifier, HealShopCostModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; @@ -168,7 +168,7 @@ export class SelectModifierPhase extends BattlePhase { modifierSelectCallback: ModifierSelectCallback, ): boolean { if (modifierType instanceof PokemonModifierType) { - if (modifierType instanceof PokemonHeldItemReward) { + if (modifierType instanceof HeldItemReward) { this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); } if (modifierType instanceof FusePokemonModifierType) { From d22f7b1d4aa82a4787ee0fb326f46a17e538fa78 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:37:12 +0200 Subject: [PATCH 069/114] Fixed types in shuckle juice and old gateau --- src/items/held-items/base-stat-flat.ts | 10 +++++----- src/items/held-items/base-stat-total.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 6111263d3ef..5144b392ad0 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -13,6 +13,7 @@ export interface BASE_STAT_FLAT_PARAMS { export interface BASE_STAT_FLAT_DATA { statModifier: number; + stats: Stat[]; } /** @@ -20,12 +21,10 @@ export interface BASE_STAT_FLAT_DATA { */ export class BaseStatFlatHeldItem extends HeldItem { public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BASE_STAT_FLAT]; - private stats: Stat[]; public isTransferable = false; - constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[]) { + constructor(type: HeldItemId, maxStackCount = 1) { super(type, maxStackCount); - this.stats = stats; } get description(): string { @@ -53,15 +52,16 @@ export class BaseStatFlatHeldItem extends HeldItem { */ apply(params: BASE_STAT_FLAT_PARAMS): boolean { const pokemon = params.pokemon; - const itemData = pokemon.heldItemManager.heldItems[this.type].data; + const itemData = pokemon.heldItemManager.heldItems[this.type]?.data as BASE_STAT_FLAT_DATA; if (!itemData) { return false; } const statModifier = itemData.statModifier; + const stats = itemData.stats; const baseStats = params.baseStats; // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats baseStats.forEach((v, i) => { - if (this.stats.includes(i)) { + if (stats.includes(i)) { const newVal = Math.floor(v + statModifier); baseStats[i] = Math.min(Math.max(newVal, 1), 999999); } diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 3b85a002e65..59bc10e3cc3 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -63,7 +63,7 @@ export class BaseStatTotalHeldItem extends HeldItem { */ apply(params: BASE_STAT_TOTAL_PARAMS): boolean { const pokemon = params.pokemon; - const itemData = pokemon.heldItemManager.heldItems[this.type].data; + const itemData = pokemon.heldItemManager.heldItems[this.type]?.data as BASE_STAT_TOTAL_DATA; if (!itemData) { return false; } From 4a3a442ebd0598b6d596769196d09f0abf274d48 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:56:05 +0200 Subject: [PATCH 070/114] MBE achievement now tracks held items --- src/battle-scene.ts | 6 +++++- src/system/achv.ts | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 140774474a6..23b13f1f755 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -87,7 +87,7 @@ import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import PokeballTray from "#app/ui/pokeball-tray"; import InvertPostFX from "#app/pipelines/invert"; import type { Achv } from "#app/system/achv"; -import { achvs, ModifierAchv, MoneyAchv } from "#app/system/achv"; +import { achvs, HeldItemAchv, ModifierAchv, MoneyAchv } from "#app/system/achv"; import type { Voucher } from "#app/system/voucher"; import { vouchers } from "#app/system/voucher"; import { Gender } from "#app/data/gender"; @@ -2698,6 +2698,10 @@ export default class BattleScene extends SceneBase { if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } + + if (pokemon.isPlayer()) { + this.validateAchvs(HeldItemAchv, pokemon); + } } addFormChangeItem(itemId: FormChangeItem, pokemon: Pokemon, ignoreUpdate?: boolean) { diff --git a/src/system/achv.ts b/src/system/achv.ts index 90816ff65c3..d94f05a6c9c 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -1,5 +1,4 @@ import type { Modifier } from "typescript"; -import { TurnHeldItemTransferModifier } from "../modifier/modifier"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; @@ -16,6 +15,8 @@ import type { ConditionFn } from "#app/@types/common"; import { Stat, getShortenedStatKey } from "#app/enums/stat"; import { Challenges } from "#app/enums/challenges"; import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; +import type Pokemon from "#app/field/pokemon"; export enum AchvTier { COMMON, @@ -189,6 +190,19 @@ export class ModifierAchv extends Achv { } } +export class HeldItemAchv extends Achv { + constructor( + localizationKey: string, + name: string, + description: string, + iconImage: string, + score: number, + pokemonFunc: (pokemon: Pokemon) => boolean, + ) { + super(localizationKey, name, description, iconImage, score, (args: any[]) => pokemonFunc(args[0] as Pokemon)); + } +} + export class ChallengeAchv extends Achv { constructor( localizationKey: string, @@ -490,13 +504,13 @@ export const achvs = { 25, ).setSecret(true), SPLICE: new Achv("SPLICE", "", "SPLICE.description", "dna_splicers", 10), - MINI_BLACK_HOLE: new ModifierAchv( + MINI_BLACK_HOLE: new HeldItemAchv( "MINI_BLACK_HOLE", "", "MINI_BLACK_HOLE.description", "mini_black_hole", 25, - modifier => modifier instanceof TurnHeldItemTransferModifier, + pokemon => pokemon.heldItemManager.hasItem(HeldItemId.MINI_BLACK_HOLE), ).setSecret(), CATCH_MYTHICAL: new Achv("CATCH_MYTHICAL", "", "CATCH_MYTHICAL.description", "strange_ball", 50).setSecret(), CATCH_SUB_LEGENDARY: new Achv("CATCH_SUB_LEGENDARY", "", "CATCH_SUB_LEGENDARY.description", "rb", 75).setSecret(), From 3891ef5f8572ca140df35b842a367a229a76643b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:12:14 +0200 Subject: [PATCH 071/114] Removed AttackTypeBoosterModifierRequirement for MEs --- .../encounters/bug-type-superfan-encounter.ts | 56 +++------- .../mystery-encounter-requirements.ts | 102 +++--------------- src/items/held-items/item-steal.ts | 4 +- src/items/held-items/stat-booster.ts | 27 ++++- 4 files changed, 60 insertions(+), 129 deletions(-) diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 4dee937927e..51452422ef9 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -1,6 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -31,26 +30,22 @@ import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler" import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { - AttackTypeBoosterHeldItemTypeRequirement, CombinationPokemonRequirement, HeldItemRequirement, TypeRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { PokemonType } from "#enums/pokemon-type"; -import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { - ContactHeldItemTransferChanceModifier, - GigantamaxAccessModifier, - MegaEvolutionAccessModifier, -} from "#app/modifier/modifier"; +import { GigantamaxAccessModifier, MegaEvolutionAccessModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { allMoves } from "#app/data/data-lists"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/bugTypeSuperfan"; @@ -141,6 +136,8 @@ const POOL_3_POKEMON: { species: SpeciesId; formIndex?: number }[] = [ const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA]; +const REQUIRED_ITEMS = [HeldItemId.QUICK_CLAW, HeldItemId.GRIP_CLAW, HeldItemId.SILVER_POWDER]; + const PHYSICAL_TUTOR_MOVES = [ MoveId.MEGAHORN, MoveId.ATTACK_ORDER, @@ -184,8 +181,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team - new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), - new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1), + new HeldItemRequirement(REQUIRED_ITEMS, 1), new TypeRequirement(PokemonType.BUG, false, 1), ), ) @@ -257,13 +253,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde }, ]; - const requiredItems = [ - generateModifierType(modifierTypes.QUICK_CLAW), - generateModifierType(modifierTypes.GRIP_CLAW), - generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]), - ]; - - const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/"); + const requiredItemString = REQUIRED_ITEMS.map(m => allHeldItems[m].name ?? "unknown").join("/"); encounter.setDialogueToken("requiredBugItems", requiredItemString); return true; @@ -413,8 +403,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Meets one or both of the below reqs - new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), - new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1), + new HeldItemRequirement(REQUIRED_ITEMS, 1), ), ) .withDialogue({ @@ -437,25 +426,19 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(item => { - return ( - (item instanceof BypassSpeedChanceModifier || - item instanceof ContactHeldItemTransferChanceModifier || - (item instanceof AttackTypeBoosterModifier && - (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) && - item.isTransferable - ); + const validItems = pokemon.heldItemManager.getTransferableHeldItems().filter(item => { + item in REQUIRED_ITEMS; }); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("selectedItem", modifier.type.name); + encounter.setDialogueToken("selectedItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -467,12 +450,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const selectableFilter = (pokemon: Pokemon) => { // If pokemon has valid item, it can be selected const hasValidItem = pokemon.getHeldItems().some(item => { - return ( - item instanceof BypassSpeedChanceModifier || - item instanceof ContactHeldItemTransferChanceModifier || - (item instanceof AttackTypeBoosterModifier && - (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG) - ); + item in REQUIRED_ITEMS; }); if (!hasValidItem) { return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; @@ -489,7 +467,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; chosenPokemon.loseHeldItem(modifier, false); - globalScene.updateModifiers(true, true); + globalScene.updateModifiers(true); const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; bugNet.type.tier = ModifierTier.ROGUE; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 88d9ba402a9..f49129e5f87 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -7,8 +7,6 @@ import { StatusEffect } from "#enums/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { WeatherType } from "#enums/weather-type"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; -import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { isNullOrUndefined } from "#app/utils/common"; import type { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -16,6 +14,8 @@ import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; +import type { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; export interface EncounterRequirement { meetsRequirement(): boolean; // Boolean to see if a requirement is met @@ -897,72 +897,13 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { } export class HeldItemRequirement extends EncounterPokemonRequirement { - requiredHeldItemModifiers: string[]; - minNumberOfPokemon: number; - invertQuery: boolean; - requireTransferable: boolean; - - constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) { - super(); - this.minNumberOfPokemon = minNumberOfPokemon; - this.invertQuery = invertQuery; - this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; - this.requireTransferable = requireTransferable; - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon)) { - return false; - } - return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; - } - - override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { - if (!this.invertQuery) { - return partyPokemon.filter(pokemon => - this.requiredHeldItemModifiers.some(heldItem => { - return pokemon.getHeldItems().some(it => { - return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable); - }); - }), - ); - } - // for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers - // E.g. functions as a blacklist - return partyPokemon.filter( - pokemon => - pokemon.getHeldItems().filter(it => { - return ( - !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) && - (!this.requireTransferable || it.isTransferable) - ); - }).length > 0, - ); - } - - override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = pokemon?.getHeldItems().filter(it => { - return ( - this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) && - (!this.requireTransferable || it.isTransferable) - ); - }); - if (requiredItems && requiredItems.length > 0) { - return ["heldItem", requiredItems[0].type.name]; - } - return ["heldItem", ""]; - } -} - -export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement { - requiredHeldItemTypes: PokemonType[]; + requiredHeldItems: HeldItemId[]; minNumberOfPokemon: number; invertQuery: boolean; requireTransferable: boolean; constructor( - heldItemTypes: PokemonType | PokemonType[], + heldItem: HeldItemId | HeldItemId[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true, @@ -970,7 +911,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes]; + this.requiredHeldItems = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requireTransferable = requireTransferable; } @@ -985,14 +926,9 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { return partyPokemon.filter(pokemon => - this.requiredHeldItemTypes.some(heldItemType => { - return pokemon.getHeldItems().some(it => { - return ( - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType && - (!this.requireTransferable || it.isTransferable) - ); - }); + this.requiredHeldItems.some(heldItem => { + pokemon.heldItemManager.hasItem(heldItem) && + (!this.requireTransferable || allHeldItems[heldItem].isTransferable); }), ); } @@ -1000,30 +936,24 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe // E.g. functions as a blacklist return partyPokemon.filter( pokemon => - pokemon.getHeldItems().filter(it => { - return !this.requiredHeldItemTypes.some( - heldItemType => - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType && - (!this.requireTransferable || it.isTransferable), + pokemon.getHeldItems().filter(item => { + return ( + !this.requiredHeldItems.some(heldItem => item === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) ); }).length > 0, ); } override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = pokemon?.getHeldItems().filter(it => { + const requiredItems = pokemon?.getHeldItems().filter(item => { return ( - this.requiredHeldItemTypes.some( - heldItemType => - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType, - ) && - (!this.requireTransferable || it.isTransferable) + this.requiredHeldItems.some(heldItem => item === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) ); }); if (requiredItems && requiredItems.length > 0) { - return ["heldItem", requiredItems[0].type.name]; + return ["heldItem", allHeldItems[requiredItems[0]].name]; } return ["heldItem", ""]; } diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 9f2d292a713..57bf222ff82 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -18,8 +18,8 @@ export interface ITEM_STEAL_PARAMS { /** * Abstract class for held items that steal other Pokemon's items. - * @see {@linkcode TurnHeldItemTransferModifier} - * @see {@linkcode ContactHeldItemTransferChanceModifier} + * @see {@linkcode TurnEndItemStealHeldItem} + * @see {@linkcode ContactItemStealChanceHeldItem} */ export abstract class ItemTransferHeldItem extends HeldItem { /** diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 831d645e807..0674cf83bc2 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -1,7 +1,7 @@ import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; import { HeldItem, ITEM_EFFECT } from "../held-item"; @@ -112,6 +112,23 @@ export class EvolutionStatBoostHeldItem extends StatBoostHeldItem { } } +export type SpeciesStatBoosterItemId = + | typeof HeldItemId.LIGHT_BALL + | typeof HeldItemId.THICK_CLUB + | typeof HeldItemId.METAL_POWDER + | typeof HeldItemId.QUICK_POWDER + | typeof HeldItemId.DEEP_SEA_SCALE + | typeof HeldItemId.DEEP_SEA_TOOTH; + +export const SPECIES_STAT_BOOSTER_ITEMS: SpeciesStatBoosterItemId[] = [ + HeldItemId.LIGHT_BALL, + HeldItemId.THICK_CLUB, + HeldItemId.METAL_POWDER, + HeldItemId.QUICK_POWDER, + HeldItemId.DEEP_SEA_SCALE, + HeldItemId.DEEP_SEA_TOOTH, +]; + /** * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a * multiplier if the holder is of a specific {@linkcode SpeciesId}. @@ -122,7 +139,13 @@ export class SpeciesStatBoostHeldItem extends StatBoostHeldItem { /** The species that the held item's stat boost(s) apply to */ private species: SpeciesId[]; - constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[], multiplier: number, species: SpeciesId[]) { + constructor( + type: SpeciesStatBoosterItemId, + maxStackCount = 1, + stats: Stat[], + multiplier: number, + species: SpeciesId[], + ) { super(type, maxStackCount, stats, multiplier); this.species = species; } From 29af976c49daaa07c0a15a3e222081e4ef8d595c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:25:13 +0200 Subject: [PATCH 072/114] Fixed Pickup --- src/battle.ts | 19 ++++--------------- src/data/abilities/ability.ts | 10 ++++------ src/field/pokemon-held-item-manager.ts | 9 +++++++-- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 2ebfb634751..d7a58dba40b 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -12,7 +12,7 @@ import { } from "#app/utils/common"; import Trainer, { TrainerVariant } from "./field/trainer"; import type { GameMode } from "./game-mode"; -import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; +import { MoneyMultiplierModifier } from "./modifier/modifier"; import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -33,6 +33,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; +import type { HeldItemId } from "#enums/held-item-id"; export enum BattlerIndex { ATTACKER = -1, @@ -77,7 +78,7 @@ export default class Battle { public turnCommands: TurnCommands; public playerParticipantIds: Set = new Set(); public battleScore = 0; - public postBattleLoot: PokemonHeldItemModifier[] = []; + public postBattleLoot: HeldItemId[] = []; public escapeAttempts = 0; public lastMove: MoveId; public battleSeed: string = randomString(16, true); @@ -176,19 +177,7 @@ export default class Battle { } addPostBattleLoot(enemyPokemon: EnemyPokemon): void { - this.postBattleLoot.push( - ...globalScene - .findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, - false, - ) - .map(i => { - const ret = i as PokemonHeldItemModifier; - //@ts-ignore - this is awful to fix/change - ret.pokemonId = null; - return ret; - }), - ); + this.postBattleLoot.push(...enemyPokemon.getHeldItems()); } pickUpScatteredMoney(): void { diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index cbeb66aea56..203a662767a 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -35,7 +35,6 @@ import { } from "#app/data/moves/move"; import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { BerryModifier, type PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; import { SpeciesFormChangeAbilityTrigger, @@ -44,7 +43,6 @@ import { } from "#app/data/pokemon-forms"; import i18next from "i18next"; import { Command } from "#app/ui/command-ui-handler"; -import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "#app/data/pokeball"; import { BattleType } from "#enums/battle-type"; import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; @@ -6203,13 +6201,13 @@ export class PostBattleAbAttr extends AbAttr { } export class PostBattleLootAbAttr extends PostBattleAbAttr { - private randItem?: PokemonHeldItemModifier; + private randItem?: HeldItemId; override canApplyPostBattle(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!simulated && postBattleLoot.length && args[0]) { this.randItem = randSeedItem(postBattleLoot); - return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1); + return pokemon.heldItemManager.getStack(this.randItem) < allHeldItems[this.randItem].maxStackCount; } return false; } @@ -6223,12 +6221,12 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { this.randItem = randSeedItem(postBattleLoot); } - if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { + if (pokemon.heldItemManager.add(this.randItem)) { postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - itemName: this.randItem.type.name, + itemName: allHeldItems[this.randItem].name, }), ); } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 59423e83c1e..f71d3d73a90 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -73,16 +73,21 @@ export class PokemonItemManager { return item ? item.stack : 0; } - add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA) { + add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA): boolean { const maxStack = allHeldItems[itemType].getMaxStackCount(); const item = this.heldItems[itemType]; if (item) { // TODO: We may want an error message of some kind instead - item.stack = Math.min(item.stack + addStack, maxStack); + if (item.stack < maxStack) { + item.stack = Math.min(item.stack + addStack, maxStack); + return true; + } } else { this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data }; + return true; } + return false; } remove(itemType: HeldItemId, removeStack = 1, all = false) { From 215ce632ef18f5da140b7af2d05cc3295f0109e9 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:53:44 +0200 Subject: [PATCH 073/114] Fixing (most) berry usage --- src/battle-scene.ts | 1 - src/data/abilities/ability.ts | 39 +++++++++++++---------------------- src/enums/held-item-id.ts | 28 +++++++++++++++++++++++-- src/events/battle-scene.ts | 14 +++++++------ src/items/all-held-items.ts | 6 +----- src/items/held-items/berry.ts | 17 +++++---------- src/phases/berry-phase.ts | 22 +++++++------------- src/ui/battle-flyout.ts | 4 ++-- 8 files changed, 64 insertions(+), 67 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 23b13f1f755..467886b25c2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -57,7 +57,6 @@ import { getModifierPoolForType, getPartyLuckValue, ModifierPoolType, - PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; import AbilityBar from "#app/ui/ability-bar"; import { diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 203a662767a..4bf6ce34762 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -88,8 +88,9 @@ import type { BattlerIndex } from "#app/battle"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; -import { HeldItemId } from "#enums/held-item-id"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; import { allHeldItems } from "#app/items/all-held-items"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; export class BlockRecoilDamageAttr extends AbAttr { constructor() { @@ -5432,10 +5433,14 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { // Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped) const cappedBerries = new Set( - globalScene - .getModifiers(BerryModifier, pokemon.isPlayer()) - .filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1) - .map(bm => bm.berryType), + pokemon + .getHeldItems() + .filter( + bm => + isItemInCategory(bm, HeldItemCategoryId.BERRY) && + pokemon.heldItemManager.getStack(bm) < allHeldItems[bm].maxStackCount, + ) + .map(bm => allHeldItems[bm].berryType), ); this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt)); @@ -5465,30 +5470,15 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { const randomIdx = randSeedInt(this.berriesUnderCap.length); const chosenBerryType = this.berriesUnderCap[randomIdx]; pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory - const chosenBerry = new BerryModifierType(chosenBerryType); + const chosenBerry = berryTypeToHeldItem[chosenBerryType]; - // Add the randomly chosen berry or update the existing one - const berryModifier = globalScene.findModifier( - m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id, - pokemon.isPlayer(), - ) as BerryModifier | undefined; - - if (berryModifier) { - berryModifier.stackCount++; - } else { - const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1); - if (pokemon.isPlayer()) { - globalScene.addModifier(newBerry); - } else { - globalScene.addEnemyModifier(newBerry); - } - } + pokemon.heldItemManager.add(chosenBerry); globalScene.updateModifiers(pokemon.isPlayer()); globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - berryName: chosenBerry.name, + berryName: allHeldItems[chosenBerry].name, }), ); return true; @@ -5537,8 +5527,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { // This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed. for (const berryType of pokemon.summonData.berriesEatenLast) { getBerryEffectFunc(berryType)(pokemon); - const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1); - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message + globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, berryType)); // trigger message } // uncomment to make cheek pouch work with cud chew diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index cff6151ada7..9991e0fe15a 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -87,7 +87,7 @@ export const HeldItemId = { MACHO_BRACE: 0x0909, // Evo trackers - GIMMIGHOUL_EVO_TRACKER: 0x0a01, + GIMMIGHOUL_EVO_TRACKER: 0x0A01, }; export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; @@ -102,4 +102,28 @@ export const HeldItemNames: Record = Object.entries return acc; }, {} as Record -); \ No newline at end of file +); + + +export const HeldItemCategoryId = { + NONE: 0x0000, + BERRY: 0x0100, + CONSUMABLE: 0x0200, + TYPE_ATTACK_BOOSTER: 0x0300, + STAT_BOOSTER: 0x0400, + CRIT_BOOSTER: 0x0500, + GAIN_INCREASE: 0x0600, + UNIQUE: 0x0700, + BASE_STAT_BOOST: 0x0900, + EVO_TRACKER: 0x0A00, +}; + +export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldItemCategoryId]; + +function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { + return itemId & 0xFF00; +} + +export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { + return getHeldItemCategory(itemId) === category; +} \ No newline at end of file diff --git a/src/events/battle-scene.ts b/src/events/battle-scene.ts index 83d260bd7d2..7e454d813e2 100644 --- a/src/events/battle-scene.ts +++ b/src/events/battle-scene.ts @@ -1,5 +1,6 @@ +import type Pokemon from "#app/field/pokemon"; +import type { BerryType } from "#enums/berry-type"; import type Move from "../data/moves/move"; -import type { BerryModifier } from "../modifier/modifier"; /** Alias for all {@linkcode BattleScene} events */ export enum BattleSceneEventType { @@ -81,12 +82,13 @@ export class MoveUsedEvent extends Event { * @extends Event */ export class BerryUsedEvent extends Event { - /** The {@linkcode BerryModifier} being used */ - public berryModifier: BerryModifier; - constructor(berry: BerryModifier) { + /** The {@linkcode BerryType} being used */ + public pokemon: Pokemon; + public berryType: BerryType; + constructor(pokemon: Pokemon, berryType: BerryType) { super(BattleSceneEventType.BERRY_USED); - - this.berryModifier = berry; + this.pokemon = pokemon; + this.berryType = berryType; } } diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index fab4c4bb5cc..5cbc2a8e344 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -153,11 +153,7 @@ export function initHeldItems() { .unstealable() .untransferable() .unsuppressable(); - allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1, [ - Stat.HP, - Stat.ATK, - Stat.DEF, - ]) + allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1) .unstealable() .untransferable() .unsuppressable(); diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index eb6da1092bd..e3f6dfd0962 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -1,4 +1,5 @@ import { getBerryEffectDescription, getBerryEffectFunc, getBerryName } from "#app/data/berry"; +import { BerryUsedEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { ConsumableHeldItem, ITEM_EFFECT } from "#app/items/held-item"; @@ -26,10 +27,8 @@ export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { }; export interface BERRY_PARAMS { - /** The pokemon with the item */ + /** The pokemon with the berry */ pokemon: Pokemon; - /** Whether the move was used by a player pokemon */ - isPlayer: boolean; } // TODO: Maybe split up into subclasses? @@ -72,7 +71,6 @@ export class BerryHeldItem extends ConsumableHeldItem { */ apply(params: BERRY_PARAMS): boolean { const pokemon = params.pokemon; - const isPlayer = params.isPlayer; const preserve = new BooleanHolder(false); globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); @@ -80,20 +78,15 @@ export class BerryHeldItem extends ConsumableHeldItem { // munch the berry and trigger unburden-like effects getBerryEffectFunc(this.berryType)(pokemon); - this.consume(pokemon, isPlayer, consumed); + this.consume(pokemon, pokemon.isPlayer(), consumed); // TODO: Update this method to work with held items // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. // Don't recover it if we proc berry pouch (no item duplication) pokemon.recordEatenBerry(this.berryType, consumed); + globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, this.berryType)); + return true; } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { - return 2; - } - return 3; - } } diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 6e40e299e7c..3d9e87e3dda 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -5,14 +5,15 @@ import { RepeatBerryNextTurnAbAttr, } from "#app/data/abilities/ability"; import { CommonAnim } from "#app/data/battle-anims"; -import { BerryUsedEvent } from "#app/events/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BerryModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import type Pokemon from "#app/field/pokemon"; +import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; +import { ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; /** * The phase after attacks where the pokemon eat berries. @@ -36,10 +37,10 @@ export class BerryPhase extends FieldPhase { * @param pokemon - The {@linkcode Pokemon} to check */ eatBerries(pokemon: Pokemon): void { - const hasUsableBerry = !!globalScene.findModifier( - m => m instanceof BerryModifier && m.shouldApply(pokemon), - pokemon.isPlayer(), - ); + const hasUsableBerry = pokemon.getHeldItems().some(m => { + //TODO: This is bugged, must fix the .shouldApply() function + isItemInCategory(m, HeldItemCategoryId.BERRY) && allHeldItems[m].shouldApply(pokemon); + }); if (!hasUsableBerry) { return; @@ -64,14 +65,7 @@ export class BerryPhase extends FieldPhase { CommonAnim.USE_ITEM, ); - for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { - // No need to track berries being eaten; already done inside applyModifiers - if (berryModifier.consumed) { - berryModifier.consumed = false; - pokemon.loseHeldItem(berryModifier); - } - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); - } + applyHeldItems(ITEM_EFFECT.BERRY, { pokemon: pokemon }); globalScene.updateModifiers(pokemon.isPlayer()); // AbilityId.CHEEK_POUCH only works once per round of nom noms diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 9a2180eccee..7356666e6c5 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -177,8 +177,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { const berryUsedEvent = event as BerryUsedEvent; if ( !berryUsedEvent || - berryUsedEvent.berryModifier.pokemonId !== this.pokemon?.id || - berryUsedEvent.berryModifier.berryType !== BerryType.LEPPA + berryUsedEvent.pokemon.id !== this.pokemon?.id || + berryUsedEvent.berryType !== BerryType.LEPPA ) { // We only care about Leppa berries return; From dccb0cf691a1295bedaed97c140044d48e97722b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:53:47 +0200 Subject: [PATCH 074/114] Using Berry held items in move.ts --- src/data/moves/move.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 94c9da520fe..a9dd3644415 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -68,7 +68,6 @@ import { } from "../abilities/ability"; import { allAbilities, allMoves } from "../data-lists"; import { - BerryModifier, PokemonHeldItemModifier, PreserveBerryModifier, } from "../../modifier/modifier"; @@ -124,7 +123,7 @@ import { SelectBiomePhase } from "#app/phases/select-biome-phase"; import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; import { ITEM_EFFECT } from "#app/items/held-item"; import { berryTypeToHeldItem } from "#app/items/held-items/berry"; -import { HeldItemId } from "#enums/held-item-id"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -2683,7 +2682,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { * Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks */ export class EatBerryAttr extends MoveEffectAttr { - protected chosenBerry: BerryModifier; + protected chosenBerry: HeldItemId; constructor(selfTarget: boolean) { super(selfTarget); } @@ -2723,9 +2722,8 @@ export class EatBerryAttr extends MoveEffectAttr { return true; } - getTargetHeldBerries(target: Pokemon): BerryModifier[] { - return globalScene.findModifiers(m => m instanceof BerryModifier - && (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[]; + getTargetHeldBerries(target: Pokemon): HeldItemId[] { + return target.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); } reduceBerryModifier(target: Pokemon) { @@ -2746,10 +2744,10 @@ export class EatBerryAttr extends MoveEffectAttr { */ protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { // consumer eats berry, owner triggers unburden and similar effects - getBerryEffectFunc(this.chosenBerry.berryType)(consumer); + getBerryEffectFunc(allHeldItems[this.chosenBerry].berryType)(consumer); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false); applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); - consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); + consumer.recordEatenBerry(allHeldItems[this.chosenBerry].berryType, updateHarvest); } } @@ -2788,7 +2786,7 @@ export class StealEatBerryAttr extends EatBerryAttr { // pick a random berry and eat it this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); - const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); + const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name }); globalScene.phaseManager.queueMessage(message); this.reduceBerryModifier(target); this.eatBerry(user, target); @@ -10586,7 +10584,7 @@ export function initMoves() { .attr(EatBerryAttr, true) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .condition((user) => { - const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); + const userBerries = user.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); return userBerries.length > 0; }) .edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki From d02d70e6ab92ea9e199a4fe3360c73a8cbd35967 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:55:41 +0200 Subject: [PATCH 075/114] Split up vitamins from the rest of stat boosting items --- src/enums/held-item-id.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 9991e0fe15a..a31f0c9d4f6 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -71,17 +71,17 @@ export const HeldItemId = { FLAME_ORB: 0x070C, SOUL_DEW: 0x070D, BATON: 0x070E, + MINI_BLACK_HOLE: 0x070F, - // Mini Black Hole - MINI_BLACK_HOLE: 0x0801, + // Vitamins + HP_UP: 0x0801, + PROTEIN: 0x0802, + IRON: 0x0803, + CALCIUM: 0x0804, + ZINC: 0x0805, + CARBOS: 0x0806, - // Stat boosting items - HP_UP: 0x0901, - PROTEIN: 0x0902, - IRON: 0x0903, - CALCIUM: 0x0904, - ZINC: 0x0905, - CARBOS: 0x0906, + // Other stat boosting items SHUCKLE_JUICE: 0x0907, OLD_GATEAU: 0x0908, MACHO_BRACE: 0x0909, @@ -114,6 +114,7 @@ export const HeldItemCategoryId = { CRIT_BOOSTER: 0x0500, GAIN_INCREASE: 0x0600, UNIQUE: 0x0700, + VITAMIN: 0x0800, BASE_STAT_BOOST: 0x0900, EVO_TRACKER: 0x0A00, }; From b975382f73265af584b3b4e387e14b0d7288cc93 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:12:27 +0200 Subject: [PATCH 076/114] Fixed form change trigger after merge conflicts --- src/battle-scene.ts | 2 +- src/data/pokemon-forms/form-change-triggers.ts | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 48c3dba0f46..397663d0a60 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -101,7 +101,7 @@ import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change- import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers"; -import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/data/pokemon-forms/form-change-triggers.ts b/src/data/pokemon-forms/form-change-triggers.ts index eb2c0a557c2..93e2bdd5ff5 100644 --- a/src/data/pokemon-forms/form-change-triggers.ts +++ b/src/data/pokemon-forms/form-change-triggers.ts @@ -3,7 +3,6 @@ import type { Constructor } from "#app/utils/common"; import type { TimeOfDay } from "#enums/time-of-day"; import type Pokemon from "#app/field/pokemon"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { getPokemonNameWithAffix } from "#app/messages"; import { globalScene } from "#app/global-scene"; import { FormChangeItem } from "#enums/form-change-item"; @@ -77,16 +76,11 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { } canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier(r => { - // Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier - const m = r as PokemonFormChangeItemModifier; - return ( - "formChangeItem" in m && - m.pokemonId === pokemon.id && - m.formChangeItem === this.item && - m.active === this.active - ); - }); + const matchItem = pokemon.heldItemManager.formChangeItems[this.item]; + if (!matchItem) { + return false; + } + return matchItem.active === this.active; } } From 6a4e4a345d650c8aa7eb4320f544f0d8c465ae2a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 21:56:57 +0200 Subject: [PATCH 077/114] Added some utility functions to check if an item fits a list of items/categories, or to filter out which held items fit --- src/enums/held-item-id.ts | 15 +++++++++++++-- src/field/pokemon-held-item-manager.ts | 9 +++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index a31f0c9d4f6..7da668d21b2 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -121,10 +121,21 @@ export const HeldItemCategoryId = { export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldItemCategoryId]; +const ITEM_CATEGORY_MASK = 0xFF00 + function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { - return itemId & 0xFF00; + return itemId & ITEM_CATEGORY_MASK; } export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { return getHeldItemCategory(itemId) === category; -} \ No newline at end of file +} + +export function isItemInRequested( + itemId: HeldItemId, + requestedItems: (HeldItemCategoryId | HeldItemId)[] +): boolean { + return requestedItems.some(entry => { + itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry + }); +} diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index f71d3d73a90..6c66c219369 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,6 +1,6 @@ import { allHeldItems } from "#app/items/all-held-items"; -import type { HeldItemId } from "#app/enums/held-item-id"; -import type { FormChangeItem } from "#app/data/pokemon-forms"; +import { isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; +import type { FormChangeItem } from "#enums/form-change-item"; 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"; @@ -102,6 +102,11 @@ export class PokemonItemManager { } } + filterRequestedItems(requestedItems: (HeldItemCategoryId | HeldItemId)[], transferableOnly = true, exclude = false) { + const currentItems = transferableOnly ? this.getTransferableHeldItems() : this.getHeldItems(); + return currentItems.filter(it => !exclude && isItemInRequested(it, requestedItems)); + } + addFormChangeItem(id: FormChangeItem) { if (!(id in this.formChangeItems)) { this.formChangeItems[id] = { active: false }; From 4dc54ba17e6a45d147b70520b8ae50731e658ff5 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 21:57:53 +0200 Subject: [PATCH 078/114] Fixed delibirdy encounter --- .../encounters/delibirdy-encounter.ts | 110 ++++++------------ 1 file changed, 35 insertions(+), 75 deletions(-) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 40893d93930..00d36eec553 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -14,20 +14,16 @@ import { selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { - BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier, } from "#app/modifier/modifier"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -37,24 +33,34 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { timedEventManager } from "#app/global-event-manager"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/delibirdy"; /** Berries only */ -const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"]; +const OPTION_2_ALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; /** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */ -const OPTION_3_DISALLOWED_MODIFIERS = [ - "BerryModifier", - "PokemonInstantReviveModifier", - "TerastallizeModifier", - "PokemonBaseStatModifier", - "PokemonBaseStatTotalModifier", -]; +const OPTION_3_DISALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; +async function backupOption() { + globalScene.getPlayerPokemon()?.heldItemManager.add(HeldItemId.SHELL_BELL); + globalScene.playSound("item_fanfare"); + await showEncounterText( + i18next.t("battle:rewardGain", { + modifierName: allHeldItems[HeldItemId.SHELL_BELL].name, + }), + null, + undefined, + true, + ); + doEventReward(); +} + const doEventReward = () => { const event_buff = timedEventManager.getDelibirdyBuff(); if (event_buff.length > 0) { @@ -169,16 +175,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { modifierName: shellBell.name }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN); doEventReward(); @@ -205,19 +202,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable; - }); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_MODIFIERS, true); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); + encounter.setDialogueToken("chosenItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -240,11 +235,11 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier; + const chosenItem: HeldItemId = encounter.misc.chosenItem; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed - if (modifier instanceof BerryModifier) { + if (isItemInCategory(chosenItem, HeldItemCategoryId.BERRY)) { // Check if the player has max stacks of that Candy Jar already const existing = globalScene.findModifier( m => m instanceof LevelIncrementBoosterModifier, @@ -252,18 +247,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { - modifierName: shellBell.name, - }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR); doEventReward(); @@ -274,25 +258,14 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { - modifierName: shellBell.name, - }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH); doEventReward(); } } - chosenPokemon.loseHeldItem(modifier, false); + chosenPokemon.loseHeldItem(chosenItem, false); leaveEncounterWithoutBattle(true); }) @@ -315,21 +288,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return ( - !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable - ); - }); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_MODIFIERS, true, true); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); + encounter.setDialogueToken("chosenItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -360,16 +329,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { modifierName: shellBell.name }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM); doEventReward(); From d7882d4ca7f0a3691edfd05294d35c4d5675da05 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:50:27 +0200 Subject: [PATCH 079/114] Various fixes --- src/data/balance/pokemon-evolutions.ts | 13 +- .../utils/encounter-pokemon-utils.ts | 9 +- src/data/pokemon-forms.ts | 1 + src/field/pokemon-held-item-manager.ts | 10 +- src/items/held-items/bypass-speed-chance.ts | 2 +- src/modifier/modifier-type.ts | 5 +- src/modifier/modifier.ts | 168 ------------------ src/ui/party-ui-handler.ts | 3 +- 8 files changed, 26 insertions(+), 185 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 298cf2d0719..d5f8ff18870 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -11,11 +11,11 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; -import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, SpeciesStatBoosterModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; -import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; import { initI18n } from "#app/plugins/i18n"; +import { allHeldItems } from "#app/items/all-held-items"; +import { HeldItemId } from "#enums/held-item-id"; export enum SpeciesWildEvolutionDelay { NONE, @@ -274,10 +274,7 @@ class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { class TreasureEvolutionCondition extends SpeciesEvolutionCondition { constructor() { - super(p => p.evoCounter - + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9); + super(p => allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER].getStackCount(p) > 9); this.description = i18next.t("pokemonEvolutions:treasure"); } } @@ -1794,8 +1791,8 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.CLAMPERL]: [ // TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.heldItemManager.hasItem(HeldItemId.DEEP_SEA_TOOTH)), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.heldItemManager.hasItem(HeldItemId.DEEP_SEA_SCALE)), SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index e8a3db46cff..6d7366ea79e 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -28,7 +28,6 @@ import { showEncounterText, } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Gender } from "#app/data/gender"; import type { PermanentStat } from "#enums/stat"; @@ -37,6 +36,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { AbilityId } from "#enums/ability-id"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; +import type { HeldItemId } from "#enums/held-item-id"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -423,6 +423,13 @@ export async function applyModifierTypeToPlayerPokemon( globalScene.addModifier(modifier, false, false, false, true); } +export function applyHeldItemWithFallback(pokemon: Pokemon, item: HeldItemId, fallbackItem?: HeldItemId) { + const added = pokemon.heldItemManager.add(item); + if (!added && fallbackItem) { + pokemon.heldItemManager.add(fallbackItem); + } +} + /** * Alternative to using AttemptCapturePhase * Assumes player sprite is visible on the screen (this is intended for non-combat uses) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index f34a589e893..b21bf78a801 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -25,6 +25,7 @@ import { type SpeciesFormChangeTrigger, SpeciesFormChangeWeatherTrigger, } from "./pokemon-forms/form-change-triggers"; +import i18next from "i18next"; export function formChangeItemName(id: FormChangeItem) { return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[id]}`); diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 6c66c219369..340347317d9 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -8,12 +8,12 @@ type HELD_ITEM_DATA = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; interface HeldItemProperties { stack: number; - disabled: boolean; + disabled?: boolean; cooldown?: number; data?: HELD_ITEM_DATA; } -type HeldItemPropertyMap = { +export type HeldItemPropertyMap = { [key in HeldItemId]?: HeldItemProperties; }; @@ -21,7 +21,7 @@ interface FormChangeItemProperties { active: boolean; } -type FormChangeItemPropertyMap = { +export type FormChangeItemPropertyMap = { [key in FormChangeItem]?: FormChangeItemProperties; }; @@ -73,6 +73,10 @@ export class PokemonItemManager { return item ? item.stack : 0; } + overrideItems(newItems: HeldItemPropertyMap) { + this.heldItems = newItems; + } + add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA): boolean { const maxStack = allHeldItems[itemType].getMaxStackCount(); const item = this.heldItems[itemType]; diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index 40fc65593ff..6c1b2aba73e 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -4,7 +4,7 @@ import type { BooleanHolder } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; export interface BYPASS_SPEED_CHANCE_PARAMS { /** The pokemon with the item */ diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 679b79cd4b0..2ca6f1d826b 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -46,7 +46,6 @@ import { MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, - PokemonFormChangeItemModifier, type PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonLevelIncrementModifier, @@ -1099,8 +1098,8 @@ export class FormChangeItemReward extends PokemonModifierType { constructor(formChangeItem: FormChangeItem) { super( "", - FormChangeItem[formChangeItem].toLowerCase(), - (_type, args) => new PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true), + "", + () => null, (pokemon: PlayerPokemon) => { // Make sure the Pokemon has alternate forms if ( diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 5ebaf1bf6df..9b5932bdbda 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,8 +1,6 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getLevelTotalExp } from "#app/data/exp"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import type { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectHealText } from "#app/data/status-effect"; import type Pokemon from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon"; @@ -10,7 +8,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { LearnMoveType } from "#enums/learn-move-type"; import type { VoucherType } from "#app/system/voucher"; -import { Command } from "#enums/command"; import { addTextObject, TextStyle } from "#app/ui/text"; import { type BooleanHolder, @@ -31,7 +28,6 @@ import i18next from "i18next"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, - type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type TerastallizeModifierType, @@ -546,107 +542,6 @@ export class TerastallizeAccessModifier extends PersistentModifier { } } -export abstract class PokemonHeldItemModifier extends PersistentModifier { - /** The ID of the {@linkcode Pokemon} that this item belongs to. */ - public pokemonId: number; - /** Whether this item can be transfered to or stolen by another Pokemon. */ - public isTransferable = true; - - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, stackCount); - - this.pokemonId = pokemonId; - } - - abstract matchType(_modifier: Modifier): boolean; - - match(modifier: Modifier) { - return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId; - } - - getArgs(): any[] { - return [this.pokemonId]; - } - - /** - * Applies the {@linkcode PokemonHeldItemModifier} to the given {@linkcode Pokemon}. - * @param pokemon The {@linkcode Pokemon} that holds the held item - * @param args additional parameters - */ - abstract override apply(pokemon: Pokemon, ...args: unknown[]): boolean; - - /** - * Checks if {@linkcode PokemonHeldItemModifier} should be applied. - * @param pokemon The {@linkcode Pokemon} that holds the item - * @param _args N/A - * @returns if {@linkcode PokemonHeldItemModifier} should be applied - */ - override shouldApply(pokemon?: Pokemon, ..._args: unknown[]): boolean { - return !!pokemon && (this.pokemonId === -1 || pokemon.id === this.pokemonId); - } - - isIconVisible(): boolean { - return !!this.getPokemon()?.isOnField(); - } - - getIcon(forSummary?: boolean): Phaser.GameObjects.Container { - const container = !forSummary ? globalScene.add.container(0, 0) : super.getIcon(); - - if (!forSummary) { - const pokemon = this.getPokemon(); - if (pokemon) { - const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); - container.add(pokemonIcon); - container.setName(pokemon.id.toString()); - } - - const item = globalScene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); - item.setScale(0.5); - item.setOrigin(0, 0.5); - item.setTexture("items", this.type.getIcon()); - container.add(item); - - const stackText = this.getIconStackText(); - if (stackText) { - container.add(stackText); - } - - const virtualStackText = this.getIconStackText(true); - if (virtualStackText) { - container.add(virtualStackText); - } - } else { - container.setScale(0.5); - } - - return container; - } - - getPokemon(): Pokemon | undefined { - return this.pokemonId ? (globalScene.getPokemonById(this.pokemonId) ?? undefined) : undefined; - } - - getScoreMultiplier(): number { - return 1; - } - - getMaxStackCount(forThreshold?: boolean): number { - const pokemon = this.getPokemon(); - if (!pokemon) { - return 0; - } - if (pokemon.isPlayer() && forThreshold) { - return globalScene - .getPlayerParty() - .map(p => this.getMaxHeldItemCount(p)) - .reduce((stackCount: number, maxStackCount: number) => Math.max(stackCount, maxStackCount), 0); - } - return this.getMaxHeldItemCount(pokemon); - } - - abstract getMaxHeldItemCount(pokemon?: Pokemon): number; -} - export class LevelIncrementBoosterModifier extends PersistentModifier { match(modifier: Modifier) { return modifier instanceof LevelIncrementBoosterModifier; @@ -1254,69 +1149,6 @@ export class ExpBalanceModifier extends PersistentModifier { } } -export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { - public override type: FormChangeItemModifierType; - public formChangeItem: FormChangeItem; - public active: boolean; - public isTransferable = false; - - constructor( - type: FormChangeItemModifierType, - pokemonId: number, - formChangeItem: FormChangeItem, - active: boolean, - stackCount?: number, - ) { - super(type, pokemonId, stackCount); - this.formChangeItem = formChangeItem; - this.active = active; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonFormChangeItemModifier && modifier.formChangeItem === this.formChangeItem; - } - - clone(): PersistentModifier { - return new PokemonFormChangeItemModifier( - this.type, - this.pokemonId, - this.formChangeItem, - this.active, - this.stackCount, - ); - } - - getArgs(): any[] { - return super.getArgs().concat(this.formChangeItem, this.active); - } - - /** - * Applies {@linkcode PokemonFormChangeItemModifier} - * @param pokemon The {@linkcode Pokemon} to apply the form change item to - * @param active `true` if the form change item is active - * @returns `true` if the form change item was applied - */ - override apply(pokemon: Pokemon, active: boolean): boolean { - const switchActive = this.active && !active; - - if (switchActive) { - this.active = false; - } - - const ret = globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); - - if (switchActive) { - this.active = true; - } - - return ret; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - export class MoneyRewardModifier extends ConsumableModifier { private moneyMultiplier: number; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 03cca175b6f..772d32d6c46 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -14,7 +14,7 @@ import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; -import { SpeciesFormChangeItemTrigger, formChangeItemName } from "#app/data/pokemon-forms/form-change-triggers"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; @@ -29,6 +29,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; import { HeldItemId } from "#enums/held-item-id"; +import { formChangeItemName } from "#app/data/pokemon-forms"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); From c198297abd099ed58fd1c9595d058ea874ea454f Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:51:25 +0200 Subject: [PATCH 080/114] Reworked EnemyPokemonConfig to include a HeldItemProperty object (to feed to the heldItemManager). Updated Dark Deal ME --- src/battle-scene.ts | 24 ++++--------------- .../encounters/dark-deal-encounter.ts | 16 ++++--------- .../utils/encounter-phase-utils.ts | 8 +++---- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 397663d0a60..fe17d349903 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -30,7 +30,6 @@ import { HealingBoosterModifier, MultipleParticipantExpBonusModifier, PersistentModifier, - PokemonHeldItemModifier, PokemonHpRestoreModifier, RememberMoveModifier, } from "./modifier/modifier"; @@ -150,7 +149,6 @@ import { import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -167,6 +165,7 @@ import { allHeldItems, applyHeldItems } from "./items/all-held-items"; import { ITEM_EFFECT } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; import { HeldItemId } from "#enums/held-item-id"; +import type { HeldItemPropertyMap } from "./field/pokemon-held-item-manager"; const DEBUG_RNG = false; @@ -2088,9 +2087,7 @@ export default class BattleScene extends SceneBase { enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: number, total: number) => (total += iv), 0) / 93) * 0.2 + 0.8); - this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map( - m => (scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()), - ); + enemy.getHeldItems().map(m => (scoreIncrease *= allHeldItems[m].getScoreMultiplier())); if (enemy.isBoss()) { scoreIncrease *= Math.sqrt(enemy.bossSegments); } @@ -2806,7 +2803,7 @@ export default class BattleScene extends SceneBase { return countTaken > 0; } - generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise { + generateEnemyModifiers(heldItemConfigs?: HeldItemPropertyMap[]): Promise { return new Promise(resolve => { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return resolve(); @@ -2828,19 +2825,8 @@ export default class BattleScene extends SceneBase { } party.forEach((enemyPokemon: EnemyPokemon, i: number) => { - if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) { - for (const mt of heldModifiersConfigs[i]) { - let modifier: PokemonHeldItemModifier; - if (mt.modifier instanceof PokemonHeldItemModifierType) { - modifier = mt.modifier.newModifier(enemyPokemon); - } else { - modifier = mt.modifier as PokemonHeldItemModifier; - modifier.pokemonId = enemyPokemon.id; - } - modifier.stackCount = mt.stackCount ?? 1; - modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; - this.addEnemyModifier(modifier, true); - } + if (heldItemConfigs && i < heldItemConfigs.length && heldItemConfigs[i]) { + enemyPokemon.heldItemManager.overrideItems(heldItemConfigs[i]); } else { const isBoss = enemyPokemon.isBoss() || diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 6474df3570e..451f62765b0 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -16,10 +16,9 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { Challenges } from "#enums/challenges"; +import type { HeldItemPropertyMap } from "#app/field/pokemon-held-item-manager"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/darkDeal"; @@ -149,7 +148,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE const removedPokemon = getRandomPlayerPokemon(true, false, true); // Get all the pokemon's held items - const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); + const itemConfig = removedPokemon.heldItemManager.heldItems; globalScene.removePokemonFromPlayerParty(removedPokemon); const encounter = globalScene.currentBattle.mysteryEncounter!; @@ -158,7 +157,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE // Store removed pokemon types encounter.misc = { removedTypes: removedPokemon.getTypes(), - modifiers, + itemConfig: itemConfig, }; }) .withOptionPhase(async () => { @@ -176,7 +175,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType); } - const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; + const bossItemConfig: HeldItemPropertyMap = encounter.misc.itemConfig; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10]; @@ -184,12 +183,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, - modifierConfigs: bossModifiers.map(m => { - return { - modifier: m, - stackCount: m.getStackCount(), - }; - }), + heldItemConfig: bossItemConfig, }; if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) { pokemonConfig.formIndex = 0; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 69984229681..8edbf78983b 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -48,7 +48,6 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import type { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; @@ -57,6 +56,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; import { timedEventManager } from "#app/global-event-manager"; +import type { HeldItemPropertyMap } from "#app/field/pokemon-held-item-manager"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -106,7 +106,7 @@ export interface EnemyPokemonConfig { /** Can set just the status, or pass a timer on the status turns */ status?: StatusEffect | [StatusEffect, number]; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; - modifierConfigs?: HeldModifierConfig[]; + heldItemConfig?: HeldItemPropertyMap; tags?: BattlerTagType[]; dataSource?: PokemonData; tera?: PokemonType; @@ -438,8 +438,8 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, ); const customModifierTypes = partyConfig?.pokemonConfigs - ?.filter(config => config?.modifierConfigs) - .map(config => config.modifierConfigs!); + ?.filter(config => config?.heldItemConfig) + .map(config => config.heldItemConfig!); globalScene.generateEnemyModifiers(customModifierTypes); } } From 64b1cf1669766a8a895ad1ac8a025fb266bf36dd Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:29:29 +0200 Subject: [PATCH 081/114] More various fixes; introduced isMaxStack(item) method to heldItemManager --- src/data/moves/move.ts | 13 +--- src/field/pokemon-held-item-manager.ts | 5 ++ src/field/pokemon.ts | 13 ++-- src/ui/party-ui-handler.ts | 86 +++++++++----------------- 4 files changed, 42 insertions(+), 75 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index d22ff2a1b72..d943b277801 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -67,7 +67,6 @@ import { } from "../abilities/ability"; import { allAbilities, allMoves } from "../data-lists"; import { - PokemonHeldItemModifier, PreserveBerryModifier, } from "../../modifier/modifier"; import type { BattlerIndex } from "#enums/battler-index"; @@ -118,7 +117,6 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; 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 { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; import { ITEM_EFFECT } from "#app/items/held-item"; @@ -2735,19 +2733,14 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { 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 { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.getHeldItems(); return heldItems.length ? 5 : 0; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.getHeldItems(); return heldItems.length ? -5 : 0; } } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 340347317d9..aae38888b2a 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -73,6 +73,11 @@ export class PokemonItemManager { return item ? item.stack : 0; } + isMaxStack(itemType: HeldItemId): boolean { + const item = this.heldItems[itemType]; + return item ? item.stack >= allHeldItems[itemType].getMaxStackCount() : false; + } + overrideItems(newItems: HeldItemPropertyMap) { this.heldItems = newItems; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8b2ee04b867..02d05b8beda 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -60,7 +60,6 @@ import { EnemyDamageReducerModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, - PokemonHeldItemModifier, ShinyRateBoosterModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, @@ -5823,14 +5822,10 @@ export class PlayerPokemon extends Pokemon { globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); - const modifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, - true, - ) as PokemonHeldItemModifier[]; - modifiers.forEach(m => { - const clonedModifier = m.clone() as PokemonHeldItemModifier; - clonedModifier.pokemonId = newPokemon.id; - globalScene.addModifier(clonedModifier, true); + //TODO: This currently does not consider any values associated with the items e.g. disabled + const heldItems = this.getHeldItems(); + heldItems.forEach(item => { + newPokemon.heldItemManager.add(item, this.heldItemManager.getStack(item)); }); globalScene.updateModifiers(true); } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 772d32d6c46..cd35e52b086 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -7,7 +7,6 @@ import { Command } from "#enums/command"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; @@ -30,6 +29,7 @@ import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; import { HeldItemId } from "#enums/held-item-id"; import { formChangeItemName } from "#app/data/pokemon-forms"; +import { allHeldItems } from "#app/items/all-held-items"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -140,10 +140,7 @@ export type PartyModifierTransferSelectCallback = ( ) => void; export type PartyModifierSpliceSelectCallback = (fromCursor: number, toCursor?: number) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string | null; -export type PokemonModifierTransferSelectFilter = ( - pokemon: PlayerPokemon, - modifier: PokemonHeldItemModifier, -) => string | null; +export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, item: HeldItemId) => string | null; export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string | null; export default class PartyUiHandler extends MessageUiHandler { @@ -222,11 +219,8 @@ export default class PartyUiHandler extends MessageUiHandler { private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; - public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { - const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier), - ) as PokemonHeldItemModifier; - if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { + public static FilterItemMaxStacks = (pokemon: PlayerPokemon, item: HeldItemId) => { + if (pokemon.heldItemManager.isMaxStack(item)) { return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; @@ -505,8 +499,10 @@ export default class PartyUiHandler extends MessageUiHandler { const ui = this.getUi(); if (this.transferCursor !== this.cursor) { if (this.transferAll) { - this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( - (_, i, array) => { + globalScene + .getPlayerParty() + [this.transferCursor].heldItemManager.getTransferableHeldItems() + .forEach((_, i, array) => { const invertedIndex = array.length - 1 - i; (this.selectCallback as PartyModifierTransferSelectCallback)( this.transferCursor, @@ -514,8 +510,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.transferQuantitiesMax[invertedIndex], this.cursor, ); - }, - ); + }); } else { (this.selectCallback as PartyModifierTransferSelectCallback)( this.transferCursor, @@ -550,18 +545,15 @@ export default class PartyUiHandler extends MessageUiHandler { const newPokemon = globalScene.getPlayerParty()[p]; // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon `p` // this returns `undefined` if the new pokemon doesn't have the item at all, otherwise it returns the `pokemonHeldItemModifier` for that item - const matchingModifier = globalScene.findModifier( - m => - m instanceof PokemonHeldItemModifier && - m.pokemonId === newPokemon.id && - m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), - ) as PokemonHeldItemModifier; + const transferItem = pokemon.heldItemManager.getTransferableHeldItems()[this.transferOptionCursor]; + const matchingItem = newPokemon.heldItemManager.hasItem(transferItem); + const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer - if (matchingModifier) { + if (matchingItem) { // if matchingModifier exists then the item exists on the new pokemon - if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { + if (newPokemon.heldItemManager.isMaxStack(transferItem)) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" ableToTransferText = i18next.t("partyUiHandler:notAble"); } else { @@ -683,12 +675,6 @@ export default class PartyUiHandler extends MessageUiHandler { return success; } - private getTransferrableItemsFromPokemon(pokemon: PlayerPokemon) { - return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]; - } - private getFilterResult(option: number, pokemon: PlayerPokemon): string | null { let filterResult: string | null; if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { @@ -702,7 +688,7 @@ export default class PartyUiHandler extends MessageUiHandler { } else { filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( pokemon, - this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ + globalScene.getPlayerParty()[this.transferCursor].heldItemManager.getTransferableHeldItems()[ this.transferOptionCursor ], ); @@ -924,14 +910,10 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.cursor < 6) { if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { /** Initialize item quantities for the selected Pokemon */ - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, - ) as PokemonHeldItemModifier[]; - this.transferQuantities = itemModifiers.map(item => item.getStackCount()); - this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); + const pokemon = globalScene.getPlayerParty()[this.cursor]; + const items = pokemon.heldItemManager.getTransferableHeldItems(); + this.transferQuantities = items.map(item => pokemon.heldItemManager.getStack(item)); + this.transferQuantitiesMax = items.map(item => pokemon.heldItemManager.getStack(item)); } this.showOptions(); ui.playSelect(); @@ -1177,14 +1159,6 @@ export default class PartyUiHandler extends MessageUiHandler { ); } - private getItemModifiers(pokemon: Pokemon): PokemonHeldItemModifier[] { - return ( - (globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]) ?? [] - ); - } - private updateOptionsWithRememberMoveModifierMode(pokemon): void { const learnableMoves = pokemon.getLearnableLevelMoves(); for (let m = 0; m < learnableMoves.length; m++) { @@ -1204,11 +1178,11 @@ export default class PartyUiHandler extends MessageUiHandler { } private updateOptionsWithModifierTransferMode(pokemon): void { - const itemModifiers = this.getItemModifiers(pokemon); - for (let im = 0; im < itemModifiers.length; im++) { + const items = pokemon.getHeldItems(); + for (let im = 0; im < items.length; im++) { this.options.push(im); } - if (itemModifiers.length > 1) { + if (items.length > 1) { this.options.push(PartyOption.ALL); } } @@ -1422,9 +1396,9 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.ALL) { optionName = i18next.t("partyUiHandler:ALL"); } else { - const itemModifiers = this.getItemModifiers(pokemon); - const itemModifier = itemModifiers[option]; - optionName = itemModifier.type.name; + const items = pokemon.getHeldItems(); + const item = items[option]; + optionName = allHeldItems[item].name; } const yCoord = -6 - 16 * o; @@ -1436,19 +1410,19 @@ export default class PartyUiHandler extends MessageUiHandler { optionText.setOrigin(0, 0); /** For every item that has stack bigger than 1, display the current quantity selection */ - const itemModifiers = this.getItemModifiers(pokemon); - const itemModifier = itemModifiers[option]; + const items = pokemon.getHeldItems(); + const item = items[option]; if ( this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && this.transferQuantitiesMax[option] > 1 && !this.transferMode && - itemModifier !== undefined && - itemModifier.type.name === optionName + item !== undefined && + allHeldItems[item].name === optionName ) { let amountText = ` (${this.transferQuantities[option]})`; /** If the amount held is the maximum, display the count in red */ - if (this.transferQuantitiesMax[option] === itemModifier.getMaxHeldItemCount(undefined)) { + if (this.transferQuantitiesMax[option] === allHeldItems[item].maxStackCount) { amountText = `[color=${getTextColor(TextStyle.SUMMARY_RED)}]${amountText}[/color]`; } From 9a79882e05ac7af023e0bf67a8f89fc3e45689b0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:06:49 +0200 Subject: [PATCH 082/114] Updated modifier-bar.ts --- src/modifier/modifier-bar.ts | 114 ++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/src/modifier/modifier-bar.ts b/src/modifier/modifier-bar.ts index 994c6960c63..5962d122a69 100644 --- a/src/modifier/modifier-bar.ts +++ b/src/modifier/modifier-bar.ts @@ -1,35 +1,39 @@ +import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { type Modifier, type PersistentModifier, PokemonHeldItemModifier } from "./modifier"; +import { allHeldItems } from "#app/items/all-held-items"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { Modifier, PersistentModifier } from "./modifier"; const iconOverflowIndex = 24; export const modifierSortFunc = (a: Modifier, b: Modifier): number => { const itemNameMatch = a.type.name.localeCompare(b.type.name); const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; - const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; - //First sort by pokemonID - if (aId < bId) { - return 1; + //Then sort by item type + if (typeNameMatch === 0) { + return itemNameMatch; + //Finally sort by item name } - if (aId > bId) { - return -1; + return typeNameMatch; +}; + +//TODO: revisit this function +export const heldItemSortFunc = (a: HeldItemId, b: HeldItemId): number => { + const itemNameMatch = allHeldItems[a].name.localeCompare(allHeldItems[b].name); + const itemIdMatch = a - b; + + if (itemIdMatch === 0) { + return itemNameMatch; + //Finally sort by item name } - if (aId === bId) { - //Then sort by item type - if (typeNameMatch === 0) { - return itemNameMatch; - //Finally sort by item name - } - return typeNameMatch; - } - return 0; + return itemIdMatch; }; export class ModifierBar extends Phaser.GameObjects.Container { private player: boolean; - private modifierCache: PersistentModifier[]; + private modifierCache: (PersistentModifier | HeldItemId)[]; + private totalVisibleLength = 0; constructor(enemy?: boolean) { super(globalScene, 1 + (enemy ? 302 : 0), 2); @@ -43,48 +47,60 @@ export class ModifierBar extends Phaser.GameObjects.Container { * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed */ - updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) { + updateModifiers(modifiers: PersistentModifier[], pokemonA: Pokemon, pokemonB?: Pokemon, hideHeldItems = false) { this.removeAll(true); - const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); - const nonPokemonSpecificModifiers = visibleIconModifiers - .filter(m => !(m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - const pokemonSpecificModifiers = visibleIconModifiers - .filter(m => (m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); + const sortedVisibleModifiers = modifiers.filter(m => m.isIconVisible()).sort(modifierSortFunc); - const sortedVisibleIconModifiers = hideHeldItems - ? nonPokemonSpecificModifiers - : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); + const heldItemsA = pokemonA.getHeldItems().sort(heldItemSortFunc); + const heldItemsB = pokemonB ? pokemonB.getHeldItems().sort(heldItemSortFunc) : []; - sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { + this.totalVisibleLength = sortedVisibleModifiers.length; + if (!hideHeldItems) { + this.totalVisibleLength += heldItemsA.length + heldItemsB.length; + } + + sortedVisibleModifiers.forEach((modifier: PersistentModifier, i: number) => { const icon = modifier.getIcon(); - if (i >= iconOverflowIndex) { - icon.setVisible(false); - } - this.add(icon); - this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => { - globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(true); - } - }); - icon.on("pointerout", () => { - globalScene.ui.hideTooltip(); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(false); - } - }); + this.addIcon(icon, i, modifier.type.name, modifier.type.getDescription()); + }); + + heldItemsA.forEach((item: HeldItemId, i: number) => { + const icon = allHeldItems[item].createPokemonIcon(pokemonA); + this.addIcon(icon, i, allHeldItems[item].name, allHeldItems[item].description); + }); + + heldItemsB.forEach((item: HeldItemId, i: number) => { + const icon = allHeldItems[item].createPokemonIcon(pokemonB); + this.addIcon(icon, i, allHeldItems[item].name, allHeldItems[item].description); }); for (const icon of this.getAll()) { this.sendToBack(icon); } - this.modifierCache = modifiers; + this.modifierCache = (modifiers as (PersistentModifier | HeldItemId)[]).concat(heldItemsA).concat(heldItemsB); + } + + addIcon(icon: Phaser.GameObjects.Container, i: number, name: string, description: string) { + if (i >= iconOverflowIndex) { + icon.setVisible(false); + } + this.add(icon); + this.setModifierIconPosition(icon, this.totalVisibleLength); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => { + globalScene.ui.showTooltip(name, description); + if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { + this.updateModifierOverflowVisibility(true); + } + }); + icon.on("pointerout", () => { + globalScene.ui.hideTooltip(); + if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { + this.updateModifierOverflowVisibility(false); + } + }); } updateModifierOverflowVisibility(ignoreLimit: boolean) { From 51a2a157997e2060cac6a31e59a9400b16d6bcb1 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:11:03 +0200 Subject: [PATCH 083/114] Converted Berries Abound encounter --- .../encounters/berries-abound-encounter.ts | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 65ae3ea6c4f..efffd2d1547 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,7 +1,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, getRandomEncounterSpecies, initBattleWithEnemyConfig, @@ -11,7 +10,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { randSeedInt } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -24,18 +23,17 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { - applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; -import { BerryModifier } from "#app/modifier/modifier"; import i18next from "#app/plugins/i18n"; import { BerryType } from "#enums/berry-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/berriesAbound"; @@ -310,35 +308,17 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType; - const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType; + const berry = berryTypeToHeldItem[berryType]; const party = globalScene.getPlayerParty(); - // Will try to apply to prioritized pokemon first, then do normal application method if it fails - if (prioritizedPokemon) { - const heldBerriesOfType = globalScene.findModifier( - m => - m instanceof BerryModifier && - m.pokemonId === prioritizedPokemon.id && - (m as BerryModifier).berryType === berryType, - true, - ) as BerryModifier; - - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { - applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); - return; - } + // Will give the berry to a Pokemon, starting from the prioritized one + if (prioritizedPokemon?.heldItemManager.add(berry)) { + return; } - // Iterate over the party until berry was successfully given for (const pokemon of party) { - const heldBerriesOfType = globalScene.findModifier( - m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, - true, - ) as BerryModifier; - - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { - applyModifierTypeToPlayerPokemon(pokemon, berry); + if (pokemon.heldItemManager.add(berry)) { return; } } From 558d06cdaa0d90474277c1ddb8a307e0ce9d4db4 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:20:44 +0200 Subject: [PATCH 084/114] Converted The Strong Stuff encounter --- .../encounters/the-strong-stuff-encounter.ts | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index a3eb78f479e..f9140089a61 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -5,9 +5,7 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, - generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; @@ -23,11 +21,11 @@ import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encou import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { HeldItemId } from "#enums/held-item-id"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theStrongStuff"; @@ -94,24 +92,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.HARDY, moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - ], + heldItemConfig: { + [HeldItemId.SITRUS_BERRY]: { stack: 1 }, + [HeldItemId.ENIGMA_BERRY]: { stack: 1 }, + [HeldItemId.APICOT_BERRY]: { stack: 1 }, + [HeldItemId.GANLON_BERRY]: { stack: 1 }, + [HeldItemId.LUM_BERRY]: { stack: 2 }, + }, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.stat_boost`); From ad7f2d133c41b8aad1f1cb56c477ca8ea7df237c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:30:09 +0200 Subject: [PATCH 085/114] Fixed Slumbering Snorlax encounter, overrideItems of heldItemConfiguration can deal with items with 0 stack --- .../slumbering-snorlax-encounter.ts | 31 +++++-------------- src/field/pokemon-held-item-manager.ts | 6 ++++ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 1ecd73143fc..1e975f25c5d 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,5 +1,4 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; @@ -11,7 +10,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, @@ -27,10 +25,9 @@ import { AiType } from "#enums/ai-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryType } from "#enums/berry-type"; -import { Stat } from "#enums/stat"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { randSeedInt } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -77,25 +74,13 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil status: [StatusEffect.SLEEP, 6], // Extra turns on timer for Snorlax's start of fight moves nature: Nature.DOCILE, moveSet: [MoveId.BODY_SLAM, MoveId.CRUNCH, MoveId.SLEEP_TALK, MoveId.REST], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, - { - modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, - ], + heldItemConfig: { + [HeldItemId.SITRUS_BERRY]: { stack: 1 }, + [HeldItemId.ENIGMA_BERRY]: { stack: 1 }, + [HeldItemId.HP_UP]: { stack: 1 }, + [HeldItemId.SOOTHE_BELL]: { stack: randSeedInt(2, 0) }, + [HeldItemId.LUCKY_EGG]: { stack: randSeedInt(2, 0) }, + }, customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep }; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index aae38888b2a..a9d1aba417c 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -80,6 +80,12 @@ export class PokemonItemManager { overrideItems(newItems: HeldItemPropertyMap) { this.heldItems = newItems; + // The following is to allow randomly generated item configs to have stack 0 + for (const [item, properties] of Object.entries(this.heldItems)) { + if (!properties || properties.stack <= 0) { + delete this.heldItems[item]; + } + } } add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA): boolean { From 301db8a1de607d05b791c68995d99bedbe8e99b5 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:12:58 +0200 Subject: [PATCH 086/114] Preliminary changes to some MEs --- .../encounters/absolute-avarice-encounter.ts | 125 ++++-------------- .../the-winstrate-challenge-encounter.ts | 1 - .../encounters/training-session-encounter.ts | 1 - .../encounters/trash-to-treasure-encounter.ts | 2 - .../encounters/uncommon-breed-encounter.ts | 1 - 5 files changed, 24 insertions(+), 106 deletions(-) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 2213dc4afaa..fe3d6770465 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -9,7 +9,6 @@ import { import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; @@ -21,7 +20,6 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -38,10 +36,23 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/absoluteAvarice"; +function berrySprite(spriteKey: string, x: number, y: number): MysteryEncounterSpriteConfig { + return { + spriteKey: spriteKey, + fileRoot: "items", + isItem: true, + x: x, + y: y, + hidden: true, + disableAnimation: true, + }; +} + /** * Absolute Avarice encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805} @@ -73,105 +84,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde repeat: true, x: -5, }, - { - spriteKey: "lum_berry", - fileRoot: "items", - isItem: true, - x: 7, - y: -14, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "salac_berry", - fileRoot: "items", - isItem: true, - x: 2, - y: 4, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "lansat_berry", - fileRoot: "items", - isItem: true, - x: 32, - y: 5, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "liechi_berry", - fileRoot: "items", - isItem: true, - x: 6, - y: -5, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "sitrus_berry", - fileRoot: "items", - isItem: true, - x: 7, - y: 8, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "enigma_berry", - fileRoot: "items", - isItem: true, - x: 26, - y: -4, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "leppa_berry", - fileRoot: "items", - isItem: true, - x: 16, - y: -27, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "petaya_berry", - fileRoot: "items", - isItem: true, - x: 30, - y: -17, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "ganlon_berry", - fileRoot: "items", - isItem: true, - x: 16, - y: -11, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "apicot_berry", - fileRoot: "items", - isItem: true, - x: 14, - y: -2, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "starf_berry", - fileRoot: "items", - isItem: true, - x: 18, - y: 9, - hidden: true, - disableAnimation: true, - }, + berrySprite("lum_berry", 7, -14), + berrySprite("salac_berry", 2, 4), + berrySprite("lansat_berry", 32, 5), + berrySprite("liechi_berry", 6, -5), + berrySprite("sitrus_berry", 7, 8), + berrySprite("enigma_berry", 26, -4), + berrySprite("leppa_berry", 16, -27), + berrySprite("petaya_berry", 30, -17), + berrySprite("ganlon_berry", 16, -11), + berrySprite("apicot_berry", 14, -2), + berrySprite("starf_berry", 18, 9), ]) .withHideWildIntroMessage(true) .withAutoHideIntroVisuals(false) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 91754629821..d70ecaca980 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -7,7 +7,6 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 9ab91f439bf..8da4d685be8 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -11,7 +11,6 @@ import { getNatureName } from "#app/data/nature"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#enums/ability-attr"; import PokemonData from "#app/system/pokemon-data"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 8bcd024de5c..4a7e0bc5a66 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -7,7 +7,6 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; @@ -17,7 +16,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { SpeciesId } from "#enums/species-id"; -import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index c48f93a9a9d..cb78ddd1eb6 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -33,7 +33,6 @@ import { BattlerIndex } from "#enums/battler-index"; import { PokeballType } from "#enums/pokeball"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { BerryModifier } from "#app/modifier/modifier"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; From 6fb85ea742d6df0d3e41969a418c84ddd0acb66f Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:14:00 +0200 Subject: [PATCH 087/114] Changes to the summary ui handler --- src/modifier/modifier-bar.ts | 15 ++++++++++++ src/ui/summary-ui-handler.ts | 44 +++++++++++++++--------------------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/modifier/modifier-bar.ts b/src/modifier/modifier-bar.ts index 5962d122a69..bef021d7484 100644 --- a/src/modifier/modifier-bar.ts +++ b/src/modifier/modifier-bar.ts @@ -1,6 +1,8 @@ +import { formChangeItemName } from "#app/data/pokemon-forms"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { allHeldItems } from "#app/items/all-held-items"; +import type { FormChangeItem } from "#enums/form-change-item"; import type { HeldItemId } from "#enums/held-item-id"; import type { Modifier, PersistentModifier } from "./modifier"; @@ -30,6 +32,19 @@ export const heldItemSortFunc = (a: HeldItemId, b: HeldItemId): number => { return itemIdMatch; }; +export const formChangeItemSortFunc = (a: FormChangeItem, b: FormChangeItem): number => { + const nameA = formChangeItemName(a); + const nameB = formChangeItemName(b); + const itemNameMatch = nameA.localeCompare(nameB); + const itemIdMatch = a - b; + + if (itemIdMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } + return itemIdMatch; +}; + export class ModifierBar extends Phaser.GameObjects.Container { private player: boolean; private modifierCache: (PersistentModifier | HeldItemId)[]; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 902553bbde9..9e0562032d7 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -25,7 +25,6 @@ import { MoveCategory } from "#enums/MoveCategory"; import { getPokeballAtlasKey } from "#app/data/pokeball"; import { getGenderColor, getGenderSymbol } from "#app/data/gender"; import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { StatusEffect } from "#enums/status-effect"; import { getBiomeName } from "#app/data/balance/biomes"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; @@ -35,7 +34,7 @@ import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import type { Ability } from "#app/data/abilities/ability-class"; import i18next from "i18next"; -import { modifierSortFunc } from "#app/modifier/modifier-bar"; +import { heldItemSortFunc } from "#app/modifier/modifier-bar"; import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; import { Nature } from "#enums/nature"; @@ -1033,31 +1032,9 @@ export default class SummaryUiHandler extends UiHandler { }); this.ivContainer.setVisible(false); - const itemModifiers = ( - globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, - this.playerParty, - ) as PokemonHeldItemModifier[] - ).sort(modifierSortFunc); + const heldItems = this.pokemon?.getHeldItems().sort(heldItemSortFunc); - itemModifiers.forEach((item, i) => { - const icon = item.getIcon(true); - - icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); - this.statsContainer.add(icon); - - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true)); - icon.on("pointerout", () => globalScene.ui.hideTooltip()); - }); - - const heldItemKeys = this.pokemon?.heldItemManager.getHeldItems(); - // TODO: Sort them - //.sort(heldItemSortFunc); - - console.log(heldItemKeys); - - heldItemKeys?.forEach((itemKey, i) => { + heldItems?.forEach((itemKey, i) => { const stack = this.pokemon?.heldItemManager.getStack(itemKey); const heldItem = allHeldItems[itemKey]; const icon = heldItem.createSummaryIcon(stack); @@ -1070,7 +1047,22 @@ export default class SummaryUiHandler extends UiHandler { icon.on("pointerover", () => globalScene.ui.showTooltip(heldItem.getName(), heldItem.getDescription(), true)); icon.on("pointerout", () => globalScene.ui.hideTooltip()); }); + /* + const formChangeItems = this.pokemon?.heldItemManager.getFormChangeItems().sort(formChangeItemSortFunc); + + //TODO: Make an equivalent function for form change items + formChangeItems?.forEach((itemKey, i) => { + const icon = heldItem.createSummaryIcon(stack); + console.log(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + this.statsContainer.add(icon); + + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => globalScene.ui.showTooltip(heldItem.getName(), heldItem.getDescription(), true)); + icon.on("pointerout", () => globalScene.ui.hideTooltip()); + }); +*/ const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? From be84790b2dae92a2d92567ca872053695b6dd548 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:15:05 +0200 Subject: [PATCH 088/114] Converting to held item pools --- src/enums/held-item-id.ts | 4 + src/enums/reward-tier.ts | 8 ++ src/field/pokemon-held-item-manager.ts | 1 + src/items/held-item-pool.ts | 110 +++++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 src/enums/reward-tier.ts create mode 100644 src/items/held-item-pool.ts diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 7da668d21b2..f6d223fc855 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -127,6 +127,10 @@ function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { return itemId & ITEM_CATEGORY_MASK; } +export function isCategoryId(categoryId: HeldItemCategoryId): boolean { + return (categoryId & ITEM_CATEGORY_MASK) === categoryId; +} + export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { return getHeldItemCategory(itemId) === category; } diff --git a/src/enums/reward-tier.ts b/src/enums/reward-tier.ts new file mode 100644 index 00000000000..e7ccc1d9166 --- /dev/null +++ b/src/enums/reward-tier.ts @@ -0,0 +1,8 @@ +export enum RewardTier { + COMMON, + GREAT, + ULTRA, + ROGUE, + MASTER, + LUXURY, +} diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index a9d1aba417c..bd6133551bf 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -9,6 +9,7 @@ type HELD_ITEM_DATA = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; interface HeldItemProperties { stack: number; disabled?: boolean; + unstealable?: boolean; //TODO: ensure this is taken into account by stealing effects cooldown?: number; data?: HELD_ITEM_DATA; } diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts new file mode 100644 index 00000000000..196f2121325 --- /dev/null +++ b/src/items/held-item-pool.ts @@ -0,0 +1,110 @@ +/* +import type { PlayerPokemon } from "#app/field/pokemon"; +import { randSeedInt } from "#app/utils/common"; +import { HeldItemCategoryId, HeldItemId, isCategoryId } from "#enums/held-item-id"; +import { RewardTier } from "#enums/reward-tier"; + +interface HeldItemPoolEntry { + weight: number; + item: HeldItemId | HeldItemCategoryId; +} + +export type HeldItemPool = { + [key in RewardTier]?: HeldItemPoolEntry[]; +}; + +const dailyStarterHeldItemPool: HeldItemPool = { + [RewardTier.COMMON]: [ + { item: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }, + { item: HeldItemCategoryId.BERRY, weight: 3 }, + ], + [RewardTier.GREAT]: [{ item: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }], + [RewardTier.ULTRA]: [ + { item: HeldItemId.REVIVER_SEED, weight: 4 }, + { item: HeldItemId.SOOTHE_BELL, weight: 1 }, + { item: HeldItemId.SOUL_DEW, weight: 1 }, + { item: HeldItemId.GOLDEN_PUNCH, weight: 1 }, + ], + [RewardTier.ROGUE]: [ + { item: HeldItemId.GRIP_CLAW, weight: 5 }, + { item: HeldItemId.BATON, weight: 2 }, + { item: HeldItemId.FOCUS_BAND, weight: 5 }, + { item: HeldItemId.QUICK_CLAW, weight: 3 }, + { item: HeldItemId.KINGS_ROCK, weight: 3 }, + ], + [RewardTier.MASTER]: [ + { item: HeldItemId.LEFTOVERS, weight: 1 }, + { item: HeldItemId.SHELL_BELL, weight: 1 }, + ], +}; + +export function getDailyRunStarterHeldItems(party: PlayerPokemon[]): PokemonHeldItemModifier[] { + const ret: HeldItemId[] = []; + for (const p of party) { + for (let m = 0; m < 3; m++) { + const tierValue = randSeedInt(64); + + let tier: RewardTier; + if (tierValue > 25) { + tier = RewardTier.COMMON; + } else if (tierValue > 12) { + tier = RewardTier.GREAT; + } else if (tierValue > 4) { + tier = RewardTier.ULTRA; + } else if (tierValue) { + tier = RewardTier.ROGUE; + } else { + tier = RewardTier.MASTER; + } + + const item = getNewHeldItemFromPool(party, dailyStarterHeldItemPool, tier); + ret.push(item); + } + } + + return ret; +} + +function getNewModifierTypeOption( + party: Pokemon[], + poolType: ModifierPoolType, + baseTier?: ModifierTier, + upgradeCount?: number, + retryCount = 0, + allowLuckUpgrades = true, +): ModifierTypeOption | null { + const player = !poolType; + const pool = getModifierPoolForType(poolType); + const thresholds = getPoolThresholds(poolType); + + const tier = determineTier(party, player, baseTier, upgradeCount, retryCount, allowLuckUpgrades); + + const tierThresholds = Object.keys(thresholds[tier]); + const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]); + const value = randSeedInt(totalWeight); + let index: number | undefined; + for (const t of tierThresholds) { + const threshold = Number.parseInt(t); + if (value < threshold) { + index = thresholds[tier][threshold]; + break; + } + } + + if (index === undefined) { + return null; + } + + if (player) { + console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); + } + + const item = pool[tier][index].item; + if (isCategoryId(item)) { + return getNewHeldItemCategoryOption(item); + } + return item; + + // console.log(modifierType, !player ? "(enemy)" : ""); +} +*/ From 89082921eb9b5ec2f40a4206db93b9babcb750f0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:43:37 +0200 Subject: [PATCH 089/114] Fixed evo tracker --- src/items/held-items/evo-tracker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 6bd4937b2ee..25e504819b8 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -1,6 +1,5 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import type { NumberHolder } from "#app/utils/common"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; @@ -76,10 +75,7 @@ export class GimmighoulEvoTrackerHeldItem extends EvoTrackerHeldItem { pokemon.evoCounter + pokemon.heldItemManager.getStack(HeldItemId.GOLDEN_PUNCH) + globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, + m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier"), ).length; return stackCount; } From 0e5499bf259302a3039f00aa27e01e223e2a520e Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 15 Jun 2025 20:51:05 +0200 Subject: [PATCH 090/114] Moved many data types for held items to a dedicated file; introduced held item pools for item generation --- src/field/pokemon-held-item-manager.ts | 50 +++--- src/items/held-item-data-types.ts | 69 ++++++++ src/items/held-item-pool.ts | 217 ++++++++++++++---------- src/items/held-items/base-stat-flat.ts | 7 +- src/items/held-items/base-stat-total.ts | 5 +- src/items/init-held-item-pools.ts | 80 +++++++++ src/modifier/init-modifier-pools.ts | 123 +------------- src/modifier/modifier-pools.ts | 4 - src/modifier/modifier-type.ts | 81 +-------- 9 files changed, 313 insertions(+), 323 deletions(-) create mode 100644 src/items/held-item-data-types.ts create mode 100644 src/items/init-held-item-pools.ts diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index bd6133551bf..c30b660c855 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,22 +1,7 @@ import { allHeldItems } from "#app/items/all-held-items"; import { isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; import type { FormChangeItem } from "#enums/form-change-item"; -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"; - -type HELD_ITEM_DATA = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; - -interface HeldItemProperties { - stack: number; - disabled?: boolean; - unstealable?: boolean; //TODO: ensure this is taken into account by stealing effects - cooldown?: number; - data?: HELD_ITEM_DATA; -} - -export type HeldItemPropertyMap = { - [key in HeldItemId]?: HeldItemProperties; -}; +import { isHeldItemSpecs, type HeldItemDataMap, type HeldItemSpecs } from "#app/items/held-item-data-types"; interface FormChangeItemProperties { active: boolean; @@ -27,7 +12,7 @@ export type FormChangeItemPropertyMap = { }; export class PokemonItemManager { - public heldItems: HeldItemPropertyMap; + public heldItems: HeldItemDataMap; public formChangeItems: FormChangeItemPropertyMap; constructor() { @@ -61,14 +46,6 @@ export class PokemonItemManager { return itemType in this.heldItems; } - /* - getItem(itemType: HeldItemId): HeldItemProperties { - if (itemType in this.heldItems) { - return this.heldItems[itemType]; - } - } -*/ - getStack(itemType: HeldItemId): number { const item = this.heldItems[itemType]; return item ? item.stack : 0; @@ -79,7 +56,7 @@ export class PokemonItemManager { return item ? item.stack >= allHeldItems[itemType].getMaxStackCount() : false; } - overrideItems(newItems: HeldItemPropertyMap) { + overrideItems(newItems: HeldItemDataMap) { this.heldItems = newItems; // The following is to allow randomly generated item configs to have stack 0 for (const [item, properties] of Object.entries(this.heldItems)) { @@ -89,7 +66,11 @@ export class PokemonItemManager { } } - add(itemType: HeldItemId, addStack = 1, data?: HELD_ITEM_DATA): boolean { + add(itemType: HeldItemId | HeldItemSpecs, addStack = 1): boolean { + if (isHeldItemSpecs(itemType)) { + return this.addItemWithSpecs(itemType); + } + const maxStack = allHeldItems[itemType].getMaxStackCount(); const item = this.heldItems[itemType]; @@ -100,12 +81,25 @@ export class PokemonItemManager { return true; } } else { - this.heldItems[itemType] = { stack: Math.min(addStack, maxStack), disabled: false, data: data }; + this.heldItems[itemType] = { stack: Math.min(addStack, maxStack) }; return true; } return false; } + addItemWithSpecs(itemSpecs: HeldItemSpecs): boolean { + const id = itemSpecs.id; + const maxStack = allHeldItems[id].getMaxStackCount(); + const item = this.heldItems[id]; + + const tempStack = item?.stack ?? 0; + + this.heldItems[id] = itemSpecs; + this.heldItems[id].stack = Math.min(itemSpecs.stack + tempStack, maxStack); + + return true; + } + remove(itemType: HeldItemId, removeStack = 1, all = false) { const item = this.heldItems[itemType]; diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts new file mode 100644 index 00000000000..ff291b04282 --- /dev/null +++ b/src/items/held-item-data-types.ts @@ -0,0 +1,69 @@ +import type Pokemon from "#app/field/pokemon"; +import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import type { RewardTier } from "#enums/reward-tier"; +import type { Stat } from "#enums/stat"; + +export interface BASE_STAT_TOTAL_DATA { + statModifier: number; +} + +export interface BASE_STAT_FLAT_DATA { + statModifier: number; + stats: Stat[]; +} + +export type HeldItemExtraData = BASE_STAT_TOTAL_DATA | BASE_STAT_FLAT_DATA; + +export type HeldItemData = { + stack: number; + disabled?: boolean; + unstealable?: boolean; + cooldown?: number; + data?: HeldItemExtraData; +}; + +export type HeldItemDataMap = { + [key in HeldItemId]?: HeldItemData; +}; + +export type HeldItemSpecs = HeldItemData & { + id: HeldItemId; +}; + +export function isHeldItemSpecs(entry: any): entry is HeldItemSpecs { + return typeof entry.id === "number" && "stack" in entry; +} + +export type HeldItemWeights = { + [key in HeldItemId]?: number; +}; + +export type HeldItemWeightFunc = (party: Pokemon[]) => number; + +export type HeldItemCategoryEntry = HeldItemData & { + id: HeldItemCategoryId; + customWeights?: HeldItemWeights; +}; + +export function isHeldItemCategoryEntry(entry: any): entry is HeldItemCategoryEntry { + return isHeldItemCategoryEntry(entry.id) && "customWeights" in entry; +} + +type HeldItemPoolEntry = { + entry: HeldItemId | HeldItemCategoryEntry | HeldItemSpecs; + weight: number | HeldItemWeightFunc; +}; + +export type HeldItemPool = HeldItemPoolEntry[]; + +export type HeldItemTieredPool = { + [key in RewardTier]?: HeldItemPool; +}; + +type HeldItemConfigurationEntry = { + entry: HeldItemId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool; + count?: number | (() => number); + excluded?: HeldItemId[]; +}; + +export type HeldItemConfiguration = HeldItemConfigurationEntry[]; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 196f2121325..ab778d68f74 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -1,110 +1,157 @@ -/* import type { PlayerPokemon } from "#app/field/pokemon"; -import { randSeedInt } from "#app/utils/common"; -import { HeldItemCategoryId, HeldItemId, isCategoryId } from "#enums/held-item-id"; +import type Pokemon from "#app/field/pokemon"; +import { coerceArray, getEnumValues, randSeedFloat, randSeedInt } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; +import { PERMANENT_STATS } from "#enums/stat"; +import { + type HeldItemPool, + type HeldItemSpecs, + type HeldItemTieredPool, + type HeldItemWeights, + isHeldItemCategoryEntry, +} from "./held-item-data-types"; +import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; +import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; +import { berryTypeToHeldItem } from "./held-items/berry"; -interface HeldItemPoolEntry { - weight: number; - item: HeldItemId | HeldItemCategoryId; -} +export const wildHeldItemPool: HeldItemTieredPool = {}; -export type HeldItemPool = { - [key in RewardTier]?: HeldItemPoolEntry[]; -}; +export const trainerHeldItemPool: HeldItemTieredPool = {}; -const dailyStarterHeldItemPool: HeldItemPool = { - [RewardTier.COMMON]: [ - { item: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }, - { item: HeldItemCategoryId.BERRY, weight: 3 }, - ], - [RewardTier.GREAT]: [{ item: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }], - [RewardTier.ULTRA]: [ - { item: HeldItemId.REVIVER_SEED, weight: 4 }, - { item: HeldItemId.SOOTHE_BELL, weight: 1 }, - { item: HeldItemId.SOUL_DEW, weight: 1 }, - { item: HeldItemId.GOLDEN_PUNCH, weight: 1 }, - ], - [RewardTier.ROGUE]: [ - { item: HeldItemId.GRIP_CLAW, weight: 5 }, - { item: HeldItemId.BATON, weight: 2 }, - { item: HeldItemId.FOCUS_BAND, weight: 5 }, - { item: HeldItemId.QUICK_CLAW, weight: 3 }, - { item: HeldItemId.KINGS_ROCK, weight: 3 }, - ], - [RewardTier.MASTER]: [ - { item: HeldItemId.LEFTOVERS, weight: 1 }, - { item: HeldItemId.SHELL_BELL, weight: 1 }, - ], -}; +export const dailyStarterHeldItemPool: HeldItemTieredPool = {}; -export function getDailyRunStarterHeldItems(party: PlayerPokemon[]): PokemonHeldItemModifier[] { - const ret: HeldItemId[] = []; +export function getDailyRunStarterHeldItems(party: PlayerPokemon[]) { for (const p of party) { for (let m = 0; m < 3; m++) { const tierValue = randSeedInt(64); - let tier: RewardTier; - if (tierValue > 25) { - tier = RewardTier.COMMON; - } else if (tierValue > 12) { - tier = RewardTier.GREAT; - } else if (tierValue > 4) { - tier = RewardTier.ULTRA; - } else if (tierValue) { - tier = RewardTier.ROGUE; - } else { - tier = RewardTier.MASTER; - } + const tier = getDailyRewardTier(tierValue); - const item = getNewHeldItemFromPool(party, dailyStarterHeldItemPool, tier); - ret.push(item); + const item = getNewHeldItemFromPool(dailyStarterHeldItemPool[tier] as HeldItemPool, p); + p.heldItemManager.add(item); } } - - return ret; } -function getNewModifierTypeOption( - party: Pokemon[], - poolType: ModifierPoolType, - baseTier?: ModifierTier, - upgradeCount?: number, - retryCount = 0, - allowLuckUpgrades = true, -): ModifierTypeOption | null { - const player = !poolType; - const pool = getModifierPoolForType(poolType); - const thresholds = getPoolThresholds(poolType); +function getDailyRewardTier(tierValue: number): RewardTier { + if (tierValue > 25) { + return RewardTier.COMMON; + } + if (tierValue > 12) { + return RewardTier.GREAT; + } + if (tierValue > 4) { + return RewardTier.ULTRA; + } + if (tierValue > 0) { + return RewardTier.ROGUE; + } + return RewardTier.MASTER; +} - const tier = determineTier(party, player, baseTier, upgradeCount, retryCount, allowLuckUpgrades); +function pickWeightedIndex(weights: number[]): number { + const totalWeight = weights.reduce((sum, w) => sum + w, 0); - const tierThresholds = Object.keys(thresholds[tier]); - const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]); - const value = randSeedInt(totalWeight); - let index: number | undefined; - for (const t of tierThresholds) { - const threshold = Number.parseInt(t); - if (value < threshold) { - index = thresholds[tier][threshold]; - break; - } + if (totalWeight <= 0) { + throw new Error("Total weight must be greater than 0."); } - if (index === undefined) { + let r = randSeedFloat() * totalWeight; + + for (let i = 0; i < weights.length; i++) { + if (r < weights[i]) { + return i; + } + r -= weights[i]; + } + + return -1; // TODO: Change to something more appropriate +} + +export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}): HeldItemId { + const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]); + const weights = items.map(t => customWeights[t] ?? t); + return items[pickWeightedIndex(weights)]; +} + +export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}): HeldItemId { + const berryTypes = getEnumValues(BerryType); + const items = berryTypes.map(b => berryTypeToHeldItem[b]); + + const weights = items.map(t => + (customWeights[t] ?? (t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY)) + ? 2 + : 1, + ); + + return items[pickWeightedIndex(weights)]; +} + +export function getNewAttackTypeBoosterHeldItem( + pokemon: Pokemon | Pokemon[], + customWeights: HeldItemWeights = {}, +): HeldItemId | null { + const party = coerceArray(pokemon); + + // TODO: make this consider moves or abilities that change types + const attackMoveTypes = party.flatMap(p => + p + .getMoveset() + .filter(m => m.getMove().is("AttackMove")) + .map(m => m.getMove().type), + ); + if (!attackMoveTypes.length) { return null; } - if (player) { - console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); - } + const attackMoveTypeWeights = attackMoveTypes.reduce((map, type) => { + const current = map.get(type) ?? 0; + if (current < 3) { + map.set(type, current + 1); + } + return map; + }, new Map()); - const item = pool[tier][index].item; - if (isCategoryId(item)) { - return getNewHeldItemCategoryOption(item); - } - return item; + const types = Array.from(attackMoveTypeWeights.keys()); - // console.log(modifierType, !player ? "(enemy)" : ""); + const weights = types.map(type => customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!); + + const type = types[pickWeightedIndex(weights)]; + return attackTypeToHeldItem[type]; +} + +export function getNewHeldItemFromCategory( + id: HeldItemCategoryId, + pokemon: Pokemon | Pokemon[], + customWeights: HeldItemWeights = {}, +): HeldItemId | null { + if (id === HeldItemCategoryId.BERRY) { + return getNewBerryHeldItem(customWeights); + } + if (id === HeldItemCategoryId.VITAMIN) { + return getNewVitaminHeldItem(customWeights); + } + if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) { + return getNewAttackTypeBoosterHeldItem(pokemon, customWeights); + } + return null; +} + +function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemId | HeldItemSpecs { + const weights = pool.map(p => (typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight)); + + const entry = pool[pickWeightedIndex(weights)].entry; + + if (typeof entry === "number") { + return entry as HeldItemId; + } + + if (isHeldItemCategoryEntry(entry)) { + return getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights) as HeldItemId; + } + + return entry as HeldItemSpecs; } -*/ diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 5144b392ad0..f191d8a06b9 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItem, ITEM_EFFECT } from "../held-item"; -import { getStatKey, type Stat } from "#enums/stat"; +import { getStatKey } from "#enums/stat"; import i18next from "i18next"; export interface BASE_STAT_FLAT_PARAMS { @@ -11,11 +11,6 @@ export interface BASE_STAT_FLAT_PARAMS { baseStats: number[]; } -export interface BASE_STAT_FLAT_DATA { - statModifier: number; - stats: Stat[]; -} - /** * Currently used by Old Gateau item */ diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 59bc10e3cc3..f5c57c6a685 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,6 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; +import type { BASE_STAT_TOTAL_DATA } from "../held-item-data"; export interface BASE_STAT_TOTAL_PARAMS { /** The pokemon with the item */ @@ -9,10 +10,6 @@ export interface BASE_STAT_TOTAL_PARAMS { baseStats: number[]; } -export interface BASE_STAT_TOTAL_DATA { - statModifier: number; -} - /** * Currently used by Shuckle Juice item */ diff --git a/src/items/init-held-item-pools.ts b/src/items/init-held-item-pools.ts new file mode 100644 index 00000000000..0e6dc7b99e6 --- /dev/null +++ b/src/items/init-held-item-pools.ts @@ -0,0 +1,80 @@ +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { RewardTier } from "#enums/reward-tier"; +import { dailyStarterHeldItemPool, trainerHeldItemPool, wildHeldItemPool } from "./held-item-pool"; + +/** + * Initialize the wild held item pool + */ +function initWildHeldItemPool() { + wildHeldItemPool[RewardTier.COMMON] = [{ entry: HeldItemCategoryId.BERRY, weight: 1 }]; + wildHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }]; + wildHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }, + { entry: HeldItemId.WHITE_HERB, weight: 0 }, + ]; + wildHeldItemPool[RewardTier.ROGUE] = [{ entry: HeldItemId.LUCKY_EGG, weight: 4 }]; + wildHeldItemPool[RewardTier.MASTER] = [{ entry: HeldItemId.GOLDEN_EGG, weight: 1 }]; +} + +/** + * Initialize the trainer pokemon held item pool + */ +function initTrainerHeldItemPool() { + trainerHeldItemPool[RewardTier.COMMON] = [ + { entry: HeldItemCategoryId.BERRY, weight: 8 }, + { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }, + ]; + trainerHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }]; + trainerHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 10 }, + { entry: HeldItemId.WHITE_HERB, weight: 0 }, + ]; + trainerHeldItemPool[RewardTier.ROGUE] = [ + { entry: HeldItemId.FOCUS_BAND, weight: 2 }, + { entry: HeldItemId.LUCKY_EGG, weight: 4 }, + { entry: HeldItemId.QUICK_CLAW, weight: 1 }, + { entry: HeldItemId.GRIP_CLAW, weight: 1 }, + { entry: HeldItemId.WIDE_LENS, weight: 1 }, + ]; + trainerHeldItemPool[RewardTier.MASTER] = [ + { entry: HeldItemId.KINGS_ROCK, weight: 1 }, + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + { entry: HeldItemId.SCOPE_LENS, weight: 1 }, + ]; +} + +/** + * Initialize the daily starter held item pool + */ +function initDailyStarterModifierPool() { + dailyStarterHeldItemPool[RewardTier.COMMON] = [ + { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }, + { entry: HeldItemCategoryId.BERRY, weight: 3 }, + ]; + dailyStarterHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }]; + dailyStarterHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemId.REVIVER_SEED, weight: 4 }, + { entry: HeldItemId.SOOTHE_BELL, weight: 1 }, + { entry: HeldItemId.SOUL_DEW, weight: 1 }, + { entry: HeldItemId.GOLDEN_PUNCH, weight: 1 }, + ]; + dailyStarterHeldItemPool[RewardTier.ROGUE] = [ + { entry: HeldItemId.GRIP_CLAW, weight: 5 }, + { entry: HeldItemId.BATON, weight: 2 }, + { entry: HeldItemId.FOCUS_BAND, weight: 5 }, + { entry: HeldItemId.QUICK_CLAW, weight: 3 }, + { entry: HeldItemId.KINGS_ROCK, weight: 3 }, + ]; + dailyStarterHeldItemPool[RewardTier.MASTER] = [ + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + ]; +} + +export function initHeldItemPools() { + // Default held item pools for specific scenarios + initWildHeldItemPool(); + initTrainerHeldItemPool(); + initDailyStarterModifierPool(); +} diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 60697333600..3d558097d78 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -1,11 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { - dailyStarterModifierPool, - enemyBuffModifierPool, - modifierPool, - trainerModifierPool, - wildModifierPool, -} from "#app/modifier/modifier-pools"; +import { enemyBuffModifierPool, modifierPool } from "#app/modifier/modifier-pools"; import { globalScene } from "#app/global-scene"; import { DoubleBattleChanceBoosterModifier, SpeciesCritBoosterModifier, TurnStatusEffectModifier } from "./modifier"; import { WeightedModifierType } from "./modifier-type"; @@ -27,35 +21,6 @@ import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; // biome-ignore lint/correctness/noUnusedImports: This is used in a tsdoc comment import type { initModifierTypes } from "./modifier-type"; -/** - * Initialize the wild modifier pool - */ -function initWildModifierPool() { - wildModifierPool[ModifierTier.COMMON] = [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - wildModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }); - wildModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - wildModifierPool[ModifierTier.ROGUE] = [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - wildModifierPool[ModifierTier.MASTER] = [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - /** * Initialize the common modifier pool */ @@ -649,46 +614,6 @@ function initMasterModifierPool() { }); } -function initTrainerModifierPool() { - trainerModifierPool[ModifierTier.COMMON] = [ - new WeightedModifierType(modifierTypes.BERRY, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - trainerModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }); - trainerModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - trainerModifierPool[ModifierTier.ROGUE] = [ - new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), - new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), - new WeightedModifierType(modifierTypes.WIDE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - trainerModifierPool[ModifierTier.MASTER] = [ - new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - /** * Initialize the enemy buff modifier pool */ @@ -737,51 +662,6 @@ function initEnemyBuffModifierPool() { }); } -/** - * Initialize the daily starter modifier pool - */ -function initDailyStarterModifierPool() { - dailyStarterModifierPool[ModifierTier.COMMON] = [ - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), - new WeightedModifierType(modifierTypes.BERRY, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - dailyStarterModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map( - m => { - m.setTier(ModifierTier.GREAT); - return m; - }, - ); - dailyStarterModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), - new WeightedModifierType(modifierTypes.SOUL_DEW, 1), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - dailyStarterModifierPool[ModifierTier.ROGUE] = [ - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - dailyStarterModifierPool[ModifierTier.MASTER] = [ - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - /** * Initialize {@linkcode modifierPool} with the initial set of modifier types. * {@linkcode initModifierTypes} MUST be called before this function. @@ -798,7 +678,6 @@ export function initModifierPools() { initWildModifierPool(); initTrainerModifierPool(); initEnemyBuffModifierPool(); - initDailyStarterModifierPool(); } /** diff --git a/src/modifier/modifier-pools.ts b/src/modifier/modifier-pools.ts index 3396dca1f93..db6ebc700e5 100644 --- a/src/modifier/modifier-pools.ts +++ b/src/modifier/modifier-pools.ts @@ -7,10 +7,6 @@ import type { ModifierPool } from "#app/@types/modifier-types"; export const modifierPool: ModifierPool = {}; -export const wildModifierPool: ModifierPool = {}; - -export const trainerModifierPool: ModifierPool = {}; - export const enemyBuffModifierPool: ModifierPool = {}; export const dailyStarterModifierPool: ModifierPool = {}; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 5cbcc293e9d..744b46e9fef 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -46,7 +46,6 @@ import { MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, - type PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonLevelIncrementModifier, PokemonNatureChangeModifier, @@ -98,12 +97,12 @@ import { HeldItemId } from "#enums/held-item-id"; import { allHeldItems } from "#app/items/all-held-items"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; -import { berryTypeToHeldItem } from "#app/items/held-items/berry"; import { permanentStatToHeldItem, statBoostItems } from "#app/items/held-items/base-stat-booster"; import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoosterItemId } from "#app/items/held-items/stat-booster"; import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { getModifierPoolForType, getModifierType } from "#app/utils/modifier-utils"; +import { getModifierPoolForType } from "#app/utils/modifier-utils"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; +import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#app/items/held-item-pool"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -767,41 +766,14 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge } } -export class BerryReward extends HeldItemReward implements GeneratedPersistentModifierType { - private berryType: BerryType; - - constructor(berryType: BerryType) { - const itemId = berryTypeToHeldItem[berryType]; - super(itemId); - - this.berryType = berryType; - this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework - } - - getPregenArgs(): any[] { - return [this.berryType]; - } -} - class BerryRewardGenerator extends ModifierTypeGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { return new BerryReward(pregenArgs[0] as BerryType); } - const berryTypes = getEnumValues(BerryType); - let randBerryType: BerryType; - const rand = randSeedInt(12); - if (rand < 2) { - randBerryType = BerryType.SITRUS; - } else if (rand < 4) { - randBerryType = BerryType.LUM; - } else if (rand < 6) { - randBerryType = BerryType.LEPPA; - } else { - randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; - } - return new BerryReward(randBerryType); + const item = getNewBerryHeldItem(); + return new HeldItemReward(item); }); } } @@ -1166,47 +1138,9 @@ class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); } - // TODO: make this consider moves or abilities that change types - const attackMoveTypes = party.flatMap(p => - p - .getMoveset() - .map(m => m.getMove()) - .filter(m => m.is("AttackMove")) - .map(m => m.type), - ); - if (!attackMoveTypes.length) { - return null; - } + const item = getNewAttackTypeBoosterHeldItem(party); - const attackMoveTypeWeights = new Map(); - let totalWeight = 0; - for (const t of attackMoveTypes) { - const weight = attackMoveTypeWeights.get(t) ?? 0; - if (weight < 3) { - attackMoveTypeWeights.set(t, weight + 1); - totalWeight++; - } - } - - if (!totalWeight) { - return null; - } - - let type: PokemonType; - - const randInt = randSeedInt(totalWeight); - let weight = 0; - - for (const t of attackMoveTypeWeights.keys()) { - const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined - if (randInt <= weight + typeWeight) { - type = t; - break; - } - weight += typeWeight; - } - - return new AttackTypeBoosterReward(type!, TYPE_BOOST_ITEM_BOOST_PERCENT); + return item ? new HeldItemReward(item) : null; }); } } @@ -1217,8 +1151,7 @@ class BaseStatBoosterRewardGenerator extends ModifierTypeGenerator { if (pregenArgs) { return new BaseStatBoosterReward(pregenArgs[0]); } - const randStat: PermanentStat = randSeedInt(Stat.SPD + 1); - return new BaseStatBoosterReward(randStat); + return new HeldItemReward(getNewVitaminHeldItem()); }); } } From 694e82f2807e63c2959c8c8e65e6b6334aa9e744 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 15 Jun 2025 20:51:31 +0200 Subject: [PATCH 091/114] Fixed some MEs --- .../encounters/fiery-fallout-encounter.ts | 19 +++---- .../utils/encounter-phase-utils.ts | 4 +- .../utils/encounter-pokemon-utils.ts | 49 +------------------ 3 files changed, 11 insertions(+), 61 deletions(-) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 4b24bf9cada..006261d90cc 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -7,10 +7,7 @@ import { setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, - generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -36,7 +33,6 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { applyAbilityOverrideToPokemon, applyDamageToPokemon, - applyModifierTypeToPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -46,6 +42,9 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { getNewHeldItemFromCategory } from "#app/items/held-item-pool"; +import { allHeldItems } from "#app/items/all-held-items"; import { allAbilities } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ @@ -302,16 +301,14 @@ function giveLeadPokemonAttackTypeBoostItem() { const leadPokemon = globalScene.getPlayerParty()?.[0]; if (leadPokemon) { // Generate type booster held item, default to Charcoal if item fails to generate - let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; - if (!boosterModifierType) { - boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.FIRE, - ]) as AttackTypeBoosterModifierType; + let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon); + if (!item) { + item = HeldItemId.CHARCOAL; } - applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType); + leadPokemon.heldItemManager.add(item); const encounter = globalScene.currentBattle.mysteryEncounter!; - encounter.setDialogueToken("itemName", boosterModifierType.name); + encounter.setDialogueToken("itemName", allHeldItems[item].name); encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); queueEncounterMessage(`${namespace}:found_item`); } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 8a142a4813a..c2881708f6e 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -53,7 +53,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; import { timedEventManager } from "#app/global-event-manager"; -import type { HeldItemPropertyMap } from "#app/field/pokemon-held-item-manager"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -103,7 +103,7 @@ export interface EnemyPokemonConfig { /** Can set just the status, or pass a timer on the status turns */ status?: StatusEffect | [StatusEffect, number]; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; - heldItemConfig?: HeldItemPropertyMap; + heldItemConfig?: HeldItemConfiguration; tags?: BattlerTagType[]; dataSource?: PokemonData; tera?: PokemonType; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index f98efcb3396..fe6415a2a13 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import i18next from "i18next"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { @@ -387,42 +386,6 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb } } -/** - * Will attempt to add a new modifier to a Pokemon. - * If the Pokemon already has max stacks of that item, it will instead apply 'fallbackModifierType', if specified. - * @param scene - * @param pokemon - * @param modType - * @param fallbackModifierType - */ -export async function applyModifierTypeToPlayerPokemon( - pokemon: PlayerPokemon, - modType: PokemonHeldItemModifierType, - fallbackModifierType?: PokemonHeldItemModifierType, -) { - // Check if the Pokemon has max stacks of that item already - const modifier = modType.newModifier(pokemon); - const existing = globalScene.findModifier( - m => - m instanceof PokemonHeldItemModifier && - m.type.id === modType.id && - m.pokemonId === pokemon.id && - m.matchType(modifier), - ) as PokemonHeldItemModifier; - - // At max stacks - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { - if (!fallbackModifierType) { - return; - } - - // Apply fallback - return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType); - } - - globalScene.addModifier(modifier, false, false, false, true); -} - export function applyHeldItemWithFallback(pokemon: Pokemon, item: HeldItemId, fallbackItem?: HeldItemId) { const added = pokemon.heldItemManager.add(item); if (!added && fallbackItem) { @@ -694,20 +657,10 @@ export async function catchPokemon( } }; const addToParty = (slotIndex?: number) => { - const newPokemon = pokemon.addToParty(pokeballType, slotIndex); - const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + pokemon.addToParty(pokeballType, slotIndex); if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === 6) { globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { - globalScene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { if (globalScene.getPlayerParty().length === 6) { From aa84d03773d79a1f467575e6c1d8342c545afcfc Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 16 Jun 2025 00:12:11 +0200 Subject: [PATCH 092/114] Added function to assign items from a given configuration --- src/items/held-item-data-types.ts | 5 +++- src/items/held-item-pool.ts | 38 ++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts index ff291b04282..fa772d7fcd8 100644 --- a/src/items/held-item-data-types.ts +++ b/src/items/held-item-data-types.ts @@ -56,6 +56,10 @@ type HeldItemPoolEntry = { export type HeldItemPool = HeldItemPoolEntry[]; +export function isHeldItemPool(value: any): value is HeldItemPool { + return Array.isArray(value) && value.every(entry => "entry" in entry && "weight" in entry); +} + export type HeldItemTieredPool = { [key in RewardTier]?: HeldItemPool; }; @@ -63,7 +67,6 @@ export type HeldItemTieredPool = { type HeldItemConfigurationEntry = { entry: HeldItemId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool; count?: number | (() => number); - excluded?: HeldItemId[]; }; export type HeldItemConfiguration = HeldItemConfigurationEntry[]; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index ab778d68f74..8d2bbc413db 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -7,11 +7,14 @@ import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; import { + type HeldItemConfiguration, type HeldItemPool, type HeldItemSpecs, type HeldItemTieredPool, type HeldItemWeights, isHeldItemCategoryEntry, + isHeldItemPool, + isHeldItemSpecs, } from "./held-item-data-types"; import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; @@ -140,7 +143,7 @@ export function getNewHeldItemFromCategory( return null; } -function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemId | HeldItemSpecs { +function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon | Pokemon[]): HeldItemId | HeldItemSpecs { const weights = pool.map(p => (typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight)); const entry = pool[pickWeightedIndex(weights)].entry; @@ -155,3 +158,36 @@ function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemI return entry as HeldItemSpecs; } + +export function assignItemsFromConfiguration(config: HeldItemConfiguration, pokemon: Pokemon) { + config.forEach(item => { + const { entry, count } = item; + const actualCount = typeof count === "function" ? count() : (count ?? 1); + + if (typeof entry === "number") { + pokemon.heldItemManager.add(entry, actualCount); + } + + if (isHeldItemSpecs(entry)) { + pokemon.heldItemManager.add(entry); + } + + if (isHeldItemCategoryEntry(entry)) { + for (let i = 1; i <= actualCount; i++) { + const newItem = getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights); + if (newItem) { + pokemon.heldItemManager.add(newItem); + } + } + } + + if (isHeldItemPool(entry)) { + for (let i = 1; i <= actualCount; i++) { + const newItem = getNewHeldItemFromPool(entry, pokemon); + if (newItem) { + pokemon.heldItemManager.add(newItem); + } + } + } + }); +} From 74349d431bd6578dd4564d159e5a6457f9e2396d Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:59:58 +0200 Subject: [PATCH 093/114] Fixed held item evolution condition --- src/data/balance/pokemon-evolutions.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 5334b176636..636727fd72b 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -15,7 +15,6 @@ import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; import { allMoves } from "#app/data/data-lists"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; -import { allHeldItems } from "#app/items/all-held-items"; import { HeldItemId } from "#enums/held-item-id"; export enum SpeciesWildEvolutionDelay { @@ -111,7 +110,7 @@ type EvolutionConditionData = {key: typeof EvoCondKey.GENDER, gender: Gender} | {key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} | {key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} | - {key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} | + {key: typeof EvoCondKey.HELD_ITEM, itemKey: HeldItemId} | {key: typeof EvoCondKey.NATURE, nature: Nature[]} | {key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} | {key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} | @@ -202,7 +201,7 @@ export class SpeciesEvolutionCondition { case EvoCondKey.SPECIES_CAUGHT: return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr; case EvoCondKey.HELD_ITEM: - return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey) + return pokemon.heldItemManager.hasItem(cond.itemKey); } }); } @@ -1761,8 +1760,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CLAMPERL]: [ - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_TOOTH}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_SCALE}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) From 847b938480d59cb22b44fb9693761e4c0cc391ac Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 19:28:40 +0200 Subject: [PATCH 094/114] Fixed some ui handlers --- src/modifier/modifier-bar.ts | 9 +++------ src/ui/modifier-select-ui-handler.ts | 11 +++++++---- src/ui/party-ui-handler.ts | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/modifier/modifier-bar.ts b/src/modifier/modifier-bar.ts index bef021d7484..1581f26d63e 100644 --- a/src/modifier/modifier-bar.ts +++ b/src/modifier/modifier-bar.ts @@ -62,18 +62,15 @@ export class ModifierBar extends Phaser.GameObjects.Container { * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed */ - updateModifiers(modifiers: PersistentModifier[], pokemonA: Pokemon, pokemonB?: Pokemon, hideHeldItems = false) { + updateModifiers(modifiers: PersistentModifier[], pokemonA?: Pokemon, pokemonB?: Pokemon) { this.removeAll(true); const sortedVisibleModifiers = modifiers.filter(m => m.isIconVisible()).sort(modifierSortFunc); - const heldItemsA = pokemonA.getHeldItems().sort(heldItemSortFunc); + const heldItemsA = pokemonA ? pokemonA.getHeldItems().sort(heldItemSortFunc) : []; const heldItemsB = pokemonB ? pokemonB.getHeldItems().sort(heldItemSortFunc) : []; - this.totalVisibleLength = sortedVisibleModifiers.length; - if (!hideHeldItems) { - this.totalVisibleLength += heldItemsA.length + heldItemsB.length; - } + this.totalVisibleLength = sortedVisibleModifiers.length + heldItemsA.length + heldItemsB.length; sortedVisibleModifiers.forEach((modifier: PersistentModifier, i: number) => { const icon = modifier.getIcon(); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index fceb6a8d175..13f98fe42e5 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -5,7 +5,7 @@ import { getPokeballAtlasKey } from "#app/data/pokeball"; import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { UiMode } from "#enums/ui-mode"; -import { LockModifierTiersModifier, PokemonHeldItemModifier, HealShopCostModifier } from "../modifier/modifier"; +import { LockModifierTiersModifier, HealShopCostModifier } from "../modifier/modifier"; import { handleTutorial, Tutorial } from "../tutorial"; import { Button } from "#enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; @@ -183,7 +183,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.player = args[0]; const partyHasHeldItem = - this.player && !!globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable).length; + globalScene + .getPlayerParty() + .map(p => p.heldItemManager.getTransferableHeldItems().length) + .reduce((tot, i) => tot + i, 0) > 0; const canLockRarities = !!globalScene.findModifier(m => m instanceof LockModifierTiersModifier); this.transferButtonContainer.setVisible(false); @@ -265,8 +268,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { // const maxUpgradeCount = typeOptions.map(to => to.upgradeCount).reduce((max, current) => Math.max(current, max), 0); const maxUpgradeCount = 0; - /* Force updateModifiers without pokemonSpecificModifiers */ - globalScene.getModifierBar().updateModifiers(globalScene.modifiers, true); + /* Force updateModifiers without pokemon held items */ + globalScene.getModifierBar().updateModifiers(globalScene.modifiers); /* Multiplies the appearance duration by the speed parameter so that it is always constant, and avoids "flashbangs" at game speed x5 */ globalScene.showShopOverlay(750 * globalScene.gameSpeed); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 217b5bb1d09..95d1072346a 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1369,7 +1369,8 @@ export default class PartyUiHandler extends MessageUiHandler { optionName = move.getName(); } break; - default: + } + default: { const formChangeItems = this.getFormChangeItems(pokemon); if (formChangeItems && option >= PartyOption.FORM_CHANGE_ITEM) { const item = formChangeItems[option - PartyOption.FORM_CHANGE_ITEM]; From a15920a605ed02ceb22c7dc0446bfb51f3c28229 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 19:33:55 +0200 Subject: [PATCH 095/114] Fixed select-modifier-phase --- src/phases/select-modifier-phase.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 64c94ff7e16..a78978fe4ce 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -229,7 +229,7 @@ export class SelectModifierPhase extends BattlePhase { party[toSlotIndex], true, itemQuantity, - true, + undefined, false, ); } else { @@ -287,7 +287,11 @@ export class SelectModifierPhase extends BattlePhase { } // Opens the party menu specifically for fusions - private openFusionMenu(modifierType, cost, modifierSelectCallback) { + private openFusionMenu( + modifierType: PokemonModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): void { const party = globalScene.getPlayerParty(); globalScene.ui.setModeWithoutClear( UiMode.PARTY, @@ -313,7 +317,11 @@ export class SelectModifierPhase extends BattlePhase { } // Opens the party menu to apply one of various modifiers - private openModifierMenu(modifierType, cost, modifierSelectCallback) { + private openModifierMenu( + modifierType: PokemonModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): void { const party = globalScene.getPlayerParty(); const pokemonModifierType = modifierType as PokemonModifierType; const isMoveModifier = modifierType instanceof PokemonMoveModifierType; @@ -382,17 +390,15 @@ export class SelectModifierPhase extends BattlePhase { // Function that determines how many reward slots are available private getModifierCount(): number { const modifierCountHolder = new NumberHolder(3); - if (this.isPlayer()) { - globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder); - globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder); - } + globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder); + globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder); // If custom modifiers are specified, overrides default item count if (this.customModifierSettings) { const newItemCount = - (this.customModifierSettings.guaranteedModifierTiers?.length || 0) + - (this.customModifierSettings.guaranteedModifierTypeOptions?.length || 0) + - (this.customModifierSettings.guaranteedModifierTypeFuncs?.length || 0); + (this.customModifierSettings.guaranteedModifierTiers?.length ?? 0) + + (this.customModifierSettings.guaranteedModifierTypeOptions?.length ?? 0) + + (this.customModifierSettings.guaranteedModifierTypeFuncs?.length ?? 0); if (this.customModifierSettings.fillRemaining) { const originalCount = modifierCountHolder.value; modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; @@ -465,6 +471,7 @@ export class SelectModifierPhase extends BattlePhase { } getModifierTypeOptions(modifierCount: number): ModifierTypeOption[] { + console.log("HERE WE ARE", modifierCount, this.customModifierSettings); return getPlayerModifierTypeOptions( modifierCount, globalScene.getPlayerParty(), From 9611948d80d35a951fa929d26ec72e467c9d3cbb Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 21:27:03 +0200 Subject: [PATCH 096/114] Some changes to modifier files --- src/enums/modifier-pool-type.ts | 7 ++- src/modifier/modifier.ts | 79 +++------------------------------ src/utils/modifier-utils.ts | 12 +---- 3 files changed, 11 insertions(+), 87 deletions(-) diff --git a/src/enums/modifier-pool-type.ts b/src/enums/modifier-pool-type.ts index 0d2b92ba80d..c4928516f71 100644 --- a/src/enums/modifier-pool-type.ts +++ b/src/enums/modifier-pool-type.ts @@ -1,7 +1,10 @@ export enum ModifierPoolType { PLAYER, - WILD, - TRAINER, ENEMY_BUFF, DAILY_STARTER } + +export enum HeldItemPoolType { + WILD, + TRAINER, +} diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 752b238c701..89fee2dbb8f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,6 +1,5 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves, modifierTypes } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { getStatusEffectHealText } from "#app/data/status-effect"; import type Pokemon from "#app/field/pokemon"; @@ -33,42 +32,26 @@ import { type ModifierType, type TerastallizeModifierType, type TmModifierType, - getModifierType, - ModifierTypeGenerator, modifierTypes, } from "./modifier-type"; -import { getModifierType } from "#app/utils/modifier-utils"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { globalScene } from "#app/global-scene"; import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; export type ModifierPredicate = (modifier: Modifier) => boolean; - const iconOverflowIndex = 24; export const modifierSortFunc = (a: Modifier, b: Modifier): number => { const itemNameMatch = a.type.name.localeCompare(b.type.name); const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; - const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; - //First sort by pokemonID - if (aId < bId) { - return 1; + //Then sort by item type + if (typeNameMatch === 0) { + return itemNameMatch; + //Finally sort by item name } - if (aId > bId) { - return -1; - } - if (aId === bId) { - //Then sort by item type - if (typeNameMatch === 0) { - return itemNameMatch; - //Finally sort by item name - } - return typeNameMatch; - } - return 0; + return typeNameMatch; }; export class ModifierBar extends Phaser.GameObjects.Container { @@ -172,23 +155,6 @@ export abstract class Modifier { return this instanceof targetModifier; } - /** - * Return whether this modifier is of the given class - * - * @remarks - * Used to avoid requiring the caller to have imported the specific modifier class, avoiding circular dependencies. - * - * @param modifier - The modifier to check against - * @returns Whether the modiifer is an instance of the given type - */ - public is(modifier: T): this is ModifierInstanceMap[T] { - const targetModifier = ModifierClassMap[modifier]; - if (!targetModifier) { - return false; - } - return this instanceof targetModifier; - } - match(_modifier: Modifier): boolean { return false; } @@ -1289,41 +1255,6 @@ export class ExpBalanceModifier extends PersistentModifier { } } -export class MoneyRewardModifier extends ConsumableModifier { - private moneyMultiplier: number; - - constructor(type: ModifierType, moneyMultiplier: number) { - super(type); - - this.moneyMultiplier = moneyMultiplier; - } - - /** - * Applies {@linkcode MoneyRewardModifier} - * @returns always `true` - */ - override apply(): boolean { - const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - - globalScene.addMoney(moneyAmount.value); - - globalScene.getPlayerParty().map(p => { - if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { - const factor = Math.min(Math.floor(this.moneyMultiplier), 3); - const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( - p, - factor, - ) as EvoTrackerModifier; - globalScene.addModifier(modifier); - } - }); - - return true; - } -} - export class MoneyMultiplierModifier extends PersistentModifier { match(modifier: Modifier): boolean { return modifier instanceof MoneyMultiplierModifier; diff --git a/src/utils/modifier-utils.ts b/src/utils/modifier-utils.ts index 3be4af3730c..dfd04fa852e 100644 --- a/src/utils/modifier-utils.ts +++ b/src/utils/modifier-utils.ts @@ -1,11 +1,5 @@ import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { - dailyStarterModifierPool, - enemyBuffModifierPool, - modifierPool, - trainerModifierPool, - wildModifierPool, -} from "#app/modifier/modifier-pools"; +import { dailyStarterModifierPool, enemyBuffModifierPool, modifierPool } from "#app/modifier/modifier-pools"; import type { ModifierPool, ModifierTypeFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import type { ModifierType } from "#app/modifier/modifier-type"; @@ -14,10 +8,6 @@ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool switch (poolType) { case ModifierPoolType.PLAYER: return modifierPool; - case ModifierPoolType.WILD: - return wildModifierPool; - case ModifierPoolType.TRAINER: - return trainerModifierPool; case ModifierPoolType.ENEMY_BUFF: return enemyBuffModifierPool; case ModifierPoolType.DAILY_STARTER: From 39be97ef8eb4eb5602f90d5d68f6680a805a6524 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 22:03:25 +0200 Subject: [PATCH 097/114] Daily run items are generated within the new system --- src/items/held-item-pool.ts | 10 +++++++--- src/phases/title-phase.ts | 8 +++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 8d2bbc413db..676fa798765 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -26,14 +26,14 @@ export const trainerHeldItemPool: HeldItemTieredPool = {}; export const dailyStarterHeldItemPool: HeldItemTieredPool = {}; -export function getDailyRunStarterHeldItems(party: PlayerPokemon[]) { +export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { for (const p of party) { for (let m = 0; m < 3; m++) { const tierValue = randSeedInt(64); const tier = getDailyRewardTier(tierValue); - const item = getNewHeldItemFromPool(dailyStarterHeldItemPool[tier] as HeldItemPool, p); + const item = getNewHeldItemFromPool(dailyStarterHeldItemPool[tier] as HeldItemPool, party); p.heldItemManager.add(item); } } @@ -143,8 +143,12 @@ export function getNewHeldItemFromCategory( return null; } +function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon | Pokemon[]): number[] { + return pool.map(p => (typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight)); +} + function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon | Pokemon[]): HeldItemId | HeldItemSpecs { - const weights = pool.map(p => (typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight)); + const weights = getPoolWeights(pool, pokemon); const entry = pool[pickWeightedIndex(weights)].entry; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 5e36081b899..72132635c1b 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -6,9 +6,7 @@ import { getBiomeKey } from "#app/field/arena"; import { GameMode, getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import type { Modifier } from "#app/modifier/modifier"; -import { getDailyRunStarterModifiers, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Phase } from "#app/phase"; import type { SessionSaveData } from "#app/system/game-data"; import { Unlockables } from "#enums/unlockables"; @@ -20,6 +18,7 @@ import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils/c import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; +import { assignDailyRunStarterHeldItems } from "#app/items/held-item-pool"; export class TitlePhase extends Phase { public readonly phaseName = "TitlePhase"; @@ -238,8 +237,6 @@ export class TitlePhase extends Phase { loadPokemonAssets.push(starterPokemon.loadAssets()); } - regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); - const modifiers: Modifier[] = Array(3) .fill(null) .map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) @@ -249,9 +246,10 @@ export class TitlePhase extends Phase { .map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier()), ) .concat([modifierTypes.MAP().withIdFromFunc(modifierTypes.MAP).newModifier()]) - .concat(getDailyRunStarterModifiers(party)) .filter(m => m !== null); + assignDailyRunStarterHeldItems(party); + for (const m of modifiers) { globalScene.addModifier(m, true, false, false, true); } From f2a339fbe5a4b385579b267d24c15918917ed989 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:08:14 +0200 Subject: [PATCH 098/114] Held item generation for enemies follows the new scheme --- src/battle-scene.ts | 25 ++++---- src/enums/modifier-pool-type.ts | 2 +- src/field/pokemon-held-item-manager.ts | 12 ++++ src/items/held-item-pool.ts | 89 +++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ff37fcc3b30..8afceb5571a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -51,13 +51,12 @@ import { allMoves } from "./data/data-lists"; import { MusicPreference } from "#app/system/settings/settings"; import { getDefaultModifierTypeForTier, - getEnemyHeldItemsForWave, getLuckString, getLuckTextTint, getPartyLuckValue, } from "#app/modifier/modifier-type"; import { getModifierPoolForType } from "./utils/modifier-utils"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { HeldItemPoolType, ModifierPoolType } from "#enums/modifier-pool-type"; import AbilityBar from "#app/ui/ability-bar"; import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs"; import { allAbilities } from "./data/data-lists"; @@ -157,7 +156,8 @@ import { allHeldItems, applyHeldItems } from "./items/all-held-items"; import { ITEM_EFFECT } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; import { HeldItemId } from "#enums/held-item-id"; -import type { HeldItemPropertyMap } from "./field/pokemon-held-item-manager"; +import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "./items/held-item-pool"; +import type { HeldItemConfiguration } from "./items/held-item-data-types"; const DEBUG_RNG = false; @@ -2758,9 +2758,12 @@ export default class BattleScene extends SceneBase { } const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); - const data = source.heldItemManager[heldItemId].data; + const itemSpecs = source.heldItemManager.getItemSpecs(heldItemId); + if (!itemSpecs) { + return false; + } source.heldItemManager.remove(heldItemId, countTaken); - target.heldItemManager.add(heldItemId, countTaken, data); + target.heldItemManager.add(itemSpecs); if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false); @@ -2801,7 +2804,7 @@ export default class BattleScene extends SceneBase { return countTaken > 0; } - generateEnemyModifiers(heldItemConfigs?: HeldItemPropertyMap[]): Promise { + generateEnemyModifiers(heldItemConfigs?: HeldItemConfiguration[]): Promise { return new Promise(resolve => { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return resolve(); @@ -2824,7 +2827,7 @@ export default class BattleScene extends SceneBase { party.forEach((enemyPokemon: EnemyPokemon, i: number) => { if (heldItemConfigs && i < heldItemConfigs.length && heldItemConfigs[i]) { - enemyPokemon.heldItemManager.overrideItems(heldItemConfigs[i]); + assignItemsFromConfiguration(heldItemConfigs[i], enemyPokemon); } else { const isBoss = enemyPokemon.isBoss() || @@ -2845,13 +2848,13 @@ export default class BattleScene extends SceneBase { if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyHeldItemsForWave( + assignEnemyHeldItemsForWave( difficultyWaveIndex, count, - [enemyPokemon], - this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, + enemyPokemon, + this.currentBattle.battleType === BattleType.TRAINER ? HeldItemPoolType.TRAINER : HeldItemPoolType.WILD, upgradeChance, - ).map(itemId => enemyPokemon.heldItemManager.add(itemId)); + ); } return true; }); diff --git a/src/enums/modifier-pool-type.ts b/src/enums/modifier-pool-type.ts index c4928516f71..99b698e568c 100644 --- a/src/enums/modifier-pool-type.ts +++ b/src/enums/modifier-pool-type.ts @@ -1,10 +1,10 @@ export enum ModifierPoolType { PLAYER, ENEMY_BUFF, - DAILY_STARTER } export enum HeldItemPoolType { WILD, TRAINER, + DAILY_STARTER, } diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index c30b660c855..71ffed6750c 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -20,6 +20,18 @@ export class PokemonItemManager { this.formChangeItems = {}; } + getItemSpecs(id: HeldItemId): HeldItemSpecs | undefined { + const item = this.heldItems[id]; + if (item) { + const itemSpecs: HeldItemSpecs = { + ...item, + id, + }; + return itemSpecs; + } + return undefined; + } + getHeldItems(): number[] { return Object.keys(this.heldItems).map(k => Number(k)); } diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 676fa798765..c79936305d4 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -1,8 +1,9 @@ -import type { PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { coerceArray, getEnumValues, randSeedFloat, randSeedInt } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; @@ -33,7 +34,7 @@ export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { const tier = getDailyRewardTier(tierValue); - const item = getNewHeldItemFromPool(dailyStarterHeldItemPool[tier] as HeldItemPool, party); + const item = getNewHeldItemFromPool(getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, party); p.heldItemManager.add(item); } } @@ -55,6 +56,90 @@ function getDailyRewardTier(tierValue: number): RewardTier { return RewardTier.MASTER; } +function getHeldItemPool(poolType: HeldItemPoolType): HeldItemTieredPool { + let pool: HeldItemTieredPool; + switch (poolType) { + case HeldItemPoolType.WILD: + pool = wildHeldItemPool; + break; + case HeldItemPoolType.TRAINER: + pool = trainerHeldItemPool; + break; + case HeldItemPoolType.DAILY_STARTER: + pool = dailyStarterHeldItemPool; + break; + } + return pool; +} + +// TODO: Add proper documentation to this function (once it fully works...) +export function assignEnemyHeldItemsForWave( + waveIndex: number, + count: number, + enemy: EnemyPokemon, + poolType: HeldItemPoolType.WILD | HeldItemPoolType.TRAINER, + upgradeChance = 0, +): void { + for (let i = 1; i <= count; i++) { + const item = getNewHeldItemFromTieredPool( + getHeldItemPool(poolType), + [enemy], + upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, + ); + enemy.heldItemManager.add(item); + } + if (!(waveIndex % 1000)) { + enemy.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE); + } +} + +function getRandomTier(): RewardTier { + const tierValue = randSeedInt(1024); + + if (tierValue > 255) { + return RewardTier.COMMON; + } + if (tierValue > 60) { + return RewardTier.GREAT; + } + if (tierValue > 12) { + return RewardTier.ULTRA; + } + if (tierValue) { + return RewardTier.ROGUE; + } + return RewardTier.MASTER; +} + +function determineEnemyPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RewardTier { + let tier = getRandomTier(); + + if (!upgradeCount) { + upgradeCount = 0; + } + + tier += upgradeCount; + while (tier && !pool[tier]?.length) { + tier--; + if (upgradeCount) { + upgradeCount--; + } + } + + return tier; +} + +function getNewHeldItemFromTieredPool( + pool: HeldItemTieredPool, + pokemon: Pokemon | Pokemon[], + upgradeCount: number, +): HeldItemId | HeldItemSpecs { + const tier = determineEnemyPoolTier(pool, upgradeCount); + const tierPool = pool[tier]; + + return getNewHeldItemFromPool(tierPool!, pokemon); +} + function pickWeightedIndex(weights: number[]): number { const totalWeight = weights.reduce((sum, w) => sum + w, 0); From 7523f6d9c0240c7b8acded2ea1e4b51a4cc557e5 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:36:51 +0200 Subject: [PATCH 099/114] Fixed init-modifier-pools.ts --- src/modifier/init-modifier-pools.ts | 53 ++++++++++++----------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 3d558097d78..16ca03fae92 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -1,14 +1,12 @@ import type Pokemon from "#app/field/pokemon"; import { enemyBuffModifierPool, modifierPool } from "#app/modifier/modifier-pools"; import { globalScene } from "#app/global-scene"; -import { DoubleBattleChanceBoosterModifier, SpeciesCritBoosterModifier, TurnStatusEffectModifier } from "./modifier"; +import { DoubleBattleChanceBoosterModifier } from "./modifier"; import { WeightedModifierType } from "./modifier-type"; import { ModifierTier } from "../enums/modifier-tier"; import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import { PokeballType } from "#enums/pokeball"; -import { BerryModifier } from "./modifier"; -import { BerryType } from "#enums/berry-type"; import { SpeciesId } from "#enums/species-id"; import { timedEventManager } from "#app/global-event-manager"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; @@ -20,6 +18,8 @@ import { AbilityId } from "#enums/ability-id"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; // biome-ignore lint/correctness/noUnusedImports: This is used in a tsdoc comment import type { initModifierTypes } from "./modifier-type"; +import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; /** * Initialize the common modifier pool @@ -57,7 +57,7 @@ function initCommonModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -76,7 +76,7 @@ function initCommonModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -113,12 +113,10 @@ function initGreatModifierPool() { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => allHeldItems[i].effect === p.status?.effect), ).length, 3, ); @@ -179,12 +177,10 @@ function initGreatModifierPool() { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => allHeldItems[i].effect === p.status?.effect), ).length, 3, ); @@ -204,7 +200,7 @@ function initGreatModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -223,7 +219,7 @@ function initGreatModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -333,7 +329,7 @@ function initUltraModifierPool() { (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) ) { // Check if Pokemon is already holding an Eviolite - return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + return !p.heldItemManager.hasItem(HeldItemId.EVIOLITE); } return false; }) @@ -350,7 +346,7 @@ function initUltraModifierPool() { // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear return party.some( p => - !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && + !p.heldItemManager.hasItem(HeldItemId.LEEK) && (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), ) @@ -363,7 +359,7 @@ function initUltraModifierPool() { modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -409,7 +405,7 @@ function initUltraModifierPool() { modifierTypes.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -455,13 +451,8 @@ function initUltraModifierPool() { modifierTypes.MYSTICAL_ROCK, (party: Pokemon[]) => { return party.some(p => { - let isHoldingMax = false; - for (const i of p.getHeldItems()) { - if (i.type.id === "MYSTICAL_ROCK") { - isHoldingMax = i.getStackCount() === i.getMaxStackCount(); - break; - } - } + const stack = p.heldItemManager.getStack(HeldItemId.MYSTICAL_ROCK); + const isHoldingMax = stack === allHeldItems[HeldItemId.MYSTICAL_ROCK].maxStackCount; if (!isHoldingMax) { const moveset = p.getMoveset(true).map(m => m.moveId); @@ -675,8 +666,6 @@ export function initModifierPools() { initMasterModifierPool(); // Modifier pools for specific scenarios - initWildModifierPool(); - initTrainerModifierPool(); initEnemyBuffModifierPool(); } From c58c1fd21ba9afcd9ca301a7722db7673274d10f Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:37:53 +0200 Subject: [PATCH 100/114] Held item overrides now use the new system --- src/modifier/modifier.ts | 174 ++++---------------------------------- src/overrides.ts | 5 +- src/phases/title-phase.ts | 2 +- 3 files changed, 19 insertions(+), 162 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 89fee2dbb8f..05ae9a63cbb 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -25,112 +25,23 @@ import { type TempBattleStat, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import type { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; -import { - type DoubleBattleChanceBoosterModifierType, - type EvolutionItemModifierType, - type ModifierOverride, - type ModifierType, - type TerastallizeModifierType, - type TmModifierType, - modifierTypes, +import type { + DoubleBattleChanceBoosterModifierType, + EvolutionItemModifierType, + ModifierOverride, + ModifierType, + TerastallizeModifierType, + TmModifierType, } from "./modifier-type"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { globalScene } from "#app/global-scene"; import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import { modifierTypes } from "#app/data/data-lists"; export type ModifierPredicate = (modifier: Modifier) => boolean; -const iconOverflowIndex = 24; - -export const modifierSortFunc = (a: Modifier, b: Modifier): number => { - const itemNameMatch = a.type.name.localeCompare(b.type.name); - const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - - //Then sort by item type - if (typeNameMatch === 0) { - return itemNameMatch; - //Finally sort by item name - } - return typeNameMatch; -}; - -export class ModifierBar extends Phaser.GameObjects.Container { - private player: boolean; - private modifierCache: PersistentModifier[]; - - constructor(enemy?: boolean) { - super(globalScene, 1 + (enemy ? 302 : 0), 2); - - this.player = !enemy; - this.setScale(0.5); - } - - /** - * Method to update content displayed in {@linkcode ModifierBar} - * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} - * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed - */ - updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) { - this.removeAll(true); - - const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); - const nonPokemonSpecificModifiers = visibleIconModifiers - .filter(m => !(m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - const pokemonSpecificModifiers = visibleIconModifiers - .filter(m => (m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - - const sortedVisibleIconModifiers = hideHeldItems - ? nonPokemonSpecificModifiers - : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); - - sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { - const icon = modifier.getIcon(); - if (i >= iconOverflowIndex) { - icon.setVisible(false); - } - this.add(icon); - this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => { - globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(true); - } - }); - icon.on("pointerout", () => { - globalScene.ui.hideTooltip(); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(false); - } - }); - }); - - for (const icon of this.getAll()) { - this.sendToBack(icon); - } - - this.modifierCache = modifiers; - } - - updateModifierOverflowVisibility(ignoreLimit: boolean) { - const modifierIcons = this.getAll().reverse(); - for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) { - modifier.setVisible(ignoreLimit); - } - } - - setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: number) { - const rowIcons: number = 12 + 6 * Math.max(Math.ceil(Math.min(modifierCount, 24) / 12) - 2, 0); - - const x = ((this.getIndex(icon) % rowIcons) * 26) / (rowIcons / 12); - const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; - - icon.setPosition(this.player ? x : -x, y); - } -} - export abstract class Modifier { public type: ModifierType; @@ -1900,7 +1811,7 @@ export function overrideModifiers(isPlayer = true): void { const modifierFunc = modifierTypes[item.name]; let modifierType: ModifierType | null = modifierFunc(); - if (modifierType.is("ModifierTypeGenerator")) { + if (modifierType?.is("ModifierTypeGenerator")) { const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; modifierType = modifierType.generateType([], pregenArgs); } @@ -1912,7 +1823,7 @@ export function overrideModifiers(isPlayer = true): void { if (isPlayer) { globalScene.addModifier(modifier, true, false, false, true); } else { - globalScene.addEnemyModifier(modifier, true, true); + globalScene.addEnemyModifier(modifier, true); } } } @@ -1926,7 +1837,7 @@ export function overrideModifiers(isPlayer = true): void { * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) */ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { - const heldItemsOverride: ModifierOverride[] = isPlayer + const heldItemsOverride: HeldItemConfiguration = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) { @@ -1934,31 +1845,10 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { } if (!isPlayer) { - globalScene.clearEnemyHeldItemModifiers(pokemon); + pokemon.heldItemManager.clearItems(); } - for (const item of heldItemsOverride) { - const modifierFunc = modifierTypes[item.name]; - let modifierType: ModifierType | null = modifierFunc(); - const qty = item.count || 1; - - if (modifierType.is("ModifierTypeGenerator")) { - const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; - modifierType = modifierType.generateType([], pregenArgs); - } - - const heldItemModifier = - modifierType && (modifierType.withIdFromFunc(modifierFunc).newModifier(pokemon) as PokemonHeldItemModifier); - if (heldItemModifier) { - heldItemModifier.pokemonId = pokemon.id; - heldItemModifier.stackCount = qty; - if (isPlayer) { - globalScene.addModifier(heldItemModifier, true, false, false, true); - } else { - globalScene.addEnemyModifier(heldItemModifier, true, true); - } - } - } + assignItemsFromConfiguration(heldItemsOverride, pokemon); } /** @@ -1981,30 +1871,8 @@ const ModifierClassMap = Object.freeze({ MegaEvolutionAccessModifier, GigantamaxAccessModifier, TerastallizeAccessModifier, - PokemonHeldItemModifier, - LapsingPokemonHeldItemModifier, - BaseStatModifier, - EvoTrackerModifier, - PokemonBaseStatTotalModifier, - PokemonBaseStatFlatModifier, - PokemonIncrementingStatModifier, - StatBoosterModifier, - SpeciesStatBoosterModifier, - CritBoosterModifier, - SpeciesCritBoosterModifier, - AttackTypeBoosterModifier, - SurviveDamageModifier, - BypassSpeedChanceModifier, - FlinchChanceModifier, - TurnHealModifier, - TurnStatusEffectModifier, - HitHealModifier, LevelIncrementBoosterModifier, - BerryModifier, PreserveBerryModifier, - PokemonInstantReviveModifier, - ResetNegativeStatStageModifier, - FieldEffectModifier, ConsumablePokemonModifier, TerrastalizeModifier, PokemonHpRestoreModifier, @@ -2022,16 +1890,8 @@ const ModifierClassMap = Object.freeze({ MultipleParticipantExpBonusModifier, HealingBoosterModifier, ExpBoosterModifier, - PokemonExpBoosterModifier, ExpShareModifier, ExpBalanceModifier, - PokemonFriendshipBoosterModifier, - PokemonNatureWeightModifier, - PokemonMoveAccuracyBoosterModifier, - PokemonMultiHitModifier, - PokemonFormChangeItemModifier, - MoneyRewardModifier, - DamageMoneyRewardModifier, MoneyInterestModifier, HiddenAbilityRateBoosterModifier, ShinyRateBoosterModifier, @@ -2039,10 +1899,6 @@ const ModifierClassMap = Object.freeze({ LockModifierTiersModifier, HealShopCostModifier, BoostBugSpawnModifier, - SwitchEffectTransferModifier, - HeldItemTransferModifier, - TurnHeldItemTransferModifier, - ContactHeldItemTransferChanceModifier, IvScannerModifier, ExtraModifierModifier, TempExtraModifierModifier, diff --git a/src/overrides.ts b/src/overrides.ts index b390b9fa70f..546935d1c45 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -22,6 +22,7 @@ import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; +import { HeldItemConfiguration } from "./items/held-item-data-types"; /** * This comment block exists to prevent IDEs from automatically removing unused imports @@ -255,9 +256,9 @@ class DefaultOverrides { readonly OPP_MODIFIER_OVERRIDE: ModifierOverride[] = []; /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ - readonly STARTING_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; + readonly STARTING_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ - readonly OPP_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; + readonly OPP_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** * Override array of {@linkcode ModifierOverride}s used to replace the generated item rolls after a wave. diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 72132635c1b..61b2cf05ecf 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -253,7 +253,7 @@ export class TitlePhase extends Phase { for (const m of modifiers) { globalScene.addModifier(m, true, false, false, true); } - globalScene.updateModifiers(true, true); + globalScene.updateModifiers(true); Promise.all(loadPokemonAssets).then(() => { globalScene.time.delayedCall(500, () => globalScene.playBgm()); From 0e0c5e9fe3447130e672892b4d1310878204f285 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:49:40 +0200 Subject: [PATCH 101/114] Removed unused getOrInferTier function (was only used by thief and covet) --- src/data/moves/move.ts | 4 - src/modifier/modifier-type.ts | 149 ++-------------------------------- src/modifier/modifier.ts | 35 ++++++++ 3 files changed, 40 insertions(+), 148 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 245dcb06068..770932ab0fe 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2605,10 +2605,6 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)]; -// const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; -// 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 stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) { return false; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index c1cbe098927..759bcd42470 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -10,7 +10,7 @@ import { FormChangeItem } from "#enums/form-change-item"; import { formChangeItemName } from "#app/data/pokemon-forms"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -43,7 +43,6 @@ import { MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, - MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonHpRestoreModifier, @@ -65,6 +64,7 @@ import { type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier, + MoneyRewardModifier, } from "#app/modifier/modifier"; import { ModifierTier } from "#enums/modifier-tier"; import Overrides from "#app/overrides"; @@ -103,9 +103,7 @@ import { ModifierPoolType } from "#enums/modifier-pool-type"; import { getModifierPoolForType } from "#app/utils/modifier-utils"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#app/items/held-item-pool"; - -const outputModifierData = false; -const useMaxWeightForOutput = false; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier | null; @@ -161,43 +159,6 @@ export class ModifierType { this.tier = tier; } - getOrInferTier(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierTier | null { - if (this.tier) { - return this.tier; - } - if (!this.id) { - return null; - } - let poolTypes: ModifierPoolType[]; - switch (poolType) { - case ModifierPoolType.PLAYER: - poolTypes = [poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD]; - break; - case ModifierPoolType.WILD: - poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER]; - break; - case ModifierPoolType.TRAINER: - poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD]; - break; - default: - poolTypes = [poolType]; - break; - } - // Try multiple pool types in case of stolen items - for (const type of poolTypes) { - const pool = getModifierPoolForType(type); - for (const tier of getEnumValues(ModifierTier)) { - if (!pool.hasOwnProperty(tier)) { - continue; - } - if (pool[tier].find(m => (m as WeightedModifierType).modifierType.id === this.id)) { - return (this.tier = tier); - } - } - } - return null; - } - /** * Populates item id for ModifierType instance * @param func @@ -770,7 +731,8 @@ class BerryRewardGenerator extends ModifierTypeGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - return new BerryReward(pregenArgs[0] as BerryType); + const item = berryTypeToHeldItem[pregenArgs[0] as BerryType]; + return new HeldItemReward(item); } const item = getNewBerryHeldItem(); return new HeldItemReward(item); @@ -1908,19 +1870,10 @@ export interface ModifierPool { let modifierPoolThresholds = {}; let ignoredPoolIndexes = {}; -let dailyStarterModifierPoolThresholds = {}; -// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK -let ignoredDailyStarterPoolIndexes = {}; - -let enemyModifierPoolThresholds = {}; -// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK -let enemyIgnoredPoolIndexes = {}; - let enemyBuffModifierPoolThresholds = {}; // biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK let enemyBuffIgnoredPoolIndexes = {}; -const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; /** * Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item. */ @@ -1933,14 +1886,12 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod }); const ignoredIndexes = {}; - const modifierTableData = {}; const thresholds = Object.fromEntries( new Map( Object.keys(pool).map(t => { ignoredIndexes[t] = []; const thresholds = new Map(); const tierModifierIds: string[] = []; - let tierMaxWeight = 0; let i = 0; pool[t].reduce((total: number, modifierType: WeightedModifierType) => { const weightedModifierType = modifierType as WeightedModifierType; @@ -1965,14 +1916,6 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod if (weightedModifierType.maxWeight) { const modifierId = weightedModifierType.modifierType.id; tierModifierIds.push(modifierId); - const outputWeight = useMaxWeightForOutput ? weightedModifierType.maxWeight : weight; - modifierTableData[modifierId] = { - weight: outputWeight, - tier: Number.parseInt(t), - tierPercent: 0, - totalPercent: 0, - }; - tierMaxWeight += outputWeight; } if (weight) { total += weight; @@ -1986,39 +1929,19 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod thresholds.set(total, i++); return total; }, 0); - for (const id of tierModifierIds) { - modifierTableData[id].tierPercent = Math.floor((modifierTableData[id].weight / tierMaxWeight) * 10000) / 100; - } return [t, Object.fromEntries(thresholds)]; }), ), ); - for (const id of Object.keys(modifierTableData)) { - modifierTableData[id].totalPercent = - Math.floor(modifierTableData[id].tierPercent * tierWeights[modifierTableData[id].tier] * 100) / 100; - modifierTableData[id].tier = ModifierTier[modifierTableData[id].tier]; - } - if (outputModifierData) { - console.table(modifierTableData); - } switch (poolType) { case ModifierPoolType.PLAYER: modifierPoolThresholds = thresholds; ignoredPoolIndexes = ignoredIndexes; break; - case ModifierPoolType.WILD: - case ModifierPoolType.TRAINER: - enemyModifierPoolThresholds = thresholds; - enemyIgnoredPoolIndexes = ignoredIndexes; - break; case ModifierPoolType.ENEMY_BUFF: enemyBuffModifierPoolThresholds = thresholds; enemyBuffIgnoredPoolIndexes = ignoredIndexes; break; - case ModifierPoolType.DAILY_STARTER: - dailyStarterModifierPoolThresholds = thresholds; - ignoredDailyStarterPoolIndexes = ignoredIndexes; - break; } } @@ -2253,58 +2176,6 @@ export function getEnemyBuffModifierForWave( return modifier; } -// TODO: Add proper documentation to this function (once it fully works...) -export function getEnemyHeldItemsForWave( - waveIndex: number, - count: number, - party: EnemyPokemon[], - poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, - upgradeChance = 0, -): HeldItemId[] { - const ret = new Array(count).fill(0).map(() => { - const reward = getNewModifierTypeOption( - party, - poolType, - undefined, - upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, - )?.type as HeldItemReward; - return reward.itemId; - }); - if (!(waveIndex % 1000)) { - ret.push(HeldItemId.MINI_BLACK_HOLE); - } - return ret; -} - -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeldItemModifier[] { - const ret: PokemonHeldItemModifier[] = []; - for (const p of party) { - for (let m = 0; m < 3; m++) { - const tierValue = randSeedInt(64); - - let tier: ModifierTier; - if (tierValue > 25) { - tier = ModifierTier.COMMON; - } else if (tierValue > 12) { - tier = ModifierTier.GREAT; - } else if (tierValue > 4) { - tier = ModifierTier.ULTRA; - } else if (tierValue) { - tier = ModifierTier.ROGUE; - } else { - tier = ModifierTier.MASTER; - } - - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier( - p, - ) as PokemonHeldItemModifier; - ret.push(modifier); - } - } - - return ret; -} - /** * Generates a ModifierType from the specified pool * @param party party of the trainer using the item @@ -2369,18 +2240,9 @@ function getPoolThresholds(poolType: ModifierPoolType) { case ModifierPoolType.PLAYER: thresholds = modifierPoolThresholds; break; - case ModifierPoolType.WILD: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.TRAINER: - thresholds = enemyModifierPoolThresholds; - break; case ModifierPoolType.ENEMY_BUFF: thresholds = enemyBuffModifierPoolThresholds; break; - case ModifierPoolType.DAILY_STARTER: - thresholds = dailyStarterModifierPoolThresholds; - break; } return thresholds; } @@ -2531,7 +2393,6 @@ export function initModifierTypes() { // For now, doing the minimal work until the modifier rework lands. const ModifierTypeConstructorMap = Object.freeze({ ModifierTypeGenerator, - PokemonHeldItemModifierType, }); /** diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 05ae9a63cbb..204023b159c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1166,6 +1166,41 @@ export class ExpBalanceModifier extends PersistentModifier { } } +export class MoneyRewardModifier extends ConsumableModifier { + private moneyMultiplier: number; + + constructor(type: ModifierType, moneyMultiplier: number) { + super(type); + + this.moneyMultiplier = moneyMultiplier; + } + + /** + * Applies {@linkcode MoneyRewardModifier} + * @returns always `true` + */ + override apply(): boolean { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + + globalScene.addMoney(moneyAmount.value); + + globalScene.getPlayerParty().map(p => { + if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { + const factor = Math.min(Math.floor(this.moneyMultiplier), 3); + const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( + p, + factor, + ) as EvoTrackerModifier; + globalScene.addModifier(modifier); + } + }); + + return true; + } +} + export class MoneyMultiplierModifier extends PersistentModifier { match(modifier: Modifier): boolean { return modifier instanceof MoneyMultiplierModifier; From d161aad372d0aeed074b21c2f59be392e8c16cf7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 18 Jun 2025 00:01:34 +0200 Subject: [PATCH 102/114] Fixed shady vitamin ME and some HeldItem files --- .../shady-vitamin-dealer-encounter.ts | 39 ++++++++----------- src/items/held-item.ts | 4 +- src/items/held-items/instant-revive.ts | 4 +- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 967c105c740..02eca80cf31 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,5 +1,4 @@ import { - generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, @@ -7,7 +6,6 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { modifierTypes } from "#app/data/data-lists"; import { randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; @@ -19,7 +17,6 @@ import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { applyDamageToPokemon, - applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -28,6 +25,8 @@ import type { Nature } from "#enums/nature"; import { getNatureName } from "#app/data/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import i18next from "i18next"; +import { getNewVitaminHeldItem } from "#app/items/held-item-pool"; +import { allHeldItems } from "#app/items/all-held-items"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/shadyVitaminDealer"; @@ -97,15 +96,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Update money updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens - const modifiers = [ - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - ]; - encounter.setDialogueToken("boost1", modifiers[0].name); - encounter.setDialogueToken("boost2", modifiers[1].name); + const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()]; + encounter.setDialogueToken("boost1", allHeldItems[items[0]].name); + encounter.setDialogueToken("boost2", allHeldItems[items[1]].name); encounter.misc = { chosenPokemon: pokemon, - modifiers: modifiers, + items: items, }; }; @@ -132,10 +128,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Choose Cheap Option const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; - const modifiers = encounter.misc.modifiers; + const items = encounter.misc.items; - for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); + for (const item of items) { + chosenPokemon.heldItemManager.add(item); } leaveEncounterWithoutBattle(true); @@ -180,15 +176,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Update money updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens - const modifiers = [ - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - ]; - encounter.setDialogueToken("boost1", modifiers[0].name); - encounter.setDialogueToken("boost2", modifiers[1].name); + const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()]; + encounter.setDialogueToken("boost1", allHeldItems[items[0]].name); + encounter.setDialogueToken("boost2", allHeldItems[items[1]].name); encounter.misc = { chosenPokemon: pokemon, - modifiers: modifiers, + items: items, }; }; @@ -203,10 +196,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Choose Expensive Option const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; - const modifiers = encounter.misc.modifiers; + const items = encounter.misc.items; - for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); + for (const item of items) { + chosenPokemon.heldItemManager.add(item); } leaveEncounterWithoutBattle(true); diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 0249e8ec40e..3ffaad3bd60 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,4 +1,4 @@ -import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; +import { applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { HeldItemNames, type HeldItemId } from "#enums/held-item-id"; @@ -157,7 +157,7 @@ export class ConsumableHeldItem extends HeldItem { globalScene.updateModifiers(isPlayer); } if (unburden) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false); } } } diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index ea8162d7c45..62e0874f995 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -5,7 +5,7 @@ import { ConsumableHeldItem, ITEM_EFFECT } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; -import { applyAbAttrs, CommanderAbAttr } from "#app/data/abilities/ability"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; export interface INSTANT_REVIVE_PARAMS { /** The pokemon with the item */ @@ -61,7 +61,7 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { // Reapply Commander on the Pokemon's side of the field, if applicable const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); for (const p of field) { - applyAbAttrs(CommanderAbAttr, p, null, false); + applyAbAttrs("CommanderAbAttr", p, null, false); } return true; } From 2321de09830a21da093927a33f237d89499ea9be Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:23:07 +0200 Subject: [PATCH 103/114] Updated various MEs. HeldItemManager can now generate a configuration from its items. --- .../encounters/dark-deal-encounter.ts | 6 +-- .../encounters/fiery-fallout-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 14 +++--- .../encounters/the-strong-stuff-encounter.ts | 14 +++--- .../encounters/training-session-encounter.ts | 49 ++++--------------- src/field/pokemon-held-item-manager.ts | 19 ++++++- 6 files changed, 46 insertions(+), 58 deletions(-) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 3480a318b08..236b4fad9d7 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -18,7 +18,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { Challenges } from "#enums/challenges"; -import type { HeldItemPropertyMap } from "#app/field/pokemon-held-item-manager"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/darkDeal"; @@ -148,7 +148,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE const removedPokemon = getRandomPlayerPokemon(true, false, true); // Get all the pokemon's held items - const itemConfig = removedPokemon.heldItemManager.heldItems; + const itemConfig = removedPokemon.heldItemManager.generateHeldItemConfiguration(); globalScene.removePokemonFromPlayerParty(removedPokemon); const encounter = globalScene.currentBattle.mysteryEncounter!; @@ -175,7 +175,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType); } - const bossItemConfig: HeldItemPropertyMap = encounter.misc.itemConfig; + const bossItemConfig: HeldItemConfiguration = encounter.misc.itemConfig; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10]; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index fea8e06ca9d..4a68066748d 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -46,7 +46,7 @@ import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; import { getNewHeldItemFromCategory } from "#app/items/held-item-pool"; import { allHeldItems } from "#app/items/all-held-items"; import { MoveUseMode } from "#enums/move-use-mode"; -import { allAbilities, modifierTypes } from "#app/data/data-lists"; +import { allAbilities } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 3050ee82c92..b2bc2321524 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -75,13 +75,13 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil status: [StatusEffect.SLEEP, 6], // Extra turns on timer for Snorlax's start of fight moves nature: Nature.DOCILE, moveSet: [MoveId.BODY_SLAM, MoveId.CRUNCH, MoveId.SLEEP_TALK, MoveId.REST], - heldItemConfig: { - [HeldItemId.SITRUS_BERRY]: { stack: 1 }, - [HeldItemId.ENIGMA_BERRY]: { stack: 1 }, - [HeldItemId.HP_UP]: { stack: 1 }, - [HeldItemId.SOOTHE_BELL]: { stack: randSeedInt(2, 0) }, - [HeldItemId.LUCKY_EGG]: { stack: randSeedInt(2, 0) }, - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 1 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 1 }, + { entry: HeldItemId.HP_UP, count: 1 }, + { entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 0) }, + { entry: HeldItemId.LUCKY_EGG, count: randSeedInt(2, 0) }, + ], customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep }; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 6399bc41382..e5aafbfdeb9 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -93,13 +93,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.HARDY, moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER], - heldItemConfig: { - [HeldItemId.SITRUS_BERRY]: { stack: 1 }, - [HeldItemId.ENIGMA_BERRY]: { stack: 1 }, - [HeldItemId.APICOT_BERRY]: { stack: 1 }, - [HeldItemId.GANLON_BERRY]: { stack: 1 }, - [HeldItemId.LUM_BERRY]: { stack: 2 }, - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 1 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 1 }, + { entry: HeldItemId.APICOT_BERRY, count: 1 }, + { entry: HeldItemId.GANLON_BERRY, count: 1 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, + ], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.stat_boost`); diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 356b5784bef..688e62d8f9c 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -24,7 +24,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import i18next from "i18next"; import { getStatKey } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -101,8 +100,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Spawn light training session with chosen pokemon // Every 50 waves, add +1 boss segment, capping at 5 const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -151,12 +149,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.gameData.setPokemonCaught(playerPokemon, false); } - // Add pokemon and mods back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } + // Make held items show up again globalScene.updateModifiers(true); queueEncounterMessage(`${namespace}:option.1.finished`); }; @@ -217,8 +210,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Spawn medium training session with chosen pokemon // Every 40 waves, add +1 boss segment, capping at 6 const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -227,12 +219,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde playerPokemon.setCustomNature(encounter.misc.chosenNature); globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature); - // Add pokemon and modifiers back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } + // Make held items show up again globalScene.updateModifiers(true); }; @@ -308,8 +295,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Every 30 waves, add +1 boss segment, capping at 6 // Also starts with +1 to all stats const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; globalScene.removePokemonFromPlayerParty(playerPokemon, false); @@ -340,12 +326,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde playerPokemon.calculateStats(); globalScene.gameData.setPokemonCaught(playerPokemon, false); - // Add pokemon and mods back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } + // Make held items show up again globalScene.updateModifiers(true); }; @@ -373,18 +354,12 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde ) .build(); -function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig { +function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number): EnemyPartyConfig { playerPokemon.resetSummonData(); // Passes modifiers by reference - modifiers.value = playerPokemon.getHeldItems(); - const modifierConfigs = modifiers.value.map(mod => { - return { - modifier: mod.clone(), - isTransferable: false, - stackCount: mod.stackCount, - }; - }) as HeldModifierConfig[]; + // TODO: fix various things, like make enemy items untransferable, make sure form change items can come back + const config = playerPokemon.heldItemManager.generateHeldItemConfiguration(); const data = new PokemonData(playerPokemon); return { @@ -396,12 +371,8 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier formIndex: playerPokemon.formIndex, level: playerPokemon.level, dataSource: data, - modifierConfigs: modifierConfigs, + heldItemConfig: config, }, ], }; } - -class ModifiersHolder { - public value: PokemonHeldItemModifier[] = []; -} diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 71ffed6750c..b9662041b52 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,7 +1,12 @@ import { allHeldItems } from "#app/items/all-held-items"; import { isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; import type { FormChangeItem } from "#enums/form-change-item"; -import { isHeldItemSpecs, type HeldItemDataMap, type HeldItemSpecs } from "#app/items/held-item-data-types"; +import { + type HeldItemConfiguration, + isHeldItemSpecs, + type HeldItemDataMap, + type HeldItemSpecs, +} from "#app/items/held-item-data-types"; interface FormChangeItemProperties { active: boolean; @@ -32,6 +37,18 @@ export class PokemonItemManager { return undefined; } + generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration { + const config: HeldItemConfiguration = []; + for (const [k, item] of Object.entries(this.heldItems)) { + const id = Number(k); + if (item && (!restrictedIds || id in restrictedIds)) { + const specs: HeldItemSpecs = { ...item, id }; + config.push({ entry: specs, count: 1 }); + } + } + return config; + } + getHeldItems(): number[] { return Object.keys(this.heldItems).map(k => Number(k)); } From ebf0f3e352cb58920835d18e6c3548cc223ecffd Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:20:54 +0200 Subject: [PATCH 104/114] Converted most MEs, introduced some more utility functions and methods --- src/battle-scene.ts | 8 + .../encounters/absolute-avarice-encounter.ts | 84 +++----- .../the-winstrate-challenge-encounter.ts | 199 ++++-------------- .../encounters/trash-to-treasure-encounter.ts | 93 ++------ .../encounters/uncommon-breed-encounter.ts | 31 ++- .../encounters/weird-dream-encounter.ts | 67 +++--- .../mystery-encounter-requirements.ts | 41 +--- .../utils/encounter-phase-utils.ts | 30 ++- src/enums/held-item-id.ts | 18 +- src/field/pokemon-held-item-manager.ts | 6 +- src/items/held-item-data-types.ts | 5 + 11 files changed, 188 insertions(+), 394 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 8afceb5571a..aff124aef73 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -906,6 +906,7 @@ export default class BattleScene extends SceneBase { variant?: Variant, ivs?: number[], nature?: Nature, + heldItemConfig?: HeldItemConfiguration, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void, ): PlayerPokemon { @@ -925,6 +926,9 @@ export default class BattleScene extends SceneBase { postProcess(pokemon); } pokemon.init(); + if (heldItemConfig) { + assignItemsFromConfiguration(heldItemConfig, pokemon); + } return pokemon; } @@ -934,6 +938,7 @@ export default class BattleScene extends SceneBase { trainerSlot: TrainerSlot, boss = false, shinyLock = false, + heldItemConfig?: HeldItemConfiguration, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void, ): EnemyPokemon { @@ -975,6 +980,9 @@ export default class BattleScene extends SceneBase { } pokemon.init(); + if (heldItemConfig) { + assignItemsFromConfiguration(heldItemConfig, pokemon); + } return pokemon; } diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 04db9e71aba..4008a6f36f7 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -1,6 +1,6 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, + getPartyBerries, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, @@ -9,14 +9,12 @@ import { import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -25,19 +23,17 @@ import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { randInt } from "#app/utils/common"; import { BattlerIndex } from "#enums/battler-index"; -import { - applyModifierTypeToPlayerPokemon, - catchPokemon, - getHighestLevelPlayerPokemon, -} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokeballType } from "#enums/pokeball"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; -import type { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import i18next from "i18next"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import { MoveUseMode } from "#enums/move-use-mode"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import { allHeldItems } from "#app/items/all-held-items"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { HeldItemRequirement } from "../mystery-encounter-requirements"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/absoluteAvarice"; @@ -64,7 +60,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde ) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(20, 180) - .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 6)) // Must have at least 6 berries to spawn + .withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 6)) // Must have at least 6 berries to spawn .withFleeAllowed(false) .withIntroSpriteConfigs([ { @@ -114,35 +110,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3"); - // Get all player berry items, remove from party, and store reference - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + // Get all berries in party, with references to the pokemon + const berryItems = getPartyBerries(); - // Sort berries by party member ID to more easily re-add later if necessary - const berryItemsMap = new Map(); - globalScene.getPlayerParty().forEach(pokemon => { - const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id); - if (pokemonBerries?.length > 0) { - berryItemsMap.set(pokemon.id, pokemonBerries); - } + encounter.misc.berryItemsMap = berryItems; + + // Adds stolen berries to the Greedent item configuration + const bossHeldItemConfig: HeldItemConfiguration = []; + berryItems.forEach(map => { + bossHeldItemConfig.push({ entry: map.item, count: 1 }); }); - encounter.misc = { berryItemsMap }; - - // Generates copies of the stolen berries to put on the Greedent - const bossModifierConfigs: HeldModifierConfig[] = []; - berryItems.forEach(berryMod => { - // Can't define stack count on a ModifierType, have to just create separate instances for each stack - // Overflow berries will be "lost" on the boss, but it's un-catchable anyway - for (let i = 0; i < berryMod.stackCount; i++) { - const modifierType = generateModifierType(modifierTypes.BERRY, [ - berryMod.berryType, - ]) as PokemonHeldItemModifierType; - bossModifierConfigs.push({ modifier: modifierType }); - } - }); - - // Do NOT remove the real berries yet or else it will be persisted in the session data - // +1 SpDef below wave 50, SpDef and Speed otherwise const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.SPDEF, Stat.SPD]; @@ -157,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde bossSegments: 3, shiny: false, // Shiny lock because of consistency issues between the different options moveSet: [MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF], - modifierConfigs: bossModifierConfigs, + heldItemConfig: bossHeldItemConfig, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); @@ -184,9 +162,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Remove the berries from the party // Session has been safely saved at this point, so data won't be lost - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; - berryItems.forEach(berryMod => { - globalScene.removeModifier(berryMod); + const berryItems = getPartyBerries(); + berryItems.forEach(map => { + map.pokemon.heldItemManager.remove(map.item); }); globalScene.updateModifiers(true); @@ -209,19 +187,14 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; // Provides 1x Reviver Seed to each party member at end of battle - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED); encounter.setDialogueToken( "foodReward", - revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), + allHeldItems[HeldItemId.REVIVER_SEED].name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), ); const givePartyPokemonReviverSeeds = () => { const party = globalScene.getPlayerParty(); party.forEach(p => { - const heldItems = p.getHeldItems(); - if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) { - const seedModifier = revSeed.newModifier(p); - globalScene.addModifier(seedModifier, false, false, false, true); - } + p.heldItemManager.add(HeldItemId.REVIVER_SEED); }); queueEncounterMessage(`${namespace}:option.1.food_stash`); }; @@ -257,19 +230,16 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Returns 2/5 of the berries stolen to each Pokemon const party = globalScene.getPlayerParty(); party.forEach(pokemon => { - const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id); - const berryTypesAsArray: BerryType[] = []; - stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType))); - const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5); + // TODO: is this check legal? + const stolenBerries = berryMap.filter(map => map.pokemon === pokemon); + const returnedBerryCount = Math.floor(((stolenBerries.length ?? 0) * 2) / 5); if (returnedBerryCount > 0) { for (let i = 0; i < returnedBerryCount; i++) { // Shuffle remaining berry types and pop - Phaser.Math.RND.shuffle(berryTypesAsArray); - const randBerryType = berryTypesAsArray.pop(); - - const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType; - applyModifierTypeToPlayerPokemon(pokemon, berryModType); + Phaser.Math.RND.shuffle(stolenBerries); + const randBerryType = stolenBerries.pop(); + pokemon.heldItemManager.add(randBerryType); } } }); diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 38ceab205f7..ac53b3c8ec6 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -1,13 +1,11 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -19,17 +17,18 @@ import { AbilityId } from "#enums/ability-id"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; -import { PokemonType } from "#enums/pokemon-type"; -import { BerryType } from "#enums/berry-type"; -import { Stat } from "#enums/stat"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import i18next from "i18next"; -import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { ModifierTier } from "#enums/modifier-tier"; +import { HeldItemId } from "#enums/held-item-id"; + +//TODO: make all items unstealable /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -257,16 +256,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Guts nature: Nature.ADAMANT, moveSet: [MoveId.FACADE, MoveId.BRAVE_BIRD, MoveId.PROTECT, MoveId.QUICK_ATTACK], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.FLAME_ORB, count: 1 }, + { entry: HeldItemId.FOCUS_BAND, count: 2 }, ], }, { @@ -275,16 +267,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig { abilityIndex: 1, // Guts nature: Nature.ADAMANT, moveSet: [MoveId.FACADE, MoveId.OBSTRUCT, MoveId.NIGHT_SLASH, MoveId.FIRE_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.FLAME_ORB, count: 1 }, + { entry: HeldItemId.LEFTOVERS, count: 2 }, ], }, ], @@ -301,16 +286,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Natural Cure nature: Nature.CALM, moveSet: [MoveId.SYNTHESIS, MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.SLEEP_POWDER], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.SOUL_DEW, count: 1 }, + { entry: HeldItemId.QUICK_CLAW, count: 2 }, ], }, { @@ -319,21 +297,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.TIMID, moveSet: [MoveId.PSYSHOCK, MoveId.MOONBLAST, MoveId.SHADOW_BALL, MoveId.WILL_O_WISP], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.PSYCHIC, - ]) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.FAIRY, - ]) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.TWISTED_SPOON, count: 1 }, + { entry: HeldItemId.FAIRY_FEATHER, count: 1 }, ], }, ], @@ -350,17 +316,9 @@ function getViviTrainerConfig(): EnemyPartyConfig { abilityIndex: 3, // Lightning Rod nature: Nature.ADAMANT, moveSet: [MoveId.WATERFALL, MoveId.MEGAHORN, MoveId.KNOCK_OFF, MoveId.REST], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - stackCount: 4, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.HP_UP, count: 4 }, ], }, { @@ -369,16 +327,9 @@ function getViviTrainerConfig(): EnemyPartyConfig { abilityIndex: 1, // Poison Heal nature: Nature.JOLLY, moveSet: [MoveId.SPORE, MoveId.SWORDS_DANCE, MoveId.SEED_BOMB, MoveId.DRAIN_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - stackCount: 4, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.HP_UP, count: 4 }, + { entry: HeldItemId.TOXIC_ORB, count: 1 }, ], }, { @@ -387,13 +338,7 @@ function getViviTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.CALM, moveSet: [MoveId.EARTH_POWER, MoveId.FIRE_BLAST, MoveId.YAWN, MoveId.PROTECT], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 3, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 3 }], }, ], }; @@ -409,12 +354,7 @@ function getVickyTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.IMPISH, moveSet: [MoveId.AXE_KICK, MoveId.ICE_PUNCH, MoveId.ZEN_HEADBUTT, MoveId.BULLET_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.SHELL_BELL, count: 1 }], }, ], }; @@ -430,13 +370,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Soundproof nature: Nature.MODEST, moveSet: [MoveId.THUNDERBOLT, MoveId.GIGA_DRAIN, MoveId.FOUL_PLAY, MoveId.THUNDER_WAVE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.ZINC, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.SWALOT), @@ -444,51 +378,18 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 2, // Gluttony nature: Nature.QUIET, moveSet: [MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.ICE_BEAM, MoveId.EARTHQUAKE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 2 }, + { entry: HeldItemId.STARF_BERRY, count: 2 }, + { entry: HeldItemId.SALAC_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.LANSAT_BERRY, count: 2 }, + { entry: HeldItemId.LIECHI_BERRY, count: 2 }, + { entry: HeldItemId.PETAYA_BERRY, count: 2 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 2 }, + { entry: HeldItemId.LEPPA_BERRY, count: 2 }, ], }, { @@ -497,13 +398,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 2, // Tangled Feet nature: Nature.JOLLY, moveSet: [MoveId.DRILL_PECK, MoveId.QUICK_ATTACK, MoveId.THRASH, MoveId.KNOCK_OFF], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.KINGS_ROCK, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.ALAKAZAM), @@ -511,13 +406,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.BOLD, moveSet: [MoveId.PSYCHIC, MoveId.SHADOW_BALL, MoveId.FOCUS_BLAST, MoveId.THUNDERBOLT], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.WIDE_LENS, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.DARMANITAN), @@ -525,13 +414,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Sheer Force nature: Nature.IMPISH, moveSet: [MoveId.EARTHQUAKE, MoveId.U_TURN, MoveId.FLARE_BLITZ, MoveId.ROCK_SLIDE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 2 }], }, ], }; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index b40233d8656..6a2d220e982 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -1,5 +1,6 @@ import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { + assignItemToFirstFreePokemon, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -16,7 +17,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { SpeciesId } from "#enums/species-id"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; import { ModifierTier } from "#enums/modifier-tier"; @@ -27,6 +27,8 @@ import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { randSeedInt } from "#app/utils/common"; import { MoveUseMode } from "#enums/move-use-mode"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/trashToTreasure"; @@ -81,41 +83,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde formIndex: 1, // Gmax bossSegmentModifier: 1, // +1 Segment from normal moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, - { - modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 1), - }, - { - modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(3, 1), - }, - { - modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, + heldItemConfig: [ + { entry: HeldItemCategoryId.BERRY, count: 4 }, + { entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 }, + { entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) }, + { entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) }, + { entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) }, + { entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) }, ], }; const config: EnemyPartyConfig = { @@ -222,44 +196,18 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde .build(); async function tryApplyDigRewardItems() { - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType; - const party = globalScene.getPlayerParty(); - // Iterate over the party until an item was successfully given // First leftovers - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, leftovers); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party); // Second leftovers - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, leftovers); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party); globalScene.playSound("item_fanfare"); await showEncounterText( i18next.t("battle:rewardGainCount", { - modifierName: leftovers.name, + modifierName: allHeldItems[HeldItemId.LEFTOVERS].name, count: 2, }), null, @@ -268,23 +216,12 @@ async function tryApplyDigRewardItems() { ); // Only Shell bell - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier; - - if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, shellBell); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.SHELL_BELL, party); globalScene.playSound("item_fanfare"); await showEncounterText( i18next.t("battle:rewardGainCount", { - modifierName: shellBell.name, + modifierName: allHeldItems[HeldItemId.SHELL_BELL].name, count: 1, }), null, diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 067688bf94b..36ed8c9ea4d 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -1,6 +1,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { + getPartyBerries, getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -15,10 +16,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { - MoveRequirement, - PersistentModifierRequirement, -} from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { HeldItemRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { @@ -36,6 +34,8 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { MoveUseMode } from "#enums/move-use-mode"; +import type { PokemonItemMap } from "#app/items/held-item-data-types"; +import { HeldItemCategoryId } from "#enums/held-item-id"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -190,7 +190,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, @@ -206,19 +206,18 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. // Remove 4 random berries from player's party // Get all player berry items, remove from party, and store reference - const berryItems: BerryModifier[] = globalScene.findModifiers( - m => m instanceof BerryModifier, - ) as BerryModifier[]; + + const berryMap = getPartyBerries(); + const stolenBerryMap: PokemonItemMap[] = []; + for (let i = 0; i < 4; i++) { - const index = randSeedInt(berryItems.length); - const randBerry = berryItems[index]; - randBerry.stackCount--; - if (randBerry.stackCount === 0) { - globalScene.removeModifier(randBerry); - berryItems.splice(index, 1); - } + const index = randSeedInt(berryMap.length); + const randBerry = berryMap[index]; + randBerry.pokemon.heldItemManager.remove(randBerry.item); + stolenBerryMap.push(randBerry); + berryMap.splice(index, 1); } - await globalScene.updateModifiers(true, true); + await globalScene.updateModifiers(true); // Pokemon joins the team, with 2 egg moves const encounter = globalScene.currentBattle.mysteryEncounter!; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 1ba756c7f5d..fd88b318c1f 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -7,7 +7,6 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, @@ -21,11 +20,9 @@ import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "# import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { allSpecies } from "#app/data/data-lists"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; +import { HiddenAbilityRateBoosterModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { @@ -40,10 +37,12 @@ import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { PartyMemberStrength } from "#enums/party-member-strength"; +import type { HeldItemConfiguration, HeldItemSpecs } from "#app/items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; +import { HeldItemId } from "#enums/held-item-id"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; @@ -265,24 +264,20 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit dataSource.player = false; // Copy held items to new pokemon - const newPokemonHeldItemConfigs: HeldModifierConfig[] = []; - for (const item of transformation.heldItems) { - newPokemonHeldItemConfigs.push({ - modifier: item.clone() as PokemonHeldItemModifier, - stackCount: item.getStackCount(), - isTransferable: false, - }); - } + // TODO: Make items untransferable + const newPokemonHeldItemConfig = transformation.heldItems; + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { const stats = getOldGateauBoostedStats(newPokemon); - newPokemonHeldItemConfigs.push({ - modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ - OLD_GATEAU_STATS_UP, - stats, - ]) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, + const gateauItem = { + id: HeldItemId.OLD_GATEAU, + stack: 1, + data: { statModifier: OLD_GATEAU_STATS_UP, stats: stats }, + } as HeldItemSpecs; + newPokemonHeldItemConfig.push({ + entry: gateauItem, + count: 1, }); } @@ -291,7 +286,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD, level: previousPokemon.level, dataSource: dataSource, - modifierConfigs: newPokemonHeldItemConfigs, + heldItemConfig: newPokemonHeldItemConfig, }; enemyPokemonConfigs.push(enemyConfig); @@ -372,7 +367,7 @@ interface PokemonTransformation { previousPokemon: PlayerPokemon; newSpecies: PokemonSpecies; newPokemon: PlayerPokemon; - heldItems: PokemonHeldItemModifier[]; + heldItems: HeldItemConfiguration; } function getTeamTransformations(): PokemonTransformation[] { @@ -397,9 +392,7 @@ function getTeamTransformations(): PokemonTransformation[] { for (let i = 0; i < numPokemon; i++) { const removed = removedPokemon[i]; const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); - pokemonTransformations[index].heldItems = removed - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier)); + pokemonTransformations[index].heldItems = removed.heldItemManager.generateHeldItemConfiguration(); const bst = removed.getSpeciesForm().getBaseStatTotal(); let newBstRange: [number, number]; @@ -455,22 +448,22 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { } // Copy old items to new pokemon - for (const item of transformation.heldItems) { - item.pokemonId = newPokemon.id; - globalScene.addModifier(item, false, false, false, true); - } + const heldItemConfiguration = transformation.heldItems; + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { const stats = getOldGateauBoostedStats(newPokemon); - const modType = modifierTypes - .MYSTERY_ENCOUNTER_OLD_GATEAU() - .generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats]) - ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); - const modifier = modType?.newModifier(newPokemon); - if (modifier) { - globalScene.addModifier(modifier, false, false, false, true); - } + const gateauItem = { + id: HeldItemId.OLD_GATEAU, + stack: 1, + data: { statModifier: OLD_GATEAU_STATS_UP, stats: stats }, + } as HeldItemSpecs; + heldItemConfiguration.push({ + entry: gateauItem, + count: 1, + }); } + assignItemsFromConfiguration(heldItemConfiguration, newPokemon); newPokemon.calculateStats(); await newPokemon.updateInfo(); diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 525f8f583fa..6b26f66f436 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -14,7 +14,7 @@ import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { TimeOfDay } from "#enums/time-of-day"; -import type { HeldItemId } from "#enums/held-item-id"; +import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; import { allHeldItems } from "#app/items/all-held-items"; export interface EncounterRequirement { @@ -351,39 +351,6 @@ export class PartySizeRequirement extends EncounterSceneRequirement { } } -export class PersistentModifierRequirement extends EncounterSceneRequirement { - requiredHeldItemModifiers: string[]; - minNumberOfItems: number; - - constructor(heldItem: string | string[], minNumberOfItems = 1) { - super(); - this.minNumberOfItems = minNumberOfItems; - this.requiredHeldItemModifiers = coerceArray(heldItem); - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) { - return false; - } - let modifierCount = 0; - for (const modifier of this.requiredHeldItemModifiers) { - const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier); - if (matchingMods?.length > 0) { - for (const matchingMod of matchingMods) { - modifierCount += matchingMod.stackCount; - } - } - } - - return modifierCount >= this.minNumberOfItems; - } - - override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { - return ["requiredItem", this.requiredHeldItemModifiers[0]]; - } -} - export class MoneyRequirement extends EncounterSceneRequirement { requiredMoney: number; // Static value scalingMultiplier: number; // Calculates required money based off wave index @@ -833,13 +800,13 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } export class HeldItemRequirement extends EncounterPokemonRequirement { - requiredHeldItems: HeldItemId[]; + requiredHeldItems: HeldItemId[] | HeldItemCategoryId[]; minNumberOfPokemon: number; invertQuery: boolean; requireTransferable: boolean; constructor( - heldItem: HeldItemId | HeldItemId[], + heldItem: HeldItemId | HeldItemId[] | HeldItemCategoryId | HeldItemCategoryId[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true, @@ -863,7 +830,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { if (!this.invertQuery) { return partyPokemon.filter(pokemon => this.requiredHeldItems.some(heldItem => { - pokemon.heldItemManager.hasItem(heldItem) && + (pokemon.heldItemManager.hasItem(heldItem) || pokemon.heldItemManager.hasItemCategory(heldItem)) && (!this.requireTransferable || allHeldItems[heldItem].isTransferable); }), ); diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index ca9eb158060..8d71590daf5 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -52,7 +52,9 @@ import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; import { timedEventManager } from "#app/global-event-manager"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types"; +import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -1305,3 +1307,29 @@ export function calculateRareSpawnAggregateStats(luckValue: number) { console.log(stats); } + +// Iterate over the party until an item is successfully given +export function assignItemToFirstFreePokemon(item: HeldItemId, party: Pokemon[]): void { + for (const pokemon of party) { + const stack = pokemon.heldItemManager.getStack(item); + if (stack < allHeldItems[item].getMaxStackCount()) { + pokemon.heldItemManager.add(item); + return; + } + } +} + +// Creates an item map of berries to pokemon, storing each berry separately (splitting up stacks) +export function getPartyBerries(): PokemonItemMap[] { + const pokemonItems: PokemonItemMap[] = []; + globalScene.getPlayerParty().forEach(pokemon => { + const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY)); + berries.forEach(berryId => { + const berryStack = pokemon.heldItemManager.getStack(berryId); + for (let i = 1; i <= berryStack; i++) { + pokemonItems.push({ item: berryId, pokemon: pokemon }); + } + }); + }); + return pokemonItems; +} diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index f6d223fc855..e371662f7a2 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -38,14 +38,13 @@ export const HeldItemId = { BLACK_GLASSES: 0x0311, FAIRY_FEATHER: 0x0312, - // Stat Boosters - EVIOLITE: 0x0401, - LIGHT_BALL: 0x0402, - THICK_CLUB: 0x0403, - METAL_POWDER: 0x0404, - QUICK_POWDER: 0x0405, - DEEP_SEA_SCALE: 0x0406, - DEEP_SEA_TOOTH: 0x0407, + // Species Stat Boosters + LIGHT_BALL: 0x0401, + THICK_CLUB: 0x0402, + METAL_POWDER: 0x0403, + QUICK_POWDER: 0x0404, + DEEP_SEA_SCALE: 0x0405, + DEEP_SEA_TOOTH: 0x0406, // Crit Boosters SCOPE_LENS: 0x0501, @@ -72,6 +71,7 @@ export const HeldItemId = { SOUL_DEW: 0x070D, BATON: 0x070E, MINI_BLACK_HOLE: 0x070F, + EVIOLITE: 0x0710, // Vitamins HP_UP: 0x0801, @@ -110,7 +110,7 @@ export const HeldItemCategoryId = { BERRY: 0x0100, CONSUMABLE: 0x0200, TYPE_ATTACK_BOOSTER: 0x0300, - STAT_BOOSTER: 0x0400, + SPECIES_STAT_BOOSTER: 0x0400, CRIT_BOOSTER: 0x0500, GAIN_INCREASE: 0x0600, UNIQUE: 0x0700, diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index b9662041b52..574cbcdd340 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,5 +1,5 @@ import { allHeldItems } from "#app/items/all-held-items"; -import { isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; +import { isItemInCategory, isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; import type { FormChangeItem } from "#enums/form-change-item"; import { type HeldItemConfiguration, @@ -75,6 +75,10 @@ export class PokemonItemManager { return itemType in this.heldItems; } + hasItemCategory(categoryId: HeldItemCategoryId): boolean { + return Object.keys(this.heldItems).some(id => isItemInCategory(Number(id), categoryId)); + } + getStack(itemType: HeldItemId): number { const item = this.heldItems[itemType]; return item ? item.stack : 0; diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts index fa772d7fcd8..4144d3b7732 100644 --- a/src/items/held-item-data-types.ts +++ b/src/items/held-item-data-types.ts @@ -70,3 +70,8 @@ type HeldItemConfigurationEntry = { }; export type HeldItemConfiguration = HeldItemConfigurationEntry[]; + +export type PokemonItemMap = { + item: HeldItemId; + pokemon: Pokemon; +}; From ede7fe4615a8f597b7d451b6ade95e19c67fb860 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:23:29 +0200 Subject: [PATCH 105/114] Replaced ModifierTier with RewardTier --- src/battle.ts | 52 +- src/data/balance/tms.ts | 636 +++++++++--------- src/data/challenge.ts | 24 +- .../encounters/a-trainers-test-encounter.ts | 4 +- .../encounters/bug-type-superfan-encounter.ts | 4 +- .../encounters/clowning-around-encounter.ts | 16 +- .../encounters/fight-or-flight-encounter.ts | 10 +- .../global-trade-system-encounter.ts | 108 +-- .../mysterious-challengers-encounter.ts | 6 +- .../encounters/mysterious-chest-encounter.ts | 10 +- .../the-winstrate-challenge-encounter.ts | 4 +- .../encounters/trash-to-treasure-encounter.ts | 4 +- .../encounters/weird-dream-encounter.ts | 14 +- src/enums/modifier-tier.ts | 8 - src/field/pokemon.ts | 8 +- src/modifier/init-modifier-pools.ts | 42 +- src/modifier/modifier-type.ts | 72 +- src/phases/add-enemy-buff-modifier-phase.ts | 8 +- src/phases/select-modifier-phase.ts | 8 +- src/ui/text.ts | 24 +- test/items/lock_capsule.test.ts | 4 +- .../clowning-around-encounter.test.ts | 6 +- .../global-trade-system-encounter.test.ts | 4 +- .../mysterious-challengers-encounter.test.ts | 18 +- .../trash-to-treasure-encounter.test.ts | 10 +- .../encounters/weird-dream-encounter.test.ts | 14 +- test/phases/select-modifier-phase.test.ts | 34 +- 27 files changed, 575 insertions(+), 577 deletions(-) delete mode 100644 src/enums/modifier-tier.ts diff --git a/src/battle.ts b/src/battle.ts index a275f669740..313870b0c37 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -30,7 +30,7 @@ import i18next from "#app/plugins/i18n"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; @@ -593,7 +593,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig() @@ -625,7 +625,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig() @@ -698,7 +698,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig() @@ -761,11 +761,11 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -780,11 +780,11 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -807,12 +807,12 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -911,12 +911,12 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.GREAT, - ModifierTier.GREAT, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.GREAT, + RewardTier.GREAT, ], allowLuckUpgrades: false, }), diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index e194dc4040c..b6317e8edd4 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -68591,324 +68591,324 @@ function transposeTmSpecies(): SpeciesTmMoves { export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies(); interface TmPoolTiers { - [key: number]: ModifierTier + [key: number]: RewardTier } export const tmPoolTiers: TmPoolTiers = { - [MoveId.MEGA_PUNCH]: ModifierTier.GREAT, - [MoveId.PAY_DAY]: ModifierTier.ULTRA, - [MoveId.FIRE_PUNCH]: ModifierTier.GREAT, - [MoveId.ICE_PUNCH]: ModifierTier.GREAT, - [MoveId.THUNDER_PUNCH]: ModifierTier.GREAT, - [MoveId.SWORDS_DANCE]: ModifierTier.COMMON, - [MoveId.CUT]: ModifierTier.COMMON, - [MoveId.FLY]: ModifierTier.COMMON, - [MoveId.MEGA_KICK]: ModifierTier.GREAT, - [MoveId.BODY_SLAM]: ModifierTier.GREAT, - [MoveId.TAKE_DOWN]: ModifierTier.GREAT, - [MoveId.DOUBLE_EDGE]: ModifierTier.ULTRA, - [MoveId.PIN_MISSILE]: ModifierTier.COMMON, - [MoveId.ROAR]: ModifierTier.COMMON, - [MoveId.FLAMETHROWER]: ModifierTier.ULTRA, - [MoveId.HYDRO_PUMP]: ModifierTier.ULTRA, - [MoveId.SURF]: ModifierTier.ULTRA, - [MoveId.ICE_BEAM]: ModifierTier.ULTRA, - [MoveId.BLIZZARD]: ModifierTier.ULTRA, - [MoveId.PSYBEAM]: ModifierTier.GREAT, - [MoveId.HYPER_BEAM]: ModifierTier.ULTRA, - [MoveId.LOW_KICK]: ModifierTier.COMMON, - [MoveId.COUNTER]: ModifierTier.COMMON, - [MoveId.STRENGTH]: ModifierTier.GREAT, - [MoveId.SOLAR_BEAM]: ModifierTier.ULTRA, - [MoveId.FIRE_SPIN]: ModifierTier.COMMON, - [MoveId.THUNDERBOLT]: ModifierTier.ULTRA, - [MoveId.THUNDER_WAVE]: ModifierTier.COMMON, - [MoveId.THUNDER]: ModifierTier.ULTRA, - [MoveId.EARTHQUAKE]: ModifierTier.ULTRA, - [MoveId.DIG]: ModifierTier.GREAT, - [MoveId.TOXIC]: ModifierTier.GREAT, - [MoveId.PSYCHIC]: ModifierTier.ULTRA, - [MoveId.AGILITY]: ModifierTier.COMMON, - [MoveId.NIGHT_SHADE]: ModifierTier.COMMON, - [MoveId.SCREECH]: ModifierTier.COMMON, - [MoveId.DOUBLE_TEAM]: ModifierTier.COMMON, - [MoveId.CONFUSE_RAY]: ModifierTier.COMMON, - [MoveId.LIGHT_SCREEN]: ModifierTier.COMMON, - [MoveId.HAZE]: ModifierTier.COMMON, - [MoveId.REFLECT]: ModifierTier.COMMON, - [MoveId.FOCUS_ENERGY]: ModifierTier.COMMON, - [MoveId.METRONOME]: ModifierTier.COMMON, - [MoveId.SELF_DESTRUCT]: ModifierTier.GREAT, - [MoveId.FIRE_BLAST]: ModifierTier.ULTRA, - [MoveId.WATERFALL]: ModifierTier.GREAT, - [MoveId.SWIFT]: ModifierTier.COMMON, - [MoveId.AMNESIA]: ModifierTier.COMMON, - [MoveId.DREAM_EATER]: ModifierTier.GREAT, - [MoveId.LEECH_LIFE]: ModifierTier.ULTRA, - [MoveId.FLASH]: ModifierTier.COMMON, - [MoveId.EXPLOSION]: ModifierTier.GREAT, - [MoveId.REST]: ModifierTier.COMMON, - [MoveId.ROCK_SLIDE]: ModifierTier.GREAT, - [MoveId.TRI_ATTACK]: ModifierTier.ULTRA, - [MoveId.SUPER_FANG]: ModifierTier.COMMON, - [MoveId.SUBSTITUTE]: ModifierTier.COMMON, - [MoveId.THIEF]: ModifierTier.GREAT, - [MoveId.SNORE]: ModifierTier.COMMON, - [MoveId.CURSE]: ModifierTier.COMMON, - [MoveId.REVERSAL]: ModifierTier.COMMON, - [MoveId.SPITE]: ModifierTier.COMMON, - [MoveId.PROTECT]: ModifierTier.COMMON, - [MoveId.SCARY_FACE]: ModifierTier.COMMON, - [MoveId.SLUDGE_BOMB]: ModifierTier.GREAT, - [MoveId.MUD_SLAP]: ModifierTier.COMMON, - [MoveId.SPIKES]: ModifierTier.COMMON, - [MoveId.ICY_WIND]: ModifierTier.GREAT, - [MoveId.OUTRAGE]: ModifierTier.ULTRA, - [MoveId.SANDSTORM]: ModifierTier.COMMON, - [MoveId.GIGA_DRAIN]: ModifierTier.ULTRA, - [MoveId.ENDURE]: ModifierTier.COMMON, - [MoveId.CHARM]: ModifierTier.COMMON, - [MoveId.FALSE_SWIPE]: ModifierTier.COMMON, - [MoveId.SWAGGER]: ModifierTier.COMMON, - [MoveId.STEEL_WING]: ModifierTier.GREAT, - [MoveId.ATTRACT]: ModifierTier.COMMON, - [MoveId.SLEEP_TALK]: ModifierTier.COMMON, - [MoveId.HEAL_BELL]: ModifierTier.COMMON, - [MoveId.RETURN]: ModifierTier.ULTRA, - [MoveId.FRUSTRATION]: ModifierTier.COMMON, - [MoveId.SAFEGUARD]: ModifierTier.COMMON, - [MoveId.PAIN_SPLIT]: ModifierTier.COMMON, - [MoveId.MEGAHORN]: ModifierTier.ULTRA, - [MoveId.BATON_PASS]: ModifierTier.COMMON, - [MoveId.ENCORE]: ModifierTier.COMMON, - [MoveId.IRON_TAIL]: ModifierTier.GREAT, - [MoveId.METAL_CLAW]: ModifierTier.COMMON, - [MoveId.SYNTHESIS]: ModifierTier.GREAT, - [MoveId.HIDDEN_POWER]: ModifierTier.GREAT, - [MoveId.RAIN_DANCE]: ModifierTier.COMMON, - [MoveId.SUNNY_DAY]: ModifierTier.COMMON, - [MoveId.CRUNCH]: ModifierTier.GREAT, - [MoveId.PSYCH_UP]: ModifierTier.COMMON, - [MoveId.SHADOW_BALL]: ModifierTier.ULTRA, - [MoveId.FUTURE_SIGHT]: ModifierTier.GREAT, - [MoveId.ROCK_SMASH]: ModifierTier.COMMON, - [MoveId.WHIRLPOOL]: ModifierTier.COMMON, - [MoveId.BEAT_UP]: ModifierTier.COMMON, - [MoveId.UPROAR]: ModifierTier.GREAT, - [MoveId.HEAT_WAVE]: ModifierTier.ULTRA, - [MoveId.HAIL]: ModifierTier.COMMON, - [MoveId.TORMENT]: ModifierTier.COMMON, - [MoveId.WILL_O_WISP]: ModifierTier.COMMON, - [MoveId.FACADE]: ModifierTier.GREAT, - [MoveId.FOCUS_PUNCH]: ModifierTier.COMMON, - [MoveId.NATURE_POWER]: ModifierTier.COMMON, - [MoveId.CHARGE]: ModifierTier.COMMON, - [MoveId.TAUNT]: ModifierTier.COMMON, - [MoveId.HELPING_HAND]: ModifierTier.COMMON, - [MoveId.TRICK]: ModifierTier.COMMON, - [MoveId.SUPERPOWER]: ModifierTier.ULTRA, - [MoveId.RECYCLE]: ModifierTier.COMMON, - [MoveId.REVENGE]: ModifierTier.GREAT, - [MoveId.BRICK_BREAK]: ModifierTier.GREAT, - [MoveId.KNOCK_OFF]: ModifierTier.GREAT, - [MoveId.ENDEAVOR]: ModifierTier.COMMON, - [MoveId.SKILL_SWAP]: ModifierTier.COMMON, - [MoveId.IMPRISON]: ModifierTier.COMMON, - [MoveId.SECRET_POWER]: ModifierTier.COMMON, - [MoveId.DIVE]: ModifierTier.GREAT, - [MoveId.FEATHER_DANCE]: ModifierTier.COMMON, - [MoveId.BLAZE_KICK]: ModifierTier.GREAT, - [MoveId.HYPER_VOICE]: ModifierTier.ULTRA, - [MoveId.BLAST_BURN]: ModifierTier.ULTRA, - [MoveId.HYDRO_CANNON]: ModifierTier.ULTRA, - [MoveId.WEATHER_BALL]: ModifierTier.COMMON, - [MoveId.FAKE_TEARS]: ModifierTier.COMMON, - [MoveId.AIR_CUTTER]: ModifierTier.GREAT, - [MoveId.OVERHEAT]: ModifierTier.ULTRA, - [MoveId.ROCK_TOMB]: ModifierTier.GREAT, - [MoveId.METAL_SOUND]: ModifierTier.COMMON, - [MoveId.COSMIC_POWER]: ModifierTier.COMMON, - [MoveId.SIGNAL_BEAM]: ModifierTier.GREAT, - [MoveId.SAND_TOMB]: ModifierTier.COMMON, - [MoveId.MUDDY_WATER]: ModifierTier.GREAT, - [MoveId.BULLET_SEED]: ModifierTier.GREAT, - [MoveId.AERIAL_ACE]: ModifierTier.GREAT, - [MoveId.ICICLE_SPEAR]: ModifierTier.GREAT, - [MoveId.IRON_DEFENSE]: ModifierTier.GREAT, - [MoveId.DRAGON_CLAW]: ModifierTier.ULTRA, - [MoveId.FRENZY_PLANT]: ModifierTier.ULTRA, - [MoveId.BULK_UP]: ModifierTier.COMMON, - [MoveId.BOUNCE]: ModifierTier.GREAT, - [MoveId.MUD_SHOT]: ModifierTier.GREAT, - [MoveId.POISON_TAIL]: ModifierTier.GREAT, - [MoveId.COVET]: ModifierTier.GREAT, - [MoveId.MAGICAL_LEAF]: ModifierTier.GREAT, - [MoveId.CALM_MIND]: ModifierTier.GREAT, - [MoveId.LEAF_BLADE]: ModifierTier.ULTRA, - [MoveId.DRAGON_DANCE]: ModifierTier.GREAT, - [MoveId.ROCK_BLAST]: ModifierTier.GREAT, - [MoveId.WATER_PULSE]: ModifierTier.GREAT, - [MoveId.ROOST]: ModifierTier.GREAT, - [MoveId.GRAVITY]: ModifierTier.COMMON, - [MoveId.GYRO_BALL]: ModifierTier.COMMON, - [MoveId.BRINE]: ModifierTier.GREAT, - [MoveId.PLUCK]: ModifierTier.GREAT, - [MoveId.TAILWIND]: ModifierTier.GREAT, - [MoveId.U_TURN]: ModifierTier.GREAT, - [MoveId.CLOSE_COMBAT]: ModifierTier.ULTRA, - [MoveId.PAYBACK]: ModifierTier.COMMON, - [MoveId.ASSURANCE]: ModifierTier.COMMON, - [MoveId.EMBARGO]: ModifierTier.COMMON, - [MoveId.FLING]: ModifierTier.COMMON, - [MoveId.GASTRO_ACID]: ModifierTier.GREAT, - [MoveId.POWER_SWAP]: ModifierTier.COMMON, - [MoveId.GUARD_SWAP]: ModifierTier.COMMON, - [MoveId.WORRY_SEED]: ModifierTier.GREAT, - [MoveId.TOXIC_SPIKES]: ModifierTier.GREAT, - [MoveId.FLARE_BLITZ]: ModifierTier.ULTRA, - [MoveId.AURA_SPHERE]: ModifierTier.GREAT, - [MoveId.ROCK_POLISH]: ModifierTier.COMMON, - [MoveId.POISON_JAB]: ModifierTier.GREAT, - [MoveId.DARK_PULSE]: ModifierTier.GREAT, - [MoveId.AQUA_TAIL]: ModifierTier.GREAT, - [MoveId.SEED_BOMB]: ModifierTier.GREAT, - [MoveId.AIR_SLASH]: ModifierTier.GREAT, - [MoveId.X_SCISSOR]: ModifierTier.GREAT, - [MoveId.BUG_BUZZ]: ModifierTier.GREAT, - [MoveId.DRAGON_PULSE]: ModifierTier.GREAT, - [MoveId.POWER_GEM]: ModifierTier.GREAT, - [MoveId.DRAIN_PUNCH]: ModifierTier.GREAT, - [MoveId.VACUUM_WAVE]: ModifierTier.COMMON, - [MoveId.FOCUS_BLAST]: ModifierTier.GREAT, - [MoveId.ENERGY_BALL]: ModifierTier.GREAT, - [MoveId.BRAVE_BIRD]: ModifierTier.ULTRA, - [MoveId.EARTH_POWER]: ModifierTier.ULTRA, - [MoveId.GIGA_IMPACT]: ModifierTier.GREAT, - [MoveId.NASTY_PLOT]: ModifierTier.COMMON, - [MoveId.AVALANCHE]: ModifierTier.GREAT, - [MoveId.SHADOW_CLAW]: ModifierTier.GREAT, - [MoveId.THUNDER_FANG]: ModifierTier.GREAT, - [MoveId.ICE_FANG]: ModifierTier.GREAT, - [MoveId.FIRE_FANG]: ModifierTier.GREAT, - [MoveId.PSYCHO_CUT]: ModifierTier.GREAT, - [MoveId.ZEN_HEADBUTT]: ModifierTier.GREAT, - [MoveId.FLASH_CANNON]: ModifierTier.GREAT, - [MoveId.ROCK_CLIMB]: ModifierTier.GREAT, - [MoveId.DEFOG]: ModifierTier.COMMON, - [MoveId.TRICK_ROOM]: ModifierTier.COMMON, - [MoveId.DRACO_METEOR]: ModifierTier.ULTRA, - [MoveId.LEAF_STORM]: ModifierTier.ULTRA, - [MoveId.POWER_WHIP]: ModifierTier.ULTRA, - [MoveId.CROSS_POISON]: ModifierTier.GREAT, - [MoveId.GUNK_SHOT]: ModifierTier.ULTRA, - [MoveId.IRON_HEAD]: ModifierTier.GREAT, - [MoveId.STONE_EDGE]: ModifierTier.ULTRA, - [MoveId.STEALTH_ROCK]: ModifierTier.COMMON, - [MoveId.GRASS_KNOT]: ModifierTier.ULTRA, - [MoveId.BUG_BITE]: ModifierTier.GREAT, - [MoveId.CHARGE_BEAM]: ModifierTier.GREAT, - [MoveId.HONE_CLAWS]: ModifierTier.COMMON, - [MoveId.WONDER_ROOM]: ModifierTier.COMMON, - [MoveId.PSYSHOCK]: ModifierTier.GREAT, - [MoveId.VENOSHOCK]: ModifierTier.GREAT, - [MoveId.MAGIC_ROOM]: ModifierTier.COMMON, - [MoveId.SMACK_DOWN]: ModifierTier.COMMON, - [MoveId.SLUDGE_WAVE]: ModifierTier.GREAT, - [MoveId.HEAVY_SLAM]: ModifierTier.GREAT, - [MoveId.ELECTRO_BALL]: ModifierTier.GREAT, - [MoveId.FLAME_CHARGE]: ModifierTier.GREAT, - [MoveId.LOW_SWEEP]: ModifierTier.GREAT, - [MoveId.ACID_SPRAY]: ModifierTier.COMMON, - [MoveId.FOUL_PLAY]: ModifierTier.ULTRA, - [MoveId.ROUND]: ModifierTier.COMMON, - [MoveId.ECHOED_VOICE]: ModifierTier.COMMON, - [MoveId.STORED_POWER]: ModifierTier.COMMON, - [MoveId.ALLY_SWITCH]: ModifierTier.COMMON, - [MoveId.SCALD]: ModifierTier.GREAT, - [MoveId.HEX]: ModifierTier.GREAT, - [MoveId.SKY_DROP]: ModifierTier.GREAT, - [MoveId.INCINERATE]: ModifierTier.GREAT, - [MoveId.QUASH]: ModifierTier.COMMON, - [MoveId.ACROBATICS]: ModifierTier.GREAT, - [MoveId.RETALIATE]: ModifierTier.GREAT, - [MoveId.WATER_PLEDGE]: ModifierTier.GREAT, - [MoveId.FIRE_PLEDGE]: ModifierTier.GREAT, - [MoveId.GRASS_PLEDGE]: ModifierTier.GREAT, - [MoveId.VOLT_SWITCH]: ModifierTier.GREAT, - [MoveId.STRUGGLE_BUG]: ModifierTier.COMMON, - [MoveId.BULLDOZE]: ModifierTier.GREAT, - [MoveId.FROST_BREATH]: ModifierTier.GREAT, - [MoveId.DRAGON_TAIL]: ModifierTier.GREAT, - [MoveId.WORK_UP]: ModifierTier.COMMON, - [MoveId.ELECTROWEB]: ModifierTier.GREAT, - [MoveId.WILD_CHARGE]: ModifierTier.GREAT, - [MoveId.DRILL_RUN]: ModifierTier.GREAT, - [MoveId.RAZOR_SHELL]: ModifierTier.GREAT, - [MoveId.HEAT_CRASH]: ModifierTier.GREAT, - [MoveId.TAIL_SLAP]: ModifierTier.GREAT, - [MoveId.HURRICANE]: ModifierTier.ULTRA, - [MoveId.SNARL]: ModifierTier.COMMON, - [MoveId.PHANTOM_FORCE]: ModifierTier.ULTRA, - [MoveId.PETAL_BLIZZARD]: ModifierTier.GREAT, - [MoveId.DISARMING_VOICE]: ModifierTier.GREAT, - [MoveId.DRAINING_KISS]: ModifierTier.GREAT, - [MoveId.GRASSY_TERRAIN]: ModifierTier.COMMON, - [MoveId.MISTY_TERRAIN]: ModifierTier.COMMON, - [MoveId.PLAY_ROUGH]: ModifierTier.GREAT, - [MoveId.CONFIDE]: ModifierTier.COMMON, - [MoveId.MYSTICAL_FIRE]: ModifierTier.GREAT, - [MoveId.EERIE_IMPULSE]: ModifierTier.COMMON, - [MoveId.VENOM_DRENCH]: ModifierTier.COMMON, - [MoveId.ELECTRIC_TERRAIN]: ModifierTier.COMMON, - [MoveId.DAZZLING_GLEAM]: ModifierTier.ULTRA, - [MoveId.INFESTATION]: ModifierTier.COMMON, - [MoveId.POWER_UP_PUNCH]: ModifierTier.GREAT, - [MoveId.DARKEST_LARIAT]: ModifierTier.GREAT, - [MoveId.HIGH_HORSEPOWER]: ModifierTier.ULTRA, - [MoveId.SOLAR_BLADE]: ModifierTier.GREAT, - [MoveId.THROAT_CHOP]: ModifierTier.GREAT, - [MoveId.POLLEN_PUFF]: ModifierTier.GREAT, - [MoveId.PSYCHIC_TERRAIN]: ModifierTier.COMMON, - [MoveId.LUNGE]: ModifierTier.GREAT, - [MoveId.SPEED_SWAP]: ModifierTier.COMMON, - [MoveId.SMART_STRIKE]: ModifierTier.GREAT, - [MoveId.BRUTAL_SWING]: ModifierTier.GREAT, - [MoveId.AURORA_VEIL]: ModifierTier.COMMON, - [MoveId.PSYCHIC_FANGS]: ModifierTier.GREAT, - [MoveId.STOMPING_TANTRUM]: ModifierTier.GREAT, - [MoveId.LIQUIDATION]: ModifierTier.ULTRA, - [MoveId.BODY_PRESS]: ModifierTier.ULTRA, - [MoveId.BREAKING_SWIPE]: ModifierTier.GREAT, - [MoveId.STEEL_BEAM]: ModifierTier.ULTRA, - [MoveId.EXPANDING_FORCE]: ModifierTier.GREAT, - [MoveId.STEEL_ROLLER]: ModifierTier.COMMON, - [MoveId.SCALE_SHOT]: ModifierTier.ULTRA, - [MoveId.METEOR_BEAM]: ModifierTier.GREAT, - [MoveId.MISTY_EXPLOSION]: ModifierTier.COMMON, - [MoveId.GRASSY_GLIDE]: ModifierTier.COMMON, - [MoveId.RISING_VOLTAGE]: ModifierTier.COMMON, - [MoveId.TERRAIN_PULSE]: ModifierTier.COMMON, - [MoveId.SKITTER_SMACK]: ModifierTier.GREAT, - [MoveId.BURNING_JEALOUSY]: ModifierTier.GREAT, - [MoveId.LASH_OUT]: ModifierTier.GREAT, - [MoveId.POLTERGEIST]: ModifierTier.ULTRA, - [MoveId.CORROSIVE_GAS]: ModifierTier.COMMON, - [MoveId.COACHING]: ModifierTier.COMMON, - [MoveId.FLIP_TURN]: ModifierTier.COMMON, - [MoveId.TRIPLE_AXEL]: ModifierTier.COMMON, - [MoveId.DUAL_WINGBEAT]: ModifierTier.COMMON, - [MoveId.SCORCHING_SANDS]: ModifierTier.GREAT, - [MoveId.TERA_BLAST]: ModifierTier.GREAT, - [MoveId.ICE_SPINNER]: ModifierTier.GREAT, - [MoveId.SNOWSCAPE]: ModifierTier.COMMON, - [MoveId.POUNCE]: ModifierTier.COMMON, - [MoveId.TRAILBLAZE]: ModifierTier.COMMON, - [MoveId.CHILLING_WATER]: ModifierTier.COMMON, - [MoveId.HARD_PRESS]: ModifierTier.GREAT, - [MoveId.DRAGON_CHEER]: ModifierTier.COMMON, - [MoveId.ALLURING_VOICE]: ModifierTier.GREAT, - [MoveId.TEMPER_FLARE]: ModifierTier.GREAT, - [MoveId.SUPERCELL_SLAM]: ModifierTier.GREAT, - [MoveId.PSYCHIC_NOISE]: ModifierTier.GREAT, - [MoveId.UPPER_HAND]: ModifierTier.COMMON, + [MoveId.MEGA_PUNCH]: RewardTier.GREAT, + [MoveId.PAY_DAY]: RewardTier.ULTRA, + [MoveId.FIRE_PUNCH]: RewardTier.GREAT, + [MoveId.ICE_PUNCH]: RewardTier.GREAT, + [MoveId.THUNDER_PUNCH]: RewardTier.GREAT, + [MoveId.SWORDS_DANCE]: RewardTier.COMMON, + [MoveId.CUT]: RewardTier.COMMON, + [MoveId.FLY]: RewardTier.COMMON, + [MoveId.MEGA_KICK]: RewardTier.GREAT, + [MoveId.BODY_SLAM]: RewardTier.GREAT, + [MoveId.TAKE_DOWN]: RewardTier.GREAT, + [MoveId.DOUBLE_EDGE]: RewardTier.ULTRA, + [MoveId.PIN_MISSILE]: RewardTier.COMMON, + [MoveId.ROAR]: RewardTier.COMMON, + [MoveId.FLAMETHROWER]: RewardTier.ULTRA, + [MoveId.HYDRO_PUMP]: RewardTier.ULTRA, + [MoveId.SURF]: RewardTier.ULTRA, + [MoveId.ICE_BEAM]: RewardTier.ULTRA, + [MoveId.BLIZZARD]: RewardTier.ULTRA, + [MoveId.PSYBEAM]: RewardTier.GREAT, + [MoveId.HYPER_BEAM]: RewardTier.ULTRA, + [MoveId.LOW_KICK]: RewardTier.COMMON, + [MoveId.COUNTER]: RewardTier.COMMON, + [MoveId.STRENGTH]: RewardTier.GREAT, + [MoveId.SOLAR_BEAM]: RewardTier.ULTRA, + [MoveId.FIRE_SPIN]: RewardTier.COMMON, + [MoveId.THUNDERBOLT]: RewardTier.ULTRA, + [MoveId.THUNDER_WAVE]: RewardTier.COMMON, + [MoveId.THUNDER]: RewardTier.ULTRA, + [MoveId.EARTHQUAKE]: RewardTier.ULTRA, + [MoveId.DIG]: RewardTier.GREAT, + [MoveId.TOXIC]: RewardTier.GREAT, + [MoveId.PSYCHIC]: RewardTier.ULTRA, + [MoveId.AGILITY]: RewardTier.COMMON, + [MoveId.NIGHT_SHADE]: RewardTier.COMMON, + [MoveId.SCREECH]: RewardTier.COMMON, + [MoveId.DOUBLE_TEAM]: RewardTier.COMMON, + [MoveId.CONFUSE_RAY]: RewardTier.COMMON, + [MoveId.LIGHT_SCREEN]: RewardTier.COMMON, + [MoveId.HAZE]: RewardTier.COMMON, + [MoveId.REFLECT]: RewardTier.COMMON, + [MoveId.FOCUS_ENERGY]: RewardTier.COMMON, + [MoveId.METRONOME]: RewardTier.COMMON, + [MoveId.SELF_DESTRUCT]: RewardTier.GREAT, + [MoveId.FIRE_BLAST]: RewardTier.ULTRA, + [MoveId.WATERFALL]: RewardTier.GREAT, + [MoveId.SWIFT]: RewardTier.COMMON, + [MoveId.AMNESIA]: RewardTier.COMMON, + [MoveId.DREAM_EATER]: RewardTier.GREAT, + [MoveId.LEECH_LIFE]: RewardTier.ULTRA, + [MoveId.FLASH]: RewardTier.COMMON, + [MoveId.EXPLOSION]: RewardTier.GREAT, + [MoveId.REST]: RewardTier.COMMON, + [MoveId.ROCK_SLIDE]: RewardTier.GREAT, + [MoveId.TRI_ATTACK]: RewardTier.ULTRA, + [MoveId.SUPER_FANG]: RewardTier.COMMON, + [MoveId.SUBSTITUTE]: RewardTier.COMMON, + [MoveId.THIEF]: RewardTier.GREAT, + [MoveId.SNORE]: RewardTier.COMMON, + [MoveId.CURSE]: RewardTier.COMMON, + [MoveId.REVERSAL]: RewardTier.COMMON, + [MoveId.SPITE]: RewardTier.COMMON, + [MoveId.PROTECT]: RewardTier.COMMON, + [MoveId.SCARY_FACE]: RewardTier.COMMON, + [MoveId.SLUDGE_BOMB]: RewardTier.GREAT, + [MoveId.MUD_SLAP]: RewardTier.COMMON, + [MoveId.SPIKES]: RewardTier.COMMON, + [MoveId.ICY_WIND]: RewardTier.GREAT, + [MoveId.OUTRAGE]: RewardTier.ULTRA, + [MoveId.SANDSTORM]: RewardTier.COMMON, + [MoveId.GIGA_DRAIN]: RewardTier.ULTRA, + [MoveId.ENDURE]: RewardTier.COMMON, + [MoveId.CHARM]: RewardTier.COMMON, + [MoveId.FALSE_SWIPE]: RewardTier.COMMON, + [MoveId.SWAGGER]: RewardTier.COMMON, + [MoveId.STEEL_WING]: RewardTier.GREAT, + [MoveId.ATTRACT]: RewardTier.COMMON, + [MoveId.SLEEP_TALK]: RewardTier.COMMON, + [MoveId.HEAL_BELL]: RewardTier.COMMON, + [MoveId.RETURN]: RewardTier.ULTRA, + [MoveId.FRUSTRATION]: RewardTier.COMMON, + [MoveId.SAFEGUARD]: RewardTier.COMMON, + [MoveId.PAIN_SPLIT]: RewardTier.COMMON, + [MoveId.MEGAHORN]: RewardTier.ULTRA, + [MoveId.BATON_PASS]: RewardTier.COMMON, + [MoveId.ENCORE]: RewardTier.COMMON, + [MoveId.IRON_TAIL]: RewardTier.GREAT, + [MoveId.METAL_CLAW]: RewardTier.COMMON, + [MoveId.SYNTHESIS]: RewardTier.GREAT, + [MoveId.HIDDEN_POWER]: RewardTier.GREAT, + [MoveId.RAIN_DANCE]: RewardTier.COMMON, + [MoveId.SUNNY_DAY]: RewardTier.COMMON, + [MoveId.CRUNCH]: RewardTier.GREAT, + [MoveId.PSYCH_UP]: RewardTier.COMMON, + [MoveId.SHADOW_BALL]: RewardTier.ULTRA, + [MoveId.FUTURE_SIGHT]: RewardTier.GREAT, + [MoveId.ROCK_SMASH]: RewardTier.COMMON, + [MoveId.WHIRLPOOL]: RewardTier.COMMON, + [MoveId.BEAT_UP]: RewardTier.COMMON, + [MoveId.UPROAR]: RewardTier.GREAT, + [MoveId.HEAT_WAVE]: RewardTier.ULTRA, + [MoveId.HAIL]: RewardTier.COMMON, + [MoveId.TORMENT]: RewardTier.COMMON, + [MoveId.WILL_O_WISP]: RewardTier.COMMON, + [MoveId.FACADE]: RewardTier.GREAT, + [MoveId.FOCUS_PUNCH]: RewardTier.COMMON, + [MoveId.NATURE_POWER]: RewardTier.COMMON, + [MoveId.CHARGE]: RewardTier.COMMON, + [MoveId.TAUNT]: RewardTier.COMMON, + [MoveId.HELPING_HAND]: RewardTier.COMMON, + [MoveId.TRICK]: RewardTier.COMMON, + [MoveId.SUPERPOWER]: RewardTier.ULTRA, + [MoveId.RECYCLE]: RewardTier.COMMON, + [MoveId.REVENGE]: RewardTier.GREAT, + [MoveId.BRICK_BREAK]: RewardTier.GREAT, + [MoveId.KNOCK_OFF]: RewardTier.GREAT, + [MoveId.ENDEAVOR]: RewardTier.COMMON, + [MoveId.SKILL_SWAP]: RewardTier.COMMON, + [MoveId.IMPRISON]: RewardTier.COMMON, + [MoveId.SECRET_POWER]: RewardTier.COMMON, + [MoveId.DIVE]: RewardTier.GREAT, + [MoveId.FEATHER_DANCE]: RewardTier.COMMON, + [MoveId.BLAZE_KICK]: RewardTier.GREAT, + [MoveId.HYPER_VOICE]: RewardTier.ULTRA, + [MoveId.BLAST_BURN]: RewardTier.ULTRA, + [MoveId.HYDRO_CANNON]: RewardTier.ULTRA, + [MoveId.WEATHER_BALL]: RewardTier.COMMON, + [MoveId.FAKE_TEARS]: RewardTier.COMMON, + [MoveId.AIR_CUTTER]: RewardTier.GREAT, + [MoveId.OVERHEAT]: RewardTier.ULTRA, + [MoveId.ROCK_TOMB]: RewardTier.GREAT, + [MoveId.METAL_SOUND]: RewardTier.COMMON, + [MoveId.COSMIC_POWER]: RewardTier.COMMON, + [MoveId.SIGNAL_BEAM]: RewardTier.GREAT, + [MoveId.SAND_TOMB]: RewardTier.COMMON, + [MoveId.MUDDY_WATER]: RewardTier.GREAT, + [MoveId.BULLET_SEED]: RewardTier.GREAT, + [MoveId.AERIAL_ACE]: RewardTier.GREAT, + [MoveId.ICICLE_SPEAR]: RewardTier.GREAT, + [MoveId.IRON_DEFENSE]: RewardTier.GREAT, + [MoveId.DRAGON_CLAW]: RewardTier.ULTRA, + [MoveId.FRENZY_PLANT]: RewardTier.ULTRA, + [MoveId.BULK_UP]: RewardTier.COMMON, + [MoveId.BOUNCE]: RewardTier.GREAT, + [MoveId.MUD_SHOT]: RewardTier.GREAT, + [MoveId.POISON_TAIL]: RewardTier.GREAT, + [MoveId.COVET]: RewardTier.GREAT, + [MoveId.MAGICAL_LEAF]: RewardTier.GREAT, + [MoveId.CALM_MIND]: RewardTier.GREAT, + [MoveId.LEAF_BLADE]: RewardTier.ULTRA, + [MoveId.DRAGON_DANCE]: RewardTier.GREAT, + [MoveId.ROCK_BLAST]: RewardTier.GREAT, + [MoveId.WATER_PULSE]: RewardTier.GREAT, + [MoveId.ROOST]: RewardTier.GREAT, + [MoveId.GRAVITY]: RewardTier.COMMON, + [MoveId.GYRO_BALL]: RewardTier.COMMON, + [MoveId.BRINE]: RewardTier.GREAT, + [MoveId.PLUCK]: RewardTier.GREAT, + [MoveId.TAILWIND]: RewardTier.GREAT, + [MoveId.U_TURN]: RewardTier.GREAT, + [MoveId.CLOSE_COMBAT]: RewardTier.ULTRA, + [MoveId.PAYBACK]: RewardTier.COMMON, + [MoveId.ASSURANCE]: RewardTier.COMMON, + [MoveId.EMBARGO]: RewardTier.COMMON, + [MoveId.FLING]: RewardTier.COMMON, + [MoveId.GASTRO_ACID]: RewardTier.GREAT, + [MoveId.POWER_SWAP]: RewardTier.COMMON, + [MoveId.GUARD_SWAP]: RewardTier.COMMON, + [MoveId.WORRY_SEED]: RewardTier.GREAT, + [MoveId.TOXIC_SPIKES]: RewardTier.GREAT, + [MoveId.FLARE_BLITZ]: RewardTier.ULTRA, + [MoveId.AURA_SPHERE]: RewardTier.GREAT, + [MoveId.ROCK_POLISH]: RewardTier.COMMON, + [MoveId.POISON_JAB]: RewardTier.GREAT, + [MoveId.DARK_PULSE]: RewardTier.GREAT, + [MoveId.AQUA_TAIL]: RewardTier.GREAT, + [MoveId.SEED_BOMB]: RewardTier.GREAT, + [MoveId.AIR_SLASH]: RewardTier.GREAT, + [MoveId.X_SCISSOR]: RewardTier.GREAT, + [MoveId.BUG_BUZZ]: RewardTier.GREAT, + [MoveId.DRAGON_PULSE]: RewardTier.GREAT, + [MoveId.POWER_GEM]: RewardTier.GREAT, + [MoveId.DRAIN_PUNCH]: RewardTier.GREAT, + [MoveId.VACUUM_WAVE]: RewardTier.COMMON, + [MoveId.FOCUS_BLAST]: RewardTier.GREAT, + [MoveId.ENERGY_BALL]: RewardTier.GREAT, + [MoveId.BRAVE_BIRD]: RewardTier.ULTRA, + [MoveId.EARTH_POWER]: RewardTier.ULTRA, + [MoveId.GIGA_IMPACT]: RewardTier.GREAT, + [MoveId.NASTY_PLOT]: RewardTier.COMMON, + [MoveId.AVALANCHE]: RewardTier.GREAT, + [MoveId.SHADOW_CLAW]: RewardTier.GREAT, + [MoveId.THUNDER_FANG]: RewardTier.GREAT, + [MoveId.ICE_FANG]: RewardTier.GREAT, + [MoveId.FIRE_FANG]: RewardTier.GREAT, + [MoveId.PSYCHO_CUT]: RewardTier.GREAT, + [MoveId.ZEN_HEADBUTT]: RewardTier.GREAT, + [MoveId.FLASH_CANNON]: RewardTier.GREAT, + [MoveId.ROCK_CLIMB]: RewardTier.GREAT, + [MoveId.DEFOG]: RewardTier.COMMON, + [MoveId.TRICK_ROOM]: RewardTier.COMMON, + [MoveId.DRACO_METEOR]: RewardTier.ULTRA, + [MoveId.LEAF_STORM]: RewardTier.ULTRA, + [MoveId.POWER_WHIP]: RewardTier.ULTRA, + [MoveId.CROSS_POISON]: RewardTier.GREAT, + [MoveId.GUNK_SHOT]: RewardTier.ULTRA, + [MoveId.IRON_HEAD]: RewardTier.GREAT, + [MoveId.STONE_EDGE]: RewardTier.ULTRA, + [MoveId.STEALTH_ROCK]: RewardTier.COMMON, + [MoveId.GRASS_KNOT]: RewardTier.ULTRA, + [MoveId.BUG_BITE]: RewardTier.GREAT, + [MoveId.CHARGE_BEAM]: RewardTier.GREAT, + [MoveId.HONE_CLAWS]: RewardTier.COMMON, + [MoveId.WONDER_ROOM]: RewardTier.COMMON, + [MoveId.PSYSHOCK]: RewardTier.GREAT, + [MoveId.VENOSHOCK]: RewardTier.GREAT, + [MoveId.MAGIC_ROOM]: RewardTier.COMMON, + [MoveId.SMACK_DOWN]: RewardTier.COMMON, + [MoveId.SLUDGE_WAVE]: RewardTier.GREAT, + [MoveId.HEAVY_SLAM]: RewardTier.GREAT, + [MoveId.ELECTRO_BALL]: RewardTier.GREAT, + [MoveId.FLAME_CHARGE]: RewardTier.GREAT, + [MoveId.LOW_SWEEP]: RewardTier.GREAT, + [MoveId.ACID_SPRAY]: RewardTier.COMMON, + [MoveId.FOUL_PLAY]: RewardTier.ULTRA, + [MoveId.ROUND]: RewardTier.COMMON, + [MoveId.ECHOED_VOICE]: RewardTier.COMMON, + [MoveId.STORED_POWER]: RewardTier.COMMON, + [MoveId.ALLY_SWITCH]: RewardTier.COMMON, + [MoveId.SCALD]: RewardTier.GREAT, + [MoveId.HEX]: RewardTier.GREAT, + [MoveId.SKY_DROP]: RewardTier.GREAT, + [MoveId.INCINERATE]: RewardTier.GREAT, + [MoveId.QUASH]: RewardTier.COMMON, + [MoveId.ACROBATICS]: RewardTier.GREAT, + [MoveId.RETALIATE]: RewardTier.GREAT, + [MoveId.WATER_PLEDGE]: RewardTier.GREAT, + [MoveId.FIRE_PLEDGE]: RewardTier.GREAT, + [MoveId.GRASS_PLEDGE]: RewardTier.GREAT, + [MoveId.VOLT_SWITCH]: RewardTier.GREAT, + [MoveId.STRUGGLE_BUG]: RewardTier.COMMON, + [MoveId.BULLDOZE]: RewardTier.GREAT, + [MoveId.FROST_BREATH]: RewardTier.GREAT, + [MoveId.DRAGON_TAIL]: RewardTier.GREAT, + [MoveId.WORK_UP]: RewardTier.COMMON, + [MoveId.ELECTROWEB]: RewardTier.GREAT, + [MoveId.WILD_CHARGE]: RewardTier.GREAT, + [MoveId.DRILL_RUN]: RewardTier.GREAT, + [MoveId.RAZOR_SHELL]: RewardTier.GREAT, + [MoveId.HEAT_CRASH]: RewardTier.GREAT, + [MoveId.TAIL_SLAP]: RewardTier.GREAT, + [MoveId.HURRICANE]: RewardTier.ULTRA, + [MoveId.SNARL]: RewardTier.COMMON, + [MoveId.PHANTOM_FORCE]: RewardTier.ULTRA, + [MoveId.PETAL_BLIZZARD]: RewardTier.GREAT, + [MoveId.DISARMING_VOICE]: RewardTier.GREAT, + [MoveId.DRAINING_KISS]: RewardTier.GREAT, + [MoveId.GRASSY_TERRAIN]: RewardTier.COMMON, + [MoveId.MISTY_TERRAIN]: RewardTier.COMMON, + [MoveId.PLAY_ROUGH]: RewardTier.GREAT, + [MoveId.CONFIDE]: RewardTier.COMMON, + [MoveId.MYSTICAL_FIRE]: RewardTier.GREAT, + [MoveId.EERIE_IMPULSE]: RewardTier.COMMON, + [MoveId.VENOM_DRENCH]: RewardTier.COMMON, + [MoveId.ELECTRIC_TERRAIN]: RewardTier.COMMON, + [MoveId.DAZZLING_GLEAM]: RewardTier.ULTRA, + [MoveId.INFESTATION]: RewardTier.COMMON, + [MoveId.POWER_UP_PUNCH]: RewardTier.GREAT, + [MoveId.DARKEST_LARIAT]: RewardTier.GREAT, + [MoveId.HIGH_HORSEPOWER]: RewardTier.ULTRA, + [MoveId.SOLAR_BLADE]: RewardTier.GREAT, + [MoveId.THROAT_CHOP]: RewardTier.GREAT, + [MoveId.POLLEN_PUFF]: RewardTier.GREAT, + [MoveId.PSYCHIC_TERRAIN]: RewardTier.COMMON, + [MoveId.LUNGE]: RewardTier.GREAT, + [MoveId.SPEED_SWAP]: RewardTier.COMMON, + [MoveId.SMART_STRIKE]: RewardTier.GREAT, + [MoveId.BRUTAL_SWING]: RewardTier.GREAT, + [MoveId.AURORA_VEIL]: RewardTier.COMMON, + [MoveId.PSYCHIC_FANGS]: RewardTier.GREAT, + [MoveId.STOMPING_TANTRUM]: RewardTier.GREAT, + [MoveId.LIQUIDATION]: RewardTier.ULTRA, + [MoveId.BODY_PRESS]: RewardTier.ULTRA, + [MoveId.BREAKING_SWIPE]: RewardTier.GREAT, + [MoveId.STEEL_BEAM]: RewardTier.ULTRA, + [MoveId.EXPANDING_FORCE]: RewardTier.GREAT, + [MoveId.STEEL_ROLLER]: RewardTier.COMMON, + [MoveId.SCALE_SHOT]: RewardTier.ULTRA, + [MoveId.METEOR_BEAM]: RewardTier.GREAT, + [MoveId.MISTY_EXPLOSION]: RewardTier.COMMON, + [MoveId.GRASSY_GLIDE]: RewardTier.COMMON, + [MoveId.RISING_VOLTAGE]: RewardTier.COMMON, + [MoveId.TERRAIN_PULSE]: RewardTier.COMMON, + [MoveId.SKITTER_SMACK]: RewardTier.GREAT, + [MoveId.BURNING_JEALOUSY]: RewardTier.GREAT, + [MoveId.LASH_OUT]: RewardTier.GREAT, + [MoveId.POLTERGEIST]: RewardTier.ULTRA, + [MoveId.CORROSIVE_GAS]: RewardTier.COMMON, + [MoveId.COACHING]: RewardTier.COMMON, + [MoveId.FLIP_TURN]: RewardTier.COMMON, + [MoveId.TRIPLE_AXEL]: RewardTier.COMMON, + [MoveId.DUAL_WINGBEAT]: RewardTier.COMMON, + [MoveId.SCORCHING_SANDS]: RewardTier.GREAT, + [MoveId.TERA_BLAST]: RewardTier.GREAT, + [MoveId.ICE_SPINNER]: RewardTier.GREAT, + [MoveId.SNOWSCAPE]: RewardTier.COMMON, + [MoveId.POUNCE]: RewardTier.COMMON, + [MoveId.TRAILBLAZE]: RewardTier.COMMON, + [MoveId.CHILLING_WATER]: RewardTier.COMMON, + [MoveId.HARD_PRESS]: RewardTier.GREAT, + [MoveId.DRAGON_CHEER]: RewardTier.COMMON, + [MoveId.ALLURING_VOICE]: RewardTier.GREAT, + [MoveId.TEMPER_FLARE]: RewardTier.GREAT, + [MoveId.SUPERCELL_SLAM]: RewardTier.GREAT, + [MoveId.PSYCHIC_NOISE]: RewardTier.GREAT, + [MoveId.UPPER_HAND]: RewardTier.COMMON, }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 3fdd83c185d..09b00f1af2c 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -22,7 +22,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import type { MoveId } from "#enums/move-id"; import { TypeColor, TypeShadow } from "#enums/color"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; @@ -459,11 +459,11 @@ export class SingleGenerationChallenge extends Challenge { .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }); @@ -476,12 +476,12 @@ export class SingleGenerationChallenge extends Challenge { .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }); diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 7a1c9821e89..4c88add526a 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -19,7 +19,7 @@ import i18next from "i18next"; import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { modifierTypes } from "#app/data/data-lists"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -165,7 +165,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. setEncounterRewards( { guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ULTRA], fillRemaining: true, }, [eggOptions], diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index d0b5920d842..6937945eeca 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -41,7 +41,7 @@ import { GigantamaxAccessModifier, MegaEvolutionAccessModifier } from "#app/modi import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { allMoves } from "#app/data/data-lists"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { HeldItemId } from "#enums/held-item-id"; @@ -470,7 +470,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.updateModifiers(true); const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; - bugNet.type.tier = ModifierTier.ROGUE; + bugNet.type.tier = RewardTier.ROGUE; setEncounterRewards({ guaranteedModifierTypeOptions: [bugNet], diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 9bdaa603540..f60ada2c6e9 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -11,7 +11,7 @@ import { import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -320,18 +320,18 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) { const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party); - const tier = type.tier ?? ModifierTier.ULTRA; - if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { + const tier = type.tier ?? RewardTier.ULTRA; + if (type.id === "GOLDEN_EGG" || tier === RewardTier.ROGUE) { numRogue += m.stackCount; globalScene.removeModifier(m); - } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) { + } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === RewardTier.ULTRA) { numUltra += m.stackCount; globalScene.removeModifier(m); } } - generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); - generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); + generateItemsOfTier(mostHeldItemsPokemon, numUltra, RewardTier.ULTRA); + generateItemsOfTier(mostHeldItemsPokemon, numRogue, RewardTier.ROGUE); }) .withOptionPhase(async () => { leaveEncounterWithoutBattle(true); @@ -487,7 +487,7 @@ function onYesAbilitySwap(resolve) { selectPokemonForOption(onPokemonSelected, onPokemonNotSelected); } -function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") { +function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: RewardTier | "Berries") { // These pools have to be defined at runtime so that modifierTypes exist // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon // This is to prevent "over-generating" a random item of a certain type during item swaps @@ -528,7 +528,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod if (tier === "Berries") { pool = berryPool; } else { - pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool; + pool = tier === RewardTier.ULTRA ? ultraPool : roguePool; } for (let i = 0; i < numItems; i++) { diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index c53ff610c48..038b3e69de0 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -9,7 +9,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; @@ -89,12 +89,12 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. // Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER const tier = globalScene.currentBattle.waveIndex > 160 - ? ModifierTier.MASTER + ? RewardTier.MASTER : globalScene.currentBattle.waveIndex > 120 - ? ModifierTier.ROGUE + ? RewardTier.ROGUE : globalScene.currentBattle.waveIndex > 40 - ? ModifierTier.ULTRA - : ModifierTier.GREAT; + ? RewardTier.ULTRA + : RewardTier.GREAT; regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); let item: ModifierTypeOption | null = null; // TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 6bbc1a68772..4a84d62c688 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -4,7 +4,6 @@ import { setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "#enums/trainer-slot"; -import { ModifierTier } from "#enums/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; @@ -33,13 +32,7 @@ import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { - HiddenAbilityRateBoosterModifier, - PokemonFormChangeItemModifier, - ShinyRateBoosterModifier, - SpeciesStatBoosterModifier, -} from "#app/modifier/modifier"; +import { HiddenAbilityRateBoosterModifier, ShinyRateBoosterModifier } from "#app/modifier/modifier"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import PokemonData from "#app/system/pokemon-data"; import i18next from "i18next"; @@ -53,6 +46,9 @@ import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; import { timedEventManager } from "#app/global-event-manager"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/items/all-held-items"; +import { RewardTier } from "#enums/reward-tier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -215,9 +211,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; - const modifiers = tradedPokemon - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); + const heldItemConfig = tradedPokemon.heldItemManager + .generateHeldItemConfiguration() + .filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER)); // Generate a trainer name const traderName = generateRandomTraderName(); @@ -241,16 +237,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil dataSource.variant, dataSource.ivs, dataSource.nature, + heldItemConfig, dataSource, ); globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); - for (const mod of modifiers) { - mod.pokemonId = newPlayerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - // Show the trade animation await showTradeBackground(); await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); @@ -336,9 +328,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; - const modifiers = tradedPokemon - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); + const heldItemConfig = tradedPokemon.heldItemManager + .generateHeldItemConfiguration() + .filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER)); // Generate a trainer name const traderName = generateRandomTraderName(); @@ -361,16 +353,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil dataSource.variant, dataSource.ivs, dataSource.nature, + heldItemConfig, dataSource, ); globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); - for (const mod of modifiers) { - mod.pokemonId = newPlayerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - // Show the trade animation await showTradeBackground(); await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); @@ -395,17 +383,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return it.isTransferable; - }); + const validItems = pokemon.heldItemManager.getTransferableHeldItems(); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((id: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[id].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); - encounter.misc.chosenModifier = modifier; + encounter.setDialogueToken("chosenItem", allHeldItems[id].name); + encounter.misc.chosenHeldItem = id; encounter.misc.chosenPokemon = pokemon; return true; }, @@ -416,10 +402,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const selectableFilter = (pokemon: Pokemon) => { // If pokemon has items to trade - const meetsReqs = - pokemon.getHeldItems().filter(it => { - return it.isTransferable; - }).length > 0; + const meetsReqs = pokemon.heldItemManager.getTransferableHeldItems().length > 0; if (!meetsReqs) { return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; } @@ -431,23 +414,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier; + const heldItemId = encounter.misc.chosenHeldItem as HeldItemId; const party = globalScene.getPlayerParty(); const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up - const type = modifier.type.withTierFromPool(ModifierPoolType.PLAYER, party); - let tier = type.tier ?? ModifierTier.GREAT; - // Eggs and White Herb are not in the pool - if (type.id === "WHITE_HERB") { - tier = ModifierTier.GREAT; - } else if (type.id === "LUCKY_EGG") { - tier = ModifierTier.ULTRA; - } else if (type.id === "GOLDEN_EGG") { - tier = ModifierTier.ROGUE; - } + let tier = globalTradeItemTiers[heldItemId] ?? RewardTier.GREAT; + // Increment tier by 1 - if (tier < ModifierTier.MASTER) { + if (tier < RewardTier.MASTER) { tier++; } @@ -467,8 +442,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil fillRemaining: false, }); - chosenPokemon.loseHeldItem(modifier, false); - await globalScene.updateModifiers(true, true); + chosenPokemon.heldItemManager.remove(heldItemId); + await globalScene.updateModifiers(true); // Generate a trainer name const traderName = generateRandomTraderName(); @@ -1000,3 +975,38 @@ function generateRandomTraderName() { const trainerNames = trainerNameString.split(" & "); return trainerNames[randInt(trainerNames.length)]; } + +const globalTradeItemTiers = { + [HeldItemCategoryId.BERRY]: RewardTier.COMMON, + + [HeldItemCategoryId.BASE_STAT_BOOST]: RewardTier.GREAT, + [HeldItemId.WHITE_HERB]: RewardTier.GREAT, + [HeldItemId.METAL_POWDER]: RewardTier.GREAT, + [HeldItemId.QUICK_POWDER]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_SCALE]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_TOOTH]: RewardTier.GREAT, + + [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RewardTier.ULTRA, + [HeldItemId.REVIVER_SEED]: RewardTier.ULTRA, + [HeldItemId.LIGHT_BALL]: RewardTier.ULTRA, + [HeldItemId.EVIOLITE]: RewardTier.ULTRA, + [HeldItemId.QUICK_CLAW]: RewardTier.ULTRA, + [HeldItemId.MYSTICAL_ROCK]: RewardTier.ULTRA, + [HeldItemId.WIDE_LENS]: RewardTier.ULTRA, + [HeldItemId.GOLDEN_PUNCH]: RewardTier.ULTRA, + [HeldItemId.TOXIC_ORB]: RewardTier.ULTRA, + [HeldItemId.FLAME_ORB]: RewardTier.ULTRA, + [HeldItemId.LUCKY_EGG]: RewardTier.ULTRA, + + [HeldItemId.FOCUS_BAND]: RewardTier.ROGUE, + [HeldItemId.KINGS_ROCK]: RewardTier.ROGUE, + [HeldItemId.LEFTOVERS]: RewardTier.ROGUE, + [HeldItemId.SHELL_BELL]: RewardTier.ROGUE, + [HeldItemId.GRIP_CLAW]: RewardTier.ROGUE, + [HeldItemId.SOUL_DEW]: RewardTier.ROGUE, + [HeldItemId.BATON]: RewardTier.ROGUE, + [HeldItemId.GOLDEN_EGG]: RewardTier.ULTRA, + + [HeldItemId.MINI_BLACK_HOLE]: RewardTier.MASTER, + [HeldItemId.MULTI_LENS]: RewardTier.MASTER, +}; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 010358ea3b2..4e63291583f 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -7,7 +7,7 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; @@ -176,7 +176,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], fillRemaining: true, }); @@ -207,7 +207,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter encounter.expMultiplier = 0.9; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], fillRemaining: true, }); diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 321e65d7008..ca3c3e5aafd 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -16,7 +16,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { randSeedInt } from "#app/utils/common"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -144,7 +144,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { // Choose between 2 COMMON / 2 GREAT tier items (20%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.GREAT, RewardTier.GREAT], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.normal`); @@ -152,7 +152,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { // Choose between 3 ULTRA tier items (30%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.good`); @@ -160,7 +160,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { // Choose between 2 ROGUE tier items (10%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.great`); @@ -171,7 +171,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde ) { // Choose 1 MASTER tier item (5%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.amazing`); diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index ac53b3c8ec6..08ffc6eb492 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -25,7 +25,7 @@ import i18next from "i18next"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; import { modifierTypes } from "#app/data/data-lists"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { HeldItemId } from "#enums/held-item-id"; //TODO: make all items unstealable @@ -164,7 +164,7 @@ async function spawnNextTrainerOrEndEncounter() { await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`); globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; - machoBrace.type.tier = ModifierTier.MASTER; + machoBrace.type.tier = RewardTier.MASTER; setEncounterRewards({ guaranteedModifierTypeOptions: [machoBrace], fillRemaining: false, diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 6a2d220e982..c5770ece3f5 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -19,7 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { SpeciesId } from "#enums/species-id"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; @@ -172,7 +172,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], fillRemaining: true, }); encounter.startOfBattleEffects.push( diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index fd88b318c1f..e0d6989b498 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -32,7 +32,7 @@ import { import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { Challenges } from "#enums/challenges"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; @@ -318,12 +318,12 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit setEncounterRewards( { guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.GREAT, - ModifierTier.GREAT, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.GREAT, + RewardTier.GREAT, ], fillRemaining: false, }, diff --git a/src/enums/modifier-tier.ts b/src/enums/modifier-tier.ts deleted file mode 100644 index d8a75e41b0a..00000000000 --- a/src/enums/modifier-tier.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum ModifierTier { - COMMON, - GREAT, - ULTRA, - ROGUE, - MASTER, - LUXURY, -} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 14f2f24619d..2861a09d80a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -139,7 +139,7 @@ import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { AbilityId } from "#enums/ability-id"; @@ -2977,11 +2977,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) { + if (tmPoolTiers[moveId] === RewardTier.COMMON && this.level >= 15) { movePool.push([moveId, 4]); - } else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) { + } else if (tmPoolTiers[moveId] === RewardTier.GREAT && this.level >= 30) { movePool.push([moveId, 8]); - } else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) { + } else if (tmPoolTiers[moveId] === RewardTier.ULTRA && this.level >= 50) { movePool.push([moveId, 14]); } } diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 16ca03fae92..7e1bb7c9957 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -3,7 +3,7 @@ import { enemyBuffModifierPool, modifierPool } from "#app/modifier/modifier-pool import { globalScene } from "#app/global-scene"; import { DoubleBattleChanceBoosterModifier } from "./modifier"; import { WeightedModifierType } from "./modifier-type"; -import { ModifierTier } from "../enums/modifier-tier"; +import { RewardTier } from "../enums/modifier-tier"; import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import { PokeballType } from "#enums/pokeball"; @@ -25,7 +25,7 @@ import { allHeldItems } from "#app/items/all-held-items"; * Initialize the common modifier pool */ function initCommonModifierPool() { - modifierPool[ModifierTier.COMMON] = [ + modifierPool[RewardTier.COMMON] = [ new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), new WeightedModifierType(modifierTypes.RARE_CANDY, 2), new WeightedModifierType( @@ -93,7 +93,7 @@ function initCommonModifierPool() { new WeightedModifierType(modifierTypes.BERRY, 2), new WeightedModifierType(modifierTypes.TM_COMMON, 2), ].map(m => { - m.setTier(ModifierTier.COMMON); + m.setTier(RewardTier.COMMON); return m; }); } @@ -102,7 +102,7 @@ function initCommonModifierPool() { * Initialize the Great modifier pool */ function initGreatModifierPool() { - modifierPool[ModifierTier.GREAT] = [ + modifierPool[RewardTier.GREAT] = [ new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), new WeightedModifierType(modifierTypes.PP_UP, 2), new WeightedModifierType( @@ -292,7 +292,7 @@ function initGreatModifierPool() { 1, ), ].map(m => { - m.setTier(ModifierTier.GREAT); + m.setTier(RewardTier.GREAT); return m; }); } @@ -301,7 +301,7 @@ function initGreatModifierPool() { * Initialize the Ultra modifier pool */ function initUltraModifierPool() { - modifierPool[ModifierTier.ULTRA] = [ + modifierPool[RewardTier.ULTRA] = [ new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), @@ -514,13 +514,13 @@ function initUltraModifierPool() { new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), new WeightedModifierType(modifierTypes.WIDE_LENS, 7), ].map(m => { - m.setTier(ModifierTier.ULTRA); + m.setTier(RewardTier.ULTRA); return m; }); } function initRogueModifierPool() { - modifierPool[ModifierTier.ROGUE] = [ + modifierPool[RewardTier.ROGUE] = [ new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LEFTOVERS, 3), @@ -558,7 +558,7 @@ function initRogueModifierPool() { 3, ), ].map(m => { - m.setTier(ModifierTier.ROGUE); + m.setTier(RewardTier.ROGUE); return m; }); } @@ -567,7 +567,7 @@ function initRogueModifierPool() { * Initialize the Master modifier pool */ function initMasterModifierPool() { - modifierPool[ModifierTier.MASTER] = [ + modifierPool[RewardTier.MASTER] = [ new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), @@ -600,7 +600,7 @@ function initMasterModifierPool() { 1, ), ].map(m => { - m.setTier(ModifierTier.MASTER); + m.setTier(RewardTier.MASTER); return m; }); } @@ -609,7 +609,7 @@ function initMasterModifierPool() { * Initialize the enemy buff modifier pool */ function initEnemyBuffModifierPool() { - enemyBuffModifierPool[ModifierTier.COMMON] = [ + enemyBuffModifierPool[RewardTier.COMMON] = [ new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), @@ -619,20 +619,20 @@ function initEnemyBuffModifierPool() { new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), ].map(m => { - m.setTier(ModifierTier.COMMON); + m.setTier(RewardTier.COMMON); return m; }); - enemyBuffModifierPool[ModifierTier.GREAT] = [ + enemyBuffModifierPool[RewardTier.GREAT] = [ new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), ].map(m => { - m.setTier(ModifierTier.GREAT); + m.setTier(RewardTier.GREAT); return m; }); - enemyBuffModifierPool[ModifierTier.ULTRA] = [ + enemyBuffModifierPool[RewardTier.ULTRA] = [ new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), @@ -640,15 +640,15 @@ function initEnemyBuffModifierPool() { new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), ].map(m => { - m.setTier(ModifierTier.ULTRA); + m.setTier(RewardTier.ULTRA); return m; }); - enemyBuffModifierPool[ModifierTier.ROGUE] = [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.ROGUE); + enemyBuffModifierPool[RewardTier.ROGUE] = [].map((m: WeightedModifierType) => { + m.setTier(RewardTier.ROGUE); return m; }); - enemyBuffModifierPool[ModifierTier.MASTER] = [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.MASTER); + enemyBuffModifierPool[RewardTier.MASTER] = [].map((m: WeightedModifierType) => { + m.setTier(RewardTier.MASTER); return m; }); } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 759bcd42470..912fd95bb43 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -66,7 +66,7 @@ import { CriticalCatchChanceBoosterModifier, MoneyRewardModifier, } from "#app/modifier/modifier"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import Overrides from "#app/overrides"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; @@ -113,7 +113,7 @@ export class ModifierType { public iconImage: string; public group: string; public soundName: string; - public tier: ModifierTier; + public tier: RewardTier; protected newModifierFunc: NewModifierFunc | null; /** @@ -155,7 +155,7 @@ export class ModifierType { return this.iconImage; } - setTier(tier: ModifierTier): void { + setTier(tier: RewardTier): void { this.tier = tier; } @@ -185,7 +185,7 @@ export class ModifierType { party?: PlayerPokemon[], rerollCount = 0, ): ModifierType { - let defaultTier: undefined | ModifierTier; + let defaultTier: undefined | RewardTier; for (const tier of Object.values(getModifierPoolForType(poolType))) { for (const modifier of tier) { if (this.id === modifier.modifierType.id) { @@ -1204,7 +1204,7 @@ class SpeciesStatBoosterRewardGenerator extends ModifierTypeGenerator { } class TmModifierTypeGenerator extends ModifierTypeGenerator { - constructor(tier: ModifierTier) { + constructor(tier: RewardTier) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { return new TmModifierType(pregenArgs[0] as MoveId); @@ -1409,7 +1409,7 @@ export class WeightedModifierType { this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); } - setTier(tier: ModifierTier) { + setTier(tier: RewardTier) { this.modifierType.setTier(tier); } } @@ -1600,9 +1600,9 @@ const modifierTypeInitObj = Object.freeze({ BERRY: () => new BerryRewardGenerator(), - TM_COMMON: () => new TmModifierTypeGenerator(ModifierTier.COMMON), - TM_GREAT: () => new TmModifierTypeGenerator(ModifierTier.GREAT), - TM_ULTRA: () => new TmModifierTypeGenerator(ModifierTier.ULTRA), + TM_COMMON: () => new TmModifierTypeGenerator(RewardTier.COMMON), + TM_GREAT: () => new TmModifierTypeGenerator(RewardTier.GREAT), + TM_ULTRA: () => new TmModifierTypeGenerator(RewardTier.ULTRA), MEMORY_MUSHROOM: () => new RememberMoveModifierType("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), @@ -1946,7 +1946,7 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod } export interface CustomModifierSettings { - guaranteedModifierTiers?: ModifierTier[]; + guaranteedModifierTiers?: RewardTier[]; guaranteedModifierTypeOptions?: ModifierTypeOption[]; guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; fillRemaining?: boolean; @@ -1967,9 +1967,9 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { * @param customModifierSettings (Optional) If specified, can customize the item shop rewards further. * - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` If specified, will override the first X items to be specific modifier options (these should be pre-genned). * - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned). - * - `guaranteedModifierTiers?: ModifierTier[]` If specified, will override the next X items to be the specified tier. These can upgrade with luck. + * - `guaranteedModifierTiers?: RewardTier[]` If specified, will override the next X items to be the specified tier. These can upgrade with luck. * - `fillRemaining?: boolean` Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value. - * - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }`, + * - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [RewardTier.GREAT], fillRemaining: true }`, * - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally. * - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value). * - `rerollMultiplier?: number` If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all. @@ -1978,7 +1978,7 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { export function getPlayerModifierTypeOptions( count: number, party: PlayerPokemon[], - modifierTiers?: ModifierTier[], + modifierTiers?: RewardTier[], customModifierSettings?: CustomModifierSettings, ): ModifierTypeOption[] { const options: ModifierTypeOption[] = []; @@ -2059,7 +2059,7 @@ function getModifierTypeOptionWithRetry( existingOptions: ModifierTypeOption[], retryCount: number, party: PlayerPokemon[], - tier?: ModifierTier, + tier?: RewardTier, allowLuckUpgrades?: boolean, ): ModifierTypeOption { allowLuckUpgrades = allowLuckUpgrades ?? true; @@ -2142,15 +2142,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC } export function getEnemyBuffModifierForWave( - tier: ModifierTier, + tier: RewardTier, enemyModifiers: PersistentModifier[], ): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { - case ModifierTier.ULTRA: + case RewardTier.ULTRA: tierStackCount = 5; break; - case ModifierTier.GREAT: + case RewardTier.GREAT: tierStackCount = 3; break; default: @@ -2188,7 +2188,7 @@ export function getEnemyBuffModifierForWave( function getNewModifierTypeOption( party: Pokemon[], poolType: ModifierPoolType, - baseTier?: ModifierTier, + baseTier?: RewardTier, upgradeCount?: number, retryCount = 0, allowLuckUpgrades = true, @@ -2223,7 +2223,7 @@ function getNewModifierTypeOption( modifierType = (modifierType as ModifierTypeGenerator).generateType(party); if (modifierType === null) { if (player) { - console.log(ModifierTier[tier], upgradeCount); + console.log(RewardTier[tier], upgradeCount); } return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); } @@ -2250,11 +2250,11 @@ function getPoolThresholds(poolType: ModifierPoolType) { function determineTier( party: Pokemon[], player: boolean, - tier?: ModifierTier, + tier?: RewardTier, upgradeCount?: number, retryCount = 0, allowLuckUpgrades = true, -): ModifierTier { +): RewardTier { if (tier === undefined) { const tierValue = randSeedInt(1024); if (!upgradeCount) { @@ -2273,15 +2273,15 @@ function determineTier( } if (tierValue > 255) { - tier = ModifierTier.COMMON; + tier = RewardTier.COMMON; } else if (tierValue > 60) { - tier = ModifierTier.GREAT; + tier = RewardTier.GREAT; } else if (tierValue > 12) { - tier = ModifierTier.ULTRA; + tier = RewardTier.ULTRA; } else if (tierValue) { - tier = ModifierTier.ROGUE; + tier = RewardTier.ROGUE; } else { - tier = ModifierTier.MASTER; + tier = RewardTier.MASTER; } tier += upgradeCount; @@ -2293,7 +2293,7 @@ function determineTier( } } else if (upgradeCount === undefined && player) { upgradeCount = 0; - if (tier < ModifierTier.MASTER && allowLuckUpgrades) { + if (tier < RewardTier.MASTER && allowLuckUpgrades) { const partyLuckValue = getPartyLuckValue(party); const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); while (pool.hasOwnProperty(tier + upgradeCount + 1) && pool[tier + upgradeCount + 1].length) { @@ -2312,9 +2312,9 @@ function determineTier( return tier; } -export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { +export function getDefaultModifierTypeForTier(tier: RewardTier): ModifierType { const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER); - let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0]; + let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || RewardTier.COMMON][0]; if (modifierType instanceof WeightedModifierType) { modifierType = (modifierType as WeightedModifierType).modifierType; } @@ -2366,19 +2366,19 @@ export function getLuckString(luckValue: number): string { } export function getLuckTextTint(luckValue: number): number { - let modifierTier: ModifierTier; + let modifierTier: RewardTier; if (luckValue > 11) { - modifierTier = ModifierTier.LUXURY; + modifierTier = RewardTier.LUXURY; } else if (luckValue > 9) { - modifierTier = ModifierTier.MASTER; + modifierTier = RewardTier.MASTER; } else if (luckValue > 5) { - modifierTier = ModifierTier.ROGUE; + modifierTier = RewardTier.ROGUE; } else if (luckValue > 2) { - modifierTier = ModifierTier.ULTRA; + modifierTier = RewardTier.ULTRA; } else if (luckValue) { - modifierTier = ModifierTier.GREAT; + modifierTier = RewardTier.GREAT; } else { - modifierTier = ModifierTier.COMMON; + modifierTier = RewardTier.COMMON; } return getModifierTierTextTint(modifierTier); } diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 218a3a653ff..6ff96bee16b 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { EnemyPersistentModifier } from "#app/modifier/modifier"; @@ -11,11 +11,7 @@ export class AddEnemyBuffModifierPhase extends Phase { super.start(); const waveIndex = globalScene.currentBattle.waveIndex; - const tier = !(waveIndex % 1000) - ? ModifierTier.ULTRA - : !(waveIndex % 250) - ? ModifierTier.GREAT - : ModifierTier.COMMON; + const tier = !(waveIndex % 1000) ? RewardTier.ULTRA : !(waveIndex % 250) ? RewardTier.GREAT : RewardTier.COMMON; regenerateModifierPoolThresholds(globalScene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index a78978fe4ce..93040b15584 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierTier } from "#enums/modifier-tier"; +import type { RewardTier } from "#enums/reward-tier"; import type { ModifierTypeOption, ModifierType } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds, @@ -32,7 +32,7 @@ export type ModifierSelectCallback = (rowCursor: number, cursor: number) => bool export class SelectModifierPhase extends BattlePhase { public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; - private modifierTiers?: ModifierTier[]; + private modifierTiers?: RewardTier[]; private customModifierSettings?: CustomModifierSettings; private isCopy: boolean; @@ -40,7 +40,7 @@ export class SelectModifierPhase extends BattlePhase { constructor( rerollCount = 0, - modifierTiers?: ModifierTier[], + modifierTiers?: RewardTier[], customModifierSettings?: CustomModifierSettings, isCopy = false, ) { @@ -193,7 +193,7 @@ export class SelectModifierPhase extends BattlePhase { globalScene.phaseManager.unshiftNew( "SelectModifierPhase", this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as RewardTier[], ); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); diff --git a/src/ui/text.ts b/src/ui/text.ts index 8812d8ee4a8..ce06bd4f153 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -4,7 +4,7 @@ import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import { globalScene } from "#app/global-scene"; -import { ModifierTier } from "../enums/modifier-tier"; +import { RewardTier } from "../enums/modifier-tier"; import i18next from "#app/plugins/i18n"; export enum TextStyle { @@ -423,19 +423,19 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui } } -export function getModifierTierTextTint(tier: ModifierTier): number { +export function getModifierTierTextTint(tier: RewardTier): number { switch (tier) { - case ModifierTier.COMMON: + case RewardTier.COMMON: return 0xf8f8f8; - case ModifierTier.GREAT: + case RewardTier.GREAT: return 0x4998f8; - case ModifierTier.ULTRA: + case RewardTier.ULTRA: return 0xf8d038; - case ModifierTier.ROGUE: + case RewardTier.ROGUE: return 0xdb4343; - case ModifierTier.MASTER: + case RewardTier.MASTER: return 0xe331c5; - case ModifierTier.LUXURY: + case RewardTier.LUXURY: return 0xe74c18; } } @@ -443,12 +443,12 @@ export function getModifierTierTextTint(tier: ModifierTier): number { export function getEggTierTextTint(tier: EggTier): number { switch (tier) { case EggTier.COMMON: - return getModifierTierTextTint(ModifierTier.COMMON); + return getModifierTierTextTint(RewardTier.COMMON); case EggTier.RARE: - return getModifierTierTextTint(ModifierTier.GREAT); + return getModifierTierTextTint(RewardTier.GREAT); case EggTier.EPIC: - return getModifierTierTextTint(ModifierTier.ULTRA); + return getModifierTierTextTint(RewardTier.ULTRA); case EggTier.LEGENDARY: - return getModifierTierTextTint(ModifierTier.MASTER); + return getModifierTierTextTint(RewardTier.MASTER); } } diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index beacc3a3907..cbf8d2ca7e9 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -1,6 +1,6 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; @@ -36,7 +36,7 @@ describe("Items - Lock Capsule", () => { await game.classicMode.startBattle(); game.scene.phaseManager.overridePhase( new SelectModifierPhase(0, undefined, { - guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON], + guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.COMMON], fillRemaining: false, }), ); diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 85193d1ec72..818cd0c923c 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -20,7 +20,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; @@ -296,10 +296,10 @@ describe("Clowning Around - Mystery Encounter", () => { const leadItemsAfter = scene.getPlayerParty()[0].getHeldItems(); const ultraCountAfter = leadItemsAfter - .filter(m => m.type.tier === ModifierTier.ULTRA) + .filter(m => m.type.tier === RewardTier.ULTRA) .reduce((a, b) => a + b.stackCount, 0); const rogueCountAfter = leadItemsAfter - .filter(m => m.type.tier === ModifierTier.ROGUE) + .filter(m => m.type.tier === RewardTier.ROGUE) .reduce((a, b) => a + b.stackCount, 0); expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index bb598f4ae6e..15e46d80634 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -17,7 +17,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import * as Utils from "#app/utils/common"; const namespace = "mysteryEncounters/globalTradeSystem"; @@ -234,7 +234,7 @@ describe("Global Trade System - Mystery Encounter", () => { h => h instanceof ModifierSelectUiHandler, ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(ModifierTier.MASTER); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(RewardTier.MASTER); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); expect(soulDewAfter?.stackCount).toBe(1); }); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 478648d88a7..0b9d3f2a5f3 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -14,7 +14,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter"; import { TrainerConfig } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; @@ -218,19 +218,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); }); }); @@ -275,19 +275,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ROGUE); + ).toBe(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ROGUE); + ).toBe(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 9ab5f16d1b9..7626e4c9660 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -14,7 +14,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { CommandPhase } from "#app/phases/command-phase"; @@ -254,19 +254,19 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 475d5cc3c6e..3690a64cab4 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -19,7 +19,7 @@ import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/wei import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [SpeciesId.MAGBY, SpeciesId.HAUNTER, SpeciesId.ABRA]; @@ -207,27 +207,27 @@ describe("Weird Dream - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); expect( modifierSelectHandler.options[5].modifierTypeOption.type.tier - modifierSelectHandler.options[5].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); }); }); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index b6c3089e236..0a4d80c7ed7 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; @@ -129,7 +129,7 @@ describe("SelectModifierPhase", () => { h => h instanceof ModifierSelectUiHandler, ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); - const firstRollTiers: ModifierTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); + const firstRollTiers: RewardTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); // TODO: nagivate ui to reroll with lock capsule enabled @@ -184,11 +184,11 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTiers: [ - ModifierTier.COMMON, - ModifierTier.GREAT, - ModifierTier.ULTRA, - ModifierTier.ROGUE, - ModifierTier.MASTER, + RewardTier.COMMON, + RewardTier.GREAT, + RewardTier.ULTRA, + RewardTier.ROGUE, + RewardTier.MASTER, ], }; const pokemon = new PlayerPokemon(getPokemonSpecies(SpeciesId.BULBASAUR), 10, undefined, 0, undefined, true, 2); @@ -212,23 +212,23 @@ describe("SelectModifierPhase", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.COMMON); + ).toEqual(RewardTier.COMMON); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.MASTER); + ).toEqual(RewardTier.MASTER); }); it("should generate custom modifiers and modifier tiers together", async () => { @@ -236,7 +236,7 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON], - guaranteedModifierTiers: [ModifierTier.MASTER, ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER, RewardTier.MASTER], }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); @@ -250,8 +250,8 @@ describe("SelectModifierPhase", () => { expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_COMMON"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); }); it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => { @@ -259,7 +259,7 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM], - guaranteedModifierTiers: [ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER], fillRemaining: true, }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); @@ -273,6 +273,6 @@ describe("SelectModifierPhase", () => { ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); }); }); From 78b9d1fa815569ba7046a2cda9df9464a98dd916 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:29:06 +0200 Subject: [PATCH 106/114] Fixed some rogue inputs --- src/modifier/init-modifier-pools.ts | 2 +- src/ui/text.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 7e1bb7c9957..d6d0b2a2fd6 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -3,7 +3,7 @@ import { enemyBuffModifierPool, modifierPool } from "#app/modifier/modifier-pool import { globalScene } from "#app/global-scene"; import { DoubleBattleChanceBoosterModifier } from "./modifier"; import { WeightedModifierType } from "./modifier-type"; -import { RewardTier } from "../enums/modifier-tier"; +import { RewardTier } from "#app/enums/reward-tier"; import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import { PokeballType } from "#enums/pokeball"; diff --git a/src/ui/text.ts b/src/ui/text.ts index ce06bd4f153..00351cd3585 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -4,7 +4,7 @@ import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import { globalScene } from "#app/global-scene"; -import { RewardTier } from "../enums/modifier-tier"; +import { RewardTier } from "#app/enums/reward-tier"; import i18next from "#app/plugins/i18n"; export enum TextStyle { From afbdf9afb24214042e80efd55f5dd195526c602b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:25:12 +0200 Subject: [PATCH 107/114] Held item pools take a single pokemon as input, set weights to 0 for max stack items --- src/items/held-item-pool.ts | 65 +++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index c79936305d4..ff311bd4593 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -7,6 +7,7 @@ import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; +import { allHeldItems } from "./all-held-items"; import { type HeldItemConfiguration, type HeldItemPool, @@ -34,7 +35,7 @@ export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { const tier = getDailyRewardTier(tierValue); - const item = getNewHeldItemFromPool(getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, party); + const item = getNewHeldItemFromPool(getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, p); p.heldItemManager.add(item); } } @@ -83,7 +84,7 @@ export function assignEnemyHeldItemsForWave( for (let i = 1; i <= count; i++) { const item = getNewHeldItemFromTieredPool( getHeldItemPool(poolType), - [enemy], + enemy, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, ); enemy.heldItemManager.add(item); @@ -131,7 +132,7 @@ function determineEnemyPoolTier(pool: HeldItemTieredPool, upgradeCount?: number) function getNewHeldItemFromTieredPool( pool: HeldItemTieredPool, - pokemon: Pokemon | Pokemon[], + pokemon: Pokemon, upgradeCount: number, ): HeldItemId | HeldItemSpecs { const tier = determineEnemyPoolTier(pool, upgradeCount); @@ -159,38 +160,37 @@ function pickWeightedIndex(weights: number[]): number { return -1; // TODO: Change to something more appropriate } -export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}): HeldItemId { +export function getNewVitaminHeldItem(pokemon: Pokemon, customWeights: HeldItemWeights = {}): HeldItemId { const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]); - const weights = items.map(t => customWeights[t] ?? t); + const weights = items.map(t => (pokemon.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1))); return items[pickWeightedIndex(weights)]; } -export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}): HeldItemId { +export function getNewBerryHeldItem(pokemon: Pokemon, customWeights: HeldItemWeights = {}): HeldItemId { const berryTypes = getEnumValues(BerryType); const items = berryTypes.map(b => berryTypeToHeldItem[b]); const weights = items.map(t => - (customWeights[t] ?? (t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY)) - ? 2 - : 1, + pokemon.heldItemManager.isMaxStack(t) + ? 0 + : (customWeights[t] ?? + (t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY)) + ? 2 + : 1, ); return items[pickWeightedIndex(weights)]; } export function getNewAttackTypeBoosterHeldItem( - pokemon: Pokemon | Pokemon[], + pokemon: Pokemon, customWeights: HeldItemWeights = {}, ): HeldItemId | null { - const party = coerceArray(pokemon); - // TODO: make this consider moves or abilities that change types - const attackMoveTypes = party.flatMap(p => - p - .getMoveset() - .filter(m => m.getMove().is("AttackMove")) - .map(m => m.getMove().type), - ); + const attackMoveTypes = pokemon + .getMoveset() + .filter(m => m.getMove().is("AttackMove")) + .map(m => m.getMove().type); if (!attackMoveTypes.length) { return null; } @@ -205,7 +205,11 @@ export function getNewAttackTypeBoosterHeldItem( const types = Array.from(attackMoveTypeWeights.keys()); - const weights = types.map(type => customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!); + const weights = types.map(type => + pokemon.heldItemManager.isMaxStack(attackTypeToHeldItem[type]) + ? 0 + : (customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!), + ); const type = types[pickWeightedIndex(weights)]; return attackTypeToHeldItem[type]; @@ -213,14 +217,14 @@ export function getNewAttackTypeBoosterHeldItem( export function getNewHeldItemFromCategory( id: HeldItemCategoryId, - pokemon: Pokemon | Pokemon[], + pokemon: Pokemon, customWeights: HeldItemWeights = {}, ): HeldItemId | null { if (id === HeldItemCategoryId.BERRY) { - return getNewBerryHeldItem(customWeights); + return getNewBerryHeldItem(pokemon, customWeights); } if (id === HeldItemCategoryId.VITAMIN) { - return getNewVitaminHeldItem(customWeights); + return getNewVitaminHeldItem(pokemon, customWeights); } if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) { return getNewAttackTypeBoosterHeldItem(pokemon, customWeights); @@ -228,11 +232,22 @@ export function getNewHeldItemFromCategory( return null; } -function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon | Pokemon[]): number[] { - return pool.map(p => (typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight)); +function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] { + return pool.map(p => { + let weight = typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight; + + if (typeof p.entry === "number") { + const itemId = p.entry as HeldItemId; + if (pokemon.heldItemManager.getStack(itemId) >= allHeldItems[itemId].getMaxStackCount()) { + weight = 0; + } + } + + return weight; + }); } -function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon | Pokemon[]): HeldItemId | HeldItemSpecs { +function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemId | HeldItemSpecs { const weights = getPoolWeights(pool, pokemon); const entry = pool[pickWeightedIndex(weights)].entry; From 46204067f5ce6d86acd9e185630d4f1cc3dd592a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:37:21 +0200 Subject: [PATCH 108/114] Restored various changes to held item pool generation; distinguishing target of the generation from the party --- src/items/held-item-pool.ts | 46 ++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index ff311bd4593..69b06058a9b 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -35,7 +35,11 @@ export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { const tier = getDailyRewardTier(tierValue); - const item = getNewHeldItemFromPool(getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, p); + const item = getNewHeldItemFromPool( + getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, + p, + party, + ); p.heldItemManager.add(item); } } @@ -160,18 +164,18 @@ function pickWeightedIndex(weights: number[]): number { return -1; // TODO: Change to something more appropriate } -export function getNewVitaminHeldItem(pokemon: Pokemon, customWeights: HeldItemWeights = {}): HeldItemId { +export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId { const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]); - const weights = items.map(t => (pokemon.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1))); + const weights = items.map(t => (target?.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1))); return items[pickWeightedIndex(weights)]; } -export function getNewBerryHeldItem(pokemon: Pokemon, customWeights: HeldItemWeights = {}): HeldItemId { +export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId { const berryTypes = getEnumValues(BerryType); const items = berryTypes.map(b => berryTypeToHeldItem[b]); const weights = items.map(t => - pokemon.heldItemManager.isMaxStack(t) + target?.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? (t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY)) @@ -183,14 +187,19 @@ export function getNewBerryHeldItem(pokemon: Pokemon, customWeights: HeldItemWei } export function getNewAttackTypeBoosterHeldItem( - pokemon: Pokemon, + pokemon: Pokemon | Pokemon[], customWeights: HeldItemWeights = {}, + target?: Pokemon, ): HeldItemId | null { + const party = coerceArray(pokemon); + // TODO: make this consider moves or abilities that change types - const attackMoveTypes = pokemon - .getMoveset() - .filter(m => m.getMove().is("AttackMove")) - .map(m => m.getMove().type); + const attackMoveTypes = party.flatMap(p => + p + .getMoveset() + .filter(m => m.getMove().is("AttackMove")) + .map(m => m.getMove().type), + ); if (!attackMoveTypes.length) { return null; } @@ -206,7 +215,7 @@ export function getNewAttackTypeBoosterHeldItem( const types = Array.from(attackMoveTypeWeights.keys()); const weights = types.map(type => - pokemon.heldItemManager.isMaxStack(attackTypeToHeldItem[type]) + target?.heldItemManager.isMaxStack(attackTypeToHeldItem[type]) ? 0 : (customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!), ); @@ -217,17 +226,18 @@ export function getNewAttackTypeBoosterHeldItem( export function getNewHeldItemFromCategory( id: HeldItemCategoryId, - pokemon: Pokemon, + pokemon: Pokemon | Pokemon[], customWeights: HeldItemWeights = {}, + target?: Pokemon, ): HeldItemId | null { if (id === HeldItemCategoryId.BERRY) { - return getNewBerryHeldItem(pokemon, customWeights); + return getNewBerryHeldItem(customWeights, target); } if (id === HeldItemCategoryId.VITAMIN) { - return getNewVitaminHeldItem(pokemon, customWeights); + return getNewVitaminHeldItem(customWeights, target); } if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) { - return getNewAttackTypeBoosterHeldItem(pokemon, customWeights); + return getNewAttackTypeBoosterHeldItem(pokemon, customWeights, target); } return null; } @@ -247,7 +257,7 @@ function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] { }); } -function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemId | HeldItemSpecs { +function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Pokemon[]): HeldItemId | HeldItemSpecs { const weights = getPoolWeights(pool, pokemon); const entry = pool[pickWeightedIndex(weights)].entry; @@ -257,7 +267,7 @@ function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon): HeldItemI } if (isHeldItemCategoryEntry(entry)) { - return getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights) as HeldItemId; + return getNewHeldItemFromCategory(entry.id, party ?? pokemon, entry?.customWeights, pokemon) as HeldItemId; } return entry as HeldItemSpecs; @@ -278,7 +288,7 @@ export function assignItemsFromConfiguration(config: HeldItemConfiguration, poke if (isHeldItemCategoryEntry(entry)) { for (let i = 1; i <= actualCount; i++) { - const newItem = getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights); + const newItem = getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights, pokemon); if (newItem) { pokemon.heldItemManager.add(newItem); } From f1c6cf4419949945165457029a1dee375fa8d53c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:55:17 +0200 Subject: [PATCH 109/114] Pools can now take a HaldItemConfigurationId without weights; set up table of item tiers; used custom pools in Clowning Around ME --- .../encounters/clowning-around-encounter.ts | 175 ++++++++---------- .../global-trade-system-encounter.ts | 40 +--- src/enums/held-item-id.ts | 2 +- src/items/held-item-data-types.ts | 4 +- src/items/held-item-pool.ts | 24 ++- src/items/held-item-tiers.ts | 47 +++++ 6 files changed, 147 insertions(+), 145 deletions(-) create mode 100644 src/items/held-item-tiers.ts diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index f60ada2c6e9..eb8fdc4a85e 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,6 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, @@ -12,8 +11,6 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { RewardTier } from "#enums/reward-tier"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; @@ -24,10 +21,7 @@ import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; -import { - applyAbilityOverrideToPokemon, - applyModifierTypeToPlayerPokemon, -} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyAbilityOverrideToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -38,8 +32,6 @@ import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { BerryModifier } from "#app/modifier/modifier"; -import { BerryType } from "#enums/berry-type"; import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { EncounterBattleAnim } from "#app/data/battle-anims"; @@ -49,7 +41,10 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; import { MoveUseMode } from "#enums/move-use-mode"; -import { allAbilities, modifierTypes } from "#app/data/data-lists"; +import { allAbilities } from "#app/data/data-lists"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { getHeldItemTier } from "#app/items/held-item-tiers"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/clowningAround"; @@ -283,16 +278,16 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder const party = globalScene.getPlayerParty(); let mostHeldItemsPokemon = party[0]; - let count = mostHeldItemsPokemon - .getHeldItems() - .filter(m => m.isTransferable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); + let count = mostHeldItemsPokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)) + .reduce((v, m) => v + mostHeldItemsPokemon.heldItemManager.getStack(m), 0); for (const pokemon of party) { - const nextCount = pokemon - .getHeldItems() - .filter(m => m.isTransferable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); + const nextCount = pokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)) + .reduce((v, m) => v + pokemon.heldItemManager.getStack(m), 0); if (nextCount > count) { mostHeldItemsPokemon = pokemon; count = nextCount; @@ -301,16 +296,31 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); - const items = mostHeldItemsPokemon.getHeldItems(); + const items = mostHeldItemsPokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)); // Shuffles Berries (if they have any) + const oldBerries = mostHeldItemsPokemon.heldItemManager + .getHeldItems() + .filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); + let numBerries = 0; - for (const m of items.filter(m => m instanceof BerryModifier)) { - numBerries += m.stackCount; - globalScene.removeModifier(m); + for (const berry of oldBerries) { + const stack = mostHeldItemsPokemon.heldItemManager.getStack(berry); + numBerries += stack; + mostHeldItemsPokemon.heldItemManager.remove(berry, stack); } - generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries"); + assignItemsFromConfiguration( + [ + { + entry: HeldItemCategoryId.BERRY, + count: numBerries, + }, + ], + mostHeldItemsPokemon, + ); // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) // For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier @@ -318,20 +328,36 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder let numUltra = 0; let numRogue = 0; - for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) { - const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party); - const tier = type.tier ?? RewardTier.ULTRA; - if (type.id === "GOLDEN_EGG" || tier === RewardTier.ROGUE) { - numRogue += m.stackCount; - globalScene.removeModifier(m); - } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === RewardTier.ULTRA) { - numUltra += m.stackCount; - globalScene.removeModifier(m); + for (const m of items) { + const tier = getHeldItemTier(m) ?? RewardTier.ULTRA; + const stack = mostHeldItemsPokemon.heldItemManager.getStack(m); + if (tier === RewardTier.ROGUE) { + numRogue += stack; + } else if (tier === RewardTier.ULTRA) { + numUltra += stack; } + mostHeldItemsPokemon.heldItemManager.remove(m, stack); } - generateItemsOfTier(mostHeldItemsPokemon, numUltra, RewardTier.ULTRA); - generateItemsOfTier(mostHeldItemsPokemon, numRogue, RewardTier.ROGUE); + assignItemsFromConfiguration( + [ + { + entry: ultraPool, + count: numUltra, + }, + ], + mostHeldItemsPokemon, + ); + + assignItemsFromConfiguration( + [ + { + entry: roguePool, + count: numRogue, + }, + ], + mostHeldItemsPokemon, + ); }) .withOptionPhase(async () => { leaveEncounterWithoutBattle(true); @@ -487,68 +513,21 @@ function onYesAbilitySwap(resolve) { selectPokemonForOption(onPokemonSelected, onPokemonNotSelected); } -function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: RewardTier | "Berries") { - // These pools have to be defined at runtime so that modifierTypes exist - // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon - // This is to prevent "over-generating" a random item of a certain type during item swaps - const ultraPool = [ - [modifierTypes.REVIVER_SEED, 1], - [modifierTypes.GOLDEN_PUNCH, 5], - [modifierTypes.ATTACK_TYPE_BOOSTER, 99], - [modifierTypes.QUICK_CLAW, 3], - [modifierTypes.WIDE_LENS, 3], - ]; +const ultraPool = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 1 }, + { entry: HeldItemId.REVIVER_SEED, weight: 1 }, + { entry: HeldItemId.GOLDEN_PUNCH, weight: 1 }, + { entry: HeldItemId.QUICK_CLAW, weight: 1 }, + { entry: HeldItemId.WIDE_LENS, weight: 1 }, +]; - const roguePool = [ - [modifierTypes.LEFTOVERS, 4], - [modifierTypes.SHELL_BELL, 4], - [modifierTypes.SOUL_DEW, 10], - [modifierTypes.SCOPE_LENS, 1], - [modifierTypes.BATON, 1], - [modifierTypes.FOCUS_BAND, 5], - [modifierTypes.KINGS_ROCK, 3], - [modifierTypes.GRIP_CLAW, 5], - ]; - - const berryPool = [ - [BerryType.APICOT, 3], - [BerryType.ENIGMA, 2], - [BerryType.GANLON, 3], - [BerryType.LANSAT, 3], - [BerryType.LEPPA, 2], - [BerryType.LIECHI, 3], - [BerryType.LUM, 2], - [BerryType.PETAYA, 3], - [BerryType.SALAC, 2], - [BerryType.SITRUS, 2], - [BerryType.STARF, 3], - ]; - - let pool: any[]; - if (tier === "Berries") { - pool = berryPool; - } else { - pool = tier === RewardTier.ULTRA ? ultraPool : roguePool; - } - - for (let i = 0; i < numItems; i++) { - if (pool.length === 0) { - // Stop generating new items if somehow runs out of items to spawn - return; - } - const randIndex = randSeedInt(pool.length); - const newItemType = pool[randIndex]; - let newMod: PokemonHeldItemModifierType; - if (tier === "Berries") { - newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType; - } else { - newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType; - } - applyModifierTypeToPlayerPokemon(pokemon, newMod); - // Decrement max stacks and remove from pool if at max - newItemType[1]--; - if (newItemType[1] <= 0) { - pool.splice(randIndex, 1); - } - } -} +const roguePool = [ + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + { entry: HeldItemId.SOUL_DEW, weight: 1 }, + { entry: HeldItemId.SCOPE_LENS, weight: 1 }, + { entry: HeldItemId.BATON, weight: 1 }, + { entry: HeldItemId.FOCUS_BAND, weight: 1 }, + { entry: HeldItemId.KINGS_ROCK, weight: 1 }, + { entry: HeldItemId.GRIP_CLAW, weight: 1 }, +]; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 4a84d62c688..433c4d423f2 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -46,9 +46,10 @@ import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; import { timedEventManager } from "#app/global-event-manager"; -import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; import { allHeldItems } from "#app/items/all-held-items"; import { RewardTier } from "#enums/reward-tier"; +import { getHeldItemTier } from "#app/items/held-item-tiers"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -419,7 +420,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up - let tier = globalTradeItemTiers[heldItemId] ?? RewardTier.GREAT; + let tier = getHeldItemTier(heldItemId) ?? RewardTier.GREAT; // Increment tier by 1 if (tier < RewardTier.MASTER) { @@ -975,38 +976,3 @@ function generateRandomTraderName() { const trainerNames = trainerNameString.split(" & "); return trainerNames[randInt(trainerNames.length)]; } - -const globalTradeItemTiers = { - [HeldItemCategoryId.BERRY]: RewardTier.COMMON, - - [HeldItemCategoryId.BASE_STAT_BOOST]: RewardTier.GREAT, - [HeldItemId.WHITE_HERB]: RewardTier.GREAT, - [HeldItemId.METAL_POWDER]: RewardTier.GREAT, - [HeldItemId.QUICK_POWDER]: RewardTier.GREAT, - [HeldItemId.DEEP_SEA_SCALE]: RewardTier.GREAT, - [HeldItemId.DEEP_SEA_TOOTH]: RewardTier.GREAT, - - [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RewardTier.ULTRA, - [HeldItemId.REVIVER_SEED]: RewardTier.ULTRA, - [HeldItemId.LIGHT_BALL]: RewardTier.ULTRA, - [HeldItemId.EVIOLITE]: RewardTier.ULTRA, - [HeldItemId.QUICK_CLAW]: RewardTier.ULTRA, - [HeldItemId.MYSTICAL_ROCK]: RewardTier.ULTRA, - [HeldItemId.WIDE_LENS]: RewardTier.ULTRA, - [HeldItemId.GOLDEN_PUNCH]: RewardTier.ULTRA, - [HeldItemId.TOXIC_ORB]: RewardTier.ULTRA, - [HeldItemId.FLAME_ORB]: RewardTier.ULTRA, - [HeldItemId.LUCKY_EGG]: RewardTier.ULTRA, - - [HeldItemId.FOCUS_BAND]: RewardTier.ROGUE, - [HeldItemId.KINGS_ROCK]: RewardTier.ROGUE, - [HeldItemId.LEFTOVERS]: RewardTier.ROGUE, - [HeldItemId.SHELL_BELL]: RewardTier.ROGUE, - [HeldItemId.GRIP_CLAW]: RewardTier.ROGUE, - [HeldItemId.SOUL_DEW]: RewardTier.ROGUE, - [HeldItemId.BATON]: RewardTier.ROGUE, - [HeldItemId.GOLDEN_EGG]: RewardTier.ULTRA, - - [HeldItemId.MINI_BLACK_HOLE]: RewardTier.MASTER, - [HeldItemId.MULTI_LENS]: RewardTier.MASTER, -}; diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index e371662f7a2..823cc614949 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -123,7 +123,7 @@ export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldIt const ITEM_CATEGORY_MASK = 0xFF00 -function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { +export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { return itemId & ITEM_CATEGORY_MASK; } diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts index 4144d3b7732..099f0f2ee06 100644 --- a/src/items/held-item-data-types.ts +++ b/src/items/held-item-data-types.ts @@ -50,7 +50,7 @@ export function isHeldItemCategoryEntry(entry: any): entry is HeldItemCategoryEn } type HeldItemPoolEntry = { - entry: HeldItemId | HeldItemCategoryEntry | HeldItemSpecs; + entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs; weight: number | HeldItemWeightFunc; }; @@ -65,7 +65,7 @@ export type HeldItemTieredPool = { }; type HeldItemConfigurationEntry = { - entry: HeldItemId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool; + entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool; count?: number | (() => number); }; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 69b06058a9b..7041abbf579 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -2,7 +2,7 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { coerceArray, getEnumValues, randSeedFloat, randSeedInt } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; -import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { HeldItemCategoryId, HeldItemId, isCategoryId } from "#enums/held-item-id"; import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; @@ -263,6 +263,9 @@ function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Po const entry = pool[pickWeightedIndex(weights)].entry; if (typeof entry === "number") { + if (isCategoryId(entry)) { + return getNewHeldItemFromCategory(entry, party ?? pokemon, {}, pokemon) as HeldItemId; + } return entry as HeldItemId; } @@ -273,12 +276,24 @@ function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Po return entry as HeldItemSpecs; } +function assignItemsFromCategory(id: HeldItemCategoryId, pokemon: Pokemon, count: number) { + for (let i = 1; i <= count; i++) { + const newItem = getNewHeldItemFromCategory(id, pokemon, {}, pokemon); + if (newItem) { + pokemon.heldItemManager.add(newItem); + } + } +} + export function assignItemsFromConfiguration(config: HeldItemConfiguration, pokemon: Pokemon) { config.forEach(item => { const { entry, count } = item; const actualCount = typeof count === "function" ? count() : (count ?? 1); if (typeof entry === "number") { + if (isCategoryId(entry)) { + assignItemsFromCategory(entry, pokemon, actualCount); + } pokemon.heldItemManager.add(entry, actualCount); } @@ -287,12 +302,7 @@ export function assignItemsFromConfiguration(config: HeldItemConfiguration, poke } if (isHeldItemCategoryEntry(entry)) { - for (let i = 1; i <= actualCount; i++) { - const newItem = getNewHeldItemFromCategory(entry.id, pokemon, entry?.customWeights, pokemon); - if (newItem) { - pokemon.heldItemManager.add(newItem); - } - } + assignItemsFromCategory(entry.id, pokemon, actualCount); } if (isHeldItemPool(entry)) { diff --git a/src/items/held-item-tiers.ts b/src/items/held-item-tiers.ts new file mode 100644 index 00000000000..fb340998198 --- /dev/null +++ b/src/items/held-item-tiers.ts @@ -0,0 +1,47 @@ +import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { RewardTier } from "#enums/reward-tier"; + +export const heldItemTiers = { + [HeldItemCategoryId.BERRY]: RewardTier.COMMON, + + [HeldItemCategoryId.BASE_STAT_BOOST]: RewardTier.GREAT, + [HeldItemId.WHITE_HERB]: RewardTier.GREAT, + [HeldItemId.METAL_POWDER]: RewardTier.GREAT, + [HeldItemId.QUICK_POWDER]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_SCALE]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_TOOTH]: RewardTier.GREAT, + [HeldItemId.SOOTHE_BELL]: RewardTier.GREAT, + + [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RewardTier.ULTRA, + [HeldItemId.REVIVER_SEED]: RewardTier.ULTRA, + [HeldItemId.LIGHT_BALL]: RewardTier.ULTRA, + [HeldItemId.EVIOLITE]: RewardTier.ULTRA, + [HeldItemId.QUICK_CLAW]: RewardTier.ULTRA, + [HeldItemId.MYSTICAL_ROCK]: RewardTier.ULTRA, + [HeldItemId.WIDE_LENS]: RewardTier.ULTRA, + [HeldItemId.GOLDEN_PUNCH]: RewardTier.ULTRA, + [HeldItemId.TOXIC_ORB]: RewardTier.ULTRA, + [HeldItemId.FLAME_ORB]: RewardTier.ULTRA, + [HeldItemId.LUCKY_EGG]: RewardTier.ULTRA, + + [HeldItemId.FOCUS_BAND]: RewardTier.ROGUE, + [HeldItemId.KINGS_ROCK]: RewardTier.ROGUE, + [HeldItemId.LEFTOVERS]: RewardTier.ROGUE, + [HeldItemId.SHELL_BELL]: RewardTier.ROGUE, + [HeldItemId.GRIP_CLAW]: RewardTier.ROGUE, + [HeldItemId.SOUL_DEW]: RewardTier.ROGUE, + [HeldItemId.BATON]: RewardTier.ROGUE, + [HeldItemId.GOLDEN_EGG]: RewardTier.ULTRA, + + [HeldItemId.MINI_BLACK_HOLE]: RewardTier.MASTER, + [HeldItemId.MULTI_LENS]: RewardTier.MASTER, +}; + +export function getHeldItemTier(item: HeldItemId): RewardTier | undefined { + let tier = heldItemTiers[item]; + if (!tier) { + const category = getHeldItemCategory(item); + tier = heldItemTiers[category]; + } + return tier; +} From 1e9239720defc0d9c9a5e2127eeb9b3efb09a727 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:15:50 +0200 Subject: [PATCH 110/114] Fixing some phases --- src/phases/add-enemy-buff-modifier-phase.ts | 3 +-- src/phases/attempt-capture-phase.ts | 20 +++++++++----------- src/phases/battle-end-phase.ts | 9 +++------ src/phases/encounter-phase.ts | 6 ------ src/phases/move-effect-phase.ts | 2 +- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 6ff96bee16b..193172f7aba 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -23,10 +23,9 @@ export class AddEnemyBuffModifierPhase extends Phase { globalScene.findModifiers(m => m instanceof EnemyPersistentModifier, false), ), true, - true, ); } - globalScene.updateModifiers(false, true); + globalScene.updateModifiers(false); this.end(); } } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 9493973b4a2..95a0317f4a4 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -12,7 +12,6 @@ import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import type { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { achvs } from "#app/system/achv"; import type { PartyOption } from "#app/ui/party-ui-handler"; @@ -255,6 +254,7 @@ export class AttemptCapturePhase extends PokemonPhase { }), null, () => { + const heldItemConfig = pokemon.heldItemManager.generateHeldItemConfiguration(); const end = () => { globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex); globalScene.pokemonInfoContainer.hide(); @@ -269,19 +269,16 @@ export class AttemptCapturePhase extends PokemonPhase { }; const addToParty = (slotIndex?: number) => { const newPokemon = pokemon.addToParty(this.pokeballType, slotIndex); - const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { - globalScene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); + globalScene.updateModifiers(true); + removePokemon(); + if (newPokemon) { + newPokemon.loadAssets().then(end); + } else { + end(); + } }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { if (globalScene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { @@ -306,6 +303,7 @@ export class AttemptCapturePhase extends PokemonPhase { pokemon.variant, pokemon.ivs, pokemon.nature, + heldItemConfig, pokemon, ); globalScene.ui.setMode( diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 8360615c480..9a0f3b4e5f9 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs"; -import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; +import { LapsingPersistentModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; export class BattleEndPhase extends BattlePhase { @@ -81,13 +81,10 @@ export class BattleEndPhase extends BattlePhase { } const lapsingModifiers = globalScene.findModifiers( - m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier, - ) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + m => m instanceof LapsingPersistentModifier, + ) as LapsingPersistentModifier[]; for (const m of lapsingModifiers) { const args: any[] = []; - if (m instanceof LapsingPokemonHeldItemModifier) { - args.push(globalScene.getPokemonById(m.pokemonId)); - } if (!m.lapse(...args)) { globalScene.removeModifier(m); } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index ae04f6ad6fd..9ddddaef76d 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -15,8 +15,6 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier } from "#app/modifier/modifier"; -import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { achvs } from "#app/system/achv"; @@ -271,10 +269,6 @@ export class EncounterPhase extends BattlePhase { if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { // generate modifiers for MEs, overriding prior ones as applicable - regenerateModifierPoolThresholds( - globalScene.getEnemyField(), - battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, - ); globalScene.generateEnemyModifiers(); overrideModifiers(false); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 84224d1e9a1..70996f55a19 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -418,7 +418,7 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } - globalScene.applyModifiers(HitHealModifier, this.player, user); + applyHeldItems(ITEM_EFFECT.HIT_HEAL, { pokemon: user }); this.getTargets().forEach(target => { target.turnData.moveEffectiveness = null; }); From f26cb11e0b7dda9333c72d09e0ca435f8df4499b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:29:46 +0200 Subject: [PATCH 111/114] Fixed several bugs related to accessing and visualizing held items --- src/battle-scene.ts | 5 ++++- src/data/trainers/trainer-config.ts | 12 +++++++++++- src/field/pokemon.ts | 4 ++-- src/items/held-item-pool.ts | 7 +++++-- src/items/held-items/base-stat-total.ts | 2 +- src/items/held-items/berry.ts | 8 ++++---- src/items/held-items/instant-revive.ts | 2 +- src/items/held-items/reset-negative-stat-stage.ts | 2 +- src/loading-scene.ts | 2 ++ src/modifier/modifier-type.ts | 14 ++++++++++---- src/phases/select-modifier-phase.ts | 3 +-- src/ui/modifier-select-ui-handler.ts | 1 + src/ui/summary-ui-handler.ts | 3 +-- 13 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index aff124aef73..f196fc930a9 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2906,7 +2906,10 @@ export default class BattleScene extends SceneBase { } this.updateParty(player ? this.getPlayerParty() : this.getEnemyParty(), true); - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); + + const pokemonA = player ? this.getPlayerParty()[0] : this.getEnemyParty()[0]; + + (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers, pokemonA); if (!player) { this.updateUIPositions(); } diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 6786aa00ef7..5b892eb9015 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1004,6 +1004,7 @@ export function getRandomPartyMemberFunc( undefined, false, undefined, + undefined, postProcess, ); }; @@ -1028,7 +1029,16 @@ function getSpeciesFilterRandomPartyMemberFunc( .getTrainerSpeciesForLevel(level, true, strength, waveIndex), ); - return globalScene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess); + return globalScene.addEnemyPokemon( + species, + level, + trainerSlot, + undefined, + false, + undefined, + undefined, + postProcess, + ); }; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2861a09d80a..19ad009e884 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -339,6 +339,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; + this.heldItemManager = new PokemonItemManager(); + if (dataSource) { this.id = dataSource.id; this.hp = dataSource.hp; @@ -444,8 +446,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!dataSource) { this.calculateStats(); } - - this.heldItemManager = new PokemonItemManager(); } /** diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 7041abbf579..56403891d21 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -2,7 +2,7 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { coerceArray, getEnumValues, randSeedFloat, randSeedInt } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; -import { HeldItemCategoryId, HeldItemId, isCategoryId } from "#enums/held-item-id"; +import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id"; import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; @@ -246,8 +246,11 @@ function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] { return pool.map(p => { let weight = typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight; - if (typeof p.entry === "number") { + if (typeof p.entry === "number" && !isCategoryId(p.entry)) { const itemId = p.entry as HeldItemId; + console.log("ITEM ID: ", itemId, HeldItemNames[itemId]); + console.log(allHeldItems[itemId]); + if (pokemon.heldItemManager.getStack(itemId) >= allHeldItems[itemId].getMaxStackCount()) { weight = 0; } diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index f5c57c6a685..4524441e64f 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -38,7 +38,7 @@ export class BaseStatTotalHeldItem extends HeldItem { }); } - get icon(): string { + get iconName(): string { return "berry_juice"; } diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index e3f6dfd0962..d9d1f17ddfc 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -1,4 +1,4 @@ -import { getBerryEffectDescription, getBerryEffectFunc, getBerryName } from "#app/data/berry"; +import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#app/data/berry"; import { BerryUsedEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; @@ -60,9 +60,9 @@ export class BerryHeldItem extends ConsumableHeldItem { * @param pokemon The {@linkcode Pokemon} that holds the berry * @returns `true` if {@linkcode BerryModifier} should be applied */ - // override shouldApply(pokemon: Pokemon): boolean { - // return !this.consumed && super.shouldApply(pokemon) && getBerryPredicate(this.berryType)(pokemon); - // } + shouldApply(pokemon: Pokemon): boolean { + return getBerryPredicate(this.berryType)(pokemon); + } /** * Applies {@linkcode BerryHeldItem} diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 62e0874f995..4290e2ede34 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -29,7 +29,7 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { return i18next.t("modifierType:ModifierType.REVIVER_SEED.description"); } - get icon(): string { + get iconName(): string { return "reviver_seed"; } /** diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index c07deee8420..5c28c235d44 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -29,7 +29,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { return i18next.t("modifierType:ModifierType.WHITE_HERB.description"); } - get icon(): string { + get iconName(): string { return "white_herb"; } /** diff --git a/src/loading-scene.ts b/src/loading-scene.ts index bd8ccf852a6..f6bb11314d1 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -24,6 +24,7 @@ import { timedEventManager } from "./global-event-manager"; import { initHeldItems } from "./items/all-held-items"; import { initModifierPools } from "./modifier/init-modifier-pools"; import { initModifierTypes } from "./modifier/modifier-type"; +import { initHeldItemPools } from "./items/init-held-item-pools"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -368,6 +369,7 @@ export class LoadingScene extends SceneBase { initModifierTypes(); initModifierPools(); + initHeldItemPools(); initAchievements(); initVouchers(); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 0c5afe6d98e..bb926ba789a 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -354,12 +354,12 @@ export class HeldItemReward extends PokemonModifierType { return allHeldItems[this.itemId].name; } - get description(): string { - return allHeldItems[this.itemId].name; + getDescription(): string { + return allHeldItems[this.itemId].description; } - get icon(): string { - return allHeldItems[this.itemId].name; + getIcon(): string { + return allHeldItems[this.itemId].iconName; } apply(pokemon: Pokemon) { @@ -2070,6 +2070,11 @@ function getModifierTypeOptionWithRetry( ++r < retryCount && existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length ) { + console.log("Retry count:", r); + console.log(candidate?.type.group); + console.log(candidate?.type.name); + console.log(existingOptions.filter(o => o.type.name === candidate?.type.name).length); + console.log(existingOptions.filter(o => o.type.group === candidate?.type.group).length); candidate = getNewModifierTypeOption( party, ModifierPoolType.PLAYER, @@ -2255,6 +2260,7 @@ function determineTier( retryCount = 0, allowLuckUpgrades = true, ): RewardTier { + const pool = getModifierPoolForType(ModifierPoolType.PLAYER); if (tier === undefined) { const tierValue = randSeedInt(1024); if (!upgradeCount) { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 93040b15584..e6d159e5da9 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -170,8 +170,7 @@ export class SelectModifierPhase extends BattlePhase { if (modifierType instanceof PokemonModifierType) { if (modifierType instanceof HeldItemReward) { this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); - } - if (modifierType instanceof FusePokemonModifierType) { + } else if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { this.openModifierMenu(modifierType, cost, modifierSelectCallback); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 13f98fe42e5..24ddbdaef05 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -779,6 +779,7 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.itemContainer); const getItem = () => { + console.log("SHOWING ICON", this.modifierTypeOption.type?.name, this.modifierTypeOption.type?.getIcon()); const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.getIcon()); return item; }; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 2846bff8ee2..17ccfdc5118 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -1035,9 +1035,8 @@ export default class SummaryUiHandler extends UiHandler { const heldItems = this.pokemon?.getHeldItems().sort(heldItemSortFunc); heldItems?.forEach((itemKey, i) => { - const stack = this.pokemon?.heldItemManager.getStack(itemKey); const heldItem = allHeldItems[itemKey]; - const icon = heldItem.createSummaryIcon(stack); + const icon = heldItem.createSummaryIcon(this.pokemon); console.log(icon); icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); From 3ea89de9c15b5303d04cc7a8eb76b5c9e97a8d2b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:33:56 +0200 Subject: [PATCH 112/114] Fixed position of enemy modifier bar --- src/battle-scene.ts | 13 ++++++++++--- src/modifier/modifier-bar.ts | 18 +++++++++++------- src/ui/modifier-select-ui-handler.ts | 4 ++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f196fc930a9..0f4079c3e79 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2058,7 +2058,7 @@ export default class BattleScene extends SceneBase { } updateUIPositions(): void { - const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length; + const enemyModifierCount = this.enemyModifierBar.totalVisibleLength; const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( -(this.game.canvas.height / 6) + @@ -2888,7 +2888,7 @@ export default class BattleScene extends SceneBase { } // TODO: Document this - updateModifiers(player = true): void { + updateModifiers(player = true, showHeldItems = true): void { const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); for (const modifier of modifiers) { @@ -2909,7 +2909,14 @@ export default class BattleScene extends SceneBase { const pokemonA = player ? this.getPlayerParty()[0] : this.getEnemyParty()[0]; - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers, pokemonA); + const bar = player ? this.modifierBar : this.enemyModifierBar; + + if (showHeldItems) { + bar.updateModifiers(modifiers, pokemonA); + } else { + bar.updateModifiers(modifiers); + } + if (!player) { this.updateUIPositions(); } diff --git a/src/modifier/modifier-bar.ts b/src/modifier/modifier-bar.ts index 1581f26d63e..7a104329f2d 100644 --- a/src/modifier/modifier-bar.ts +++ b/src/modifier/modifier-bar.ts @@ -48,7 +48,7 @@ export const formChangeItemSortFunc = (a: FormChangeItem, b: FormChangeItem): nu export class ModifierBar extends Phaser.GameObjects.Container { private player: boolean; private modifierCache: (PersistentModifier | HeldItemId)[]; - private totalVisibleLength = 0; + public totalVisibleLength = 0; constructor(enemy?: boolean) { super(globalScene, 1 + (enemy ? 302 : 0), 2); @@ -72,19 +72,23 @@ export class ModifierBar extends Phaser.GameObjects.Container { this.totalVisibleLength = sortedVisibleModifiers.length + heldItemsA.length + heldItemsB.length; - sortedVisibleModifiers.forEach((modifier: PersistentModifier, i: number) => { + let iconCount = 0; + sortedVisibleModifiers.forEach(modifier => { const icon = modifier.getIcon(); - this.addIcon(icon, i, modifier.type.name, modifier.type.getDescription()); + iconCount += 1; + this.addIcon(icon, iconCount, modifier.type.name, modifier.type.getDescription()); }); - heldItemsA.forEach((item: HeldItemId, i: number) => { + heldItemsA.forEach(item => { const icon = allHeldItems[item].createPokemonIcon(pokemonA); - this.addIcon(icon, i, allHeldItems[item].name, allHeldItems[item].description); + iconCount += 1; + this.addIcon(icon, iconCount, allHeldItems[item].name, allHeldItems[item].description); }); - heldItemsB.forEach((item: HeldItemId, i: number) => { + heldItemsB.forEach(item => { const icon = allHeldItems[item].createPokemonIcon(pokemonB); - this.addIcon(icon, i, allHeldItems[item].name, allHeldItems[item].description); + iconCount += 1; + this.addIcon(icon, iconCount, allHeldItems[item].name, allHeldItems[item].description); }); for (const icon of this.getAll()) { diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 24ddbdaef05..32a5fca713e 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -269,7 +269,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const maxUpgradeCount = 0; /* Force updateModifiers without pokemon held items */ - globalScene.getModifierBar().updateModifiers(globalScene.modifiers); + globalScene.updateModifiers(true, false); /* Multiplies the appearance duration by the speed parameter so that it is always constant, and avoids "flashbangs" at game speed x5 */ globalScene.showShopOverlay(750 * globalScene.gameSpeed); @@ -692,7 +692,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { globalScene.hideLuckText(250); /* Normally already called just after the shop, but not sure if it happens in 100% of cases */ - globalScene.getModifierBar().updateModifiers(globalScene.modifiers); + globalScene.updateModifiers(true); const options = this.options.concat(this.shopOptionsRows.flat()); this.options.splice(0, this.options.length); From 90bc14c6673a18729a3fc209ad0ece5bb881eba8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:51:21 +0200 Subject: [PATCH 113/114] Fixed berries, removed some debug messages --- src/items/held-items/berry.ts | 4 ++++ src/phases/berry-phase.ts | 3 +-- src/phases/select-modifier-phase.ts | 1 - src/ui/modifier-select-ui-handler.ts | 18 ------------------ 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index d9d1f17ddfc..2d6bca55f03 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -72,6 +72,10 @@ export class BerryHeldItem extends ConsumableHeldItem { apply(params: BERRY_PARAMS): boolean { const pokemon = params.pokemon; + if (!this.shouldApply(pokemon)) { + return false; + } + const preserve = new BooleanHolder(false); globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); const consumed = !preserve.value; diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index a4d63cf769e..0441f39bec0 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -33,8 +33,7 @@ export class BerryPhase extends FieldPhase { */ eatBerries(pokemon: Pokemon): void { const hasUsableBerry = pokemon.getHeldItems().some(m => { - //TODO: This is bugged, must fix the .shouldApply() function - isItemInCategory(m, HeldItemCategoryId.BERRY) && allHeldItems[m].shouldApply(pokemon); + return isItemInCategory(m, HeldItemCategoryId.BERRY) && allHeldItems[m].shouldApply(pokemon); }); if (!hasUsableBerry) { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index e6aa1d4d1fb..039ea31c45f 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -475,7 +475,6 @@ export class SelectModifierPhase extends BattlePhase { } getModifierTypeOptions(modifierCount: number): ModifierTypeOption[] { - console.log("HERE WE ARE", modifierCount, this.customModifierSettings); return getPlayerModifierTypeOptions( modifierCount, globalScene.getPlayerParty(), diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 32a5fca713e..9db15e8fb84 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -219,7 +219,6 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { : []; const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; - console.log("ui_shop_options", shopTypeOptions); for (let m = 0; m < typeOptions.length; m++) { const sliceWidth = globalScene.game.canvas.width / 6 / (typeOptions.length + 2); @@ -233,7 +232,6 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.modifierContainer.add(option); this.options.push(option); } - console.log("ui_options", this.options); // Set "Continue" button height based on number of rows in healing items shop const continueButton = this.continueButtonContainer.getAt(0); @@ -296,20 +294,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { }, }); - console.log("maxUpgradeCount", maxUpgradeCount); - globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => { - console.log("delayed1", partyHasHeldItem); for (const shopOption of this.shopOptionsRows.flat()) { - console.log("uishop", shopOption); shopOption.show(0, 0); } }); globalScene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => { - console.log("delayed2", partyHasHeldItem); if (partyHasHeldItem) { - console.log("uihelditem", partyHasHeldItem); this.transferButtonContainer.setAlpha(0); this.transferButtonContainer.setVisible(true); globalScene.tweens.add({ @@ -364,8 +356,6 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { }); }); - console.log("ui_end"); - return true; } @@ -779,7 +769,6 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.itemContainer); const getItem = () => { - console.log("SHOWING ICON", this.modifierTypeOption.type?.name, this.modifierTypeOption.type?.getIcon()); const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.getIcon()); return item; }; @@ -815,7 +804,6 @@ class ModifierOption extends Phaser.GameObjects.Container { } show(remainingDuration: number, upgradeCountOffset: number) { - console.log("mo1"); if (!this.modifierTypeOption.cost) { globalScene.tweens.add({ targets: this.pb, @@ -823,7 +811,6 @@ class ModifierOption extends Phaser.GameObjects.Container { duration: 1250, ease: "Bounce.Out", }); - console.log("mo2"); let lastValue = 1; let bounceCount = 0; @@ -850,7 +837,6 @@ class ModifierOption extends Phaser.GameObjects.Container { lastValue = value; }, }); - console.log("mo3"); for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) { const upgradeIndex = u; @@ -888,14 +874,12 @@ class ModifierOption extends Phaser.GameObjects.Container { }, ); } - console.log("mo4"); } globalScene.time.delayedCall(remainingDuration + 2000, () => { if (!globalScene) { return; } - console.log("mo5"); if (!this.modifierTypeOption.cost) { this.pb.setTexture("pb", `${this.getPbAtlasKey(0)}_open`); @@ -910,7 +894,6 @@ class ModifierOption extends Phaser.GameObjects.Container { onComplete: () => this.pb.destroy(), }); } - console.log("mo6"); globalScene.tweens.add({ targets: this.itemContainer, @@ -945,7 +928,6 @@ class ModifierOption extends Phaser.GameObjects.Container { }); } }); - console.log("mo_end"); } getPbAtlasKey(tierOffset = 0) { From c992508dc35d32a716af15ac7dc54e551878a863 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:52:09 +0200 Subject: [PATCH 114/114] Vitamins max stack temporarily set to 30 --- src/items/all-held-items.ts | 2 +- src/items/held-items/base-stat-booster.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 5cbc2a8e344..203e5e3ca42 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -143,7 +143,7 @@ export function initHeldItems() { // vitamins for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { const stat = Number(statKey) as PermanentStat; - allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 10, stat) + allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 30, stat) .unstealable() .untransferable() .unsuppressable(); diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index e024e74fd48..f71a1b6ef5b 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -3,7 +3,6 @@ import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; import { HeldItem, ITEM_EFFECT } from "../held-item"; -import type { STAT_BOOST_PARAMS } from "./stat-booster"; export interface BASE_STAT_BOOSTER_PARAMS { /** The pokemon with the item */ @@ -79,10 +78,4 @@ export class BaseStatBoosterHeldItem extends HeldItem { baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1)); return true; } - - getMaxStackCount(params: STAT_BOOST_PARAMS): number { - const pokemon = params.pokemon; - const stackCount = pokemon.heldItemManager.getStack(this.type); - return stackCount; - } }