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] 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); } }