Add evolution trackers for Stantler and Primeape's use X times evo

This commit is contained in:
AJ Fontaine 2025-06-16 16:26:25 -04:00
parent 6ff258fb37
commit c24a084eb3
6 changed files with 122 additions and 10 deletions

View File

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

View File

@ -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,

View File

@ -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",

View File

@ -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,

View File

@ -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) {

View File

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