Merge remote-tracking branch 'upstream/beta' into chilly-reception-msg

This commit is contained in:
Bertie690 2025-06-14 14:13:55 -04:00
commit 77ad05f11f
61 changed files with 248 additions and 261 deletions

View File

@ -218,7 +218,7 @@ module.exports = {
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
@ -226,7 +226,7 @@ module.exports = {
// moduleSystems: ['cjs', 'es6'],
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
@ -271,7 +271,7 @@ module.exports = {
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
@ -322,8 +322,8 @@ module.exports = {
A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation
documentation
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],

2
.github/CODEOWNERS vendored
View File

@ -8,7 +8,7 @@
# Art Team
/public/**/*.png @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team
/public/images @pagefaultgames/art-team
/public/battle-anims @pagefaultgames/art-team

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples -->
<!--
Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
- [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
-->
<!--
Make sure that this PR is not overlapping with someone else's work
Please try to keep the PR self-contained (and small)
Please try to keep the PR self-contained (and small!)
-->
## What are the changes the user will see?
@ -66,11 +69,11 @@ Do the reviewers need to do something special in order to test your changes?
- [ ] Have I provided a clear explanation of the changes?
- [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test:silent`)
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes?
- [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes?
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
Are there any localization additions or changes? If so:
- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo?
- [ ] If so, please leave a link to it here:
- [ ] Has the translation team been contacted for proofreading/translation?
- [ ] Has the translation team been contacted for proofreading/translation?

View File

@ -35,7 +35,7 @@ jobs:
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy build on server
if: github.event_name == 'push' && github.ref_name == 'main'
run: |
run: |
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
- name: Purge Cloudflare Cache

View File

@ -33,7 +33,7 @@ jobs:
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters
run: npm run eslint-ci

View File

@ -47,8 +47,8 @@
"correctness": {
"noUndeclaredVariables": "off",
"noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
"noSwitchDeclarations": "error",
"noVoidTypeReturn": "error",
"noUnusedImports": "error"
},
"style": {
@ -85,7 +85,7 @@
"useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
"noUselessConstructor": "error",
"noBannedTypes": "warn" // TODO: Refactor and make this an error
},
"nursery": {

2
global.d.ts vendored
View File

@ -7,7 +7,7 @@ declare global {
* Only used in testing.
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
* Used to load i18n files exclusively.
*
*
* To set up your own server in a test see `game_data.test.ts`
*/
var server: SetupServerApi;

View File

@ -18,7 +18,7 @@
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc",
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",

View File

@ -29,4 +29,4 @@ export type ModifierString = keyof ModifierConstructorMap;
export type ModifierPool = {
[tier: string]: WeightedModifierType[];
}
};

View File

@ -9,6 +9,7 @@ import {
randSeedInt,
type Constructor,
randSeedFloat,
coerceArray,
} from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages";
import { GroundedTag } from "#app/data/battler-tags";
@ -4689,7 +4690,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
super(true);
this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes];
this.immuneTagTypes = coerceArray(immuneTagTypes);
}
override canApplyPreApplyBattlerTag(
@ -6694,7 +6695,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr {
constructor(stats: BattleStat[], stages: number) {
super();
this.stats = Array.isArray(stats) ? stats : [stats];
this.stats = stats;
this.stages = stages;
}

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName, coerceArray } from "../utils/common";
import type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags";
@ -520,7 +520,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
* @param encounterAnim one or more animations to fetch
*/
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
const anims = coerceArray(encounterAnim);
const encounterAnimNames = getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) {

View File

@ -21,7 +21,7 @@ import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import type { MovePhase } from "#app/phases/move-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id";
@ -50,7 +50,7 @@ export class BattlerTag {
isBatonPassable = false,
) {
this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType];
this.lapseTypes = coerceArray(lapseType);
this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId;
@ -125,16 +125,6 @@ export interface TerrainBattlerTag {
* Players and enemies should not be allowed to select restricted moves.
*/
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
constructor(
tagType: BattlerTagType,
lapseType: BattlerTagLapseType | BattlerTagLapseType[],
turnCount: number,
sourceMove?: MoveId,
sourceId?: number,
) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
}
/** @override */
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
@ -1470,16 +1460,6 @@ export class WrapTag extends DamagingTrapTag {
}
export abstract class VortexTrapTag extends DamagingTrapTag {
constructor(
tagType: BattlerTagType,
commonAnim: CommonAnim,
turnCount: number,
sourceMove: MoveId,
sourceId: number,
) {
super(tagType, commonAnim, turnCount, sourceMove, sourceId);
}
getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battlerTags:vortexOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),

View File

@ -2173,6 +2173,7 @@ export class PlantHealAttr extends WeatherHealAttr {
case WeatherType.SANDSTORM:
case WeatherType.HAIL:
case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN:
return 0.25;
default:
@ -4174,6 +4175,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr {
case WeatherType.SANDSTORM:
case WeatherType.HAIL:
case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN:
power.value *= 0.5;
return true;

View File

@ -11,10 +11,7 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requir
import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#enums/modifier-tier";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";

View File

@ -7,10 +7,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { ModifierTier } from "#enums/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";

View File

@ -11,7 +11,7 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils/common";
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
this.requiredTimeOfDay = coerceArray(timeOfDay);
}
override meetsRequirement(): boolean {
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) {
super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
this.requiredWeather = coerceArray(weather);
}
override meetsRequirement(): boolean {
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) {
super();
this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
this.requiredHeldItemModifiers = coerceArray(heldItem);
}
override meetsRequirement(): boolean {
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species];
this.requiredSpecies = coerceArray(species);
}
override meetsRequirement(): boolean {
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature];
this.requiredNature = coerceArray(nature);
}
override meetsRequirement(): boolean {
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type];
this.requiredType = coerceArray(type);
}
override meetsRequirement(): boolean {
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
this.requiredMoves = coerceArray(moves);
}
override meetsRequirement(): boolean {
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
this.requiredMoves = coerceArray(learnableMove);
}
override meetsRequirement(): boolean {
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
this.requiredAbilities = coerceArray(abilities);
}
override meetsRequirement(): boolean {
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
this.requiredStatusEffect = coerceArray(statusEffect);
}
override meetsRequirement(): boolean {
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
this.requiredFormChangeItem = coerceArray(formChangeItem);
}
override meetsRequirement(): boolean {
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
this.requiredEvolutionItem = coerceArray(evolutionItems);
}
override meetsRequirement(): boolean {
@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
this.requiredHeldItemModifiers = coerceArray(heldItem);
this.requireTransferable = requireTransferable;
}
@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
this.requireTransferable = requireTransferable;
}

View File

@ -2,7 +2,7 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encoun
import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
import { capitalizeFirstLetter, coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
@ -717,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withAnimations(
...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
const animations = coerceArray(encounterAnimations);
return Object.assign(this, { encounterAnimations: animations });
}
@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedGameModes(
...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
const gameModes = coerceArray(disallowedGameModes);
return Object.assign(this, { disallowedGameModes: gameModes });
}
@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedChallenges(
...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
const challenges = coerceArray(disallowedChallenges);
return Object.assign(this, { disallowedChallenges: challenges });
}

View File

@ -1,7 +1,7 @@
import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { isNullOrUndefined } from "#app/utils/common";
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene";
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
super();
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
this.requiredMoves = coerceArray(requiredMoves);
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
this.excludeTmMoves = options.excludeTmMoves ?? false;

View File

@ -25,7 +25,7 @@ import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-optio
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler";
import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type";
@ -449,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* @param moves
*/
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
moves = Array.isArray(moves) ? moves : [moves];
moves = coerceArray(moves);
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
}
@ -792,7 +792,7 @@ export function setEncounterRewards(
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
*/
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
const participantIds = coerceArray(participantId);
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));

View File

@ -1,5 +1,5 @@
import i18next from "i18next";
import type { Constructor } from "#app/utils/common";
import { coerceArray, type Constructor } from "#app/utils/common";
import type { TimeOfDay } from "#enums/time-of-day";
import type Pokemon from "#app/field/pokemon";
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
@ -125,10 +125,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.statusEffects = coerceArray(statusEffects);
this.invert = invert;
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
}

View File

@ -1,7 +1,14 @@
import { globalScene } from "#app/global-scene";
import { modifierTypes } from "../data-lists";
import { PokemonMove } from "../moves/pokemon-move";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common";
import {
toReadableString,
isNullOrUndefined,
randSeedItem,
randSeedInt,
coerceArray,
randSeedIntRange,
} from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms";
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
}
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
// If specialty type is provided, set species filter and specialty type.
@ -729,12 +726,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
// Set species filter and specialty type if provided, otherwise filter by base total.

View File

@ -262,7 +262,7 @@ export class Arena {
return 5;
}
break;
case SpeciesId.LYCANROC:
case SpeciesId.LYCANROC: {
const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) {
case TimeOfDay.DAY:
@ -274,6 +274,7 @@ export class Arena {
return 1;
}
break;
}
}
return 0;

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
import Pokemon from "./pokemon";
import { fixedInt, randInt } from "#app/utils/common";
import { fixedInt, coerceArray, randInt } from "#app/utils/common";
export default class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>;
@ -57,9 +57,7 @@ export default class PokemonSpriteSparkleHandler {
}
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites)) {
sprites = [sprites];
}
sprites = coerceArray(sprites);
for (const s of sprites) {
if (this.sprites.has(s)) {
continue;
@ -69,9 +67,7 @@ export default class PokemonSpriteSparkleHandler {
}
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites)) {
sprites = [sprites];
}
sprites = coerceArray(sprites);
for (const s of sprites) {
this.sprites.delete(s);
}

View File

@ -41,6 +41,7 @@ import {
type nil,
type Constructor,
randSeedIntRange,
coerceArray,
} from "#app/utils/common";
import type { TypeDamageMultiplier } from "#app/data/type";
import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
@ -1774,9 +1775,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
? Overrides.MOVESET_OVERRIDE
: Overrides.OPP_MOVESET_OVERRIDE;
if (!Array.isArray(overrideArray)) {
overrideArray = [overrideArray];
}
overrideArray = coerceArray(overrideArray);
if (overrideArray.length > 0) {
if (!this.isPlayer()) {
this.moveset = [];
@ -4386,14 +4385,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// biome-ignore lint: there are a ton of issues..
faintCry(callback: Function): void {
if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) {
return this.fusionFaintCry(callback);
this.fusionFaintCry(callback);
return;
}
const key = this.species.getCryKey(this.formIndex);
let rate = 0.85;
const cry = globalScene.playSound(key, { rate: rate }) as AnySound;
if (!cry || globalScene.fieldVolume === 0) {
return callback();
callback();
return;
}
const sprite = this.getSprite();
const tintSprite = this.getTintSprite();
@ -4461,7 +4462,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate: rate,
}) as AnySound;
if (!cry || !fusionCry || globalScene.fieldVolume === 0) {
return callback();
callback();
return;
}
fusionCry.stop();
duration = Math.min(duration, fusionCry.totalDuration * 1000);

View File

@ -2393,8 +2393,6 @@ export interface ModifierPool {
[tier: string]: WeightedModifierType[];
}
const modifierPool: ModifierPool = {};
let modifierPoolThresholds = {};
let ignoredPoolIndexes = {};
@ -2859,7 +2857,7 @@ function getNewModifierTypeOption(
}
tier += upgradeCount;
while (tier && (!modifierPool.hasOwnProperty(tier) || !modifierPool[tier].length)) {
while (tier && (!pool.hasOwnProperty(tier) || !pool[tier].length)) {
tier--;
if (upgradeCount) {
upgradeCount--;
@ -2870,7 +2868,7 @@ function getNewModifierTypeOption(
if (tier < ModifierTier.MASTER && allowLuckUpgrades) {
const partyLuckValue = getPartyLuckValue(party);
const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4));
while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) {
while (pool.hasOwnProperty(tier + upgradeCount + 1) && pool[tier + upgradeCount + 1].length) {
if (randSeedInt(upgradeOdds) < 4) {
upgradeCount++;
} else {
@ -2920,6 +2918,7 @@ function getNewModifierTypeOption(
}
export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType {
const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER);
let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0];
if (modifierType instanceof WeightedModifierType) {
modifierType = (modifierType as WeightedModifierType).modifierType;

View File

@ -272,7 +272,7 @@ class DefaultOverrides {
/**
* Set all non-scripted waves to use the selected battle type.
*
*
* Ignored if set to {@linkcode BattleType.TRAINER} and `DISABLE_STANDARD_TRAINERS_OVERRIDE` is `true`.
*/
readonly BATTLE_TYPE_OVERRIDE: Exclude<BattleType, BattleType.CLEAR> | null = null;
@ -298,4 +298,4 @@ export type RandomTrainerOverride = {
}
/** The type of the {@linkcode DefaultOverrides} class */
export type OverridesType = typeof DefaultOverrides;
export type OverridesType = typeof DefaultOverrides;

View File

@ -12,7 +12,7 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import type { Constructor } from "#app/utils/common";
import { coerceArray, type Constructor } from "#app/utils/common";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
@ -438,9 +438,7 @@ export class PhaseManager {
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
phase = coerceArray(phase);
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
@ -460,9 +458,7 @@ export class PhaseManager {
* @returns `true` if a `targetPhase` was found to append to
*/
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
phase = coerceArray(phase);
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph)));

View File

@ -1,8 +1,5 @@
import { ModifierTier } from "#enums/modifier-tier";
import {
regenerateModifierPoolThresholds,
getEnemyBuffModifierForWave,
} from "#app/modifier/modifier-type";
import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { EnemyPersistentModifier } from "#app/modifier/modifier";
import { Phase } from "#app/phase";

View File

@ -149,7 +149,7 @@ export class CommandPhase extends FieldPhase {
switch (command) {
case Command.TERA:
case Command.FIGHT:
case Command.FIGHT: {
let useStruggle = false;
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
if (
@ -233,7 +233,8 @@ export class CommandPhase extends FieldPhase {
);
}
break;
case Command.BALL:
}
case Command.BALL: {
const notInDex =
globalScene
.getEnemyField()
@ -337,8 +338,9 @@ export class CommandPhase extends FieldPhase {
}
}
break;
}
case Command.POKEMON:
case Command.RUN:
case Command.RUN: {
const isSwitch = command === Command.POKEMON;
const { currentBattle, arena } = globalScene;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
@ -445,6 +447,7 @@ export class CommandPhase extends FieldPhase {
}
}
break;
}
}
if (success) {

View File

@ -135,7 +135,8 @@ export class MovePhase extends BattlePhase {
this.showMoveText();
this.showFailedText();
}
return this.end();
this.end();
return;
}
this.pokemon.turnData.acted = true;
@ -310,7 +311,8 @@ export class MovePhase extends BattlePhase {
if (fail) {
this.showMoveText();
this.showFailedText();
return this.end();
this.end();
return;
}
}

View File

@ -53,7 +53,8 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstituteAddAnim(): void {
const substitute = this.pokemon.getTag(SubstituteTag);
if (isNullOrUndefined(substitute)) {
return this.end();
this.end();
return;
}
const getSprite = () => {
@ -116,12 +117,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstitutePreMoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
this.end();
return;
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
this.end();
return;
}
globalScene.tweens.add({
@ -145,12 +148,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstitutePostMoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
this.end();
return;
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
this.end();
return;
}
globalScene.tweens.add({
@ -174,12 +179,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstituteRemoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
this.end();
return;
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
this.end();
return;
}
const getSprite = () => {
@ -244,12 +251,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doCommanderApplyAnim(): void {
if (!globalScene.currentBattle?.double) {
return this.end();
this.end();
return;
}
const dondozo = this.pokemon.getAlly();
if (dondozo?.species?.speciesId !== SpeciesId.DONDOZO) {
return this.end();
this.end();
return;
}
const tatsugiriX = this.pokemon.x + this.pokemon.getSprite().x;
@ -329,7 +338,8 @@ export class PokemonAnimPhase extends BattlePhase {
const tatsugiri = this.pokemon.getAlly();
if (isNullOrUndefined(tatsugiri)) {
console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined");
return this.end();
this.end();
return;
}
const tatsuSprite = globalScene.addPokemonSprite(

View File

@ -29,7 +29,8 @@ export class PokemonTransformPhase extends PokemonPhase {
const target = globalScene.getField(true).find(p => p.getBattlerIndex() === this.targetIndex);
if (!target) {
return this.end();
this.end();
return;
}
user.summonData.speciesForm = target.getSpeciesForm();

View File

@ -29,7 +29,8 @@ export class QuietFormChangePhase extends BattlePhase {
super.start();
if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) {
return this.end();
this.end();
return;
}
const preName = getPokemonNameWithAffix(this.pokemon);

View File

@ -51,7 +51,7 @@ float hue2rgb(float f1, float f2, float hue) {
vec3 rgb2hsl(vec3 color) {
vec3 hsl;
float fmin = min(min(color.r, color.g), color.b);
float fmax = max(max(color.r, color.g), color.b);
float delta = fmax - fmin;
@ -66,7 +66,7 @@ vec3 rgb2hsl(vec3 color) {
hsl.y = delta / (fmax + fmin);
else
hsl.y = delta / (2.0 - fmax - fmin);
float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;
@ -89,24 +89,24 @@ vec3 rgb2hsl(vec3 color) {
vec3 hsl2rgb(vec3 hsl) {
vec3 rgb;
if (hsl.y == 0.0)
rgb = vec3(hsl.z);
else {
float f2;
if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}

View File

@ -83,7 +83,7 @@ vec3 rgb2hsl(vec3 color) {
hsl.y = delta / (fmax + fmin);
else
hsl.y = delta / (2.0 - fmax - fmin);
float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;
@ -106,24 +106,24 @@ vec3 rgb2hsl(vec3 color) {
vec3 hsl2rgb(vec3 hsl) {
vec3 rgb;
if (hsl.y == 0.0)
rgb = vec3(hsl.z);
else {
float f2;
if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}

View File

@ -1,10 +1,8 @@
import { coerceArray } from "#app/utils/common";
let manifest: object;
export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin {
constructor(scene: Phaser.Scene) {
super(scene);
}
get manifest() {
return manifest;
}
@ -14,9 +12,7 @@ export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin
}
addFile(file): void {
if (!Array.isArray(file)) {
file = [file];
}
file = coerceArray(file);
file.forEach(item => {
if (manifest) {

View File

@ -174,7 +174,24 @@ export async function initI18n(): Promise<void> {
"es-MX": ["es-ES", "en"],
default: ["en"],
},
supportedLngs: ["en", "es-ES", "es-MX", "fr", "it", "de", "zh-CN", "zh-TW", "pt-BR", "ko", "ja", "ca", "da", "tr", "ro", "ru"],
supportedLngs: [
"en",
"es-ES",
"es-MX",
"fr",
"it",
"de",
"zh-CN",
"zh-TW",
"pt-BR",
"ko",
"ja",
"ca",
"da",
"tr",
"ro",
"ru",
],
backend: {
loadPath(lng: string, [ns]: string[]) {
let fileName: string;

View File

@ -41,9 +41,9 @@ export function minifyJsonPlugin(basePath: string | string[], recursive?: boolea
},
async closeBundle() {
console.log("Minifying JSON files...");
const basePathes = Array.isArray(basePath) ? basePath : [basePath];
const basePaths = Array.isArray(basePath) ? basePath : [basePath];
basePathes.forEach(basePath => {
basePaths.forEach(basePath => {
const baseDir = path.resolve(buildDir, basePath);
if (fs.existsSync(baseDir)) {
applyToDir(baseDir, recursive);

View File

@ -1,3 +1,5 @@
import { coerceArray } from "#app/utils/common";
export const legacyCompatibleImages: string[] = [];
export class SceneBase extends Phaser.Scene {
@ -88,9 +90,7 @@ export class SceneBase extends Phaser.Scene {
} else {
folder += "/";
}
if (!Array.isArray(filenames)) {
filenames = [filenames];
}
filenames = coerceArray(filenames);
for (const f of filenames as string[]) {
this.load.audio(folder + key, this.getCachedUrl(`audio/${folder}${f}`));
}

View File

@ -32,7 +32,10 @@ const pressAction = i18next.t("settings:pressActionToAssign");
export const settingGamepadOptions = {
[SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")],
[SettingGamepad.Gamepad_Support]: [i18next.t("settings:gamepadSupportAuto"), i18next.t("settings:gamepadSupportDisabled")],
[SettingGamepad.Gamepad_Support]: [
i18next.t("settings:gamepadSupportAuto"),
i18next.t("settings:gamepadSupportDisabled"),
],
[SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction],
[SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction],
[SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction],

View File

@ -959,7 +959,7 @@ export function setSetting(setting: string, value: number): boolean {
},
{
label: "Türkçe (Needs Help)",
handler: () => changeLocaleHandler("tr")
handler: () => changeLocaleHandler("tr"),
},
{
label: "Русский (Needs Help)",
@ -967,11 +967,11 @@ export function setSetting(setting: string, value: number): boolean {
},
{
label: "Dansk (Needs Help)",
handler: () => changeLocaleHandler("da")
handler: () => changeLocaleHandler("da"),
},
{
label: "Română (Needs Help)",
handler: () => changeLocaleHandler("ro")
handler: () => changeLocaleHandler("ro"),
},
{
label: i18next.t("settings:back"),

View File

@ -56,10 +56,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected defaultTextStyle: TextStyle = TextStyle.WINDOW;
protected textContent: string;
constructor(mode: UiMode | null) {
super(mode);
}
abstract getWindowWidth(): number;
getWindowHeight(): number {

View File

@ -69,7 +69,7 @@ export default class AdminUiHandler extends FormModalUiHandler {
case AdminMode.SEARCH:
inputFieldConfigs.push({ label: "Username" });
break;
case AdminMode.ADMIN:
case AdminMode.ADMIN: {
const adminResult = this.adminResult ?? {
username: "",
discordId: "",
@ -90,6 +90,7 @@ export default class AdminUiHandler extends FormModalUiHandler {
inputFieldConfigs.push({ label: "Last played", isReadOnly: true });
inputFieldConfigs.push({ label: "Registered", isReadOnly: true });
break;
}
}
return inputFieldConfigs;
}

View File

@ -169,12 +169,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container {
entryContainer.add(scoreLabel);
switch (this.category) {
case ScoreboardCategory.DAILY:
case ScoreboardCategory.DAILY: {
const waveLabel = addTextObject(68, 0, wave, TextStyle.WINDOW, {
fontSize: "54px",
});
entryContainer.add(waveLabel);
break;
}
case ScoreboardCategory.WEEKLY:
scoreLabel.x -= 16;
break;

View File

@ -131,7 +131,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
gachaInfoContainer.add(gachaUpLabel);
switch (gachaType as GachaType) {
case GachaType.LEGENDARY:
case GachaType.LEGENDARY: {
if (["de", "es-ES"].includes(currentLanguage)) {
gachaUpLabel.setAlign("center");
gachaUpLabel.setY(0);
@ -152,6 +152,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
gachaInfoContainer.add(pokemonIcon);
break;
}
case GachaType.MOVE:
if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) {
gachaUpLabel.setAlign("center");
@ -623,11 +624,12 @@ export default class EggGachaUiHandler extends MessageUiHandler {
updateGachaInfo(gachaType: GachaType): void {
const infoContainer = this.gachaInfoContainers[gachaType];
switch (gachaType as GachaType) {
case GachaType.LEGENDARY:
case GachaType.LEGENDARY: {
const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime()));
const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite;
pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false));
break;
}
}
}

View File

@ -686,7 +686,7 @@ export default class MenuUiHandler extends MessageUiHandler {
error = true;
}
break;
case MenuOptions.LOG_OUT:
case MenuOptions.LOG_OUT: {
success = true;
const doLogout = () => {
ui.setMode(UiMode.LOADING, {
@ -718,6 +718,7 @@ export default class MenuUiHandler extends MessageUiHandler {
doLogout();
}
break;
}
}
} else if (button === Button.CANCEL) {
success = true;

View File

@ -1385,7 +1385,7 @@ export default class PartyUiHandler extends MessageUiHandler {
case PartyOption.MOVE_1:
case PartyOption.MOVE_2:
case PartyOption.MOVE_3:
case PartyOption.MOVE_4:
case PartyOption.MOVE_4: {
const move = pokemon.moveset[option - PartyOption.MOVE_1];
if (this.showMovePp) {
const maxPP = move.getMovePp();
@ -1395,7 +1395,8 @@ export default class PartyUiHandler extends MessageUiHandler {
optionName = move.getName();
}
break;
default:
}
default: {
const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon);
if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) {
const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM];
@ -1410,6 +1411,7 @@ export default class PartyUiHandler extends MessageUiHandler {
}
}
break;
}
}
} else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) {
const learnableLevelMoves = pokemon.getLearnableLevelMoves();

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene";
import { fixedInt } from "#app/utils/common";
import { fixedInt, coerceArray } from "#app/utils/common";
export enum PokemonIconAnimMode {
NONE,
@ -49,9 +49,7 @@ export default class PokemonIconAnimHandler {
}
addOrUpdate(icons: PokemonIcon | PokemonIcon[], mode: PokemonIconAnimMode): void {
if (!Array.isArray(icons)) {
icons = [icons];
}
icons = coerceArray(icons);
for (const i of icons) {
if (this.icons.has(i) && this.icons.get(i) === mode) {
continue;
@ -66,9 +64,7 @@ export default class PokemonIconAnimHandler {
}
remove(icons: PokemonIcon | PokemonIcon[]): void {
if (!Array.isArray(icons)) {
icons = [icons];
}
icons = coerceArray(icons);
for (const i of icons) {
if (this.toggled) {
const icon = this.icons.get(i);

View File

@ -567,7 +567,7 @@ export default class RunInfoUiHandler extends UiHandler {
case GameModes.SPLICED_ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false);
break;
case GameModes.CHALLENGE:
case GameModes.CHALLENGE: {
modeText.appendText(`${i18next.t("gameMode:challenge")}`, false);
modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `);
modeText.setWrapMode(1); // wrap by word
@ -582,6 +582,7 @@ export default class RunInfoUiHandler extends UiHandler {
}
}
break;
}
case GameModes.ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endless")}`, false);
break;
@ -687,7 +688,7 @@ export default class RunInfoUiHandler extends UiHandler {
case Challenges.SINGLE_GENERATION:
rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
break;
case Challenges.SINGLE_TYPE:
case Challenges.SINGLE_TYPE: {
const typeRule = PokemonType[this.runInfo.challenges[i].value - 1];
const typeTextColor = `[color=${TypeColor[typeRule]}]`;
const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`;
@ -695,16 +696,18 @@ export default class RunInfoUiHandler extends UiHandler {
typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
rules.push(typeText);
break;
}
case Challenges.INVERSE_BATTLE:
rules.push(i18next.t("challenges:inverseBattle.shortName"));
break;
default:
default: {
const localisationKey = Challenges[this.runInfo.challenges[i].id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
rules.push(i18next.t(`challenges:${localisationKey}.name`));
break;
}
}
}
}

View File

@ -126,6 +126,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
);
this.actionsBg.setOrigin(0, 0);
/*
* If there isn't enough space to fit all the icons and texts, there will be an overlap
* This currently doesn't happen, but it's something to keep in mind.
*/
const iconAction = globalScene.add.sprite(0, 0, "keyboard");
iconAction.setOrigin(0, -0.1);
iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4);
@ -137,7 +142,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
const iconCancel = globalScene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4);
iconCancel.setPositionRelative(this.actionsBg, actionText.x - 28, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
@ -146,7 +151,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
const iconReset = globalScene.add.sprite(0, 0, "keyboard");
iconReset.setOrigin(0, -0.1);
iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4);
iconReset.setPositionRelative(this.actionsBg, cancelText.x - 28, 4);
this.navigationIcons["BUTTON_HOME"] = iconReset;
const resetText = addTextObject(0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL);

View File

@ -94,7 +94,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler {
const iconCancel = globalScene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4);
iconCancel.setPositionRelative(actionsBg, actionText.x - 28, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
@ -332,12 +332,13 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler {
case Button.CYCLE_SHINY:
success = this.navigationContainer.navigate(button);
break;
case Button.ACTION:
case Button.ACTION: {
const setting: Setting = this.settings[cursor];
if (setting?.activatable) {
success = this.activateSetting(setting);
}
break;
}
}
}

View File

@ -98,7 +98,7 @@ export default class MoveTouchControlsHandler {
<div id="cancelButton" class="button">${i18next.t("settings:touchCancel")}</div>
</div>
<div class="info-row">
<div class="orientation-label">
<div class="orientation-label">
${i18next.t("settings:orientation")}
<span id="orientation">
${this.isLandscapeMode ? i18next.t("settings:landscape") : i18next.t("settings:portrait")}

View File

@ -1763,7 +1763,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
} else if (this.randomCursorObj.visible) {
switch (button) {
case Button.ACTION:
case Button.ACTION: {
if (this.starterSpecies.length >= 6) {
error = true;
break;
@ -1815,6 +1815,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
});
break;
}
case Button.UP:
this.randomCursorObj.setVisible(false);
this.filterBarCursor = this.filterBar.numFilters - 1;

View File

@ -10,10 +10,6 @@ import { UiMode } from "#enums/ui-mode";
export default class TestDialogueUiHandler extends FormModalUiHandler {
keys: string[];
constructor(mode) {
super(mode);
}
setup() {
super.setup();

View File

@ -611,3 +611,12 @@ export function getShinyDescriptor(variant: Variant): string {
return i18next.t("common:commonShiny");
}
}
/**
* If the input isn't already an array, turns it into one.
* @returns An array with the same type as the type of the input
*/
export function coerceArray<T>(input: T): T extends any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}

View File

@ -164,15 +164,13 @@ describe("Moves - Last Respects", () => {
await game.toNextWave();
expect(game.scene.currentBattle.enemyFaints).toBe(0);
game.removeEnemyHeldItems();
game.move.select(MoveId.LAST_RESPECTS);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("MoveEndPhase");
const enemy = game.field.getEnemyPokemon();
const player = game.field.getPlayerPokemon();
const items = `Player items: ${player.getHeldItems()} | Enemy Items: ${enemy.getHeldItems()} |`;
expect(move.calculateBattlePower, items).toHaveLastReturnedWith(50);
expect(move.calculateBattlePower).toHaveLastReturnedWith(50);
});
it("should reset playerFaints count if we enter new trainer battle", async () => {

View File

@ -12,6 +12,7 @@ import { UiMode } from "#enums/ui-mode";
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
import { vi } from "vitest";
import { coerceArray } from "#app/utils/common";
/**
* Helper to handle a Pokemon's move
@ -157,9 +158,7 @@ export class MoveHelper extends GameManagerHelper {
* @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to.
*/
public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void {
if (!Array.isArray(moveset)) {
moveset = [moveset];
}
moveset = coerceArray(moveset);
pokemon.moveset = [];
moveset.forEach(move => {
pokemon.moveset.push(new PokemonMove(move));

View File

@ -14,7 +14,7 @@ import { StatusEffect } from "#enums/status-effect";
import type { WeatherType } from "#enums/weather-type";
import { expect, vi } from "vitest";
import { GameManagerHelper } from "./gameManagerHelper";
import { shiftCharCodes } from "#app/utils/common";
import { coerceArray, shiftCharCodes } from "#app/utils/common";
import type { RandomTrainerOverride } from "#app/overrides";
import type { BattleType } from "#enums/battle-type";
@ -202,9 +202,7 @@ export class OverridesHelper extends GameManagerHelper {
*/
public moveset(moveset: MoveId | MoveId[]): this {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
if (!Array.isArray(moveset)) {
moveset = [moveset];
}
moveset = coerceArray(moveset);
const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", ");
this.log(`Player Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`);
return this;
@ -382,9 +380,7 @@ export class OverridesHelper extends GameManagerHelper {
*/
public enemyMoveset(moveset: MoveId | MoveId[]): this {
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
if (!Array.isArray(moveset)) {
moveset = [moveset];
}
moveset = coerceArray(moveset);
const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", ");
this.log(`Enemy Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`);
return this;

View File

@ -1,3 +1,4 @@
import { coerceArray } from "#app/utils/common";
import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager";
import type { MockGameObject } from "../mockGameObject";
@ -216,11 +217,7 @@ export default class MockContainer implements MockGameObject {
}
add(obj: MockGameObject | MockGameObject[]): this {
if (Array.isArray(obj)) {
this.list.push(...obj);
} else {
this.list.push(obj);
}
this.list.push(...coerceArray(obj));
return this;
}
@ -232,18 +229,12 @@ export default class MockContainer implements MockGameObject {
addAt(obj: MockGameObject | MockGameObject[], index = 0): this {
// Adds a Game Object to this Container at the given index.
if (!Array.isArray(obj)) {
obj = [obj];
}
this.list.splice(index, 0, ...obj);
this.list.splice(index, 0, ...coerceArray(obj));
return this;
}
remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this {
if (!Array.isArray(obj)) {
obj = [obj];
}
for (const item of obj) {
for (const item of coerceArray(obj)) {
const index = this.list.indexOf(item);
if (index !== -1) {
this.list.splice(index, 1);

View File

@ -1,3 +1,4 @@
import { coerceArray } from "#app/utils/common";
import type { MockGameObject } from "../mockGameObject";
export default class MockRectangle implements MockGameObject {
@ -50,11 +51,7 @@ export default class MockRectangle implements MockGameObject {
add(obj: MockGameObject | MockGameObject[]): this {
// Adds a child to this Game Object.
if (Array.isArray(obj)) {
this.list.push(...obj);
} else {
this.list.push(obj);
}
this.list.push(...coerceArray(obj));
return this;
}

View File

@ -1,6 +1,8 @@
import { coerceArray } from "#app/utils/common";
import Phaser from "phaser";
import type { MockGameObject } from "../mockGameObject";
import Frame = Phaser.Textures.Frame;
type Frame = Phaser.Textures.Frame;
export default class MockSprite implements MockGameObject {
private phaserSprite;
@ -204,11 +206,7 @@ export default class MockSprite implements MockGameObject {
add(obj: MockGameObject | MockGameObject[]): this {
// Adds a child to this Game Object.
if (Array.isArray(obj)) {
this.list.push(...obj);
} else {
this.list.push(obj);
}
this.list.push(...coerceArray(obj));
return this;
}