This commit is contained in:
Wlowscha 2025-07-19 16:30:48 +02:00
commit e1edb87990
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
83 changed files with 2130 additions and 1781 deletions

View File

@ -11,7 +11,7 @@ on:
jobs:
deploy:
if: github.repository == 'pagefaultgames/pokerogue' && github.ref_name == ${{ vars.BETA_DEPLOY_BRANCH || 'beta' }}
if: github.repository == 'pagefaultgames/pokerogue' && github.ref_name == (vars.BETA_DEPLOY_BRANCH || 'beta')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@ -40,6 +40,7 @@
## Backgrounds
- Squip (Paid Commissions)
- Contributions by Someonealive-QN
- Contributions by redactedinlight
## UI
- GAMEFREAK

View File

@ -15,4 +15,6 @@ post-merge:
post-checkout:
commands:
update-submodules:
run: git submodule update --init --recursive
# cf https://git-scm.com/docs/githooks#_post_checkout:
# The 3rd argument is 1 for branch checkouts and 0 for file checkouts.
run: if test {3} -eq "1"; then git submodule update --init --recursive; fi

View File

@ -29,34 +29,34 @@
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.13.14",
"@vitest/coverage-istanbul": "^3.0.9",
"@types/node": "^22.16.3",
"@vitest/coverage-istanbul": "^3.2.4",
"chalk": "^5.4.1",
"dependency-cruiser": "^16.3.10",
"inquirer": "^12.4.2",
"jsdom": "^26.0.0",
"lefthook": "^1.11.5",
"msw": "^2.7.3",
"dependency-cruiser": "^16.10.4",
"inquirer": "^12.7.0",
"jsdom": "^26.1.0",
"lefthook": "^1.12.2",
"msw": "^2.10.4",
"phaser3spectorjs": "^0.0.8",
"typedoc": "^0.28.1",
"typescript": "^5.8.2",
"vite": "^6.3.4",
"typedoc": "^0.28.7",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9",
"vitest": "^3.2.4",
"vitest-canvas-mock": "^0.3.3"
},
"dependencies": {
"@material/material-color-utilities": "^0.2.7",
"compare-versions": "^6.1.1",
"crypto-js": "^4.2.0",
"i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.2.0",
"json-stable-stringify": "^1.3.0",
"jszip": "^3.10.1",
"phaser": "^3.88.2",
"phaser3-rex-plugins": "^1.80.15"
"phaser": "^3.90.0",
"phaser3-rex-plugins": "^1.80.16"
},
"engines": {
"node": ">=22.0.0"

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -1 +1 @@
Subproject commit 288ffa034d07a90ea227710703eb1331b99ffed0
Subproject commit 362b2c4fcc20b31a7be6c2dab537055fbaeb247f

18
src/@types/enum-types.ts Normal file
View File

@ -0,0 +1,18 @@
/** Union type accepting any TS Enum or `const object`, with or without reverse mapping. */
export type EnumOrObject = Record<string | number, string | number>;
/**
* Utility type to extract the enum values from a `const object`,
* or convert an `enum` interface produced by `typeof Enum` into the union type representing its values.
*/
export type EnumValues<E> = E[keyof E];
/**
* Generic type constraint representing a TS numeric enum with reverse mappings.
* @example
* TSNumericEnum<typeof WeatherType>
*/
export type TSNumericEnum<T extends EnumOrObject> = number extends EnumValues<T> ? T : never;
/** Generic type constraint representing a non reverse-mapped TS enum or `const object`. */
export type NormalEnum<T extends EnumOrObject> = Exclude<T, TSNumericEnum<T>>;

View File

@ -2,8 +2,8 @@ import type { PartyMemberStrength } from "#enums/party-member-strength";
import type { SpeciesId } from "#enums/species-id";
import type { EnemyPokemon } from "#field/pokemon";
import type { TrainerItemConfiguration } from "#items/trainer-item-data-types";
import type { TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import type { TrainerConfig } from "#trainers/trainer-config";
import type { TrainerPartyTemplate } from "#trainers/trainer-party-template";
export type PartyTemplateFunc = () => TrainerPartyTemplate;
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;

View File

@ -2,8 +2,11 @@
* A collection of custom utility types that aid in type checking and ensuring strict type conformity
*/
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { AbAttr } from "#types/ability-types";
// biome-ignore-start lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { AbAttr } from "#abilities/ability";
// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { EnumValues } from "#types/enum-types";
/**
* Exactly matches the type of the argument, preventing adding additional properties.
@ -11,7 +14,7 @@ import type { AbAttr } from "#types/ability-types";
* Should never be used with `extends`, as this will nullify the exactness of the type.
*
* As an example, used to ensure that the parameters of {@linkcode AbAttr.canApply} and {@linkcode AbAttr.getTriggerMessage} are compatible with
* the type of the apply method
* the type of its {@linkcode AbAttr.apply | apply} method.
*
* @typeParam T - The type to match exactly
*/
@ -26,9 +29,18 @@ export type Exact<T> = {
export type Closed<X> = X;
/**
* Remove `readonly` from all properties of the provided type
* @typeParam T - The type to make mutable
* Remove `readonly` from all properties of the provided type.
* @typeParam T - The type to make mutable.
*/
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
/**
* Type helper to obtain the keys associated with a given value inside a `const object`.
* @typeParam O - The type of the object
* @typeParam V - The type of one of O's values
*/
export type InferKeys<O extends Record<keyof any, unknown>, V extends EnumValues<O>> = {
[K in keyof O]: O[K] extends V ? K : never;
}[keyof O];

View File

@ -138,7 +138,6 @@ import {
type Constructor,
fixedInt,
formatMoney,
getEnumValues,
getIvsFromId,
isBetween,
isNullOrUndefined,
@ -148,6 +147,7 @@ import {
shiftCharCodes,
} from "#utils/common";
import { deepMergeSpriteData } from "#utils/data";
import { getEnumValues } from "#utils/enums";
import { getModifierPoolForType } from "#utils/modifier-utils";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";
@ -2178,6 +2178,7 @@ export class BattleScene extends SceneBase {
),
]
: allSpecies.filter(s => s.isCatchable());
// TODO: should this use `randSeedItem`?
return filteredSpecies[randSeedInt(filteredSpecies.length)];
}
@ -2203,6 +2204,7 @@ export class BattleScene extends SceneBase {
}
}
// TODO: should this use `randSeedItem`?
return biomes[randSeedInt(biomes.length)];
}
@ -3497,6 +3499,7 @@ export class BattleScene extends SceneBase {
console.log("No Mystery Encounters found, falling back to Mysterious Challengers.");
return allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS];
}
// TODO: should this use `randSeedItem`?
encounter = availableEncounters[randSeedInt(availableEncounters.length)];
// New encounter object to not dirty flags
encounter = new MysteryEncounter(encounter);

View File

@ -5,12 +5,10 @@ import { BattleSpec } from "#enums/battle-spec";
import { BattleType } from "#enums/battle-type";
import { BattlerIndex } from "#enums/battler-index";
import type { Command } from "#enums/command";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import type { HeldItemId } from "#enums/held-item-id";
import type { MoveId } from "#enums/move-id";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PlayerGender } from "#enums/player-gender";
import type { PokeballType } from "#enums/pokeball";
import { RewardTier } from "#enums/reward-tier";
import { SpeciesFormKey } from "#enums/species-form-key";
@ -27,7 +25,6 @@ import { MusicPreference } from "#system/settings";
import { trainerConfigs } from "#trainers/trainer-config";
import type { TurnMove } from "#types/turn-move";
import {
getEnumValues,
NumberHolder,
randInt,
randomString,
@ -36,6 +33,7 @@ import {
randSeedItem,
shiftCharCodes,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
export interface TurnCommand {
command: Command;
@ -566,369 +564,3 @@ export function getRandomTrainerFunc(
return new Trainer(trainerTypes[rand], trainerGender);
};
}
export interface FixedBattleConfigs {
[key: number]: FixedBattleConfig;
}
/**
* Youngster/Lass on 5
* Rival on 8, 55, 95, 145, 195
* Evil team grunts on 35, 62, 64, and 112
* Evil team admin on 66 and 114
* Evil leader on 115, 165
* E4 on 182, 184, 186, 188
* Champion on 190
*/
export const classicFixedBattles: FixedBattleConfigs = {
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() => new Trainer(TrainerType.YOUNGSTER, randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT),
),
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
),
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_2,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_3,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_4,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
1,
),
),
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_1,
TrainerType.MAXIE,
TrainerType.ARCHIE,
TrainerType.CYRUS,
TrainerType.GHETSIS,
TrainerType.LYSANDRE,
TrainerType.LUSAMINE,
TrainerType.GUZMA,
TrainerType.ROSE,
TrainerType.PENNY,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
RewardTier.ROGUE,
RewardTier.ROGUE,
RewardTier.ULTRA,
RewardTier.ULTRA,
RewardTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_5,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
RewardTier.ROGUE,
RewardTier.ROGUE,
RewardTier.ROGUE,
RewardTier.ULTRA,
RewardTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_2,
TrainerType.MAXIE_2,
TrainerType.ARCHIE_2,
TrainerType.CYRUS_2,
TrainerType.GHETSIS_2,
TrainerType.LYSANDRE_2,
TrainerType.LUSAMINE_2,
TrainerType.GUZMA_2,
TrainerType.ROSE_2,
TrainerType.PENNY_2,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
RewardTier.ROGUE,
RewardTier.ROGUE,
RewardTier.ULTRA,
RewardTier.ULTRA,
RewardTier.ULTRA,
RewardTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LORELEI,
TrainerType.WILL,
TrainerType.SIDNEY,
TrainerType.AARON,
TrainerType.SHAUNTAL,
TrainerType.MALVA,
[TrainerType.HALA, TrainerType.MOLAYNE],
TrainerType.MARNIE_ELITE,
TrainerType.RIKA,
TrainerType.CRISPIN,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BRUNO,
TrainerType.KOGA,
TrainerType.PHOEBE,
TrainerType.BERTHA,
TrainerType.MARSHAL,
TrainerType.SIEBOLD,
TrainerType.OLIVIA,
TrainerType.NESSA_ELITE,
TrainerType.POPPY,
TrainerType.AMARYS,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.AGATHA,
TrainerType.BRUNO,
TrainerType.GLACIA,
TrainerType.FLINT,
TrainerType.GRIMSLEY,
TrainerType.WIKSTROM,
TrainerType.ACEROLA,
[TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE],
TrainerType.LARRY_ELITE,
TrainerType.LACEY,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LANCE,
TrainerType.KAREN,
TrainerType.DRAKE,
TrainerType.LUCIAN,
TrainerType.CAITLIN,
TrainerType.DRASNA,
TrainerType.KAHILI,
TrainerType.RAIHAN_ELITE,
TrainerType.HASSEL,
TrainerType.DRAYTON,
]),
),
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BLUE,
[TrainerType.RED, TrainerType.LANCE_CHAMPION],
[TrainerType.STEVEN, TrainerType.WALLACE],
TrainerType.CYNTHIA,
[TrainerType.ALDER, TrainerType.IRIS],
TrainerType.DIANTHA,
[TrainerType.KUKUI, TrainerType.HAU],
[TrainerType.LEON, TrainerType.MUSTARD],
[TrainerType.GEETA, TrainerType.NEMONA],
TrainerType.KIERAN,
]),
),
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_6,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
RewardTier.ROGUE,
RewardTier.ROGUE,
RewardTier.ULTRA,
RewardTier.ULTRA,
RewardTier.GREAT,
RewardTier.GREAT,
],
allowLuckUpgrades: false,
}),
};

View File

@ -1117,7 +1117,6 @@ export class TrickRoomTag extends ArenaTag {
globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:trickRoomOnAdd", {
moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(),
}),
);
}

View File

