mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-07 16:09:27 +02:00
Merge branch 'beta' into rest
This commit is contained in:
commit
b3eea02779
2
.github/workflows/deploy-beta.yml
vendored
2
.github/workflows/deploy-beta.yml
vendored
@ -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
|
||||
|
32
package.json
32
package.json
@ -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"
|
||||
|
1987
pnpm-lock.yaml
1987
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
18
src/@types/enum-types.ts
Normal file
18
src/@types/enum-types.ts
Normal 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>>;
|
@ -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 { PersistentModifier } from "#modifiers/modifier";
|
||||
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;
|
||||
|
@ -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];
|
||||
|
@ -140,7 +140,6 @@ import {
|
||||
type Constructor,
|
||||
fixedInt,
|
||||
formatMoney,
|
||||
getEnumValues,
|
||||
getIvsFromId,
|
||||
isBetween,
|
||||
isNullOrUndefined,
|
||||
@ -150,6 +149,7 @@ import {
|
||||
shiftCharCodes,
|
||||
} from "#utils/common";
|
||||
import { deepMergeSpriteData } from "#utils/data";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { getModifierPoolForType, getModifierType } 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)];
|
||||
}
|
||||
|
||||
@ -3726,6 +3728,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);
|
||||
|
371
src/battle.ts
371
src/battle.ts
@ -5,12 +5,9 @@ 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 { ModifierTier } from "#enums/modifier-tier";
|
||||
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 { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
@ -26,7 +23,6 @@ import { MusicPreference } from "#system/settings";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import {
|
||||
getEnumValues,
|
||||
NumberHolder,
|
||||
randInt,
|
||||
randomString,
|
||||
@ -35,6 +31,7 @@ import {
|
||||
randSeedItem,
|
||||
shiftCharCodes,
|
||||
} from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
@ -577,369 +574,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: [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,
|
||||
}),
|
||||
};
|
||||
|
@ -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 ])[]
|
||||
|
@ -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 ],
|
||||
@ -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`;
|
||||
}
|
||||
}
|
||||
|
@ -1630,7 +1630,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)
|
||||
@ -1845,7 +1845,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)
|
||||
|
@ -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)) {
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -87,7 +87,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, coerceArray, type Constructor, getEnumValues, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
|
||||
import { BooleanHolder, coerceArray, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,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";
|
||||
@ -310,7 +311,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 = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
@ -44,8 +44,8 @@ import {
|
||||
HeldItemRequirement,
|
||||
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";
|
||||
@ -289,6 +289,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)]));
|
||||
@ -386,6 +387,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
specialOptions.push(rareFormChangeModifier);
|
||||
}
|
||||
if (specialOptions.length > 0) {
|
||||
// TODO: should this use `randSeedItem`?
|
||||
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,8 @@ import {
|
||||
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";
|
||||
@ -138,6 +138,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 };
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -28,7 +28,8 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou
|
||||
import { PokemonData } from "#system/pokemon-data";
|
||||
import type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
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 */
|
||||
|
@ -36,8 +36,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 type { HeldModifierConfig } from "#types/held-modifier-config";
|
||||
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
@ -305,6 +305,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);
|
||||
@ -466,6 +467,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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1098,8 +1098,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)];
|
||||
}
|
||||
}
|
||||
|
@ -110,20 +110,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];
|
||||
}
|
||||
@ -517,7 +521,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);
|
||||
|
@ -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);
|
||||
|
376
src/data/trainers/fixed-battle-configs.ts
Normal file
376
src/data/trainers/fixed-battle-configs.ts
Normal 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,
|
||||
}),
|
||||
};
|
@ -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,
|
||||
|
@ -143,6 +143,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") {
|
||||
@ -154,6 +155,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];
|
||||
@ -166,19 +168,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,38 +490,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:
|
||||
|
@ -155,7 +155,6 @@ import {
|
||||
coerceArray,
|
||||
deltaRgb,
|
||||
fixedInt,
|
||||
getEnumValues,
|
||||
getIvsFromId,
|
||||
isBetween,
|
||||
isNullOrUndefined,
|
||||
@ -169,6 +168,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";
|
||||
@ -2525,41 +2525,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;
|
||||
@ -2567,6 +2576,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)) {
|
||||
@ -2576,15 +2588,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 {
|
||||
|
@ -14,10 +14,13 @@ import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import type { EnemyPokemon } from "#field/pokemon";
|
||||
import type { PersistentModifier } from "#modifiers/modifier";
|
||||
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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -21,7 +21,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 {
|
||||
|
@ -116,15 +116,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, getModifierType } from "#utils/modifier-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -1524,6 +1517,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]);
|
||||
});
|
||||
@ -1577,6 +1571,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: should this use `randSeedItem`?
|
||||
return new EvolutionItemModifierType(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
|
||||
});
|
||||
}
|
||||
@ -1662,6 +1657,7 @@ export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: should this use `randSeedItem`?
|
||||
return new FormChangeItemModifierType(formChangeItemPool[randSeedInt(formChangeItemPool.length)]);
|
||||
});
|
||||
}
|
||||
@ -1932,7 +1928,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: () =>
|
||||
|
@ -120,7 +120,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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -56,6 +56,7 @@ export class SelectBiomePhase extends BattlePhase {
|
||||
delay: 1000,
|
||||
});
|
||||
} else {
|
||||
// TODO: should this use `randSeedItem`?
|
||||
setNextBiome(biomes[randSeedInt(biomes.length)]);
|
||||
}
|
||||
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
||||
|
@ -179,7 +179,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
} else {
|
||||
this.applyModifier(modifierType.newModifier()!);
|
||||
}
|
||||
return !cost;
|
||||
return cost === -1;
|
||||
}
|
||||
|
||||
// Reroll rewards
|
||||
|
@ -62,8 +62,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";
|
||||
|
@ -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 {
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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);
|
||||
|
@ -269,13 +269,22 @@ export class ModifierSelectUiHandler 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];
|
||||
@ -286,67 +295,77 @@ export class ModifierSelectUiHandler 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];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -687,7 +706,11 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||
scale: 0.01,
|
||||
duration: 250,
|
||||
ease: "Cubic.easeIn",
|
||||
onComplete: () => options.forEach(o => o.destroy()),
|
||||
onComplete: () => {
|
||||
options.forEach(o => {
|
||||
o.destroy();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
[
|
||||
@ -819,7 +842,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,
|
||||
@ -832,41 +855,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);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
|
@ -29,7 +29,6 @@ import { UiHandler } from "#ui/ui-handler";
|
||||
import {
|
||||
fixedInt,
|
||||
formatStat,
|
||||
getEnumValues,
|
||||
getLocalizedSpriteKey,
|
||||
getShinyDescriptor,
|
||||
isNullOrUndefined,
|
||||
@ -37,6 +36,7 @@ import {
|
||||
rgbHexToRgba,
|
||||
toReadableString,
|
||||
} from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { argbFromRgba } from "@material/material-color-utilities";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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 executeIf<T>(condition: boolean, promiseFunc: () => Promise<T>): Promise<T | null> {
|
||||
return condition ? promiseFunc() : new Promise<T | null>(resolve => resolve(null));
|
||||
}
|
||||
@ -644,25 +640,3 @@ export function coerceArray<T>(input: T): T extends any[] ? T : [T];
|
||||
export function coerceArray<T>(input: T): T | [T] {
|
||||
return Array.isArray(input) ? input : [input];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
|
74
src/utils/enums.ts
Normal file
74
src/utils/enums.ts
Normal 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}`);
|
||||
}
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,5 @@
|
||||
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";
|
||||
@ -28,7 +27,7 @@ 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)
|
||||
|
@ -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 { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
6
test/testUtils/mocks/mocksContainer/mock-bbcode-text.ts
Normal file
6
test/testUtils/mocks/mocksContainer/mock-bbcode-text.ts
Normal 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") {}
|
||||
}
|
24
test/testUtils/mocks/mocksContainer/mock-input-text.ts
Normal file
24
test/testUtils/mocks/mocksContainer/mock-input-text.ts
Normal 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) {}
|
||||
}
|
104
test/types/enum-types.test-d.ts
Normal file
104
test/types/enum-types.test-d.ts
Normal 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">();
|
||||
});
|
||||
});
|
||||
});
|
@ -47,11 +47,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
14
tsdoc.json
Normal 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
7
typedoc.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"entryPoints": ["./src"],
|
||||
"entryPointStrategy": "expand",
|
||||
"exclude": ["**/*+.test.ts"],
|
||||
"out": "typedoc",
|
||||
"highlightLanguages": ["javascript", "json", "jsonc", "json5", "tsx", "typescript", "markdown"]
|
||||
}
|
@ -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
|
||||
|
@ -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",
|
||||
]);
|
Loading…
Reference in New Issue
Block a user