From 8f6dc78608a86733763479c5e58d423149d3d9d5 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:51:46 -0500 Subject: [PATCH 01/11] Cleanup evolution phase --- src/phases/evolution-phase.ts | 604 +++++++++++++++++++--------------- 1 file changed, 342 insertions(+), 262 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index bcc93b028bd..b7fe999309e 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -40,13 +40,23 @@ export class EvolutionPhase extends Phase { protected pokemonEvoSprite: Phaser.GameObjects.Sprite; protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite; - constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number) { + /** Whether the evolution can be cancelled by the player */ + protected canCancel: boolean; + + /** + * @param pokemon - The Pokemon that is evolving + * @param evolution - The form being evolved into + * @param lastLevel - The level at which the Pokemon is evolving + * @param canCancel - Whether the evolution can be cancelled by the player + */ + constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number, canCancel = false) { super(); this.pokemon = pokemon; this.evolution = evolution; this.lastLevel = lastLevel; this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution; + this.canCancel = canCancel; } validate(): boolean { @@ -57,198 +67,233 @@ export class EvolutionPhase extends Phase { return globalScene.ui.setModeForceTransition(UiMode.EVOLUTION_SCENE); } - start() { - super.start(); + /** + * Set up the following evolution assets + * - {@linkcode evolutionContainer} + * - {@linkcode evolutionBaseBg} + * - {@linkcode evolutionBg} + * - {@linkcode evolutionBgOverlay} + * - {@linkcode evolutionOverlay} + * + */ + private setupEvolutionAssets(): void { + this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler; + this.evolutionContainer = this.evolutionHandler.evolutionContainer; + this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg").setOrigin(0); - this.setMode().then(() => { - if (!this.validate()) { - return this.end(); - } + this.evolutionBg = globalScene.add + .video(0, 0, "evo_bg") + .stop() + .setOrigin(0) + .setScale(0.4359673025) + .setVisible(false); - globalScene.fadeOutBgm(undefined, false); + this.evolutionBgOverlay = globalScene.add + .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626) + .setOrigin(0) + .setAlpha(0); + this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBg, this.evolutionBgOverlay]); - this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler; + this.evolutionOverlay = globalScene.add.rectangle( + 0, + -globalScene.game.canvas.height / 6, + globalScene.game.canvas.width / 6, + globalScene.game.canvas.height / 6 - 48, + 0xffffff, + ); + this.evolutionOverlay.setOrigin(0).setAlpha(0); + globalScene.ui.add(this.evolutionOverlay); + } - this.evolutionContainer = this.evolutionHandler.evolutionContainer; + /** + * Configure the sprite, setting its pipeline data + * @param pokemon - The pokemon object that the sprite information is configured from + * @param sprite - The sprite object to configure + * @param setPipeline - Whether to also set the pipeline; should be false + * if the sprite is only being updated with new sprite assets + * + * + * @returns The sprite object that was passed in + */ + private configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite { + const spriteKey = this.pokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } - this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg"); - this.evolutionBaseBg.setOrigin(0, 0); - this.evolutionContainer.add(this.evolutionBaseBg); - - this.evolutionBg = globalScene.add.video(0, 0, "evo_bg").stop(); - this.evolutionBg.setOrigin(0, 0); - this.evolutionBg.setScale(0.4359673025); - this.evolutionBg.setVisible(false); - this.evolutionContainer.add(this.evolutionBg); - - this.evolutionBgOverlay = globalScene.add.rectangle( - 0, - 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, - 0x262626, - ); - this.evolutionBgOverlay.setOrigin(0, 0); - this.evolutionBgOverlay.setAlpha(0); - this.evolutionContainer.add(this.evolutionBgOverlay); - - const getPokemonSprite = () => { - const ret = globalScene.addPokemonSprite( - this.pokemon, - this.evolutionBaseBg.displayWidth / 2, - this.evolutionBaseBg.displayHeight / 2, - "pkmn__sub", - ); - ret.setPipeline(globalScene.spritePipeline, { - tone: [0.0, 0.0, 0.0, 0.0], - ignoreTimeTint: true, - }); - return ret; - }; - - this.evolutionContainer.add((this.pokemonSprite = getPokemonSprite())); - this.evolutionContainer.add((this.pokemonTintSprite = getPokemonSprite())); - this.evolutionContainer.add((this.pokemonEvoSprite = getPokemonSprite())); - this.evolutionContainer.add((this.pokemonEvoTintSprite = getPokemonSprite())); - - this.pokemonTintSprite.setAlpha(0); - this.pokemonTintSprite.setTintFill(0xffffff); - this.pokemonEvoSprite.setVisible(false); - this.pokemonEvoTintSprite.setVisible(false); - this.pokemonEvoTintSprite.setTintFill(0xffffff); - - this.evolutionOverlay = globalScene.add.rectangle( - 0, - -globalScene.game.canvas.height / 6, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6 - 48, - 0xffffff, - ); - this.evolutionOverlay.setOrigin(0, 0); - this.evolutionOverlay.setAlpha(0); - globalScene.ui.add(this.evolutionOverlay); - - [this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => { - const spriteKey = this.pokemon.getSpriteKey(true); - try { - sprite.play(spriteKey); - } catch (err: unknown) { - console.error(`Failed to play animation for ${spriteKey}`, err); - } - - sprite.setPipeline(globalScene.spritePipeline, { - tone: [0.0, 0.0, 0.0, 0.0], - hasShadow: false, - teraColor: getTypeRgb(this.pokemon.getTeraType()), - isTerastallized: this.pokemon.isTerastallized, - }); - sprite.setPipelineData("ignoreTimeTint", true); - sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); - sprite.setPipelineData("shiny", this.pokemon.shiny); - sprite.setPipelineData("variant", this.pokemon.variant); - ["spriteColors", "fusionSpriteColors"].map(k => { - if (this.pokemon.summonData.speciesForm) { - k += "Base"; - } - sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; - }); + if (setPipeline) { + sprite.setPipeline(globalScene.spritePipeline, { + tone: [0.0, 0.0, 0.0, 0.0], + hasShadow: false, + teraColor: getTypeRgb(pokemon.getTeraType()), + isTerastallized: pokemon.isTerastallized, + }); + } + + sprite + .setPipelineData("ignoreTimeTint", true) + .setPipelineData("spriteKey", pokemon.getSpriteKey()) + .setPipelineData("shiny", pokemon.shiny) + .setPipelineData("variant", pokemon.variant); + + for (let k of ["spriteColors", "fusionSpriteColors"]) { + if (pokemon.summonData.speciesForm) { + k += "Base"; + } + sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k]; + } + + return sprite; + } + + private getPokemonSprite(): Phaser.GameObjects.Sprite { + const sprite = globalScene.addPokemonSprite( + this.pokemon, + this.evolutionBaseBg.displayWidth / 2, + this.evolutionBaseBg.displayHeight / 2, + "pkmn__sub", + ); + sprite.setPipeline(globalScene.spritePipeline, { + tone: [0.0, 0.0, 0.0, 0.0], + ignoreTimeTint: true, + }); + return sprite; + } + + /** + * Initialize {@linkcode pokemonSprite}, {@linkcode pokemonTintSprite}, {@linkcode pokemonEvoSprite}, and {@linkcode pokemonEvoTintSprite} + * and add them to the {@linkcode evolutionContainer} + */ + private setupPokemonSprites(): void { + this.pokemonSprite = this.configureSprite(this.pokemon, this.getPokemonSprite()); + this.pokemonTintSprite = this.configureSprite( + this.pokemon, + this.getPokemonSprite().setAlpha(0).setTintFill(0xffffff), + ); + this.pokemonEvoSprite = this.configureSprite(this.pokemon, this.getPokemonSprite().setVisible(false)); + this.pokemonEvoTintSprite = this.configureSprite( + this.pokemon, + this.getPokemonSprite().setVisible(false).setTintFill(0xffffff), + ); + + this.evolutionContainer.add([ + this.pokemonSprite, + this.pokemonTintSprite, + this.pokemonEvoSprite, + this.pokemonEvoTintSprite, + ]); + } + + async start() { + super.start(); + await this.setMode(); + + if (!this.validate()) { + return this.end(); + } + this.setupEvolutionAssets(); + this.setupPokemonSprites(); + this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); + this.doEvolution(); + } + + /** + * Update the sprites depicting the evolved Pokemon + * @param evolvedPokemon - The evolved Pokemon + */ + private updateEvolvedPokemonSprites(evolvedPokemon: Pokemon): void { + this.configureSprite(evolvedPokemon, this.pokemonEvoSprite, false); + this.configureSprite(evolvedPokemon, this.pokemonEvoTintSprite, false); + } + + /** + * Adds the evolution tween and begins playing it + */ + private playEvolutionAnimation(evolvedPokemon: Pokemon): void { + globalScene.time.delayedCall(1000, () => { + this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution"); + globalScene.tweens.add({ + targets: this.evolutionBgOverlay, + alpha: 1, + delay: 500, + duration: 1500, + ease: "Sine.easeOut", + onComplete: () => { + globalScene.time.delayedCall(1000, () => { + globalScene.tweens.add({ + targets: this.evolutionBgOverlay, + alpha: 0, + duration: 250, + }); + this.evolutionBg.setVisible(true); + this.evolutionBg.play(); + }); + globalScene.playSound("se/charge"); + this.doSpiralUpward(); + this.fadeOutPokemonSprite(evolvedPokemon); + }, }); - this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); - this.doEvolution(); }); } + private fadeOutPokemonSprite(evolvedPokemon: Pokemon): void { + globalScene.tweens.addCounter({ + from: 0, + to: 1, + duration: 2000, + onUpdate: t => { + this.pokemonTintSprite.setAlpha(t.getValue()); + }, + onComplete: () => { + this.pokemonSprite.setVisible(false); + globalScene.time.delayedCall(1100, () => { + globalScene.playSound("se/beam"); + this.doArcDownward(); + this.prepareForCycle(evolvedPokemon); + }); + }, + }); + } + + /** + * Prepares the evolution cycle by setting up the tint sprites and starting the cycle + */ + private prepareForCycle(evolvedPokemon: Pokemon): void { + globalScene.time.delayedCall(1500, () => { + this.pokemonEvoTintSprite.setScale(0.25).setVisible(true); + this.evolutionHandler.canCancel = true; + this.doCycle(1).then(success => { + if (success) { + this.handleSuccessEvolution(evolvedPokemon); + } else { + this.handleFailedEvolution(evolvedPokemon); + } + }); + }); + } + + /** + * Show the evolution text and then commence the evolution animation + */ doEvolution(): void { globalScene.ui.showText( i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { this.pokemon.cry(); - this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => { - [this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => { - const spriteKey = evolvedPokemon.getSpriteKey(true); - try { - sprite.play(spriteKey); - } catch (err: unknown) { - console.error(`Failed to play animation for ${spriteKey}`, err); - } - - sprite.setPipelineData("ignoreTimeTint", true); - sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey()); - sprite.setPipelineData("shiny", evolvedPokemon.shiny); - sprite.setPipelineData("variant", evolvedPokemon.variant); - ["spriteColors", "fusionSpriteColors"].map(k => { - if (evolvedPokemon.summonData.speciesForm) { - k += "Base"; - } - sprite.pipelineData[k] = evolvedPokemon.getSprite().pipelineData[k]; - }); - }); - - globalScene.time.delayedCall(1000, () => { - this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution"); - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 1, - delay: 500, - duration: 1500, - ease: "Sine.easeOut", - onComplete: () => { - globalScene.time.delayedCall(1000, () => { - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 0, - duration: 250, - }); - this.evolutionBg.setVisible(true); - this.evolutionBg.play(); - }); - globalScene.playSound("se/charge"); - this.doSpiralUpward(); - globalScene.tweens.addCounter({ - from: 0, - to: 1, - duration: 2000, - onUpdate: t => { - this.pokemonTintSprite.setAlpha(t.getValue()); - }, - onComplete: () => { - this.pokemonSprite.setVisible(false); - globalScene.time.delayedCall(1100, () => { - globalScene.playSound("se/beam"); - this.doArcDownward(); - globalScene.time.delayedCall(1500, () => { - this.pokemonEvoTintSprite.setScale(0.25); - this.pokemonEvoTintSprite.setVisible(true); - this.evolutionHandler.canCancel = true; - this.doCycle(1).then(success => { - if (success) { - this.handleSuccessEvolution(evolvedPokemon); - } else { - this.handleFailedEvolution(evolvedPokemon); - } - }); - }); - }); - }, - }); - }, - }); - }); + this.updateEvolvedPokemonSprites(evolvedPokemon); + this.playEvolutionAnimation(evolvedPokemon); }); }, 1000, ); } - /** - * Handles a failed/stopped evolution - * @param evolvedPokemon - The evolved Pokemon - */ - private handleFailedEvolution(evolvedPokemon: Pokemon): void { - this.pokemonSprite.setVisible(true); - this.pokemonTintSprite.setScale(1); + /** Used exclusively by {@linkcode handleFailedEvolution} to fade out the evolution sprites and music */ + private fadeOutEvolutionAssets(): void { globalScene.tweens.add({ targets: [this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite], alpha: 0, @@ -257,9 +302,40 @@ export class EvolutionPhase extends Phase { this.evolutionBg.setVisible(false); }, }); - SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + } + /** + * Show the confirmation prompt for pausing evolutions + * @param endCallback - The callback to call after either option is selected. + * This should end the evolution phase + */ + private showPauseEvolutionConfirmation(endCallback: () => void): void { + globalScene.ui.setOverlayMode( + UiMode.CONFIRM, + () => { + globalScene.ui.revertMode(); + this.pokemon.pauseEvolutions = true; + globalScene.ui.showText( + i18next.t("menu:evolutionsPaused", { + pokemonName: this.preEvolvedPokemonName, + }), + null, + endCallback, + 3000, + ); + }, + () => { + globalScene.ui.revertMode(); + globalScene.time.delayedCall(3000, endCallback); + }, + ); + } + + /** + * Used exclusively by {@linkcode handleFailedEvolution} to show the failed evolution UI messages + */ + private showFailedEvolutionUI(evolvedPokemon: Pokemon): void { globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); globalScene.ui.showText( @@ -280,25 +356,7 @@ export class EvolutionPhase extends Phase { evolvedPokemon.destroy(); this.end(); }; - globalScene.ui.setOverlayMode( - UiMode.CONFIRM, - () => { - globalScene.ui.revertMode(); - this.pokemon.pauseEvolutions = true; - globalScene.ui.showText( - i18next.t("menu:evolutionsPaused", { - pokemonName: this.preEvolvedPokemonName, - }), - null, - end, - 3000, - ); - }, - () => { - globalScene.ui.revertMode(); - globalScene.time.delayedCall(3000, end); - }, - ); + this.showPauseEvolutionConfirmation(end); }, ); }, @@ -307,6 +365,97 @@ export class EvolutionPhase extends Phase { ); } + /** + * Fade out the evolution assets, show the failed evolution UI messages, and enqueue the EndEvolutionPhase + * @param evolvedPokemon - The evolved Pokemon + */ + private handleFailedEvolution(evolvedPokemon: Pokemon): void { + this.pokemonSprite.setVisible(true); + this.pokemonTintSprite.setScale(1); + this.fadeOutEvolutionAssets(); + + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); + this.showFailedEvolutionUI(evolvedPokemon); + } + + /** + * 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); + globalScene.time.delayedCall(250, () => { + this.pokemon.cry(); + globalScene.time.delayedCall(1250, () => { + globalScene.playSoundWithoutBgm("evolution_fanfare"); + + evolvedPokemon.destroy(); + globalScene.ui.showText( + i18next.t("menu:evolutionDone", { + pokemonName: this.preEvolvedPokemonName, + evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(), + }), + null, + () => this.end(), + null, + true, + fixedInt(4000), + ); + globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm()); + }); + }); + } + + private postEvolve(evolvedPokemon: Pokemon): void { + const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved + ? LearnMoveSituation.EVOLUTION_FUSED + : this.pokemon.fusionSpecies + ? LearnMoveSituation.EVOLUTION_FUSED_BASE + : LearnMoveSituation.EVOLUTION; + const levelMoves = this.pokemon + .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) + .filter(lm => lm[0] === EVOLVE_MOVE); + for (const lm of levelMoves) { + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(this.pokemon), + lm[1], + ); + } + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); + + globalScene.playSound("se/shine"); + this.doSpray(); + + globalScene.tweens.chain({ + targets: null, + tweens: [ + { + targets: this.evolutionOverlay, + alpha: 1, + duration: 250, + easing: "Sine.easeIn", + onComplete: () => { + this.evolutionBgOverlay.setAlpha(1); + this.evolutionBg.setVisible(false); + }, + }, + { + targets: [this.evolutionOverlay, this.pokemonEvoTintSprite], + alpha: 0, + duration: 2000, + delay: 150, + easing: "Sine.easeIn", + }, + { + targets: this.evolutionBgOverlay, + alpha: 0, + duration: 250, + onComplete: () => this.onEvolutionComplete(evolvedPokemon), + }, + ], + }); + } + /** * Handles a successful evolution * @param evolvedPokemon - The evolved Pokemon @@ -316,79 +465,10 @@ export class EvolutionPhase extends Phase { this.pokemonEvoSprite.setVisible(true); this.doCircleInward(); - const onEvolutionComplete = () => { - SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); - globalScene.time.delayedCall(250, () => { - this.pokemon.cry(); - globalScene.time.delayedCall(1250, () => { - globalScene.playSoundWithoutBgm("evolution_fanfare"); - - evolvedPokemon.destroy(); - globalScene.ui.showText( - i18next.t("menu:evolutionDone", { - pokemonName: this.preEvolvedPokemonName, - evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(), - }), - null, - () => this.end(), - null, - true, - fixedInt(4000), - ); - globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm()); - }); - }); - }; - globalScene.time.delayedCall(900, () => { - this.evolutionHandler.canCancel = false; + this.evolutionHandler.canCancel = this.canCancel; - this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved - ? LearnMoveSituation.EVOLUTION_FUSED - : this.pokemon.fusionSpecies - ? LearnMoveSituation.EVOLUTION_FUSED_BASE - : LearnMoveSituation.EVOLUTION; - const levelMoves = this.pokemon - .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) - .filter(lm => lm[0] === EVOLVE_MOVE); - for (const lm of levelMoves) { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(this.pokemon), - lm[1], - ); - } - globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); - - globalScene.playSound("se/shine"); - this.doSpray(); - globalScene.tweens.add({ - targets: this.evolutionOverlay, - alpha: 1, - duration: 250, - easing: "Sine.easeIn", - onComplete: () => { - this.evolutionBgOverlay.setAlpha(1); - this.evolutionBg.setVisible(false); - globalScene.tweens.add({ - targets: [this.evolutionOverlay, this.pokemonEvoTintSprite], - alpha: 0, - duration: 2000, - delay: 150, - easing: "Sine.easeIn", - onComplete: () => { - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 0, - duration: 250, - onComplete: onEvolutionComplete, - }); - }, - }); - }, - }); - }); + this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => this.postEvolve(evolvedPokemon)); }); } From 75511354eef21fa858cc850a943cb1436c370b36 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:34:44 -0500 Subject: [PATCH 02/11] Update evolution phase and types --- src/phases/evolution-phase.ts | 63 +++++++++++++++++++---------------- src/system/game-speed.ts | 23 ++++++++++--- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index b7fe999309e..4e56b8fa769 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -49,7 +49,7 @@ export class EvolutionPhase extends Phase { * @param lastLevel - The level at which the Pokemon is evolving * @param canCancel - Whether the evolution can be cancelled by the player */ - constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number, canCancel = false) { + constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number, canCancel = true) { super(); this.pokemon = pokemon; @@ -115,7 +115,7 @@ export class EvolutionPhase extends Phase { * * @returns The sprite object that was passed in */ - private configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite { + protected configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite { const spriteKey = this.pokemon.getSpriteKey(true); try { sprite.play(spriteKey); @@ -263,7 +263,7 @@ export class EvolutionPhase extends Phase { private prepareForCycle(evolvedPokemon: Pokemon): void { globalScene.time.delayedCall(1500, () => { this.pokemonEvoTintSprite.setScale(0.25).setVisible(true); - this.evolutionHandler.canCancel = true; + this.evolutionHandler.canCancel = this.canCancel; this.doCycle(1).then(success => { if (success) { this.handleSuccessEvolution(evolvedPokemon); @@ -510,34 +510,39 @@ export class EvolutionPhase extends Phase { }); } - doCycle(l: number, lastCycle = 15): Promise { + /** + * Return a tween chain that cycles the evolution sprites + */ + doCycle(cycles: number, lastCycle = 15): Promise { return new Promise(resolve => { - const isLastCycle = l === lastCycle; - globalScene.tweens.add({ - targets: this.pokemonTintSprite, - scale: 0.25, - ease: "Cubic.easeInOut", - duration: 500 / l, - yoyo: !isLastCycle, - }); - globalScene.tweens.add({ - targets: this.pokemonEvoTintSprite, - scale: 1, - ease: "Cubic.easeInOut", - duration: 500 / l, - yoyo: !isLastCycle, - onComplete: () => { - if (this.evolutionHandler.cancelled) { - return resolve(false); - } - if (l < lastCycle) { - this.doCycle(l + 0.5, lastCycle).then(success => resolve(success)); - } else { - this.pokemonTintSprite.setVisible(false); - resolve(true); - } + const isLastCycle = cycles === lastCycle; + globalScene.tweens.addMultiple([ + { + targets: this.pokemonTintSprite, + scale: 0.25, + ease: "Cubic.easeInOut", + duration: 500 / cycles, + yoyo: !isLastCycle, }, - }); + { + targets: this.pokemonEvoTintSprite, + scale: 1, + ease: "Cubic.easeInOut", + duration: 500 / cycles, + yoyo: !isLastCycle, + onComplete: () => { + if (this.evolutionHandler.cancelled) { + return resolve(false); + } + if (cycles < lastCycle) { + this.doCycle(cycles + 0.5, lastCycle).then(success => resolve(success)); + } else { + this.pokemonTintSprite.setVisible(false); + resolve(true); + } + }, + }, + ]); }); } diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index 712870dfaf1..b06fa6da815 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -26,6 +26,8 @@ export function initGameSpeed() { return originalAddEvent.apply(this, [config]); }; const originalTweensAdd = this.tweens.add; + + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This isn't bad this.tweens.add = function ( config: | Phaser.Types.Tweens.TweenBuilderConfig @@ -33,8 +35,11 @@ export function initGameSpeed() { | Phaser.Tweens.Tween | Phaser.Tweens.TweenChain, ) { + if (config.completeDelay) { + config.completeDelay = transformValue(config.completeDelay as number | FixedInt); + } if (config.loopDelay) { - config.loopDelay = transformValue(config.loopDelay as number); + config.loopDelay = transformValue(config.loopDelay as number | FixedInt); } if (!(config instanceof Phaser.Tweens.TweenChain)) { @@ -44,7 +49,7 @@ export function initGameSpeed() { if (!(config instanceof Phaser.Tweens.Tween)) { if (config.delay) { - config.delay = transformValue(config.delay as number); + config.delay = transformValue(config.delay as number | FixedInt); } if (config.repeatDelay) { config.repeatDelay = transformValue(config.repeatDelay); @@ -52,11 +57,13 @@ export function initGameSpeed() { if (config.hold) { config.hold = transformValue(config.hold); } + 1; } } return originalTweensAdd.apply(this, [config]); }; const originalTweensChain = this.tweens.chain; + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This isn't bad this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain { if (config.tweens) { for (const t of config.tweens) { @@ -64,17 +71,20 @@ export function initGameSpeed() { t.duration = transformValue(t.duration); } if (t.delay) { - t.delay = transformValue(t.delay as number); + t.delay = transformValue(t.delay); } if (t.repeatDelay) { t.repeatDelay = transformValue(t.repeatDelay); } if (t.loopDelay) { - t.loopDelay = transformValue(t.loopDelay as number); + t.loopDelay = transformValue(t.loopDelay); } if (t.hold) { t.hold = transformValue(t.hold); } + if (t.completeDelay) { + t.completeDelay = transformValue(t.completeDelay); + } } } return originalTweensChain.apply(this, [config]); @@ -91,11 +101,14 @@ export function initGameSpeed() { config.repeatDelay = transformValue(config.repeatDelay); } if (config.loopDelay) { - config.loopDelay = transformValue(config.loopDelay as number); + config.loopDelay = transformValue(config.loopDelay); } if (config.hold) { config.hold = transformValue(config.hold); } + if (config.completeDelay) { + config.completeDelay = transformValue(config.completeDelay as number | FixedInt); + } return originalAddCounter.apply(this, [config]); }; From 9a601b80a2dbcf6f85d2e599c94cfb7581f3163d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:50:36 -0500 Subject: [PATCH 03/11] Refactor form change phase --- src/phases/form-change-phase.ts | 270 +++++++++++++++++--------------- 1 file changed, 141 insertions(+), 129 deletions(-) diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 13cd410ef87..caf9ca18c26 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; -import { fixedInt } from "#app/utils/common"; +import { FixedInt, fixedInt } from "#app/utils/common"; import { achvs } from "../system/achv"; import type { SpeciesFormChange } from "../data/pokemon-forms"; import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers"; -import type { PlayerPokemon } from "../field/pokemon"; +import type { default as Pokemon, PlayerPokemon } from "../field/pokemon"; import { UiMode } from "#enums/ui-mode"; import type PartyUiHandler from "../ui/party-ui-handler"; import { getPokemonNameWithAffix } from "../messages"; @@ -34,146 +34,158 @@ export class FormChangePhase extends EvolutionPhase { return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE); } - doEvolution(): void { - const preName = getPokemonNameWithAffix(this.pokemon); - - this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => { - [this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => { - const spriteKey = transformedPokemon.getSpriteKey(true); - try { - sprite.play(spriteKey); - } catch (err: unknown) { - console.error(`Failed to play animation for ${spriteKey}`, err); + /** + * Commence the tweens that play after the form change animation finishes + * @param transformedPokemon - The Pokemon after the evolution + * @param preName - The name of the Pokemon before the evolution + */ + private postFormChangeTweens(transformedPokemon: Pokemon, preName: string): void { + globalScene.tweens.chain({ + targets: null, + tweens: [ + { + targets: this.evolutionOverlay, + alpha: 1, + duration: 250, + easing: "Sine.easeIn", + onComplete: () => { + this.evolutionBgOverlay.setAlpha(1); + this.evolutionBg.setVisible(false); + }, + }, + { + targets: [this.evolutionOverlay, this.pokemonEvoTintSprite], + alpha: 0, + duration: 2000, + delay: 150, + easing: "Sine.easeIn", + }, + { + targets: this.evolutionBgOverlay, + alpha: 0, + duration: 250, + completeDelay: 250, + onComplete: () => this.pokemon.cry(), + }, + ], + // 1.25 seconds after the pokemon cry + completeDelay: 1250, + onComplete: () => { + let playEvolutionFanfare = false; + if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) { + globalScene.validateAchv(achvs.MEGA_EVOLVE); + playEvolutionFanfare = true; + } else if ( + this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || + this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1 + ) { + globalScene.validateAchv(achvs.GIGANTAMAX); + playEvolutionFanfare = true; } - sprite.setPipelineData("ignoreTimeTint", true); - sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey()); - sprite.setPipelineData("shiny", transformedPokemon.shiny); - sprite.setPipelineData("variant", transformedPokemon.variant); - ["spriteColors", "fusionSpriteColors"].map(k => { - if (transformedPokemon.summonData.speciesForm) { - k += "Base"; - } - sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k]; - }); - }); + const delay = playEvolutionFanfare ? 4000 : 1750; + globalScene.playSoundWithoutBgm(playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare"); + transformedPokemon.destroy(); + globalScene.ui.showText( + getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), + null, + () => this.end(), + null, + true, + fixedInt(delay), + ); + globalScene.time.delayedCall(fixedInt(delay + 250), () => globalScene.playBgm()); + }, + }); + } - globalScene.time.delayedCall(250, () => { - globalScene.tweens.add({ + /** + * Commence the animations that occur once the form change evolution cycle ({@linkcode doCycle}) is complete + * + * @privateRemarks + * This would prefer {@linkcode doCycle} to be refactored and de-promisified so this can be moved into {@linkcode beginTweens} + * @param preName - The name of the Pokemon before the evolution + * @param transformedPokemon - The Pokemon being transformed into + */ + private afterCycle(preName: string, transformedPokemon: Pokemon): void { + globalScene.playSound("se/sparkle"); + this.pokemonEvoSprite.setVisible(true); + this.doCircleInward(); + globalScene.time.delayedCall(900, () => { + this.pokemon.changeForm(this.formChange).then(() => { + if (!this.modal) { + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); + } + globalScene.playSound("se/shine"); + this.doSpray(); + this.postFormChangeTweens(transformedPokemon, preName); + }); + }); + } + + /** + * Commence the sequence of tweens and events that occur during the evolution animation + * @param preName The name of the Pokemon before the evolution + * @param transformedPokemon The Pokemon after the evolution + */ + private beginTweens(preName: string, transformedPokemon: Pokemon): void { + globalScene.tweens.chain({ + // Starts 250ms after sprites have been configured + delay: 250, + targets: null, + tweens: [ + // Step 1: Fade in the background overlay + { targets: this.evolutionBgOverlay, alpha: 1, - delay: 500, duration: 1500, ease: "Sine.easeOut", + // We want the backkground overlay to fade out after it fades in onComplete: () => { - globalScene.time.delayedCall(1000, () => { - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 0, - duration: 250, - }); - this.evolutionBg.setVisible(true); - this.evolutionBg.play(); + globalScene.tweens.add({ + targets: this.evolutionBgOverlay, + alpha: 0, + duration: 250, + delay: fixedInt(1000), }); + this.evolutionBg.setVisible(true).play(); + }, + }, + // Step 2: Play the sounds and fade in the tint sprite + { + targets: this.pokemonTintSprite, + alpha: { from: 0, to: 1 }, + duration: 2000, + onStart: () => { globalScene.playSound("se/charge"); this.doSpiralUpward(); - globalScene.tweens.addCounter({ - from: 0, - to: 1, - duration: 2000, - onUpdate: t => { - this.pokemonTintSprite.setAlpha(t.getValue()); - }, - onComplete: () => { - this.pokemonSprite.setVisible(false); - globalScene.time.delayedCall(1100, () => { - globalScene.playSound("se/beam"); - this.doArcDownward(); - globalScene.time.delayedCall(1000, () => { - this.pokemonEvoTintSprite.setScale(0.25); - this.pokemonEvoTintSprite.setVisible(true); - this.doCycle(1, 1).then(_success => { - globalScene.playSound("se/sparkle"); - this.pokemonEvoSprite.setVisible(true); - this.doCircleInward(); - globalScene.time.delayedCall(900, () => { - this.pokemon.changeForm(this.formChange).then(() => { - if (!this.modal) { - globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); - } - - globalScene.playSound("se/shine"); - this.doSpray(); - globalScene.tweens.add({ - targets: this.evolutionOverlay, - alpha: 1, - duration: 250, - easing: "Sine.easeIn", - onComplete: () => { - this.evolutionBgOverlay.setAlpha(1); - this.evolutionBg.setVisible(false); - globalScene.tweens.add({ - targets: [this.evolutionOverlay, this.pokemonEvoTintSprite], - alpha: 0, - duration: 2000, - delay: 150, - easing: "Sine.easeIn", - onComplete: () => { - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 0, - duration: 250, - onComplete: () => { - globalScene.time.delayedCall(250, () => { - this.pokemon.cry(); - globalScene.time.delayedCall(1250, () => { - let playEvolutionFanfare = false; - if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) { - globalScene.validateAchv(achvs.MEGA_EVOLVE); - playEvolutionFanfare = true; - } else if ( - this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || - this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1 - ) { - globalScene.validateAchv(achvs.GIGANTAMAX); - playEvolutionFanfare = true; - } - - const delay = playEvolutionFanfare ? 4000 : 1750; - globalScene.playSoundWithoutBgm( - playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare", - ); - - transformedPokemon.destroy(); - globalScene.ui.showText( - getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), - null, - () => this.end(), - null, - true, - fixedInt(delay), - ); - globalScene.time.delayedCall(fixedInt(delay + 250), () => - globalScene.playBgm(), - ); - }); - }); - }, - }); - }, - }); - }, - }); - }); - }); - }); - }); - }); - }, - }); }, + onComplete: () => { + this.pokemonSprite.setVisible(false); + }, + }, + ], + + // Step 3: Commence the form change animation via doCycle then continue the animation chain with afterCycle + completeDelay: new FixedInt(1100), + onComplete: () => { + globalScene.playSound("se/beam"); + this.doArcDownward(); + globalScene.time.delayedCall(1000, () => { + this.pokemonEvoTintSprite.setScale(0.25).setVisible(true); + this.doCycle(1, 1).then(() => this.afterCycle(preName, transformedPokemon)); }); - }); + }, + }); + } + + doEvolution(): void { + const preName = getPokemonNameWithAffix(this.pokemon, false); + + this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => { + this.configureSprite(transformedPokemon, this.pokemonEvoSprite, false); + this.configureSprite(transformedPokemon, this.pokemonEvoTintSprite, false); + this.beginTweens(preName, transformedPokemon); }); } From f49c4e7a420a5f31cff52bc853866bea07a2a206 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:19:24 -0500 Subject: [PATCH 04/11] Simplify game-speed.ts and update evo phase --- src/phases/evolution-phase.ts | 73 +++++++++--------- src/phases/form-change-phase.ts | 8 +- src/system/game-speed.ts | 133 ++++++++++++++------------------ 3 files changed, 100 insertions(+), 114 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 4e56b8fa769..8a6ff05e19a 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -23,6 +23,8 @@ export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; protected lastLevel: number; + protected evoChain: Phaser.Tweens.TweenChain | null = null; + private preEvolvedPokemonName: string; private evolution: SpeciesFormEvolution | null; @@ -116,7 +118,7 @@ export class EvolutionPhase extends Phase { * @returns The sprite object that was passed in */ protected configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite { - const spriteKey = this.pokemon.getSpriteKey(true); + const spriteKey = pokemon.getSpriteKey(true); try { sprite.play(spriteKey); } catch (err: unknown) { @@ -264,11 +266,11 @@ export class EvolutionPhase extends Phase { globalScene.time.delayedCall(1500, () => { this.pokemonEvoTintSprite.setScale(0.25).setVisible(true); this.evolutionHandler.canCancel = this.canCancel; - this.doCycle(1).then(success => { - if (success) { - this.handleSuccessEvolution(evolvedPokemon); - } else { + this.doCycle(1, undefined, () => { + if (this.evolutionHandler.cancelled) { this.handleFailedEvolution(evolvedPokemon); + } else { + this.handleSuccessEvolution(evolvedPokemon); } }); }); @@ -474,7 +476,6 @@ export class EvolutionPhase extends Phase { doSpiralUpward() { let f = 0; - globalScene.tweens.addCounter({ repeat: 64, duration: getFrameMs(1), @@ -513,36 +514,38 @@ export class EvolutionPhase extends Phase { /** * Return a tween chain that cycles the evolution sprites */ - doCycle(cycles: number, lastCycle = 15): Promise { - return new Promise(resolve => { - const isLastCycle = cycles === lastCycle; - globalScene.tweens.addMultiple([ - { - targets: this.pokemonTintSprite, - scale: 0.25, - ease: "Cubic.easeInOut", - duration: 500 / cycles, - yoyo: !isLastCycle, + doCycle(cycles: number, lastCycle = 15, onComplete = () => {}): void { + // Make our tween start both at the same time + const tweens: Phaser.Types.Tweens.TweenBuilderConfig[] = []; + for (let i = cycles; i <= lastCycle; i += 0.5) { + tweens.push({ + targets: [this.pokemonTintSprite, this.pokemonEvoTintSprite], + scale: (_target, _key, _value, targetIndex: number, _totalTargets, _tween) => (targetIndex === 0 ? 0.25 : 1), + ease: "Cubic.easeInOut", + duration: 500 / i, + yoyo: i !== lastCycle, + onComplete: () => { + if (this.evolutionHandler.cancelled) { + // cause the tween chain to complete instantly, skipping the remaining tweens. + this.pokemonEvoTintSprite.setScale(1); + this.pokemonEvoTintSprite.setVisible(false); + this.evoChain?.complete?.(); + return; + } + if (i === lastCycle) { + this.pokemonEvoTintSprite.setScale(1); + } }, - { - targets: this.pokemonEvoTintSprite, - scale: 1, - ease: "Cubic.easeInOut", - duration: 500 / cycles, - yoyo: !isLastCycle, - onComplete: () => { - if (this.evolutionHandler.cancelled) { - return resolve(false); - } - if (cycles < lastCycle) { - this.doCycle(cycles + 0.5, lastCycle).then(success => resolve(success)); - } else { - this.pokemonTintSprite.setVisible(false); - resolve(true); - } - }, - }, - ]); + }); + } + + this.evoChain = globalScene.tweens.chain({ + targets: null, + tweens, + onComplete: () => { + this.evoChain = null; + onComplete(); + }, }); } diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index caf9ca18c26..37bae6bd3c3 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { FixedInt, fixedInt } from "#app/utils/common"; +import { fixedInt } from "#app/utils/common"; import { achvs } from "../system/achv"; import type { SpeciesFormChange } from "../data/pokemon-forms"; import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers"; @@ -146,7 +146,7 @@ export class FormChangePhase extends EvolutionPhase { targets: this.evolutionBgOverlay, alpha: 0, duration: 250, - delay: fixedInt(1000), + delay: 1000, }); this.evolutionBg.setVisible(true).play(); }, @@ -167,13 +167,13 @@ export class FormChangePhase extends EvolutionPhase { ], // Step 3: Commence the form change animation via doCycle then continue the animation chain with afterCycle - completeDelay: new FixedInt(1100), + completeDelay: 1100, onComplete: () => { globalScene.playSound("se/beam"); this.doArcDownward(); globalScene.time.delayedCall(1000, () => { this.pokemonEvoTintSprite.setScale(0.25).setVisible(true); - this.doCycle(1, 1).then(() => this.afterCycle(preName, transformedPokemon)); + this.doCycle(1, 1, () => this.afterCycle(preName, transformedPokemon)); }); }, }); diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index b06fa6da815..17916e93a38 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -5,9 +5,13 @@ import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { FixedInt } from "#app/utils/common"; +type TweenManager = typeof Phaser.Tweens.TweenManager.prototype; + +/** The set of properties to mutate */ +const PROPERTIES = ["delay", "completeDelay", "loopDelay", "duration", "repeatDelay", "hold", "startDelay"]; + type FadeInType = typeof FadeIn; type FadeOutType = typeof FadeOut; - export function initGameSpeed() { const thisArg = this as BattleScene; @@ -18,16 +22,44 @@ export function initGameSpeed() { return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed)); }; - const originalAddEvent = this.time.addEvent; + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complexity is necessary here + const mutateProperties = (obj: any, allowArray = false) => { + // We do not mutate Tweens or TweenChains + if (obj instanceof Phaser.Tweens.Tween || obj instanceof Phaser.Tweens.TweenChain) { + return; + } + // If allowArray is true then check if first obj is an array and if so, mutate the tweens inside + if (allowArray && Array.isArray(obj)) { + for (const tween of obj) { + mutateProperties(tween); + } + return; + } + + for (const prop of PROPERTIES) { + const objProp = obj[prop]; + if (typeof objProp === "number" || objProp instanceof FixedInt) { + obj[prop] = transformValue(objProp); + } + } + // If the object has a 'tweens' property, then it is a tween chain + // and we need to mutate its properties as well + if (obj.tweens) { + for (const tween of obj.tweens) { + mutateProperties(tween); + } + } + }; + + const originalAddEvent: typeof Phaser.Time.Clock.prototype.addEvent = this.time.addEvent; this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) { if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) { config.delay = transformValue(config.delay); } return originalAddEvent.apply(this, [config]); }; - const originalTweensAdd = this.tweens.add; + const originalTweensAdd: TweenManager["add"] = this.tweens.add; - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This isn't bad this.tweens.add = function ( config: | Phaser.Types.Tweens.TweenBuilderConfig @@ -35,82 +67,33 @@ export function initGameSpeed() { | Phaser.Tweens.Tween | Phaser.Tweens.TweenChain, ) { - if (config.completeDelay) { - config.completeDelay = transformValue(config.completeDelay as number | FixedInt); - } - if (config.loopDelay) { - config.loopDelay = transformValue(config.loopDelay as number | FixedInt); - } - - if (!(config instanceof Phaser.Tweens.TweenChain)) { - if (config.duration) { - config.duration = transformValue(config.duration); - } - - if (!(config instanceof Phaser.Tweens.Tween)) { - if (config.delay) { - config.delay = transformValue(config.delay as number | FixedInt); - } - if (config.repeatDelay) { - config.repeatDelay = transformValue(config.repeatDelay); - } - if (config.hold) { - config.hold = transformValue(config.hold); - } - 1; - } - } + mutateProperties(config); return originalTweensAdd.apply(this, [config]); - }; - const originalTweensChain = this.tweens.chain; - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This isn't bad + } as typeof originalTweensAdd; + + const originalTweensChain: TweenManager["chain"] = this.tweens.chain; this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain { - if (config.tweens) { - for (const t of config.tweens) { - if (t.duration) { - t.duration = transformValue(t.duration); - } - if (t.delay) { - t.delay = transformValue(t.delay); - } - if (t.repeatDelay) { - t.repeatDelay = transformValue(t.repeatDelay); - } - if (t.loopDelay) { - t.loopDelay = transformValue(t.loopDelay); - } - if (t.hold) { - t.hold = transformValue(t.hold); - } - if (t.completeDelay) { - t.completeDelay = transformValue(t.completeDelay); - } - } - } + mutateProperties(config); return originalTweensChain.apply(this, [config]); - }; - const originalAddCounter = this.tweens.addCounter; + } as typeof originalTweensChain; + const originalAddCounter: TweenManager["addCounter"] = this.tweens.addCounter; + this.tweens.addCounter = function (config: Phaser.Types.Tweens.NumberTweenBuilderConfig) { - if (config.duration) { - config.duration = transformValue(config.duration); - } - if (config.delay) { - config.delay = transformValue(config.delay); - } - if (config.repeatDelay) { - config.repeatDelay = transformValue(config.repeatDelay); - } - if (config.loopDelay) { - config.loopDelay = transformValue(config.loopDelay); - } - if (config.hold) { - config.hold = transformValue(config.hold); - } - if (config.completeDelay) { - config.completeDelay = transformValue(config.completeDelay as number | FixedInt); - } + mutateProperties(config); return originalAddCounter.apply(this, [config]); - }; + } as typeof originalAddCounter; + + // const originalCreate: TweenManager["create"] = this.tweens.create; + // this.tweens.create = function (config: Phaser.Types.Tweens.TweenBuilderConfig) { + // mutateProperties(config, true); + // return originalCreate.apply(this, [config]); + // } as typeof originalCreate; + + const originalAddMultiple: TweenManager["addMultiple"] = this.tweens.addMultiple; + this.tweens.addMultiple = function (config: Phaser.Types.Tweens.TweenBuilderConfig[]) { + mutateProperties(config, true); + return originalAddMultiple.apply(this, [config]); + } as typeof originalAddMultiple; const originalFadeOut = SoundFade.fadeOut; SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) => From ee7e2094e1aaf97a0651b97c1167931f688a54b5 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:47:36 -0500 Subject: [PATCH 05/11] Move delay in formChangePhase to first element --- src/phases/form-change-phase.ts | 2 +- src/system/game-speed.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 37bae6bd3c3..6d60cacd69d 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -131,11 +131,11 @@ export class FormChangePhase extends EvolutionPhase { private beginTweens(preName: string, transformedPokemon: Pokemon): void { globalScene.tweens.chain({ // Starts 250ms after sprites have been configured - delay: 250, targets: null, tweens: [ // Step 1: Fade in the background overlay { + delay: 250, targets: this.evolutionBgOverlay, alpha: 1, duration: 1500, diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index 17916e93a38..207a4fb44a1 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -24,7 +24,7 @@ export function initGameSpeed() { // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complexity is necessary here const mutateProperties = (obj: any, allowArray = false) => { - // We do not mutate Tweens or TweenChains + // We do not mutate Tweens or TweenChain objects themselves. if (obj instanceof Phaser.Tweens.Tween || obj instanceof Phaser.Tweens.TweenChain) { return; } @@ -42,9 +42,9 @@ export function initGameSpeed() { obj[prop] = transformValue(objProp); } } - // If the object has a 'tweens' property, then it is a tween chain + // If the object has a 'tweens' property that is an array, then it is a tween chain // and we need to mutate its properties as well - if (obj.tweens) { + if (obj.tweens && Array.isArray(obj.tweens)) { for (const tween of obj.tweens) { mutateProperties(tween); } @@ -83,11 +83,11 @@ export function initGameSpeed() { return originalAddCounter.apply(this, [config]); } as typeof originalAddCounter; - // const originalCreate: TweenManager["create"] = this.tweens.create; - // this.tweens.create = function (config: Phaser.Types.Tweens.TweenBuilderConfig) { - // mutateProperties(config, true); - // return originalCreate.apply(this, [config]); - // } as typeof originalCreate; + const originalCreate: TweenManager["create"] = this.tweens.create; + this.tweens.create = function (config: Phaser.Types.Tweens.TweenBuilderConfig) { + mutateProperties(config, true); + return originalCreate.apply(this, [config]); + } as typeof originalCreate; const originalAddMultiple: TweenManager["addMultiple"] = this.tweens.addMultiple; this.tweens.addMultiple = function (config: Phaser.Types.Tweens.TweenBuilderConfig[]) { From df361accd72175525836ed43450f93590f9a238d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 12:55:34 -0500 Subject: [PATCH 06/11] Fix mock video object return methods --- test/testUtils/mocks/mockVideoGameObject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testUtils/mocks/mockVideoGameObject.ts b/test/testUtils/mocks/mockVideoGameObject.ts index 1789229b1c7..9b25877c80c 100644 --- a/test/testUtils/mocks/mockVideoGameObject.ts +++ b/test/testUtils/mocks/mockVideoGameObject.ts @@ -5,7 +5,7 @@ export class MockVideoGameObject implements MockGameObject { public name: string; public active = true; - public play = () => null; + public play = () => this; public stop = () => this; public setOrigin = () => this; public setScale = () => this; From 4934a9e35720d7a354f1ee9c2dc9afffed597f3e Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 16:28:06 -0500 Subject: [PATCH 07/11] Fix tween chain mock --- test/testUtils/gameWrapper.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/testUtils/gameWrapper.ts b/test/testUtils/gameWrapper.ts index 9264b68d421..21c4c7ecd6b 100644 --- a/test/testUtils/gameWrapper.ts +++ b/test/testUtils/gameWrapper.ts @@ -124,13 +124,17 @@ export default class GameWrapper { this.scene.tweens = { add: data => { - if (data.onComplete) { - data.onComplete(); - } + // TODO: our mock of `add` should have the same signature as the real one, which returns the tween + data.onComplete?.(); }, getTweensOf: () => [], killTweensOf: () => [], - chain: () => null, + + chain: data => { + // TODO: our mock of `chain` should have the same signature as the real one, which returns the chain + data?.tweens?.forEach(tween => tween.onComplete?.()); + data.onComplete?.(); + }, addCounter: data => { if (data.onComplete) { data.onComplete(); From ea0b240f3260f646c00824b4b75dedabb93084b9 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 16:29:45 -0500 Subject: [PATCH 08/11] Add todo comment to mock phaser's tween manager --- test/testUtils/gameWrapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/testUtils/gameWrapper.ts b/test/testUtils/gameWrapper.ts index 21c4c7ecd6b..d1be55ae1b9 100644 --- a/test/testUtils/gameWrapper.ts +++ b/test/testUtils/gameWrapper.ts @@ -122,6 +122,7 @@ export default class GameWrapper { }, }; + // TODO: Replace this with a proper mock of phaser's TweenManager. this.scene.tweens = { add: data => { // TODO: our mock of `add` should have the same signature as the real one, which returns the tween From ea622a091cadc71d9112965e604bb9b38f9f1c25 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 29 May 2025 23:32:30 -0500 Subject: [PATCH 09/11] Remove jarring flash when evolution begins --- src/phases/evolution-phase.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 8a6ff05e19a..666ada36dc9 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -94,7 +94,7 @@ export class EvolutionPhase extends Phase { .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626) .setOrigin(0) .setAlpha(0); - this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBg, this.evolutionBgOverlay]); + this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBgOverlay, this.evolutionBg]); this.evolutionOverlay = globalScene.add.rectangle( 0, @@ -224,11 +224,6 @@ export class EvolutionPhase extends Phase { ease: "Sine.easeOut", onComplete: () => { globalScene.time.delayedCall(1000, () => { - globalScene.tweens.add({ - targets: this.evolutionBgOverlay, - alpha: 0, - duration: 250, - }); this.evolutionBg.setVisible(true); this.evolutionBg.play(); }); From 4e119b74c18f786c43ae2b7c93aa06d97a14fa30 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 29 May 2025 23:42:10 -0500 Subject: [PATCH 10/11] Fix missing method chaining in evo phase --- src/phases/evolution-phase.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 666ada36dc9..c0ed0d7590a 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -224,8 +224,7 @@ export class EvolutionPhase extends Phase { ease: "Sine.easeOut", onComplete: () => { globalScene.time.delayedCall(1000, () => { - this.evolutionBg.setVisible(true); - this.evolutionBg.play(); + this.evolutionBg.setVisible(true).play(); }); globalScene.playSound("se/charge"); this.doSpiralUpward(); From 7ea2d3a4cd093cfe6c3450c2f9e5cc37e07fe786 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:43:31 -0500 Subject: [PATCH 11/11] Apply biome formatting --- src/phases/evolution-phase.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index c0ed0d7590a..8e4300986b3 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -411,11 +411,7 @@ export class EvolutionPhase extends Phase { .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .filter(lm => lm[0] === EVOLVE_MOVE); for (const lm of levelMoves) { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(this.pokemon), - lm[1], - ); + globalScene.phaseManager.unshiftNew("LearnMovePhase", globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]); } globalScene.phaseManager.unshiftNew("EndEvolutionPhase");