pokerogue/src/phases/form-change-phase.ts
2025-06-18 16:40:38 -06:00

209 lines
7.0 KiB
TypeScript

import { globalScene } from "#app/global-scene";
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 { 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";
import { EvolutionPhase } from "./evolution-phase";
import { BattlerTagType } from "#enums/battler-tag-type";
import { SpeciesFormKey } from "#enums/species-form-key";
export class FormChangePhase extends EvolutionPhase {
public readonly phaseName = "FormChangePhase";
private formChange: SpeciesFormChange;
private modal: boolean;
constructor(pokemon: PlayerPokemon, formChange: SpeciesFormChange, modal: boolean) {
super(pokemon, null, 0);
this.formChange = formChange;
this.modal = modal;
}
validate(): boolean {
return !!this.formChange;
}
setMode(): Promise<void> {
if (!this.modal) {
return super.setMode();
}
return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE);
}
/**
* 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;
}
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());
},
});
}
/**
* 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,
duration: 1500,
ease: "Sine.easeOut",
// We want the backkground overlay to fade out after it fades in
onComplete: () => {
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();
},
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);
});
}
end(): void {
this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED);
if (this.modal) {
globalScene.ui.revertMode().then(() => {
if (globalScene.ui.getMode() === UiMode.PARTY) {
const partyUiHandler = globalScene.ui.getHandler() as PartyUiHandler;
partyUiHandler.clearPartySlots();
partyUiHandler.populatePartySlots();
}
super.end();
});
} else {
super.end();
}
}
}