Merge branch 'beta' of https://github.com/pagefaultgames/pokerogue into pokeutils

This commit is contained in:
AJ Fontaine 2025-07-27 21:27:26 -04:00
commit e47eee9991
110 changed files with 891 additions and 453 deletions

View File

@ -11,17 +11,18 @@ _cfg: &cfg
ls:
<<: *cfg
src:
src: &src
<<: *cfg
.dir: kebab-case | regex:@types
.js: exists:0
src/system/version-migration/versions:
.ts: snake_case
<<: *cfg
test: *src
ignore:
- node_modules
- .vscode
- .github
- .git
- public
- dist

View File

@ -177,9 +177,10 @@
}
},
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes),
// as well as in all TS files in `scripts/` (which are assumed to be boilerplate templates).
{
"includes": ["**/src/overrides.ts", "**/src/enums/**/*"],
"includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/scripts/**/*.ts"],
"linter": {
"rules": {
"correctness": {
@ -189,7 +190,7 @@
}
},
{
"includes": ["**/src/overrides.ts"],
"includes": ["**/src/overrides.ts", "**/scripts/**/*.ts"],
"linter": {
"rules": {
"style": {

View File

@ -32,6 +32,7 @@
"@types/jsdom": "^21.1.7",
"@types/node": "^22.16.3",
"@vitest/coverage-istanbul": "^3.2.4",
"@vitest/expect": "^3.2.4",
"chalk": "^5.4.1",
"dependency-cruiser": "^16.10.4",
"inquirer": "^12.7.0",

View File

@ -57,6 +57,9 @@ importers:
'@vitest/coverage-istanbul':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0))
'@vitest/expect':
specifier: ^3.2.4
version: 3.2.4
chalk:
specifier: ^5.4.1
version: 5.4.1

@ -1 +1 @@
Subproject commit 362b2c4fcc20b31a7be6c2dab537055fbaeb247f
Subproject commit e2fbba17ea7a96068970ea98a8a84ed3e25b6f07

View File

@ -44,20 +44,22 @@ function getTestFolderPath(...folders) {
* @returns {Promise<{selectedOption: {label: string, dir: string}}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create?",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
},
]);
const typeAnswer = await inquirer
.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create?",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), { name: "EXIT", value: "N/A" }],
},
])
.then(ans => ans.selectedOption);
if (typeAnswer.selectedOption === "EXIT") {
if (typeAnswer.name === "EXIT") {
console.log("Exiting...");
return process.exit();
return process.exit(0);
}
if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) {
if (!choices.some(choice => choice.dir === typeAnswer.dir)) {
console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`);
return await promptTestType();
}

View File

@ -75,3 +75,14 @@ export type NonFunctionPropertiesRecursive<Class> = {
};
export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
/**
* Type helper that iterates through the fields of the type and coerces any `null` properties to `undefined` (including in union types).
*
* @remarks
* This is primarily useful when an object with nullable properties wants to be serialized and have its `null`
* properties coerced to `undefined`.
*/
export type CoerceNullPropertiesToUndefined<T extends object> = {
[K in keyof T]: null extends T[K] ? Exclude<T[K], null> | undefined : T[K];
};

10
src/@types/ui.ts Normal file
View File

@ -0,0 +1,10 @@
import type Phaser from "phaser";
import type InputText from "phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText";
export interface TextStyleOptions {
scale: number;
styleOptions: Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig;
shadowColor: string;
shadowXpos: number;
shadowYpos: number;
}

View File

@ -67,6 +67,7 @@ import { PokemonType } from "#enums/pokemon-type";
import { ShopCursorTarget } from "#enums/shop-cursor-target";
import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect";
import { TextStyle } from "#enums/text-style";
import type { TrainerSlot } from "#enums/trainer-slot";
import { TrainerType } from "#enums/trainer-type";
import { TrainerVariant } from "#enums/trainer-variant";
@ -132,7 +133,7 @@ import { CharSprite } from "#ui/char-sprite";
import { PartyExpBar } from "#ui/party-exp-bar";
import { PokeballTray } from "#ui/pokeball-tray";
import { PokemonInfoContainer } from "#ui/pokemon-info-container";
import { addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addTextObject, getTextColor } from "#ui/text";
import { UI } from "#ui/ui";
import { addUiThemeOverrides } from "#ui/ui-theme";
import {

View File

@ -1,8 +1,8 @@
import { allMoves } from "#data/data-lists";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { toReadableString } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
import { toTitleCase } from "#utils/strings";
export const speciesEggMoves = {
[SpeciesId.BULBASAUR]: [ MoveId.SAPPY_SEED, MoveId.MALIGNANT_CHAIN, MoveId.EARTH_POWER, MoveId.MATCHA_GOTCHA ],
@ -617,7 +617,7 @@ function parseEggMoves(content: string): void {
}
if (eggMoves.every(m => m === MoveId.NONE)) {
console.warn(`Species ${toReadableString(SpeciesId[species])} could not be parsed, excluding from output...`)
console.warn(`Species ${toTitleCase(SpeciesId[species])} could not be parsed, excluding from output...`)
} else {
output += `[SpeciesId.${SpeciesId[species]}]: [ ${eggMoves.map(m => `MoveId.${MoveId[m]}`).join(", ")} ],\n`;
}

View File

@ -7,8 +7,9 @@ import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } fro
import { MoveFlags } from "#enums/move-flags";
import { MoveId } from "#enums/move-id";
import type { Pokemon } from "#field/pokemon";
import { animationFileName, coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common";
import { coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common";
import { getEnumKeys, getEnumValues } from "#utils/enums";
import { toKebabCase } from "#utils/strings";
import Phaser from "phaser";
export class AnimConfig {
@ -412,7 +413,7 @@ export function initCommonAnims(): Promise<void> {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/_/g, "-")}.json`)
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimNames[ca])}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
);
@ -450,7 +451,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
const fetchAnimAndResolve = (move: MoveId) => {
globalScene
.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
.cachedFetch(`./battle-anims/${toKebabCase(MoveId[move])}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
@ -506,7 +507,7 @@ function useDefaultAnim(move: MoveId, defaultMoveAnim: MoveId) {
* @remarks use {@linkcode useDefaultAnim} to use a default animation
*/
function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
const moveName = animationFileName(move);
const moveName = toKebabCase(MoveId[move]);
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
}
@ -524,7 +525,7 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte
}
encounterAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/_/g, "-")}.json`)
.cachedFetch(`./battle-anims/encounter-${toKebabCase(encounterAnimNames[anim])}.json`)
.then(response => response.json())
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))),
);
@ -548,7 +549,7 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
} else {
chargeAnims.set(chargeAnim, null);
globalScene
.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/_/g, "-")}.json`)
.cachedFetch(`./battle-anims/${toKebabCase(ChargeAnim[chargeAnim])}.json`)
.then(response => response.json())
.then(ca => {
if (Array.isArray(ca)) {
@ -1405,7 +1406,9 @@ export async function populateAnims() {
const chargeAnimIds = getEnumValues(ChargeAnim);
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
const moveNameToId = {};
// Exclude MoveId.NONE;
for (const move of getEnumValues(MoveId).slice(1)) {
// KARATE_CHOP => KARATECHOP
const moveName = MoveId[move].toUpperCase().replace(/_/g, "");
moveNameToId[moveName] = move;
}

View File

@ -26,6 +26,7 @@ import type { DexAttrProps, GameData } from "#system/game-data";
import { BooleanHolder, type NumberHolder, randSeedItem } from "#utils/common";
import { deepCopy } from "#utils/data";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
import { toCamelCase, toSnakeCase } from "#utils/strings";
import i18next from "i18next";
/** A constant for the default max cost of the starting party before a run */
@ -66,14 +67,11 @@ export abstract class Challenge {
}
/**
* Gets the localisation key for the challenge
* @returns {@link string} The i18n key for this challenge
* Gets the localization key for the challenge
* @returns The i18n key for this challenge as camel case.
*/
geti18nKey(): string {
return Challenges[this.id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
return toCamelCase(Challenges[this.id]);
}
/**
@ -104,23 +102,22 @@ export abstract class Challenge {
}
/**
* Returns the textual representation of a challenge's current value.
* @param overrideValue {@link number} The value to check for. If undefined, gets the current value.
* @returns {@link string} The localised name for the current value.
* Return the textual representation of a challenge's current value.
* @param overrideValue - The value to check for; default {@linkcode this.value}
* @returns The localised text for the current value.
*/
getValue(overrideValue?: number): string {
const value = overrideValue ?? this.value;
return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
getValue(overrideValue: number = this.value): string {
return i18next.t(`challenges:${this.geti18nKey()}.value.${overrideValue}`);
}
/**
* Returns the description of a challenge's current value.
* @param overrideValue {@link number} The value to check for. If undefined, gets the current value.
* @returns {@link string} The localised description for the current value.
* Return the description of a challenge's current value.
* @param overrideValue - The value to check for; default {@linkcode this.value}
* @returns The localised description for the current value.
*/
getDescription(overrideValue?: number): string {
const value = overrideValue ?? this.value;
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
// TODO: Do we need an override value here? it's currently unused
getDescription(overrideValue: number = this.value): string {
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${overrideValue}`, `challenges:${this.geti18nKey()}.desc`])}`;
}
/**
@ -578,31 +575,19 @@ export class SingleGenerationChallenge extends Challenge {
return this.value > 0 ? 1 : 0;
}
/**
* Returns the textual representation of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: number): string {
const value = overrideValue ?? this.value;
if (value === 0) {
getValue(overrideValue: number = this.value): string {
if (overrideValue === 0) {
return i18next.t("settings:off");
}
return i18next.t(`starterSelectUiHandler:gen${value}`);
return i18next.t(`starterSelectUiHandler:gen${overrideValue}`);
}
/**
* Returns the description of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: number): string {
const value = overrideValue ?? this.value;
if (value === 0) {
getDescription(overrideValue: number = this.value): string {
if (overrideValue === 0) {
return i18next.t("challenges:singleGeneration.desc_default");
}
return i18next.t("challenges:singleGeneration.desc", {
gen: i18next.t(`challenges:singleGeneration.gen_${value}`),
gen: i18next.t(`challenges:singleGeneration.gen_${overrideValue}`),
});
}
@ -670,29 +655,13 @@ export class SingleTypeChallenge extends Challenge {
return this.value > 0 ? 1 : 0;
}
/**
* Returns the textual representation of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: number): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return PokemonType[this.value - 1].toLowerCase();
getValue(overrideValue: number = this.value): string {
return toSnakeCase(PokemonType[overrideValue - 1]);
}
/**
* Returns the description of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: number): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`);
const typeColor = `[color=${TypeColor[PokemonType[this.value - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
getDescription(overrideValue: number = this.value): string {
const type = i18next.t(`pokemonInfo:Type.${PokemonType[overrideValue - 1]}`);
const typeColor = `[color=${TypeColor[PokemonType[overrideValue - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
const defaultDesc = i18next.t("challenges:singleType.desc_default");
const typeDesc = i18next.t("challenges:singleType.desc", {
type: typeColor,
@ -831,13 +800,7 @@ export class LowerStarterMaxCostChallenge extends Challenge {
super(Challenges.LOWER_MAX_STARTER_COST, 9);
}
/**
* @override
*/
getValue(overrideValue?: number): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
getValue(overrideValue: number = this.value): string {
return (DEFAULT_PARTY_MAX_COST - overrideValue).toString();
}
@ -865,13 +828,7 @@ export class LowerStarterPointsChallenge extends Challenge {
super(Challenges.LOWER_STARTER_POINTS, 9);
}
/**
* @override
*/
getValue(overrideValue?: number): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
getValue(overrideValue: number = this.value): string {
return (DEFAULT_PARTY_MAX_COST - overrideValue).toString();
}

View File

@ -1,6 +1,7 @@
import { BattleSpec } from "#enums/battle-spec";
import { TrainerType } from "#enums/trainer-type";
import { trainerConfigs } from "#trainers/trainer-config";
import { capitalizeFirstLetter } from "#utils/strings";
export interface TrainerTypeMessages {
encounter?: string | string[];
@ -1755,8 +1756,7 @@ export function initTrainerTypeDialogue(): void {
trainerConfigs[trainerType][`${messageType}Messages`] = messages[0][messageType];
}
if (messages.length > 1) {
trainerConfigs[trainerType][`female${messageType.slice(0, 1).toUpperCase()}${messageType.slice(1)}Messages`] =
messages[1][messageType];
trainerConfigs[trainerType][`female${capitalizeFirstLetter(messageType)}Messages`] = messages[1][messageType];
}
} else {
trainerConfigs[trainerType][`${messageType}Messages`] = messages[messageType];

View File

@ -87,8 +87,9 @@ import type { AttackMoveResult } from "#types/attack-move-result";
import type { Localizable } from "#types/locales";
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
import type { TurnMove } from "#types/turn-move";
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { toTitleCase } from "#utils/strings";
import i18next from "i18next";
/**
@ -8137,7 +8138,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
}
const type = validTypes[user.randBattleSeedInt(validTypes.length)];
user.summonData.types = [ type ];
globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) }));
globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toTitleCase(PokemonType[type]) }));
user.updateInfo();
return true;

View File

@ -1,4 +1,4 @@
import type { TextStyle } from "#ui/text";
import type { TextStyle } from "#enums/text-style";
export class TextDisplay {
speaker?: string;

View File

@ -25,7 +25,8 @@ import {
StatusEffectRequirement,
WaveRangeRequirement,
} from "#mystery-encounters/mystery-encounter-requirements";
import { capitalizeFirstLetter, coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
import { capitalizeFirstLetter } from "#utils/strings";
export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon;

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
import type { TextStyle } from "#enums/text-style";
import { UiTheme } from "#enums/ui-theme";
import type { TextStyle } from "#ui/text";
import { getTextWithColors } from "#ui/text";
import { isNullOrUndefined } from "#utils/common";
import i18next from "i18next";

View File

@ -1,8 +1,9 @@
import { Nature } from "#enums/nature";
import { EFFECTIVE_STATS, getShortenedStatKey, Stat } from "#enums/stat";
import { TextStyle } from "#enums/text-style";
import { UiTheme } from "#enums/ui-theme";
import { getBBCodeFrag, TextStyle } from "#ui/text";
import { toReadableString } from "#utils/common";
import { getBBCodeFrag } from "#ui/text";
import { toTitleCase } from "#utils/strings";
import i18next from "i18next";
export function getNatureName(
@ -12,7 +13,7 @@ export function getNatureName(
ignoreBBCode = false,
uiTheme: UiTheme = UiTheme.DEFAULT,
): string {
let ret = toReadableString(Nature[nature]);
let ret = toTitleCase(Nature[nature]);
//Translating nature
if (i18next.exists(`nature:${ret}`)) {
ret = i18next.t(`nature:${ret}` as any);

View File

@ -28,8 +28,9 @@ import type { Variant, VariantSet } from "#sprites/variant";
import { populateVariantColorCache, variantColorCache, variantData } from "#sprites/variant";
import type { StarterMoveset } from "#system/game-data";
import type { Localizable } from "#types/locales";
import { capitalizeString, isNullOrUndefined, randSeedFloat, randSeedGauss, randSeedInt } from "#utils/common";
import { isNullOrUndefined, randSeedFloat, randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { toCamelCase, toPascalCase } from "#utils/strings";
import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities";
import i18next from "i18next";
@ -819,14 +820,14 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
* @returns the pokemon-form locale key for the single form name ("Alolan Form", "Eternal Flower" etc)
*/
getFormNameToDisplay(formIndex = 0, append = false): string {
const formKey = this.forms?.[formIndex!]?.formKey;
const formText = capitalizeString(formKey, "-", false, false) || "";
const speciesName = capitalizeString(SpeciesId[this.speciesId], "_", true, false);
const formKey = this.forms[formIndex]?.formKey ?? "";
const formText = toPascalCase(formKey);
const speciesName = toCamelCase(SpeciesId[this.speciesId]);
let ret = "";
const region = this.getRegion();
if (this.speciesId === SpeciesId.ARCEUS) {
ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
ret = i18next.t(`pokemonInfo:Type.${formText.toUpperCase()}`);
} else if (
[
SpeciesFormKey.MEGA,
@ -852,7 +853,7 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
if (i18next.exists(i18key)) {
ret = i18next.t(i18key);
} else {
const rootSpeciesName = capitalizeString(SpeciesId[this.getRootSpeciesId()], "_", true, false);
const rootSpeciesName = toCamelCase(SpeciesId[this.getRootSpeciesId()]);
const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`;
ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText;
}

View File

@ -1,18 +1,29 @@
import { type BattlerTag, loadBattlerTag } from "#data/battler-tags";
import { allSpecies } from "#data/data-lists";
import type { Gender } from "#data/gender";
import { PokemonMove } from "#data/moves/pokemon-move";
import type { PokemonSpeciesForm } from "#data/pokemon-species";
import { getPokemonSpeciesForm, type PokemonSpeciesForm } from "#data/pokemon-species";
import type { TypeDamageMultiplier } from "#data/type";
import type { AbilityId } from "#enums/ability-id";
import type { BerryType } from "#enums/berry-type";
import type { MoveId } from "#enums/move-id";
import type { Nature } from "#enums/nature";
import type { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import type { AttackMoveResult } from "#types/attack-move-result";
import type { IllusionData } from "#types/illusion-data";
import type { TurnMove } from "#types/turn-move";
import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers";
import { isNullOrUndefined } from "#utils/common";
/**
* The type that {@linkcode PokemonSpeciesForm} is converted to when an object containing it serializes it.
*/
type SerializedSpeciesForm = {
id: SpeciesId;
formIdx: number;
};
/**
* Permanent data that can customize a Pokemon in non-standard ways from its Species.
* Includes abilities, nature, changed types, etc.
@ -41,9 +52,59 @@ export class CustomPokemonData {
}
}
/**
* Deserialize a pokemon species form from an object containing `id` and `formIdx` properties.
* @param value - The value to deserialize
* @returns The `PokemonSpeciesForm` or `null` if the fields could not be properly discerned
*/
function deserializePokemonSpeciesForm(value: SerializedSpeciesForm | PokemonSpeciesForm): PokemonSpeciesForm | null {
// @ts-expect-error: We may be deserializing a PokemonSpeciesForm, but we catch later on
let { id, formIdx } = value;
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
// @ts-expect-error: Typescript doesn't know that in block, `value` must be a PokemonSpeciesForm
id = value.speciesId;
// @ts-expect-error: Same as above (plus we are accessing a protected property)
formIdx = value._formIndex;
}
// If for some reason either of these fields are null/undefined, we cannot reconstruct the species form
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
return null;
}
return getPokemonSpeciesForm(id, formIdx);
}
interface SerializedIllusionData extends Omit<IllusionData, "fusionSpecies"> {
/** The id of the illusioned fusion species, or `undefined` if not a fusion */
fusionSpecies?: SpeciesId;
}
interface SerializedPokemonSummonData {
statStages: number[];
moveQueue: TurnMove[];
tags: BattlerTag[];
abilitySuppressed: boolean;
speciesForm?: SerializedSpeciesForm;
fusionSpeciesForm?: SerializedSpeciesForm;
ability?: AbilityId;
passiveAbility?: AbilityId;
gender?: Gender;
fusionGender?: Gender;
stats: number[];
moveset?: PokemonMove[];
types: PokemonType[];
addedType?: PokemonType;
illusion?: SerializedIllusionData;
illusionBroken: boolean;
berriesEatenLast: BerryType[];
moveHistory: TurnMove[];
}
/**
* Persistent in-battle data for a {@linkcode Pokemon}.
* Resets on switch or new battle.
*
* @sealed
*/
export class PokemonSummonData {
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
@ -86,7 +147,7 @@ export class PokemonSummonData {
*/
public moveHistory: TurnMove[] = [];
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
constructor(source?: PokemonSummonData | SerializedPokemonSummonData) {
if (isNullOrUndefined(source)) {
return;
}
@ -97,6 +158,30 @@ export class PokemonSummonData {
continue;
}
if (key === "speciesForm" || key === "fusionSpeciesForm") {
this[key] = deserializePokemonSpeciesForm(value);
}
if (key === "illusion" && typeof value === "object") {
// Make a copy so as not to mutate provided value
const illusionData = {
...value,
};
if (!isNullOrUndefined(illusionData.fusionSpecies)) {
switch (typeof illusionData.fusionSpecies) {
case "object":
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies.speciesId];
break;
case "number":
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies];
break;
default:
illusionData.fusionSpecies = undefined;
}
}
this[key] = illusionData as IllusionData;
}
if (key === "moveset") {
this.moveset = value?.map((m: any) => PokemonMove.loadMove(m));
continue;
@ -110,6 +195,49 @@ export class PokemonSummonData {
this[key] = value;
}
}
/**
* Serialize this PokemonSummonData to JSON, converting {@linkcode PokemonSpeciesForm} and {@linkcode IllusionData.fusionSpecies}
* into simpler types instead of serializing all of their fields.
*
* @remarks
* - `IllusionData.fusionSpecies` is serialized as just the species ID
* - `PokemonSpeciesForm` and `PokemonSpeciesForm.fusionSpeciesForm` are converted into {@linkcode SerializedSpeciesForm} objects
*/
public toJSON(): SerializedPokemonSummonData {
// Pokemon species forms are never saved, only the species ID.
const illusion = this.illusion;
const speciesForm = this.speciesForm;
const fusionSpeciesForm = this.fusionSpeciesForm;
const illusionSpeciesForm = illusion?.fusionSpecies;
const t = {
// the "as omit" is required to avoid TS resolving the overwritten properties to "never"
// We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined`
...(this as Omit<
CoerceNullPropertiesToUndefined<PokemonSummonData>,
"speciesForm" | "fusionSpeciesForm" | "illusion"
>),
speciesForm: isNullOrUndefined(speciesForm)
? undefined
: { id: speciesForm.speciesId, formIdx: speciesForm.formIndex },
fusionSpeciesForm: isNullOrUndefined(fusionSpeciesForm)
? undefined
: { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex },
illusion: isNullOrUndefined(illusion)
? undefined
: {
...(this.illusion as Omit<typeof illusion, "fusionSpecies">),
fusionSpecies: illusionSpeciesForm?.speciesId,
},
};
// Replace `null` with `undefined`, as `undefined` never gets serialized
for (const [key, value] of Object.entries(t)) {
if (value === null) {
t[key] = undefined;
}
}
return t;
}
}
// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added

View File

@ -1,12 +1,12 @@
import { TrainerType } from "#enums/trainer-type";
import { toReadableString } from "#utils/common";
import { toPascalSnakeCase } from "#utils/strings";
class TrainerNameConfig {
public urls: string[];
public femaleUrls: string[] | null;
constructor(type: TrainerType, ...urls: string[]) {
this.urls = urls.length ? urls : [toReadableString(TrainerType[type]).replace(/ /g, "_")];
this.urls = urls.length ? urls : [toPascalSnakeCase(TrainerType[type])];
}
hasGenderVariant(...femaleUrls: string[]): TrainerNameConfig {

View File

@ -41,15 +41,9 @@ import type {
TrainerConfigs,
TrainerTierPools,
} from "#types/trainer-funcs";
import {
coerceArray,
isNullOrUndefined,
randSeedInt,
randSeedIntRange,
randSeedItem,
toReadableString,
} from "#utils/common";
import { coerceArray, isNullOrUndefined, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { toSnakeCase, toTitleCase } from "#utils/strings";
import i18next from "i18next";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */
@ -140,7 +134,7 @@ export class TrainerConfig {
constructor(trainerType: TrainerType, allowLegendaries?: boolean) {
this.trainerType = trainerType;
this.trainerAI = new TrainerAI();
this.name = toReadableString(TrainerType[this.getDerivedType()]);
this.name = toTitleCase(TrainerType[this.getDerivedType()]);
this.battleBgm = "battle_trainer";
this.mixedBattleBgm = "battle_trainer";
this.victoryBgm = "victory_trainer";
@ -734,7 +728,7 @@ export class TrainerConfig {
}
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
const nameForCall = toSnakeCase(this.name);
this.name = i18next.t(`trainerNames:${nameForCall}`);
// Set the title to "elite_four". (this is the key in the i18n file)

59
src/enums/text-style.ts Normal file
View File

@ -0,0 +1,59 @@
export const TextStyle = Object.freeze({
MESSAGE: 1,
WINDOW: 2,
WINDOW_ALT: 3,
WINDOW_BATTLE_COMMAND: 4,
BATTLE_INFO: 5,
PARTY: 6,
PARTY_RED: 7,
PARTY_CANCEL_BUTTON: 8,
INSTRUCTIONS_TEXT: 9,
MOVE_LABEL: 10,
SUMMARY: 11,
SUMMARY_DEX_NUM: 12,
SUMMARY_DEX_NUM_GOLD: 13,
SUMMARY_ALT: 14,
SUMMARY_HEADER: 15,
SUMMARY_RED: 16,
SUMMARY_BLUE: 17,
SUMMARY_PINK: 18,
SUMMARY_GOLD: 19,
SUMMARY_GRAY: 20,
SUMMARY_GREEN: 21,
SUMMARY_STATS: 22,
SUMMARY_STATS_BLUE: 23,
SUMMARY_STATS_PINK: 24,
SUMMARY_STATS_GOLD: 25,
LUCK_VALUE: 26,
STATS_HEXAGON: 27,
GROWTH_RATE_TYPE: 28,
MONEY: 29, // Money default styling (pale yellow)
MONEY_WINDOW: 30, // Money displayed in Windows (needs different colors based on theme)
HEADER_LABEL: 31,
STATS_LABEL: 32,
STATS_VALUE: 33,
SETTINGS_VALUE: 34,
SETTINGS_LABEL: 35,
SETTINGS_LABEL_NAVBAR: 36,
SETTINGS_SELECTED: 37,
SETTINGS_LOCKED: 38,
EGG_LIST: 39,
EGG_SUMMARY_NAME: 40,
EGG_SUMMARY_DEX: 41,
STARTER_VALUE_LIMIT: 42,
TOOLTIP_TITLE: 43,
TOOLTIP_CONTENT: 44,
FILTER_BAR_MAIN: 45,
MOVE_INFO_CONTENT: 46,
MOVE_PP_FULL: 47,
MOVE_PP_HALF_FULL: 48,
MOVE_PP_NEAR_EMPTY: 49,
MOVE_PP_EMPTY: 50,
SMALLER_WINDOW_ALT: 51,
BGM_BAR: 52,
PERFECT_IV: 53,
ME_OPTION_DEFAULT: 54, // Default style for choices in ME
ME_OPTION_SPECIAL: 55, // Style for choices with special requirements in ME
SHADOW_TEXT: 56 // to obscure unavailable options
})
export type TextStyle = typeof TextStyle[keyof typeof TextStyle];

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index";
import { HitResult } from "#enums/hit-result";
import { TextStyle } from "#enums/text-style";
import type { Pokemon } from "#field/pokemon";
import type { DamageResult } from "#types/damage-result";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { fixedInt, formatStat } from "#utils/common";
type TextAndShadowArr = [string | null, string | null];

View File

@ -5,10 +5,11 @@ import { coerceArray, fixedInt, randInt } from "#utils/common";
export class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>;
private counterTween?: Phaser.Tweens.Tween;
setup(): void {
this.sprites = new Set();
globalScene.tweens.addCounter({
this.counterTween = globalScene.tweens.addCounter({
duration: fixedInt(200),
from: 0,
to: 1,
@ -78,4 +79,12 @@ export class PokemonSpriteSparkleHandler {
this.sprites.delete(s);
}
}
destroy(): void {
this.removeAll();
if (this.counterTween) {
this.counterTween.destroy();
this.counterTween = undefined;
}
}
}

View File

@ -1694,12 +1694,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns Whether this Pokemon is shiny
*/
isShiny(useIllusion = false): boolean {
if (useIllusion) {
const illusion = this.summonData.illusion;
return illusion?.shiny || (!!illusion?.fusionSpecies && !!illusion.fusionShiny);
}
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
return this.isBaseShiny(useIllusion) || this.isFusionShiny(useIllusion);
}
isBaseShiny(useIllusion = false) {

View File

@ -23,6 +23,7 @@ import {
} from "#trainers/trainer-party-template";
import { randSeedInt, randSeedItem, randSeedWeightedItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import { toSnakeCase } from "#utils/strings";
import i18next from "i18next";
export class Trainer extends Phaser.GameObjects.Container {
@ -170,7 +171,7 @@ export class Trainer extends Phaser.GameObjects.Container {
const evilTeamTitles = ["grunt"];
if (this.name === "" && evilTeamTitles.some(t => name.toLocaleLowerCase().includes(t))) {
// This is a evil team grunt so we localize it by only using the "name" as the title
title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`);
title = i18next.t(`trainerClasses:${toSnakeCase(name)}`);
console.log("Localized grunt name: " + title);
// Since grunts are not named we can just return the title
return title;
@ -187,7 +188,7 @@ export class Trainer extends Phaser.GameObjects.Container {
}
// Get the localized trainer class name from the i18n file and set it as the title.
// This is used for trainer class names, not titles like "Elite Four, Champion, etc."
title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`);
title = i18next.t(`trainerClasses:${toSnakeCase(name)}`);
}
// If no specific trainer slot is set.
@ -208,7 +209,7 @@ export class Trainer extends Phaser.GameObjects.Container {
if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) {
title = this.config.titleDouble;
name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`);
name = i18next.t(`trainerNames:${toSnakeCase(this.config.nameDouble)}`);
}
console.log(title ? `${title} ${name}` : name);

View File

@ -23,6 +23,7 @@ import type { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { BATTLE_STATS, type PermanentStat, Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { TextStyle } from "#enums/text-style";
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
import type {
DoubleBattleChanceBoosterModifierType,
@ -40,7 +41,7 @@ import type {
} from "#modifiers/modifier-type";
import type { VoucherType } from "#system/voucher";
import type { ModifierInstanceMap, ModifierString } from "#types/modifier-types";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#utils/common";
import { getModifierType } from "#utils/modifier-utils";
import i18next from "i18next";

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase";
import { PlayerGender } from "#enums/player-gender";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
export class EndCardPhase extends Phase {

View File

@ -2,9 +2,10 @@ import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import type { BattlerIndex } from "#enums/battler-index";
import { PERMANENT_STATS, Stat } from "#enums/stat";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { PokemonPhase } from "#phases/pokemon-phase";
import { getTextColor, TextStyle } from "#ui/text";
import { getTextColor } from "#ui/text";
import i18next from "i18next";
export class ScanIvsPhase extends PokemonPhase {

View File

@ -1,5 +1,5 @@
import pkg from "#package.json";
import { camelCaseToKebabCase } from "#utils/common";
import { toKebabCase } from "#utils/strings";
import i18next from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import HttpBackend from "i18next-http-backend";
@ -194,14 +194,16 @@ export async function initI18n(): Promise<void> {
],
backend: {
loadPath(lng: string, [ns]: string[]) {
// Use namespace maps where required
let fileName: string;
if (namespaceMap[ns]) {
fileName = namespaceMap[ns];
} else if (ns.startsWith("mysteryEncounters/")) {
fileName = camelCaseToKebabCase(ns + "Dialogue");
fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue
} else {
fileName = camelCaseToKebabCase(ns);
fileName = toKebabCase(ns);
}
// ex: "./locales/en/move-anims"
return `./locales/${lng}/${fileName}.json?v=${pkg.version}`;
},
},

View File

@ -1454,11 +1454,10 @@ export class GameData {
reader.onload = (_ => {
return e => {
let dataName: string;
let dataName = GameDataType[dataType].toLowerCase();
let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct?
let valid = false;
try {
dataName = GameDataType[dataType].toLowerCase();
switch (dataType) {
case GameDataType.SYSTEM: {
dataStr = this.convertSystemDataStr(dataStr);
@ -1493,7 +1492,6 @@ export class GameData {
const displayError = (error: string) =>
globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), fixedInt(1500));
dataName = dataName!; // tell TS compiler that dataName is defined!
if (!valid) {
return globalScene.ui.showText(

View File

@ -5,8 +5,9 @@ import { Challenges } from "#enums/challenges";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { WeatherType } from "#enums/weather-type";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import type { nil } from "#utils/common";
import { isNullOrUndefined } from "#utils/common";
import i18next from "i18next";

View File

@ -1,5 +1,6 @@
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
const barWidth = 118;

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { addBBCodeTextObject, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text";
import { addBBCodeTextObject, getTextColor, getTextStyleOptions } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, rgbHexToRgba } from "#utils/common";

View File

@ -1,8 +1,9 @@
import { globalScene } from "#app/global-scene";
import type { PlayerGender } from "#enums/player-gender";
import { TextStyle } from "#enums/text-style";
import { Achv, getAchievementDescription } from "#system/achv";
import { Voucher } from "#system/voucher";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
export class AchvBar extends Phaser.GameObjects.Container {
private defaultWidth: number;

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { PlayerGender } from "#enums/player-gender";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import type { Achv } from "#system/achv";
import { achvs, getAchievementDescription } from "#system/achv";
@ -9,7 +10,7 @@ import type { Voucher } from "#system/voucher";
import { getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#system/voucher";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { ScrollBar } from "#ui/scroll-bar";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,12 +1,12 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { TextStyle } from "#ui/text";
import { formatText } from "#utils/common";
import { toTitleCase } from "#utils/strings";
type AdminUiHandlerService = "discord" | "google";
type AdminUiHandlerServiceMode = "Link" | "Unlink";
@ -21,9 +21,9 @@ export class AdminUiHandler extends FormModalUiHandler {
private readonly httpUserNotFoundErrorCode: number = 404;
private readonly ERR_REQUIRED_FIELD = (field: string) => {
if (field === "username") {
return `${formatText(field)} is required`;
return `${toTitleCase(field)} is required`;
}
return `${formatText(field)} Id is required`;
return `${toTitleCase(field)} Id is required`;
};
// returns a string saying whether a username has been successfully linked/unlinked to discord/google
private readonly SUCCESS_SERVICE_MODE = (service: string, mode: string) => {

View File

@ -3,6 +3,7 @@ import { ArenaTrapTag } from "#data/arena-tag";
import { TerrainType } from "#data/terrain";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { ArenaTagType } from "#enums/arena-tag-type";
import { TextStyle } from "#enums/text-style";
import { WeatherType } from "#enums/weather-type";
import type { ArenaEvent } from "#events/arena";
import {
@ -14,10 +15,11 @@ import {
} from "#events/arena";
import type { TurnEndEvent } from "#events/battle-scene";
import { BattleSceneEventType } from "#events/battle-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { TimeOfDayWidget } from "#ui/time-of-day-widget";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { fixedInt, formatText, toCamelCaseString } from "#utils/common";
import { fixedInt } from "#utils/common";
import { toCamelCase, toTitleCase } from "#utils/strings";
import type { ParseKeys } from "i18next";
import i18next from "i18next";
@ -48,10 +50,10 @@ export function getFieldEffectText(arenaTagType: string): string {
if (!arenaTagType || arenaTagType === ArenaTagType.NONE) {
return arenaTagType;
}
const effectName = toCamelCaseString(arenaTagType);
const effectName = toCamelCase(arenaTagType);
const i18nKey = `arenaFlyout:${effectName}` as ParseKeys;
const resultName = i18next.t(i18nKey);
return !resultName || resultName === i18nKey ? formatText(arenaTagType) : resultName;
return !resultName || resultName === i18nKey ? toTitleCase(arenaTagType) : resultName;
}
export class ArenaFlyout extends Phaser.GameObjects.Container {

View File

@ -2,9 +2,10 @@ import { globalScene } from "#app/global-scene";
import { getPokeballName } from "#data/pokeball";
import { Button } from "#enums/buttons";
import { Command } from "#enums/command";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { CommandPhase } from "#phases/command-phase";
import { addTextObject, getTextStyleOptions, TextStyle } from "#ui/text";
import { addTextObject, getTextStyleOptions } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,6 +1,7 @@
import type { InfoToggle } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt } from "#utils/common";
import i18next from "i18next";

View File

@ -2,12 +2,13 @@ import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { BerryType } from "#enums/berry-type";
import { MoveId } from "#enums/move-id";
import { TextStyle } from "#enums/text-style";
import { UiTheme } from "#enums/ui-theme";
import type { BerryUsedEvent, MoveUsedEvent } from "#events/battle-scene";
import { BattleSceneEventType } from "#events/battle-scene";
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
import type { Move } from "#moves/move";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { fixedInt } from "#utils/common";
/** Container for info about a {@linkcode Move} */

View File

@ -4,9 +4,10 @@ import { getTypeRgb } from "#data/type";
import { PokemonType } from "#enums/pokemon-type";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { TextStyle } from "#enums/text-style";
import type { Pokemon } from "#field/pokemon";
import { getVariantTint } from "#sprites/variant";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { fixedInt, getLocalizedSpriteKey, getShinyDescriptor } from "#utils/common";
import i18next from "i18next";

View File

@ -1,10 +1,11 @@
import { globalScene } from "#app/global-scene";
import { Stat } from "#enums/stat";
import { TextStyle } from "#enums/text-style";
import type { EnemyPokemon } from "#field/pokemon";
import { BattleFlyout } from "#ui/battle-flyout";
import type { BattleInfoParamList } from "#ui/battle-info";
import { BattleInfo } from "#ui/battle-info";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import i18next from "i18next";
import type { GameObjects } from "phaser";

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { getStatKey, PERMANENT_STATS } from "#enums/stat";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { formatText } from "#utils/common";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import { toTitleCase } from "#utils/strings";
import i18next from "i18next";
const hiddenX = -150;
@ -100,7 +101,7 @@ export class BgmBar extends Phaser.GameObjects.Container {
getRealBgmName(bgmName: string): string {
return i18next.t([`bgmName:${bgmName}`, "bgmName:missing_entries"], {
name: formatText(bgmName),
name: toTitleCase(bgmName),
});
}
}

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import type { SpeciesId } from "#enums/species-id";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import { rgbHexToRgba } from "#utils/common";
import { argbFromRgba } from "@material/material-color-utilities";

View File

@ -3,8 +3,9 @@ import type { Challenge } from "#data/challenge";
import { Button } from "#enums/buttons";
import { Challenges } from "#enums/challenges";
import { Color, ShadowColor } from "#enums/color";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { getLocalizedSpriteKey } from "#utils/common";

View File

@ -5,11 +5,12 @@ import { Button } from "#enums/buttons";
import { Command } from "#enums/command";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { TerastallizeAccessModifier } from "#modifiers/modifier";
import type { CommandPhase } from "#phases/command-phase";
import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import i18next from "i18next";

View File

@ -1,6 +1,7 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { executeIf } from "#utils/common";
import { getEnumKeys } from "#utils/enums";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import { ScrollBar } from "#ui/scroll-bar";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,8 +1,9 @@
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import type { EggCountChangedEvent } from "#events/egg";
import { EggEventType } from "#events/egg";
import type { EggHatchSceneHandler } from "#ui/egg-hatch-scene-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
/**

View File

@ -6,10 +6,11 @@ import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#data/egg";
import { Button } from "#enums/buttons";
import { EggTier } from "#enums/egg-type";
import { GachaType } from "#enums/gacha-types";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { getVoucherTypeIcon, VoucherType } from "#system/voucher";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addTextObject, getEggTierTextTint, getTextStyleOptions, TextStyle } from "#ui/text";
import { addTextObject, getEggTierTextTint, getTextStyleOptions } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, randSeedShuffle } from "#utils/common";
import { getEnumValues } from "#utils/enums";
@ -74,7 +75,7 @@ export class EggGachaUiHandler extends MessageUiHandler {
const gachaInfoContainer = globalScene.add.container(160, 46);
const currentLanguage = i18next.resolvedLanguage ?? "en";
let gachaTextStyle = TextStyle.WINDOW_ALT;
let gachaTextStyle: TextStyle = TextStyle.WINDOW_ALT;
let gachaX = 4;
let gachaY = 0;
let pokemonIconX = -20;

View File

@ -1,11 +1,12 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler";
import { ScrollBar } from "#ui/scroll-bar";
import { ScrollableGridUiHandler } from "#ui/scrollable-grid-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,8 +1,9 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
export class EvolutionSceneHandler extends MessageUiHandler {
public evolutionContainer: Phaser.GameObjects.Container;

View File

@ -7,12 +7,13 @@ import { Command } from "#enums/command";
import { MoveCategory } from "#enums/move-category";
import { MoveUseMode } from "#enums/move-use-mode";
import { PokemonType } from "#enums/pokemon-type";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { EnemyPokemon, Pokemon } from "#field/pokemon";
import type { PokemonMove } from "#moves/pokemon-move";
import type { CommandPhase } from "#phases/command-phase";
import { MoveInfoOverlay } from "#ui/move-info-overlay";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { fixedInt, getLocalizedSpriteKey, padInt } from "#utils/common";
import i18next from "i18next";
@ -284,7 +285,7 @@ export class FightUiHandler extends UiHandler implements InfoToggle {
const ppColorStyle = FightUiHandler.ppRatioToColor(pp / maxPP);
//** Changes the text color and shadow according to the determined TextStyle */
// Changes the text color and shadow according to the determined TextStyle
this.ppText.setColor(this.getTextColor(ppColorStyle, false)).setShadowColor(this.getTextColor(ppColorStyle, true));
this.moveInfoOverlay.show(pokemonMove.getMove());

View File

@ -1,10 +1,11 @@
import { globalScene } from "#app/global-scene";
import type { DropDownColumn } from "#enums/drop-down-column";
import { TextStyle } from "#enums/text-style";
import type { UiTheme } from "#enums/ui-theme";
import type { DropDown } from "#ui/dropdown";
import { DropDownType } from "#ui/dropdown";
import type { StarterContainer } from "#ui/starter-container";
import { addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addTextObject, getTextColor } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
export class FilterBar extends Phaser.GameObjects.Container {

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { UiTheme } from "#enums/ui-theme";
import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler";
import type { StarterContainer } from "#ui/starter-container";
import { addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addTextObject, getTextColor } from "#ui/text";
import type { UI } from "#ui/ui";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { ModalUiHandler } from "#ui/modal-ui-handler";
import { addTextInputObject, addTextObject, TextStyle } from "#ui/text";
import { addTextInputObject, addTextObject } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { fixedInt } from "#utils/common";
import type InputText from "phaser3-rex-plugins/plugins/inputtext";

View File

@ -2,12 +2,14 @@ import { globalScene } from "#app/global-scene";
import { speciesStarterCosts } from "#balance/starters";
import { Button } from "#enums/buttons";
import { DexAttr } from "#enums/dex-attr";
import { TextStyle } from "#enums/text-style";
import { UiTheme } from "#enums/ui-theme";
import type { GameData } from "#system/game-data";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { formatFancyLargeNumber, getPlayTimeString, toReadableString } from "#utils/common";
import { formatFancyLargeNumber, getPlayTimeString } from "#utils/common";
import { toTitleCase } from "#utils/strings";
import i18next from "i18next";
import Phaser from "phaser";
@ -501,11 +503,9 @@ export function initStatsKeys() {
sourceFunc: gameData => gameData.gameStats[key].toString(),
};
}
if (!(displayStats[key] as DisplayStat).label_key) {
if (!displayStats[key].label_key) {
const splittableKey = key.replace(/([a-z]{2,})([A-Z]{1}(?:[^A-Z]|$))/g, "$1_$2");
(displayStats[key] as DisplayStat).label_key = toReadableString(
`${splittableKey[0].toUpperCase()}${splittableKey.slice(1)}`,
);
displayStats[key].label_key = toTitleCase(splittableKey);
}
}
}

View File

@ -1,6 +1,7 @@
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { ModalUiHandler } from "#ui/modal-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
export class LoadingModalUiHandler extends ModalUiHandler {

View File

@ -1,11 +1,12 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt } from "#utils/common";
import i18next from "i18next";

View File

@ -5,13 +5,14 @@ import { bypassLogin } from "#app/global-vars/bypass-login";
import { handleTutorial, Tutorial } from "#app/tutorial";
import { Button } from "#enums/buttons";
import { GameDataType } from "#enums/game-data-type";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler";
import { AdminMode, getAdminModeName } from "#ui/admin-ui-handler";
import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler";
import { BgmBar } from "#ui/bgm-bar";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { addTextObject, getTextStyleOptions, TextStyle } from "#ui/text";
import { addTextObject, getTextStyleOptions } from "#ui/text";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { fixedInt, isLocal, sessionIdKey } from "#utils/common";
import { getCookie } from "#utils/cookies";

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene";
import type { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow, WindowVariant } from "#ui/ui-theme";

View File

@ -6,13 +6,14 @@ import { getPokeballAtlasKey } from "#data/pokeball";
import { Button } from "#enums/buttons";
import type { PokeballType } from "#enums/pokeball";
import { ShopCursorTarget } from "#enums/shop-cursor-target";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { HealShopCostModifier, LockModifierTiersModifier, PokemonHeldItemModifier } from "#modifiers/modifier";
import type { ModifierTypeOption } from "#modifiers/modifier-type";
import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "#modifiers/modifier-type";
import { AwaitableUiHandler } from "#ui/awaitable-ui-handler";
import { MoveInfoOverlay } from "#ui/move-info-overlay";
import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text";
import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions } from "#ui/text";
import { formatMoney, NumberHolder } from "#utils/common";
import i18next from "i18next";
import Phaser from "phaser";

View File

@ -2,8 +2,9 @@ import type { InfoToggle } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { MoveCategory } from "#enums/move-category";
import { PokemonType } from "#enums/pokemon-type";
import { TextStyle } from "#enums/text-style";
import type { Move } from "#moves/move";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, getLocalizedSpriteKey } from "#utils/common";
import i18next from "i18next";

View File

@ -3,13 +3,14 @@ import { getPokeballAtlasKey } from "#data/pokeball";
import { Button } from "#enums/buttons";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { getEncounterText } from "#mystery-encounters/encounter-dialogue-utils";
import type { OptionSelectSettings } from "#mystery-encounters/encounter-phase-utils";
import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option";
import type { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { PartyUiMode } from "#ui/party-ui-handler";
import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "#ui/text";
import { addBBCodeTextObject, getBBCodeFrag } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow, WindowVariant } from "#ui/ui-theme";
import { fixedInt, isNullOrUndefined } from "#utils/common";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import type { Pokemon } from "#field/pokemon";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
export class PartyExpBar extends Phaser.GameObjects.Container {

View File

@ -13,6 +13,7 @@ import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
import type { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#modifiers/modifier";
@ -23,9 +24,10 @@ import type { TurnMove } from "#types/turn-move";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { MoveInfoOverlay } from "#ui/move-info-overlay";
import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { BooleanHolder, getLocalizedSpriteKey, randInt, toReadableString } from "#utils/common";
import { BooleanHolder, getLocalizedSpriteKey, randInt } from "#utils/common";
import { toTitleCase } from "#utils/strings";
import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
@ -1408,7 +1410,7 @@ export class PartyUiHandler extends MessageUiHandler {
if (this.localizedOptions.includes(option)) {
optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`);
} else {
optionName = toReadableString(PartyOption[option]);
optionName = toTitleCase(PartyOption[option]);
}
}
break;

View File

@ -1,6 +1,7 @@
import type { InfoToggle } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt } from "#utils/common";

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene";
import type { PokemonSpecies } from "#data/pokemon-species";
import { TextStyle } from "#enums/text-style";
import type { Variant } from "#sprites/variant";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { isNullOrUndefined } from "#utils/common";
interface SpeciesDetails {

View File

@ -38,6 +38,7 @@ import type { Nature } from "#enums/nature";
import { Passive as PassiveAttr } from "#enums/passive";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { TimeOfDay } from "#enums/time-of-day";
import { UiMode } from "#enums/ui-mode";
import type { Variant } from "#sprites/variant";
@ -51,18 +52,12 @@ import { MessageUiHandler } from "#ui/message-ui-handler";
import { MoveInfoOverlay } from "#ui/move-info-overlay";
import { PokedexInfoOverlay } from "#ui/pokedex-info-overlay";
import { StatsContainer } from "#ui/stats-container";
import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import {
BooleanHolder,
getLocalizedSpriteKey,
isNullOrUndefined,
padInt,
rgbHexToRgba,
toReadableString,
} from "#utils/common";
import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, padInt, rgbHexToRgba } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
import { toTitleCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
@ -2619,7 +2614,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
// Setting growth rate text
if (isFormCaught) {
let growthReadable = toReadableString(GrowthRate[species.growthRate]);
let growthReadable = toTitleCase(GrowthRate[species.growthRate]);
const growthAux = growthReadable.replace(" ", "_");
if (i18next.exists("growth:" + growthAux)) {
growthReadable = i18next.t(("growth:" + growthAux) as any);

View File

@ -26,6 +26,7 @@ import type { Nature } from "#enums/nature";
import { Passive as PassiveAttr } from "#enums/passive";
import { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { Variant } from "#sprites/variant";
import { getVariantIcon, getVariantTint } from "#sprites/variant";
@ -40,7 +41,7 @@ import { MessageUiHandler } from "#ui/message-ui-handler";
import { PokedexMonContainer } from "#ui/pokedex-mon-container";
import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler";
import { ScrollBar } from "#ui/scroll-bar";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { BooleanHolder, fixedInt, getLocalizedSpriteKey, padInt, randIntRange, rgbHexToRgba } from "#utils/common";
import type { StarterPreferences } from "#utils/data";

View File

@ -7,9 +7,10 @@ import type { EggHatchData } from "#data/egg-hatch-data";
import { Gender } from "#data/gender";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import type { PlayerPokemon } from "#field/pokemon";
import { PokemonInfoContainer } from "#ui/pokemon-info-container";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { padInt, rgbHexToRgba } from "#utils/common";
import { getPokemonSpeciesForm } from "#utils/pokemon-utils";
import { argbFromRgba } from "@material/material-color-utilities";

View File

@ -3,13 +3,14 @@ import { Gender, getGenderColor, getGenderSymbol } from "#data/gender";
import { getNatureName } from "#data/nature";
import { DexAttr } from "#enums/dex-attr";
import { PokemonType } from "#enums/pokemon-type";
import { TextStyle } from "#enums/text-style";
import type { Pokemon } from "#field/pokemon";
import { getVariantTint } from "#sprites/variant";
import type { StarterDataEntry } from "#system/game-data";
import type { DexEntry } from "#types/dex-data";
import { ConfirmUiHandler } from "#ui/confirm-ui-handler";
import { StatsContainer } from "#ui/stats-container";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, getShinyDescriptor } from "#utils/common";
import i18next from "i18next";

View File

@ -1,10 +1,11 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { InputFieldConfig } from "#ui/form-modal-ui-handler";
import { FormModalUiHandler } from "#ui/form-modal-ui-handler";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
interface LanguageSetting {

View File

@ -3,13 +3,14 @@ import { BattleType } from "#enums/battle-type";
import { Button } from "#enums/buttons";
import { GameModes } from "#enums/game-modes";
import { PlayerGender } from "#enums/player-gender";
import { TextStyle } from "#enums/text-style";
import { TrainerVariant } from "#enums/trainer-variant";
import { UiMode } from "#enums/ui-mode";
import type { RunEntry } from "#system/game-data";
import type { PokemonData } from "#system/pokemon-data";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { RunDisplayMode } from "#ui/run-info-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, formatLargeNumber } from "#utils/common";
import i18next from "i18next";

View File

@ -12,6 +12,7 @@ import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PlayerGender } from "#enums/player-gender";
import { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { TrainerVariant } from "#enums/trainer-variant";
import { UiMode } from "#enums/ui-mode";
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
@ -21,7 +22,7 @@ import { getVariantTint } from "#sprites/variant";
import type { SessionSaveData } from "#system/game-data";
import type { PokemonData } from "#system/pokemon-data";
import { SettingKeyboard } from "#system/settings-keyboard";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common";

View File

@ -1,6 +1,7 @@
import { GameMode } from "#app/game-mode";
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
import * as Modifier from "#modifiers/modifier";
@ -8,7 +9,7 @@ import type { SessionSaveData } from "#system/game-data";
import type { PokemonData } from "#system/pokemon-data";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { RunDisplayMode } from "#ui/run-info-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } from "#utils/common";
import i18next from "i18next";

View File

@ -1,7 +1,8 @@
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { ModalUiHandler } from "#ui/modal-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
export class SessionReloadModalUiHandler extends ModalUiHandler {
constructor(mode: UiMode | null = null) {

View File

@ -1,8 +1,9 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { NavigationManager } from "#ui/navigation-menu";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -2,13 +2,15 @@ import { globalScene } from "#app/global-scene";
import type { InterfaceConfig } from "#app/inputs-controller";
import { Button } from "#enums/buttons";
import type { Device } from "#enums/devices";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { getIconWithSettingName } from "#inputs/config-handler";
import { NavigationManager, NavigationMenu } from "#ui/navigation-menu";
import { ScrollBar } from "#ui/scroll-bar";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
export interface InputsIcons {
@ -87,12 +89,6 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler {
return settings;
}
private camelize(string: string): string {
return string
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (index === 0 ? word.toLowerCase() : word.toUpperCase()))
.replace(/\s+/g, "");
}
/**
* Setup UI elements.
*/
@ -209,14 +205,15 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler {
settingFiltered.forEach((setting, s) => {
// Convert the setting key from format 'Key_Name' to 'Key name' for display.
const settingName = setting.replace(/_/g, " ");
// TODO: IDK if this can be followed by both an underscore and a space, so leaving it as a regex matching both for now
const i18nKey = toCamelCase(setting.replace(/Alt(_| )/, ""));
// Create and add a text object for the setting name to the scene.
const isLock = this.settingBlacklisted.includes(this.setting[setting]);
const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL;
const isAlt = setting.includes("Alt");
let labelText: string;
const i18nKey = this.camelize(settingName.replace("Alt ", ""));
if (settingName.toLowerCase().includes("alt")) {
if (isAlt) {
labelText = `${i18next.t(`settings:${i18nKey}`)}${i18next.t("settings:alt")}`;
} else {
labelText = i18next.t(`settings:${i18nKey}`);

View File

@ -1,5 +1,6 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { SettingType } from "#system/settings";
import { Setting, SettingKeys } from "#system/settings";
@ -7,7 +8,7 @@ import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { NavigationManager, NavigationMenu } from "#ui/navigation-menu";
import { ScrollBar } from "#ui/scroll-bar";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import { Device } from "#enums/devices";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { getIconWithSettingName, getKeyWithKeycode } from "#inputs/config-handler";
import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
export class GamepadBindingUiHandler extends AbstractBindingUiHandler {

View File

@ -1,9 +1,10 @@
import { globalScene } from "#app/global-scene";
import { Device } from "#enums/devices";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import { getKeyWithKeycode } from "#inputs/config-handler";
import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import i18next from "i18next";
export class KeyboardBindingUiHandler extends AbstractBindingUiHandler {

View File

@ -1,8 +1,9 @@
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler";
import { addTextObject, setTextStyle, TextStyle } from "#ui/text";
import { addTextObject, setTextStyle } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import i18next from "i18next";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { InterfaceConfig } from "#app/inputs-controller";
import { Device } from "#enums/devices";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import pad_dualshock from "#inputs/pad-dualshock";
import pad_unlicensedSNES from "#inputs/pad-unlicensed-snes";
@ -13,7 +14,7 @@ import {
settingGamepadOptions,
} from "#system/settings-gamepad";
import { AbstractControlSettingsUiHandler } from "#ui/abstract-control-settings-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { truncateString } from "#utils/common";
import i18next from "i18next";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { InterfaceConfig } from "#app/inputs-controller";
import { Device } from "#enums/devices";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import cfg_keyboard_qwerty from "#inputs/cfg-keyboard-qwerty";
import { deleteBind } from "#inputs/config-handler";
@ -13,8 +14,9 @@ import {
} from "#system/settings-keyboard";
import { AbstractControlSettingsUiHandler } from "#ui/abstract-control-settings-ui-handler";
import { NavigationManager } from "#ui/navigation-menu";
import { addTextObject, TextStyle } from "#ui/text";
import { reverseValueToKeySetting, truncateString } from "#utils/common";
import { addTextObject } from "#ui/text";
import { truncateString } from "#utils/common";
import { toPascalSnakeCase } from "#utils/strings";
import i18next from "i18next";
/**
@ -100,7 +102,7 @@ export class SettingsKeyboardUiHandler extends AbstractControlSettingsUiHandler
}
const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position.
const selection = this.settingLabels[cursor].text;
const key = reverseValueToKeySetting(selection);
const key = toPascalSnakeCase(selection);
const settingName = SettingKeyboard[key];
const activeConfig = this.getActiveConfig();
const success = deleteBind(this.getActiveConfig(), settingName);

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { PokemonSpecies } from "#data/pokemon-species";
import { addTextObject, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addTextObject } from "#ui/text";
export class StarterContainer extends Phaser.GameObjects.Container {
public species: PokemonSpecies;

View File

@ -38,6 +38,7 @@ import type { Nature } from "#enums/nature";
import { Passive as PassiveAttr } from "#enums/passive";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { CandyUpgradeNotificationChangedEvent } from "#events/battle-scene";
import { BattleSceneEventType } from "#events/battle-scene";
@ -56,7 +57,7 @@ import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-an
import { ScrollBar } from "#ui/scroll-bar";
import { StarterContainer } from "#ui/starter-container";
import { StatsContainer } from "#ui/stats-container";
import { addBBCodeTextObject, addTextObject, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import {
BooleanHolder,
@ -67,10 +68,10 @@ import {
padInt,
randIntRange,
rgbHexToRgba,
toReadableString,
} from "#utils/common";
import type { StarterPreferences } from "#utils/data";
import { loadStarterPreferences, saveStarterPreferences } from "#utils/data";
import { toTitleCase } from "#utils/strings";
import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
@ -1476,7 +1477,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
loop: -1,
// Make the initial bounce a little randomly delayed
delay: randIntRange(0, 50) * 5,
loopDelay: 1000,
loopDelay: fixedInt(1000),
tweens: [
{
targets: icon,
@ -3526,7 +3527,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible);
//Growth translate
let growthReadable = toReadableString(GrowthRate[species.growthRate]);
let growthReadable = toTitleCase(GrowthRate[species.growthRate]);
const growthAux = growthReadable.replace(" ", "_");
if (i18next.exists("growth:" + growthAux)) {
growthReadable = i18next.t(("growth:" + growthAux) as any);

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import { getStatKey, PERMANENT_STATS } from "#enums/stat";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text";
import { TextStyle } from "#enums/text-style";
import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";

View File

@ -16,6 +16,7 @@ import { PlayerGender } from "#enums/player-gender";
import { PokemonType } from "#enums/pokemon-type";
import { getStatKey, PERMANENT_STATS, Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import type { PlayerPokemon } from "#field/pokemon";
import { modifierSortFunc, PokemonHeldItemModifier } from "#modifiers/modifier";
@ -24,7 +25,7 @@ import type { PokemonMove } from "#moves/pokemon-move";
import type { Variant } from "#sprites/variant";
import { getVariantTint } from "#sprites/variant";
import { achvs } from "#system/achv";
import { addBBCodeTextObject, addTextObject, getBBCodeFrag, TextStyle } from "#ui/text";
import { addBBCodeTextObject, addTextObject, getBBCodeFrag } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import {
fixedInt,
@ -34,9 +35,9 @@ import {
isNullOrUndefined,
padInt,
rgbHexToRgba,
toReadableString,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { toTitleCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
@ -961,8 +962,8 @@ export class SummaryUiHandler extends UiHandler {
this.passiveContainer?.descriptionText?.setVisible(false);
const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT);
const rawNature = toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct?
const nature = `${getBBCodeFrag(toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct?
const rawNature = toTitleCase(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct?
const nature = `${getBBCodeFrag(toTitleCase(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct?
const memoString = i18next.t("pokemonSummary:memoString", {
metFragment: i18next.t(

View File

@ -1,79 +1,14 @@
import { globalScene } from "#app/global-scene";
import { EggTier } from "#enums/egg-type";
import { ModifierTier } from "#enums/modifier-tier";
import { TextStyle } from "#enums/text-style";
import { UiTheme } from "#enums/ui-theme";
import i18next from "#plugins/i18n";
import type { TextStyleOptions } from "#types/ui";
import type Phaser from "phaser";
import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import type InputText from "phaser3-rex-plugins/plugins/inputtext";
export enum TextStyle {
MESSAGE,
WINDOW,
WINDOW_ALT,
WINDOW_BATTLE_COMMAND,
BATTLE_INFO,
PARTY,
PARTY_RED,
PARTY_CANCEL_BUTTON,
INSTRUCTIONS_TEXT,
MOVE_LABEL,
SUMMARY,
SUMMARY_DEX_NUM,
SUMMARY_DEX_NUM_GOLD,
SUMMARY_ALT,
SUMMARY_HEADER,
SUMMARY_RED,
SUMMARY_BLUE,
SUMMARY_PINK,
SUMMARY_GOLD,
SUMMARY_GRAY,
SUMMARY_GREEN,
SUMMARY_STATS,
SUMMARY_STATS_BLUE,
SUMMARY_STATS_PINK,
SUMMARY_STATS_GOLD,
LUCK_VALUE,
STATS_HEXAGON,
GROWTH_RATE_TYPE,
MONEY, // Money default styling (pale yellow)
MONEY_WINDOW, // Money displayed in Windows (needs different colors based on theme)
HEADER_LABEL,
STATS_LABEL,
STATS_VALUE,
SETTINGS_VALUE,
SETTINGS_LABEL,
SETTINGS_LABEL_NAVBAR,
SETTINGS_SELECTED,
SETTINGS_LOCKED,
EGG_LIST,
EGG_SUMMARY_NAME,
EGG_SUMMARY_DEX,
STARTER_VALUE_LIMIT,
TOOLTIP_TITLE,
TOOLTIP_CONTENT,
FILTER_BAR_MAIN,
MOVE_INFO_CONTENT,
MOVE_PP_FULL,
MOVE_PP_HALF_FULL,
MOVE_PP_NEAR_EMPTY,
MOVE_PP_EMPTY,
SMALLER_WINDOW_ALT,
BGM_BAR,
PERFECT_IV,
ME_OPTION_DEFAULT, // Default style for choices in ME
ME_OPTION_SPECIAL, // Style for choices with special requirements in ME
SHADOW_TEXT, // To obscure unavailable options
}
export interface TextStyleOptions {
scale: number;
styleOptions: Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig;
shadowColor: string;
shadowXpos: number;
shadowYpos: number;
}
export function addTextObject(
x: number,
y: number,
@ -87,9 +22,10 @@ export function addTextObject(
extraStyleOptions,
);
const ret = globalScene.add.text(x, y, content, styleOptions);
ret.setScale(scale);
ret.setShadow(shadowXpos, shadowYpos, shadowColor);
const ret = globalScene.add
.text(x, y, content, styleOptions)
.setScale(scale)
.setShadow(shadowXpos, shadowYpos, shadowColor);
if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) {
ret.setLineSpacing(scale * 30);
}
@ -107,8 +43,7 @@ export function setTextStyle(
globalScene.uiTheme,
extraStyleOptions,
);
obj.setScale(scale);
obj.setShadow(shadowXpos, shadowYpos, shadowColor);
obj.setScale(scale).setShadow(shadowXpos, shadowYpos, shadowColor);
if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) {
obj.setLineSpacing(scale * 30);
}
@ -133,8 +68,7 @@ export function addBBCodeTextObject(
const ret = new BBCodeText(globalScene, x, y, content, styleOptions as BBCodeText.TextStyle);
globalScene.add.existing(ret);
ret.setScale(scale);
ret.setShadow(shadowXpos, shadowYpos, shadowColor);
ret.setScale(scale).setShadow(shadowXpos, shadowYpos, shadowColor);
if (!(styleOptions as BBCodeText.TextStyle).lineSpacing) {
ret.setLineSpacing(scale * 60);
}

View File

@ -5,10 +5,11 @@ import { TimedEventDisplay } from "#app/timed-event-manager";
import { getSplashMessages } from "#data/splash-messages";
import { PlayerGender } from "#enums/player-gender";
import type { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { version } from "#package.json";
import { OptionSelectUiHandler } from "#ui/option-select-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { fixedInt, randInt, randItem } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { Button } from "#enums/buttons";
import type { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import type { TextStyle } from "#ui/text";
import { getTextColor } from "#ui/text";
/**

View File

@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
import type { Button } from "#enums/buttons";
import { Device } from "#enums/devices";
import { PlayerGender } from "#enums/player-gender";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { AchvBar } from "#ui/achv-bar";
import { AchvsUiHandler } from "#ui/achvs-ui-handler";
@ -51,7 +52,7 @@ import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler";
import { SummaryUiHandler } from "#ui/summary-ui-handler";
import { TargetSelectUiHandler } from "#ui/target-select-ui-handler";
import { TestDialogueUiHandler } from "#ui/test-dialogue-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { TitleUiHandler } from "#ui/title-ui-handler";
import type { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";

View File

@ -1,9 +1,10 @@
import { updateUserInfo } from "#app/account";
import { globalScene } from "#app/global-scene";
import { TextStyle } from "#enums/text-style";
import type { UiMode } from "#enums/ui-mode";
import type { ModalConfig } from "#ui/modal-ui-handler";
import { ModalUiHandler } from "#ui/modal-ui-handler";
import { addTextObject, TextStyle } from "#ui/text";
import { addTextObject } from "#ui/text";
import { sessionIdKey } from "#utils/common";
import { removeCookie } from "#utils/cookies";
import i18next from "i18next";

View File

@ -1,6 +1,5 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { MoneyFormat } from "#enums/money-format";
import { MoveId } from "#enums/move-id";
import type { Variant } from "#sprites/variant";
import i18next from "i18next";
@ -10,19 +9,6 @@ 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 => capitalizeFirstLetter(s.toLowerCase()))
.join(" ");
}
export function randomString(length: number, seeded = false) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@ -278,7 +264,7 @@ export function formatMoney(format: MoneyFormat, amount: number) {
}
export function formatStat(stat: number, forHp = false): string {
return formatLargeNumber(stat, forHp ? 100000 : 1000000);
return formatLargeNumber(stat, forHp ? 100_000 : 1_000_000);
}
export function executeIf<T>(condition: boolean, promiseFunc: () => Promise<T>): Promise<T | null> {
@ -359,31 +345,6 @@ export function fixedInt(value: number): number {
return new FixedInt(value) as unknown as number;
}
/**
* Formats a string to title case
* @param unformattedText Text to be formatted
* @returns the formatted string
*/
export function formatText(unformattedText: string): string {
const text = unformattedText.split("_");
for (let i = 0; i < text.length; i++) {
text[i] = text[i].charAt(0).toUpperCase() + text[i].substring(1).toLowerCase();
}
return text.join(" ");
}
export function toCamelCaseString(unformattedText: string): string {
if (!unformattedText) {
return "";
}
return unformattedText
.split(/[_ ]/)
.filter(f => f)
.map((f, i) => (i ? `${f[0].toUpperCase()}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
}
export function rgbToHsv(r: number, g: number, b: number) {
const v = Math.max(r, g, b);
const c = v - Math.min(r, g, b);
@ -510,41 +471,6 @@ export function truncateString(str: string, maxLength = 10) {
return str;
}
/**
* Convert a space-separated string into a capitalized and underscored string.
* @param input - The string to be converted.
* @returns The converted string with words capitalized and separated by underscores.
*/
export function reverseValueToKeySetting(input: string) {
// Split the input string into an array of words
const words = input.split(" ");
// Capitalize the first letter of each word and convert the rest to lowercase
const capitalizedWords = words.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
// Join the capitalized words with underscores and return the result
return capitalizedWords.join("_");
}
/**
* Capitalize a string.
* @param str - The string to be capitalized.
* @param sep - The separator between the words of the string.
* @param lowerFirstChar - Whether the first character of the string should be lowercase or not.
* @param returnWithSpaces - Whether the returned string should have spaces between the words or not.
* @returns The capitalized string.
*/
export function capitalizeString(str: string, sep: string, lowerFirstChar = true, returnWithSpaces = false) {
if (str) {
const splitedStr = str.toLowerCase().split(sep);
for (let i = +lowerFirstChar; i < splitedStr?.length; i++) {
splitedStr[i] = splitedStr[i].charAt(0).toUpperCase() + splitedStr[i].substring(1);
}
return returnWithSpaces ? splitedStr.join(" ") : splitedStr.join("");
}
return null;
}
/**
* Report whether a given value is nullish (`null`/`undefined`).
* @param val - The value whose nullishness is being checked
@ -554,15 +480,6 @@ export function isNullOrUndefined(val: any): val is null | undefined {
return val === null || val === undefined;
}
/**
* Capitalize the first letter of a string.
* @param str - The string whose first letter is being capitalized
* @return The original string with its first letter capitalized
*/
export function capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* This function is used in the context of a Pokémon battle game to calculate the actual integer damage value from a float result.
* Many damage calculation formulas involve various parameters and result in float values.
@ -597,26 +514,6 @@ export function isBetween(num: number, min: number, max: number): boolean {
return min <= num && num <= max;
}
/**
* Helper method to return the animation filename for a given move
*
* @param move the move for which the animation filename is needed
*/
export function animationFileName(move: MoveId): string {
return MoveId[move].toLowerCase().replace(/_/g, "-");
}
/**
* Transforms a camelCase string into a kebab-case string
* @param str The camelCase string
* @returns A kebab-case string
*
* @source {@link https://stackoverflow.com/a/67243723/}
*/
export function camelCaseToKebabCase(str: string): string {
return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase());
}
/** Get the localized shiny descriptor for the provided variant
* @param variant - The variant to get the shiny descriptor for
* @returns The localized shiny descriptor

179
src/utils/strings.ts Normal file
View File

@ -0,0 +1,179 @@
// TODO: Standardize file and path casing to remove the need for all these different casing methods
// #region Split string code
// Regexps involved with splitting words in various case formats.
// Sourced from https://www.npmjs.com/package/change-case (with slight tweaking here and there)
/** Regex to split at word boundaries.*/
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
/** Regex to split around single-letter uppercase words.*/
const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
/** Regexp involved with stripping non-word delimiters from the result. */
const DELIM_STRIP_REGEXP = /[-_ ]+/giu;
// The replacement value for splits.
const SPLIT_REPLACE_VALUE = "$1\0$2";
/**
* Split any cased string into an array of its constituent words.
* @param string - The string to be split
* @returns The new string, delimited at each instance of one or more spaces, underscores, hyphens
* or lower-to-upper boundaries.
* @remarks
* **DO NOT USE THIS FUNCTION!**
* Exported only to allow for testing.
* @todo Consider tests into [in-source testing](https://vitest.dev/guide/in-source.html) and converting this to unexported
*/
export function splitWords(value: string): string[] {
let result = value.trim();
result = result.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE).replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE);
result = result.replace(DELIM_STRIP_REGEXP, "\0");
// Trim the delimiter from around the output string
return trimFromStartAndEnd(result, "\0").split(/\0/g);
}
/**
* Helper function to remove one or more sequences of characters from either end of a string.
* @param str - The string to replace
* @param charToTrim - The string to remove
* @returns The result of removing all instances of {@linkcode charsToTrim} from either end of {@linkcode str}.
*/
function trimFromStartAndEnd(str: string, charToTrim: string): string {
let start = 0;
let end = str.length;
const blockLength = charToTrim.length;
while (str.startsWith(charToTrim, start)) {
start += blockLength;
}
if (start - end === blockLength) {
// Occurs if the ENTIRE string is made up of charToTrim (at which point we return nothing)
return "";
}
while (str.endsWith(charToTrim, end)) {
end -= blockLength;
}
return str.slice(start, end);
}
// #endregion Split String code
/**
* Capitalize the first letter of a string.
* @param str - The string whose first letter is to be capitalized
* @return The original string with its first letter capitalized.
* @example
* ```ts
* console.log(capitalizeFirstLetter("consectetur adipiscing elit")); // returns "Consectetur adipiscing elit"
* ```
*/
export function capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Helper method to convert a string into `Title Case` (such as one used for console logs).
* @param str - The string being converted
* @returns The result of converting `str` into title case.
* @example
* ```ts
* console.log(toTitleCase("lorem ipsum dolor sit amet")); // returns "Lorem Ipsum Dolor Sit Amet"
* ```
*/
export function toTitleCase(str: string): string {
return splitWords(str)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
}
/**
* Helper method to convert a string into `camelCase` (such as one used for i18n keys).
* @param str - The string being converted
* @returns The result of converting `str` into camel case.
* @example
* ```ts
* console.log(toCamelCase("BIG_ANGRY_TRAINER")); // returns "bigAngryTrainer"
* ```
*/
export function toCamelCase(str: string) {
return splitWords(str)
.map((word, index) => (index === 0 ? word.toLowerCase() : capitalizeFirstLetter(word)))
.join("");
}
/**
* Helper method to convert a string into `PascalCase`.
* @param str - The string being converted
* @returns The result of converting `str` into pascal case.
* @example
* ```ts
* console.log(toPascalCase("hi how was your day")); // returns "HiHowWasYourDay"
* ```
* @remarks
*/
export function toPascalCase(str: string) {
return splitWords(str)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join("");
}
/**
* Helper method to convert a string into `kebab-case` (such as one used for filenames).
* @param str - The string being converted
* @returns The result of converting `str` into kebab case.
* @example
* ```ts
* console.log(toKebabCase("not_kebab-caSe String")); // returns "not-kebab-case-string"
* ```
*/
export function toKebabCase(str: string): string {
return splitWords(str)
.map(word => word.toLowerCase())
.join("-");
}
/**
* Helper method to convert a string into `snake_case` (such as one used for filenames).
* @param str - The string being converted
* @returns The result of converting `str` into snake case.
* @example
* ```ts
* console.log(toSnakeCase("not-in snake_CaSe")); // returns "not_in_snake_case"
* ```
*/
export function toSnakeCase(str: string) {
return splitWords(str)
.map(word => word.toLowerCase())
.join("_");
}
/**
* Helper method to convert a string into `UPPER_SNAKE_CASE`.
* @param str - The string being converted
* @returns The result of converting `str` into upper snake case.
* @example
* ```ts
* console.log(toUpperSnakeCase("apples bananas_oranGes-PearS")); // returns "APPLES_BANANAS_ORANGES_PEARS"
* ```
*/
export function toUpperSnakeCase(str: string) {
return splitWords(str)
.map(word => word.toUpperCase())
.join("_");
}
/**
* Helper method to convert a string into `Pascal_Snake_Case`.
* @param str - The string being converted
* @returns The result of converting `str` into pascal snake case.
* @example
* ```ts
* console.log(toPascalSnakeCase("apples-bananas_oranGes Pears")); // returns "Apples_Bananas_Oranges_Pears"
* ```
*/
export function toPascalSnakeCase(str: string) {
return splitWords(str)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join("_");
}

26
test/@types/vitest.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
import type { Pokemon } from "#field/pokemon";
import type { PokemonType } from "#enums/pokemon-type";
import type { expect } from "vitest";
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
declare module "vitest" {
interface Assertion {
/**
* Matcher to check if an array contains EXACTLY the given items (in any order).
*
* Different from {@linkcode expect.arrayContaining} as the latter only requires the array contain
* _at least_ the listed items.
*
* @param expected - The expected contents of the array, in any order.
* @see {@linkcode expect.arrayContaining}
*/
toEqualArrayUnsorted<E>(expected: E[]): void;
/**
* Matcher to check if a {@linkcode Pokemon}'s current typing includes the given types.
*
* @param expected - The expected types (in any order).
* @param options - The options passed to the matcher.
*/
toHaveTypes(expected: PokemonType[], options?: toHaveTypesOptions): void;
}
}

Some files were not shown because too many files have changed in this diff Show More