mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-06 07:29:30 +02:00
[Misc] Moved + cleaned up string manipulation functions (#6112)
* Added string utility package to replace util functions
* Changed string manipulation functions fully over to `change-case`
* Fixed missing comma in package.json
trailing commas when :(
* fixed lockfile
* Hopefully re-added all the string utils
* fixed package json
* Fixed remaining cases of regex + code dupliation
* Fixed more bugs and errors
* Moved around functions and hopefully fixed the regex issues
* Minor renaming
* Fixed incorrect casing on setting strings
pascal snake case 💀
* ran biome
This commit is contained in:
parent
29d9bb6e7b
commit
19730f9cf0
@ -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`;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import type { DexAttrProps, GameData } from "#system/game-data";
|
||||
import { BooleanHolder, type NumberHolder, randSeedItem } from "#utils/common";
|
||||
import { deepCopy } from "#utils/data";
|
||||
import { getPokemonSpecies } 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 */
|
||||
@ -67,14 +68,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]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,23 +103,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`])}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -579,31 +576,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}`),
|
||||
});
|
||||
}
|
||||
|
||||
@ -671,29 +656,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,
|
||||
@ -832,13 +801,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();
|
||||
}
|
||||
|
||||
@ -866,13 +829,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();
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -3,7 +3,7 @@ import { EFFECTIVE_STATS, getShortenedStatKey, Stat } from "#enums/stat";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import { getBBCodeFrag } from "#ui/text";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function getNatureName(
|
||||
@ -13,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);
|
||||
|
@ -29,15 +29,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,
|
||||
randSeedItem,
|
||||
} 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";
|
||||
|
||||
@ -91,6 +85,7 @@ export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): Po
|
||||
return retSpecies;
|
||||
}
|
||||
|
||||
// TODO: Clean this up and seriously review alternate means of fusion naming
|
||||
export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string {
|
||||
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i;
|
||||
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i;
|
||||
@ -904,14 +899,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,
|
||||
@ -937,7 +932,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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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}`;
|
||||
},
|
||||
},
|
||||
|
@ -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(
|
||||
|
@ -6,7 +6,7 @@ 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 { 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) => {
|
||||
|
@ -18,7 +18,8 @@ import { BattleSceneEventType } from "#events/battle-scene";
|
||||
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";
|
||||
|
||||
@ -49,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 {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import { formatText } from "#utils/common";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
const hiddenX = -150;
|
||||
@ -101,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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ import type { GameData } from "#system/game-data";
|
||||
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";
|
||||
|
||||
@ -502,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ import { MoveInfoOverlay } from "#ui/move-info-overlay";
|
||||
import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler";
|
||||
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";
|
||||
|
||||
@ -1409,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;
|
||||
|
@ -54,16 +54,10 @@ import { PokedexInfoOverlay } from "#ui/pokedex-info-overlay";
|
||||
import { StatsContainer } from "#ui/stats-container";
|
||||
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 } 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";
|
||||
@ -2620,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);
|
||||
|
@ -10,6 +10,7 @@ import { ScrollBar } from "#ui/scroll-bar";
|
||||
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 {
|
||||
@ -88,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.
|
||||
*/
|
||||
@ -210,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}`);
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
import { AbstractControlSettingsUiHandler } from "#ui/abstract-control-settings-ui-handler";
|
||||
import { NavigationManager } from "#ui/navigation-menu";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import { reverseValueToKeySetting, truncateString } from "#utils/common";
|
||||
import { truncateString } from "#utils/common";
|
||||
import { toPascalSnakeCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
@ -101,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);
|
||||
|
@ -69,10 +69,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 { argbFromRgba } from "@material/material-color-utilities";
|
||||
import i18next from "i18next";
|
||||
import type { GameObjects } from "phaser";
|
||||
@ -3527,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);
|
||||
|
@ -35,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";
|
||||
|
||||
@ -962,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(
|
||||
|
@ -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
179
src/utils/strings.ts
Normal 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("_");
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { getIconForLatestInput, getSettingNameWithKeycode } from "#inputs/config-handler";
|
||||
import { SettingKeyboard } from "#system/settings-keyboard";
|
||||
import { toPascalSnakeCase } from "#utils/strings";
|
||||
import { expect } from "vitest";
|
||||
|
||||
export class InGameManip {
|
||||
@ -56,22 +57,11 @@ export class InGameManip {
|
||||
return this;
|
||||
}
|
||||
|
||||
normalizeSettingNameString(input) {
|
||||
// Convert the input string to lower case
|
||||
const lowerCasedInput = input.toLowerCase();
|
||||
|
||||
// Replace underscores with spaces, capitalize the first letter of each word, and join them back with underscores
|
||||
const words = lowerCasedInput.split("_").map(word => word.charAt(0).toUpperCase() + word.slice(1));
|
||||
const result = words.join("_");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
weShouldTriggerTheButton(settingName) {
|
||||
if (!settingName.includes("Button_")) {
|
||||
settingName = "Button_" + settingName;
|
||||
}
|
||||
this.settingName = SettingKeyboard[this.normalizeSettingNameString(settingName)];
|
||||
this.settingName = SettingKeyboard[toPascalSnakeCase(settingName)];
|
||||
expect(getSettingNameWithKeycode(this.config, this.keycode)).toEqual(this.settingName);
|
||||
return this;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ export class MenuManip {
|
||||
this.specialCaseIcon = null;
|
||||
}
|
||||
|
||||
// TODO: Review this
|
||||
convertNameToButtonString(input) {
|
||||
// Check if the input starts with "Alt_Button"
|
||||
if (input.startsWith("Alt_Button")) {
|
||||
|
@ -12,7 +12,8 @@ import type { CommandPhase } from "#phases/command-phase";
|
||||
import type { EnemyCommandPhase } from "#phases/enemy-command-phase";
|
||||
import { MoveEffectPhase } from "#phases/move-effect-phase";
|
||||
import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper";
|
||||
import { coerceArray, toReadableString } from "#utils/common";
|
||||
import { coerceArray } from "#utils/common";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import type { MockInstance } from "vitest";
|
||||
import { expect, vi } from "vitest";
|
||||
|
||||
@ -66,12 +67,12 @@ export class MoveHelper extends GameManagerHelper {
|
||||
const movePosition = this.getMovePosition(pkmIndex, move);
|
||||
if (movePosition === -1) {
|
||||
expect.fail(
|
||||
`MoveHelper.select called with move '${toReadableString(MoveId[move])}' not in moveset!` +
|
||||
`\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` +
|
||||
`MoveHelper.select called with move '${toTitleCase(MoveId[move])}' not in moveset!` +
|
||||
`\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` +
|
||||
`\nMoveset: [${this.game.scene
|
||||
.getPlayerParty()
|
||||
[pkmIndex].getMoveset()
|
||||
.map(pm => toReadableString(MoveId[pm.moveId]))
|
||||
.map(pm => toTitleCase(MoveId[pm.moveId]))
|
||||
.join(", ")}]`,
|
||||
);
|
||||
}
|
||||
@ -110,12 +111,12 @@ export class MoveHelper extends GameManagerHelper {
|
||||
const movePosition = this.getMovePosition(pkmIndex, move);
|
||||
if (movePosition === -1) {
|
||||
expect.fail(
|
||||
`MoveHelper.selectWithTera called with move '${toReadableString(MoveId[move])}' not in moveset!` +
|
||||
`\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` +
|
||||
`MoveHelper.selectWithTera called with move '${toTitleCase(MoveId[move])}' not in moveset!` +
|
||||
`\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` +
|
||||
`\nMoveset: [${this.game.scene
|
||||
.getPlayerParty()
|
||||
[pkmIndex].getMoveset()
|
||||
.map(pm => toReadableString(MoveId[pm.moveId]))
|
||||
.map(pm => toTitleCase(MoveId[pm.moveId]))
|
||||
.join(", ")}]`,
|
||||
);
|
||||
}
|
||||
|
47
test/utils/strings.test.ts
Normal file
47
test/utils/strings.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { splitWords } from "#utils/strings";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
interface testCase {
|
||||
input: string;
|
||||
words: string[];
|
||||
}
|
||||
|
||||
const testCases: testCase[] = [
|
||||
{
|
||||
input: "Lorem ipsum dolor sit amet",
|
||||
words: ["Lorem", "ipsum", "dolor", "sit", "amet"],
|
||||
},
|
||||
{
|
||||
input: "consectetur-adipiscing-elit",
|
||||
words: ["consectetur", "adipiscing", "elit"],
|
||||
},
|
||||
{
|
||||
input: "sed_do_eiusmod_tempor_incididunt_ut_labore",
|
||||
words: ["sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore"],
|
||||
},
|
||||
{
|
||||
input: "Et Dolore Magna Aliqua",
|
||||
words: ["Et", "Dolore", "Magna", "Aliqua"],
|
||||
},
|
||||
{
|
||||
input: "BIG_ANGRY_TRAINER",
|
||||
words: ["BIG", "ANGRY", "TRAINER"],
|
||||
},
|
||||
{
|
||||
input: "ApplesBananasOrangesAndAPear",
|
||||
words: ["Apples", "Bananas", "Oranges", "And", "A", "Pear"],
|
||||
},
|
||||
{
|
||||
input: "mysteryEncounters/anOfferYouCantRefuse",
|
||||
words: ["mystery", "Encounters/an", "Offer", "You", "Cant", "Refuse"],
|
||||
},
|
||||
];
|
||||
|
||||
describe("Utils - Casing -", () => {
|
||||
describe("splitWords", () => {
|
||||
it.each(testCases)("should split a string into its constituent words - $input", ({ input, words }) => {
|
||||
const ret = splitWords(input);
|
||||
expect(ret).toEqual(words);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user