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