Merge branch 'beta' into enemy_ai

This commit is contained in:
Sirz Benjie 2025-06-13 21:52:26 -04:00 committed by GitHub
commit b25f4c09c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 110 additions and 141 deletions

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples --> <!-- Feel free to look at other PRs for examples -->
<!-- <!--
Make sure the title includes categorization (choose the one that best fits): Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix - [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality - [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality - [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items - [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters - [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests - [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements - [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx - [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites - [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance - [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges - [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code - [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments) - [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [GitHub]: For changes to GitHub workflows/templates/etc - [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Misc]: If no other category fits the 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 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? ## 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 provided a clear explanation of the changes?
- [ ] Have I tested the changes manually? - [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test:silent`) - [ ] 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 provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? - [ ] 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: 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? - [ ] 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: - [ ] 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

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

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; 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 type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags"; import { SubstituteTag } from "./battler-tags";
@ -520,7 +520,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
* @param encounterAnim one or more animations to fetch * @param encounterAnim one or more animations to fetch
*/ */
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> { 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 encounterAnimNames = getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = []; const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) { 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 { MovePhase } from "#app/phases/move-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n"; 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 { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -50,7 +50,7 @@ export class BattlerTag {
isBatonPassable = false, isBatonPassable = false,
) { ) {
this.tagType = tagType; this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType]; this.lapseTypes = coerceArray(lapseType);
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct? this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;

View File

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

View File

@ -11,7 +11,7 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; 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 type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super(); super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay]; this.requiredTimeOfDay = coerceArray(timeOfDay);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) { constructor(weather: WeatherType | WeatherType[]) {
super(); super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather]; this.requiredWeather = coerceArray(weather);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) { constructor(heldItem: string | string[], minNumberOfItems = 1) {
super(); super();
this.minNumberOfItems = minNumberOfItems; this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species]; this.requiredSpecies = coerceArray(species);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature]; this.requiredNature = coerceArray(nature);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted; this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type]; this.requiredType = coerceArray(type);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves]; this.requiredMoves = coerceArray(moves);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove]; this.requiredMoves = coerceArray(learnableMove);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities]; this.requiredAbilities = coerceArray(abilities);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect]; this.requiredStatusEffect = coerceArray(statusEffect);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem]; this.requiredFormChangeItem = coerceArray(formChangeItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems]; this.requiredEvolutionItem = coerceArray(evolutionItems);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
this.requireTransferable = requireTransferable; this.requireTransferable = requireTransferable;
} }
@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes]; this.requiredHeldItemTypes = coerceArray(heldItemTypes);
this.requireTransferable = requireTransferable; 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 { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move"; import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals 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( withAnimations(
...encounterAnimations: EncounterAnim[] ...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> { ): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; const animations = coerceArray(encounterAnimations);
return Object.assign(this, { encounterAnimations: animations }); return Object.assign(this, { encounterAnimations: animations });
} }
@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedGameModes( withDisallowedGameModes(
...disallowedGameModes: GameModes[] ...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> { ): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes]; const gameModes = coerceArray(disallowedGameModes);
return Object.assign(this, { disallowedGameModes: gameModes }); return Object.assign(this, { disallowedGameModes: gameModes });
} }
@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedChallenges( withDisallowedChallenges(
...disallowedChallenges: Challenges[] ...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> { ): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges]; const challenges = coerceArray(disallowedChallenges);
return Object.assign(this, { disallowedChallenges: challenges }); return Object.assign(this, { disallowedChallenges: challenges });
} }

View File

@ -1,7 +1,7 @@
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; 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 { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) { constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
super(); super();
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves]; this.requiredMoves = coerceArray(requiredMoves);
this.excludeLevelMoves = options.excludeLevelMoves ?? false; this.excludeLevelMoves = options.excludeLevelMoves ?? false;
this.excludeTmMoves = options.excludeTmMoves ?? 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 type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { UiMode } from "#enums/ui-mode"; 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 type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
@ -449,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* @param moves * @param moves
*/ */
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) { 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)); 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 * @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) { 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.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds)); globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));

View File

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

View File

@ -1,7 +1,14 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "../data-lists"; import { modifierTypes } from "../data-lists";
import { PokemonMove } from "../moves/pokemon-move"; 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 { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName]; this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
} }
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
if (!isNullOrUndefined(specialtyType)) { if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { 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. // 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. // 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. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { 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. // 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. // Set species filter and specialty type if provided, otherwise filter by base total.

View File

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

View File

@ -41,6 +41,7 @@ import {
type nil, type nil,
type Constructor, type Constructor,
randSeedIntRange, randSeedIntRange,
coerceArray,
} from "#app/utils/common"; } from "#app/utils/common";
import type { TypeDamageMultiplier } from "#app/data/type"; import type { TypeDamageMultiplier } from "#app/data/type";
import { getTypeDamageMultiplier, getTypeRgb } 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() let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
? Overrides.MOVESET_OVERRIDE ? Overrides.MOVESET_OVERRIDE
: Overrides.OPP_MOVESET_OVERRIDE; : Overrides.OPP_MOVESET_OVERRIDE;
if (!Array.isArray(overrideArray)) { overrideArray = coerceArray(overrideArray);
overrideArray = [overrideArray];
}
if (overrideArray.length > 0) { if (overrideArray.length > 0) {
if (!this.isPlayer()) { if (!this.isPlayer()) {
this.moveset = []; this.moveset = [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -611,3 +611,12 @@ export function getShinyDescriptor(variant: Variant): string {
return i18next.t("common:commonShiny"); 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

@ -12,6 +12,7 @@ import { UiMode } from "#enums/ui-mode";
import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { getMovePosition } from "#test/testUtils/gameManagerUtils";
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
import { vi } from "vitest"; import { vi } from "vitest";
import { coerceArray } from "#app/utils/common";
/** /**
* Helper to handle a Pokemon's move * 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. * @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to.
*/ */
public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void { public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void {
if (!Array.isArray(moveset)) { moveset = coerceArray(moveset);
moveset = [moveset];
}
pokemon.moveset = []; pokemon.moveset = [];
moveset.forEach(move => { moveset.forEach(move => {
pokemon.moveset.push(new PokemonMove(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 type { WeatherType } from "#enums/weather-type";
import { expect, vi } from "vitest"; import { expect, vi } from "vitest";
import { GameManagerHelper } from "./gameManagerHelper"; 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 { RandomTrainerOverride } from "#app/overrides";
import type { BattleType } from "#enums/battle-type"; import type { BattleType } from "#enums/battle-type";
@ -202,9 +202,7 @@ export class OverridesHelper extends GameManagerHelper {
*/ */
public moveset(moveset: MoveId | MoveId[]): this { public moveset(moveset: MoveId | MoveId[]): this {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(moveset); vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
if (!Array.isArray(moveset)) { moveset = coerceArray(moveset);
moveset = [moveset];
}
const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", "); const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", ");
this.log(`Player Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`); this.log(`Player Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`);
return this; return this;
@ -382,9 +380,7 @@ export class OverridesHelper extends GameManagerHelper {
*/ */
public enemyMoveset(moveset: MoveId | MoveId[]): this { public enemyMoveset(moveset: MoveId | MoveId[]): this {
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset); vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset);
if (!Array.isArray(moveset)) { moveset = coerceArray(moveset);
moveset = [moveset];
}
const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", "); const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", ");
this.log(`Enemy Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`); this.log(`Enemy Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`);
return this; return this;

View File

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

View File

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

View File

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