@ -5,7 +5,8 @@ import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type";
import { getEnumValues, randSeedInt } from "#utils/common";
import { randSeedInt } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import i18next from "i18next";
export function getBiomeName(biome: BiomeId | -1) {
@ -489,7 +490,7 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.NIGHT]: [],
[TimeOfDay.ALL]: [ SpeciesId.POLITOED, SpeciesId.GALAR_STUNFISK ]
},
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AZELF, SpeciesId.POIPOLE ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AZELF, { 1: [ SpeciesId.POIPOLE ], 60: [ SpeciesId.NAGANADEL ] } ] },
[BiomePoolTier.BOSS]: {
[TimeOfDay.DAWN]: [ SpeciesId.QUAGSIRE, SpeciesId.LUDICOLO ],
[TimeOfDay.DAY]: [ SpeciesId.QUAGSIRE, SpeciesId.LUDICOLO ],
@ -504,7 +505,7 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.NIGHT]: [],
[TimeOfDay.ALL]: [ SpeciesId.FERALIGATR, SpeciesId.POLITOED, SpeciesId.SWAMPERT, SpeciesId.GALAR_STUNFISK ]
},
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AZELF, SpeciesId.NAGANADEL ] },
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AZELF, { 1: [ SpeciesId.POIPOLE ], 60: [ SpeciesId.NAGANADEL ] } ] },
[BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] }
},
[BiomeId.BEACH]: {
@ -1117,7 +1118,7 @@ export const biomePokemonPools: BiomePokemonPools = {
},
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, SpeciesId.LUCARIO, SpeciesId.THROH, SpeciesId.SAWK, { 1: [ SpeciesId.PANCHAM ], 52: [ SpeciesId.PANGORO ] } ] },
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONTOP, SpeciesId.GALLADE, SpeciesId.GALAR_FARFETCHD ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, SpeciesId.KUBFU, SpeciesId.GALAR_ZAPDOS ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU] }, SpeciesId.GALAR_ZAPDOS ] },
[BiomePoolTier.BOSS]: {
[TimeOfDay.DAWN]: [],
[TimeOfDay.DAY]: [],
@ -1126,7 +1127,7 @@ export const biomePokemonPools: BiomePokemonPools = {
[TimeOfDay.ALL]: [ SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, SpeciesId.HARIYAMA, SpeciesId.MEDICHAM, SpeciesId.LUCARIO, SpeciesId.TOXICROAK, SpeciesId.THROH, SpeciesId.SAWK, SpeciesId.SCRAFTY, SpeciesId.MIENSHAO, SpeciesId.BEWEAR, SpeciesId.GRAPPLOCT, SpeciesId.ANNIHILAPE ]
},
[BiomePoolTier.BOSS_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONTOP, SpeciesId.GALLADE, SpeciesId.PANGORO, SpeciesId.SIRFETCHD, SpeciesId.HISUI_DECIDUEYE ] },
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, SpeciesId.URSHIFU ] },
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU] } ] },
[BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ZAMAZENTA, SpeciesId.GALAR_ZAPDOS ] }
},
[BiomeId.FACTORY]: {
@ -1417,8 +1418,8 @@ export const biomePokemonPools: BiomePokemonPools = {
{ 1: [ SpeciesId.HATENNA ], 32: [ SpeciesId.HATTREM ], 42: [ SpeciesId.HATTERENE ] }
]
},
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AUDINO, SpeciesId.ETERNAL_FLOETTE ] },
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] },
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.AUDINO ] },
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ETERNAL_FLOETTE ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.DIANCIE, SpeciesId.ENAMORUS ] },
[BiomePoolTier.BOSS]: {
[TimeOfDay.DAWN]: [],
@ -1595,10 +1596,10 @@ export const biomePokemonPools: BiomePokemonPools = {
[BiomePoolTier.UNCOMMON]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.SOLOSIS ], 32: [ SpeciesId.DUOSION ], 41: [ SpeciesId.REUNICLUS ] } ] },
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.DITTO, { 1: [ SpeciesId.PORYGON ], 30: [ SpeciesId.PORYGON2 ] } ] },
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TYPE_NULL ] },
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [SpeciesId.TYPE_NULL], 60: [ SpeciesId.SILVALLY ] } ] },
[BiomePoolTier.BOSS]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.MUK, SpeciesId.ELECTRODE, SpeciesId.BRONZONG, SpeciesId.MAGNEZONE, SpeciesId.PORYGON_Z, SpeciesId.REUNICLUS, SpeciesId.KLINKLANG ] },
[BiomePoolTier.BOSS_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] },
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM, SpeciesId.ZYGARDE, SpeciesId.SILVALLY ] },
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM, SpeciesId.ZYGARDE, { 1: [SpeciesId.TYPE_NULL], 60: [ SpeciesId.SILVALLY ] } ] },
[BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.MEWTWO, SpeciesId.MIRAIDON ] }
},
[BiomeId.END]: {
@ -6968,7 +6969,7 @@ export function initBiomes() {
]
],
[ SpeciesId.ETERNAL_FLOETTE, PokemonType.FAIRY, -1, [
[ BiomeId.FAIRY_CAVE, BiomePoolTier.RARE ],
[ BiomeId.FAIRY_CAVE, BiomePoolTier.SUPER_RARE ],
[ BiomeId.FAIRY_CAVE, BiomePoolTier.BOSS_RARE ]
]
],
@ -7707,10 +7708,10 @@ export function initBiomes() {
const traverseBiome = (biome: BiomeId, depth: number) => {
if (biome === BiomeId.END) {
const biomeList = Object.keys(BiomeId).filter(key => !Number.isNaN(Number(key)));
const biomeList = getEnumValues(BiomeId)
biomeList.pop(); // Removes BiomeId.END from the list
const randIndex = randSeedInt(biomeList.length, 1); // Will never be BiomeId.TOWN
biome = BiomeId[biomeList[randIndex]];
biome = biomeList[randIndex];
}
const linkedBiomes: (BiomeId | [ BiomeId, number ])[] = Array.isArray(biomeLinks[biome])
? biomeLinks[biome] as (BiomeId | [ BiomeId, number ])[]

View File

@ -1,8 +1,8 @@
import { allMoves } from "#data/data-lists";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { getEnumKeys, getEnumValues } from "#utils/common";
import { toReadableString } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
export const speciesEggMoves = {
[SpeciesId.BULBASAUR]: [ MoveId.SAPPY_SEED, MoveId.MALIGNANT_CHAIN, MoveId.EARTH_POWER, MoveId.MATCHA_GOTCHA ],
@ -26,14 +26,14 @@ export const speciesEggMoves = {
[SpeciesId.MEOWTH]: [ MoveId.HEART_STAMP, MoveId.SWORDS_DANCE, MoveId.SIZZLY_SLIDE, MoveId.TAIL_SLAP ],
[SpeciesId.PSYDUCK]: [ MoveId.FROST_BREATH, MoveId.AQUA_STEP, MoveId.MYSTICAL_POWER, MoveId.BOUNCY_BUBBLE ],
[SpeciesId.MANKEY]: [ MoveId.DRAIN_PUNCH, MoveId.SLACK_OFF, MoveId.METEOR_MASH, MoveId.NO_RETREAT ],
[SpeciesId.GROWLITHE]: [ MoveId.ZING_ZAP, MoveId.PARTING_SHOT, MoveId.MORNING_SUN, MoveId.SACRED_FIRE ],
[SpeciesId.GROWLITHE]: [ MoveId.ZING_ZAP, MoveId.DRAGON_DANCE, MoveId.MORNING_SUN, MoveId.SACRED_FIRE ],
[SpeciesId.POLIWAG]: [ MoveId.SLACK_OFF, MoveId.WILDBOLT_STORM, MoveId.DRAIN_PUNCH, MoveId.SURGING_STRIKES ],
[SpeciesId.ABRA]: [ MoveId.AURA_SPHERE, MoveId.BADDY_BAD, MoveId.ICE_BEAM, MoveId.PSYSTRIKE ],
[SpeciesId.MACHOP]: [ MoveId.COMBAT_TORQUE, MoveId.METEOR_MASH, MoveId.MOUNTAIN_GALE, MoveId.FISSURE ],
[SpeciesId.BELLSPROUT]: [ MoveId.SOLAR_BLADE, MoveId.STRENGTH_SAP, MoveId.FIRE_LASH, MoveId.VICTORY_DANCE ],
[SpeciesId.TENTACOOL]: [ MoveId.BANEFUL_BUNKER, MoveId.MALIGNANT_CHAIN, MoveId.BOUNCY_BUBBLE, MoveId.STRENGTH_SAP ],
[SpeciesId.GEODUDE]: [ MoveId.FLARE_BLITZ, MoveId.HEAD_SMASH, MoveId.SHORE_UP, MoveId.SHELL_SMASH ],
[SpeciesId.PONYTA]: [ MoveId.HEADLONG_RUSH, MoveId.FIRE_LASH, MoveId.SWORDS_DANCE, MoveId.VOLT_TACKLE ],
[SpeciesId.PONYTA]: [ MoveId.HEADLONG_RUSH, MoveId.HIGH_JUMP_KICK, MoveId.SWORDS_DANCE, MoveId.VOLT_TACKLE ],
[SpeciesId.SLOWPOKE]: [ MoveId.SPLISHY_SPLASH, MoveId.FROST_BREATH, MoveId.SHED_TAIL, MoveId.MYSTICAL_POWER ],
[SpeciesId.MAGNEMITE]: [ MoveId.PARABOLIC_CHARGE, MoveId.FLAMETHROWER, MoveId.ICE_BEAM, MoveId.THUNDERCLAP ],
[SpeciesId.FARFETCHD]: [ MoveId.IVY_CUDGEL, MoveId.TRIPLE_ARROWS, MoveId.DRILL_RUN, MoveId.VICTORY_DANCE ],
@ -63,9 +63,9 @@ export const speciesEggMoves = {
[SpeciesId.LAPRAS]: [ MoveId.RECOVER, MoveId.FREEZE_DRY, MoveId.SCALD, MoveId.SHELL_SMASH ],
[SpeciesId.DITTO]: [ MoveId.MIMIC, MoveId.SKETCH, MoveId.METRONOME, MoveId.IMPRISON ],
[SpeciesId.EEVEE]: [ MoveId.WISH, MoveId.NO_RETREAT, MoveId.ZIPPY_ZAP, MoveId.BOOMBURST ],
[SpeciesId.PORYGON]: [ MoveId.THUNDERCLAP, MoveId.AURA_SPHERE, MoveId.FLAMETHROWER, MoveId.TECHNO_BLAST ],
[SpeciesId.PORYGON]: [ MoveId.THUNDERCLAP, MoveId.DAZZLING_GLEAM, MoveId.FLAMETHROWER, MoveId.TECHNO_BLAST ],
[SpeciesId.OMANYTE]: [ MoveId.FREEZE_DRY, MoveId.GIGA_DRAIN, MoveId.POWER_GEM, MoveId.STEAM_ERUPTION ],
[SpeciesId.KABUTO]: [ MoveId.CEASELESS_EDGE, MoveId.HIGH_HORSEPOWER, MoveId.CRABHAMMER, MoveId.MIGHTY_CLEAVE ],
[SpeciesId.KABUTO]: [ MoveId.CEASELESS_EDGE, MoveId.HIGH_HORSEPOWER, MoveId.MIGHTY_CLEAVE, MoveId.CRABHAMMER ],
[SpeciesId.AERODACTYL]: [ MoveId.FLOATY_FALL, MoveId.HIGH_HORSEPOWER, MoveId.STONE_AXE, MoveId.SWORDS_DANCE ],
[SpeciesId.ARTICUNO]: [ MoveId.EARTH_POWER, MoveId.CALM_MIND, MoveId.AURORA_VEIL, MoveId.AEROBLAST ],
[SpeciesId.ZAPDOS]: [ MoveId.BLEAKWIND_STORM, MoveId.CALM_MIND, MoveId.SANDSEAR_STORM, MoveId.ELECTRO_SHOT ],
@ -75,8 +75,8 @@ export const speciesEggMoves = {
[SpeciesId.MEW]: [ MoveId.PHOTON_GEYSER, MoveId.MOONBLAST, MoveId.ASTRAL_BARRAGE, MoveId.SHELL_SMASH ],
[SpeciesId.CHIKORITA]: [ MoveId.SAPPY_SEED, MoveId.STONE_AXE, MoveId.DRAGON_DANCE, MoveId.SPORE ],
[SpeciesId.CYNDAQUIL]: [ MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.FIERY_DANCE, MoveId.ELECTRO_DRIFT ],
[SpeciesId.TOTODILE]: [ MoveId.THUNDER_PUNCH, MoveId.DRAGON_DANCE, MoveId.PLAY_ROUGH, MoveId.SURGING_STRIKES ],
[SpeciesId.CYNDAQUIL]: [ MoveId.BURNING_BULWARK, MoveId.EARTH_POWER, MoveId.FIERY_DANCE, MoveId.ELECTRO_DRIFT ],
[SpeciesId.TOTODILE]: [ MoveId.THUNDER_FANG, MoveId.DRAGON_DANCE, MoveId.DRAIN_PUNCH, MoveId.SURGING_STRIKES ],
[SpeciesId.SENTRET]: [ MoveId.TIDY_UP, MoveId.FAKE_OUT, MoveId.NUZZLE, MoveId.EXTREME_SPEED ],
[SpeciesId.HOOTHOOT]: [ MoveId.TAKE_HEART, MoveId.ESPER_WING, MoveId.AEROBLAST, MoveId.BOOMBURST ],
[SpeciesId.LEDYBA]: [ MoveId.POLLEN_PUFF, MoveId.MAT_BLOCK, MoveId.PARTING_SHOT, MoveId.SPORE ],
@ -97,7 +97,7 @@ export const speciesEggMoves = {
[SpeciesId.MISDREAVUS]: [ MoveId.TAKE_HEART, MoveId.MOONBLAST, MoveId.AURA_SPHERE, MoveId.MOONGEIST_BEAM ],
[SpeciesId.UNOWN]: [ MoveId.NATURE_POWER, MoveId.COSMIC_POWER, MoveId.ANCIENT_POWER, MoveId.MYSTICAL_POWER ],
[SpeciesId.GIRAFARIG]: [ MoveId.MYSTICAL_POWER, MoveId.NIGHT_DAZE, MoveId.RECOVER, MoveId.BOOMBURST ],
[SpeciesId.PINECO]: [ MoveId.METAL_BURST, MoveId.SHORE_UP, MoveId.BODY_PRESS, MoveId.DIAMOND_STORM ],
[SpeciesId.PINECO]: [ MoveId.METAL_BURST, MoveId.RECOVER, MoveId.LEECH_LIFE, MoveId.SPIN_OUT ],
[SpeciesId.DUNSPARCE]: [ MoveId.WICKED_TORQUE, MoveId.MAGICAL_TORQUE, MoveId.BLAZING_TORQUE, MoveId.EXTREME_SPEED ],
[SpeciesId.GLIGAR]: [ MoveId.FLOATY_FALL, MoveId.THOUSAND_WAVES, MoveId.SPIKY_SHIELD, MoveId.MIGHTY_CLEAVE ],
[SpeciesId.SNUBBULL]: [ MoveId.FACADE, MoveId.HIGH_HORSEPOWER, MoveId.SWORDS_DANCE, MoveId.EXTREME_SPEED ],
@ -111,12 +111,12 @@ export const speciesEggMoves = {
[SpeciesId.CORSOLA]: [ MoveId.SCALD, MoveId.FREEZE_DRY, MoveId.STRENGTH_SAP, MoveId.SALT_CURE ],
[SpeciesId.REMORAID]: [ MoveId.WATER_SHURIKEN, MoveId.TAKE_HEART, MoveId.SHELL_SIDE_ARM, MoveId.BOUNCY_BUBBLE ],
[SpeciesId.DELIBIRD]: [ MoveId.BONEMERANG, MoveId.FLOATY_FALL, MoveId.VICTORY_DANCE, MoveId.GLACIAL_LANCE ],
[SpeciesId.SKARMORY]: [ MoveId.ROOST, MoveId.BODY_PRESS, MoveId.SPIKY_SHIELD, MoveId.BEAK_BLAST ],
[SpeciesId.SKARMORY]: [ MoveId.ROOST, MoveId.BODY_PRESS, MoveId.CEASELESS_EDGE, MoveId.BEAK_BLAST ],
[SpeciesId.HOUNDOUR]: [ MoveId.FIERY_WRATH, MoveId.THUNDERBOLT, MoveId.MOONBLAST, MoveId.ARMOR_CANNON ],
[SpeciesId.PHANPY]: [ MoveId.SHORE_UP, MoveId.SWORDS_DANCE, MoveId.MOUNTAIN_GALE, MoveId.COLLISION_COURSE ],
[SpeciesId.STANTLER]: [ MoveId.THUNDEROUS_KICK, MoveId.PHOTON_GEYSER, MoveId.SWORDS_DANCE, MoveId.BOOMBURST ],
[SpeciesId.SMEARGLE]: [ MoveId.CONVERSION, MoveId.BURNING_BULWARK, MoveId.SALT_CURE, MoveId.DARK_VOID ],
[SpeciesId.TYROGUE]: [ MoveId.VICTORY_DANCE, MoveId.THUNDEROUS_KICK, MoveId.METEOR_MASH, MoveId.WICKED_BLOW ],
[SpeciesId.TYROGUE]: [ MoveId.DARKEST_LARIAT, MoveId.THUNDEROUS_KICK, MoveId.METEOR_MASH, MoveId.VICTORY_DANCE ],
[SpeciesId.SMOOCHUM]: [ MoveId.LUSTER_PURGE, MoveId.AURA_SPHERE, MoveId.FREEZE_DRY, MoveId.QUIVER_DANCE ],
[SpeciesId.ELEKID]: [ MoveId.FIRE_LASH, MoveId.ZING_ZAP, MoveId.MOUNTAIN_GALE, MoveId.SHIFT_GEAR ],
[SpeciesId.MAGBY]: [ MoveId.THUNDERCLAP, MoveId.EARTH_POWER, MoveId.ENERGY_BALL, MoveId.BLUE_FLARE ],
@ -142,7 +142,7 @@ export const speciesEggMoves = {
[SpeciesId.RALTS]: [ MoveId.PSYBLADE, MoveId.BITTER_BLADE, MoveId.NO_RETREAT, MoveId.BOOMBURST ],
[SpeciesId.SURSKIT]: [ MoveId.POLLEN_PUFF, MoveId.FIERY_DANCE, MoveId.BOUNCY_BUBBLE, MoveId.AEROBLAST ],
[SpeciesId.SHROOMISH]: [ MoveId.ACCELEROCK, MoveId.TRAILBLAZE, MoveId.STORM_THROW, MoveId.SAPPY_SEED ],
[SpeciesId.SLAKOTH]: [ MoveId.FACADE, MoveId.DRAIN_PUNCH, MoveId.KNOCK_OFF, MoveId.SKILL_SWAP ],
[SpeciesId.SLAKOTH]: [ MoveId.CRUSH_GRIP, MoveId.DRAIN_PUNCH, MoveId.PARTING_SHOT, MoveId.SKILL_SWAP ],
[SpeciesId.NINCADA]: [ MoveId.BULLDOZE, MoveId.STICKY_WEB, MoveId.SHADOW_BONE, MoveId.SHELL_SMASH ],
[SpeciesId.WHISMUR]: [ MoveId.ALLURING_VOICE, MoveId.SHIFT_GEAR, MoveId.SPARKLING_ARIA, MoveId.TORCH_SONG ],
[SpeciesId.MAKUHITA]: [ MoveId.COMBAT_TORQUE, MoveId.SLACK_OFF, MoveId.HEAT_CRASH, MoveId.DOUBLE_IRON_BASH ],
@ -167,7 +167,7 @@ export const speciesEggMoves = {
[SpeciesId.SPINDA]: [ MoveId.SUPERPOWER, MoveId.SLACK_OFF, MoveId.FLEUR_CANNON, MoveId.V_CREATE ],
[SpeciesId.TRAPINCH]: [ MoveId.FIRE_LASH, MoveId.DRAGON_DARTS, MoveId.THOUSAND_ARROWS, MoveId.DRAGON_ENERGY ],
[SpeciesId.CACNEA]: [ MoveId.EARTH_POWER, MoveId.CEASELESS_EDGE, MoveId.NIGHT_DAZE, MoveId.IVY_CUDGEL ],
[SpeciesId.SWABLU]: [ MoveId.ROOST, MoveId.NASTY_PLOT, MoveId.FLOATY_FALL, MoveId.BOOMBURST ],
[SpeciesId.SWABLU]: [ MoveId.ROOST, MoveId.TAKE_HEART, MoveId.AEROBLAST, MoveId.BOOMBURST ],
[SpeciesId.ZANGOOSE]: [ MoveId.FACADE, MoveId.HIGH_HORSEPOWER, MoveId.EXTREME_SPEED, MoveId.TIDY_UP ],
[SpeciesId.SEVIPER]: [ MoveId.ICE_BEAM, MoveId.BITTER_BLADE, MoveId.SUCKER_PUNCH, MoveId.NO_RETREAT ],
[SpeciesId.LUNATONE]: [ MoveId.REVELATION_DANCE, MoveId.MOONGEIST_BEAM, MoveId.SHELL_SMASH, MoveId.LUMINA_CRASH ],
@ -203,13 +203,13 @@ export const speciesEggMoves = {
[SpeciesId.JIRACHI]: [ MoveId.TACHYON_CUTTER, MoveId.TRIPLE_ARROWS, MoveId.ROCK_SLIDE, MoveId.SHELL_SMASH ],
[SpeciesId.DEOXYS]: [ MoveId.COLLISION_COURSE, MoveId.FUSION_FLARE, MoveId.PARTING_SHOT, MoveId.LUMINA_CRASH ],
[SpeciesId.TURTWIG]: [ MoveId.SHELL_SMASH, MoveId.MIGHTY_CLEAVE, MoveId.ICE_SPINNER, MoveId.SAPPY_SEED ],
[SpeciesId.TURTWIG]: [ MoveId.SHELL_SMASH, MoveId.STONE_AXE, MoveId.ICE_SPINNER, MoveId.SAPPY_SEED ],
[SpeciesId.CHIMCHAR]: [ MoveId.THUNDERBOLT, MoveId.SECRET_SWORD, MoveId.TRIPLE_AXEL, MoveId.SACRED_FIRE ],
[SpeciesId.PIPLUP]: [ MoveId.KINGS_SHIELD, MoveId.TACHYON_CUTTER, MoveId.FREEZE_DRY, MoveId.STEAM_ERUPTION ],
[SpeciesId.STARLY]: [ MoveId.SWORDS_DANCE, MoveId.HEAD_CHARGE, MoveId.FLARE_BLITZ, MoveId.EXTREME_SPEED ],
[SpeciesId.BIDOOF]: [ MoveId.EXTREME_SPEED, MoveId.COSMIC_POWER, MoveId.POWER_TRIP, MoveId.AQUA_STEP ],
[SpeciesId.KRICKETOT]: [ MoveId.BONEMERANG, MoveId.VICTORY_DANCE, MoveId.STONE_AXE, MoveId.POPULATION_BOMB ],
[SpeciesId.SHINX]: [ MoveId.FIRE_LASH, MoveId.TRIPLE_AXEL, MoveId.ZIPPY_ZAP, MoveId.BOLT_STRIKE ],
[SpeciesId.SHINX]: [ MoveId.THUNDEROUS_KICK, MoveId.TRIPLE_AXEL, MoveId.ZIPPY_ZAP, MoveId.BOLT_STRIKE ],
[SpeciesId.BUDEW]: [ MoveId.FIERY_DANCE, MoveId.ACID_SPRAY, MoveId.BOUNCY_BUBBLE, MoveId.QUIVER_DANCE ],
[SpeciesId.CRANIDOS]: [ MoveId.VOLT_TACKLE, MoveId.ACCELEROCK, MoveId.FLARE_BLITZ, MoveId.SHIFT_GEAR ],
[SpeciesId.SHIELDON]: [ MoveId.SHORE_UP, MoveId.BODY_PRESS, MoveId.KINGS_SHIELD, MoveId.DIAMOND_STORM ],
@ -253,14 +253,14 @@ export const speciesEggMoves = {
[SpeciesId.PHIONE]: [ MoveId.BOUNCY_BUBBLE, MoveId.FREEZE_DRY, MoveId.STORED_POWER, MoveId.ORIGIN_PULSE ],
[SpeciesId.MANAPHY]: [ MoveId.BOUNCY_BUBBLE, MoveId.FROST_BREATH, MoveId.WILDBOLT_STORM, MoveId.ORIGIN_PULSE ],
[SpeciesId.DARKRAI]: [ MoveId.FIERY_WRATH, MoveId.MOONBLAST, MoveId.FIERY_DANCE, MoveId.MAKE_IT_RAIN ],
[SpeciesId.SHAYMIN]: [ MoveId.MATCHA_GOTCHA, MoveId.FIERY_DANCE, MoveId.AEROBLAST, MoveId.QUIVER_DANCE ],
[SpeciesId.SHAYMIN]: [ MoveId.MATCHA_GOTCHA, MoveId.HEAT_WAVE, MoveId.AEROBLAST, MoveId.QUIVER_DANCE ],
[SpeciesId.ARCEUS]: [ MoveId.NO_RETREAT, MoveId.COLLISION_COURSE, MoveId.ASTRAL_BARRAGE, MoveId.MULTI_ATTACK ],
[SpeciesId.VICTINI]: [ MoveId.BLUE_FLARE, MoveId.BOLT_STRIKE, MoveId.LUSTER_PURGE, MoveId.VICTORY_DANCE ],
[SpeciesId.SNIVY]: [ MoveId.FLAMETHROWER, MoveId.CLANGING_SCALES, MoveId.MAKE_IT_RAIN, MoveId.FLEUR_CANNON ],
[SpeciesId.TEPIG]: [ MoveId.WAVE_CRASH, MoveId.VOLT_TACKLE, MoveId.AXE_KICK, MoveId.VICTORY_DANCE ],
[SpeciesId.OSHAWOTT]: [ MoveId.FREEZE_DRY, MoveId.SHELL_SIDE_ARM, MoveId.SACRED_SWORD, MoveId.SHELL_SMASH ],
[SpeciesId.PATRAT]: [ MoveId.FAKE_OUT, MoveId.SWORDS_DANCE, MoveId.DYNAMIC_PUNCH, MoveId.EXTREME_SPEED ],
[SpeciesId.PATRAT]: [ MoveId.FAKE_OUT, MoveId.INSTRUCT, MoveId.DYNAMIC_PUNCH, MoveId.EXTREME_SPEED ],
[SpeciesId.LILLIPUP]: [ MoveId.CLOSE_COMBAT, MoveId.BODY_SLAM, MoveId.HIGH_HORSEPOWER, MoveId.LAST_RESPECTS ],
[SpeciesId.PURRLOIN]: [ MoveId.ENCORE, MoveId.OBSTRUCT, MoveId.PARTING_SHOT, MoveId.WICKED_BLOW ],
[SpeciesId.PANSAGE]: [ MoveId.SWORDS_DANCE, MoveId.FIRE_LASH, MoveId.EARTHQUAKE, MoveId.IVY_CUDGEL ],
@ -269,7 +269,7 @@ export const speciesEggMoves = {
[SpeciesId.MUNNA]: [ MoveId.COSMIC_POWER, MoveId.AURA_SPHERE, MoveId.LUNAR_BLESSING, MoveId.MYSTICAL_POWER ],
[SpeciesId.PIDOVE]: [ MoveId.SLASH, MoveId.TIDY_UP, MoveId.FLOATY_FALL, MoveId.TRIPLE_ARROWS ],
[SpeciesId.BLITZLE]: [ MoveId.HORN_LEECH, MoveId.SWORDS_DANCE, MoveId.FLARE_BLITZ, MoveId.BOLT_STRIKE ],
[SpeciesId.ROGGENROLA]: [ MoveId.BODY_PRESS, MoveId.CURSE, MoveId.SHORE_UP, MoveId.DIAMOND_STORM ],
[SpeciesId.ROGGENROLA]: [ MoveId.BODY_PRESS, MoveId.SPIKY_SHIELD, MoveId.SHORE_UP, MoveId.DIAMOND_STORM ],
[SpeciesId.WOOBAT]: [ MoveId.ESPER_WING, MoveId.STORED_POWER, MoveId.MYSTICAL_FIRE, MoveId.OBLIVION_WING ],
[SpeciesId.DRILBUR]: [ MoveId.METEOR_MASH, MoveId.ICE_SPINNER, MoveId.SHIFT_GEAR, MoveId.THOUSAND_ARROWS ],
[SpeciesId.AUDINO]: [ MoveId.TAKE_HEART, MoveId.MOONBLAST, MoveId.WISH, MoveId.MATCHA_GOTCHA ],
@ -298,7 +298,7 @@ export const speciesEggMoves = {
[SpeciesId.SOLOSIS]: [ MoveId.MIST_BALL, MoveId.SPEED_SWAP, MoveId.FLAMETHROWER, MoveId.LIGHT_OF_RUIN ],
[SpeciesId.DUCKLETT]: [ MoveId.SPLISHY_SPLASH, MoveId.SANDSEAR_STORM, MoveId.WILDBOLT_STORM, MoveId.QUIVER_DANCE ],
[SpeciesId.VANILLITE]: [ MoveId.EARTH_POWER, MoveId.AURORA_VEIL, MoveId.CALM_MIND, MoveId.SPARKLY_SWIRL ],
[SpeciesId.DEERLING]: [ MoveId.TIDY_UP, MoveId.HEADBUTT, MoveId.COMBAT_TORQUE, MoveId.FLOWER_TRICK ],
[SpeciesId.DEERLING]: [ MoveId.TIDY_UP, MoveId.HEADBUTT, MoveId.AXE_KICK, MoveId.FLOWER_TRICK ],
[SpeciesId.EMOLGA]: [ MoveId.ICICLE_CRASH, MoveId.ZING_ZAP, MoveId.FLOATY_FALL, MoveId.ELECTRIFY ],
[SpeciesId.KARRABLAST]: [ MoveId.LEECH_LIFE, MoveId.BITTER_BLADE, MoveId.OBSTRUCT, MoveId.DOUBLE_IRON_BASH ],
[SpeciesId.FOONGUS]: [ MoveId.POLLEN_PUFF, MoveId.PARTING_SHOT, MoveId.FOUL_PLAY, MoveId.SAPPY_SEED ],
@ -322,8 +322,8 @@ export const speciesEggMoves = {
[SpeciesId.BOUFFALANT]: [ MoveId.HORN_LEECH, MoveId.HIGH_JUMP_KICK, MoveId.HEAD_SMASH, MoveId.FLARE_BLITZ ],
[SpeciesId.RUFFLET]: [ MoveId.FLOATY_FALL, MoveId.AURA_SPHERE, MoveId.NO_RETREAT, MoveId.BOLT_BEAK ],
[SpeciesId.VULLABY]: [ MoveId.FOUL_PLAY, MoveId.BODY_PRESS, MoveId.ROOST, MoveId.RUINATION ],
[SpeciesId.HEATMOR]: [ MoveId.EARTH_POWER, MoveId.OVERHEAT, MoveId.THUNDERBOLT, MoveId.V_CREATE ],
[SpeciesId.DURANT]: [ MoveId.HIGH_HORSEPOWER, MoveId.FIRST_IMPRESSION, MoveId.SWORDS_DANCE, MoveId.BEHEMOTH_BASH ],
[SpeciesId.HEATMOR]: [ MoveId.EARTH_POWER, MoveId.OVERHEAT, MoveId.SUPERCELL_SLAM, MoveId.V_CREATE ],
[SpeciesId.DURANT]: [ MoveId.HIGH_HORSEPOWER, MoveId.FIRST_IMPRESSION, MoveId.U_TURN, MoveId.BEHEMOTH_BASH ],
[SpeciesId.DEINO]: [ MoveId.FIERY_WRATH, MoveId.ESPER_WING, MoveId.SLUDGE_BOMB, MoveId.FICKLE_BEAM ],
[SpeciesId.LARVESTA]: [ MoveId.THUNDERBOLT, MoveId.DAZZLING_GLEAM, MoveId.EARTH_POWER, MoveId.HYDRO_STEAM ],
[SpeciesId.COBALION]: [ MoveId.BEHEMOTH_BLADE, MoveId.MIGHTY_CLEAVE, MoveId.CEASELESS_EDGE, MoveId.VICTORY_DANCE ],
@ -381,8 +381,8 @@ export const speciesEggMoves = {
[SpeciesId.ROWLET]: [ MoveId.THOUSAND_ARROWS, MoveId.SHADOW_BONE, MoveId.FIRST_IMPRESSION, MoveId.VICTORY_DANCE ],
[SpeciesId.LITTEN]: [ MoveId.SUCKER_PUNCH, MoveId.PARTING_SHOT, MoveId.SLACK_OFF, MoveId.SACRED_FIRE ],
[SpeciesId.POPPLIO]: [ MoveId.PSYCHIC_NOISE, MoveId.MOONLIGHT, MoveId.OVERDRIVE, MoveId.TORCH_SONG ],
[SpeciesId.PIKIPEK]: [ MoveId.DUAL_WINGBEAT, MoveId.BONE_RUSH, MoveId.BURNING_BULWARK, MoveId.POPULATION_BOMB ],
[SpeciesId.YUNGOOS]: [ MoveId.EXTREME_SPEED, MoveId.KNOCK_OFF, MoveId.TIDY_UP, MoveId.MULTI_ATTACK ],
[SpeciesId.PIKIPEK]: [ MoveId.TRAILBLAZE, MoveId.BONE_RUSH, MoveId.BURNING_BULWARK, MoveId.POPULATION_BOMB ],
[SpeciesId.YUNGOOS]: [ MoveId.FAKE_OUT, MoveId.HIGH_HORSEPOWER, MoveId.TIDY_UP, MoveId.EXTREME_SPEED ],
[SpeciesId.GRUBBIN]: [ MoveId.ICE_BEAM, MoveId.EARTH_POWER, MoveId.CALM_MIND, MoveId.THUNDERCLAP ],
[SpeciesId.CRABRAWLER]: [ MoveId.JET_PUNCH, MoveId.SHORE_UP, MoveId.MACH_PUNCH, MoveId.SURGING_STRIKES ],
[SpeciesId.ORICORIO]: [ MoveId.QUIVER_DANCE, MoveId.FIERY_DANCE, MoveId.THUNDERCLAP, MoveId.OBLIVION_WING ],
@ -396,7 +396,7 @@ export const speciesEggMoves = {
[SpeciesId.MORELULL]: [ MoveId.CALM_MIND, MoveId.SAPPY_SEED, MoveId.DRAINING_KISS, MoveId.MATCHA_GOTCHA ],
[SpeciesId.SALANDIT]: [ MoveId.SCALD, MoveId.MALIGNANT_CHAIN, MoveId.CORE_ENFORCER, MoveId.ERUPTION ],
[SpeciesId.STUFFUL]: [ MoveId.DRAIN_PUNCH, MoveId.METEOR_MASH, MoveId.TRIPLE_AXEL, MoveId.RAGE_FIST ],
[SpeciesId.BOUNSWEET]: [ MoveId.TRIPLE_AXEL, MoveId.AQUA_STEP, MoveId.THUNDEROUS_KICK, MoveId.SAPPY_SEED ],
[SpeciesId.BOUNSWEET]: [ MoveId.TRIPLE_AXEL, MoveId.AQUA_STEP, MoveId.THUNDEROUS_KICK, MoveId.FLOWER_TRICK ],
[SpeciesId.COMFEY]: [ MoveId.REVIVAL_BLESSING, MoveId.TAKE_HEART, MoveId.STRENGTH_SAP, MoveId.MATCHA_GOTCHA ],
[SpeciesId.ORANGURU]: [ MoveId.JUNGLE_HEALING, MoveId.YAWN, MoveId.FOLLOW_ME, MoveId.LUMINA_CRASH ],
[SpeciesId.PASSIMIAN]: [ MoveId.PYRO_BALL, MoveId.SUCKER_PUNCH, MoveId.ZING_ZAP, MoveId.VICTORY_DANCE ],
@ -423,7 +423,7 @@ export const speciesEggMoves = {
[SpeciesId.PHEROMOSA]: [ MoveId.SECRET_SWORD, MoveId.MAKE_IT_RAIN, MoveId.ATTACK_ORDER, MoveId.DIAMOND_STORM ],
[SpeciesId.XURKITREE]: [ MoveId.FLAMETHROWER, MoveId.GIGA_DRAIN, MoveId.TAIL_GLOW, MoveId.THUNDERCLAP ],
[SpeciesId.CELESTEELA]: [ MoveId.RECOVER, MoveId.BUZZY_BUZZ, MoveId.EARTH_POWER, MoveId.OBLIVION_WING ],
[SpeciesId.KARTANA]: [ MoveId.MIGHTY_CLEAVE, MoveId.DUAL_CHOP, MoveId.BITTER_BLADE, MoveId.BEHEMOTH_BLADE ],
[SpeciesId.KARTANA]: [ MoveId.MIGHTY_CLEAVE, MoveId.DUAL_CHOP, MoveId.BEHEMOTH_BLADE, MoveId.BITTER_BLADE ],
[SpeciesId.GUZZLORD]: [ MoveId.SUCKER_PUNCH, MoveId.COMEUPPANCE, MoveId.SLACK_OFF, MoveId.SHED_TAIL ],
[SpeciesId.NECROZMA]: [ MoveId.DYNAMAX_CANNON, MoveId.SACRED_FIRE, MoveId.ASTRAL_BARRAGE, MoveId.CLANGOROUS_SOUL ],
[SpeciesId.MAGEARNA]: [ MoveId.STRENGTH_SAP, MoveId.EARTH_POWER, MoveId.MOONBLAST, MoveId.MAKE_IT_RAIN ],
@ -431,23 +431,23 @@ export const speciesEggMoves = {
[SpeciesId.POIPOLE]: [ MoveId.MALIGNANT_CHAIN, MoveId.ICE_BEAM, MoveId.ARMOR_CANNON, MoveId.CLANGING_SCALES ],
[SpeciesId.STAKATAKA]: [ MoveId.HEAVY_SLAM, MoveId.SHORE_UP, MoveId.CURSE, MoveId.SALT_CURE ],
[SpeciesId.BLACEPHALON]: [ MoveId.STEEL_BEAM, MoveId.MOONBLAST, MoveId.CHLOROBLAST, MoveId.MOONGEIST_BEAM ],
[SpeciesId.ZERAORA]: [ MoveId.SWORDS_DANCE, MoveId.U_TURN, MoveId.COLLISION_COURSE, MoveId.TRIPLE_AXEL ],
[SpeciesId.ZERAORA]: [ MoveId.SWORDS_DANCE, MoveId.FIRE_LASH, MoveId.COLLISION_COURSE, MoveId.TRIPLE_AXEL ],
[SpeciesId.MELTAN]: [ MoveId.BULLET_PUNCH, MoveId.DRAIN_PUNCH, MoveId.BULK_UP, MoveId.PLASMA_FISTS ],
[SpeciesId.ALOLA_RATTATA]: [ MoveId.FALSE_SURRENDER, MoveId.PSYCHIC_FANGS, MoveId.COIL, MoveId.EXTREME_SPEED ],
[SpeciesId.ALOLA_SANDSHREW]: [ MoveId.SPIKY_SHIELD, MoveId.LIQUIDATION, MoveId.SHIFT_GEAR, MoveId.GLACIAL_LANCE ],
[SpeciesId.ALOLA_VULPIX]: [ MoveId.MOONBLAST, MoveId.GLARE, MoveId.MYSTICAL_FIRE, MoveId.REVIVAL_BLESSING ],
[SpeciesId.ALOLA_VULPIX]: [ MoveId.MOONBLAST, MoveId.GLARE, MoveId.MYSTICAL_FIRE, MoveId.LUNAR_BLESSING ],
[SpeciesId.ALOLA_DIGLETT]: [ MoveId.THOUSAND_WAVES, MoveId.SWORDS_DANCE, MoveId.TRIPLE_DIVE, MoveId.PYRO_BALL ],
[SpeciesId.ALOLA_MEOWTH]: [ MoveId.BADDY_BAD, MoveId.BUZZY_BUZZ, MoveId.PARTING_SHOT, MoveId.MAKE_IT_RAIN ],
[SpeciesId.ALOLA_GEODUDE]: [ MoveId.THOUSAND_WAVES, MoveId.BULK_UP, MoveId.STONE_AXE, MoveId.EXTREME_SPEED ],
[SpeciesId.ALOLA_GEODUDE]: [ MoveId.LANDS_WRATH, MoveId.FUSION_BOLT, MoveId.STONE_AXE, MoveId.EXTREME_SPEED ],
[SpeciesId.ALOLA_GRIMER]: [ MoveId.SUCKER_PUNCH, MoveId.BARB_BARRAGE, MoveId.RECOVER, MoveId.SURGING_STRIKES ],
[SpeciesId.GROOKEY]: [ MoveId.ROCK_SLIDE, MoveId.PLAY_ROUGH, MoveId.GRASSY_GLIDE, MoveId.CLANGOROUS_SOUL ],
[SpeciesId.SCORBUNNY]: [ MoveId.EXTREME_SPEED, MoveId.HIGH_JUMP_KICK, MoveId.TRIPLE_AXEL, MoveId.BOLT_STRIKE ],
[SpeciesId.SCORBUNNY]: [ MoveId.EXTREME_SPEED, MoveId.HIGH_JUMP_KICK, MoveId.SUPERCELL_SLAM, MoveId.TRIPLE_AXEL ],
[SpeciesId.SOBBLE]: [ MoveId.AEROBLAST, MoveId.FROST_BREATH, MoveId.ENERGY_BALL, MoveId.NASTY_PLOT ],
[SpeciesId.SKWOVET]: [ MoveId.SUCKER_PUNCH, MoveId.SLACK_OFF, MoveId.COIL, MoveId.POPULATION_BOMB ],
[SpeciesId.ROOKIDEE]: [ MoveId.ROOST, MoveId.BODY_PRESS, MoveId.KINGS_SHIELD, MoveId.BEHEMOTH_BASH ],
[SpeciesId.BLIPBUG]: [ MoveId.HEAL_ORDER, MoveId.LUSTER_PURGE, MoveId.SLEEP_POWDER, MoveId.TAIL_GLOW ],
[SpeciesId.NICKIT]: [ MoveId.BADDY_BAD, MoveId.FLAMETHROWER, MoveId.SPARKLY_SWIRL, MoveId.MAKE_IT_RAIN ],
[SpeciesId.NICKIT]: [ MoveId.BADDY_BAD, MoveId.MYSTICAL_FIRE, MoveId.SPARKLY_SWIRL, MoveId.MAKE_IT_RAIN ],
[SpeciesId.GOSSIFLEUR]: [ MoveId.PARTING_SHOT, MoveId.STRENGTH_SAP, MoveId.SAPPY_SEED, MoveId.SEED_FLARE ],
[SpeciesId.WOOLOO]: [ MoveId.NUZZLE, MoveId.MILK_DRINK, MoveId.BODY_PRESS, MoveId.MULTI_ATTACK ],
[SpeciesId.CHEWTLE]: [ MoveId.ICE_FANG, MoveId.PSYCHIC_FANGS, MoveId.SHELL_SMASH, MoveId.MIGHTY_CLEAVE ],
@ -467,7 +467,7 @@ export const speciesEggMoves = {
[SpeciesId.FALINKS]: [ MoveId.BATON_PASS, MoveId.POWER_TRIP, MoveId.COMBAT_TORQUE, MoveId.HEAL_ORDER ],
[SpeciesId.PINCURCHIN]: [ MoveId.TRICK_ROOM, MoveId.VOLT_SWITCH, MoveId.STRENGTH_SAP, MoveId.THUNDERCLAP ],
[SpeciesId.SNOM]: [ MoveId.FROST_BREATH, MoveId.HEAL_ORDER, MoveId.EARTH_POWER, MoveId.SPORE ],
[SpeciesId.STONJOURNER]: [ MoveId.BODY_PRESS, MoveId.HELPING_HAND, MoveId.ACCELEROCK, MoveId.DIAMOND_STORM ],
[SpeciesId.STONJOURNER]: [ MoveId.AXE_KICK, MoveId.HELPING_HAND, MoveId.ACCELEROCK, MoveId.DIAMOND_STORM ],
[SpeciesId.EISCUE]: [ MoveId.TRIPLE_AXEL, MoveId.AQUA_STEP, MoveId.AXE_KICK, MoveId.SHELL_SMASH ],
[SpeciesId.INDEEDEE]: [ MoveId.MATCHA_GOTCHA, MoveId.EXPANDING_FORCE, MoveId.MOONBLAST, MoveId.REVIVAL_BLESSING ],
[SpeciesId.MORPEKO]: [ MoveId.TRIPLE_AXEL, MoveId.OBSTRUCT, MoveId.SWORDS_DANCE, MoveId.COLLISION_COURSE ],
@ -478,8 +478,8 @@ export const speciesEggMoves = {
[SpeciesId.ARCTOVISH]: [ MoveId.ICE_FANG, MoveId.THUNDER_FANG, MoveId.HIGH_HORSEPOWER, MoveId.SHIFT_GEAR ],
[SpeciesId.DURALUDON]: [ MoveId.CORE_ENFORCER, MoveId.BODY_PRESS, MoveId.RECOVER, MoveId.TACHYON_CUTTER ],
[SpeciesId.DREEPY]: [ MoveId.SHADOW_BONE, MoveId.POWER_UP_PUNCH, MoveId.FIRE_LASH, MoveId.DIRE_CLAW ],
[SpeciesId.ZACIAN]: [ MoveId.MAGICAL_TORQUE, MoveId.MIGHTY_CLEAVE, MoveId.BITTER_BLADE, MoveId.PRECIPICE_BLADES ],
[SpeciesId.ZAMAZENTA]: [ MoveId.BULK_UP, MoveId.BODY_PRESS, MoveId.SLACK_OFF, MoveId.DIAMOND_STORM ],
[SpeciesId.ZACIAN]: [ MoveId.MAGICAL_TORQUE, MoveId.MIGHTY_CLEAVE, MoveId.EARTHQUAKE, MoveId.BITTER_BLADE ],
[SpeciesId.ZAMAZENTA]: [ MoveId.BULK_UP, MoveId.BODY_PRESS, MoveId.POWER_TRIP, MoveId.SLACK_OFF ],
[SpeciesId.ETERNATUS]: [ MoveId.BODY_PRESS, MoveId.NASTY_PLOT, MoveId.MALIGNANT_CHAIN, MoveId.DRAGON_ENERGY ],
[SpeciesId.KUBFU]: [ MoveId.METEOR_MASH, MoveId.DRAIN_PUNCH, MoveId.JET_PUNCH, MoveId.DRAGON_DANCE ],
[SpeciesId.ZARUDE]: [ MoveId.SAPPY_SEED, MoveId.MIGHTY_CLEAVE, MoveId.WICKED_BLOW, MoveId.VICTORY_DANCE ],
@ -511,18 +511,18 @@ export const speciesEggMoves = {
[SpeciesId.FUECOCO]: [ MoveId.ALLURING_VOICE, MoveId.SLACK_OFF, MoveId.OVERDRIVE, MoveId.MOONGEIST_BEAM ],
[SpeciesId.QUAXLY]: [ MoveId.DRAGON_DANCE, MoveId.TRIPLE_AXEL, MoveId.POWER_TRIP, MoveId.THUNDEROUS_KICK ],
[SpeciesId.LECHONK]: [ MoveId.MILK_DRINK, MoveId.PSYSHIELD_BASH, MoveId.BLAZING_TORQUE, MoveId.FILLET_AWAY ],
[SpeciesId.TAROUNTULA]: [ MoveId.STONE_AXE, MoveId.LEECH_LIFE, MoveId.THIEF, MoveId.SPORE ],
[SpeciesId.TAROUNTULA]: [ MoveId.STONE_AXE, MoveId.LEECH_LIFE, MoveId.FAKE_OUT, MoveId.SPORE ],
[SpeciesId.NYMBLE]: [ MoveId.KNOCK_OFF, MoveId.FELL_STINGER, MoveId.ATTACK_ORDER, MoveId.WICKED_BLOW ],
[SpeciesId.PAWMI]: [ MoveId.DRAIN_PUNCH, MoveId.METEOR_MASH, MoveId.JET_PUNCH, MoveId.PLASMA_FISTS ],
[SpeciesId.TANDEMAUS]: [ MoveId.BATON_PASS, MoveId.COVET, MoveId.SIZZLY_SLIDE, MoveId.REVIVAL_BLESSING ],
[SpeciesId.FIDOUGH]: [ MoveId.SOFT_BOILED, MoveId.HIGH_HORSEPOWER, MoveId.SIZZLY_SLIDE, MoveId.TIDY_UP ],
[SpeciesId.SMOLIV]: [ MoveId.STRENGTH_SAP, MoveId.EARTH_POWER, MoveId.CALM_MIND, MoveId.BOOMBURST ],
[SpeciesId.SQUAWKABILLY]: [ MoveId.PARTING_SHOT, MoveId.EARTHQUAKE, MoveId.FLARE_BLITZ, MoveId.EXTREME_SPEED ],
[SpeciesId.NACLI]: [ MoveId.BODY_PRESS, MoveId.TOXIC, MoveId.CURSE, MoveId.DIAMOND_STORM ],
[SpeciesId.NACLI]: [ MoveId.KNOCK_OFF, MoveId.TOXIC, MoveId.SAND_TOMB, MoveId.DIAMOND_STORM ],
[SpeciesId.CHARCADET]: [ MoveId.SACRED_SWORD, MoveId.PHOTON_GEYSER, MoveId.MOONBLAST, MoveId.SPECTRAL_THIEF ],
[SpeciesId.TADBULB]: [ MoveId.PARABOLIC_CHARGE, MoveId.SCALD, MoveId.EARTH_POWER, MoveId.ELECTRO_SHOT ],
[SpeciesId.WATTREL]: [ MoveId.NASTY_PLOT, MoveId.SPLISHY_SPLASH, MoveId.SANDSEAR_STORM, MoveId.WILDBOLT_STORM ],
[SpeciesId.MASCHIFF]: [ MoveId.PARTING_SHOT, MoveId.COMBAT_TORQUE, MoveId.PSYCHIC_FANGS, MoveId.NO_RETREAT ],
[SpeciesId.MASCHIFF]: [ MoveId.PARTING_SHOT, MoveId.LEECH_LIFE, MoveId.PSYCHIC_FANGS, MoveId.NO_RETREAT ],
[SpeciesId.SHROODLE]: [ MoveId.GASTRO_ACID, MoveId.PARTING_SHOT, MoveId.TOXIC, MoveId.SKETCH ],
[SpeciesId.BRAMBLIN]: [ MoveId.TAILWIND, MoveId.STRENGTH_SAP, MoveId.FLOWER_TRICK, MoveId.LAST_RESPECTS ],
[SpeciesId.TOEDSCOOL]: [ MoveId.STRENGTH_SAP, MoveId.TOPSY_TURVY, MoveId.SAPPY_SEED, MoveId.TAIL_GLOW ],
@ -535,7 +535,7 @@ export const speciesEggMoves = {
[SpeciesId.BOMBIRDIER]: [ MoveId.FLOATY_FALL, MoveId.SWORDS_DANCE, MoveId.SUCKER_PUNCH, MoveId.MIGHTY_CLEAVE ],
[SpeciesId.FINIZEN]: [ MoveId.TRIPLE_AXEL, MoveId.DRAIN_PUNCH, MoveId.HEADLONG_RUSH, MoveId.SURGING_STRIKES ],
[SpeciesId.VAROOM]: [ MoveId.COMBAT_TORQUE, MoveId.U_TURN, MoveId.BLAZING_TORQUE, MoveId.NOXIOUS_TORQUE ],
[SpeciesId.CYCLIZAR]: [ MoveId.PARTING_SHOT, MoveId.FIRE_LASH, MoveId.MAGICAL_TORQUE, MoveId.GLAIVE_RUSH ],
[SpeciesId.CYCLIZAR]: [ MoveId.PARTING_SHOT, MoveId.FIRE_LASH, MoveId.HIGH_HORSEPOWER, MoveId.MAGICAL_TORQUE ],
[SpeciesId.ORTHWORM]: [ MoveId.SIZZLY_SLIDE, MoveId.COIL, MoveId.BODY_PRESS, MoveId.SHORE_UP ],
[SpeciesId.GLIMMET]: [ MoveId.CALM_MIND, MoveId.GIGA_DRAIN, MoveId.FIERY_DANCE, MoveId.MALIGNANT_CHAIN ],
[SpeciesId.GREAVARD]: [ MoveId.SHADOW_BONE, MoveId.SIZZLY_SLIDE, MoveId.SHORE_UP, MoveId.COLLISION_COURSE ],
@ -548,7 +548,7 @@ export const speciesEggMoves = {
[SpeciesId.SCREAM_TAIL]: [ MoveId.TORCH_SONG, MoveId.GLITZY_GLOW, MoveId.MOONLIGHT, MoveId.SPARKLY_SWIRL ],
[SpeciesId.BRUTE_BONNET]: [ MoveId.SAPPY_SEED, MoveId.STRENGTH_SAP, MoveId.EARTHQUAKE, MoveId.WICKED_BLOW ],
[SpeciesId.FLUTTER_MANE]: [ MoveId.MOONLIGHT, MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.MOONGEIST_BEAM ],
[SpeciesId.SLITHER_WING]: [ MoveId.MIGHTY_CLEAVE, MoveId.THUNDEROUS_KICK, MoveId.FIRE_LASH, MoveId.VICTORY_DANCE ],
[SpeciesId.SLITHER_WING]: [ MoveId.ROCK_SLIDE, MoveId.THUNDEROUS_KICK, MoveId.SUNSTEEL_STRIKE, MoveId.VICTORY_DANCE ],
[SpeciesId.SANDY_SHOCKS]: [ MoveId.MORNING_SUN, MoveId.ICE_BEAM, MoveId.NASTY_PLOT, MoveId.THUNDERCLAP ],
[SpeciesId.IRON_TREADS]: [ MoveId.FUSION_BOLT, MoveId.SHIFT_GEAR, MoveId.SHORE_UP, MoveId.SUNSTEEL_STRIKE ],
[SpeciesId.IRON_BUNDLE]: [ MoveId.EARTH_POWER, MoveId.SPLISHY_SPLASH, MoveId.VOLT_SWITCH, MoveId.NASTY_PLOT ],
@ -563,7 +563,7 @@ export const speciesEggMoves = {
[SpeciesId.TING_LU]: [ MoveId.SHORE_UP, MoveId.CEASELESS_EDGE, MoveId.SAPPY_SEED, MoveId.PRECIPICE_BLADES ],
[SpeciesId.CHI_YU]: [ MoveId.FIERY_WRATH, MoveId.HYDRO_STEAM, MoveId.MORNING_SUN, MoveId.BLUE_FLARE ],
[SpeciesId.ROARING_MOON]: [ MoveId.FIRE_LASH, MoveId.DRAGON_HAMMER, MoveId.METEOR_MASH, MoveId.DRAGON_ASCENT ],
[SpeciesId.IRON_VALIANT]: [ MoveId.PLASMA_FISTS, MoveId.NO_RETREAT, MoveId.SECRET_SWORD, MoveId.MAGICAL_TORQUE ],
[SpeciesId.IRON_VALIANT]: [ MoveId.PHOTON_GEYSER, MoveId.NO_RETREAT, MoveId.SECRET_SWORD, MoveId.MAGICAL_TORQUE ],
[SpeciesId.KORAIDON]: [ MoveId.SUNSTEEL_STRIKE, MoveId.SOLAR_BLADE, MoveId.DRAGON_DARTS, MoveId.BITTER_BLADE ],
[SpeciesId.MIRAIDON]: [ MoveId.FROST_BREATH, MoveId.WILDBOLT_STORM, MoveId.SPACIAL_REND, MoveId.RISING_VOLTAGE ],
[SpeciesId.WALKING_WAKE]: [ MoveId.BOUNCY_BUBBLE, MoveId.FUSION_FLARE, MoveId.SLUDGE_WAVE, MoveId.CORE_ENFORCER ],
@ -573,7 +573,7 @@ export const speciesEggMoves = {
[SpeciesId.MUNKIDORI]: [ MoveId.TWIN_BEAM, MoveId.HEAT_WAVE, MoveId.EARTH_POWER, MoveId.MALIGNANT_CHAIN ],
[SpeciesId.FEZANDIPITI]: [ MoveId.BARB_BARRAGE, MoveId.BONEMERANG, MoveId.TRIPLE_AXEL, MoveId.VICTORY_DANCE ],
[SpeciesId.OGERPON]: [ MoveId.SLEEP_POWDER, MoveId.BONEMERANG, MoveId.TRIPLE_AXEL, MoveId.FLOWER_TRICK ],
[SpeciesId.GOUGING_FIRE]: [ MoveId.EXTREME_SPEED, MoveId.BULK_UP, MoveId.SACRED_FIRE, MoveId.GLAIVE_RUSH ],
[SpeciesId.GOUGING_FIRE]: [ MoveId.EXTREME_SPEED, MoveId.DRAGON_DANCE, MoveId.ZING_ZAP, MoveId.SACRED_FIRE ],
[SpeciesId.RAGING_BOLT]: [ MoveId.NASTY_PLOT, MoveId.FLAMETHROWER, MoveId.MORNING_SUN, MoveId.ELECTRO_DRIFT ],
[SpeciesId.IRON_BOULDER]: [ MoveId.PSYBLADE, MoveId.KOWTOW_CLEAVE, MoveId.STONE_AXE, MoveId.BITTER_BLADE ],
[SpeciesId.IRON_CROWN]: [ MoveId.NASTY_PLOT, MoveId.SECRET_SWORD, MoveId.PSYSTRIKE, MoveId.ELECTRO_DRIFT ],
@ -584,17 +584,24 @@ export const speciesEggMoves = {
[SpeciesId.BLOODMOON_URSALUNA]: [ MoveId.NASTY_PLOT, MoveId.ROCK_POLISH, MoveId.SANDSEAR_STORM, MoveId.BOOMBURST ]
};
/**
* Parse a CSV-separated list of Egg Moves (such as one sourced from a Google Sheets)
* into code able to form the `speciesEggMoves` const object as above.
* @param content - The CSV-formatted string to convert into code.
*/
// TODO: Move this into the scripts folder and stop running it on initialization
function parseEggMoves(content: string): void {
let output = "";
const speciesNames = getEnumKeys(SpeciesId);
const speciesValues = getEnumValues(SpeciesId);
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
const lines = content.split(/\n/g);
for (const line of lines) {
const cols = line.split(",").slice(0, 5);
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_");
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_") as keyof typeof SpeciesId;
// TODO: This should use reverse mapping instead of `indexOf`
const species = speciesValues[speciesNames.indexOf(enumSpeciesName)];
const eggMoves: MoveId[] = [];
@ -602,14 +609,16 @@ function parseEggMoves(content: string): void {
for (let m = 0; m < 4; m++) {
const moveName = cols[m + 1].trim();
const moveIndex = moveName !== "N/A" ? moveNames.indexOf(moveName.toLowerCase()) : -1;
eggMoves.push(moveIndex > -1 ? moveIndex as MoveId : MoveId.NONE);
if (moveIndex === -1) {
console.warn(moveName, "could not be parsed");
}
eggMoves.push(moveIndex > -1 ? moveIndex as MoveId : MoveId.NONE);
}
if (eggMoves.find(m => m !== MoveId.NONE)) {
if (eggMoves.every(m => m === MoveId.NONE)) {
console.warn(`Species ${toReadableString(SpeciesId[species])} could not be parsed, excluding from output...`)
} else {
output += `[SpeciesId.${SpeciesId[species]}]: [ ${eggMoves.map(m => `MoveId.${MoveId[m]}`).join(", ")} ],\n`;
}
}

View File

@ -178,9 +178,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.QUILAVA]: { 0: AbilityId.DROUGHT },
[SpeciesId.TYPHLOSION]: { 0: AbilityId.DROUGHT },
[SpeciesId.HISUI_TYPHLOSION]: { 0: AbilityId.DROUGHT },
[SpeciesId.TOTODILE]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.CROCONAW]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.FERALIGATR]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.TOTODILE]: { 0: AbilityId.STRONG_JAW },
[SpeciesId.CROCONAW]: { 0: AbilityId.STRONG_JAW },
[SpeciesId.FERALIGATR]: { 0: AbilityId.STRONG_JAW },
[SpeciesId.SENTRET]: { 0: AbilityId.PICKUP },
[SpeciesId.FURRET]: { 0: AbilityId.PICKUP },
[SpeciesId.HOOTHOOT]: { 0: AbilityId.AERILATE },
@ -252,7 +252,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.REMORAID]: { 0: AbilityId.SIMPLE },
[SpeciesId.OCTILLERY]: { 0: AbilityId.SIMPLE },
[SpeciesId.DELIBIRD]: { 0: AbilityId.HUGE_POWER },
[SpeciesId.SKARMORY]: { 0: AbilityId.LIGHTNING_ROD },
[SpeciesId.SKARMORY]: { 0: AbilityId.STAMINA },
[SpeciesId.HOUNDOUR]: { 0: AbilityId.BALL_FETCH },
[SpeciesId.HOUNDOOM]: { 0: AbilityId.LIGHTNING_ROD, 1: AbilityId.LIGHTNING_ROD },
[SpeciesId.PHANPY]: { 0: AbilityId.STURDY },
@ -319,9 +319,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.MASQUERAIN]: { 0: AbilityId.WATER_BUBBLE },
[SpeciesId.SHROOMISH]: { 0: AbilityId.GUTS },
[SpeciesId.BRELOOM]: { 0: AbilityId.GUTS },
[SpeciesId.SLAKOTH]: { 0: AbilityId.GUTS },
[SpeciesId.VIGOROTH]: { 0: AbilityId.GUTS },
[SpeciesId.SLAKING]: { 0: AbilityId.GUTS },
[SpeciesId.SLAKOTH]: { 0: AbilityId.COMATOSE },
[SpeciesId.VIGOROTH]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.SLAKING]: { 0: AbilityId.COMATOSE },
[SpeciesId.NINCADA]: { 0: AbilityId.TECHNICIAN },
[SpeciesId.NINJASK]: { 0: AbilityId.TECHNICIAN },
[SpeciesId.SHEDINJA]: { 0: AbilityId.MAGIC_GUARD },
@ -421,7 +421,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.KYOGRE]: { 0: AbilityId.MOLD_BREAKER, 1: AbilityId.TERAVOLT },
[SpeciesId.GROUDON]: { 0: AbilityId.MOLD_BREAKER, 1: AbilityId.TURBOBLAZE },
[SpeciesId.RAYQUAZA]: { 0: AbilityId.UNNERVE, 1: AbilityId.UNNERVE },
[SpeciesId.JIRACHI]: { 0: AbilityId.COMATOSE },
[SpeciesId.JIRACHI]: { 0: AbilityId.PURIFYING_SALT },
[SpeciesId.DEOXYS]: { 0: AbilityId.PROTEAN, 1: AbilityId.ADAPTABILITY, 2: AbilityId.REGENERATOR, 3: AbilityId.SHADOW_SHIELD },
[SpeciesId.TURTWIG]: { 0: AbilityId.SOLID_ROCK },
@ -689,9 +689,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.FENNEKIN]: { 0: AbilityId.FLUFFY },
[SpeciesId.BRAIXEN]: { 0: AbilityId.PSYCHIC_SURGE },
[SpeciesId.DELPHOX]: { 0: AbilityId.PSYCHIC_SURGE },
[SpeciesId.FROAKIE]: { 0: AbilityId.STAKEOUT, 1: AbilityId.STAKEOUT },
[SpeciesId.FROGADIER]: { 0: AbilityId.STAKEOUT, 1: AbilityId.STAKEOUT },
[SpeciesId.GRENINJA]: { 0: AbilityId.STAKEOUT, 1: AbilityId.STAKEOUT, 2: AbilityId.STAKEOUT },
[SpeciesId.FROAKIE]: { 0: AbilityId.TECHNICIAN, 1: AbilityId.STAKEOUT },
[SpeciesId.FROGADIER]: { 0: AbilityId.TECHNICIAN, 1: AbilityId.STAKEOUT },
[SpeciesId.GRENINJA]: { 0: AbilityId.TECHNICIAN, 1: AbilityId.STAKEOUT, 2: AbilityId.SUPER_LUCK },
[SpeciesId.BUNNELBY]: { 0: AbilityId.INNER_FOCUS },
[SpeciesId.DIGGERSBY]: { 0: AbilityId.THICK_FAT },
[SpeciesId.FLETCHLING]: { 0: AbilityId.FLAME_BODY },
@ -763,9 +763,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.DARTRIX]: { 0: AbilityId.WIND_RIDER },
[SpeciesId.DECIDUEYE]: { 0: AbilityId.SNIPER },
[SpeciesId.HISUI_DECIDUEYE]: { 0: AbilityId.SNIPER },
[SpeciesId.LITTEN]: { 0: AbilityId.OPPORTUNIST },
[SpeciesId.TORRACAT]: { 0: AbilityId.OPPORTUNIST },
[SpeciesId.INCINEROAR]: { 0: AbilityId.OPPORTUNIST },
[SpeciesId.LITTEN]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.TORRACAT]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.INCINEROAR]: { 0: AbilityId.TOUGH_CLAWS },
[SpeciesId.POPPLIO]: { 0: AbilityId.PUNK_ROCK },
[SpeciesId.BRIONNE]: { 0: AbilityId.PUNK_ROCK },
[SpeciesId.PRIMARINA]: { 0: AbilityId.PUNK_ROCK },
@ -815,7 +815,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.MINIOR]: { 0: AbilityId.STURDY, 1: AbilityId.STURDY, 2: AbilityId.STURDY, 3: AbilityId.STURDY, 4: AbilityId.STURDY, 5: AbilityId.STURDY, 6: AbilityId.STURDY, 7: AbilityId.AERILATE, 8: AbilityId.AERILATE, 9: AbilityId.AERILATE, 10: AbilityId.AERILATE, 11: AbilityId.AERILATE, 12: AbilityId.AERILATE, 13: AbilityId.AERILATE },
[SpeciesId.KOMALA]: { 0: AbilityId.GUTS },
[SpeciesId.TURTONATOR]: { 0: AbilityId.DAUNTLESS_SHIELD },
[SpeciesId.TOGEDEMARU]: { 0: AbilityId.ROUGH_SKIN },
[SpeciesId.TOGEDEMARU]: { 0: AbilityId.CHEEK_POUCH },
[SpeciesId.MIMIKYU]: { 0: AbilityId.TOUGH_CLAWS, 1: AbilityId.TOUGH_CLAWS },
[SpeciesId.BRUXISH]: { 0: AbilityId.MULTISCALE },
[SpeciesId.DRAMPA]: { 0: AbilityId.THICK_FAT },
@ -856,8 +856,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.ALOLA_NINETALES]: { 0: AbilityId.ICE_BODY },
[SpeciesId.ALOLA_DIGLETT]: { 0: AbilityId.STURDY },
[SpeciesId.ALOLA_DUGTRIO]: { 0: AbilityId.STURDY },
[SpeciesId.ALOLA_MEOWTH]: { 0: AbilityId.DARK_AURA },
[SpeciesId.ALOLA_PERSIAN]: { 0: AbilityId.DARK_AURA },
[SpeciesId.ALOLA_MEOWTH]: { 0: AbilityId.DAZZLING },
[SpeciesId.ALOLA_PERSIAN]: { 0: AbilityId.DAZZLING },
[SpeciesId.ALOLA_GEODUDE]: { 0: AbilityId.DRY_SKIN },
[SpeciesId.ALOLA_GRAVELER]: { 0: AbilityId.DRY_SKIN },
[SpeciesId.ALOLA_GOLEM]: { 0: AbilityId.DRY_SKIN },
@ -867,9 +867,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.GROOKEY]: { 0: AbilityId.PICKPOCKET },
[SpeciesId.THWACKEY]: { 0: AbilityId.PICKPOCKET },
[SpeciesId.RILLABOOM]: { 0: AbilityId.GRASS_PELT, 1: AbilityId.GRASS_PELT },
[SpeciesId.SCORBUNNY]: { 0: AbilityId.SHEER_FORCE },
[SpeciesId.RABOOT]: { 0: AbilityId.SHEER_FORCE },
[SpeciesId.CINDERACE]: { 0: AbilityId.NO_GUARD, 1: AbilityId.NO_GUARD },
[SpeciesId.SCORBUNNY]: { 0: AbilityId.OPPORTUNIST },
[SpeciesId.RABOOT]: { 0: AbilityId.OPPORTUNIST },
[SpeciesId.CINDERACE]: { 0: AbilityId.OPPORTUNIST, 1: AbilityId.OPPORTUNIST },
[SpeciesId.SOBBLE]: { 0: AbilityId.SUPER_LUCK },
[SpeciesId.DRIZZILE]: { 0: AbilityId.SUPER_LUCK },
[SpeciesId.INTELEON]: { 0: AbilityId.SUPER_LUCK, 1: AbilityId.SUPER_LUCK },
@ -1041,7 +1041,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[SpeciesId.WIGLETT]: { 0: AbilityId.STURDY },
[SpeciesId.WUGTRIO]: { 0: AbilityId.STURDY },
[SpeciesId.BOMBIRDIER]: { 0: AbilityId.UNBURDEN },
[SpeciesId.FINIZEN]: { 0: AbilityId.SWIFT_SWIM },
[SpeciesId.FINIZEN]: { 0: AbilityId.FRIEND_GUARD },
[SpeciesId.PALAFIN]: { 0: AbilityId.EMERGENCY_EXIT, 1: AbilityId.IRON_FIST },
[SpeciesId.VAROOM]: { 0: AbilityId.LEVITATE },
[SpeciesId.REVAVROOM]: { 0: AbilityId.LEVITATE, 1: AbilityId.DARK_AURA, 2: AbilityId.FLASH_FIRE, 3: AbilityId.MERCILESS, 4: AbilityId.FILTER, 5: AbilityId.SCRAPPY },

View File

@ -1627,7 +1627,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(SpeciesId.TSAREENA, 28, null, {key: EvoCondKey.MOVE, move: MoveId.STOMP}, SpeciesWildEvolutionDelay.LONG)
],
[SpeciesId.POIPOLE]: [
new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_PULSE}, SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_PULSE}, SpeciesWildEvolutionDelay.VERY_LONG)
],
[SpeciesId.ALOLA_SANDSHREW]: [
new SpeciesEvolution(SpeciesId.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1842,7 +1842,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(SpeciesId.LEAVANNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG)
],
[SpeciesId.TYPE_NULL]: [
new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 100}, SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 100}, SpeciesWildEvolutionDelay.VERY_LONG)
],
[SpeciesId.ALOLA_MEOWTH]: [
new SpeciesEvolution(SpeciesId.ALOLA_PERSIAN, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG)

View File

@ -234,7 +234,7 @@ export const speciesStarterCosts = {
[SpeciesId.KYOGRE]: 9,
[SpeciesId.GROUDON]: 9,
[SpeciesId.RAYQUAZA]: 9,
[SpeciesId.JIRACHI]: 7,
[SpeciesId.JIRACHI]: 6,
[SpeciesId.DEOXYS]: 7,
[SpeciesId.TURTWIG]: 3,
@ -319,7 +319,7 @@ export const speciesStarterCosts = {
[SpeciesId.SANDILE]: 4,
[SpeciesId.DARUMAKA]: 4,
[SpeciesId.MARACTUS]: 2,
[SpeciesId.DWEBBLE]: 2,
[SpeciesId.DWEBBLE]: 3,
[SpeciesId.SCRAGGY]: 3,
[SpeciesId.SIGILYPH]: 4,
[SpeciesId.YAMASK]: 3,

View File

@ -7,15 +7,8 @@ import { MoveFlags } from "#enums/MoveFlags";
import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
import { MoveId } from "#enums/move-id";
import type { Pokemon } from "#field/pokemon";
import {
animationFileName,
coerceArray,
getEnumKeys,
getEnumValues,
getFrameMs,
isNullOrUndefined,
type nil,
} from "#utils/common";
import { animationFileName, coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
import Phaser from "phaser";
export class AnimConfig {
@ -1406,10 +1399,10 @@ export class EncounterBattleAnim extends BattleAnim {
export async function populateAnims() {
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, ""));
const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[];
const commonAnimIds = getEnumValues(CommonAnim);
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/_/g, " "));
const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[];
const chargeAnimIds = getEnumValues(ChargeAnim);
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
const moveNameToId = {};
for (const move of getEnumValues(MoveId).slice(1)) {

View File

@ -8,7 +8,8 @@ import { PartyMemberStrength } from "#enums/party-member-strength";
import type { SpeciesId } from "#enums/species-id";
import { PlayerPokemon } from "#field/pokemon";
import type { Starter } from "#ui/starter-select-ui-handler";
import { getEnumValues, randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
export interface DailyRunConfig {
@ -165,5 +166,6 @@ export function getDailyStartingBiome(): BiomeId {
}
// Fallback in case something went wrong
// TODO: should this use `randSeedItem`?
return biomes[randSeedInt(biomes.length)];
}

View File

@ -1744,6 +1744,7 @@ export function getCharVariantFromDialogue(message: string): string {
}
export function initTrainerTypeDialogue(): void {
// TODO: this should not be using `Object.Keys`
const trainerTypes = Object.keys(trainerTypeDialogue).map(t => Number.parseInt(t) as TrainerType);
for (const trainerType of trainerTypes) {
const messages = trainerTypeDialogue[trainerType];

View File

@ -83,7 +83,8 @@ import type { AttackMoveResult } from "#types/attack-move-result";
import type { Localizable } from "#types/locales";
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
import type { TurnMove } from "#types/turn-move";
import { BooleanHolder, type Constructor, getEnumValues, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import i18next from "i18next";
/**

View File

@ -34,7 +34,8 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
import i18next from "#plugins/i18n";
import { PokemonData } from "#system/pokemon-data";
import { randSeedInt } from "#utils/common";
import { randSeedItem } from "#utils/common";
import { getEnumValues } from "#utils/enums";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/berriesAbound";
@ -308,7 +309,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
.build();
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType;
const berryType = randSeedItem(getEnumValues(BerryType));
const berry = berryTypeToHeldItem[berryType];
const party = globalScene.getPlayerParty();

View File

@ -36,8 +36,8 @@ import {
HoldingItemRequirement,
TypeRequirement,
} from "#mystery-encounters/mystery-encounter-requirements";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import { getRandomPartyMemberFunc, trainerConfigs } from "#trainers/trainer-config";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template";
import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
import { MoveInfoOverlay } from "#ui/move-info-overlay";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#utils/common";
@ -276,6 +276,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
// Init the moves available for tutor
const moveTutorOptions: PokemonMove[] = [];
// TODO: should this use `randSeedItem`?
moveTutorOptions.push(new PokemonMove(PHYSICAL_TUTOR_MOVES[randSeedInt(PHYSICAL_TUTOR_MOVES.length)]));
moveTutorOptions.push(new PokemonMove(SPECIAL_TUTOR_MOVES[randSeedInt(SPECIAL_TUTOR_MOVES.length)]));
moveTutorOptions.push(new PokemonMove(STATUS_TUTOR_MOVES[randSeedInt(STATUS_TUTOR_MOVES.length)]));
@ -373,6 +374,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
specialOptions.push(rareFormChangeModifier);
}
if (specialOptions.length > 0) {
// TODO: should this use `randSeedItem`?
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
}

View File

@ -38,8 +38,8 @@ import { applyAbilityOverrideToPokemon } from "#mystery-encounters/encounter-pok
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import { trainerConfigs } from "#trainers/trainer-config";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template";
import type { OptionSelectConfig } from "#ui/abstact-option-select-ui-handler";
import { randSeedInt, randSeedShuffle } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
@ -133,6 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
// Generate random ability for Blacephalon from pool
// TODO: should this use `randSeedItem`?
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
encounter.setDialogueToken("ability", allAbilities[ability].name);
encounter.misc = { ability };

View File

@ -9,12 +9,12 @@ import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils
import { initBattleWithEnemyConfig, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils";
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
import { trainerConfigs } from "#trainers/trainer-config";
import {
TrainerPartyCompoundTemplate,
TrainerPartyTemplate,
trainerPartyTemplates,
} from "#trainers/TrainerPartyTemplate";
import { trainerConfigs } from "#trainers/trainer-config";
} from "#trainers/trainer-party-template";
import { randSeedInt } from "#utils/common";
/** the i18n namespace for the encounter */

View File

@ -190,6 +190,7 @@ async function doBiomeTransitionDialogueAndBattleInit() {
// Calculate new biome (cannot be current biome)
const filteredBiomes = BIOME_CANDIDATES.filter(b => globalScene.arena.biomeType !== b);
// TODO: should this use `randSeedItem`?
const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)];
// Show dialogue and transition biome

View File

@ -26,7 +26,8 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
import { PokemonData } from "#system/pokemon-data";
import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
import { getEnumValues, isNullOrUndefined, randSeedShuffle } from "#utils/common";
import { isNullOrUndefined, randSeedShuffle } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import i18next from "i18next";
/** The i18n namespace for the encounter */

View File

@ -35,8 +35,8 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou
import i18next from "#plugins/i18n";
import { achvs } from "#system/achv";
import { PokemonData } from "#system/pokemon-data";
import { TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import { trainerConfigs } from "#trainers/trainer-config";
import { TrainerPartyTemplate } from "#trainers/trainer-party-template";
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
@ -297,6 +297,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
// TODO: should this use `randSeedItem`?
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
enablePassiveMon.updateInfo(true);
@ -453,6 +454,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
// One random pokemon will get its passive unlocked
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) {
// TODO: should this use `randSeedItem`?
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
await enablePassiveMon.updateInfo(true);

View File

@ -144,11 +144,13 @@ export class MysteryEncounterOption implements IMysteryEncounterOption {
}
if (truePrimaryPool.length > 0) {
// always choose from the non-overlapping pokemon first
// TODO: should this use `randSeedItem`?
this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)];
return true;
}
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
if (overlap.length > 1 || this.secondaryPokemon.length - overlap.length >= 1) {
// TODO: should this use `randSeedItem`?
this.primaryPokemon = overlap[randSeedInt(overlap.length)];
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
return true;

View File

@ -381,6 +381,7 @@ export class MysteryEncounter implements IMysteryEncounter {
// If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
if (overlap.length > 1 || this.secondaryPokemon.length - overlap.length >= 1) {
// is this working?
// TODO: should this use `randSeedItem`?
this.primaryPokemon = overlap[randSeedInt(overlap.length, 0)];
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
return true;
@ -391,6 +392,7 @@ export class MysteryEncounter implements IMysteryEncounter {
return false;
}
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
// TODO: should this use `randSeedItem`?
this.primaryPokemon = qualified[randSeedInt(qualified.length, 0)];
return true;
}

View File

@ -1087,8 +1087,10 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
if (biomes! && biomes.length > 0) {
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
if (specialBiomes.length > 0) {
// TODO: should this use `randSeedItem`?
currentBiome = specialBiomes[randSeedInt(specialBiomes.length)];
} else {
// TODO: should this use `randSeedItem`?
currentBiome = biomes[randSeedInt(biomes.length)];
}
}

View File

@ -108,20 +108,24 @@ export function getRandomPlayerPokemon(
// If there is only 1 legal/unfainted mon left, select from fainted legal mons
const faintedLegalMons = party.filter(p => (!isAllowed || p.isAllowedInChallenge()) && p.isFainted());
if (faintedLegalMons.length > 0) {
// TODO: should this use `randSeedItem`?
chosenIndex = randSeedInt(faintedLegalMons.length);
chosenPokemon = faintedLegalMons[chosenIndex];
}
}
if (!chosenPokemon && fullyLegalMons.length > 0) {
// TODO: should this use `randSeedItem`?
chosenIndex = randSeedInt(fullyLegalMons.length);
chosenPokemon = fullyLegalMons[chosenIndex];
}
if (!chosenPokemon && isAllowed && allowedOnlyMons.length > 0) {
// TODO: should this use `randSeedItem`?
chosenIndex = randSeedInt(allowedOnlyMons.length);
chosenPokemon = allowedOnlyMons[chosenIndex];
}
if (!chosenPokemon) {
// If no other options worked, returns fully random
// TODO: should this use `randSeedItem`?
chosenIndex = randSeedInt(party.length);
chosenPokemon = party[chosenIndex];
}
@ -468,7 +472,7 @@ export function trainerThrowPokeball(
repeatDelay: 500,
onUpdate: t => {
if (shakeCount && shakeCount < 4) {
const value = t.getValue();
const value = t.getValue() ?? 0;
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
pokeball.setX(pbX + value * 4 * directionMultiplier);
pokeball.setAngle(value * 27.5 * directionMultiplier);

View File

@ -127,7 +127,7 @@ export function doPokemonTransformationSequence(
to: 1,
duration: 1000,
onUpdate: t => {
pokemonTintSprite.setAlpha(t.getValue());
pokemonTintSprite.setAlpha(t.getValue() ?? 1);
},
onComplete: () => {
pokemonSprite.setVisible(false);

View File

@ -0,0 +1,376 @@
import { FixedBattleConfig, getRandomTrainerFunc } from "#app/battle";
import { Trainer } from "#app/field/trainer";
import { globalScene } from "#app/global-scene";
import { randSeedInt } from "#app/utils/common";
import { BattleType } from "#enums/battle-type";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { ModifierTier } from "#enums/modifier-tier";
import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type";
import { TrainerVariant } from "#enums/trainer-variant";
export interface FixedBattleConfigs {
[key: number]: FixedBattleConfig;
}
/**
* Youngster/Lass on 5
* Rival on 8, 55, 95, 145, 195
* Evil team grunts on 35, 62, 64, and 112
* Evil team admin on 66 and 114
* Evil leader on 115, 165
* E4 on 182, 184, 186, 188
* Champion on 190
*/
export const classicFixedBattles: FixedBattleConfigs = {
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() => new Trainer(TrainerType.YOUNGSTER, randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT),
),
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
),
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_2,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_3,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_4,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
1,
),
),
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_1,
TrainerType.MAXIE,
TrainerType.ARCHIE,
TrainerType.CYRUS,
TrainerType.GHETSIS,
TrainerType.LYSANDRE,
TrainerType.LUSAMINE,
TrainerType.GUZMA,
TrainerType.ROSE,
TrainerType.PENNY,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_5,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_2,
TrainerType.MAXIE_2,
TrainerType.ARCHIE_2,
TrainerType.CYRUS_2,
TrainerType.GHETSIS_2,
TrainerType.LYSANDRE_2,
TrainerType.LUSAMINE_2,
TrainerType.GUZMA_2,
TrainerType.ROSE_2,
TrainerType.PENNY_2,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LORELEI,
TrainerType.WILL,
TrainerType.SIDNEY,
TrainerType.AARON,
TrainerType.SHAUNTAL,
TrainerType.MALVA,
[TrainerType.HALA, TrainerType.MOLAYNE],
TrainerType.MARNIE_ELITE,
TrainerType.RIKA,
TrainerType.CRISPIN,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BRUNO,
TrainerType.KOGA,
TrainerType.PHOEBE,
TrainerType.BERTHA,
TrainerType.MARSHAL,
TrainerType.SIEBOLD,
TrainerType.OLIVIA,
TrainerType.NESSA_ELITE,
TrainerType.POPPY,
TrainerType.AMARYS,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.AGATHA,
TrainerType.BRUNO,
TrainerType.GLACIA,
TrainerType.FLINT,
TrainerType.GRIMSLEY,
TrainerType.WIKSTROM,
TrainerType.ACEROLA,
[TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE],
TrainerType.LARRY_ELITE,
TrainerType.LACEY,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LANCE,
TrainerType.KAREN,
TrainerType.DRAKE,
TrainerType.LUCIAN,
TrainerType.CAITLIN,
TrainerType.DRASNA,
TrainerType.KAHILI,
TrainerType.RAIHAN_ELITE,
TrainerType.HASSEL,
TrainerType.DRAYTON,
]),
),
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BLUE,
[TrainerType.RED, TrainerType.LANCE_CHAMPION],
[TrainerType.STEVEN, TrainerType.WALLACE],
TrainerType.CYNTHIA,
[TrainerType.ALDER, TrainerType.IRIS],
TrainerType.DIANTHA,
[TrainerType.KUKUI, TrainerType.HAU],
[TrainerType.LEON, TrainerType.MUSTARD],
[TrainerType.GEETA, TrainerType.NEMONA],
TrainerType.KIERAN,
]),
),
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_6,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
allowLuckUpgrades: false,
}),
};

View File

@ -30,7 +30,7 @@ import {
TrainerPartyCompoundTemplate,
TrainerPartyTemplate,
trainerPartyTemplates,
} from "#trainers/TrainerPartyTemplate";
} from "#trainers/trainer-party-template";
import type { ModifierTypeFunc } from "#types/modifier-types";
import type {
GenAIFunc,

View File

@ -144,6 +144,7 @@ export class Arena {
if (!tierPool.length) {
ret = globalScene.randomSpecies(waveIndex, level);
} else {
// TODO: should this use `randSeedItem`?
const entry = tierPool[randSeedInt(tierPool.length)];
let species: SpeciesId;
if (typeof entry === "number") {
@ -155,6 +156,7 @@ export class Arena {
if (level >= levelThreshold) {
const speciesIds = entry[levelThreshold];
if (speciesIds.length > 1) {
// TODO: should this use `randSeedItem`?
species = speciesIds[randSeedInt(speciesIds.length)];
} else {
species = speciesIds[0];
@ -167,19 +169,11 @@ export class Arena {
ret = getPokemonSpecies(species!);
if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) {
case ret.baseTotal >= 720:
regen = level < 90;
break;
case ret.baseTotal >= 670:
regen = level < 70;
break;
case ret.baseTotal >= 580:
regen = level < 50;
break;
default:
regen = level < 30;
break;
const waveDifficulty = globalScene.gameMode.getWaveForDifficulty(waveIndex);
if (ret.baseTotal >= 660) {
regen = waveDifficulty < 80; // Wave 50+ in daily (however, max Daily wave is 50 currently so not possible)
} else {
regen = waveDifficulty < 55; // Wave 25+ in daily
}
}
}
@ -497,38 +491,37 @@ export class Arena {
getTrainerChance(): number {
switch (this.biomeType) {
case BiomeId.METROPOLIS:
return 2;
case BiomeId.SLUM:
case BiomeId.BEACH:
case BiomeId.DOJO:
case BiomeId.CONSTRUCTION_SITE:
return 4;
case BiomeId.PLAINS:
case BiomeId.GRASS:
case BiomeId.BEACH:
case BiomeId.LAKE:
case BiomeId.CAVE:
case BiomeId.DESERT:
case BiomeId.CONSTRUCTION_SITE:
case BiomeId.SLUM:
return 6;
case BiomeId.TALL_GRASS:
case BiomeId.FOREST:
case BiomeId.SEA:
case BiomeId.SWAMP:
case BiomeId.MOUNTAIN:
case BiomeId.BADLANDS:
case BiomeId.DESERT:
case BiomeId.MEADOW:
case BiomeId.POWER_PLANT:
case BiomeId.GRAVEYARD:
case BiomeId.FACTORY:
case BiomeId.SNOWY_FOREST:
return 8;
case BiomeId.SEA:
case BiomeId.ICE_CAVE:
case BiomeId.VOLCANO:
case BiomeId.GRAVEYARD:
case BiomeId.RUINS:
case BiomeId.WASTELAND:
case BiomeId.JUNGLE:
case BiomeId.FAIRY_CAVE:
case BiomeId.ISLAND:
return 12;
case BiomeId.SEABED:
case BiomeId.ABYSS:
case BiomeId.SPACE:
case BiomeId.TEMPLE:
@ -933,6 +926,7 @@ export function getBiomeKey(biome: BiomeId): string {
export function getBiomeHasProps(biomeType: BiomeId): boolean {
switch (biomeType) {
case BiomeId.PLAINS:
case BiomeId.METROPOLIS:
case BiomeId.BEACH:
case BiomeId.LAKE:

View File

@ -141,7 +141,6 @@ import {
coerceArray,
deltaRgb,
fixedInt,
getEnumValues,
getIvsFromId,
isBetween,
isNullOrUndefined,
@ -155,6 +154,7 @@ import {
rgbToHsv,
toDmgValue,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities";
import i18next from "i18next";
@ -552,7 +552,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
getDexAttr(): bigint {
let ret = 0n;
ret |= this.gender !== Gender.FEMALE ? DexAttr.MALE : DexAttr.FEMALE;
if (this.gender !== Gender.GENDERLESS) {
ret |= this.gender !== Gender.FEMALE ? DexAttr.MALE : DexAttr.FEMALE;
}
ret |= !this.shiny ? DexAttr.NON_SHINY : DexAttr.SHINY;
ret |= this.variant >= 2 ? DexAttr.VARIANT_3 : this.variant === 1 ? DexAttr.VARIANT_2 : DexAttr.DEFAULT_VARIANT;
ret |= globalScene.gameData.getFormAttr(this.formIndex);
@ -2508,41 +2510,50 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns A score value based on how favorable this Pokemon is when fighting the given Pokemon
*/
getMatchupScore(opponent: Pokemon): number {
const types = this.getTypes(true);
const enemyTypes = opponent.getTypes(true, true, false, true);
const enemyTypes = opponent.getTypes(true, false, false, true);
/** Is this Pokemon faster than the opponent? */
const outspeed =
(this.isActive(true) ? this.getEffectiveStat(Stat.SPD, opponent) : this.getStat(Stat.SPD, false)) >=
opponent.getEffectiveStat(Stat.SPD, this);
/**
* Based on how effective this Pokemon's types are offensively against the opponent's types.
* This score is increased by 25 percent if this Pokemon is faster than the opponent.
*/
let atkScore =
opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1);
/**
* Based on how effectively this Pokemon defends against the opponent's types.
* This score cannot be higher than 4.
*/
let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25);
if (types.length > 1) {
atkScore *= opponent.getAttackTypeEffectiveness(types[1], this);
}
if (enemyTypes.length > 1) {
defScore *=
1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25);
}
atkScore *= 1.25; //give more value for the pokemon's typing
const moveset = this.moveset;
let moveAtkScoreLength = 0;
let atkScore = 0;
// TODO: this calculation needs to consider more factors; it's currently very simplistic
for (const move of moveset) {
if (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL) {
atkScore += opponent.getAttackTypeEffectiveness(move.getMove().type, this, false, true, undefined, true);
moveAtkScoreLength++;
const resolvedMove = move.getMove();
// NOTE: Counter and Mirror Coat are considered as attack moves here
if (resolvedMove.category === MoveCategory.STATUS || move.getPpRatio() <= 0) {
continue;
}
const moveType = resolvedMove.type;
let thisScore = opponent.getAttackTypeEffectiveness(moveType, this, false, true, undefined, true);
// Add STAB multiplier for attack type effectiveness.
// For now, simply don't apply STAB to moves that may change type
if (this.getTypes(true).includes(moveType) && !move.getMove().hasAttr("VariableMoveTypeAttr")) {
thisScore *= 1.5;
}
atkScore += thisScore;
moveAtkScoreLength++;
}
atkScore = atkScore / (moveAtkScoreLength + 1); //calculate the median for the attack score
// Get average attack score of all damaging moves (|| 1 prevents division by zero))
// TODO: Averaging the attack score is excessively simplistic, and doesn't reflect the AI's move selection logic
// e.g. if the mon has one 4x effective move and three 0.5x effective moves, this score would be ~1.375
// which does not seem fair, given that if the AI were to switch, in all likelihood it would use the 4x move.
// We could consider a weighted average...
atkScore /= moveAtkScoreLength || 1;
/**
* Based on this Pokemon's HP ratio compared to that of the opponent.
* This ratio is multiplied by 1.5 if this Pokemon outspeeds the opponent;
@ -2550,6 +2561,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
*/
const hpRatio = this.getHpRatio();
const oppHpRatio = opponent.getHpRatio();
// TODO: use better logic for predicting whether the pokemon "is dying"
// E.g., perhaps check if it would faint if the opponent were to use the same move it just used
// (twice if the user is slower)
const isDying = hpRatio <= 0.2;
let hpDiffRatio = hpRatio + (1 - oppHpRatio);
if (isDying && this.isActive(true)) {
@ -2559,15 +2573,15 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
//It might not be a worthy sacrifice if it doesn't outspeed or doesn't do enough damage
hpDiffRatio *= 0.85;
} else {
hpDiffRatio = Math.min(1 - hpRatio + (outspeed ? 0.2 : 0.1), 1);
hpDiffRatio = 1 - hpRatio + (outspeed ? 0.2 : 0.1);
}
} else if (outspeed) {
hpDiffRatio = Math.min(hpDiffRatio * 1.25, 1);
hpDiffRatio = hpDiffRatio * 1.25;
} else if (hpRatio > 0.2 && hpRatio <= 0.4) {
//Might be considered to be switched because it's not in low enough health
hpDiffRatio = Math.min(hpDiffRatio * 0.5, 1);
// Might be considered to be switched because it's not in low enough health
hpDiffRatio = hpDiffRatio * 0.5;
}
return (atkScore + defScore) * hpDiffRatio;
return (atkScore + defScore) * Math.min(hpDiffRatio, 1);
}
getEvolution(): SpeciesFormEvolution | null {
@ -3360,10 +3374,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
}
getOpponentDescriptor(): string {
const opponents = this.getOpponents();
if (opponents.length === 1) {
return opponents[0].name;
}
return this.isPlayer() ? i18next.t("arenaTag:opposingTeam") : i18next.t("arenaTag:yourTeam");
}

View File

@ -14,10 +14,13 @@ import { TrainerVariant } from "#enums/trainer-variant";
import type { EnemyPokemon } from "#field/pokemon";
import type { TrainerItemConfiguration } from "#items/trainer-item-data-types";
import { getIsInitialized, initI18n } from "#plugins/i18n";
import type { TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import { TrainerPartyCompoundTemplate, trainerPartyTemplates } from "#trainers/TrainerPartyTemplate";
import type { TrainerConfig } from "#trainers/trainer-config";
import { trainerConfigs } from "#trainers/trainer-config";
import {
TrainerPartyCompoundTemplate,
type TrainerPartyTemplate,
trainerPartyTemplates,
} from "#trainers/trainer-party-template";
import { randSeedInt, randSeedItem, randSeedWeightedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";
@ -420,7 +423,8 @@ export class Trainer extends Phaser.GameObjects.Container {
// If useNewSpeciesPool is true, we need to generate a new species from the new species pool, otherwise we generate a random species
let species = useNewSpeciesPool
? getPokemonSpecies(newSpeciesPool[Math.floor(randSeedInt(newSpeciesPool.length))])
? // TODO: should this use `randSeedItem`?
getPokemonSpecies(newSpeciesPool[Math.floor(randSeedInt(newSpeciesPool.length))])
: template.isSameSpecies(index) && index > offset
? getPokemonSpecies(
battle.enemyParty[offset].species.getTrainerSpeciesForLevel(
@ -618,6 +622,8 @@ export class Trainer extends Phaser.GameObjects.Container {
if (maxScorePartyMemberIndexes.length > 1) {
let rand: number;
// TODO: should this use `randSeedItem`?
globalScene.executeWithSeedOffset(
() => (rand = randSeedInt(maxScorePartyMemberIndexes.length)),
globalScene.currentBattle.turn << 2,

View File

@ -1,5 +1,4 @@
import type { FixedBattleConfigs } from "#app/battle";
import { classicFixedBattles, FixedBattleConfig } from "#app/battle";
import { FixedBattleConfig } from "#app/battle";
import { CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides";
@ -14,6 +13,7 @@ import { Challenges } from "#enums/challenges";
import { GameModes } from "#enums/game-modes";
import { SpeciesId } from "#enums/species-id";
import type { Arena } from "#field/arena";
import { classicFixedBattles, type FixedBattleConfigs } from "#trainers/fixed-battle-configs";
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common";
import i18next from "i18next";
@ -164,14 +164,14 @@ export class GameMode implements GameModeConfig {
if (waveIndex % 10 !== 1 && waveIndex % 10) {
/**
* Do not check X1 floors since there's a bug that stops trainer sprites from appearing
* after a X0 full party heal
* after a X0 full party heal, this also allows for a smoother biome transition for general gameplay feel
*/
const trainerChance = arena.getTrainerChance();
let allowTrainerBattle = true;
if (trainerChance) {
const waveBase = Math.floor(waveIndex / 10) * 10;
// Stop generic trainers from spawning in within 3 waves of a trainer battle
for (let w = Math.max(waveIndex - 3, waveBase + 2); w <= Math.min(waveIndex + 3, waveBase + 9); w++) {
// Stop generic trainers from spawning in within 2 waves of a fixed trainer battle
for (let w = Math.max(waveIndex - 2, waveBase + 2); w <= Math.min(waveIndex + 2, waveBase + 9); w++) {
if (w === waveIndex) {
continue;
}

View File

@ -15,8 +15,8 @@ import type { SettingKeyboard } from "#system/settings-keyboard";
import { MoveTouchControlsHandler } from "#ui/move-touch-controls-handler";
import type { SettingsGamepadUiHandler } from "#ui/settings-gamepad-ui-handler";
import type { SettingsKeyboardUiHandler } from "#ui/settings-keyboard-ui-handler";
import { getEnumValues } from "#utils/common";
import { deepCopy } from "#utils/data";
import { getEnumValues } from "#utils/enums";
import Phaser from "phaser";
export interface DeviceMapping {

View File

@ -25,7 +25,8 @@ import { initAchievements } from "#system/achv";
import { initVouchers } from "#system/voucher";
import { initStatsKeys } from "#ui/game-stats-ui-handler";
import { getWindowVariantSuffix, WindowVariant } from "#ui/ui-theme";
import { getEnumValues, hasAllLocalizedSprites, localPing } from "#utils/common";
import { hasAllLocalizedSprites, localPing } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import i18next from "i18next";
export class LoadingScene extends SceneBase {

View File

@ -61,15 +61,8 @@ import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#types/mo
import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler";
import { PartyUiHandler } from "#ui/party-ui-handler";
import { getModifierTierTextTint } from "#ui/text";
import {
formatMoney,
getEnumKeys,
getEnumValues,
isNullOrUndefined,
NumberHolder,
padInt,
randSeedInt,
} from "#utils/common";
import { formatMoney, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
import { getModifierPoolForType } from "#utils/modifier-utils";
import i18next from "i18next";
@ -1128,6 +1121,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator {
if (!tierUniqueCompatibleTms.length) {
return null;
}
// TODO: should this use `randSeedItem`?
const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length);
return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]);
});
@ -1181,6 +1175,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
return null;
}
// TODO: should this use `randSeedItem`?
return new EvolutionItemModifierType(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
});
}
@ -1257,6 +1252,7 @@ export class FormChangeItemRewardGenerator extends ModifierTypeGenerator {
return null;
}
// TODO: should this use `randSeedItem`?
return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]);
});
}
@ -1412,7 +1408,7 @@ const modifierTypeInitObj = Object.freeze({
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) {
return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature);
}
return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature);
return new PokemonNatureChangeModifierType(randSeedItem(getEnumValues(Nature)));
}),
MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK),

View File

@ -119,7 +119,7 @@ export class AttemptCapturePhase extends PokemonPhase {
repeatDelay: 500,
onUpdate: t => {
if (shakeCount && shakeCount < (isCritical ? 2 : 4)) {
const value = t.getValue();
const value = t.getValue() ?? 0;
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
this.pokeball.setX(pbX + value * 4 * directionMultiplier);
this.pokeball.setAngle(value * 27.5 * directionMultiplier);

View File

@ -239,7 +239,7 @@ export class EvolutionPhase extends Phase {
to: 1,
duration: 2000,
onUpdate: t => {
this.pokemonTintSprite.setAlpha(t.getValue());
this.pokemonTintSprite.setAlpha(t.getValue() ?? 1);
},
onComplete: () => {
this.pokemonSprite.setVisible(false);

View File

@ -26,7 +26,8 @@ import { applyMoveAttrs } from "#moves/apply-attrs";
import { frenzyMissFunc } from "#moves/move-utils";
import type { PokemonMove } from "#moves/pokemon-move";
import { BattlePhase } from "#phases/battle-phase";
import { enumValueToKey, NumberHolder } from "#utils/common";
import { NumberHolder } from "#utils/common";
import { enumValueToKey } from "#utils/enums";
import i18next from "i18next";
export class MovePhase extends BattlePhase {

View File

@ -55,6 +55,7 @@ export class SelectBiomePhase extends BattlePhase {
delay: 1000,
});
} else {
// TODO: should this use `randSeedItem`?
setNextBiome(biomes[randSeedInt(biomes.length)]);
}
} else if (biomeLinks.hasOwnProperty(currentBiome)) {

View File

@ -185,7 +185,7 @@ export class SelectRewardPhase extends BattlePhase {
} else {
this.applyModifier(modifierType.newModifier()!);
}
return !cost;
return cost === -1;
}
// Reroll rewards

View File

@ -59,8 +59,9 @@ import { VoucherType, vouchers } from "#system/voucher";
import { trainerConfigs } from "#trainers/trainer-config";
import type { DexData, DexEntry } from "#types/dex-data";
import { RUN_HISTORY_LIMIT } from "#ui/run-history-ui-handler";
import { executeIf, fixedInt, getEnumKeys, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common";
import { executeIf, fixedInt, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common";
import { decrypt, encrypt } from "#utils/data";
import { getEnumKeys } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { AES, enc } from "crypto-js";
import i18next from "i18next";

View File

@ -2,7 +2,8 @@ import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { executeIf, getEnumKeys } from "#utils/common";
import { executeIf } from "#utils/common";
import { getEnumKeys } from "#utils/enums";
import i18next from "i18next";
export interface RankingEntry {

View File

@ -11,7 +11,8 @@ import { getVoucherTypeIcon, VoucherType } from "#system/voucher";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addTextObject, getEggTierTextTint, getTextStyleOptions, TextStyle } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, getEnumValues, randSeedShuffle } from "#utils/common";
import { fixedInt, randSeedShuffle } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";

View File

@ -134,6 +134,8 @@ export class FightUiHandler extends UiHandler implements InfoToggle {
const pokemon = (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon();
if (pokemon.tempSummonData.turnCount <= 1) {
this.setCursor(0);
} else {
this.setCursor(this.fieldIndex ? this.cursor2 : this.cursor);
}
this.displayMoves();
this.toggleInfo(false); // in case cancel was pressed while info toggle is active

View File

@ -13,8 +13,9 @@ import { BgmBar } from "#ui/bgm-bar";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addTextObject, getTextStyleOptions, TextStyle } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { fixedInt, getEnumKeys, isLocal, sessionIdKey } from "#utils/common";
import { fixedInt, isLocal, sessionIdKey } from "#utils/common";
import { getCookie } from "#utils/cookies";
import { getEnumValues } from "#utils/enums";
import { isBeta } from "#utils/utility-vars";
import i18next from "i18next";
@ -76,11 +77,9 @@ export class MenuUiHandler extends MessageUiHandler {
{ condition: bypassLogin, options: [MenuOptions.LOG_OUT] },
];
this.menuOptions = getEnumKeys(MenuOptions)
.map(m => Number.parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.menuOptions = getEnumValues(MenuOptions).filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
}
setup(): void {
@ -131,11 +130,9 @@ export class MenuUiHandler extends MessageUiHandler {
{ condition: bypassLogin, options: [MenuOptions.LOG_OUT] },
];
this.menuOptions = getEnumKeys(MenuOptions)
.map(m => Number.parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.menuOptions = getEnumValues(MenuOptions).filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.optionSelectText = addTextObject(
0,
@ -511,11 +508,9 @@ export class MenuUiHandler extends MessageUiHandler {
this.render();
super.show(args);
this.menuOptions = getEnumKeys(MenuOptions)
.map(m => Number.parseInt(MenuOptions[m]) as MenuOptions)
.filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.menuOptions = getEnumValues(MenuOptions).filter(m => {
return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m));
});
this.menuContainer.setVisible(true);
this.setCursor(0);

View File

@ -1257,17 +1257,18 @@ export class PartyUiHandler extends MessageUiHandler {
const allowBatonModifierSwitch = this.allowBatonModifierSwitch();
const isBatonPassMove = this.isBatonPassMove();
if (allowBatonModifierSwitch && !isBatonPassMove) {
// the BATON modifier gives an extra switch option for
// pokemon-command switches, allowing buffs to be optionally passed
this.options.push(PartyOption.PASS_BATON);
}
// isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true
// at the same time, because they both explicitly check for a mutually
// exclusive partyUiMode. But better safe than sorry.
this.options.push(
isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT,
);
if (allowBatonModifierSwitch && !isBatonPassMove) {
// the BATON modifier gives an extra switch option for
// pokemon-command switches, allowing buffs to be optionally passed
this.options.push(PartyOption.PASS_BATON);
}
}
this.addCommonOptions(pokemon);
if (this.partyUiMode === PartyUiMode.SWITCH) {
@ -1307,13 +1308,13 @@ export class PartyUiHandler extends MessageUiHandler {
this.addCommonOptions(pokemon);
break;
case PartyUiMode.CHECK:
this.addCommonOptions(pokemon);
if (globalScene.phaseManager.getCurrentPhase()?.is("SelectRewardPhase")) {
const formChangeItems = this.getFormChangeItems(pokemon);
for (let i = 0; i < formChangeItems.length; i++) {
this.options.push(PartyOption.FORM_CHANGE_ITEM + i);
}
}
this.addCommonOptions(pokemon);
break;
case PartyUiMode.SELECT:
this.options.push(PartyOption.SELECT);

View File

@ -55,13 +55,13 @@ import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions,
import { addWindow } from "#ui/ui-theme";
import {
BooleanHolder,
getEnumKeys,
getLocalizedSpriteKey,
isNullOrUndefined,
padInt,
rgbHexToRgba,
toReadableString,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
@ -640,7 +640,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.menuContainer.setVisible(false);
this.menuOptions = getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions);
this.menuOptions = getEnumValues(MenuOptions);
this.optionSelectText = addBBCodeTextObject(
0,
@ -744,7 +744,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.starterAttributes = this.initStarterPrefs();
this.menuOptions = getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions);
this.menuOptions = getEnumValues(MenuOptions);
this.menuContainer.setVisible(true);

View File

@ -275,13 +275,22 @@ export class RewardSelectUiHandler extends AwaitableUiHandler {
globalScene.updateBiomeWaveText();
globalScene.updateMoneyText();
// DO NOT REMOVE: Fixes bug which allows action input to be processed before the UI is shown,
// causing errors if reroll is selected
this.awaitingActionInput = false;
// TODO: Replace with `Promise.withResolvers` when possible.
let tweenResolve: () => void;
const tweenPromise = new Promise<void>(resolve => (tweenResolve = resolve));
let i = 0;
// TODO: Rework this bespoke logic for animating the modifier options.
globalScene.tweens.addCounter({
ease: "Sine.easeIn",
duration: 1250,
onUpdate: t => {
const value = t.getValue();
// The bang here is safe, as `getValue()` only returns null if the tween has been destroyed (which obviously isn't the case inside onUpdate)
const value = t.getValue()!;
const index = Math.floor(value * typeOptions.length);
if (index > i && index <= typeOptions.length) {
const option = this.options[i];
@ -292,67 +301,77 @@ export class RewardSelectUiHandler extends AwaitableUiHandler {
i++;
}
},
onComplete: () => {
tweenResolve();
},
});
globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => {
for (const shopOption of this.shopOptionsRows.flat()) {
shopOption.show(0, 0);
}
let shopResolve: () => void;
const shopPromise = new Promise<void>(resolve => (shopResolve = resolve));
tweenPromise.then(() => {
globalScene.time.delayedCall(1000, () => {
for (const shopOption of this.shopOptionsRows.flat()) {
shopOption.show(0, 0);
}
shopResolve();
});
});
globalScene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => {
if (partyHasHeldItem) {
this.transferButtonContainer.setAlpha(0);
this.transferButtonContainer.setVisible(true);
shopPromise.then(() => {
globalScene.time.delayedCall(500, () => {
if (partyHasHeldItem) {
this.transferButtonContainer.setAlpha(0);
this.transferButtonContainer.setVisible(true);
globalScene.tweens.add({
targets: this.transferButtonContainer,
alpha: 1,
duration: 250,
});
}
this.rerollButtonContainer.setAlpha(0);
this.checkButtonContainer.setAlpha(0);
this.lockRarityButtonContainer.setAlpha(0);
this.continueButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.continueButtonContainer.setVisible(this.rerollCost < 0);
this.lockRarityButtonContainer.setVisible(canLockRarities);
globalScene.tweens.add({
targets: this.transferButtonContainer,
targets: [this.checkButtonContainer, this.continueButtonContainer],
alpha: 1,
duration: 250,
});
}
this.rerollButtonContainer.setAlpha(0);
this.checkButtonContainer.setAlpha(0);
this.lockRarityButtonContainer.setAlpha(0);
this.continueButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.continueButtonContainer.setVisible(this.rerollCost < 0);
this.lockRarityButtonContainer.setVisible(canLockRarities);
globalScene.tweens.add({
targets: [this.rerollButtonContainer, this.lockRarityButtonContainer],
alpha: this.rerollCost < 0 ? 0.5 : 1,
duration: 250,
});
globalScene.tweens.add({
targets: [this.checkButtonContainer, this.continueButtonContainer],
alpha: 1,
duration: 250,
});
const updateCursorTarget = () => {
if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) {
this.setRowCursor(0);
this.setCursor(2);
} else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) {
this.setRowCursor(ShopCursorTarget.REWARDS);
this.setCursor(0);
} else {
this.setRowCursor(globalScene.shopCursorTarget);
this.setCursor(0);
}
};
globalScene.tweens.add({
targets: [this.rerollButtonContainer, this.lockRarityButtonContainer],
alpha: this.rerollCost < 0 ? 0.5 : 1,
duration: 250,
});
updateCursorTarget();
const updateCursorTarget = () => {
if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) {
this.setRowCursor(0);
this.setCursor(2);
} else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) {
this.setRowCursor(ShopCursorTarget.REWARDS);
this.setCursor(0);
} else {
this.setRowCursor(globalScene.shopCursorTarget);
this.setCursor(0);
}
};
updateCursorTarget();
handleTutorial(Tutorial.Select_Item).then(res => {
if (res) {
updateCursorTarget();
}
this.awaitingActionInput = true;
this.onActionInput = args[2];
handleTutorial(Tutorial.Select_Item).then(res => {
if (res) {
updateCursorTarget();
}
this.awaitingActionInput = true;
this.onActionInput = args[2];
});
});
});
@ -693,7 +712,11 @@ export class RewardSelectUiHandler extends AwaitableUiHandler {
scale: 0.01,
duration: 250,
ease: "Cubic.easeIn",
onComplete: () => options.forEach(o => o.destroy()),
onComplete: () => {
options.forEach(o => {
o.destroy();
});
},
});
[
@ -825,7 +848,7 @@ class ModifierOption extends Phaser.GameObjects.Container {
if (!globalScene) {
return;
}
const value = t.getValue();
const value = t.getValue()!;
if (!bounce && value > lastValue) {
globalScene.playSound("se/pb_bounce_1", {
volume: 1 / ++bounceCount,
@ -838,41 +861,38 @@ class ModifierOption extends Phaser.GameObjects.Container {
},
});
// TODO: Figure out proper delay between chains and then convert this into a single tween chain
// rather than starting multiple tween chains.
for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) {
const upgradeIndex = u;
globalScene.time.delayedCall(
remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (upgradeIndex + 1 + upgradeCountOffset)),
() => {
globalScene.playSound("se/upgrade", {
rate: 1 + 0.25 * upgradeIndex,
});
this.pbTint.setPosition(this.pb.x, this.pb.y);
this.pbTint.setTintFill(0xffffff);
this.pbTint.setAlpha(0);
this.pbTint.setVisible(true);
globalScene.tweens.add({
globalScene.tweens.chain({
tweens: [
{
delay: remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (u + 1 + upgradeCountOffset)),
onStart: () => {
globalScene.playSound("se/upgrade", {
rate: 1 + 0.25 * u,
});
this.pbTint.setPosition(this.pb.x, this.pb.y).setTintFill(0xffffff).setVisible(true).setAlpha(0);
},
targets: this.pbTint,
alpha: 1,
duration: 1000,
ease: "Sine.easeIn",
onComplete: () => {
this.pb.setTexture(
"pb",
this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (upgradeIndex + 1)),
);
globalScene.tweens.add({
targets: this.pbTint,
alpha: 0,
duration: 750,
ease: "Sine.easeOut",
onComplete: () => {
this.pbTint.setVisible(false);
},
});
this.pb.setTexture("pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (u + 1)));
},
});
},
);
},
{
targets: this.pbTint,
alpha: 0,
duration: 750,
ease: "Sine.easeOut",
onComplete: () => {
this.pbTint.setVisible(false);
},
},
],
});
}
}

View File

@ -147,7 +147,7 @@ export class StatsContainer extends Phaser.GameObjects.Container {
duration: 1000,
ease: "Cubic.easeOut",
onUpdate: (tween: Phaser.Tweens.Tween) => {
const progress = tween.getValue();
const progress = tween.getValue() ?? 1;
const interpolatedData = ivChartData.map(
(v: number, i: number) => v * progress + lastIvChartData[i] * (1 - progress),
);

View File

@ -30,7 +30,6 @@ import { UiHandler } from "#ui/ui-handler";
import {
fixedInt,
formatStat,
getEnumValues,
getLocalizedSpriteKey,
getShinyDescriptor,
isNullOrUndefined,
@ -38,6 +37,7 @@ import {
rgbHexToRgba,
toReadableString,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";

View File

@ -157,8 +157,8 @@ export class TargetSelectUiHandler extends UiHandler {
yoyo: true,
onUpdate: t => {
for (const target of this.targetsHighlighted) {
target.setAlpha(t.getValue());
this.highlightItems(target.id, t.getValue());
target.setAlpha(t.getValue() ?? 1);
this.highlightItems(target.id, t.getValue() ?? 1);
}
},
});

View File

@ -5,7 +5,7 @@ import { UiTheme } from "#enums/ui-theme";
import i18next from "#plugins/i18n";
import type Phaser from "phaser";
import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
import type InputText from "phaser3-rex-plugins/plugins/inputtext";
export enum TextStyle {
MESSAGE,
@ -152,8 +152,7 @@ export function addTextInputObject(
): InputText {
const { scale, styleOptions } = getTextStyleOptions(style, globalScene.uiTheme, extraStyleOptions);
const ret = new InputText(globalScene, x, y, width, height, styleOptions as InputText.IConfig);
globalScene.add.existing(ret);
const ret = globalScene.add.rexInputText(x, y, width, height, styleOptions as InputText.IConfig);
ret.setScale(scale);
return ret;
@ -319,10 +318,14 @@ export function getTextStyleOptions(
case TextStyle.MESSAGE:
styleOptions.fontSize = defaultFontSize;
break;
case TextStyle.HEADER_LABEL:
styleOptions.fontSize = defaultFontSize;
styleOptions.padding = { top: 6 };
case TextStyle.HEADER_LABEL: {
switch (lang) {
case "ja":
styleOptions.padding = { top: 6 };
break;
}
break;
}
case TextStyle.SETTINGS_VALUE:
case TextStyle.SETTINGS_LABEL: {
shadowXpos = 3;

View File

@ -8,11 +8,19 @@ export type nil = null | undefined;
export const MissingTextureKey = "__MISSING";
// TODO: Draft tests for these utility functions
// TODO: Break up this file
/**
* Convert a `snake_case` string in any capitalization (such as one from an enum reverse mapping)
* into a readable `Title Case` version.
* @param str - The snake case string to be converted.
* @returns The result of converting `str` into title case.
*/
export function toReadableString(str: string): string {
return str
.replace(/_/g, " ")
.split(" ")
.map(s => `${s.slice(0, 1)}${s.slice(1).toLowerCase()}`)
.map(s => capitalizeFirstLetter(s.toLowerCase()))
.join(" ");
}
@ -273,18 +281,6 @@ export function formatStat(stat: number, forHp = false): string {
return formatLargeNumber(stat, forHp ? 100000 : 1000000);
}
export function getEnumKeys(enumType: any): string[] {
return Object.values(enumType)
.filter(v => Number.isNaN(Number.parseInt(v!.toString())))
.map(v => v!.toString());
}
export function getEnumValues(enumType: any): number[] {
return Object.values(enumType)
.filter(v => !Number.isNaN(Number.parseInt(v!.toString())))
.map(v => Number.parseInt(v!.toString()));
}
export function getTypedKeys<T extends Record<number, any>, K extends number = Extract<keyof T, number>>(obj: T): K[] {
return Object.keys(obj).map(k => Number(k) as K);
}
@ -653,28 +649,6 @@ export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}
/**
* Returns the name of the key that matches the enum [object] value.
* @param input - The enum [object] to check
* @param val - The value to get the key of
* @returns The name of the key with the specified value
* @example
* const thing = {
* one: 1,
* two: 2,
* } as const;
* console.log(enumValueToKey(thing, thing.two)); // output: "two"
* @throws An `Error` if an invalid enum value is passed to the function
*/
export function enumValueToKey<T extends Record<string, string | number>>(input: T, val: T[keyof T]): keyof T {
for (const [key, value] of Object.entries(input)) {
if (val === value) {
return key as keyof T;
}
}
throw new Error(`Invalid value passed to \`enumValueToKey\`! Value: ${val}`);
}
export function pickWeightedIndex(weights: number[]): number | undefined {
const totalWeight = weights.reduce((sum, w) => sum + w, 0);

74
src/utils/enums.ts Normal file
View File

@ -0,0 +1,74 @@
import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types";
import type { InferKeys } from "#app/@types/type-helpers";
/**
* Return the string keys of an Enum object, excluding reverse-mapped numbers.
* @param enumType - The numeric enum to retrieve keys for
* @returns An ordered array of all of `enumType`'s string keys
* @example
* enum fruit {
* apple = 1,
* banana = 2,
* cherry = 3,
* orange = 12,
* };
*
* console.log(getEnumKeys<typeof fruit>(fruit)); // output: ["apple", "banana", "cherry", "orange"]
* @remarks
* To retrieve the keys of a {@linkcode NormalEnum}, use {@linkcode Object.keys} instead.
*/
export function getEnumKeys<E extends EnumOrObject>(enumType: TSNumericEnum<E>): (keyof E)[] {
// All enum values are either normal numbers or reverse mapped strings, so we can retrieve the keys by filtering out numbers.
return Object.values(enumType).filter(v => typeof v === "string");
}
/**
* Return the numeric values of a numeric Enum object, excluding reverse-mapped strings.
* @param enumType - The enum object to retrieve keys for
* @returns An ordered array of all of `enumType`'s number values
* @example
* enum fruit {
* apple = 1,
* banana = 2,
* cherry = 3,
* orange = 12,
* };
*
* console.log(getEnumValues<typeof fruit>(fruit)); // output: [1, 2, 3, 12]
*
* @remarks
* To retrieve the keys of a {@linkcode NormalEnum}, use {@linkcode Object.values} instead.
*/
// NB: This intentionally does not use `EnumValues<E>` as using `E[keyof E]` leads to improved variable highlighting in IDEs.
export function getEnumValues<E extends EnumOrObject>(enumType: TSNumericEnum<E>): E[keyof E][] {
return Object.values(enumType).filter(v => typeof v !== "string") as E[keyof E][];
}
/**
* Return the name of the key that matches the given Enum value.
* Can be used to emulate Typescript reverse mapping for `const object`s or string enums.
* @param object - The {@linkcode NormalEnum} to check
* @param val - The value to get the key of
* @returns The name of the key with the specified value
* @example
* const thing = {
* one: 1,
* two: 2,
* } as const;
* console.log(enumValueToKey(thing, 2)); // output: "two"
* @throws Error if an invalid enum value is passed to the function
* @remarks
* If multiple keys map to the same value, the first one (in insertion order) will be retrieved,
* but the return type will be the union of ALL their corresponding keys.
*/
export function enumValueToKey<T extends EnumOrObject, V extends EnumValues<T>>(
object: NormalEnum<T>,
val: V,
): InferKeys<T, V> {
for (const [key, value] of Object.entries(object)) {
if (val === value) {
return key as InferKeys<T, V>;
}
}
throw new Error(`Invalid value passed to \`enumValueToKey\`! Value: ${val}`);
}

View File

@ -1,5 +1,4 @@
import { AbilityId } from "#enums/ability-id";
import { BattleType } from "#enums/battle-type";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
@ -24,6 +23,7 @@ describe("Abilities - Intimidate", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.criticalHits(false)
.battleStyle("single")
.enemySpecies(SpeciesId.RATTATA)
.enemyAbility(AbilityId.INTIMIDATE)
@ -55,8 +55,8 @@ describe("Abilities - Intimidate", () => {
});
it("should not trigger on switching moves used by wild Pokemon", async () => {
game.override.enemyMoveset(MoveId.VOLT_SWITCH).battleType(BattleType.WILD);
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
game.override.enemyMoveset(MoveId.VOLT_SWITCH);
await game.classicMode.startBattle([SpeciesId.VENUSAUR]);
const player = game.field.getPlayerPokemon();
expect(player.getStatStage(Stat.ATK)).toBe(-1);
@ -69,7 +69,7 @@ describe("Abilities - Intimidate", () => {
});
it("should trigger on moves that switch user/target out during trainer battles", async () => {
game.override.battleType(BattleType.TRAINER).startingWave(50);
game.override.startingWave(5).enemyLevel(100);
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
const player = game.field.getPlayerPokemon();

View File

@ -159,7 +159,8 @@ describe("Abilities - Magic Bounce", () => {
expect(game.scene.getEnemyPokemon()!.getTag(BattlerTagType.CURSED)).toBeDefined();
});
it("should not cause encore to be interrupted after bouncing", async () => {
// TODO: enable when Magic Bounce is fixed to properly reset the hit count
it.todo("should not cause encore to be interrupted after bouncing", async () => {
game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]).enemyMoveset([MoveId.TACKLE, MoveId.GROWL]);
// game.override.ability(AbilityId.MOLD_BREAKER);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -167,7 +168,7 @@ describe("Abilities - Magic Bounce", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!;
// Give the player MOLD_BREAKER for this turn to bypass Magic Bounce.
vi.spyOn(playerPokemon, "getAbility").mockReturnValue(allAbilities[AbilityId.MOLD_BREAKER]);
const playerAbilitySpy = game.field.mockAbility(playerPokemon, AbilityId.MOLD_BREAKER);
// turn 1
game.move.select(MoveId.ENCORE);
@ -177,7 +178,7 @@ describe("Abilities - Magic Bounce", () => {
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(MoveId.TACKLE);
// turn 2
vi.spyOn(playerPokemon, "getAbility").mockRestore();
playerAbilitySpy.mockRestore();
game.move.select(MoveId.GROWL);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("BerryPhase");

View File

@ -101,7 +101,8 @@ describe("BattlerTag - StockpilingTag", () => {
});
describe("stack limit, stat tracking, and removal", () => {
it("can be added up to three times, even when one stat does not change", async () => {
// TODO: do we even want this file at all? regardless, this test is broken and is also likely unimportant
it.todo("can be added up to three times, even when one stat does not change", async () => {
const mockPokemon = {
summonData: new PokemonSummonData(),
getBattlerIndex: () => 0,
@ -150,7 +151,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect(subject.stockpiledCount).toBe(3);
vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(_phase => {
throw new Error("Should not be called a fourth time");
expect.fail("Should not be called a fourth time");
});
// fourth stack should not be applied

View File

@ -2,10 +2,9 @@ import { AbilityId } from "#enums/ability-id";
import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { RandomMoveAttr } from "#moves/move";
import { GameManager } from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Ability-Ignoring Moves", () => {
let phaserGame: Phaser.Game;
@ -57,7 +56,7 @@ describe("Moves - Ability-Ignoring Moves", () => {
it("should not ignore enemy abilities when called by Metronome", async () => {
await game.classicMode.startBattle([SpeciesId.MILOTIC]);
vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.PHOTON_GEYSER);
game.move.forceMetronomeMove(MoveId.PHOTON_GEYSER, true);
const enemy = game.field.getEnemyPokemon();
game.move.select(MoveId.METRONOME);

View File

@ -5,10 +5,9 @@ import { MoveResult } from "#enums/move-result";
import { MoveUseMode } from "#enums/move-use-mode";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { RandomMoveAttr } from "#moves/move";
import { GameManager } from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Copycat", () => {
let phaserGame: Phaser.Game;
@ -65,7 +64,7 @@ describe("Moves - Copycat", () => {
it("should copy the called move when the last move successfully calls another", async () => {
game.override.moveset([MoveId.SPLASH, MoveId.METRONOME]).enemyMoveset(MoveId.COPYCAT);
await game.classicMode.startBattle([SpeciesId.DRAMPA]);
vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.SWORDS_DANCE);
game.move.forceMetronomeMove(MoveId.SWORDS_DANCE, true);
game.move.select(MoveId.METRONOME);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); // Player moves first so enemy can copy Swords Dance

View File

@ -1,18 +1,16 @@
import { allMoves } from "#data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { BattleType } from "#enums/battle-type";
import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { MoveUseMode } from "#enums/move-use-mode";
import { SpeciesId } from "#enums/species-id";
import { GameManager } from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, type MockInstance, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Fishious Rend & Bolt Beak", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let powerSpy: MockInstance;
beforeAll(() => {
phaserGame = new Phaser.Game({
@ -29,21 +27,19 @@ describe("Moves - Fishious Rend & Bolt Beak", () => {
game.override
.ability(AbilityId.STURDY)
.battleStyle("single")
.battleType(BattleType.TRAINER)
.startingWave(5)
.criticalHits(false)
.enemyLevel(100)
.enemySpecies(SpeciesId.DRACOVISH)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);
powerSpy = vi.spyOn(allMoves[MoveId.BOLT_BEAK], "calculateBattlePower");
});
it.each<{ name: string; move: MoveId }>([
{ name: "Bolt Beak", move: MoveId.BOLT_BEAK },
{ name: "Fishious Rend", move: MoveId.FISHIOUS_REND },
])("$name should double power if the user moves before the target", async ({ move }) => {
powerSpy = vi.spyOn(allMoves[move], "calculateBattlePower");
const powerSpy = vi.spyOn(allMoves[move], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
// turn 1: enemy, then player (no boost)
@ -63,6 +59,7 @@ describe("Moves - Fishious Rend & Bolt Beak", () => {
it("should only consider the selected target in Double Battles", async () => {
game.override.battleStyle("double");
const powerSpy = vi.spyOn(allMoves[MoveId.BOLT_BEAK], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]);
// Use move after everyone but P1 and enemy 1 have already moved
@ -76,6 +73,7 @@ describe("Moves - Fishious Rend & Bolt Beak", () => {
it("should double power on the turn the target switches in", async () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
const powerSpy = vi.spyOn(allMoves[MoveId.BOLT_BEAK], "calculateBattlePower");
game.move.use(MoveId.BOLT_BEAK);
game.forceEnemyToSwitch();
@ -86,6 +84,7 @@ describe("Moves - Fishious Rend & Bolt Beak", () => {
it("should double power on forced switch-induced sendouts", async () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
const powerSpy = vi.spyOn(allMoves[MoveId.BOLT_BEAK], "calculateBattlePower");
game.move.use(MoveId.BOLT_BEAK);
await game.move.forceEnemyMove(MoveId.U_TURN);
@ -100,7 +99,7 @@ describe("Moves - Fishious Rend & Bolt Beak", () => {
{ type: "an Instructed", allyMove: MoveId.INSTRUCT },
])("should double power if $type move is used as the target's first action that turn", async ({ allyMove }) => {
game.override.battleStyle("double").enemyAbility(AbilityId.DANCER);
powerSpy = vi.spyOn(allMoves[MoveId.FISHIOUS_REND], "calculateBattlePower");
const powerSpy = vi.spyOn(allMoves[MoveId.FISHIOUS_REND], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.DRACOVISH, SpeciesId.ARCTOZOLT]);
// Simulate enemy having used splash last turn to allow Instruct to copy it

View File

@ -20,8 +20,8 @@ import {
} from "#test/mystery-encounter/encounter-test-utils";
import { GameManager } from "#test/testUtils/gameManager";
import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate";
import { TrainerConfig } from "#trainers/trainer-config";
import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template";
import { RewardSelectUiHandler } from "#ui/reward-select-ui-handler";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

View File

@ -1,4 +1,5 @@
import Overrides from "#app/overrides";
import { allMoves } from "#data/data-lists";
import { BattlerIndex } from "#enums/battler-index";
import { Command } from "#enums/command";
import { MoveId } from "#enums/move-id";
@ -12,6 +13,7 @@ import type { EnemyCommandPhase } from "#phases/enemy-command-phase";
import { MoveEffectPhase } from "#phases/move-effect-phase";
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
import { coerceArray, toReadableString } from "#utils/common";
import type { MockInstance } from "vitest";
import { expect, vi } from "vitest";
/**
@ -305,4 +307,20 @@ export class MoveHelper extends GameManagerHelper {
*/
await this.game.phaseInterceptor.to("EnemyCommandPhase");
}
/**
* Force the move used by Metronome to be a specific move.
* @param move - The move to force metronome to use
* @param once - If `true`, uses {@linkcode MockInstance#mockReturnValueOnce} when mocking, else uses {@linkcode MockInstance#mockReturnValue}.
* @returns The spy that for Metronome that was mocked (Usually unneeded).
*/
public forceMetronomeMove(move: MoveId, once = false): MockInstance {
const spy = vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMoveOverride");
if (once) {
spy.mockReturnValueOnce(move);
} else {
spy.mockReturnValue(move);
}
return spy;
}
}

View File

@ -1,4 +1,6 @@
import type { MockGameObject } from "#test/testUtils/mocks/mockGameObject";
import { MockBBCodeText } from "#test/testUtils/mocks/mocksContainer/mock-bbcode-text";
import { MockInputText } from "#test/testUtils/mocks/mocksContainer/mock-input-text";
import { MockContainer } from "#test/testUtils/mocks/mocksContainer/mockContainer";
import { MockImage } from "#test/testUtils/mocks/mocksContainer/mockImage";
import { MockNineslice } from "#test/testUtils/mocks/mocksContainer/mockNineslice";
@ -33,6 +35,8 @@ export class MockTextureManager {
image: this.image.bind(this),
polygon: this.polygon.bind(this),
text: this.text.bind(this),
rexBBCodeText: this.rexBBCodeText.bind(this),
rexInputText: this.rexInputText.bind(this),
bitmapText: this.text.bind(this),
displayList: this.displayList,
video: () => new MockVideoGameObject(),
@ -103,9 +107,25 @@ export class MockTextureManager {
return text;
}
rexBBCodeText(x, y, content, styleOptions) {
const text = new MockBBCodeText(this, x, y, content, styleOptions);
this.list.push(text);
return text;
}
rexInputText(x, y, w, h, content, styleOptions) {
const text = new MockInputText(this, x, y, w, h, content, styleOptions);
this.list.push(text);
return text;
}
polygon(x, y, content, fillColor, fillAlpha) {
const polygon = new MockPolygon(this, x, y, content, fillColor, fillAlpha);
this.list.push(polygon);
return polygon;
}
exists(key: string): boolean {
return this.textures.has(key);
}
}

View File

@ -0,0 +1,6 @@
import { MockText } from "#test/testUtils/mocks/mocksContainer/mockText";
export class MockBBCodeText extends MockText {
setMaxLines(_lines: number) {}
setWrapMode(_mode: 0 | 1 | 2 | 3 | "none" | "word" | "char" | "character" | "mix") {}
}

View File

@ -0,0 +1,24 @@
import { MockText } from "#test/testUtils/mocks/mocksContainer/mockText";
export class MockInputText extends MockText {
public inputType: string;
public selectionStart: number;
public selectionEnd: number;
public selectedText: string;
constructor(textureManager, x, y, _w, _h, content, styleOptions) {
super(textureManager, x, y, content, styleOptions);
}
selectText(_selectionStart?: number, _selectionEnd?: number) {}
selectAll() {}
setCursorPosition(_value: number) {}
scrollToBottom() {}
resize(_width: number, _height: number) {}
setElement(_element, _style, _innerText) {}
}

View File

@ -0,0 +1,104 @@
import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types";
import type { enumValueToKey, getEnumKeys, getEnumValues } from "#app/utils/enums";
import { describe, expectTypeOf, it } from "vitest";
enum testEnumNum {
testN1 = 1,
testN2 = 2,
}
enum testEnumString {
testS1 = "apple",
testS2 = "banana",
}
const testObjNum = { testON1: 1, testON2: 2 } as const;
const testObjString = { testOS1: "apple", testOS2: "banana" } as const;
describe("Enum Type Helpers", () => {
describe("EnumValues", () => {
it("should go from enum object type to value type", () => {
expectTypeOf<EnumValues<typeof testEnumNum>>().toEqualTypeOf<testEnumNum>();
expectTypeOf<EnumValues<typeof testEnumNum>>().branded.toEqualTypeOf<1 | 2>();
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString>();
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString.testS1 | testEnumString.testS2>();
expectTypeOf<EnumValues<typeof testEnumString>>().toMatchTypeOf<"apple" | "banana">();
});
it("should produce union of const object values as type", () => {
expectTypeOf<EnumValues<typeof testObjNum>>().toEqualTypeOf<1 | 2>();
expectTypeOf<EnumValues<typeof testObjString>>().toEqualTypeOf<"apple" | "banana">();
});
});
describe("TSNumericEnum", () => {
it("should match numeric enums", () => {
expectTypeOf<TSNumericEnum<typeof testEnumNum>>().toEqualTypeOf<typeof testEnumNum>();
});
it("should not match string enums or const objects", () => {
expectTypeOf<TSNumericEnum<typeof testEnumString>>().toBeNever();
expectTypeOf<TSNumericEnum<typeof testObjNum>>().toBeNever();
expectTypeOf<TSNumericEnum<typeof testObjString>>().toBeNever();
});
});
describe("NormalEnum", () => {
it("should match string enums or const objects", () => {
expectTypeOf<NormalEnum<typeof testEnumString>>().toEqualTypeOf<typeof testEnumString>();
expectTypeOf<NormalEnum<typeof testObjNum>>().toEqualTypeOf<typeof testObjNum>();
expectTypeOf<NormalEnum<typeof testObjString>>().toEqualTypeOf<typeof testObjString>();
});
it("should not match numeric enums", () => {
expectTypeOf<NormalEnum<typeof testEnumNum>>().toBeNever();
});
});
describe("EnumOrObject", () => {
it("should match any enum or const object", () => {
expectTypeOf<typeof testEnumNum>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testEnumString>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testObjNum>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testObjString>().toMatchTypeOf<EnumOrObject>();
});
it("should not match an enum value union w/o typeof", () => {
expectTypeOf<testEnumNum>().not.toMatchTypeOf<EnumOrObject>();
expectTypeOf<testEnumString>().not.toMatchTypeOf<EnumOrObject>();
});
it("should be equivalent to `TSNumericEnum | NormalEnum`", () => {
expectTypeOf<EnumOrObject>().branded.toEqualTypeOf<TSNumericEnum<EnumOrObject> | NormalEnum<EnumOrObject>>();
});
});
});
describe("Enum Functions", () => {
describe("getEnumKeys", () => {
it("should retrieve keys of numeric enum", () => {
expectTypeOf<typeof getEnumKeys<typeof testEnumNum>>().returns.toEqualTypeOf<("testN1" | "testN2")[]>();
});
});
describe("getEnumValues", () => {
it("should retrieve values of numeric enum", () => {
expectTypeOf<typeof getEnumValues<typeof testEnumNum>>().returns.branded.toEqualTypeOf<(1 | 2)[]>();
});
});
describe("enumValueToKey", () => {
it("should retrieve values for a given key", () => {
expectTypeOf<
typeof enumValueToKey<typeof testEnumString, testEnumString.testS1>
>().returns.toEqualTypeOf<"testS1">();
expectTypeOf<typeof enumValueToKey<typeof testEnumString, testEnumString>>().returns.toEqualTypeOf<
"testS1" | "testS2"
>();
expectTypeOf<typeof enumValueToKey<typeof testObjNum, 1>>().returns.toEqualTypeOf<"testON1">();
expectTypeOf<typeof enumValueToKey<typeof testObjNum, 1 | 2>>().returns.toEqualTypeOf<"testON1" | "testON2">();
});
});
});

View File

@ -48,11 +48,5 @@
"outDir": "./build",
"noEmit": true
},
"typedocOptions": {
"entryPoints": ["./src"],
"entryPointStrategy": "expand",
"exclude": "**/*+.test.ts",
"out": "typedoc"
},
"exclude": ["node_modules", "dist", "vite.config.ts", "vitest.config.ts", "vitest.workspace.ts"]
}

14
tsdoc.json Normal file
View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/tsdoc/v0/tsdoc.schema.json",
"noStandardTags": false,
"tagDefinitions": [
{
"tagName": "@todo",
"syntaxKind": "block"
},
{
"tagName": "@linkcode",
"syntaxKind": "inline"
}
]
}

7
typedoc.json Normal file
View File

@ -0,0 +1,7 @@
{
"entryPoints": ["./src"],
"entryPointStrategy": "expand",
"exclude": ["**/*+.test.ts"],
"out": "typedoc",
"highlightLanguages": ["javascript", "json", "jsonc", "json5", "tsx", "typescript", "markdown"]
}

View File

@ -2,31 +2,13 @@ import { defineProject } from "vitest/config";
import { BaseSequencer, type TestSpecification } from "vitest/node";
import { defaultConfig } from "./vite.config";
function getTestOrder(testName: string): number {
if (testName.includes("battle-scene.test.ts")) {
return 1;
}
if (testName.includes("inputs.test.ts")) {
return 2;
}
return 3;
}
export default defineProject(({ mode }) => ({
...defaultConfig,
test: {
testTimeout: 20000,
setupFiles: ["./test/fontFace.setup.ts", "./test/vitest.setup.ts"],
sequence: {
sequencer: class CustomSequencer extends BaseSequencer {
async sort(files: TestSpecification[]) {
// use default sorting at first.
files = await super.sort(files);
// Except, forcibly reorder
return files.sort((a, b) => getTestOrder(a.moduleId) - getTestOrder(b.moduleId));
}
},
sequencer: MySequencer,
},
environment: "jsdom" as const,
environmentOptions: {
@ -34,6 +16,10 @@ export default defineProject(({ mode }) => ({
resources: "usable",
},
},
typecheck: {
tsconfig: "tsconfig.json",
include: ["./test/types/**/*.{test,spec}{-|.}d.ts"],
},
threads: false,
trace: true,
restoreMocks: true,
@ -51,3 +37,38 @@ export default defineProject(({ mode }) => ({
keepNames: true,
},
}));
//#region Helpers
/**
* Class for sorting test files in the desired order.
*/
class MySequencer extends BaseSequencer {
async sort(files: TestSpecification[]) {
files = await super.sort(files);
return files.sort((a, b) => {
const aTestOrder = getTestOrder(a.moduleId);
const bTestOrder = getTestOrder(b.moduleId);
return aTestOrder - bTestOrder;
});
}
}
/**
* A helper function for sorting test files in a desired order.
*
* A lower number means that a test file must be run earlier,
* or else it breaks due to running tests with `--no-isolate.`
*/
function getTestOrder(testName: string): number {
if (testName.includes("battle-scene.test.ts")) {
return 1;
}
if (testName.includes("inputs.test.ts")) {
return 2;
}
return 3;
}
//#endregion

View File

@ -1,14 +0,0 @@
import { defineWorkspace } from "vitest/config";
import { defaultConfig } from "./vite.config";
export default defineWorkspace([
{
...defaultConfig,
test: {
name: "pre",
include: ["./test/pre.test.ts"],
environment: "jsdom",
},
},
"./vitest.config.ts",
]);