Added Stat Boost items; generic name and description in HeldItem class

This commit is contained in:
Wlowscha 2025-06-07 12:53:47 +02:00
parent d39744041e
commit 955592bdf6
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
5 changed files with 222 additions and 210 deletions

View File

@ -2,7 +2,8 @@ import { getEnumValues } from "#app/utils/common";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { HeldItemId } from "#enums/held-item-id"; import { HeldItemId } from "#enums/held-item-id";
import type { PokemonType } from "#enums/pokemon-type"; 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 { ITEM_EFFECT } from "./held-item";
import { import {
type ATTACK_TYPE_BOOST_PARAMS, type ATTACK_TYPE_BOOST_PARAMS,
@ -22,6 +23,11 @@ import {
ResetNegativeStatStageHeldItem, ResetNegativeStatStageHeldItem,
type RESET_NEGATIVE_STAT_STAGE_PARAMS, type RESET_NEGATIVE_STAT_STAGE_PARAMS,
} from "./held-items/reset-negative-stat-stage"; } 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 type { TURN_END_HEAL_PARAMS } from "./held-items/turn-end-heal";
import { TurnEndHealHeldItem } 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[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.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40);
allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); 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.BERRY]: BERRY_PARAMS;
[ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS;
[ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; [ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS;
[ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS;
}; };
export function applyHeldItems<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { export function applyHeldItems<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) {

View File

@ -1,7 +1,8 @@
import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability"; import { applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/abilities/ability";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene"; 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 = { export const ITEM_EFFECT = {
ATTACK_TYPE_BOOST: 1, ATTACK_TYPE_BOOST: 1,
@ -13,6 +14,7 @@ export const ITEM_EFFECT = {
BERRY: 6, BERRY: 6,
BASE_STAT_BOOSTER: 7, BASE_STAT_BOOSTER: 7,
INSTANT_REVIVE: 8, INSTANT_REVIVE: 8,
STAT_BOOST: 9,
} as const; } as const;
export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT];
@ -34,6 +36,18 @@ export class HeldItem {
this.isSuppressable = true; 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 { get name(): string {
return ""; return "";
} }

View File

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

View File

@ -14,18 +14,6 @@ export interface TURN_END_HEAL_PARAMS {
export class TurnEndHealHeldItem extends HeldItem { export class TurnEndHealHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_HEAL]; 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 { apply(params: TURN_END_HEAL_PARAMS): boolean {
const pokemon = params.pokemon; const pokemon = params.pokemon;
const stackCount = pokemon.heldItemManager.getStack(this.type); const stackCount = pokemon.heldItemManager.getStack(this.type);

View File

@ -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). * Modifier used for held items that apply critical-hit stage boost(s).
* @extends PokemonHeldItemModifier * @extends PokemonHeldItemModifier