[Dev] Improve typescript performance and version bump node and dependencies (#6627)

* Add caching for test matchers types to improve ts performance

* add skipLibCheck to tsconfig.json

* Bump package versions

* Move tm species map to its own file

* Turn on ts-nocheck in pokemon-level-moves

* Move initBiomes to own file

* Add types to methods in ME encounter phase utils

* Fix spacing

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Sirz Benjie 2025-10-04 19:29:23 -05:00 committed by GitHub
parent a13ea90e46
commit f4456f6c7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 75906 additions and 75734 deletions

2
.nvmrc
View File

@ -1 +1 @@
v22.14.0
v24.9.0

View File

@ -38,7 +38,7 @@
// TODO: enable linting this file
"!**/src/data/moves/move.ts",
// this file is too big
"!**/src/data/balance/tms.ts"
"!**/src/data/balance/tm-species-map.ts"
]
},
"assist": {

View File

@ -37,18 +37,18 @@
"update-submodules:remote": "pnpm update-locales:remote && pnpm update-assets:remote"
},
"devDependencies": {
"@biomejs/biome": "2.2.4",
"@biomejs/biome": "2.2.5",
"@ls-lint/ls-lint": "2.3.1",
"@types/crypto-js": "^4.2.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.16.5",
"@types/jsdom": "^27.0.0",
"@types/node": "^24",
"@vitest/coverage-istanbul": "^3.2.4",
"@vitest/expect": "^3.2.4",
"@vitest/utils": "^3.2.4",
"chalk": "^5.4.1",
"dependency-cruiser": "^16.10.4",
"dependency-cruiser": "^17.0.2",
"inquirer": "^12.8.2",
"jsdom": "^26.1.0",
"jsdom": "^27.0.0",
"lefthook": "^1.12.2",
"msw": "^2.10.4",
"phaser3spectorjs": "^0.0.8",
@ -63,10 +63,10 @@
"vitest-canvas-mock": "^0.3.3"
},
"dependencies": {
"@material/material-color-utilities": "^0.2.7",
"@material/material-color-utilities": "^0.3.0",
"compare-versions": "^6.1.1",
"crypto-js": "^4.2.0",
"i18next": "^24.2.3",
"i18next": "^25.5.3",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"i18next-korean-postposition-processor": "^1.0.0",
@ -76,7 +76,7 @@
"phaser3-rex-plugins": "^1.80.16"
},
"engines": {
"node": ">=22.0.0"
"node": ">=24.9.0"
},
"packageManager": "pnpm@10.17.0"
"packageManager": "pnpm@10.18.0"
}

File diff suppressed because it is too large Load Diff

44
src/@types/biomes.ts Normal file
View File

@ -0,0 +1,44 @@
import type { BiomeId } from "#enums/biome-id";
import type { BiomePoolTier } from "#enums/biome-pool-tier";
import type { SpeciesId } from "#enums/species-id";
import type { TimeOfDay } from "#enums/time-of-day";
import type { TrainerType } from "#enums/trainer-type";
export interface BiomeLinks {
[key: number]: BiomeId | (BiomeId | [BiomeId, number])[];
}
export interface BiomeDepths {
[key: number]: [number, number];
}
interface SpeciesTree {
[key: number]: SpeciesId[];
}
export interface PokemonPools {
[key: number]: (SpeciesId | SpeciesTree)[];
}
interface BiomeTierPokemonPools {
[key: number]: PokemonPools;
}
export interface BiomePokemonPools {
[key: number]: BiomeTierPokemonPools;
}
export interface BiomeTierTod {
biome: BiomeId;
tier: BiomePoolTier;
tod: TimeOfDay[];
}
export interface CatchableSpecies {
[key: number]: BiomeTierTod[];
}
export interface BiomeTierTrainerPools {
[key: number]: TrainerType[];
}
export interface BiomeTrainerPools {
[key: number]: BiomeTierTrainerPools;
}

View File

@ -1 +1,4 @@
export type ConditionFn = (args?: any[]) => boolean;
/** A union type of all primitives (types that are always passed by value) */
export type Primitive = string | number | boolean | bigint | null | undefined | symbol;

