diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index e97a51fed29..6ae4cf96a18 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,6 +1,5 @@ import { globalScene } from "#app/global-scene"; import { Gender, getGenderSymbol } from "#app/data/gender"; -import { PokeballType } from "#enums/pokeball"; import type Pokemon from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import { coerceArray, isNullOrUndefined, randSeedInt } from "#app/utils/common"; @@ -100,6 +99,7 @@ const EvoCondKey = { GENDER: 13, NATURE: 14, HELD_ITEM: 15, // Currently checks only for species stat booster items + USE_MOVE_COUNT: 16, } as const; type EvolutionConditionData = @@ -114,6 +114,7 @@ type EvolutionConditionData = {key: typeof EvoCondKey.NATURE, nature: Nature[]} | {key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} | {key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} | + {key: typeof EvoCondKey.USE_MOVE_COUNT, move: MoveId, value: number} | {key: typeof EvoCondKey.SHEDINJA}; export class SpeciesEvolutionCondition { @@ -157,6 +158,8 @@ export class SpeciesEvolutionCondition { return i18next.t("pokemonEvolutions:caught", {species: getPokemonSpecies(cond.speciesCaught).name}); case EvoCondKey.HELD_ITEM: return i18next.t(`pokemonEvolutions:heldItem.${cond.itemKey}`); + case EvoCondKey.USE_MOVE_COUNT: + return i18next.t("pokemonEvolutions:moveUseCount", {move: allMoves[cond.move].name, count: cond.value}); } }).filter(s => !isNullOrUndefined(s)); // Filter out stringless conditions return this.desc; @@ -201,16 +204,14 @@ 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.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey); + case EvoCondKey.USE_MOVE_COUNT: + return pokemon.getHeldItems().some(m => m.is("MoveTrackerModifier") && m.getStackCount() >= cond.value); } }); } } -export function validateShedinjaEvo(): boolean { - return globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0; -} - export class SpeciesFormEvolution { public speciesId: SpeciesId; public preFormKey: string | null; @@ -1545,7 +1546,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.MAMOSWINE, 1, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.STANTLER]: [ - new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, {key: EvoCondKey.MOVE, move: MoveId.PSYSHIELD_BASH}, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, {key: EvoCondKey.USE_MOVE_COUNT, move: MoveId.PSYSHIELD_BASH, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.LOMBRE]: [ new SpeciesEvolution(SpeciesId.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1791,7 +1792,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.PRIMEAPE]: [ - new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, {key: EvoCondKey.MOVE, move: MoveId.RAGE_FIST}, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, {key: EvoCondKey.USE_MOVE_COUNT, move: MoveId.RAGE_FIST, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.GOLBAT]: [ new SpeciesEvolution(SpeciesId.CROBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.VERY_LONG) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e9cc4f70d70..0affbf52c3c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -80,8 +80,8 @@ import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution, - validateShedinjaEvo, } from "#app/data/balance/pokemon-evolutions"; +import { validateShedinjaEvo } from "#app/utils/evolution-utils"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a22486210b0..97ea28907c8 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -96,6 +96,7 @@ import { TempExtraModifierModifier, CriticalCatchChanceBoosterModifier, FieldEffectModifier, + MoveTrackerModifier, } from "#app/modifier/modifier"; import { ModifierTier } from "#enums/modifier-tier"; import Overrides from "#app/overrides"; @@ -1878,6 +1879,21 @@ const modifierTypeInitObj = Object.freeze({ new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10, (args[1] as number) ?? 1), ), + EVOLUTION_TRACKER_PRIMEAPE: () => + new PokemonHeldItemModifierType( + "modifierType:ModifierType.EVOLUTION_TRACKER_PRIMEAPE", + "tm_ghost", + (type, args) => new MoveTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.PRIMEAPE, MoveId.RAGE_FIST, 10), + ), + + EVOLUTION_TRACKER_STANTLER: () => + new PokemonHeldItemModifierType( + "modifierType:ModifierType.EVOLUTION_TRACKER_STANTLER", + "tm_psychic", + (type, args) => + new MoveTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.STANTLER, MoveId.PSYSHIELD_BASH, 10), + ), + MEGA_BRACELET: () => new ModifierType( "modifierType:ModifierType.MEGA_BRACELET", diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 54b7323569a..449af08e2ab 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -946,6 +946,71 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { } } +export class MoveTrackerModifier extends PokemonHeldItemModifier { + protected species: SpeciesId; + protected required: number; + protected move: MoveId; + public isTransferable = false; + + constructor( + type: ModifierType, + pokemonId: number, + species: SpeciesId, + move: MoveId, + required: number, + stackCount?: number, + ) { + super(type, pokemonId, stackCount); + this.species = species; + this.move = move; + this.required = required; + } + + matchType(modifier: Modifier): boolean { + return ( + modifier instanceof MoveTrackerModifier && + modifier.species === this.species && + modifier.required === this.required && + modifier.move === this.move + ); + } + + clone(): PersistentModifier { + return new MoveTrackerModifier(this.type, this.pokemonId, this.species, this.move, this.required, this.stackCount); + } + + getArgs(): any[] { + return super.getArgs().concat([this.species, this.move, this.required]); + } + + /** + * Applies the {@linkcode MoveTrackerModifier} + * @returns always `true` + */ + override apply(): boolean { + return true; + } + + getMaxHeldItemCount(_pokemon: Pokemon): number { + return 999; + } + + override getSpecies(): SpeciesId { + return this.species; + } + + getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { + const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (this.stackCount >= this.required) { + text.setTint(0xf89890); + } + text.setOrigin(0, 0); + + return text; + } +} + /** * Currently used by Shuckle Juice item */ @@ -2913,7 +2978,7 @@ export class MoneyRewardModifier extends ConsumableModifier { globalScene.addMoney(moneyAmount.value); globalScene.getPlayerParty().map(p => { - if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { + if (p.hasSpecies(SpeciesId.GIMMIGHOUL)) { const factor = Math.min(Math.floor(this.moneyMultiplier), 3); const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( p, @@ -3867,6 +3932,7 @@ const ModifierClassMap = Object.freeze({ LapsingPokemonHeldItemModifier, BaseStatModifier, EvoTrackerModifier, + MoveTrackerModifier, PokemonBaseStatTotalModifier, PokemonBaseStatFlatModifier, PokemonIncrementingStatModifier, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index d7da1ab996c..7251c8c9269 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -55,6 +55,7 @@ import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; import { DamageAchv } from "#app/system/achv"; import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode"; +import { handleMoveUseTracker } from "#app/utils/evolution-utils"; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -805,6 +806,7 @@ export class MoveEffectPhase extends PokemonPhase { } if (this.lastHit) { globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + handleMoveUseTracker(user, this.move.id); // Multi-hit check for Wimp Out/Emergency Exit if (user.turnData.hitCount > 1) { diff --git a/src/utils/evolution-utils.ts b/src/utils/evolution-utils.ts new file mode 100644 index 00000000000..e3a41c9ca51 --- /dev/null +++ b/src/utils/evolution-utils.ts @@ -0,0 +1,27 @@ +import { modifierTypes } from "#app/data/data-lists"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type { MoveTrackerModifier } from "#app/modifier/modifier"; +import { MoveId } from "#enums/move-id"; +import { PokeballType } from "#enums/pokeball"; +import { SpeciesId } from "#enums/species-id"; +import { getModifierType } from "./modifier-utils"; + +export function validateShedinjaEvo(): boolean { + return globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0; +} + +/** + * Increments or creates evolution trackers for mons with USE_MOVE_COUNT evolution conditions + * @param pokemon {@linkcode Pokemon} that successfully used a move + * @param moveId {@linkcode MoveId} the move used + */ +export function handleMoveUseTracker(pokemon: Pokemon, moveId: MoveId) { + if (pokemon.hasSpecies(SpeciesId.PRIMEAPE) && moveId === MoveId.RAGE_FIST) { + const mod = getModifierType(modifierTypes.EVOLUTION_TRACKER_PRIMEAPE).newModifier(pokemon) as MoveTrackerModifier; + globalScene.addModifier(mod); + } else if (pokemon.hasSpecies(SpeciesId.STANTLER) && moveId === MoveId.PSYSHIELD_BASH) { + const mod = getModifierType(modifierTypes.EVOLUTION_TRACKER_STANTLER).newModifier(pokemon) as MoveTrackerModifier; + globalScene.addModifier(mod); + } +}