From ec0aaaf4ca79654b6d10c74ab182592a652a04de Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Mon, 23 Sep 2024 16:11:38 -0400 Subject: [PATCH] stash current changes --- src/battle-scene.ts | 47 ++- src/data/battle-anims.ts | 91 ++++-- src/default-assets.ts | 157 ++++++++++ src/field/pokemon.ts | 31 +- src/loading-scene.ts | 391 ++++++++---------------- src/main.ts | 3 + src/phases/new-biome-encounter-phase.ts | 2 +- src/phases/next-encounter-phase.ts | 2 + src/scene-base.ts | 111 ++++++- 9 files changed, 517 insertions(+), 318 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f55f1658648..3e7335a1620 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -6,7 +6,7 @@ import { Constructor, isNullOrUndefined } from "#app/utils"; import * as Utils from "./utils"; import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { PokeballType } from "./data/pokeball"; -import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims"; +import { getAnimAssetKeys, getCommonAnimConfigs, getMoveAnimConfigs, initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims"; import { Phase } from "./phase"; import { initGameSpeed } from "./system/game-speed"; import { Arena, ArenaBase } from "./field/arena"; @@ -336,7 +336,7 @@ 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, this.getCachedUrl(`images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`), this.getCachedUrl(`images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`)); } async preload() { @@ -2207,6 +2207,49 @@ export default class BattleScene extends SceneBase { return 0; } + clearUnusedAssets() { + // Get manifest of assets that are used by the player's party Pokemon or general-use + // These should not be unloaded + const backgrounds = new Set(); + const sounds = new Set(); + const graphics = new Set(); + + // Player Pokemon assets and Enemy Pokemon assets for upcoming battle + const allPokemon: Pokemon[] = [...this.getParty(), ...this.getEnemyParty()]; + for (const p of allPokemon) { + // Move assets + const animConfigs = getMoveAnimConfigs(p.getMoveset().filter(m => !!m).map(m => m.getMove().id)); + getAnimAssetKeys(animConfigs, sounds, backgrounds, graphics); + + // Cry + sounds.add(`cry/${p.getSpeciesForm().getCryKey(p.formIndex)}`); + + // Sprite assets + let spriteKey = p.getSpeciesForm().getSpriteKey(p.getGender() === Gender.FEMALE, p.formIndex, p.shiny, p.variant); + graphics.add(spriteKey); + let spriteBackKey = spriteKey.replace("pkmn__", "pkmn__back__"); + graphics.add(spriteBackKey); + if (p.getFusionSpeciesForm()) { + spriteKey = p.getFusionSpeciesForm().getSpriteKey(p.getGender() === Gender.FEMALE, p.formIndex, p.shiny, p.variant); + graphics.add(spriteKey); + spriteBackKey = spriteKey.replace("pkmn__", "pkmn__back__"); + graphics.add(spriteBackKey); + } + } + + // Common Move assets + getAnimAssetKeys(getCommonAnimConfigs(), sounds, backgrounds, graphics); + + // Current Biome BGM + sounds.add(this.arena.bgm); + + // Rival sprites + graphics.add("rival_m"); + graphics.add("rival_f"); + + this.clearAssets(sounds, backgrounds, graphics); + } + toggleInvert(invert: boolean): void { if (invert) { this.cameras.main.setPostPipeline(InvertPostFX); diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index eb0dce3bf0c..c0cbacb2e42 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -620,10 +620,15 @@ function populateMoveChargeAnim(chargeAnim: ChargeAnim, animSource: any) { export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise { return new Promise(resolve => { - loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve()); + const animConfigs = getCommonAnimConfigs(); + loadAnimAssets(scene, animConfigs, startLoad).then(() => resolve()); }); } +export function getCommonAnimConfigs() { + return Array.from(commonAnims.values()); +} + /** * Loads encounter animation assets to scene * MUST be called after {@linkcode initEncounterAnims()} to load all required animations properly @@ -634,51 +639,46 @@ export async function loadEncounterAnimAssets(scene: BattleScene, startLoad?: bo await loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad); } -export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise { - return new Promise(resolve => { - const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); - for (const moveId of moveIds) { - const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] - || allMoves[moveId].getAttrs(DelayedAttackAttr)[0] - || allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]; - if (chargeAttr) { - const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); - moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? - if (Array.isArray(moveChargeAnims)) { - moveAnimations.push(moveChargeAnims[1]); - } +export async function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise { + const moveAnimations = getMoveAnimConfigs(moveIds); + await loadAnimAssets(scene, moveAnimations, startLoad); +} + +export function getMoveAnimConfigs(moveIds: Moves[]): AnimConfig[] { + const moveAnimations: AnimConfig[] = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); + for (const moveId of moveIds) { + const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] + || allMoves[moveId].getAttrs(DelayedAttackAttr)[0] + || allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]; + if (chargeAttr) { + const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); + moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? + if (Array.isArray(moveChargeAnims)) { + moveAnimations.push(moveChargeAnims[1]); } } - loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve()); - }); + } + + return moveAnimations; } function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boolean): Promise { return new Promise(resolve => { const backgrounds = new Set(); const sounds = new Set(); - for (const a of anims) { - if (!a.frames?.length) { - continue; - } - const animSounds = a.getSoundResourceNames(); - for (const ms of animSounds) { - sounds.add(ms); - } - const animBackgrounds = a.getBackgroundResourceNames(); - for (const abg of animBackgrounds) { - backgrounds.add(abg); - } - if (a.graphic) { - scene.loadSpritesheet(a.graphic, "battle_anims", 96); - } - } + const graphics = new Set(); + + getAnimAssetKeys(anims, sounds, backgrounds, graphics); + for (const bg of backgrounds) { scene.loadImage(bg, "battle_anims"); } for (const s of sounds) { scene.loadSe(s, "battle_anims", s); } + for (const g of graphics) { + scene.loadSpritesheet(g, "battle_anims", 96); + } if (startLoad) { scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); if (!scene.load.isLoading()) { @@ -690,6 +690,33 @@ function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boo }); } +/** + * Gets the asset keys for the specified {@linkcode AnimConfig}s. + * Passes the keys by reference via `backgrounds` and `sounds`. + * @param anims + * @param backgrounds Returns Set of image keys passed by reference + * @param sounds Returns Set of sound effect keys passed by reference + * @param graphics Returns Set of graphics keys passed by reference + */ +export function getAnimAssetKeys(anims: AnimConfig[], sounds: Set, backgrounds: Set, graphics: Set): void { + for (const a of anims) { + if (!a || !a.frames?.length) { + continue; + } + const animSounds = a.getSoundResourceNames(); + for (const ms of animSounds) { + sounds.add(ms); + } + const animBackgrounds = a.getBackgroundResourceNames(); + for (const abg of animBackgrounds) { + backgrounds.add(abg); + } + if (a.graphic) { + graphics.add(a.graphic); + } + } +} + interface GraphicFrameData { x: number, y: number, diff --git a/src/default-assets.ts b/src/default-assets.ts index 007398de5c5..10c8e37e3c2 100644 --- a/src/default-assets.ts +++ b/src/default-assets.ts @@ -59,3 +59,160 @@ export const allDefaultSeAssets: Map = new Map = new Map([ + ["loading_bg", "arenas"], + ["logo", ""], + ["candy", "ui"], + ["candy_overlay", "ui"], + ["cursor", "ui"], + ["cursor_reverse", "ui"], + ["pbinfo_player", "ui"], + ["pbinfo_player_stats", "ui"], + ["pbinfo_player_mini", "ui"], + ["pbinfo_player_mini_stats", "ui"], + ["pbinfo_enemy_mini", "ui"], + ["pbinfo_enemy_mini_stats", "ui"], + ["pbinfo_enemy_boss", "ui"], + ["pbinfo_enemy_boss_stats", "ui"], + ["overlay_lv", "ui"], + ["overlay_exp", "ui"], + ["icon_owned", "ui"], + ["icon_egg_move", "ui"], + ["ability_bar_left", "ui"], + ["bgm_bar", "ui"], + ["party_exp_bar", "ui"], + ["achv_bar", "ui"], + ["achv_bar_2", "ui"], + ["achv_bar_3", "ui"], + ["achv_bar_4", "ui"], + ["achv_bar_5", "ui"], + ["shiny_star", ["ui", "shiny.png"]], + ["shiny_star_1", ["ui", "shiny_1.png"]], + ["shiny_star_2", ["ui", "shiny_2.png"]], + ["shiny_star_small", ["ui", "shiny_small.png"]], + ["shiny_star_small_1", ["ui", "shiny_small_1.png"]], + ["shiny_star_small_2", ["ui", "shiny_small_2.png"]], + ["favorite", ["ui", "favorite.png"]], + ["passive_bg", ["ui", "passive_bg.png"]], + ["ha_capsule", ["ui", "ha_capsule.png"]], + ["champion_ribbon", ["ui", "champion_ribbon.png"]], + ["icon_spliced", "ui"], + ["icon_lock", ["ui", "icon_lock.png"]], + ["icon_stop", ["ui", "icon_stop.png"]], + ["icon_tera", "ui"], + ["type_tera", "ui"], + ["dawn_icon_fg", "ui"], + ["dawn_icon_mg", "ui"], + ["dawn_icon_bg", "ui"], + ["day_icon_fg", "ui"], + ["day_icon_mg", "ui"], + ["day_icon_bg", "ui"], + ["dusk_icon_fg", "ui"], + ["dusk_icon_mg", "ui"], + ["dusk_icon_bg", "ui"], + ["night_icon_fg", "ui"], + ["night_icon_mg", "ui"], + ["night_icon_bg", "ui"], + ["pb_tray_overlay_player", "ui"], + ["pb_tray_overlay_enemy", "ui"], + ["party_bg", "ui"], + ["party_bg_double", "ui"], + ["party_slot_overlay_lv", "ui"], + ["party_slot_hp_bar", "ui"], + ["summary_bg", "ui"], + ["summary_overlay_shiny", "ui"], + ["summary_profile", "ui"], + ["summary_profile_prompt_z", "ui"], // The pixel Z button prompt + ["summary_profile_prompt_a", "ui"], // The pixel A button prompt + ["summary_profile_ability", "ui"], // Pixel text 'ABILITY' + ["summary_profile_passive", "ui"], // Pixel text 'PASSIVE' + ["summary_status", "ui"], + ["summary_stats", "ui"], + ["summary_stats_overlay_exp", "ui"], + ["summary_moves", "ui"], + ["summary_moves_effect", "ui"], + ["summary_moves_overlay_row", "ui"], + ["summary_moves_overlay_pp", "ui"], + ["scroll_bar", "ui"], + ["scroll_bar_handle", "ui"], + ["starter_container_bg", "ui"], + ["starter_select_bg", "ui"], + ["select_cursor", "ui"], + ["select_cursor_highlight", "ui"], + ["select_cursor_highlight_thick", "ui"], + ["select_cursor_pokerus", "ui"], + ["select_gen_cursor", "ui"], + ["select_gen_cursor_highlight", "ui"], + ["saving_icon", "ui"], + ["discord", "ui"], + ["google", "ui"], + ["settings_icon", "ui"], + ["default_bg", "arenas"], + ["pkmn__back__sub", ["pokemon/back", "sub.png"]], + ["pkmn__sub", ["pokemon", "sub.png"]], + ["tera", "effects"], + ["evo_sparkle", "effects"], + ["gacha_glass", "egg"], + ["gacha_eggs", "egg"], + ["gacha_knob", "egg"], + ["egg_list_bg", "ui"], + ["egg_summary_bg", "ui"], + ["end_m", "cg"], + ["end_f", "cg"], + ["encounter_radar", "mystery-encounters"], +]); + +export const allDefaultAtlasAssets: Map = new Map([ + ["bg", "ui"], + ["prompt", "ui"], + ["namebox", "ui"], + ["pbinfo_player_type", "ui"], + ["pbinfo_player_type1", "ui"], + ["pbinfo_player_type2", "ui"], + ["pbinfo_enemy_type", "ui"], + ["pbinfo_enemy_type1", "ui"], + ["pbinfo_enemy_type2", "ui"], + ["pbinfo_stat", "ui"], + ["pbinfo_stat_numbers", "ui"], + ["numbers", "ui"], + ["numbers_red", "ui"], + ["overlay_hp", "ui"], + ["overlay_hp_boss", "ui"], + ["shiny_icons", "ui"], + ["type_bgs", "ui"], + ["pb_tray_ball", "ui"], + ["party_slot_main", "ui"], + ["party_slot", "ui"], + ["party_slot_hp_overlay", "ui"], + ["party_pb", "ui"], + ["party_cancel", "ui"], + ["summary_moves_cursor", "ui"], + // Load character sprites + ["trainer_m_back", "trainer"], + ["trainer_m_back_pb", "trainer"], + ["trainer_f_back", "trainer"], + ["trainer_f_back_pb", "trainer"], + ["c_rival_m", ["character", "rival_m"]], + ["c_rival_f", ["character", "rival_f"]], + // Load pokemon-related images + ["battle_stats", "effects"], + ["shiny", "effects"], + ["shiny_2", "effects"], + ["shiny_3", "effects"], + ["pb_particles", "effects"], + ["tera_sparkle", "effects"], + ["pb", ""], + ["items", ""], + ["types", ""], + ["statuses", ""], + ["categories", ""], + ["egg", "egg"], + ["egg_crack", "egg"], + ["egg_icons", "egg"], + ["egg_shard", "egg"], + ["egg_lightrays", "egg"], + ["gacha_hatch", "egg"], + ["dualshock", "inputs"], + ["xbox", "inputs"], + ["keyboard", "inputs"], +]); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e17272cd955..367943a3432 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1,36 +1,35 @@ import Phaser from "phaser"; import BattleScene, { AnySound } from "../battle-scene"; -import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; -import { variantData } from "#app/data/variant"; -import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; -import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; +import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant"; +import BattleInfo, { EnemyBattleInfo, PlayerBattleInfo } from "../ui/battle-info"; +import Move, { allMoves, applyMoveAttrs, AttackMove, BypassBurnDamageReductionAttr, ChargeAttr, CounterDamageAttr, CritOnlyAttr, FixedDamageAttr, getMoveTargets, HighCritAttr, HitsTagAttr, IgnoreOpponentStatStagesAttr, IgnoreWeatherTypeDebuffAttr, ModifiedDamageAttr, MoveCategory, MoveTarget, OneHitKOAccuracyAttr, OneHitKOAttr, RechargeAttr, RespectAttackTypeImmunityAttr, SacrificialAttr, SacrificialAttrOnHit, StatStageChangeAttr, TypelessAttr, VariableAtkAttr, VariableDefAttr, VariableMoveCategoryAttr, VariableMoveTypeAttr, VariableMoveTypeMultiplierAttr } from "../data/move"; +import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, PokemonSpeciesForm, SpeciesFormKey, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "../utils"; -import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; +import { getTypeDamageMultiplier, getTypeRgb, Type, TypeDamageMultiplier } from "../data/type"; import { getLevelTotalExp } from "../data/exp"; -import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; -import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier } from "../modifier/modifier"; +import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, PERMANENT_STATS, type PermanentStat, Stat } from "#enums/stat"; +import { BaseStatModifier, CritBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EvoTrackerModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonIncrementingStatModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, StatBoosterModifier, SurviveDamageModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeModifier } from "../modifier/modifier"; import { PokeballType } from "../data/pokeball"; import { Gender } from "../data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; -import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; -import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; -import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } from "../data/battler-tags"; +import { getRandomStatus, Status, StatusEffect } from "../data/status-effect"; +import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions, SpeciesEvolutionCondition, SpeciesFormEvolution } from "../data/pokemon-evolutions"; +import { reverseCompatibleTms, tmPoolTiers, tmSpecies } from "../data/tms"; +import { BattlerTag, BattlerTagLapseType, CritBoostTag, DragonCheerTag, EncoreTag, ExposedTag, getBattlerTag, GroundedTag, HighestStatBoostTag, MoveRestrictionBattlerTag, SemiInvulnerableTag, SubstituteTag, TarShotTag, TrappedTag, TypeBoostTag, TypeImmuneTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability"; +import { AbAttr, Ability, AddSecondStrikeAbAttr, allAbilities, applyAbAttrs, applyCheckTrappedAbAttrs, applyFieldStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, applyStatMultiplierAbAttrs, BattlerTagImmunityAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, CheckTrappedAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMultiplyStatAbAttr, FieldPriorityMoveImmunityAbAttr, FullHpResistTypeAbAttr, IgnoreOpponentStatStagesAbAttr, IgnoreTypeImmunityAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, MoveImmunityAbAttr, MoveTypeChangeAbAttr, MultCritAbAttr, NoFusionAbilityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatMultiplierAbAttr, StatusEffectImmunityAbAttr, SuppressFieldAbilitiesAbAttr, TypeImmunityAbAttr, UnsuppressableAbilityAbAttr, UserFieldBattlerTagImmunityAbAttr, UserFieldStatusEffectImmunityAbAttr, WeightMultiplierAbAttr } from "../data/ability"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; import { Mode } from "../ui/ui"; import PartyUiHandler, { PartyOption, PartyUiMode } from "../ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { LevelMoves } from "../data/pokemon-level-moves"; -import { DamageAchv, achvs } from "../system/achv"; +import { achvs, DamageAchv } from "../system/achv"; import { DexAttr, StarterDataEntry, StarterMoveset } from "../system/game-data"; -import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; -import { Nature, getNatureStatMultiplier } from "../data/nature"; +import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; +import { getNatureStatMultiplier, Nature } from "../data/nature"; import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "../data/pokemon-forms"; import { TerrainType } from "../data/terrain"; import { TrainerSlot } from "../data/trainer-config"; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index a7f37fda9ed..f270a449e0b 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -1,5 +1,4 @@ import { GachaType } from "./enums/gacha-types"; -import { trainerConfigs } from "./data/trainer-config"; import { getBiomeHasProps } from "./field/arena"; import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin"; import { SceneBase } from "./scene-base"; @@ -21,9 +20,8 @@ import i18next from "i18next"; import { initStatsKeys } from "./ui/game-stats-ui-handler"; import { initVouchers } from "./system/voucher"; import { Biome } from "#enums/biome"; -import { TrainerType } from "#enums/trainer-type"; import {initMysteryEncounters} from "#app/data/mystery-encounters/mystery-encounters"; -import { allDefaultBgmAssets, allDefaultSeAssets } from "#app/default-assets"; +import { allDefaultAtlasAssets, allDefaultBgmAssets, allDefaultImageAssets, allDefaultSeAssets } from "#app/default-assets"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -38,266 +36,10 @@ export class LoadingScene extends SceneBase { } preload() { + this.logPartitionedStorageSize(); Utils.localPing(); this.load["manifest"] = this.game["manifest"]; - this.loadImage("loading_bg", "arenas"); - this.loadImage("logo", ""); - - // Load menu images - this.loadAtlas("bg", "ui"); - this.loadAtlas("prompt", "ui"); - this.loadImage("candy", "ui"); - this.loadImage("candy_overlay", "ui"); - this.loadImage("cursor", "ui"); - this.loadImage("cursor_reverse", "ui"); - for (const wv of Utils.getEnumValues(WindowVariant)) { - for (let w = 1; w <= 5; w++) { - this.loadImage(`window_${w}${getWindowVariantSuffix(wv)}`, "ui/windows"); - } - } - this.loadAtlas("namebox", "ui"); - this.loadImage("pbinfo_player", "ui"); - this.loadImage("pbinfo_player_stats", "ui"); - this.loadImage("pbinfo_player_mini", "ui"); - this.loadImage("pbinfo_player_mini_stats", "ui"); - this.loadAtlas("pbinfo_player_type", "ui"); - this.loadAtlas("pbinfo_player_type1", "ui"); - this.loadAtlas("pbinfo_player_type2", "ui"); - this.loadImage("pbinfo_enemy_mini", "ui"); - this.loadImage("pbinfo_enemy_mini_stats", "ui"); - this.loadImage("pbinfo_enemy_boss", "ui"); - this.loadImage("pbinfo_enemy_boss_stats", "ui"); - this.loadAtlas("pbinfo_enemy_type", "ui"); - this.loadAtlas("pbinfo_enemy_type1", "ui"); - this.loadAtlas("pbinfo_enemy_type2", "ui"); - this.loadAtlas("pbinfo_stat", "ui"); - this.loadAtlas("pbinfo_stat_numbers", "ui"); - this.loadImage("overlay_lv", "ui"); - this.loadAtlas("numbers", "ui"); - this.loadAtlas("numbers_red", "ui"); - this.loadAtlas("overlay_hp", "ui"); - this.loadAtlas("overlay_hp_boss", "ui"); - this.loadImage("overlay_exp", "ui"); - this.loadImage("icon_owned", "ui"); - this.loadImage("icon_egg_move", "ui"); - this.loadImage("ability_bar_left", "ui"); - this.loadImage("bgm_bar", "ui"); - this.loadImage("party_exp_bar", "ui"); - this.loadImage("achv_bar", "ui"); - this.loadImage("achv_bar_2", "ui"); - this.loadImage("achv_bar_3", "ui"); - this.loadImage("achv_bar_4", "ui"); - this.loadImage("achv_bar_5", "ui"); - this.loadImage("shiny_star", "ui", "shiny.png"); - this.loadImage("shiny_star_1", "ui", "shiny_1.png"); - this.loadImage("shiny_star_2", "ui", "shiny_2.png"); - this.loadImage("shiny_star_small", "ui", "shiny_small.png"); - this.loadImage("shiny_star_small_1", "ui", "shiny_small_1.png"); - this.loadImage("shiny_star_small_2", "ui", "shiny_small_2.png"); - this.loadImage("favorite", "ui", "favorite.png"); - this.loadImage("passive_bg", "ui", "passive_bg.png"); - this.loadAtlas("shiny_icons", "ui"); - this.loadImage("ha_capsule", "ui", "ha_capsule.png"); - this.loadImage("champion_ribbon", "ui", "champion_ribbon.png"); - this.loadImage("icon_spliced", "ui"); - this.loadImage("icon_lock", "ui", "icon_lock.png"); - this.loadImage("icon_stop", "ui", "icon_stop.png"); - this.loadImage("icon_tera", "ui"); - this.loadImage("type_tera", "ui"); - this.loadAtlas("type_bgs", "ui"); - - this.loadImage("dawn_icon_fg", "ui"); - this.loadImage("dawn_icon_mg", "ui"); - this.loadImage("dawn_icon_bg", "ui"); - this.loadImage("day_icon_fg", "ui"); - this.loadImage("day_icon_mg", "ui"); - this.loadImage("day_icon_bg", "ui"); - this.loadImage("dusk_icon_fg", "ui"); - this.loadImage("dusk_icon_mg", "ui"); - this.loadImage("dusk_icon_bg", "ui"); - this.loadImage("night_icon_fg", "ui"); - this.loadImage("night_icon_mg", "ui"); - this.loadImage("night_icon_bg", "ui"); - - this.loadImage("pb_tray_overlay_player", "ui"); - this.loadImage("pb_tray_overlay_enemy", "ui"); - this.loadAtlas("pb_tray_ball", "ui"); - - this.loadImage("party_bg", "ui"); - this.loadImage("party_bg_double", "ui"); - this.loadAtlas("party_slot_main", "ui"); - this.loadAtlas("party_slot", "ui"); - this.loadImage("party_slot_overlay_lv", "ui"); - this.loadImage("party_slot_hp_bar", "ui"); - this.loadAtlas("party_slot_hp_overlay", "ui"); - this.loadAtlas("party_pb", "ui"); - this.loadAtlas("party_cancel", "ui"); - - this.loadImage("summary_bg", "ui"); - this.loadImage("summary_overlay_shiny", "ui"); - this.loadImage("summary_profile", "ui"); - this.loadImage("summary_profile_prompt_z", "ui"); // The pixel Z button prompt - this.loadImage("summary_profile_prompt_a", "ui"); // The pixel A button prompt - this.loadImage("summary_profile_ability", "ui"); // Pixel text 'ABILITY' - this.loadImage("summary_profile_passive", "ui"); // Pixel text 'PASSIVE' - this.loadImage("summary_status", "ui"); - this.loadImage("summary_stats", "ui"); - this.loadImage("summary_stats_overlay_exp", "ui"); - this.loadImage("summary_moves", "ui"); - this.loadImage("summary_moves_effect", "ui"); - this.loadImage("summary_moves_overlay_row", "ui"); - this.loadImage("summary_moves_overlay_pp", "ui"); - this.loadAtlas("summary_moves_cursor", "ui"); - for (let t = 1; t <= 3; t++) { - this.loadImage(`summary_tabs_${t}`, "ui"); - } - - this.loadImage("scroll_bar", "ui"); - this.loadImage("scroll_bar_handle", "ui"); - this.loadImage("starter_container_bg", "ui"); - this.loadImage("starter_select_bg", "ui"); - this.loadImage("select_cursor", "ui"); - this.loadImage("select_cursor_highlight", "ui"); - this.loadImage("select_cursor_highlight_thick", "ui"); - this.loadImage("select_cursor_pokerus", "ui"); - this.loadImage("select_gen_cursor", "ui"); - this.loadImage("select_gen_cursor_highlight", "ui"); - - this.loadImage("saving_icon", "ui"); - this.loadImage("discord", "ui"); - this.loadImage("google", "ui"); - this.loadImage("settings_icon", "ui"); - - this.loadImage("default_bg", "arenas"); - // Load arena images - Utils.getEnumValues(Biome).map(bt => { - const btKey = Biome[bt].toLowerCase(); - const isBaseAnimated = btKey === "end"; - const baseAKey = `${btKey}_a`; - const baseBKey = `${btKey}_b`; - this.loadImage(`${btKey}_bg`, "arenas"); - if (!isBaseAnimated) { - this.loadImage(baseAKey, "arenas"); - } else { - this.loadAtlas(baseAKey, "arenas"); - } - if (!isBaseAnimated) { - this.loadImage(baseBKey, "arenas"); - } else { - this.loadAtlas(baseBKey, "arenas"); - } - if (getBiomeHasProps(bt)) { - for (let p = 1; p <= 3; p++) { - const isPropAnimated = p === 3 && [ "power_plant", "end" ].find(b => b === btKey); - const propKey = `${btKey}_b_${p}`; - if (!isPropAnimated) { - this.loadImage(propKey, "arenas"); - } else { - this.loadAtlas(propKey, "arenas"); - } - } - } - }); - - // Load bitmap fonts - this.load.bitmapFont("item-count", "fonts/item-count.png", "fonts/item-count.xml"); - - // Load trainer images - this.loadAtlas("trainer_m_back", "trainer"); - this.loadAtlas("trainer_m_back_pb", "trainer"); - this.loadAtlas("trainer_f_back", "trainer"); - this.loadAtlas("trainer_f_back_pb", "trainer"); - - Utils.getEnumValues(TrainerType).map(tt => { - const config = trainerConfigs[tt]; - this.loadAtlas(config.getSpriteKey(), "trainer"); - if (config.doubleOnly || config.hasDouble) { - this.loadAtlas(config.getSpriteKey(true), "trainer"); - } - }); - - // Load character sprites - this.loadAtlas("c_rival_m", "character", "rival_m"); - this.loadAtlas("c_rival_f", "character", "rival_f"); - - // Load pokemon-related images - this.loadImage("pkmn__back__sub", "pokemon/back", "sub.png"); - this.loadImage("pkmn__sub", "pokemon", "sub.png"); - this.loadAtlas("battle_stats", "effects"); - this.loadAtlas("shiny", "effects"); - this.loadAtlas("shiny_2", "effects"); - this.loadAtlas("shiny_3", "effects"); - this.loadImage("tera", "effects"); - this.loadAtlas("pb_particles", "effects"); - this.loadImage("evo_sparkle", "effects"); - this.loadAtlas("tera_sparkle", "effects"); - this.load.video("evo_bg", "images/effects/evo_bg.mp4", true); - - this.loadAtlas("pb", ""); - this.loadAtlas("items", ""); - this.loadAtlas("types", ""); - - // Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_ - const lang = i18next.resolvedLanguage; - if (lang !== "en") { - if (Utils.verifyLang(lang)) { - this.loadAtlas(`statuses_${lang}`, ""); - this.loadAtlas(`types_${lang}`, ""); - } else { - // Fallback to English - this.loadAtlas("statuses", ""); - this.loadAtlas("types", ""); - } - } else { - this.loadAtlas("statuses", ""); - this.loadAtlas("types", ""); - } - const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"]; - if (lang && availableLangs.includes(lang)) { - this.loadImage("egg-update_"+lang, "events"); - } else { - this.loadImage("egg-update_en", "events"); - } - - this.loadAtlas("statuses", ""); - this.loadAtlas("categories", ""); - - this.loadAtlas("egg", "egg"); - this.loadAtlas("egg_crack", "egg"); - this.loadAtlas("egg_icons", "egg"); - this.loadAtlas("egg_shard", "egg"); - this.loadAtlas("egg_lightrays", "egg"); - Utils.getEnumKeys(GachaType).forEach(gt => { - const key = gt.toLowerCase(); - this.loadImage(`gacha_${key}`, "egg"); - this.loadAtlas(`gacha_underlay_${key}`, "egg"); - }); - this.loadImage("gacha_glass", "egg"); - this.loadImage("gacha_eggs", "egg"); - this.loadAtlas("gacha_hatch", "egg"); - this.loadImage("gacha_knob", "egg"); - - this.loadImage("egg_list_bg", "ui"); - this.loadImage("egg_summary_bg", "ui"); - - this.loadImage("end_m", "cg"); - this.loadImage("end_f", "cg"); - - for (let i = 0; i < 10; i++) { - this.loadAtlas(`pokemon_icons_${i}`, ""); - if (i) { - this.loadAtlas(`pokemon_icons_${i}v`, ""); - } - } - - // Load Mystery Encounter dex progress icon - this.loadImage("encounter_radar", "mystery-encounters"); - - this.loadAtlas("dualshock", "inputs"); - this.loadAtlas("xbox", "inputs"); - this.loadAtlas("keyboard", "inputs"); - for (const key of allDefaultSeAssets.keys()) { this.loadSe(key, allDefaultSeAssets.get(key)); } @@ -306,6 +48,132 @@ export class LoadingScene extends SceneBase { this.loadBgm(key, allDefaultBgmAssets.get(key)); } + for (const key of allDefaultImageAssets.keys()) { + const val = allDefaultImageAssets.get(key); + if (val instanceof Array) { + this.loadImage(key, val[0], val[1]); + } else if (val !== undefined) { + this.loadImage(key, val); + } + } + + for (const key of allDefaultAtlasAssets.keys()) { + const val = allDefaultAtlasAssets.get(key); + if (val instanceof Array) { + this.loadAtlas(key, val[0], val[1]); + } else if (val !== undefined) { + this.loadAtlas(key, val); + } + } + + // UI windows + for (const wv of Utils.getEnumValues(WindowVariant)) { + for (let w = 1; w <= 5; w++) { + const key = `window_${w}${getWindowVariantSuffix(wv)}`; + allDefaultImageAssets.set(key, "ui/windows"); + this.loadImage(key, "ui/windows"); + } + } + + // Summary screen tabs + for (let t = 1; t <= 3; t++) { + const key = `summary_tabs_${t}`; + allDefaultImageAssets.set(key, "ui"); + this.loadImage(key, "ui"); + } + + // Load arena images + Utils.getEnumValues(Biome).map(bt => { + const btKey = Biome[bt].toLowerCase(); + const isBaseAnimated = btKey === "end"; + const baseAKey = `${btKey}_a`; + const baseBKey = `${btKey}_b`; + allDefaultImageAssets.set(`${btKey}_bg`, "arenas"); + this.loadImage(`${btKey}_bg`, "arenas"); + if (!isBaseAnimated) { + allDefaultImageAssets.set(baseAKey, "arenas"); + this.loadImage(baseAKey, "arenas"); + } else { + allDefaultAtlasAssets.set(baseAKey, "arenas"); + this.loadAtlas(baseAKey, "arenas"); + } + if (!isBaseAnimated) { + allDefaultImageAssets.set(baseBKey, "arenas"); + this.loadImage(baseBKey, "arenas"); + } else { + allDefaultAtlasAssets.set(baseBKey, "arenas"); + this.loadAtlas(baseBKey, "arenas"); + } + if (getBiomeHasProps(bt)) { + for (let p = 1; p <= 3; p++) { + const isPropAnimated = p === 3 && [ "power_plant", "end" ].find(b => b === btKey); + const propKey = `${btKey}_b_${p}`; + if (!isPropAnimated) { + allDefaultImageAssets.set(propKey, "arenas"); + this.loadImage(propKey, "arenas"); + } else { + allDefaultAtlasAssets.set(propKey, "arenas"); + this.loadAtlas(propKey, "arenas"); + } + } + } + }); + + // Load bitmap fonts + allDefaultAtlasAssets.set("item-count", ""); + this.load.bitmapFont("item-count", "fonts/item-count.png", "fonts/item-count.xml"); + + this.load.video("evo_bg", "images/effects/evo_bg.mp4", true); + + // Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_ + const lang = i18next.resolvedLanguage; + if (lang !== "en") { + if (Utils.verifyLang(lang)) { + allDefaultAtlasAssets.set(`statuses_${lang}`, ""); + allDefaultAtlasAssets.set(`types_${lang}`, ""); + this.loadAtlas(`statuses_${lang}`, ""); + this.loadAtlas(`types_${lang}`, ""); + } else { + // Fallback to English + allDefaultAtlasAssets.set("statuses", ""); + allDefaultAtlasAssets.set("types", ""); + this.loadAtlas("statuses", ""); + this.loadAtlas("types", ""); + } + } else { + allDefaultAtlasAssets.set("statuses", ""); + allDefaultAtlasAssets.set("types", ""); + this.loadAtlas("statuses", ""); + this.loadAtlas("types", ""); + } + const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"]; + if (lang && availableLangs.includes(lang)) { + const key = "egg-update_"+lang; + allDefaultImageAssets.set(key, "events"); + this.loadImage(key, "events"); + } else { + allDefaultImageAssets.set("egg-update_en", "events"); + this.loadImage("egg-update_en", "events"); + } + + // Gacha UI + Utils.getEnumKeys(GachaType).forEach(gt => { + const key = gt.toLowerCase(); + allDefaultImageAssets.set(`gacha_${key}`, "egg"); + allDefaultAtlasAssets.set(`gacha_underlay_${key}`, "egg"); + this.loadImage(`gacha_${key}`, "egg"); + this.loadAtlas(`gacha_underlay_${key}`, "egg"); + }); + + for (let i = 0; i < 10; i++) { + allDefaultAtlasAssets.set(`pokemon_icons_${i}`, ""); + this.loadAtlas(`pokemon_icons_${i}`, ""); + if (i) { + allDefaultAtlasAssets.set(`pokemon_icons_${i}v`, ""); + this.loadAtlas(`pokemon_icons_${i}v`, ""); + } + } + this.load.plugin("rextexteditplugin", "https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rextexteditplugin.min.js", true); this.loadLoadingScreen(); @@ -323,6 +191,9 @@ export class LoadingScene extends SceneBase { initAbilities(); initChallenges(); initMysteryEncounters(); + + this.logLocalStorageSize(); + this.logSessionStorageSize(); } loadLoadingScreen() { diff --git a/src/main.ts b/src/main.ts index b5f813bdf2f..2570b351c39 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,6 +33,8 @@ const config: Phaser.Types.Core.GameConfig = { height: 1080, mode: Phaser.Scale.FIT }, + /** Unnecessary since there is full-screen background art {@link https://newdocs.phaser.io/docs/3.85.1/focus/Phaser.Core.Config-clearBeforeRender} */ + clearBeforeRender: false, plugins: { global: [{ key: "rexInputTextPlugin", @@ -67,6 +69,7 @@ const config: Phaser.Types.Core.GameConfig = { }, pixelArt: true, pipeline: [ InvertPostFX ] as unknown as Phaser.Types.Core.PipelineConfig, + powerPreference: "high-performance", scene: [ LoadingScene, BattleScene ], version: version }; diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index bfb97454082..1ed17b680d2 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -11,7 +11,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { start() { super.start(); - this.scene.clearAllAudio(); + // this.scene.clearAllAudio(); } doEncounter(): void { diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts index d63823e4167..409e668ac65 100644 --- a/src/phases/next-encounter-phase.ts +++ b/src/phases/next-encounter-phase.ts @@ -8,6 +8,8 @@ export class NextEncounterPhase extends EncounterPhase { start() { super.start(); + + this.scene.clearUnusedAssets(); } doEncounter(): void { diff --git a/src/scene-base.ts b/src/scene-base.ts index a2a5b39974b..04fd875a7ac 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -1,4 +1,5 @@ -import { allDefaultBgmAssets, allDefaultSeAssets } from "#app/default-assets"; +import { allDefaultAtlasAssets, allDefaultBgmAssets, allDefaultImageAssets, allDefaultSeAssets } from "#app/default-assets"; +import CanvasTexture = Phaser.Textures.CanvasTexture; export const legacyCompatibleImages: string[] = []; @@ -94,7 +95,57 @@ export class SceneBase extends Phaser.Scene { this.load.audio(key, this.getCachedUrl(`audio/bgm/${filename}`)); } - clearAllAudio() { + logLocalStorageSize() { + let _lsTotal = 0; + let _xLen, _x; + for (_x in localStorage) { + if (!localStorage.hasOwnProperty(_x)) { + continue; + } + _xLen = ((localStorage[_x].length + _x.length) * 2); + _lsTotal += _xLen; + // console.log(_x.substr(0, 50) + " = " + (_xLen / 1024).toFixed(2) + " KB"); + } + console.log("Estimated localStorage: " + (_lsTotal / 1024).toFixed(2) + " KB"); + } + + logSessionStorageSize() { + let _lsTotal = 0; + let _xLen, _x; + for (_x in sessionStorage) { + if (!sessionStorage.hasOwnProperty(_x)) { + continue; + } + _xLen = ((sessionStorage[_x].length + _x.length) * 2); + _lsTotal += _xLen; + // console.log(_x.substr(0, 50) + " = " + (_xLen / 1024).toFixed(2) + " KB"); + } + console.log("Estimated sessionStorage: " + (_lsTotal / 1024).toFixed(2) + " KB"); + } + + async logPartitionedStorageSize() { + const storageEstimate = await navigator.storage.estimate(); + const storagePercent = ((storageEstimate.usage ?? 0) / (storageEstimate.quota ?? 1) * 100).toFixed(2); + const storageUse = ((storageEstimate.usage ?? 0) / 1024 ).toFixed(2) + "kB"; + const storageMax = ((storageEstimate.quota ?? 0) / 1024 / 1024).toFixed(2) + "MB"; + console.log(`Estimated partitioned storage usage: ${storageUse} (${storagePercent}%) of ${storageMax}`); + } + + protected async clearAssets(excludedAudioKeys: Set, excludedImageKeys: Set, excludedGraphicsKeys: Set) { + const storageEstimate = await navigator.storage.estimate(); + console.log("Estimated cache usage: " + JSON.stringify(storageEstimate)); + this.logLocalStorageSize(); + + await this.clearAllAudio(excludedAudioKeys); + + await this.clearAllImages(new Set([...excludedImageKeys, ... excludedGraphicsKeys])); + + const newStorageEstimate = await navigator.storage.estimate(); + console.log("Estimated cache usage: " + JSON.stringify(newStorageEstimate)); + this.logLocalStorageSize(); + } + + private async clearAllAudio(excludedAudioKeys: Set) { let removedAssets = ""; for (const key of this.cache.audio.entries.keys()) { const audioKey = key as string; @@ -104,11 +155,10 @@ export class SceneBase extends Phaser.Scene { continue; } - // TODO: filter for player's Pokemon assets and do not unload - // if (audioKey.includes("PRSFX - ")) { - // // Asset in use by one of the player's party Pokemon, don't unload - // continue; - // } + if (excludedAudioKeys.has(audioKey) || (keyWithoutFolder && excludedAudioKeys.has(keyWithoutFolder))) { + // Asset in use by one of the player's party Pokemon, don't unload + continue; + } if (this.cache.audio.has(audioKey)) { removedAssets += `${audioKey}\n`; @@ -118,4 +168,51 @@ export class SceneBase extends Phaser.Scene { console.log(`Audio keys removed from cache:\n${removedAssets}`); } + + private async clearAllImages(excludedImageKeys: Set) { + let removedAssets = ""; + for (const textureProperty of Object.keys(this.textures.list)) { + const texture = this.textures.get(textureProperty); + if (!texture || texture instanceof CanvasTexture || ["__NORMAL", "__DEFAULT", "__MISSING", "__WHITE"].includes(texture.key)) { + continue; + } + + const imageKey = texture.key; + + // const keyWithoutFolder = imageKey.split("/").pop(); + if (allDefaultImageAssets.has(imageKey) || allDefaultAtlasAssets.has(imageKey)) { + // Default image/atlas asset, don't unload + continue; + } + + if (excludedImageKeys.has(imageKey)) { + // Asset in use by one of the player's party Pokemon, don't unload + continue; + } + + if (texture.source.some(s => (s.source as any)?.currentSrc.includes(".mp4"))) { + // Cached image from a video, skip + continue; + } + + const removeLegacySuffix = imageKey.replace("_legacy", ""); + if (legacyCompatibleImages.includes(imageKey) || legacyCompatibleImages.includes(removeLegacySuffix)) { + // UI image, skip + continue; + } + + if (imageKey.includes("pkmn__")) { + // TODO: figure out why Pokemon assets being reloaded into TextureManager breaks + // Skip all Pokemon asset removal + continue; + } + + if (this.textures.exists(imageKey)) { + removedAssets += `${imageKey}\n`; + this.textures.remove(imageKey); + } + } + + console.log(`Image keys removed from cache:\n${removedAssets}`); + } }