View File

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2025 Pagefault Games
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { MoveId } from "#enums/move-id";
export type LevelMoves = [number, MoveId][];
export interface PokemonSpeciesLevelMoves {
[key: number]: LevelMoves;
}
interface PokemonFormLevelMoves {
[key: number]: LevelMoves;
}
export interface PokemonSpeciesFormLevelMoves {
[key: number]: PokemonFormLevelMoves;
}

View File

@ -1,3 +1,4 @@
import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/constants";
import { globalScene } from "#app/global-scene";
import { speciesEggMoves } from "#balance/egg-moves";
import {
@ -19,7 +20,6 @@ import {
ULTRA_TIER_TM_LEVEL_REQUIREMENT,
ULTRA_TM_MOVESET_WEIGHT,
} from "#balance/moveset-generation";
import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves";
import { speciesTmMoves, tmPoolTiers } from "#balance/tms";
import { allMoves } from "#data/data-lists";
import { ModifierTier } from "#enums/modifier-tier";

View File

@ -24,11 +24,10 @@ import { SceneBase } from "#app/scene-base";
import { startingWave } from "#app/starting-wave";
import { TimedEventManager } from "#app/timed-event-manager";
import { UiInputs } from "#app/ui-inputs";
import { biomeDepths, getBiomeName } from "#balance/biomes";
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#balance/starters";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets } from "#data/battle-anims";
import { allAbilities, allMoves, allSpecies, modifierTypes } from "#data/data-lists";
import { allAbilities, allMoves, allSpecies, biomeDepths, modifierTypes } from "#data/data-lists";
import { battleSpecDialogue } from "#data/dialogue";
import type { SpeciesFormChangeTrigger } from "#data/form-change-triggers";
import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger } from "#data/form-change-triggers";
@ -135,6 +134,7 @@ import {
type Constructor,
fixedInt,
formatMoney,
getBiomeName,
getIvsFromId,
isBetween,
NumberHolder,

View File

@ -112,3 +112,9 @@ export const RARE_CANDY_FRIENDSHIP_CAP = 200;
* The maximum number of times a player can Terastallize in a single arena run
*/
export const MAX_TERAS_PER_ARENA = 1;
/** Moves that can only be learned with a memory-mushroom */
export const RELEARN_MOVE = -1;
/** Moves that can only be learned with an evolve */
export const EVOLVE_MOVE = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,15 @@
// @ts-nocheck
/**
* @internal
* @module
*/
import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/constants";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import type { PokemonSpeciesFormLevelMoves, PokemonSpeciesLevelMoves } from "#types/pokemon-level-moves";
export type LevelMoves = ([number, MoveId])[];
interface PokemonSpeciesLevelMoves {
[key: number]: LevelMoves
}
interface PokemonFormLevelMoves {
[key: number]: LevelMoves
}
interface PokemonSpeciesFormLevelMoves {
[key: number]: PokemonFormLevelMoves
}
/** Moves that can only be learned with a memory-mushroom */
export const RELEARN_MOVE = -1;
/** Moves that can only be learned with an evolve */
export const EVOLVE_MOVE = 0;
export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
export const pokemonSpeciesLevelMoves = {
[SpeciesId.BULBASAUR]: [
[ 1, MoveId.TACKLE ],
[ 1, MoveId.GROWL ],
@ -18932,9 +18921,9 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
[ 64, MoveId.HAMMER_ARM ],
[ 70, MoveId.BLOOD_MOON ],
]
};
} as PokemonSpeciesLevelMoves;
export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
export const pokemonFormLevelMoves = {
[SpeciesId.PIKACHU]: { // Custom
1: [
[ 1, MoveId.TAIL_WHIP ],
@ -20018,4 +20007,4 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 60, MoveId.CLOSE_COMBAT ],
]
}
};
} as PokemonSpeciesFormLevelMoves;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
import type { Ability } from "#abilities/ability";
import type { PokemonSpecies } from "#data/pokemon-species";
import type { SpeciesId } from "#enums/species-id";
import type { ModifierTypes } from "#modifiers/modifier-type";
import type { Move } from "#moves/move";
import type { BiomeDepths, CatchableSpecies } from "#types/biomes";
export const allAbilities: Ability[] = [];
export const allMoves: Move[] = [];
@ -9,3 +11,7 @@ export const allSpecies: PokemonSpecies[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;
export const catchableSpecies: CatchableSpecies = {};
export const biomeDepths: BiomeDepths = {};
export const uncatchableSpecies: SpeciesId[] = [];

View File

@ -1,4 +1,3 @@
import { getBiomeName } from "#balance/biomes";
import { BiomeId } from "#enums/biome-id";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { ATrainersTestEncounter } from "#mystery-encounters/a-trainers-test-encounter";
@ -33,6 +32,7 @@ import { TrainingSessionEncounter } from "#mystery-encounters/training-session-e
import { TrashToTreasureEncounter } from "#mystery-encounters/trash-to-treasure-encounter";
import { UncommonBreedEncounter } from "#mystery-encounters/uncommon-breed-encounter";
import { WeirdDreamEncounter } from "#mystery-encounters/weird-dream-encounter";
import { getBiomeName } from "#utils/common";
export const EXTREME_ENCOUNTER_BIOMES = [
BiomeId.SEA,

View File

@ -3,7 +3,7 @@ import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } fro
import { timedEventManager } from "#app/global-event-manager";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { BiomePoolTier, biomeLinks } from "#balance/biomes";
import { biomeLinks } from "#balance/biomes";
import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims";
import { modifierTypes } from "#data/data-lists";
import type { IEggOptions } from "#data/egg";
@ -17,6 +17,7 @@ import type { AiType } from "#enums/ai-type";
import { BattleType } from "#enums/battle-type";
import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id";
import { BiomePoolTier } from "#enums/biome-pool-tier";
import { FieldPosition } from "#enums/field-position";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import type { MoveId } from "#enums/move-id";
@ -57,7 +58,7 @@ import i18next from "i18next";
* Animates exclamation sprite over trainer's head at start of encounter
* @param scene
*/
export function doTrainerExclamation() {
export function doTrainerExclamation(): void {
const exclamationSprite = globalScene.add.sprite(0, 0, "encounter_exclaim");
exclamationSprite.setName("exclamation");
globalScene.field.add(exclamationSprite);
@ -446,9 +447,9 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* This promise does not need to be awaited on if called in an encounter onInit (will just load lazily)
* @param moves
*/
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
moves = coerceArray(moves);
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
export async function loadCustomMovesForEncounter(moves: MoveId | MoveId[]): Promise<void> {
const movesArray: MoveId[] = coerceArray(moves);
return Promise.all(movesArray.map((move: MoveId) => initMoveAnim(move))).then(() => loadMoveAnimAssets(movesArray));
}
/**
@ -457,7 +458,7 @@ export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
* @param playSound
* @param showMessage
*/
export function updatePlayerMoney(changeValue: number, playSound = true, showMessage = true) {
export function updatePlayerMoney(changeValue: number, playSound = true, showMessage = true): void {
globalScene.money = Math.min(Math.max(globalScene.money + changeValue, 0), Number.MAX_SAFE_INTEGER);
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
@ -729,7 +730,7 @@ export function setEncounterRewards(
customShopRewards?: CustomModifierSettings,
eggRewards?: IEggOptions[],
preRewardsCallback?: Function,
) {
): void {
globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => {
if (preRewardsCallback) {
preRewardsCallback();
@ -797,7 +798,7 @@ export class OptionSelectSettings {
* MUST be used only in onOptionPhase, will not work in onPreOptionPhase or onPostOptionPhase
* @param optionSelectSettings
*/
export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) {
export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings): void {
globalScene.phaseManager.pushNew("MysteryEncounterPhase", optionSelectSettings);
}
@ -810,7 +811,7 @@ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSet
export function leaveEncounterWithoutBattle(
addHealPhase = false,
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
) {
): void {
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
globalScene.phaseManager.clearPhaseQueue(true);
handleMysteryEncounterVictory(addHealPhase);
@ -821,7 +822,7 @@ export function leaveEncounterWithoutBattle(
* @param addHealPhase - Adds an empty shop phase to allow player to purchase healing items
* @param doNotContinue - default `false`. If set to true, will not end the battle and continue to next wave
*/
export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinue = false) {
export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinue = false): void {
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) {
@ -864,7 +865,7 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
* Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge
* @param addHealPhase
*/
export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotContinue = false) {
export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotContinue = false): void {
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) {
@ -944,7 +945,7 @@ export function transitionMysteryEncounterIntroVisuals(hide = true, destroy = tr
* Will queue moves for any pokemon to use before the first CommandPhase of a battle
* Mostly useful for allowing {@linkcode MysteryEncounter} enemies to "cheat" and use moves before the first turn
*/
export function handleMysteryEncounterBattleStartEffects() {
export function handleMysteryEncounterBattleStartEffects(): void {
const encounter = globalScene.currentBattle.mysteryEncounter;
if (
globalScene.currentBattle.isBattleMysteryEncounter()
@ -1035,7 +1036,7 @@ export function getRandomEncounterSpecies(level: number, isBoss = false, rerollH
* Just a helper function to calculate aggregate stats for MEs in a Classic run
* @param baseSpawnWeight
*/
export function calculateMEAggregateStats(baseSpawnWeight: number) {
export function calculateMEAggregateStats(baseSpawnWeight: number): void {
const numRuns = 1000;
let run = 0;
const biomes = Object.keys(BiomeId).filter(key => Number.isNaN(Number(key)));
@ -1218,7 +1219,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
* Just a helper function to calculate aggregate stats for MEs in a Classic run
* @param luckValue - 0 to 14
*/
export function calculateRareSpawnAggregateStats(luckValue: number) {
export function calculateRareSpawnAggregateStats(luckValue: number): void {
const numRuns = 1000;
let run = 0;

View File

@ -2,17 +2,16 @@ import { determineEnemySpecies } from "#app/ai/ai-species-gen";
import type { AnySound } from "#app/battle-scene";
import type { GameMode } from "#app/game-mode";
import { globalScene } from "#app/global-scene";
import { uncatchableSpecies } from "#balance/biomes";
import { speciesEggMoves } from "#balance/egg-moves";
import { starterPassiveAbilities } from "#balance/passives";
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
import type { LevelMoves } from "#balance/pokemon-level-moves";
import {
pokemonFormLevelMoves,
pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves,
pokemonSpeciesLevelMoves,
} from "#balance/pokemon-level-moves";
import { speciesStarterCosts } from "#balance/starters";
import { uncatchableSpecies } from "#data/data-lists";
import type { GrowthRate } from "#data/exp";
import { Gender } from "#data/gender";
import { AbilityId } from "#enums/ability-id";
@ -28,6 +27,7 @@ import { hasExpSprite } from "#sprites/sprite-utils";
import type { Variant, VariantSet } from "#sprites/variant";
import { populateVariantColorCache, variantColorCache, variantData } from "#sprites/variant";
import type { Localizable } from "#types/locales";
import type { LevelMoves } from "#types/pokemon-level-moves";
import type { StarterMoveset } from "#types/save-data";
import type { EvolutionLevel, EvolutionLevelWithThreshold } from "#types/species-gen-types";
import { randSeedFloat, randSeedGauss } from "#utils/common";

View File

@ -1,7 +1,7 @@
import { getRandomRivalPartyMemberFunc } from "#app/ai/rival-team-gen";
import { globalScene } from "#app/global-scene";
import { signatureSpecies } from "#balance/signature-species";
import { tmSpecies } from "#balance/tms";
import { tmSpecies } from "#balance/tm-species-map";
import { modifierTypes } from "#data/data-lists";
import { doubleBattleDialogue } from "#data/double-battle-dialogue";
import { Gender } from "#data/gender";

View File

@ -0,0 +1,11 @@
export enum BiomePoolTier {
COMMON,
UNCOMMON,
RARE,
SUPER_RARE,
ULTRA_RARE,
BOSS,
BOSS_RARE,
BOSS_SUPER_RARE,
BOSS_ULTRA_RARE,
}

View File

@ -5,8 +5,7 @@ import type { PositionalTag } from "#data/positional-tags/positional-tag";
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides";
import type { BiomeTierTrainerPools, PokemonPools } from "#balance/biomes";
import { BiomePoolTier, biomePokemonPools, biomeTrainerPools } from "#balance/biomes";
import { biomePokemonPools, biomeTrainerPools } from "#balance/biomes";
import type { ArenaTag } from "#data/arena-tag";
import { EntryHazardTag, getArenaTag } from "#data/arena-tag";
import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers";
@ -24,6 +23,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side";
import type { ArenaTagType } from "#enums/arena-tag-type";
import type { BattlerIndex } from "#enums/battler-index";
import { BiomeId } from "#enums/biome-id";
import { BiomePoolTier } from "#enums/biome-pool-tier";
import { CommonAnim } from "#enums/move-anims-common";
import type { MoveId } from "#enums/move-id";
import type { PokemonType } from "#enums/pokemon-type";
@ -35,6 +35,7 @@ import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEven
import type { Pokemon } from "#field/pokemon";
import { FieldEffectModifier } from "#modifiers/modifier";
import type { Move } from "#moves/move";
import type { BiomeTierTrainerPools, PokemonPools } from "#types/biomes";
import type { AbstractConstructor } from "#types/type-helpers";
import { type Constructor, NumberHolder, randSeedInt } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";

View File

@ -2,7 +2,7 @@ import type { Ability, PreAttackModifyDamageAbAttrParams } from "#abilities/abil
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs";
import { generateMoveset } from "#app/ai/ai-moveset-gen";
import type { AnySound, BattleScene } from "#app/battle-scene";
import { PLAYER_PARTY_MAX_SIZE, RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants";
import { EVOLVE_MOVE, PLAYER_PARTY_MAX_SIZE, RARE_CANDY_FRIENDSHIP_CAP, RELEARN_MOVE } from "#app/constants";
import { timedEventManager } from "#app/global-event-manager";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
@ -15,11 +15,10 @@ import {
pokemonPrevolutions,
validateShedinjaEvo,
} from "#balance/pokemon-evolutions";
import type { LevelMoves } from "#balance/pokemon-level-moves";
import { EVOLVE_MOVE, RELEARN_MOVE } from "#balance/pokemon-level-moves";
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#balance/rates";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters";
import { reverseCompatibleTms, tmSpecies } from "#balance/tms";
import { tmSpecies } from "#balance/tm-species-map";
import { reverseCompatibleTms } from "#balance/tms";
import type { SuppressAbilitiesTag } from "#data/arena-tag";
import { NoCritTag, WeakenMoveScreenTag } from "#data/arena-tag";
import {
@ -144,6 +143,7 @@ import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#types
import type { getAttackDamageParams, getBaseDamageParams } from "#types/damage-params";
import type { DamageCalculationResult, DamageResult } from "#types/damage-result";
import type { IllusionData } from "#types/illusion-data";
import type { LevelMoves } from "#types/pokemon-level-moves";
import type { StarterDataEntry, StarterMoveset } from "#types/save-data";
import type { TurnMove } from "#types/turn-move";
import { BattleInfo } from "#ui/battle-info";

View File

@ -1,5 +1,5 @@
import { initAbilities } from "#abilities/ability";
import { initBiomes } from "#balance/biomes";
import { initBiomes } from "#balance/init-biomes";
import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions";
import { initSpecies } from "#balance/pokemon-species";
import { initChallenges } from "#data/challenge";

View File

@ -4,7 +4,8 @@ import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides";
import { EvolutionItem, pokemonEvolutions } from "#balance/pokemon-evolutions";
import { tmPoolTiers, tmSpecies } from "#balance/tms";
import { tmSpecies } from "#balance/tm-species-map";
import { tmPoolTiers } from "#balance/tms";
import { getBerryEffectDescription, getBerryName } from "#data/berry";
import { getDailyEventSeedLuck } from "#data/daily-run";
import { allMoves, modifierTypes } from "#data/data-lists";

View File

@ -1,10 +1,10 @@
import type { AnySound } from "#app/battle-scene";
import { EVOLVE_MOVE } from "#app/constants";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { Phase } from "#app/phase";
import type { SpeciesFormEvolution } from "#balance/pokemon-evolutions";
import { FusionSpeciesFormEvolution } from "#balance/pokemon-evolutions";
import { EVOLVE_MOVE } from "#balance/pokemon-level-moves";
import { getTypeRgb } from "#data/type";
import { LearnMoveSituation } from "#enums/learn-move-situation";
import { UiMode } from "#enums/ui-mode";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene";
import { biomeLinks, getBiomeName } from "#balance/biomes";
import { biomeLinks } from "#balance/biomes";
import { BiomeId } from "#enums/biome-id";
import { ChallengeType } from "#enums/challenge-type";
import { UiMode } from "#enums/ui-mode";
@ -7,7 +7,7 @@ import { MapModifier, MoneyInterestModifier } from "#modifiers/modifier";
import { BattlePhase } from "#phases/battle-phase";
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import { applyChallenges } from "#utils/challenge-utils";
import { BooleanHolder, randSeedInt } from "#utils/common";
import { BooleanHolder, getBiomeName, randSeedInt } from "#utils/common";
export class SelectBiomePhase extends BattlePhase {
public readonly phaseName = "SelectBiomePhase";

View File

@ -1,13 +1,10 @@
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import Overrides from "#app/overrides";
import type { BiomeTierTod } from "#balance/biomes";
import { BiomePoolTier, catchableSpecies } from "#balance/biomes";
import { speciesEggMoves } from "#balance/egg-moves";
import { starterPassiveAbilities } from "#balance/passives";
import type { SpeciesFormEvolution } from "#balance/pokemon-evolutions";
import { pokemonEvolutions, pokemonPrevolutions, pokemonStarters } from "#balance/pokemon-evolutions";
import type { LevelMoves } from "#balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#balance/pokemon-level-moves";
import {
getPassiveCandyCount,
@ -17,7 +14,7 @@ import {
speciesStarterCosts,
} from "#balance/starters";
import { speciesTmMoves } from "#balance/tms";
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
import { allAbilities, allMoves, allSpecies, catchableSpecies } from "#data/data-lists";
import { Egg, getEggTierForSpecies } from "#data/egg";
import { GrowthRate, getGrowthRateColor } from "#data/exp";
import { Gender, getGenderColor, getGenderSymbol } from "#data/gender";
@ -29,6 +26,7 @@ import { normalForm } from "#data/pokemon-species";
import { AbilityAttr } from "#enums/ability-attr";
import type { AbilityId } from "#enums/ability-id";
import { BiomeId } from "#enums/biome-id";
import { BiomePoolTier } from "#enums/biome-pool-tier";
import { Button } from "#enums/buttons";
import { Device } from "#enums/devices";
import { DexAttr } from "#enums/dex-attr";
@ -44,7 +42,9 @@ import { UiMode } from "#enums/ui-mode";
import type { Variant } from "#sprites/variant";
import { getVariantIcon, getVariantTint } from "#sprites/variant";
import { SettingKeyboard } from "#system/settings-keyboard";
import type { BiomeTierTod } from "#types/biomes";
import type { DexEntry } from "#types/dex-data";
import type { LevelMoves } from "#types/pokemon-level-moves";
import type { StarterAttributes } from "#types/save-data";
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import { BaseStatsOverlay } from "#ui/base-stats-overlay";

View File

@ -1,6 +1,5 @@
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import { catchableSpecies } from "#balance/biomes";
import { speciesEggMoves } from "#balance/egg-moves";
import { pokemonStarters } from "#balance/pokemon-evolutions";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#balance/pokemon-level-moves";
@ -13,7 +12,7 @@ import {
speciesStarterCosts,
} from "#balance/starters";
import { speciesTmMoves } from "#balance/tms";
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
import { allAbilities, allMoves, allSpecies, catchableSpecies } from "#data/data-lists";
import type { PokemonForm, PokemonSpecies } from "#data/pokemon-species";
import { normalForm } from "#data/pokemon-species";
import { AbilityAttr } from "#enums/ability-attr";

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene";
import { getBiomeName } from "#balance/biomes";
import { getNatureName, getNatureStatMultiplier } from "#data/nature";
import { getPokeballAtlasKey } from "#data/pokeball";
import { getTypeRgb } from "#data/type";
@ -25,7 +24,7 @@ import type { SessionSaveData } from "#types/save-data";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common";
import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getBiomeName, getPlayTimeString } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle";

View File

@ -6,7 +6,6 @@ import Overrides from "#app/overrides";
import { handleTutorial, Tutorial } from "#app/tutorial";
import { speciesEggMoves } from "#balance/egg-moves";
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
import type { LevelMoves } from "#balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#balance/pokemon-level-moves";
import {
getPassiveCandyCount,
@ -49,6 +48,7 @@ import { achvs } from "#system/achv";
import { RibbonData } from "#system/ribbons/ribbon-data";
import { SettingKeyboard } from "#system/settings-keyboard";
import type { DexEntry } from "#types/dex-data";
import type { LevelMoves } from "#types/pokemon-level-moves";
import type { Starter, StarterAttributes, StarterDataEntry, StarterMoveset } from "#types/save-data";
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown";

View File

@ -2,7 +2,6 @@ import type { Ability } from "#abilities/ability";
import { loggedInUser } from "#app/account";
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import { getBiomeName } from "#balance/biomes";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters";
import { getLevelRelExp, getLevelTotalExp } from "#data/exp";
import { getGenderColor, getGenderSymbol } from "#data/gender";
@ -27,7 +26,15 @@ import { getVariantTint } from "#sprites/variant";
import { achvs } from "#system/achv";
import { addBBCodeTextObject, addTextObject, getBBCodeFrag, getTextColor } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { fixedInt, formatStat, getLocalizedSpriteKey, getShinyDescriptor, padInt, rgbHexToRgba } from "#utils/common";
import {
fixedInt,
formatStat,
getBiomeName,
getLocalizedSpriteKey,
getShinyDescriptor,
padInt,
rgbHexToRgba,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { toCamelCase, toTitleCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";

View File

@ -1,6 +1,8 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { BiomeId } from "#enums/biome-id";
import { MoneyFormat } from "#enums/money-format";
import type { Variant } from "#sprites/variant";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
export type nil = null | undefined;
@ -515,3 +517,19 @@ export function coerceArray<T>(input: T): T extends any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}
export function getBiomeName(biome: BiomeId | -1) {
if (biome === -1) {
return i18next.t("biome:unknownLocation");
}
switch (biome) {
case BiomeId.GRASS:
return i18next.t("biome:grass");
case BiomeId.RUINS:
return i18next.t("biome:ruins");
case BiomeId.END:
return i18next.t("biome:end");
default:
return i18next.t(`biome:${toCamelCase(BiomeId[biome])}`);
}
}

View File

@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2025 Pagefault Games
* SPDX-FileContributor: SirzBenjie
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
*
* `pokemon-level-moves.ts` has `@ts-nocheck` at the top to disable type checking, as not
* using this directive dramatically slows down type checking due to the sheer
* size of the file.
* To remedy this while preserving type safety, this test ensures that
* each entry of `pokemonSpeciesLevelMoves` and `pokemonFormLevelMoves` is valid
*
* @module
*/
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#balance/pokemon-level-moves";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { describe, expect, test } from "vitest";
describe("TypeCheck - pokemon-level-moves.ts", () => {
function validateLevelMoves(value: any): void {
expect(Array.isArray(value), "level moves must be an array").toBe(true);
for (const entry of value) {
expect(Array.isArray(entry) && entry.length === 2, "each entry must be an array of length 2").toBe(true);
const [level, moveId] = entry;
expect(level, "level must be a number").toBeTypeOf("number");
expect(MoveId[moveId], "moveId must be a valid MoveId").toBeDefined();
}
}
describe("pokemonSpeciesLevelMoves", () => {
const pokemonSpeciesNameMap = Object.entries(pokemonSpeciesLevelMoves).map(([speciesId, value]) => {
return { name: SpeciesId[+speciesId], value };
});
test.each(pokemonSpeciesNameMap)("$name has valid level moves", ({ name, value }) => {
expect(name, "species is a valid species ID").toBeDefined();
validateLevelMoves(value);
});
describe("pokemonFormLevelMoves", () => {
const pokemonSpeciesFormNameMap = Object.entries(pokemonFormLevelMoves).map(([speciesId, value]) => {
return { name: SpeciesId[+speciesId], value, speciesId };
});
test.each(pokemonSpeciesFormNameMap)("$name has valid form level moves", ({ name, value, speciesId }) => {
expect(name, "species is a valid species ID").toBeDefined();
expect(typeof value === "object" && value !== null, "value must be an object").toBe(true);
const species = getPokemonSpecies(+speciesId);
expect(species, "species must be in allSpecies").toBeDefined();
const speciesForms = species.forms;
expect(Array.isArray(speciesForms), "species.forms must be an array").toBe(true);
for (const [formId, formLevelMoves] of Object.entries(value)) {
const formIdAsNumber = +formId;
expect(formIdAsNumber, "form ID must be a number").toBeTypeOf("number");
expect(speciesForms[formIdAsNumber], "form ID must be in species.forms").toBeDefined();
validateLevelMoves(formLevelMoves);
}
});
});
});
});

View File

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2025 Pagefault Games
* SPDX-FileContributor: SirzBenjie
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* `tm-species-map.ts` has `@ts-nocheck` at the top to disable type checking, as not
* using this directive dramatically slows down type checking due to the sheer
* size of the file.
* To remedy this while preserving type safety, this test ensures that
* each entry of `tmSpecies` is a valid `TMSpeciesEntry`.
*
*
* @module
*/
import { tmSpecies } from "#balance/tm-species-map";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { describe, expect, test } from "vitest";
describe("TypeCheck - tmSpecies", () => {
// Basic sanity check
const tmNameMap = Object.entries(tmSpecies).map(([moveId, value]) => {
return { name: MoveId[+moveId], value };
});
test.each(tmNameMap)("$name has valid species", ({ name, value }) => {
expect(name, "tm is a valid move ID").toBeDefined();
expect(Array.isArray(value), "value is an array").toBe(true);
for (const entry of value) {
const speciesId = typeof entry === "number" ? entry : entry[0];
expect(SpeciesId[speciesId], "each entry should be a species ID").toBeDefined();
if (typeof entry !== "number") {
expect(Array.isArray(entry), "non-numeric entry must be array").toBe(true);
expect(entry.length, "array entry must have at least 2 elements").toBeGreaterThan(1);
for (const subEntry of entry.slice(1)) {
expect(subEntry, "form arrays must have strings for remaining elements").toBeTypeOf("string");
}
}
}
});
});

View File

@ -12,6 +12,24 @@ import type { BattlerTagDataMap, SerializableBattlerTagType } from "#types/battl
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
// intersection required to preserve T for inferences
/**
* Helper type for serializable battler tag options. Allows for caching of the type to avoid
* instantiation each time typescript encounters the type. (dramatically speeds up typechecking)
* @internal
*/
type SerializableTagOptions<B extends SerializableBattlerTagType> = OneOther<BattlerTagDataMap[B], "tagType"> & {
tagType: B;
};
/**
* Helper type for non-serializable battler tag options.
* @internal
*/
type NonSerializableTagOptions<B extends BattlerTagType> = OneOther<BattlerTagTypeMap[B], "tagType"> & {
tagType: B;
};
/**
* Options type for {@linkcode toHaveBattlerTag}.
* @typeParam B - The {@linkcode BattlerTagType} being checked
@ -19,11 +37,9 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
* If B corresponds to a serializable `BattlerTag`, only properties allowed to be serialized
* (i.e. can change across instances) will be present and able to be checked.
*/
export type toHaveBattlerTagOptions<B extends BattlerTagType> = (B extends SerializableBattlerTagType
? OneOther<BattlerTagDataMap[B], "tagType">
: OneOther<BattlerTagTypeMap[B], "tagType">) & {
tagType: B;
};
export type toHaveBattlerTagOptions<B extends BattlerTagType> = B extends SerializableBattlerTagType
? SerializableTagOptions<B>
: NonSerializableTagOptions<B>;
/**
* Matcher that checks if a {@linkcode Pokemon} has a specific {@linkcode BattlerTag}.

View File

@ -18,6 +18,7 @@
"ScriptHost",
"WebWorker.ImportScripts"
],
"skipLibCheck": true, // Safe and almost always beneficial
"moduleResolution": "bundler",
"resolveJsonModule": true,
"esModuleInterop": true,