From bd7de61a5620afdf9a0b6c13e77400dd51c7a411 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Tue, 2 Sep 2025 06:13:15 +1000 Subject: [PATCH] Improve error handling when playing unloaded sounds --- src/battle-scene.ts | 15 ++++++---- src/data/battle-anims.ts | 12 ++++++-- src/data/pokemon-species.ts | 4 +-- src/field/pokemon.ts | 54 ++++++++++++++++++++-------------- src/phases/egg-hatch-phase.ts | 2 +- src/phases/evolution-phase.ts | 10 +++++-- src/phases/party-heal-phase.ts | 16 +++++----- 7 files changed, 70 insertions(+), 43 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index be02962867c..9ac6e385220 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2318,7 +2318,7 @@ export class BattleScene extends SceneBase { }); } - playSound(sound: string | AnySound, config?: object): AnySound { + playSound(sound: string | AnySound, config?: object): AnySound | null { const key = typeof sound === "string" ? sound : sound.key; config = config ?? {}; try { @@ -2354,16 +2354,19 @@ export class BattleScene extends SceneBase { this.sound.play(key, config); return this.sound.get(key) as AnySound; } catch { - console.log(`${key} not found`); - return sound as AnySound; + console.warn(`${key} not found`); + return null; } } - playSoundWithoutBgm(soundName: string, pauseDuration?: number): AnySound { + playSoundWithoutBgm(soundName: string, pauseDuration?: number): AnySound | null { this.bgmCache.add(soundName); const resumeBgm = this.pauseBgm(); this.playSound(soundName); - const sound = this.sound.get(soundName) as AnySound; + const sound = this.sound.get(soundName); + if (!sound) { + return sound; + } if (this.bgmResumeTimer) { this.bgmResumeTimer.destroy(); } @@ -2373,7 +2376,7 @@ export class BattleScene extends SceneBase { this.bgmResumeTimer = null; }); } - return sound; + return sound as AnySound; } /** The loop point of any given battle, mystery encounter, or title track, read as seconds and milliseconds. */ diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index aa4951f3263..5ff4472d148 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -291,9 +291,17 @@ class AnimTimedSoundEvent extends AnimTimedEvent { } catch (err) { console.error(err); } - return Math.ceil((globalScene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33); + const sound = globalScene.sound.get(`battle_anims/${this.resourceName}`); + if (!sound) { + return 0; + } + return Math.ceil((sound.totalDuration * 1000) / 33.33); } - return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct? + const cry = battleAnim.user!.cry(soundConfig); // TODO: is the bang behind user correct? + if (!cry) { + return 0; + } + return Math.ceil((cry.totalDuration * 1000) / 33.33); } getEventType(): string { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index fd8551f2289..d5049569e61 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -593,14 +593,14 @@ export abstract class PokemonSpeciesForm { }); } - cry(soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound { + cry(soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound | null { const cryKey = this.getCryKey(this.formIndex); let cry: AnySound | null = globalScene.sound.get(cryKey) as AnySound; if (cry?.pendingRemove) { cry = null; } cry = globalScene.playSound(cry ?? cryKey, soundConfig); - if (ignorePlay) { + if (cry && ignorePlay) { cry.stop(); } return cry; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index db973ef2d78..207ec0feecd 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4547,28 +4547,36 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { }); } - cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound { + cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound | null { const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed? const cry = this.getSpeciesForm(undefined, true).cry(soundConfig); + if (!cry) { + return cry; + } let duration = cry.totalDuration * 1000; if (this.fusionSpecies && this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true)) { let fusionCry = this.getFusionSpeciesForm(undefined, true).cry(soundConfig, true); + if (!fusionCry) { + return cry; + } duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => { try { SoundFade.fadeOut(scene, cry, fixedInt(Math.ceil(duration * 0.2))); fusionCry = this.getFusionSpeciesForm(undefined, true).cry({ - seek: Math.max(fusionCry.totalDuration * 0.4, 0), + seek: Math.max(fusionCry!.totalDuration * 0.4, 0), ...soundConfig, }); - SoundFade.fadeIn( - scene, - fusionCry, - fixedInt(Math.ceil(duration * 0.2)), - scene.masterVolume * scene.fieldVolume, - 0, - ); + if (fusionCry) { + SoundFade.fadeIn( + scene, + fusionCry, + fixedInt(Math.ceil(duration * 0.2)), + scene.masterVolume * scene.fieldVolume, + 0, + ); + } } catch (err) { console.error(err); } @@ -4596,14 +4604,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { crySoundConfig.rate = 0.7; } } - const cry = globalScene.playSound(key, crySoundConfig) as AnySound; + const cry = globalScene.playSound(key, crySoundConfig); if (!cry || globalScene.fieldVolume === 0) { callback(); return; } const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); - const delay = Math.max(globalScene.sound.get(key).totalDuration * 50, 25); + const delay = Math.max(cry.totalDuration * 50, 25); let frameProgress = 0; let frameThreshold: number; @@ -4656,20 +4664,20 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { const key = this.species.getCryKey(this.formIndex); let i = 0; let rate = 0.85; - const cry = globalScene.playSound(key, { rate: rate }) as AnySound; + const cry = globalScene.playSound(key, { rate: rate }); const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); - let duration = cry.totalDuration * 1000; const fusionCryKey = this.fusionSpecies!.getCryKey(this.fusionFormIndex); let fusionCry = globalScene.playSound(fusionCryKey, { rate: rate, - }) as AnySound; + }); if (!cry || !fusionCry || globalScene.fieldVolume === 0) { callback(); return; } fusionCry.stop(); + let duration = cry.totalDuration * 1000; duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); const delay = Math.max(duration * 0.05, 25); @@ -4712,16 +4720,18 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { if (i === transitionIndex && fusionCryKey) { SoundFade.fadeOut(globalScene, cry, fixedInt(Math.ceil((duration / rate) * 0.2))); fusionCry = globalScene.playSound(fusionCryKey, { - seek: Math.max(fusionCry.totalDuration * 0.4, 0), + seek: Math.max(fusionCry!.totalDuration * 0.4, 0), rate: rate, }); - SoundFade.fadeIn( - globalScene, - fusionCry, - fixedInt(Math.ceil((duration / rate) * 0.2)), - globalScene.masterVolume * globalScene.fieldVolume, - 0, - ); + if (fusionCry) { + SoundFade.fadeIn( + globalScene, + fusionCry, + fixedInt(Math.ceil((duration / rate) * 0.2)), + globalScene.masterVolume * globalScene.fieldVolume, + 0, + ); + } } rate *= 0.99; if (cry && !cry.pendingRemove) { diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index e9d28e0fe2a..547ca778c6b 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -64,7 +64,7 @@ export class EggHatchPhase extends Phase { private canSkip: boolean; private skipped: boolean; /** The sound effect being played when the egg is hatched */ - private evolutionBgm: AnySound; + private evolutionBgm: AnySound | null; private eggLapsePhase: EggLapsePhase; constructor(hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: number) { diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 542a2100452..6095dfafa21 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -28,7 +28,7 @@ export class EvolutionPhase extends Phase { private evolution: SpeciesFormEvolution | null; private fusionSpeciesEvolved: boolean; // Whether the evolution is of the fused species - private evolutionBgm: AnySound; + private evolutionBgm: AnySound | null; private evolutionHandler: EvolutionSceneHandler; /** Container for all assets used by the scene. When the scene is cleared, the children within this are destroyed. */ @@ -298,7 +298,9 @@ export class EvolutionPhase extends Phase { this.evolutionBg.setVisible(false); }, }); - SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + if (this.evolutionBgm) { + SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + } } /** @@ -378,7 +380,9 @@ export class EvolutionPhase extends Phase { * Fadeout evolution music, play the cry, show the evolution completed text, and end the phase */ private onEvolutionComplete(evolvedPokemon: Pokemon) { - SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + if (this.evolutionBgm) { + SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + } globalScene.time.delayedCall(250, () => { this.pokemon.cry(); globalScene.time.delayedCall(1250, () => { diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 1030d5eb9d9..154e95bc970 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -38,13 +38,15 @@ export class PartyHealPhase extends BattlePhase { pokemon.updateInfo(true); } const healSong = globalScene.playSoundWithoutBgm("heal"); - globalScene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => { - healSong.destroy(); - if (this.resumeBgm && bgmPlaying) { - globalScene.playBgm(); - } - globalScene.ui.fadeIn(500).then(() => this.end()); - }); + if (healSong) { + globalScene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => { + healSong.destroy(); + if (this.resumeBgm && bgmPlaying) { + globalScene.playBgm(); + } + globalScene.ui.fadeIn(500).then(() => this.end()); + }); + } }); globalScene.arena.playerTerasUsed = 0; }