From e128326e753a3e615dfa15ef7c6c87aaa2e682df Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 21 Feb 2025 02:04:43 -0600 Subject: [PATCH] Break up imports --- src/battle-scene.ts | 1872 +++++++++++++++++++++++-------- src/phases/move-effect-phase.ts | 2 +- 2 files changed, 1401 insertions(+), 473 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 72c91e96e1c..f03ce64ecb9 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -8,10 +8,37 @@ import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import type { Constructor } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; -import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier } from "./modifier/modifier"; +import type { + Modifier, + ModifierPredicate, + TurnHeldItemTransferModifier, +} from "./modifier/modifier"; +import { + ConsumableModifier, + ConsumablePokemonModifier, + DoubleBattleChanceBoosterModifier, + ExpBalanceModifier, + ExpShareModifier, + FusePokemonModifier, + HealingBoosterModifier, + ModifierBar, + MultipleParticipantExpBonusModifier, + PersistentModifier, + PokemonExpBoosterModifier, + PokemonFormChangeItemModifier, + PokemonHeldItemModifier, + PokemonHpRestoreModifier, + PokemonIncrementingStatModifier, + RememberMoveModifier, +} from "./modifier/modifier"; import { PokeballType } from "#enums/pokeball"; -import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; +import { + initCommonAnims, + initMoveAnim, + loadCommonAnimAssets, + loadMoveAnimAssets, + populateAnims, +} from "#app/data/battle-anims"; import type { Phase } from "#app/phase"; import { initGameSpeed } from "#app/system/game-speed"; import { Arena, ArenaBase } from "#app/field/arena"; @@ -19,9 +46,29 @@ import { GameData } from "#app/system/game-data"; import { addTextObject, getTextColor, TextStyle } from "#app/ui/text"; import { allMoves } from "#app/data/move"; import { MusicPreference } from "#app/system/settings/settings"; -import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { + getDefaultModifierTypeForTier, + getEnemyModifierTypesForWave, + getLuckString, + getLuckTextTint, + getModifierPoolForType, + getModifierType, + getPartyLuckValue, + ModifierPoolType, + modifierTypes, + PokemonHeldItemModifierType, +} from "#app/modifier/modifier-type"; import AbilityBar from "#app/ui/ability-bar"; -import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr, PostItemLostAbAttr } from "#app/data/ability"; +import { + allAbilities, + applyAbAttrs, + applyPostBattleInitAbAttrs, + applyPostItemLostAbAttrs, + BlockItemTheftAbAttr, + DoubleBattleChanceAbAttr, + PostBattleInitAbAttr, + PostItemLostAbAttr, +} from "#app/data/ability"; import type { FixedBattleConfig } from "#app/battle"; import Battle, { BattleType } from "#app/battle"; import type { GameMode } from "#app/game-mode"; @@ -46,8 +93,16 @@ import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "#app/ui/ui-theme"; import type PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger } from "#app/data/pokemon-forms"; +import type { + SpeciesFormChange, + SpeciesFormChangeTrigger, +} from "#app/data/pokemon-forms"; +import { + FormChangeItem, + pokemonFormChanges, + SpeciesFormChangeManualTrigger, + SpeciesFormChangeTimeOfDayTrigger, +} from "#app/data/pokemon-forms"; import { FormChangePhase } from "#app/phases/form-change-phase"; import { getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; @@ -100,7 +155,14 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters"; +import { + allMysteryEncounters, + ANTI_VARIANCE_WEIGHT_MODIFIER, + AVERAGE_ENCOUNTERS_PER_RUN_TARGET, + BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, + MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, + mysteryEncountersByBiome, +} from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -118,11 +180,11 @@ export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; const DEBUG_RNG = false; -const OPP_IVS_OVERRIDE_VALIDATED : number[] = ( - Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? - Overrides.OPP_IVS_OVERRIDE : - new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) -).map(iv => isNaN(iv) || iv === null || iv > 31 ? -1 : iv); +const OPP_IVS_OVERRIDE_VALIDATED: number[] = ( + Array.isArray(Overrides.OPP_IVS_OVERRIDE) + ? Overrides.OPP_IVS_OVERRIDE + : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) +).map((iv) => (isNaN(iv) || iv === null || iv > 31 ? -1 : iv)); export const startingWave = Overrides.STARTING_WAVE_OVERRIDE || 1; @@ -130,18 +192,21 @@ const expSpriteKeys: string[] = []; export let starterColors: StarterColors; interface StarterColors { - [key: string]: [string, string] + [key: string]: [string, string]; } export interface PokeballCounts { - [pb: string]: number; + [pb: string]: number; } -export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound | Phaser.Sound.NoAudioSound; +export type AnySound = + | Phaser.Sound.WebAudioSound + | Phaser.Sound.HTML5AudioSound + | Phaser.Sound.NoAudioSound; export interface InfoToggle { - toggleInfo(force?: boolean): void; - isActive(): boolean; + toggleInfo(force?: boolean): void; + isActive(): boolean; } export default class BattleScene extends SceneBase { @@ -167,7 +232,8 @@ export default class BattleScene extends SceneBase { public showTimeOfDayWidget: boolean = true; public timeOfDayAnimation: EaseType = EaseType.NONE; public showLevelUpStats: boolean = true; - public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; + public enableTutorials: boolean = + import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableMoveInfo: boolean = true; public enableRetries: boolean = false; public hideIvs: boolean = false; @@ -201,17 +267,17 @@ export default class BattleScene extends SceneBase { public eggSkipPreference: number = 0; /** - * Defines the experience gain display mode. - * - * @remarks - * The `expParty` can have several modes: - * - `0` - Default: The normal experience gain display, nothing changed. - * - `1` - Level Up Notification: Displays the level up in the small frame instead of a message. - * - `2` - Skip: No level up frame nor message. - * - * Modes `1` and `2` are still compatible with stats display, level up, new move, etc. - * @default 0 - Uses the default normal experience gain display. - */ + * Defines the experience gain display mode. + * + * @remarks + * The `expParty` can have several modes: + * - `0` - Default: The normal experience gain display, nothing changed. + * - `1` - Level Up Notification: Displays the level up in the small frame instead of a message. + * - `2` - Skip: No level up frame nor message. + * + * Modes `1` and `2` are still compatible with stats display, level up, new move, etc. + * @default 0 - Uses the default normal experience gain display. + */ public expParty: ExpNotification = 0; public hpBarSpeed: number = 0; public fusionPaletteSwaps: boolean = true; @@ -227,9 +293,9 @@ export default class BattleScene extends SceneBase { public battleStyle: number = BattleStyle.SWITCH; /** - * Defines whether or not to show type effectiveness hints - * - true: No hints - * - false: Show hints for moves + * Defines whether or not to show type effectiveness hints + * - true: No hints + * - false: Show hints for moves */ public typeHints: boolean = false; @@ -276,7 +342,8 @@ export default class BattleScene extends SceneBase { public pokemonInfoContainer: PokemonInfoContainer; private party: PlayerPokemon[]; /** Session save data that pertains to Mystery Encounters */ - public mysteryEncounterSaveData: MysteryEncounterSaveData = new MysteryEncounterSaveData(); + public mysteryEncounterSaveData: MysteryEncounterSaveData = + new MysteryEncounterSaveData(); /** If the previous wave was a MysteryEncounter, tracks the object with this variable. Mostly used for visual object cleanup */ public lastMysteryEncounter?: MysteryEncounter; /** Combined Biome and Wave count text */ @@ -292,7 +359,7 @@ export default class BattleScene extends SceneBase { private fieldOverlay: Phaser.GameObjects.Rectangle; private shopOverlay: Phaser.GameObjects.Rectangle; private shopOverlayShown: boolean = false; - private shopOverlayOpacity: number = .8; + private shopOverlayOpacity: number = 0.8; public modifiers: PersistentModifier[]; private enemyModifiers: PersistentModifier[]; @@ -358,19 +425,30 @@ export default class BattleScene extends SceneBase { if (variant) { atlasPath = atlasPath.replace("variant/", ""); } - this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`); + this.load.atlas( + key, + `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, + `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`, + ); } /** * Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache} */ - public async loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant): Promise { - const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey); + public async loadPokemonVariantAssets( + spriteKey: string, + fileRoot: string, + variant?: Variant, + ): Promise { + const useExpSprite = + this.experimentalSprites && this.hasExpSprite(spriteKey); if (useExpSprite) { fileRoot = `exp/${fileRoot}`; } let variantConfig = variantData; - fileRoot.split("/").map((p) => (variantConfig ? (variantConfig = variantConfig[p]) : null)); + fileRoot + .split("/") + .map((p) => (variantConfig ? (variantConfig = variantConfig[p]) : null)); const variantSet = variantConfig as VariantSet; return new Promise((resolve) => { @@ -393,10 +471,20 @@ export default class BattleScene extends SceneBase { async preload() { if (DEBUG_RNG) { const originalRealInRange = Phaser.Math.RND.realInRange; - Phaser.Math.RND.realInRange = function (min: number, max: number): number { + Phaser.Math.RND.realInRange = function ( + min: number, + max: number, + ): number { const ret = originalRealInRange.apply(this, [ min, max ]); - const args = [ "RNG", ++this.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; - args.push(`seed: ${this.rngSeedOverride || this.waveSeed || this.seed}`); + const args = [ + "RNG", + ++this.rngCounter, + ret / (max - min), + `min: ${min} / max: ${max}`, + ]; + args.push( + `seed: ${this.rngSeedOverride || this.waveSeed || this.seed}`, + ); if (this.rngOffset) { args.push(`offset: ${this.rngOffset}`); } @@ -423,10 +511,16 @@ export default class BattleScene extends SceneBase { this.load.setBaseURL(); this.spritePipeline = new SpritePipeline(this.game); - (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("Sprite", this.spritePipeline); + (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add( + "Sprite", + this.spritePipeline, + ); this.fieldSpritePipeline = new FieldSpritePipeline(this.game); - (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add("FieldSprite", this.fieldSpritePipeline); + (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.add( + "FieldSprite", + this.fieldSpritePipeline, + ); this.launchBattle(); } @@ -441,7 +535,7 @@ export default class BattleScene extends SceneBase { this.arenaBgTransition = this.add.sprite(0, 0, "plains_bg"); this.arenaBgTransition.setName("sprite-arena-bg-transition"); - [ this.arenaBgTransition, this.arenaBg ].forEach(a => { + [ this.arenaBgTransition, this.arenaBg ].forEach((a) => { a.setPipeline(this.fieldSpritePipeline); a.setScale(6); a.setOrigin(0); @@ -461,13 +555,16 @@ export default class BattleScene extends SceneBase { this.fieldUI = fieldUI; - const transition = this.make.rexTransitionImagePack({ - x: 0, - y: 0, - scale: 6, - key: "loading_bg", - origin: { x: 0, y: 0 } - }, true); + const transition = this.make.rexTransitionImagePack( + { + x: 0, + y: 0, + scale: 6, + key: "loading_bg", + origin: { x: 0, y: 0 }, + }, + true, + ); //@ts-ignore (the defined types in the package are incromplete...) transition.transit({ @@ -489,14 +586,26 @@ export default class BattleScene extends SceneBase { this.uiContainer = uiContainer; const overlayWidth = this.game.canvas.width / 6; - const overlayHeight = (this.game.canvas.height / 6) - 48; - this.fieldOverlay = this.add.rectangle(0, overlayHeight * -1 - 48, overlayWidth, overlayHeight, 0x424242); + const overlayHeight = this.game.canvas.height / 6 - 48; + this.fieldOverlay = this.add.rectangle( + 0, + overlayHeight * -1 - 48, + overlayWidth, + overlayHeight, + 0x424242, + ); this.fieldOverlay.setName("rect-field-overlay"); this.fieldOverlay.setOrigin(0, 0); this.fieldOverlay.setAlpha(0); this.fieldUI.add(this.fieldOverlay); - this.shopOverlay = this.add.rectangle(0, overlayHeight * -1 - 48, overlayWidth, overlayHeight, 0x070707); + this.shopOverlay = this.add.rectangle( + 0, + overlayHeight * -1 - 48, + overlayWidth, + overlayHeight, + 0x070707, + ); this.shopOverlay.setName("rect-shop-overlay"); this.shopOverlay.setOrigin(0, 0); this.shopOverlay.setAlpha(0); @@ -547,28 +656,56 @@ export default class BattleScene extends SceneBase { this.candyBar.setup(); this.fieldUI.add(this.candyBar); - this.biomeWaveText = addTextObject((this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); + this.biomeWaveText = addTextObject( + this.game.canvas.width / 6 - 2, + 0, + startingWave.toString(), + TextStyle.BATTLE_INFO, + ); this.biomeWaveText.setName("text-biome-wave"); this.biomeWaveText.setOrigin(1, 0.5); this.fieldUI.add(this.biomeWaveText); - this.moneyText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY); + this.moneyText = addTextObject( + this.game.canvas.width / 6 - 2, + 0, + "", + TextStyle.MONEY, + ); this.moneyText.setName("text-money"); this.moneyText.setOrigin(1, 0.5); this.fieldUI.add(this.moneyText); - this.scoreText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.scoreText = addTextObject( + this.game.canvas.width / 6 - 2, + 0, + "", + TextStyle.PARTY, + { fontSize: "54px" }, + ); this.scoreText.setName("text-score"); this.scoreText.setOrigin(1, 0.5); this.fieldUI.add(this.scoreText); - this.luckText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.luckText = addTextObject( + this.game.canvas.width / 6 - 2, + 0, + "", + TextStyle.PARTY, + { fontSize: "54px" }, + ); this.luckText.setName("text-luck"); this.luckText.setOrigin(1, 0.5); this.luckText.setVisible(false); this.fieldUI.add(this.luckText); - this.luckLabelText = addTextObject((this.game.canvas.width / 6) - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, { fontSize: "54px" }); + this.luckLabelText = addTextObject( + this.game.canvas.width / 6 - 2, + 0, + i18next.t("common:luckIndicator"), + TextStyle.PARTY, + { fontSize: "54px" }, + ); this.luckLabelText.setName("text-luck-label"); this.luckLabelText.setOrigin(1, 0.5); this.luckLabelText.setVisible(false); @@ -576,7 +713,10 @@ export default class BattleScene extends SceneBase { this.arenaFlyout = new ArenaFlyout(); this.fieldUI.add(this.arenaFlyout); - this.fieldUI.moveBelow(this.arenaFlyout, this.fieldOverlay); + this.fieldUI.moveBelow( + this.arenaFlyout, + this.fieldOverlay, + ); this.updateUIPositions(); @@ -585,7 +725,10 @@ export default class BattleScene extends SceneBase { this.spriteSparkleHandler = new PokemonSpriteSparkleHandler(); this.spriteSparkleHandler.setup(); - this.pokemonInfoContainer = new PokemonInfoContainer((this.game.canvas.width / 6) + 52, -(this.game.canvas.height / 6) + 66); + this.pokemonInfoContainer = new PokemonInfoContainer( + this.game.canvas.width / 6 + 52, + -(this.game.canvas.height / 6) + 66, + ); this.pokemonInfoContainer.setup(); this.fieldUI.add(this.pokemonInfoContainer); @@ -607,14 +750,23 @@ export default class BattleScene extends SceneBase { this.arenaPlayerTransition.setVisible(false); this.arenaNextEnemy.setVisible(false); - [ this.arenaPlayer, this.arenaPlayerTransition, this.arenaEnemy, this.arenaNextEnemy ].forEach(a => { + [ + this.arenaPlayer, + this.arenaPlayerTransition, + this.arenaEnemy, + this.arenaNextEnemy, + ].forEach((a) => { if (a instanceof Phaser.GameObjects.Sprite) { a.setOrigin(0, 0); } field.add(a); }); - const trainer = this.addFieldSprite(0, 0, `trainer_${this.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + const trainer = this.addFieldSprite( + 0, + 0, + `trainer_${this.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`, + ); trainer.setOrigin(0.5, 1); trainer.setName("sprite-trainer"); @@ -627,16 +779,19 @@ export default class BattleScene extends SceneBase { frames: this.anims.generateFrameNumbers("prompt", { start: 1, end: 4 }), frameRate: 6, repeat: -1, - showOnStart: true + showOnStart: true, }); this.anims.create({ key: "tera_sparkle", - frames: this.anims.generateFrameNumbers("tera_sparkle", { start: 0, end: 12 }), + frames: this.anims.generateFrameNumbers("tera_sparkle", { + start: 0, + end: 12, + }), frameRate: 18, repeat: 0, showOnStart: true, - hideOnComplete: true + hideOnComplete: true, }); this.reset(false, false, true); @@ -648,13 +803,22 @@ export default class BattleScene extends SceneBase { ui.setup(); - const defaultMoves = [ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ]; + const defaultMoves = [ + Moves.TACKLE, + Moves.TAIL_WHIP, + Moves.FOCUS_ENERGY, + Moves.STRUGGLE, + ]; Promise.all([ Promise.all(loadPokemonAssets), initCommonAnims().then(() => loadCommonAnimAssets(true)), - Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(defaultMoves, true)), - this.initStarterColors() + Promise.all( + [ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map( + (m) => initMoveAnim(m), + ), + ).then(() => loadMoveAnimAssets(defaultMoves, true)), + this.initStarterColors(), ]).then(() => { this.pushPhase(new LoginPhase()); this.pushPhase(new TitlePhase()); @@ -688,7 +852,7 @@ export default class BattleScene extends SceneBase { if (this.lastSavePlayTime !== null) { this.lastSavePlayTime++; } - } + }, }); this.updateBiomeWaveText(); @@ -700,19 +864,22 @@ export default class BattleScene extends SceneBase { if (expSpriteKeys.length) { return; } - this.cachedFetch("./exp-sprites.json").then(res => res.json()).then(keys => { - if (Array.isArray(keys)) { - expSpriteKeys.push(...keys); - } - Promise.resolve(); - }); + this.cachedFetch("./exp-sprites.json") + .then((res) => res.json()) + .then((keys) => { + if (Array.isArray(keys)) { + expSpriteKeys.push(...keys); + } + Promise.resolve(); + }); } async initVariantData(): Promise { - Object.keys(variantData).forEach(key => delete variantData[key]); - await this.cachedFetch("./images/pokemon/variant/_masterlist.json").then(res => res.json()) - .then(v => { - Object.keys(v).forEach(k => variantData[k] = v[k]); + Object.keys(variantData).forEach((key) => delete variantData[key]); + await this.cachedFetch("./images/pokemon/variant/_masterlist.json") + .then((res) => res.json()) + .then((v) => { + Object.keys(v).forEach((k) => (variantData[k] = v[k])); if (this.experimentalSprites) { const expVariantData = variantData["exp"]; const traverseVariantData = (keys: string[]) => { @@ -722,7 +889,10 @@ export default class BattleScene extends SceneBase { if (i < keys.length - 1) { variantTree = variantTree[k]; expTree = expTree[k]; - } else if (variantTree.hasOwnProperty(k) && expTree.hasOwnProperty(k)) { + } else if ( + variantTree.hasOwnProperty(k) && + expTree.hasOwnProperty(k) + ) { if ([ "back", "female" ].includes(k)) { traverseVariantData(keys.concat(k)); } else { @@ -731,7 +901,9 @@ export default class BattleScene extends SceneBase { } }); }; - Object.keys(expVariantData).forEach(ek => traverseVariantData([ ek ])); + Object.keys(expVariantData).forEach((ek) => + traverseVariantData([ ek ]), + ); } Promise.resolve(); }); @@ -749,18 +921,20 @@ export default class BattleScene extends SceneBase { } initStarterColors(): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { if (starterColors) { return resolve(); } - this.cachedFetch("./starter-colors.json").then(res => res.json()).then(sc => { - starterColors = {}; - Object.keys(sc).forEach(key => { - starterColors[key] = sc[key]; - }); + this.cachedFetch("./starter-colors.json") + .then((res) => res.json()) + .then((sc) => { + starterColors = {}; + Object.keys(sc).forEach((key) => { + starterColors[key] = sc[key]; + }); - /*const loadPokemonAssets: Promise[] = []; + /*const loadPokemonAssets: Promise[] = []; for (let s of Object.keys(speciesStarters)) { const species = getPokemonSpecies(parseInt(s)); @@ -782,13 +956,16 @@ export default class BattleScene extends SceneBase { resolve(); });*/ - resolve(); - }); + resolve(); + }); }); } hasExpSprite(key: string): boolean { - const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key); + const keyMatch = + /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec( + key, + ); if (!keyMatch) { return false; } @@ -821,7 +998,7 @@ export default class BattleScene extends SceneBase { * that are {@linkcode Pokemon.isAllowedInBattle | allowed in battle}. */ public getPokemonAllowedInBattle(): PlayerPokemon[] { - return this.getPlayerParty().filter(p => p.isAllowedInBattle()); + return this.getPlayerParty().filter((p) => p.isAllowedInBattle()); } /** @@ -831,8 +1008,12 @@ export default class BattleScene extends SceneBase { * or `undefined` if there are no valid pokemon * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` */ - public getPlayerPokemon(includeSwitching: boolean = true): PlayerPokemon | undefined { - return this.getPlayerField().find(p => p.isActive() && (includeSwitching || p.switchOutStatus === false)); + public getPlayerPokemon( + includeSwitching: boolean = true, + ): PlayerPokemon | undefined { + return this.getPlayerField().find( + (p) => p.isActive() && (includeSwitching || p.switchOutStatus === false), + ); } /** @@ -842,7 +1023,10 @@ export default class BattleScene extends SceneBase { */ public getPlayerField(): PlayerPokemon[] { const party = this.getPlayerParty(); - return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1)); + return party.slice( + 0, + Math.min(party.length, this.currentBattle?.double ? 2 : 1), + ); } public getEnemyParty(): EnemyPokemon[] { @@ -856,8 +1040,12 @@ export default class BattleScene extends SceneBase { * or `undefined` if there are no valid pokemon * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` */ - public getEnemyPokemon(includeSwitching: boolean = true): EnemyPokemon | undefined { - return this.getEnemyField().find(p => p.isActive() && (includeSwitching || p.switchOutStatus === false)); + public getEnemyPokemon( + includeSwitching: boolean = true, + ): EnemyPokemon | undefined { + return this.getEnemyField().find( + (p) => p.isActive() && (includeSwitching || p.switchOutStatus === false), + ); } /** @@ -867,7 +1055,10 @@ export default class BattleScene extends SceneBase { */ public getEnemyField(): EnemyPokemon[] { const party = this.getEnemyParty(); - return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1)); + return party.slice( + 0, + Math.min(party.length, this.currentBattle?.double ? 2 : 1), + ); } /** @@ -882,9 +1073,7 @@ export default class BattleScene extends SceneBase { const enemyField = this.getEnemyField(); ret.splice(0, playerField.length, ...playerField); ret.splice(2, enemyField.length, ...enemyField); - return activeOnly - ? ret.filter(p => p?.isActive()) - : ret; + return activeOnly ? ret.filter((p) => p?.isActive()) : ret; } /** @@ -900,8 +1089,17 @@ export default class BattleScene extends SceneBase { if (allyPokemon?.isActive(true)) { let targetingMovePhase: MovePhase; do { - targetingMovePhase = this.findPhase(mp => mp instanceof MovePhase && mp.targets.length === 1 && mp.targets[0] === removedPokemon.getBattlerIndex() && mp.pokemon.isPlayer() !== allyPokemon.isPlayer()) as MovePhase; - if (targetingMovePhase && targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex()) { + targetingMovePhase = this.findPhase( + (mp) => + mp instanceof MovePhase && + mp.targets.length === 1 && + mp.targets[0] === removedPokemon.getBattlerIndex() && + mp.pokemon.isPlayer() !== allyPokemon.isPlayer(), + ) as MovePhase; + if ( + targetingMovePhase && + targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex() + ) { targetingMovePhase.targets[0] = allyPokemon.getBattlerIndex(); } } while (targetingMovePhase); @@ -924,16 +1122,46 @@ export default class BattleScene extends SceneBase { // return the stored info toggles; used by ui-inputs getInfoToggles(activeOnly: boolean = false): InfoToggle[] { - return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles; + return activeOnly + ? this.infoToggles.filter((t) => t?.isActive()) + : this.infoToggles; } getPokemonById(pokemonId: number): Pokemon | null { - const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); - return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null; + const findInParty = (party: Pokemon[]) => + party.find((p) => p.id === pokemonId); + return ( + (findInParty(this.getPlayerParty()) || + findInParty(this.getEnemyParty())) ?? + null + ); } - addPlayerPokemon(species: PokemonSpecies, level: number, abilityIndex?: number, formIndex?: number, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: number[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { - const pokemon = new PlayerPokemon(species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); + addPlayerPokemon( + species: PokemonSpecies, + level: number, + abilityIndex?: number, + formIndex?: number, + gender?: Gender, + shiny?: boolean, + variant?: Variant, + ivs?: number[], + nature?: Nature, + dataSource?: Pokemon | PokemonData, + postProcess?: (playerPokemon: PlayerPokemon) => void, + ): PlayerPokemon { + const pokemon = new PlayerPokemon( + species, + level, + abilityIndex, + formIndex, + gender, + shiny, + variant, + ivs, + nature, + dataSource, + ); if (postProcess) { postProcess(pokemon); } @@ -941,17 +1169,37 @@ export default class BattleScene extends SceneBase { return pokemon; } - addEnemyPokemon(species: PokemonSpecies, level: number, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { + addEnemyPokemon( + species: PokemonSpecies, + level: number, + trainerSlot: TrainerSlot, + boss: boolean = false, + shinyLock: boolean = false, + dataSource?: PokemonData, + postProcess?: (enemyPokemon: EnemyPokemon) => void, + ): EnemyPokemon { if (Overrides.OPP_LEVEL_OVERRIDE > 0) { level = Overrides.OPP_LEVEL_OVERRIDE; } if (Overrides.OPP_SPECIES_OVERRIDE) { species = getPokemonSpecies(Overrides.OPP_SPECIES_OVERRIDE); // The fact that a Pokemon is a boss or not can change based on its Species and level - boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1; + boss = + this.getEncounterBossSegments( + this.currentBattle.waveIndex, + level, + species, + ) > 1; } - const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource); + const pokemon = new EnemyPokemon( + species, + level, + trainerSlot, + boss, + shinyLock, + dataSource, + ); if (Overrides.OPP_FUSION_OVERRIDE) { pokemon.generateFusionSpecies(); } @@ -960,7 +1208,13 @@ export default class BattleScene extends SceneBase { const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296)); for (let s = 0; s < pokemon.ivs.length; s++) { - pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75)); + pokemon.ivs[s] = Math.round( + Phaser.Math.Linear( + Math.min(pokemon.ivs[s], secondaryIvs[s]), + Math.max(pokemon.ivs[s], secondaryIvs[s]), + 0.75, + ), + ); } } if (postProcess) { @@ -983,7 +1237,10 @@ export default class BattleScene extends SceneBase { * @param pokemon * @param destroy Default true. If true, will destroy the {@linkcode PlayerPokemon} after removing */ - removePokemonFromPlayerParty(pokemon: PlayerPokemon, destroy: boolean = true) { + removePokemonFromPlayerParty( + pokemon: PlayerPokemon, + destroy: boolean = true, + ) { if (!pokemon) { return; } @@ -997,7 +1254,14 @@ export default class BattleScene extends SceneBase { this.updateModifiers(true); } - addPokemonIcon(pokemon: Pokemon, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container { + addPokemonIcon( + pokemon: Pokemon, + x: number, + y: number, + originX: number = 0.5, + originY: number = 0.5, + ignoreOverride: boolean = false, + ): Phaser.GameObjects.Container { const container = this.add.container(x, y); container.setName(`${pokemon.name}-icon`); @@ -1006,7 +1270,9 @@ export default class BattleScene extends SceneBase { icon.setFrame(pokemon.getIconId(true)); // Temporary fix to show pokemon's default icon if variant icon doesn't exist if (icon.frame.name !== pokemon.getIconId(true)) { - console.log(`${pokemon.name}'s variant icon does not exist. Replacing with default.`); + console.log( + `${pokemon.name}'s variant icon does not exist. Replacing with default.`, + ); const temp = pokemon.shiny; pokemon.shiny = false; icon.setTexture(pokemon.getIconAtlasKey(ignoreOverride)); @@ -1018,7 +1284,11 @@ export default class BattleScene extends SceneBase { container.add(icon); if (pokemon.isFusion()) { - const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride)); + const fusionIcon = this.add.sprite( + 0, + 0, + pokemon.getFusionIconAtlasKey(ignoreOverride), + ); fusionIcon.setName("sprite-fusion-icon"); fusionIcon.setOrigin(0.5, 0); fusionIcon.setFrame(pokemon.getFusionIconId(true)); @@ -1027,13 +1297,24 @@ export default class BattleScene extends SceneBase { const originalHeight = icon.height; const originalFrame = icon.frame; - const iconHeight = (icon.frame.cutHeight <= fusionIcon.frame.cutHeight ? Math.ceil : Math.floor)((icon.frame.cutHeight + fusionIcon.frame.cutHeight) / 4); + const iconHeight = ( + icon.frame.cutHeight <= fusionIcon.frame.cutHeight + ? Math.ceil + : Math.floor + )((icon.frame.cutHeight + fusionIcon.frame.cutHeight) / 4); // Inefficient, but for some reason didn't work with only the unique properties as part of the name const iconFrameId = `${icon.frame.name}f${fusionIcon.frame.name}`; if (!icon.frame.texture.has(iconFrameId)) { - icon.frame.texture.add(iconFrameId, icon.frame.sourceIndex, icon.frame.cutX, icon.frame.cutY, icon.frame.cutWidth, iconHeight); + icon.frame.texture.add( + iconFrameId, + icon.frame.sourceIndex, + icon.frame.cutX, + icon.frame.cutY, + icon.frame.cutWidth, + iconHeight, + ); } icon.setFrame(iconFrameId); @@ -1043,13 +1324,21 @@ export default class BattleScene extends SceneBase { const originalFusionFrame = fusionIcon.frame; const fusionIconY = fusionIcon.frame.cutY + icon.frame.cutHeight; - const fusionIconHeight = fusionIcon.frame.cutHeight - icon.frame.cutHeight; + const fusionIconHeight = + fusionIcon.frame.cutHeight - icon.frame.cutHeight; // Inefficient, but for some reason didn't work with only the unique properties as part of the name const fusionIconFrameId = `${fusionIcon.frame.name}f${icon.frame.name}`; if (!fusionIcon.frame.texture.has(fusionIconFrameId)) { - fusionIcon.frame.texture.add(fusionIconFrameId, fusionIcon.frame.sourceIndex, fusionIcon.frame.cutX, fusionIconY, fusionIcon.frame.cutWidth, fusionIconHeight); + fusionIcon.frame.texture.add( + fusionIconFrameId, + fusionIcon.frame.sourceIndex, + fusionIcon.frame.cutX, + fusionIconY, + fusionIcon.frame.cutWidth, + fusionIconHeight, + ); } fusionIcon.setFrame(fusionIconFrameId); @@ -1062,7 +1351,7 @@ export default class BattleScene extends SceneBase { container.x -= originalWidth * (originX - 0.5); } if (originY !== 0) { - container.y -= (originalHeight) * originY; + container.y -= originalHeight * originY; } } else { if (originX !== 0.5) { @@ -1097,7 +1386,11 @@ export default class BattleScene extends SceneBase { return this.currentBattle?.randSeedInt(range, min); } - reset(clearScene: boolean = false, clearData: boolean = false, reloadI18n: boolean = false): void { + reset( + clearScene: boolean = false, + clearData: boolean = false, + reloadI18n: boolean = false, + ): void { if (clearData) { this.gameData = new GameData(); } @@ -1111,7 +1404,11 @@ export default class BattleScene extends SceneBase { this.lockModifierTiers = false; - this.pokeballCounts = Object.fromEntries(Utils.getEnumValues(PokeballType).filter(p => p <= PokeballType.MASTER_BALL).map(t => [ t, 0 ])); + this.pokeballCounts = Object.fromEntries( + Utils.getEnumValues(PokeballType) + .filter((p) => p <= PokeballType.MASTER_BALL) + .map((t) => [ t, 0 ]), + ); this.pokeballCounts[PokeballType.POKEBALL] += 5; if (Overrides.POKEBALL_OVERRIDE.active) { this.pokeballCounts = Overrides.POKEBALL_OVERRIDE.pokeballs; @@ -1132,7 +1429,10 @@ export default class BattleScene extends SceneBase { // If this is a ME, clear any residual visual sprites before reloading if (this.currentBattle?.mysteryEncounter?.introVisuals) { - this.field.remove(this.currentBattle.mysteryEncounter?.introVisuals, true); + this.field.remove( + this.currentBattle.mysteryEncounter?.introVisuals, + true, + ); } //@ts-ignore - allowing `null` for currentBattle causes a lot of trouble @@ -1153,7 +1453,7 @@ export default class BattleScene extends SceneBase { this.updateScoreText(); this.scoreText.setVisible(false); - [ this.luckLabelText, this.luckText ].map(t => t.setVisible(false)); + [ this.luckLabelText, this.luckText ].map((t) => t.setVisible(false)); this.newArena(Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN); @@ -1162,12 +1462,16 @@ export default class BattleScene extends SceneBase { this.arenaBgTransition.setPosition(0, 0); this.arenaPlayer.setPosition(300, 0); this.arenaPlayerTransition.setPosition(0, 0); - [ this.arenaEnemy, this.arenaNextEnemy ].forEach(a => a.setPosition(-280, 0)); + [ this.arenaEnemy, this.arenaNextEnemy ].forEach((a) => + a.setPosition(-280, 0), + ); this.arenaNextEnemy.setVisible(false); this.arena.init(); - this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + this.trainer.setTexture( + `trainer_${this.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`, + ); this.trainer.setPosition(406, 186); this.trainer.setVisible(true); @@ -1180,7 +1484,16 @@ export default class BattleScene extends SceneBase { ...allSpecies, ...allMoves, ...allAbilities, - ...Utils.getEnumValues(ModifierPoolType).map(mpt => getModifierPoolForType(mpt)).map(mp => Object.values(mp).flat().map(mt => mt.modifierType).filter(mt => "localize" in mt).map(lpb => lpb as unknown as Localizable)).flat() + ...Utils.getEnumValues(ModifierPoolType) + .map((mpt) => getModifierPoolForType(mpt)) + .map((mp) => + Object.values(mp) + .flat() + .map((mt) => mt.modifierType) + .filter((mt) => "localize" in mt) + .map((lpb) => lpb as unknown as Localizable), + ) + .flat(), ]; for (const item of localizable) { item.localize(); @@ -1206,21 +1519,32 @@ export default class BattleScene extends SceneBase { this.children.removeAll(true); this.game.domContainer.innerHTML = ""; this.launchBattle(); - } + }, }); } } getDoubleBattleChance(newWaveIndex: number, playerField: PlayerPokemon[]) { - const doubleChance = new Utils.NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); + const doubleChance = new Utils.NumberHolder( + newWaveIndex % 10 === 0 ? 32 : 8, + ); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); + playerField.forEach((p) => + applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance), + ); return Math.max(doubleChance.value, 1); } - newBattle(waveIndex?: number, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounterType?: MysteryEncounterType): Battle | null { + newBattle( + waveIndex?: number, + battleType?: BattleType, + trainerData?: TrainerData, + double?: boolean, + mysteryEncounterType?: MysteryEncounterType, + ): Battle | null { const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; - const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); + const newWaveIndex = + waveIndex || (this.currentBattle?.waveIndex || _startingWave - 1) + 1; let newDouble: boolean | undefined; let newBattleType: BattleType; let newTrainer: Trainer | undefined; @@ -1231,11 +1555,17 @@ export default class BattleScene extends SceneBase { const playerField = this.getPlayerField(); - if (this.gameMode.isFixedBattle(newWaveIndex) && trainerData === undefined) { + if ( + this.gameMode.isFixedBattle(newWaveIndex) && + trainerData === undefined + ) { battleConfig = this.gameMode.getFixedBattle(newWaveIndex); newDouble = battleConfig.double; newBattleType = battleConfig.battleType; - this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8); + this.executeWithSeedOffset( + () => (newTrainer = battleConfig?.getTrainer()), + (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8, + ); if (newTrainer) { this.field.add(newTrainer); } @@ -1243,7 +1573,9 @@ export default class BattleScene extends SceneBase { if (!this.gameMode.hasTrainers) { newBattleType = BattleType.WILD; } else if (battleType === undefined) { - newBattleType = this.gameMode.isWaveTrainer(newWaveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD; + newBattleType = this.gameMode.isWaveTrainer(newWaveIndex, this.arena) + ? BattleType.TRAINER + : BattleType.WILD; } else { newBattleType = battleType; } @@ -1254,29 +1586,50 @@ export default class BattleScene extends SceneBase { if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; } else if (trainerConfigs[trainerType].hasDouble) { - doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); + doubleTrainer = !Utils.randSeedInt( + this.getDoubleBattleChance(newWaveIndex, playerField), + ); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance - if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { + if ( + trainerConfigs[trainerType].trainerTypeDouble && + ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType) + ) { doubleTrainer = false; } } - const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); - newTrainer = trainerData !== undefined ? trainerData.toTrainer() : new Trainer(trainerType, variant); + const variant = doubleTrainer + ? TrainerVariant.DOUBLE + : Utils.randSeedInt(2) + ? TrainerVariant.FEMALE + : TrainerVariant.DEFAULT; + newTrainer = + trainerData !== undefined + ? trainerData.toTrainer() + : new Trainer(trainerType, variant); this.field.add(newTrainer); } // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { + if ( + this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || + newBattleType === BattleType.MYSTERY_ENCOUNTER + ) { newBattleType = BattleType.MYSTERY_ENCOUNTER; // Reset to base spawn weight - this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; + this.mysteryEncounterSaveData.encounterSpawnChance = + BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; } } if (double === undefined && newWaveIndex > 1) { - if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) { - newDouble = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); + if ( + newBattleType === BattleType.WILD && + !this.gameMode.isWaveFinal(newWaveIndex) + ) { + newDouble = !Utils.randSeedInt( + this.getDoubleBattleChance(newWaveIndex, playerField), + ); } else if (newBattleType === BattleType.TRAINER) { newDouble = newTrainer?.variant === TrainerVariant.DOUBLE; } @@ -1300,10 +1653,10 @@ export default class BattleScene extends SceneBase { doubleOverrideForWave = "single"; break; case "even-doubles": - doubleOverrideForWave = (newWaveIndex % 2) ? "single" : "double"; + doubleOverrideForWave = newWaveIndex % 2 ? "single" : "double"; break; case "odd-doubles": - doubleOverrideForWave = (newWaveIndex % 2) ? "double" : "single"; + doubleOverrideForWave = newWaveIndex % 2 ? "double" : "single"; break; } @@ -1314,7 +1667,10 @@ export default class BattleScene extends SceneBase { * Override battles into single only if not fighting with trainers. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 | GitHub Issue #1948} */ - if (newBattleType !== BattleType.TRAINER && doubleOverrideForWave === "single") { + if ( + newBattleType !== BattleType.TRAINER && + doubleOverrideForWave === "single" + ) { newDouble = false; } } @@ -1332,13 +1688,25 @@ export default class BattleScene extends SceneBase { } if (lastBattle?.double && !newDouble) { - this.tryRemovePhase(p => p instanceof SwitchPhase); - this.getPlayerField().forEach(p => p.lapseTag(BattlerTagType.COMMANDED)); + this.tryRemovePhase((p) => p instanceof SwitchPhase); + this.getPlayerField().forEach((p) => + p.lapseTag(BattlerTagType.COMMANDED), + ); } - this.executeWithSeedOffset(() => { - this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); - }, newWaveIndex << 3, this.waveSeed); + this.executeWithSeedOffset( + () => { + this.currentBattle = new Battle( + this.gameMode, + newWaveIndex, + newBattleType, + newTrainer, + newDouble, + ); + }, + newWaveIndex << 3, + this.waveSeed, + ); this.currentBattle.incrementTurn(); if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { @@ -1350,20 +1718,33 @@ export default class BattleScene extends SceneBase { if (!waveIndex && lastBattle) { const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10); - const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily; - const isEndlessFifthWave = this.gameMode.hasShortBiomes && (lastBattle.waveIndex % 5) === 0; - const isWaveIndexMultipleOfFiftyMinusOne = (lastBattle.waveIndex % 50) === 49; - const isNewBiome = isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne); - const resetArenaState = isNewBiome || [ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.currentBattle.battleType) || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; - this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy()); + const isEndlessOrDaily = + this.gameMode.hasShortBiomes || this.gameMode.isDaily; + const isEndlessFifthWave = + this.gameMode.hasShortBiomes && lastBattle.waveIndex % 5 === 0; + const isWaveIndexMultipleOfFiftyMinusOne = + lastBattle.waveIndex % 50 === 49; + const isNewBiome = + isWaveIndexMultipleOfTen || + isEndlessFifthWave || + (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne); + const resetArenaState = + isNewBiome || + [ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes( + this.currentBattle.battleType, + ) || + this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; + this.getEnemyParty().forEach((enemyPokemon) => enemyPokemon.destroy()); this.trySpreadPokerus(); - if (!isNewBiome && (newWaveIndex % 10) === 5) { + if (!isNewBiome && newWaveIndex % 10 === 5) { this.arena.updatePoolsForTimeOfDay(); } if (resetArenaState) { this.arena.resetArenaEffects(); - playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); + playerField.forEach((pokemon) => + pokemon.lapseTag(BattlerTagType.COMMANDED), + ); playerField.forEach((pokemon, p) => { if (pokemon.isOnField()) { @@ -1375,7 +1756,12 @@ export default class BattleScene extends SceneBase { pokemon.resetBattleData(); pokemon.resetTera(); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); - if (pokemon.hasSpecies(Species.TERAPAGOS) || (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)) { + if ( + pokemon.hasSpecies(Species.TERAPAGOS) || + (this.gameMode.isClassic && + this.currentBattle.waveIndex > 180 && + this.currentBattle.waveIndex <= 190) + ) { this.arena.playerTerasUsed = 0; } } @@ -1386,7 +1772,10 @@ export default class BattleScene extends SceneBase { } for (const pokemon of this.getPlayerParty()) { - this.triggerPokemonFormChange(pokemon, SpeciesFormChangeTimeOfDayTrigger); + this.triggerPokemonFormChange( + pokemon, + SpeciesFormChangeTimeOfDayTrigger, + ); } if (!this.gameMode.hasRandomBiomes && !isNewBiome) { @@ -1409,23 +1798,35 @@ export default class BattleScene extends SceneBase { this.arena = new Arena(biome, Biome[biome].toLowerCase(), playerFaints); this.eventTarget.dispatchEvent(new NewArenaEvent()); - this.arenaBg.pipelineData = { terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome() }; + this.arenaBg.pipelineData = { + terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome(), + }; return this.arena; } updateFieldScale(): Promise { - return new Promise(resolve => { - const fieldScale = Math.floor(Math.pow(1 / this.getField(true) - .map(p => p.getSpriteScale()) - .reduce((highestScale: number, scale: number) => highestScale = Math.max(scale, highestScale), 0), 0.7) * 40 - ) / 40; + return new Promise((resolve) => { + const fieldScale = + Math.floor( + Math.pow( + 1 / + this.getField(true) + .map((p) => p.getSpriteScale()) + .reduce( + (highestScale: number, scale: number) => + (highestScale = Math.max(scale, highestScale)), + 0, + ), + 0.7, + ) * 40, + ) / 40; this.setFieldScale(fieldScale).then(() => resolve()); }); } setFieldScale(scale: number, instant: boolean = false): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { scale *= 6; if (this.field.scale === scale) { return resolve(); @@ -1441,19 +1842,28 @@ export default class BattleScene extends SceneBase { scale: scale, x: (defaultWidth - scaledWidth) / 2, y: defaultHeight - scaledHeight, - duration: !instant ? Utils.fixedInt(Math.abs(this.field.scale - scale) * 200) : 0, + duration: !instant + ? Utils.fixedInt(Math.abs(this.field.scale - scale) * 200) + : 0, ease: "Sine.easeInOut", - onComplete: () => resolve() + onComplete: () => resolve(), }); }); } - getSpeciesFormIndex(species: PokemonSpecies, gender?: Gender, nature?: Nature, ignoreArena?: boolean): number { + getSpeciesFormIndex( + species: PokemonSpecies, + gender?: Gender, + nature?: Nature, + ignoreArena?: boolean, + ): number { if (!species.forms?.length) { return 0; } - const isEggPhase: boolean = [ "EggLapsePhase", "EggHatchPhase" ].includes(this.getCurrentPhase()?.constructor.name ?? ""); + const isEggPhase: boolean = [ "EggLapsePhase", "EggHatchPhase" ].includes( + this.getCurrentPhase()?.constructor.name ?? "", + ); switch (species.speciesId) { case Species.UNOWN: @@ -1481,12 +1891,19 @@ export default class BattleScene extends SceneBase { case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); case Species.PIKACHU: - if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { + if ( + this.currentBattle?.battleType === BattleType.TRAINER && + this.currentBattle?.waveIndex < 30 + ) { return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 } return Utils.randSeedInt(8); case Species.EEVEE: - if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30 && !isEggPhase) { + if ( + this.currentBattle?.battleType === BattleType.TRAINER && + this.currentBattle?.waveIndex < 30 && + !isEggPhase + ) { return 0; // No Partner Eevee for Wave 12 Preschoolers } return Utils.randSeedInt(2); @@ -1509,13 +1926,26 @@ export default class BattleScene extends SceneBase { case Species.OINKOLOGNE: return gender === Gender.FEMALE ? 1 : 0; case Species.TOXTRICITY: - const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; + const lowkeyNatures = [ + Nature.LONELY, + Nature.BOLD, + Nature.RELAXED, + Nature.TIMID, + Nature.SERIOUS, + Nature.MODEST, + Nature.MILD, + Nature.QUIET, + Nature.BASHFUL, + Nature.CALM, + Nature.GENTLE, + Nature.CAREFUL, + ]; if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { return 1; } return 0; case Species.GIMMIGHOUL: - // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs + // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs if (this.gameMode.hasMysteryEncounters && !isEggPhase) { return 1; // Wandering form } else { @@ -1539,21 +1969,34 @@ export default class BattleScene extends SceneBase { private getGeneratedOffsetGym(): boolean { let ret = false; - this.executeWithSeedOffset(() => { - ret = !Utils.randSeedInt(2); - }, 0, this.seed.toString()); + this.executeWithSeedOffset( + () => { + ret = !Utils.randSeedInt(2); + }, + 0, + this.seed.toString(), + ); return ret; } private getGeneratedWaveCycleOffset(): number { let ret = 0; - this.executeWithSeedOffset(() => { - ret = Utils.randSeedInt(8) * 5; - }, 0, this.seed.toString()); + this.executeWithSeedOffset( + () => { + ret = Utils.randSeedInt(8) * 5; + }, + 0, + this.seed.toString(), + ); return ret; } - getEncounterBossSegments(waveIndex: number, level: number, species?: PokemonSpecies, forceBoss: boolean = false): number { + getEncounterBossSegments( + waveIndex: number, + level: number, + species?: PokemonSpecies, + forceBoss: boolean = false, + ): number { if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1) { return Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE; } else if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE === 1) { @@ -1566,11 +2009,19 @@ export default class BattleScene extends SceneBase { } let isBoss: boolean | undefined; - if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) { + if ( + forceBoss || + (species && + (species.subLegendary || species.legendary || species.mythical)) + ) { isBoss = true; } else { this.executeWithSeedOffset(() => { - isBoss = waveIndex % 10 === 0 || (this.gameMode.hasRandomBosses && Utils.randSeedInt(100) < Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30)); + isBoss = + waveIndex % 10 === 0 || + (this.gameMode.hasRandomBosses && + Utils.randSeedInt(100) < + Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30)); }, waveIndex << 2); } if (!isBoss) { @@ -1607,14 +2058,17 @@ export default class BattleScene extends SceneBase { return; } - this.executeWithSeedOffset(() => { - if (p) { - spread(p, -1); - } - if (p < party.length - 1) { - spread(p, 1); - } - }, this.currentBattle.waveIndex + (p << 8)); + this.executeWithSeedOffset( + () => { + if (p) { + spread(p, -1); + } + if (p < party.length - 1) { + spread(p, 1); + } + }, + this.currentBattle.waveIndex + (p << 8), + ); }); } @@ -1626,7 +2080,11 @@ export default class BattleScene extends SceneBase { this.rngCounter = 0; } - executeWithSeedOffset(func: Function, offset: number, seedOverride?: string): void { + executeWithSeedOffset( + func: Function, + offset: number, + seedOverride?: string, + ): void { if (!func) { return; } @@ -1634,7 +2092,9 @@ export default class BattleScene extends SceneBase { const tempRngOffset = this.rngOffset; const tempRngSeedOverride = this.rngSeedOverride; const state = Phaser.Math.RND.state(); - Phaser.Math.RND.sow([ Utils.shiftCharCodes(seedOverride || this.seed, offset) ]); + Phaser.Math.RND.sow([ + Utils.shiftCharCodes(seedOverride || this.seed, offset), + ]); this.rngCounter = 0; this.rngOffset = offset; this.rngSeedOverride = seedOverride || ""; @@ -1645,7 +2105,13 @@ export default class BattleScene extends SceneBase { this.rngSeedOverride = tempRngSeedOverride; } - addFieldSprite(x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, terrainColorRatio: number = 0): Phaser.GameObjects.Sprite { + addFieldSprite( + x: number, + y: number, + texture: string | Phaser.Textures.Texture, + frame?: string | number, + terrainColorRatio: number = 0, + ): Phaser.GameObjects.Sprite { const ret = this.add.sprite(x, y, texture, frame); ret.setPipeline(this.fieldSpritePipeline); if (terrainColorRatio) { @@ -1655,14 +2121,33 @@ export default class BattleScene extends SceneBase { return ret; } - addPokemonSprite(pokemon: Pokemon, x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite { + addPokemonSprite( + pokemon: Pokemon, + x: number, + y: number, + texture: string | Phaser.Textures.Texture, + frame?: string | number, + hasShadow: boolean = false, + ignoreOverride: boolean = false, + ): Phaser.GameObjects.Sprite { const ret = this.addFieldSprite(x, y, texture, frame); this.initPokemonSprite(ret, pokemon, hasShadow, ignoreOverride); return ret; } - initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite { - sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined, isTerastallized: pokemon ? pokemon.isTerastallized : false }); + initPokemonSprite( + sprite: Phaser.GameObjects.Sprite, + pokemon?: Pokemon, + hasShadow: boolean = false, + ignoreOverride: boolean = false, + ): Phaser.GameObjects.Sprite { + sprite.setPipeline(this.spritePipeline, { + tone: [ 0.0, 0.0, 0.0, 0.0 ], + hasShadow: hasShadow, + ignoreOverride: ignoreOverride, + teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined, + isTerastallized: pokemon ? pokemon.isTerastallized : false, + }); this.spriteSparkleHandler.add(sprite); return sprite; } @@ -1675,25 +2160,25 @@ export default class BattleScene extends SceneBase { } showFieldOverlay(duration: number): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { this.tweens.add({ targets: this.fieldOverlay, alpha: 0.5, ease: "Sine.easeOut", duration: duration, - onComplete: () => resolve() + onComplete: () => resolve(), }); }); } hideFieldOverlay(duration: number): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { this.tweens.add({ targets: this.fieldOverlay, alpha: 0, duration: duration, ease: "Cubic.easeIn", - onComplete: () => resolve() + onComplete: () => resolve(), }); }); } @@ -1708,26 +2193,26 @@ export default class BattleScene extends SceneBase { showShopOverlay(duration: number): Promise { this.shopOverlayShown = true; - return new Promise(resolve => { + return new Promise((resolve) => { this.tweens.add({ targets: this.shopOverlay, alpha: this.shopOverlayOpacity, ease: "Sine.easeOut", duration, - onComplete: () => resolve() + onComplete: () => resolve(), }); }); } hideShopOverlay(duration: number): Promise { this.shopOverlayShown = false; - return new Promise(resolve => { + return new Promise((resolve) => { this.tweens.add({ targets: this.shopOverlay, alpha: 0, duration: duration, ease: "Cubic.easeIn", - onComplete: () => resolve() + onComplete: () => resolve(), }); }); } @@ -1744,7 +2229,9 @@ export default class BattleScene extends SceneBase { const isBoss = !(this.currentBattle.waveIndex % 10); const biomeString: string = getBiomeName(this.arena.biomeType); this.fieldUI.moveAbove(this.biomeWaveText, this.luckText); - this.biomeWaveText.setText( biomeString + " - " + this.currentBattle.waveIndex.toString()); + this.biomeWaveText.setText( + biomeString + " - " + this.currentBattle.waveIndex.toString(), + ); this.biomeWaveText.setColor(!isBoss ? "#ffffff" : "#f89890"); this.biomeWaveText.setShadowColor(!isBoss ? "#636363" : "#984038"); this.biomeWaveText.setVisible(true); @@ -1755,7 +2242,9 @@ export default class BattleScene extends SceneBase { return; } const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money); - this.moneyText.setText(i18next.t("battleScene:moneyOwned", { formattedMoney })); + this.moneyText.setText( + i18next.t("battleScene:moneyOwned", { formattedMoney }), + ); this.fieldUI.moveAbove(this.moneyText, this.luckText); if (forceVisible) { this.moneyText.setVisible(true); @@ -1774,7 +2263,8 @@ export default class BattleScene extends SceneBase { scale: this.moneyText.scale + deltaScale, loop: 0, yoyo: true, - onComplete: (_) => this.moneyText.setShadowColor(getTextColor(TextStyle.MONEY, true)), + onComplete: (_) => + this.moneyText.setShadowColor(getTextColor(TextStyle.MONEY, true)), }); } @@ -1789,7 +2279,7 @@ export default class BattleScene extends SceneBase { */ updateAndShowText(duration: number): void { const labels = [ this.luckLabelText, this.luckText ]; - labels.forEach(t => t.setAlpha(0)); + labels.forEach((t) => t.setAlpha(0)); const luckValue = getPartyLuckValue(this.getPlayerParty()); this.luckText.setText(getLuckString(luckValue)); if (luckValue < 14) { @@ -1797,14 +2287,16 @@ export default class BattleScene extends SceneBase { } else { this.luckText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); } - this.luckLabelText.setX((this.game.canvas.width / 6) - 2 - (this.luckText.displayWidth + 2)); + this.luckLabelText.setX( + this.game.canvas.width / 6 - 2 - (this.luckText.displayWidth + 2), + ); this.tweens.add({ targets: labels, duration: duration, alpha: 1, onComplete: () => { - labels.forEach(t => t.setVisible(true)); - } + labels.forEach((t) => t.setVisible(true)); + }, }); } @@ -1818,21 +2310,29 @@ export default class BattleScene extends SceneBase { duration: duration, alpha: 0, onComplete: () => { - labels.forEach(l => l.setVisible(false)); - } + labels.forEach((l) => l.setVisible(false)); + }, }); } updateUIPositions(): void { - const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length; - const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; + const enemyModifierCount = this.enemyModifiers.filter((m) => + m.isIconVisible(), + ).length; + const biomeWaveTextHeight = + this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( - -(this.game.canvas.height / 6) + (enemyModifierCount ? enemyModifierCount <= 12 ? 15 : 24 : 0) + (biomeWaveTextHeight / 2) + -(this.game.canvas.height / 6) + + (enemyModifierCount ? (enemyModifierCount <= 12 ? 15 : 24) : 0) + + biomeWaveTextHeight / 2, ); this.moneyText.setY(this.biomeWaveText.y + 10); this.scoreText.setY(this.moneyText.y + 10); - [ this.luckLabelText, this.luckText ].map(l => l.setY((this.scoreText.visible ? this.scoreText : this.moneyText).y + 10)); - const offsetY = (this.scoreText.visible ? this.scoreText : this.moneyText).y + 15; + [ this.luckLabelText, this.luckText ].map((l) => + l.setY((this.scoreText.visible ? this.scoreText : this.moneyText).y + 10), + ); + const offsetY = + (this.scoreText.visible ? this.scoreText : this.moneyText).y + 15; this.partyExpBar.setY(offsetY); this.candyBar.setY(offsetY + 15); this.ui?.achvBar.setY(this.game.canvas.height / 6 + offsetY); @@ -1848,8 +2348,20 @@ export default class BattleScene extends SceneBase { } addFaintedEnemyScore(enemy: EnemyPokemon): void { - let scoreIncrease = enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: number, total: number) => total += iv, 0) / 93) * 0.2 + 0.8); - this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map(m => scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()); + let scoreIncrease = + enemy.getSpeciesForm().getBaseExp() * + (enemy.level / this.getMaxExpLevel()) * + ((enemy.ivs.reduce((iv: number, total: number) => (total += iv), 0) / + 93) * + 0.2 + + 0.8); + this.findModifiers( + (m) => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, + false, + ).map( + (m) => + (scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()), + ); if (enemy.isBoss()) { scoreIncrease *= Math.sqrt(enemy.bossSegments); } @@ -1864,35 +2376,68 @@ export default class BattleScene extends SceneBase { } const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); - const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; + const baseLevel = + (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * + 1.2; return Math.ceil(baseLevel / 2) * 2 + 2; } - randomSpecies(waveIndex: number, level: number, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { + randomSpecies( + waveIndex: number, + level: number, + fromArenaPool?: boolean, + speciesFilter?: PokemonSpeciesFilter, + filterAllEvolutions?: boolean, + ): PokemonSpecies { if (fromArenaPool) { - return this.arena.randomSpecies(waveIndex, level, undefined, getPartyLuckValue(this.party)); + return this.arena.randomSpecies( + waveIndex, + level, + undefined, + getPartyLuckValue(this.party), + ); } - const filteredSpecies = speciesFilter ? [ ...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => { - if (!filterAllEvolutions) { - while (pokemonPrevolutions.hasOwnProperty(s.speciesId)) { - s = getPokemonSpecies(pokemonPrevolutions[s.speciesId]); - } - } - return s; - })) ] : allSpecies.filter(s => s.isCatchable()); + const filteredSpecies = speciesFilter + ? [ + ...new Set( + allSpecies + .filter((s) => s.isCatchable()) + .filter(speciesFilter) + .map((s) => { + if (!filterAllEvolutions) { + while (pokemonPrevolutions.hasOwnProperty(s.speciesId)) { + s = getPokemonSpecies(pokemonPrevolutions[s.speciesId]); + } + } + return s; + }), + ), + ] + : allSpecies.filter((s) => s.isCatchable()); return filteredSpecies[Utils.randSeedInt(filteredSpecies.length)]; } generateRandomBiome(waveIndex: number): Biome { const relWave = waveIndex % 250; - const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); + const biomes = Utils.getEnumValues(Biome).filter( + (b) => b !== Biome.TOWN && b !== Biome.END, + ); const maxDepth = biomeDepths[Biome.END][0] - 2; - const depthWeights = new Array(maxDepth + 1).fill(null) - .map((_, i: number) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250); + const depthWeights = new Array(maxDepth + 1) + .fill(null) + .map( + (_, i: number) => + ((1 - + Math.min(Math.abs(i / (maxDepth - 1) - relWave / 250) + 0.25, 1)) / + 0.75) * + 250, + ); const biomeThresholds: number[] = []; let totalWeight = 0; for (const biome of biomes) { - totalWeight += Math.ceil(depthWeights[biomeDepths[biome][0] - 1] / biomeDepths[biome][1]); + totalWeight += Math.ceil( + depthWeights[biomeDepths[biome][0] - 1] / biomeDepths[biome][1], + ); biomeThresholds.push(totalWeight); } @@ -1918,7 +2463,7 @@ export default class BattleScene extends SceneBase { if (this.bgm && bgmName === this.bgm.key) { if (!this.bgm.isPlaying) { this.bgm.play({ - volume: this.masterVolume * this.bgmVolume + volume: this.masterVolume * this.bgmVolume, }); } return; @@ -1929,15 +2474,16 @@ export default class BattleScene extends SceneBase { this.bgmCache.add(bgmName); this.loadBgm(bgmName); let loopPoint = 0; - loopPoint = bgmName === this.arena.bgm - ? this.arena.getBgmLoopPoint() - : this.getBgmLoopPoint(bgmName); + loopPoint = + bgmName === this.arena.bgm + ? this.arena.getBgmLoopPoint() + : this.getBgmLoopPoint(bgmName); let loaded = false; const playNewBgm = () => { this.ui.bgmBar.setBgmToBgmBar(bgmName); if (bgmName === null && this.bgm && !this.bgm.pendingRemove) { this.bgm.play({ - volume: this.masterVolume * this.bgmVolume + volume: this.masterVolume * this.bgmVolume, }); return; } @@ -1946,7 +2492,7 @@ export default class BattleScene extends SceneBase { } this.bgm = this.sound.add(bgmName, { loop: true }); this.bgm.play({ - volume: this.masterVolume * this.bgmVolume + volume: this.masterVolume * this.bgmVolume, }); if (loopPoint) { this.bgm.on("looped", () => this.bgm.play({ seek: loopPoint })); @@ -1995,7 +2541,6 @@ export default class BattleScene extends SceneBase { } else { const soundDetails = sound.key.split("/"); switch (soundDetails[0]) { - case "battle_anims": case "cry": if (soundDetails[1].startsWith("PRSFX- ")) { @@ -2017,7 +2562,9 @@ export default class BattleScene extends SceneBase { if (!this.bgm) { return false; } - const bgm = this.sound.getAllPlaying().find(bgm => bgm.key === this.bgm.key); + const bgm = this.sound + .getAllPlaying() + .find((bgm) => bgm.key === this.bgm.key); if (bgm) { SoundFade.fadeOut(this, this.bgm, duration, destroy); return true; @@ -2032,7 +2579,11 @@ export default class BattleScene extends SceneBase { * @param destroy * @param delay */ - fadeAndSwitchBgm(newBgmKey: string, destroy: boolean = false, delay: number = 2000) { + fadeAndSwitchBgm( + newBgmKey: string, + destroy: boolean = false, + delay: number = 2000, + ) { this.fadeOutBgm(delay, destroy); this.time.delayedCall(delay, () => { this.playBgm(newBgmKey); @@ -2052,24 +2603,24 @@ export default class BattleScene extends SceneBase { case "heal": case "evolution": case "evolution_fanfare": - // These sounds are loaded in as BGM, but played as sound effects - // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() - config["volume"] *= (this.masterVolume * this.bgmVolume); + // These sounds are loaded in as BGM, but played as sound effects + // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() + config["volume"] *= this.masterVolume * this.bgmVolume; break; case "battle_anims": case "cry": - config["volume"] *= (this.masterVolume * this.fieldVolume); + config["volume"] *= this.masterVolume * this.fieldVolume; //PRSFX sound files are unusually loud if (keyDetails[1].startsWith("PRSFX- ")) { config["volume"] *= 0.5; } break; case "ui": - //As of, right now this applies to the "select", "menu_open", "error" sound effects - config["volume"] *= (this.masterVolume * this.uiVolume); + //As of, right now this applies to the "select", "menu_open", "error" sound effects + config["volume"] *= this.masterVolume * this.uiVolume; break; case "se": - config["volume"] *= (this.masterVolume * this.seVolume); + config["volume"] *= this.masterVolume * this.seVolume; break; } this.sound.play(key, config); @@ -2089,10 +2640,13 @@ export default class BattleScene extends SceneBase { this.bgmResumeTimer.destroy(); } if (resumeBgm) { - this.bgmResumeTimer = this.time.delayedCall((pauseDuration || Utils.fixedInt(sound.totalDuration * 1000)), () => { - this.resumeBgm(); - this.bgmResumeTimer = null; - }); + this.bgmResumeTimer = this.time.delayedCall( + pauseDuration || Utils.fixedInt(sound.totalDuration * 1000), + () => { + this.resumeBgm(); + this.bgmResumeTimer = null; + }, + ); } return sound; } @@ -2101,9 +2655,9 @@ export default class BattleScene extends SceneBase { getBgmLoopPoint(bgmName: string): number { switch (bgmName) { case "title": //Firel PokéRogue Title - return 46.500; + return 46.5; case "battle_kanto_champion": //B2W2 Kanto Champion Battle - return 13.950; + return 13.95; case "battle_johto_champion": //B2W2 Johto Champion Battle return 23.498; case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle @@ -2117,7 +2671,7 @@ export default class BattleScene extends SceneBase { case "battle_champion_iris": //B2W2 Unova Champion Battle return 10.145; case "battle_kalos_champion": //XY Kalos Champion Battle - return 10.380; + return 10.38; case "battle_champion_kukui": //SM Kukui Battle return 15.784; case "battle_alola_champion": //USUM Alola Champion Battle @@ -2133,17 +2687,17 @@ export default class BattleScene extends SceneBase { case "battle_champion_kieran": //SV Champion Kieran Battle return 7.206; case "battle_hoenn_elite": //ORAS Elite Four Battle - return 11.350; + return 11.35; case "battle_unova_elite": //BW Elite Four Battle - return 17.730; + return 17.73; case "battle_kalos_elite": //XY Elite Four Battle - return 12.340; + return 12.34; case "battle_alola_elite": //SM Elite Four Battle return 19.212; case "battle_galar_elite": //SWSH League Tournament Battle return 164.069; case "battle_paldea_elite": //SV Elite Four Battle - return 12.770; + return 12.77; case "battle_bb_elite": //SV BB League Elite Four Battle return 19.434; case "battle_final_encounter": //PMD RTDX Rayquaza's Domain @@ -2161,7 +2715,7 @@ export default class BattleScene extends SceneBase { case "battle_unova_gym": //BW Unova Gym Battle return 19.145; case "battle_kalos_gym": //XY Kalos Gym Battle - return 44.810; + return 44.81; case "battle_galar_gym": //SWSH Galar Gym Battle return 171.262; case "battle_paldea_gym": //SV Paldea Gym Battle @@ -2175,13 +2729,13 @@ export default class BattleScene extends SceneBase { case "battle_legendary_suicune": //HGSS Suicune Battle return 12.636; case "battle_legendary_lugia": //HGSS Lugia Battle - return 19.770; + return 19.77; case "battle_legendary_ho_oh": //HGSS Ho-oh Battle return 17.668; case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle - return 49.500; + return 49.5; case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle - return 21.130; + return 21.13; case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle return 10.547; case "battle_legendary_rayquaza": //ORAS Rayquaza Battle @@ -2191,7 +2745,7 @@ export default class BattleScene extends SceneBase { case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle return 16.887; case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle - return 22.770; + return 22.77; case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle return 16.009; case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle @@ -2209,7 +2763,7 @@ export default class BattleScene extends SceneBase { case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle return 26.468; case "battle_legendary_tapu": //SM Tapu Battle - return 0.000; + return 0.0; case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle return 6.525; case "battle_legendary_ub": //SM Ultra Beast Battle @@ -2233,7 +2787,7 @@ export default class BattleScene extends SceneBase { case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle return 6.442; case "battle_legendary_loyal_three": //SV Loyal Three Battle - return 6.500; + return 6.5; case "battle_legendary_ogerpon": //SV Ogerpon Battle return 14.335; case "battle_legendary_terapagos": //SV Terapagos Battle @@ -2241,7 +2795,7 @@ export default class BattleScene extends SceneBase { case "battle_legendary_pecharunt": //SV Pecharunt Battle return 6.508; case "battle_rival": //BW Rival Battle - return 14.110; + return 14.11; case "battle_rival_2": //BW N Battle return 17.714; case "battle_rival_3": //BW Final N Battle @@ -2251,7 +2805,7 @@ export default class BattleScene extends SceneBase { case "battle_wild": //BW Wild Battle return 12.703; case "battle_wild_strong": //BW Strong Wild Battle - return 13.940; + return 13.94; case "end_summit": //PMD RTDX Sky Tower Summit return 30.025; case "battle_rocket_grunt": //HGSS Team Rocket Battle @@ -2265,7 +2819,7 @@ export default class BattleScene extends SceneBase { case "battle_flare_grunt": //XY Team Flare Battle return 4.228; case "battle_aether_grunt": // SM Aether Foundation Battle - return 16.00; + return 16.0; case "battle_skull_grunt": // SM Team Skull Battle return 20.87; case "battle_macro_grunt": // SWSH Trainer Battle @@ -2279,7 +2833,7 @@ export default class BattleScene extends SceneBase { case "battle_skull_admin": //SM Team Skull Admin Battle return 15.463; case "battle_oleana": //SWSH Oleana Battle - return 14.110; + return 14.11; case "battle_star_admin": //SV Team Star Boss Battle return 9.493; case "battle_rocket_boss": //USUM Giovanni Battle @@ -2332,7 +2886,6 @@ export default class BattleScene extends SceneBase { return this.standbyPhase; } - /** * Adds a phase to the conditional queue and ensures it is executed only when the specified condition is met. * @@ -2364,7 +2917,11 @@ export default class BattleScene extends SceneBase { if (this.phaseQueuePrependSpliceIndex === -1) { this.phaseQueuePrepend.push(...phases); } else { - this.phaseQueuePrepend.splice(this.phaseQueuePrependSpliceIndex, 0, ...phases); + this.phaseQueuePrepend.splice( + this.phaseQueuePrependSpliceIndex, + 0, + ...phases, + ); } } @@ -2437,7 +2994,10 @@ export default class BattleScene extends SceneBase { } if (this.currentPhase) { - console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); + console.log( + `%cStart Phase ${this.currentPhase.constructor.name}`, + "color:green;", + ); this.currentPhase.start(); } } @@ -2461,11 +3021,16 @@ export default class BattleScene extends SceneBase { * @param phaseFilter filter function to use to find the wanted phase * @returns the found phase or undefined if none found */ - findPhase

(phaseFilter: (phase: P) => boolean): P | undefined { + findPhase

( + phaseFilter: (phase: P) => boolean, + ): P | undefined { return this.phaseQueue.find(phaseFilter) as P; } - tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { + tryReplacePhase( + phaseFilter: (phase: Phase) => boolean, + phase: Phase, + ): boolean { const phaseIndex = this.phaseQueue.findIndex(phaseFilter); if (phaseIndex > -1) { this.phaseQueue[phaseIndex] = phase; @@ -2502,11 +3067,16 @@ export default class BattleScene extends SceneBase { * @param targetPhase {@linkcode Phase} the type of phase to search for in phaseQueue * @returns boolean if a targetPhase was found and added */ - prependToPhase(phase: Phase | Phase [], targetPhase: Constructor): boolean { + prependToPhase( + phase: Phase | Phase[], + targetPhase: Constructor, + ): boolean { if (!Array.isArray(phase)) { phase = [ phase ]; } - const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase); + const targetIndex = this.phaseQueue.findIndex( + (ph) => ph instanceof targetPhase, + ); if (targetIndex !== -1) { this.phaseQueue.splice(targetIndex, 0, ...phase); @@ -2523,11 +3093,16 @@ export default class BattleScene extends SceneBase { * @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue} * @returns `true` if a `targetPhase` was found to append to */ - appendToPhase(phase: Phase | Phase[], targetPhase: Constructor): boolean { + appendToPhase( + phase: Phase | Phase[], + targetPhase: Constructor, + ): boolean { if (!Array.isArray(phase)) { phase = [ phase ]; } - const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase); + const targetIndex = this.phaseQueue.findIndex( + (ph) => ph instanceof targetPhase, + ); if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) { this.phaseQueue.splice(targetIndex + 1, 0, ...phase); @@ -2546,7 +3121,13 @@ export default class BattleScene extends SceneBase { * @param promptDelay optional param for MessagePhase constructor * @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue */ - queueMessage(message: string, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, defer?: boolean | null) { + queueMessage( + message: string, + callbackDelay?: number | null, + prompt?: boolean | null, + promptDelay?: number | null, + defer?: boolean | null, + ) { const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay); if (!defer) { // adds to the end of PhaseQueuePrepend @@ -2578,11 +3159,22 @@ export default class BattleScene extends SceneBase { getWaveMoneyAmount(moneyMultiplier: number): number { const waveIndex = this.currentBattle.waveIndex; const waveSetIndex = Math.ceil(waveIndex / 10) - 1; - const moneyValue = Math.pow((waveSetIndex + 1 + (0.75 + (((waveIndex - 1) % 10) + 1) / 10)) * 100, 1 + 0.005 * waveSetIndex) * moneyMultiplier; + const moneyValue = + Math.pow( + (waveSetIndex + 1 + (0.75 + (((waveIndex - 1) % 10) + 1) / 10)) * 100, + 1 + 0.005 * waveSetIndex, + ) * moneyMultiplier; return Math.floor(moneyValue / 10) * 10; } - addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): boolean { + addModifier( + modifier: Modifier | null, + ignoreUpdate?: boolean, + playSound?: boolean, + virtual?: boolean, + instant?: boolean, + cost?: number, + ): boolean { if (!modifier) { return false; } @@ -2602,14 +3194,25 @@ export default class BattleScene extends SceneBase { this.playSound(soundName); } } else if (!virtual) { - const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); + const defaultModifierType = getDefaultModifierTypeForTier( + modifier.type.tier, + ); this.queueMessage( - i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name }), + i18next.t("battle:itemStackFull", { + fullItemName: modifier.type.name, + itemName: defaultModifierType.name, + }), undefined, false, - 3000 + 3000, + ); + return this.addModifier( + defaultModifierType.newModifier(), + ignoreUpdate, + playSound, + false, + instant, ); - return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant); } for (const rm of modifiersToRemove) { @@ -2632,14 +3235,23 @@ export default class BattleScene extends SceneBase { if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { const hpRestoreMultiplier = new Utils.NumberHolder(1); - this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); + this.applyModifiers( + HealingBoosterModifier, + true, + hpRestoreMultiplier, + ); args.push(hpRestoreMultiplier.value); } else { args.push(1); } } else if (modifier instanceof FusePokemonModifier) { - args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); - } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { + args.push( + this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon, + ); + } else if ( + modifier instanceof RememberMoveModifier && + !Utils.isNullOrUndefined(cost) + ) { args.push(cost); } @@ -2661,8 +3273,12 @@ export default class BattleScene extends SceneBase { return success; } - addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { - return new Promise(resolve => { + addEnemyModifier( + modifier: PersistentModifier, + ignoreUpdate?: boolean, + instant?: boolean, + ): Promise { + return new Promise((resolve) => { const modifiersToRemove: PersistentModifier[] = []; if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { if (modifier instanceof PokemonFormChangeItemModifier) { @@ -2721,7 +3337,10 @@ export default class BattleScene extends SceneBase { const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; newItemModifier.pokemonId = target.id; const matchingModifier = this.findModifier( - (m) => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, + (m) => + m instanceof PokemonHeldItemModifier && + m.matchType(itemModifier) && + m.pokemonId === target.id, target.isPlayer(), ) as PokemonHeldItemModifier; @@ -2745,11 +3364,24 @@ export default class BattleScene extends SceneBase { const removeOld = itemModifier.stackCount === 0; - if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { + if ( + !removeOld || + !source || + this.removeModifier(itemModifier, !source.isPlayer()) + ) { const addModifier = () => { - if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { + if ( + !matchingModifier || + this.removeModifier(matchingModifier, !target.isPlayer()) + ) { if (target.isPlayer()) { - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); + this.addModifier( + newItemModifier, + ignoreUpdate, + playSound, + false, + instant, + ); if (source && itemLost) { applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); } @@ -2776,9 +3408,13 @@ export default class BattleScene extends SceneBase { } removePartyMemberModifiers(partyMemberIndex: number): Promise { - return new Promise(resolve => { + return new Promise((resolve) => { const pokemonId = this.getPlayerParty()[partyMemberIndex].id; - const modifiersToRemove = this.modifiers.filter(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId); + const modifiersToRemove = this.modifiers.filter( + (m) => + m instanceof PokemonHeldItemModifier && + (m as PokemonHeldItemModifier).pokemonId === pokemonId, + ); for (const m of modifiersToRemove) { this.modifiers.splice(this.modifiers.indexOf(m), 1); } @@ -2787,13 +3423,19 @@ export default class BattleScene extends SceneBase { }); } - generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise { - return new Promise(resolve => { + generateEnemyModifiers( + heldModifiersConfigs?: HeldModifierConfig[][], + ): Promise { + return new Promise((resolve) => { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return resolve(); } - const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(this.currentBattle.waveIndex); - const isFinalBoss = this.gameMode.isWaveFinal(this.currentBattle.waveIndex); + const difficultyWaveIndex = this.gameMode.getWaveForDifficulty( + this.currentBattle.waveIndex, + ); + const isFinalBoss = this.gameMode.isWaveFinal( + this.currentBattle.waveIndex, + ); let chances = Math.ceil(difficultyWaveIndex / 10); if (isFinalBoss) { chances = Math.ceil(chances * 2.5); @@ -2811,8 +3453,12 @@ export default class BattleScene extends SceneBase { } party.forEach((enemyPokemon: EnemyPokemon, i: number) => { - if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) { - heldModifiersConfigs[i].forEach(mt => { + if ( + heldModifiersConfigs && + i < heldModifiersConfigs.length && + heldModifiersConfigs[i] + ) { + heldModifiersConfigs[i].forEach((mt) => { let modifier: PokemonHeldItemModifier; if (mt.modifier instanceof PokemonHeldItemModifierType) { modifier = mt.modifier.newModifier(enemyPokemon); @@ -2821,11 +3467,15 @@ export default class BattleScene extends SceneBase { modifier.pokemonId = enemyPokemon.id; } modifier.stackCount = mt.stackCount ?? 1; - modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; + modifier.isTransferable = + mt.isTransferable ?? modifier.isTransferable; this.addEnemyModifier(modifier, true); }); } else { - const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss); + const isBoss = + enemyPokemon.isBoss() || + (this.currentBattle.battleType === BattleType.TRAINER && + !!this.currentBattle.trainer?.config.isBoss); let upgradeChance = 32; if (isBoss) { upgradeChance /= 2; @@ -2835,19 +3485,32 @@ export default class BattleScene extends SceneBase { } const modifierChance = this.gameMode.getEnemyModifierChance(isBoss); let pokemonModifierChance = modifierChance; - if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer) - pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line + if ( + this.currentBattle.battleType === BattleType.TRAINER && + this.currentBattle.trainer + ) { + pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); + } let count = 0; for (let c = 0; c < chances; c++) { - if (!Utils.randSeedInt(modifierChance)) { + if (!Utils.randSeedInt(pokemonModifierChance)) { count++; } } if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false)); + getEnemyModifierTypesForWave( + difficultyWaveIndex, + count, + [ enemyPokemon ], + this.currentBattle.battleType === BattleType.TRAINER + ? ModifierPoolType.TRAINER + : ModifierPoolType.WILD, + upgradeChance, + ).map((mt) => + mt.newModifier(enemyPokemon).add(this.enemyModifiers, false), + ); } return true; }); @@ -2857,10 +3520,12 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type - */ + * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type + */ clearEnemyModifiers(): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier); + const modifiersToRemove = this.enemyModifiers.filter( + (m) => m instanceof PersistentModifier, + ); for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } @@ -2869,11 +3534,15 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type - * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} - */ + * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type + * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} + */ clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon() === pokemon)); + const modifiersToRemove = this.enemyModifiers.filter( + (m) => + m instanceof PokemonHeldItemModifier && + (!pokemon || m.getPokemon() === pokemon), + ); for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } @@ -2882,11 +3551,13 @@ export default class BattleScene extends SceneBase { } setModifiersVisible(visible: boolean) { - [ this.modifierBar, this.enemyModifierBar ].map(m => m.setVisible(visible)); + [ this.modifierBar, this.enemyModifierBar ].map((m) => m.setVisible(visible)); } updateModifiers(player: boolean = true, instant?: boolean): void { - const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); + const modifiers = player + ? this.modifiers + : (this.enemyModifiers as PersistentModifier[]); for (let m = 0; m < modifiers.length; m++) { const modifier = modifiers[m]; if ( @@ -2909,19 +3580,26 @@ export default class BattleScene extends SceneBase { } } - this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant); - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); + this.updatePartyForModifiers( + player ? this.getPlayerParty() : this.getEnemyParty(), + instant, + ); + (player ? this.modifierBar : this.enemyModifierBar).updateModifiers( + modifiers, + ); if (!player) { this.updateUIPositions(); } } updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { - return new Promise(resolve => { - Promise.allSettled(party.map(p => { - p.calculateStats(); - return p.updateInfo(instant); - })).then(() => resolve()); + return new Promise((resolve) => { + Promise.allSettled( + party.map((p) => { + p.calculateStats(); + return p.updateInfo(instant); + }), + ).then(() => resolve()); }); } @@ -2933,7 +3611,10 @@ export default class BattleScene extends SceneBase { * @param enemy If `true`, remove an item owned by the enemy. If `false`, remove an item owned by the player. Default is `false`. * @returns `true` if the item exists and was successfully removed, `false` otherwise. */ - removeModifier(modifier: PersistentModifier, enemy: boolean = false): boolean { + removeModifier( + modifier: PersistentModifier, + enemy: boolean = false, + ): boolean { const modifiers = !enemy ? this.modifiers : this.enemyModifiers; const modifierIndex = modifiers.indexOf(modifier); if (modifierIndex > -1) { @@ -2956,8 +3637,13 @@ export default class BattleScene extends SceneBase { * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the list of all modifiers that matched `modifierType`. */ - getModifiers(modifierType: Constructor, player: boolean = true): T[] { - return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); + getModifiers( + modifierType: Constructor, + player: boolean = true, + ): T[] { + return (player ? this.modifiers : this.enemyModifiers).filter( + (m): m is T => m instanceof modifierType, + ); } /** @@ -2966,8 +3652,13 @@ export default class BattleScene extends SceneBase { * @param isPlayer Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the list of all modifiers that passed the `modifierFilter` function */ - findModifiers(modifierFilter: ModifierPredicate, isPlayer: boolean = true): PersistentModifier[] { - return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); + findModifiers( + modifierFilter: ModifierPredicate, + isPlayer: boolean = true, + ): PersistentModifier[] { + return (isPlayer ? this.modifiers : this.enemyModifiers).filter( + modifierFilter, + ); } /** @@ -2976,7 +3667,10 @@ export default class BattleScene extends SceneBase { * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed */ - findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined { + findModifier( + modifierFilter: ModifierPredicate, + player: boolean = true, + ): PersistentModifier | undefined { return (player ? this.modifiers : this.enemyModifiers).find(modifierFilter); } @@ -2987,18 +3681,31 @@ export default class BattleScene extends SceneBase { * @param ...args The list of arguments needed to invoke `modifierType.apply` * @returns the list of all modifiers that matched `modifierType` and were applied. */ - applyShuffledModifiers(modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { - let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); - this.executeWithSeedOffset(() => { - const shuffleModifiers = mods => { - if (mods.length < 1) { - return mods; - } - const rand = Utils.randSeedInt(mods.length); - return [ mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand)) ]; - }; - modifiers = shuffleModifiers(modifiers); - }, this.currentBattle.turn << 4, this.waveSeed); + applyShuffledModifiers( + modifierType: Constructor, + player: boolean = true, + ...args: Parameters + ): T[] { + let modifiers = (player ? this.modifiers : this.enemyModifiers).filter( + (m): m is T => m instanceof modifierType && m.shouldApply(...args), + ); + this.executeWithSeedOffset( + () => { + const shuffleModifiers = (mods) => { + if (mods.length < 1) { + return mods; + } + const rand = Utils.randSeedInt(mods.length); + return [ + mods[rand], + ...shuffleModifiers(mods.filter((_, i) => i !== rand)), + ]; + }; + modifiers = shuffleModifiers(modifiers); + }, + this.currentBattle.turn << 4, + this.waveSeed, + ); return this.applyModifiersInternal(modifiers, player, args); } @@ -3009,13 +3716,23 @@ export default class BattleScene extends SceneBase { * @param ...args The list of arguments needed to invoke `modifierType.apply` * @returns the list of all modifiers that matched `modifierType` and were applied. */ - applyModifiers(modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { - const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); + applyModifiers( + modifierType: Constructor, + player: boolean = true, + ...args: Parameters + ): T[] { + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter( + (m): m is T => m instanceof modifierType && m.shouldApply(...args), + ); return this.applyModifiersInternal(modifiers, player, args); } /** Helper function to apply all passed modifiers */ - applyModifiersInternal(modifiers: T[], player: boolean, args: Parameters): T[] { + applyModifiersInternal( + modifiers: T[], + player: boolean, + args: Parameters, + ): T[] { const appliedModifiers: T[] = []; for (const modifier of modifiers) { if (modifier.apply(...args)) { @@ -3034,8 +3751,14 @@ export default class BattleScene extends SceneBase { * @param ...args The list of arguments needed to invoke `modifierType.apply` * @returns the first modifier that matches `modifierType` and was applied; return `null` if none matched */ - applyModifier(modifierType: Constructor, player: boolean = true, ...args: Parameters): T | null { - const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); + applyModifier( + modifierType: Constructor, + player: boolean = true, + ...args: Parameters + ): T | null { + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter( + (m): m is T => m instanceof modifierType && m.shouldApply(...args), + ); for (const modifier of modifiers) { if (modifier.apply(...args)) { console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); @@ -3046,22 +3769,42 @@ export default class BattleScene extends SceneBase { return null; } - triggerPokemonFormChange(pokemon: Pokemon, formChangeTriggerType: Constructor, delayed: boolean = false, modal: boolean = false): boolean { + triggerPokemonFormChange( + pokemon: Pokemon, + formChangeTriggerType: Constructor, + delayed: boolean = false, + modal: boolean = false, + ): boolean { if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId)) { - // in case this is NECROZMA, determine which forms this - const matchingFormChangeOpts = pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.findTrigger(formChangeTriggerType) && fc.canChange(pokemon)); + const matchingFormChangeOpts = pokemonFormChanges[ + pokemon.species.speciesId + ].filter( + (fc) => fc.findTrigger(formChangeTriggerType) && fc.canChange(pokemon), + ); let matchingFormChange: SpeciesFormChange | null; - if (pokemon.species.speciesId === Species.NECROZMA && matchingFormChangeOpts.length > 1) { + if ( + pokemon.species.speciesId === Species.NECROZMA && + matchingFormChangeOpts.length > 1 + ) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. - const formChangeItemModifiers = (this.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]).filter(m => m.active).map(m => m.formChangeItem); + const formChangeItemModifiers = ( + this.findModifiers( + (m) => + m instanceof PokemonFormChangeItemModifier && + m.pokemonId === pokemon.id, + ) as PokemonFormChangeItemModifier[] + ) + .filter((m) => m.active) + .map((m) => m.formChangeItem); - - matchingFormChange = formChangeItemModifiers.includes(FormChangeItem.N_LUNARIZER) ? - matchingFormChangeOpts[0] : - formChangeItemModifiers.includes(FormChangeItem.N_SOLARIZER) ? - matchingFormChangeOpts[1] : - null; + matchingFormChange = formChangeItemModifiers.includes( + FormChangeItem.N_LUNARIZER, + ) + ? matchingFormChangeOpts[0] + : formChangeItemModifiers.includes(FormChangeItem.N_SOLARIZER) + ? matchingFormChangeOpts[1] + : null; } else { matchingFormChange = matchingFormChangeOpts[0]; } @@ -3072,7 +3815,11 @@ export default class BattleScene extends SceneBase { } else { phase = new QuietFormChangePhase(pokemon, matchingFormChange); } - if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) { + if ( + pokemon instanceof PlayerPokemon && + !matchingFormChange.quiet && + modal + ) { this.overridePhase(phase); } else if (delayed) { this.pushPhase(phase); @@ -3086,8 +3833,17 @@ export default class BattleScene extends SceneBase { return false; } - triggerPokemonBattleAnim(pokemon: Pokemon, battleAnimType: PokemonAnimType, fieldAssets?: Phaser.GameObjects.Sprite[], delayed: boolean = false): boolean { - const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); + triggerPokemonBattleAnim( + pokemon: Pokemon, + battleAnimType: PokemonAnimType, + fieldAssets?: Phaser.GameObjects.Sprite[], + delayed: boolean = false, + ): boolean { + const phase: Phase = new PokemonAnimPhase( + battleAnimType, + pokemon, + fieldAssets, + ); if (delayed) { this.pushPhase(phase); } else { @@ -3097,15 +3853,20 @@ export default class BattleScene extends SceneBase { } validateAchvs(achvType: Constructor, ...args: unknown[]): void { - const filteredAchvs = Object.values(achvs).filter(a => a instanceof achvType); + const filteredAchvs = Object.values(achvs).filter( + (a) => a instanceof achvType, + ); for (const achv of filteredAchvs) { this.validateAchv(achv, args); } } validateAchv(achv: Achv, args?: unknown[]): boolean { - if ((!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) - && achv.validate(args)) { + if ( + (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || + Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) && + achv.validate(args) + ) { this.gameData.achvUnlocks[achv.id] = new Date().getTime(); this.ui.achvBar.showAchv(achv); if (vouchers.hasOwnProperty(achv.id)) { @@ -3118,7 +3879,10 @@ export default class BattleScene extends SceneBase { } validateVoucher(voucher: Voucher, args?: unknown[]): boolean { - if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(args)) { + if ( + !this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && + voucher.validate(args) + ) { this.gameData.voucherUnlocks[voucher.id] = new Date().getTime(); this.ui.achvBar.showAchv(voucher); this.gameData.voucherCounts[voucher.voucherType]++; @@ -3134,19 +3898,21 @@ export default class BattleScene extends SceneBase { gameMode: this.currentBattle ? this.gameMode.getName() : "Title", biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "", wave: this.currentBattle?.waveIndex ?? 0, - party: this.party ? this.party.map((p) => { - return { - name: p.name, - form: p.getFormKey(), - types: p.getTypes().map((type) => Type[type]), - teraType: Type[p.getTeraType()], - isTerastallized: p.isTerastallized, - level: p.level, - currentHP: p.hp, - maxHP: p.getMaxHp(), - status: p.status?.effect ? StatusEffect[p.status.effect] : "" - }; - }) : [], + party: this.party + ? this.party.map((p) => { + return { + name: p.name, + form: p.getFormKey(), + types: p.getTypes().map((type) => Type[type]), + teraType: Type[p.getTeraType()], + isTerastallized: p.isTerastallized, + level: p.level, + currentHP: p.hp, + maxHP: p.getMaxHp(), + status: p.status?.effect ? StatusEffect[p.status.effect] : "", + }; + }) + : [], modeChain: this.ui?.getModeChain() ?? [], }; (window as any).gameInfo = gameInfo; @@ -3180,26 +3946,44 @@ export default class BattleScene extends SceneBase { * @param pokemon The (enemy) pokemon */ initFinalBossPhaseTwo(pokemon: Pokemon): void { - if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { + if ( + pokemon instanceof EnemyPokemon && + pokemon.isBoss() && + !pokemon.formIndex && + pokemon.bossSegmentIndex < 1 + ) { this.fadeOutBgm(Utils.fixedInt(2000), false); - this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, undefined, () => { - const finalBossMBH = getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as TurnHeldItemTransferModifier; - finalBossMBH.setTransferrableFalse(); - this.addEnemyModifier(finalBossMBH, false, true); - pokemon.generateAndPopulateMoveset(1); - this.setFieldScale(0.75); - this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); - this.currentBattle.double = true; - const availablePartyMembers = this.getPlayerParty().filter((p) => p.isAllowedInBattle()); - if (availablePartyMembers.length > 1) { - this.pushPhase(new ToggleDoublePositionPhase(true)); - if (!availablePartyMembers[1].isOnField()) { - this.pushPhase(new SummonPhase(1)); + this.ui.showDialogue( + battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, + pokemon.species.name, + undefined, + () => { + const finalBossMBH = getModifierType( + modifierTypes.MINI_BLACK_HOLE, + ).newModifier(pokemon) as TurnHeldItemTransferModifier; + finalBossMBH.setTransferrableFalse(); + this.addEnemyModifier(finalBossMBH, false, true); + pokemon.generateAndPopulateMoveset(1); + this.setFieldScale(0.75); + this.triggerPokemonFormChange( + pokemon, + SpeciesFormChangeManualTrigger, + false, + ); + this.currentBattle.double = true; + const availablePartyMembers = this.getPlayerParty().filter((p) => + p.isAllowedInBattle(), + ); + if (availablePartyMembers.length > 1) { + this.pushPhase(new ToggleDoublePositionPhase(true)); + if (!availablePartyMembers[1].isOnField()) { + this.pushPhase(new SummonPhase(1)); + } } - } - this.shiftPhase(); - }); + this.shiftPhase(); + }, + ); return; } @@ -3213,33 +3997,62 @@ export default class BattleScene extends SceneBase { * @param useWaveIndexMultiplier Default false. If true, will multiply expValue by a scaling waveIndex multiplier. Not needed if expValue is already scaled by level/wave * @param pokemonParticipantIds Participants. If none are defined, no exp will be given. To spread evenly among the party, should pass all ids of party members. */ - applyPartyExp(expValue: number, pokemonDefeated: boolean, useWaveIndexMultiplier?: boolean, pokemonParticipantIds?: Set): void { - const participantIds = pokemonParticipantIds ?? this.currentBattle.playerParticipantIds; + applyPartyExp( + expValue: number, + pokemonDefeated: boolean, + useWaveIndexMultiplier?: boolean, + pokemonParticipantIds?: Set, + ): void { + const participantIds = + pokemonParticipantIds ?? this.currentBattle.playerParticipantIds; const party = this.getPlayerParty(); - const expShareModifier = this.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; - const multipleParticipantExpBonusModifier = this.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; - const nonFaintedPartyMembers = party.filter(p => p.hp); - const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.getMaxExpLevel()); + const expShareModifier = this.findModifier( + (m) => m instanceof ExpShareModifier, + ) as ExpShareModifier; + const expBalanceModifier = this.findModifier( + (m) => m instanceof ExpBalanceModifier, + ) as ExpBalanceModifier; + const multipleParticipantExpBonusModifier = this.findModifier( + (m) => m instanceof MultipleParticipantExpBonusModifier, + ) as MultipleParticipantExpBonusModifier; + const nonFaintedPartyMembers = party.filter((p) => p.hp); + const expPartyMembers = nonFaintedPartyMembers.filter( + (p) => p.level < this.getMaxExpLevel(), + ); const partyMemberExp: number[] = []; // EXP value calculation is based off Pokemon.getExpValue if (useWaveIndexMultiplier) { - expValue = Math.floor(expValue * this.currentBattle.waveIndex / 5 + 1); + expValue = Math.floor((expValue * this.currentBattle.waveIndex) / 5 + 1); } if (participantIds.size > 0) { - if (this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { + if ( + this.currentBattle.battleType === BattleType.TRAINER || + this.currentBattle.mysteryEncounter?.encounterMode === + MysteryEncounterMode.TRAINER_BATTLE + ) { expValue = Math.floor(expValue * 1.5); - } else if (this.currentBattle.isBattleMysteryEncounter() && this.currentBattle.mysteryEncounter) { - expValue = Math.floor(expValue * this.currentBattle.mysteryEncounter.expMultiplier); + } else if ( + this.currentBattle.isBattleMysteryEncounter() && + this.currentBattle.mysteryEncounter + ) { + expValue = Math.floor( + expValue * this.currentBattle.mysteryEncounter.expMultiplier, + ); } for (const partyMember of nonFaintedPartyMembers) { const pId = partyMember.id; const participated = participantIds.has(pId); if (participated && pokemonDefeated) { partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); - const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); - if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount()) { + const machoBraceModifier = partyMember + .getHeldItems() + .find((m) => m instanceof PokemonIncrementingStatModifier); + if ( + machoBraceModifier && + machoBraceModifier.stackCount < + machoBraceModifier.getMaxStackCount() + ) { machoBraceModifier.stackCount++; this.updateModifiers(true, true); partyMember.updateInfo(); @@ -3254,12 +4067,14 @@ export default class BattleScene extends SceneBase { } let expMultiplier = 0; if (participated) { - expMultiplier += (1 / participantIds.size); + expMultiplier += 1 / participantIds.size; if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { - expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; + expMultiplier += + multipleParticipantExpBonusModifier.getStackCount() * 0.2; } } else if (expShareModifier) { - expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; + expMultiplier += + (expShareModifier.getStackCount() * 0.2) / participantIds.size; } if (partyMember.pokerus) { expMultiplier *= 1.5; @@ -3268,7 +4083,12 @@ export default class BattleScene extends SceneBase { expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; } const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); - this.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + this.applyModifiers( + PokemonExpBoosterModifier, + true, + partyMember, + pokemonExp, + ); partyMemberExp.push(Math.floor(pokemonExp.value)); } @@ -3289,10 +4109,16 @@ export default class BattleScene extends SceneBase { } }); - const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length); + const splitExp = Math.floor( + totalExp / recipientExpPartyMemberIndexes.length, + ); expPartyMembers.forEach((_partyMember, pm) => { - partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount()); + partyMemberExp[pm] = Phaser.Math.Linear( + partyMemberExp[pm], + recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, + 0.2 * expBalanceModifier.getStackCount(), + ); }); } @@ -3301,7 +4127,11 @@ export default class BattleScene extends SceneBase { if (exp) { const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(partyMemberIndex, exp) : new ShowPartyExpBarPhase(partyMemberIndex, exp)); + this.unshiftPhase( + expPartyMembers[pm].isOnField() + ? new ExpPhase(partyMemberIndex, exp) + : new ShowPartyExpBarPhase(partyMemberIndex, exp), + ); } } } @@ -3312,9 +4142,19 @@ export default class BattleScene extends SceneBase { * Even if returns `true`, does not guarantee that a wave will actually be a ME. * That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead. */ - isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean { - const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); - return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave; + isMysteryEncounterValidForWave( + battleType: BattleType, + waveIndex: number, + ): boolean { + const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = + this.gameMode.getMysteryEncounterLegalWaves(); + return ( + this.gameMode.hasMysteryEncounters && + battleType === BattleType.WILD && + !this.gameMode.isBoss(waveIndex) && + waveIndex < highestMysteryEncounterWave && + waveIndex > lowestMysteryEncounterWave + ); } /** @@ -3324,31 +4164,56 @@ export default class BattleScene extends SceneBase { * @param newBattleType * @param waveIndex */ - private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean { - const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); + private isWaveMysteryEncounter( + newBattleType: BattleType, + waveIndex: number, + ): boolean { + const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = + this.gameMode.getMysteryEncounterLegalWaves(); if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) { // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor - const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; + const sessionEncounterRate = + this.mysteryEncounterSaveData.encounterSpawnChance; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; // If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well) // Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET // Favored rate changes can never exceed 50%. So if base rate is 15/256 and favored rate would add 200/256, result will be (15 + 128)/256 - const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (waveIndex - lowestMysteryEncounterWave); - const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length; - const favoredEncounterRate = sessionEncounterRate + Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2); + const expectedEncountersByFloor = + (AVERAGE_ENCOUNTERS_PER_RUN_TARGET / + (highestMysteryEncounterWave - lowestMysteryEncounterWave)) * + (waveIndex - lowestMysteryEncounterWave); + const currentRunDiffFromAvg = + expectedEncountersByFloor - encounteredEvents.length; + const favoredEncounterRate = + sessionEncounterRate + + Math.min( + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, + MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2, + ); - const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; + const successRate = isNullOrUndefined( + Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE, + ) + ? favoredEncounterRate + : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; // If the most recent ME was 3 or fewer waves ago, can never spawn a ME - const canSpawn = encounteredEvents.length === 0 || (waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); + const canSpawn = + encounteredEvents.length === 0 || + waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > + 3 || + !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); if (canSpawn) { let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; // Always rolls the check on the same offset to ensure no RNG changes from reloading session - this.executeWithSeedOffset(() => { - roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT); - }, waveIndex * 3 * 1000); + this.executeWithSeedOffset( + () => { + roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT); + }, + waveIndex * 3 * 1000, + ); return roll < successRate; } } @@ -3362,10 +4227,16 @@ export default class BattleScene extends SceneBase { * @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements * @returns */ - getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter { + getMysteryEncounter( + encounterType?: MysteryEncounterType, + canBypass?: boolean, + ): MysteryEncounter { // Loading override or session encounter let encounter: MysteryEncounter | null; - if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { + if ( + !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && + allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) + ) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; if (canBypass) { return encounter; @@ -3374,13 +4245,22 @@ export default class BattleScene extends SceneBase { encounter = allMysteryEncounters[encounterType ?? -1]; return encounter; } else { - encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; + encounter = !isNullOrUndefined(encounterType) + ? allMysteryEncounters[encounterType] + : null; } // Check for queued encounters first - if (!encounter && this.mysteryEncounterSaveData?.queuedEncounters && this.mysteryEncounterSaveData.queuedEncounters.length > 0) { + if ( + !encounter && + this.mysteryEncounterSaveData?.queuedEncounters && + this.mysteryEncounterSaveData.queuedEncounters.length > 0 + ) { let i = 0; - while (i < this.mysteryEncounterSaveData.queuedEncounters.length && !!encounter) { + while ( + i < this.mysteryEncounterSaveData.queuedEncounters.length && + !!encounter + ) { const candidate = this.mysteryEncounterSaveData.queuedEncounters[i]; const forcedChance = candidate.spawnPercent; if (Utils.randSeedInt(100) < forcedChance) { @@ -3398,34 +4278,56 @@ export default class BattleScene extends SceneBase { } // See Enum values for base tier weights - const tierWeights = [ MysteryEncounterTier.COMMON, MysteryEncounterTier.GREAT, MysteryEncounterTier.ULTRA, MysteryEncounterTier.ROGUE ]; + const tierWeights = [ + MysteryEncounterTier.COMMON, + MysteryEncounterTier.GREAT, + MysteryEncounterTier.ULTRA, + MysteryEncounterTier.ROGUE, + ]; // Adjust tier weights by previously encountered events to lower odds of only Common/Great in run - this.mysteryEncounterSaveData.encounteredEvents.forEach(seenEncounterData => { - if (seenEncounterData.tier === MysteryEncounterTier.COMMON) { - tierWeights[0] = tierWeights[0] - 6; - } else if (seenEncounterData.tier === MysteryEncounterTier.GREAT) { - tierWeights[1] = tierWeights[1] - 4; - } - }); + this.mysteryEncounterSaveData.encounteredEvents.forEach( + (seenEncounterData) => { + if (seenEncounterData.tier === MysteryEncounterTier.COMMON) { + tierWeights[0] = tierWeights[0] - 6; + } else if (seenEncounterData.tier === MysteryEncounterTier.GREAT) { + tierWeights[1] = tierWeights[1] - 4; + } + }, + ); const totalWeight = tierWeights.reduce((a, b) => a + b); const tierValue = Utils.randSeedInt(totalWeight); const commonThreshold = totalWeight - tierWeights[0]; const greatThreshold = totalWeight - tierWeights[0] - tierWeights[1]; - const ultraThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; - let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE; + const ultraThreshold = + totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; + let tier: MysteryEncounterTier | null = + tierValue > commonThreshold + ? MysteryEncounterTier.COMMON + : tierValue > greatThreshold + ? MysteryEncounterTier.GREAT + : tierValue > ultraThreshold + ? MysteryEncounterTier.ULTRA + : MysteryEncounterTier.ROGUE; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) { tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE; } let availableEncounters: MysteryEncounter[] = []; - const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ? - this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type - : null; - const disabledEncounters = this.eventManager.getEventMysteryEncountersDisabled(); - const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType)?.filter(enc => !disabledEncounters.includes(enc)) ?? []; + const previousEncounter = + this.mysteryEncounterSaveData.encounteredEvents.length > 0 + ? this.mysteryEncounterSaveData.encounteredEvents[ + this.mysteryEncounterSaveData.encounteredEvents.length - 1 + ].type + : null; + const disabledEncounters = + this.eventManager.getEventMysteryEncountersDisabled(); + const biomeMysteryEncounters = + mysteryEncountersByBiome + .get(this.arena.biomeType) + ?.filter((enc) => !disabledEncounters.includes(enc)) ?? []; // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available while (availableEncounters.length === 0 && tier !== null) { availableEncounters = biomeMysteryEncounters @@ -3434,34 +4336,57 @@ export default class BattleScene extends SceneBase { if (!encounterCandidate) { return false; } - if (this.eventManager.getMysteryEncounterTierForEvent(encounterType, encounterCandidate.encounterTier) !== tier) { + if ( + this.eventManager.getMysteryEncounterTierForEvent( + encounterType, + encounterCandidate.encounterTier, + ) !== tier + ) { return false; } const disallowedGameModes = encounterCandidate.disallowedGameModes; - if (disallowedGameModes && disallowedGameModes.length > 0 - && disallowedGameModes.includes(this.gameMode.modeId)) { + if ( + disallowedGameModes && + disallowedGameModes.length > 0 && + disallowedGameModes.includes(this.gameMode.modeId) + ) { return false; } if (this.gameMode.modeId === GameModes.CHALLENGE) { - const disallowedChallenges = encounterCandidate.disallowedChallenges; - if (disallowedChallenges && disallowedChallenges.length > 0 && this.gameMode.challenges.some(challenge => disallowedChallenges.includes(challenge.id))) { + const disallowedChallenges = + encounterCandidate.disallowedChallenges; + if ( + disallowedChallenges && + disallowedChallenges.length > 0 && + this.gameMode.challenges.some((challenge) => + disallowedChallenges.includes(challenge.id), + ) + ) { return false; } } if (!encounterCandidate.meetsRequirements()) { return false; } - if (previousEncounter !== null && encounterType === previousEncounter) { + if ( + previousEncounter !== null && + encounterType === previousEncounter + ) { return false; } - if (this.mysteryEncounterSaveData.encounteredEvents.length > 0 && - (encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0) - && this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters) { + if ( + this.mysteryEncounterSaveData.encounteredEvents.length > 0 && + encounterCandidate.maxAllowedEncounters && + encounterCandidate.maxAllowedEncounters > 0 && + this.mysteryEncounterSaveData.encounteredEvents.filter( + (e) => e.type === encounterType, + ).length >= encounterCandidate.maxAllowedEncounters + ) { return false; } return true; }) - .map((m) => (allMysteryEncounters[m])); + .map((m) => allMysteryEncounters[m]); // Decrement tier if (tier === MysteryEncounterTier.ROGUE) { tier = MysteryEncounterTier.ULTRA; @@ -3476,10 +4401,13 @@ export default class BattleScene extends SceneBase { // If absolutely no encounters are available, spawn 0th encounter if (availableEncounters.length === 0) { - console.log("No Mystery Encounters found, falling back to Mysterious Challengers."); + console.log( + "No Mystery Encounters found, falling back to Mysterious Challengers.", + ); return allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS]; } - encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)]; + encounter = + availableEncounters[Utils.randSeedInt(availableEncounters.length)]; // New encounter object to not dirty flags encounter = new MysteryEncounter(encounter); encounter.populateDialogueTokensFromRequirements(); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 74043e42f1d..f878cb40e2e 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -197,7 +197,7 @@ export class MoveEffectPhase extends PokemonPhase { && (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) && !targets[0]?.getTag(SemiInvulnerableTag); - const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected && targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT)); + const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected && targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT)); /** * If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target