pokerogue/src/egg-hatch-phase.ts

478 lines
18 KiB
TypeScript
Raw Normal View History

import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { Phase } from "./phase";
import BattleScene, { AnySound } from "./battle-scene";
import * as Utils from "./utils";
import { Mode } from "./ui/ui";
import { EGG_SEED, Egg, GachaType, getLegendaryGachaSpeciesForTimestamp, getTypeGachaTypeForTimestamp } from "./data/egg";
import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler";
import { Species } from "./data/enums/species";
2024-02-29 17:08:50 -08:00
import { PlayerPokemon } from "./field/pokemon";
import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import { StatsContainer } from "./ui/stats-container";
2024-01-05 19:24:05 -08:00
import { TextStyle, addBBCodeTextObject, addTextObject } from "./ui/text";
import { Gender, getGenderColor, getGenderSymbol } from "./data/gender";
import { achvs } from "./system/achv";
import { addWindow } from "./ui/window";
2024-01-05 19:24:05 -08:00
import { getNatureName } from "./data/nature";
2024-02-25 09:45:41 -08:00
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
import { EggTier } from "./data/enums/egg-type";
export class EggHatchPhase extends Phase {
private egg: Egg;
private eggHatchContainer: Phaser.GameObjects.Container;
private eggHatchBg: Phaser.GameObjects.Image;
private eggHatchOverlay: Phaser.GameObjects.Rectangle;
private eggContainer: Phaser.GameObjects.Container;
private eggSprite: Phaser.GameObjects.Sprite;
private eggCrackSprite: Phaser.GameObjects.Sprite;
private eggLightraysOverlay: Phaser.GameObjects.Sprite;
private pokemonSprite: Phaser.GameObjects.Sprite;
private pokemonShinySparkle: Phaser.GameObjects.Sprite;
private infoContainer: Phaser.GameObjects.Container;
private statsContainer: StatsContainer;
2024-02-19 21:24:39 -08:00
private pokemon: PlayerPokemon;
2024-02-25 09:45:41 -08:00
private eggMoveIndex: integer;
2024-02-19 21:24:39 -08:00
private canSkip: boolean;
private skipped: boolean;
private evolutionBgm: AnySound;
constructor(scene: BattleScene, egg: Egg) {
super(scene);
this.egg = egg;
}
start() {
super.start();
this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SCENE).then(() => {
if (!this.egg)
return this.end();
const eggIndex = this.scene.gameData.eggs.findIndex(e => e.id === this.egg.id);
if (eggIndex === -1)
return this.end();
this.scene.gameData.eggs.splice(eggIndex, 1);
this.scene.fadeOutBgm(null, false);
const eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
this.eggHatchContainer = eggHatchHandler.eggHatchContainer;
this.eggHatchBg = this.scene.add.image(0, 0, 'default_bg');
this.eggHatchBg.setOrigin(0, 0);
this.eggHatchContainer.add(this.eggHatchBg);
this.eggContainer = this.scene.add.container(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2);
this.eggSprite = this.scene.add.sprite(0, 0, 'egg', `egg_${this.egg.getKey()}`);
this.eggCrackSprite = this.scene.add.sprite(0, 0, 'egg_crack', '0');
this.eggCrackSprite.setVisible(false);
this.eggLightraysOverlay = this.scene.add.sprite((-this.eggHatchBg.displayWidth / 2) + 4, -this.eggHatchBg.displayHeight / 2, 'egg_lightrays', '3');
this.eggLightraysOverlay.setOrigin(0, 0);
this.eggLightraysOverlay.setVisible(false);
this.eggContainer.add(this.eggSprite);
this.eggContainer.add(this.eggCrackSprite);
this.eggContainer.add(this.eggLightraysOverlay);
this.eggHatchContainer.add(this.eggContainer);
const getPokemonSprite = () => this.scene.add.sprite(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2, `pkmn__sub`);
this.eggHatchContainer.add((this.pokemonSprite = getPokemonSprite()));
this.pokemonShinySparkle = this.scene.add.sprite(this.pokemonSprite.x, this.pokemonSprite.y, 'shiny');
this.pokemonShinySparkle.setVisible(false);
this.eggHatchContainer.add(this.pokemonShinySparkle);
this.eggHatchOverlay = this.scene.add.rectangle(0, -this.scene.game.canvas.height / 6, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0xFFFFFF);
this.eggHatchOverlay.setOrigin(0, 0);
this.eggHatchOverlay.setAlpha(0);
this.scene.fieldUI.add(this.eggHatchOverlay);
2024-01-05 19:24:05 -08:00
const infoBg = addWindow(this.scene, 0, 0, 104, 132);
infoBg.setOrigin(0.5, 0.5);
this.infoContainer = this.scene.add.container(this.eggHatchBg.displayWidth + infoBg.width / 2, this.eggHatchBg.displayHeight / 2);
2024-01-05 19:24:05 -08:00
this.statsContainer = new StatsContainer(this.scene, -48, -64, true);
this.infoContainer.add(infoBg);
this.infoContainer.add(this.statsContainer);
2024-01-05 19:24:05 -08:00
const pokemonGenderLabelText = addTextObject(this.scene, -18, 20, 'Gender:', TextStyle.WINDOW, { fontSize: '64px' });
pokemonGenderLabelText.setOrigin(1, 0);
pokemonGenderLabelText.setVisible(false);
this.infoContainer.add(pokemonGenderLabelText);
2024-01-05 19:24:05 -08:00
const pokemonGenderText = addTextObject(this.scene, -14, 20, '', TextStyle.WINDOW, { fontSize: '64px' });
pokemonGenderText.setOrigin(0, 0);
pokemonGenderText.setVisible(false);
this.infoContainer.add(pokemonGenderText);
const pokemonAbilityLabelText = addTextObject(this.scene, -18, 30, 'Ability:', TextStyle.WINDOW, { fontSize: '64px' });
pokemonAbilityLabelText.setOrigin(1, 0);
this.infoContainer.add(pokemonAbilityLabelText);
const pokemonAbilityText = addTextObject(this.scene, -14, 30, '', TextStyle.WINDOW, { fontSize: '64px' });
pokemonAbilityText.setOrigin(0, 0);
this.infoContainer.add(pokemonAbilityText);
const pokemonNatureLabelText = addTextObject(this.scene, -18, 40, 'Nature:', TextStyle.WINDOW, { fontSize: '64px' });
2024-01-05 19:24:05 -08:00
pokemonNatureLabelText.setOrigin(1, 0);
this.infoContainer.add(pokemonNatureLabelText);
const pokemonNatureText = addBBCodeTextObject(this.scene, -14, 40, '', TextStyle.WINDOW, { fontSize: '64px', lineSpacing: 3, maxLines: 2 });
2024-01-05 19:24:05 -08:00
pokemonNatureText.setOrigin(0, 0);
this.infoContainer.add(pokemonNatureText);
this.eggHatchContainer.add(this.infoContainer);
const pokemon = this.generatePokemon();
if (pokemon.fusionSpecies)
pokemon.clearFusionSpecies();
if (pokemon.gender > Gender.GENDERLESS) {
pokemonGenderText.setText(getGenderSymbol(pokemon.gender));
pokemonGenderText.setColor(getGenderColor(pokemon.gender));
pokemonGenderText.setShadowColor(getGenderColor(pokemon.gender, true));
pokemonGenderLabelText.setVisible(true);
pokemonGenderText.setVisible(true);
}
pokemonAbilityText.setText(pokemon.getAbility().name);
2024-01-05 19:24:05 -08:00
pokemonNatureText.setText(getNatureName(pokemon.nature, true));
const originalIvs: integer[] = this.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr
? this.scene.gameData.dexData[pokemon.species.speciesId].ivs
: null;
this.statsContainer.updateIvs(pokemon.ivs, originalIvs);
this.pokemonSprite.setVisible(false);
2024-02-19 21:24:39 -08:00
this.pokemon = pokemon;
pokemon.loadAssets().then(() => {
2024-02-19 21:24:39 -08:00
this.scene.time.delayedCall(1000, () => {
this.evolutionBgm = this.scene.playSoundWithoutBgm('evolution');
this.canSkip = true;
});
this.scene.time.delayedCall(2000, () => {
2024-02-19 21:24:39 -08:00
if (this.skipped)
return;
this.eggCrackSprite.setVisible(true);
this.doSpray(1, this.eggSprite.displayHeight / -2);
this.doEggShake(2).then(() => {
2024-02-19 21:24:39 -08:00
if (this.skipped)
return;
this.scene.time.delayedCall(1000, () => {
2024-02-19 21:24:39 -08:00
if (this.skipped)
return;
this.doSpray(2, this.eggSprite.displayHeight / -4);
this.eggCrackSprite.setFrame('1');
this.scene.time.delayedCall(125, () => this.eggCrackSprite.setFrame('2'));
this.doEggShake(4).then(() => {
2024-02-19 21:24:39 -08:00
if (this.skipped)
return;
this.scene.time.delayedCall(1000, () => {
2024-02-19 21:24:39 -08:00
if (this.skipped)
return;
this.scene.playSound('egg_crack');
this.doSpray(4);
this.eggCrackSprite.setFrame('3');
this.scene.time.delayedCall(125, () => this.eggCrackSprite.setFrame('4'));
this.doEggShake(8, 2).then(() => {
2024-02-19 21:24:39 -08:00
if (!this.skipped)
this.doHatch();
});
});
});
})
});
});
});
});
}
doEggShake(intensity: number, repeatCount?: integer, count?: integer): Promise<void> {
return new Promise(resolve => {
if (repeatCount === undefined)
repeatCount = 0;
if (count === undefined)
count = 0;
this.scene.playSound('pb_move');
this.scene.tweens.add({
targets: this.eggContainer,
x: `-=${intensity / (count ? 1 : 2)}`,
ease: 'Sine.easeInOut',
duration: 125,
onComplete: () => {
this.scene.tweens.add({
targets: this.eggContainer,
x: `+=${intensity}`,
ease: 'Sine.easeInOut',
duration: 250,
onComplete: () => {
count++;
if (count < repeatCount)
return this.doEggShake(intensity, repeatCount, count).then(() => resolve());
this.scene.tweens.add({
targets: this.eggContainer,
x: `-=${intensity / 2}`,
ease: 'Sine.easeInOut',
duration: 125,
onComplete: () => resolve()
});
}
})
}
});
});
}
2024-02-19 21:24:39 -08:00
trySkip(): boolean {
if (!this.canSkip || this.skipped)
return false;
this.skipped = true;
this.doHatch();
return true;
}
doHatch(): void {
this.canSkip = false;
SoundFade.fadeOut(this.scene, this.evolutionBgm, Utils.fixedInt(100));
for (let e = 0; e < 5; e++)
this.scene.time.delayedCall(Utils.fixedInt(375 * e), () => this.scene.playSound('egg_hatch', { volume: 1 - (e * 0.2) }));
this.eggLightraysOverlay.setVisible(true);
this.eggLightraysOverlay.play('egg_lightrays');
this.scene.tweens.add({
duration: Utils.fixedInt(125),
targets: this.eggHatchOverlay,
alpha: 1,
ease: 'Cubic.easeIn'
});
this.scene.time.delayedCall(Utils.fixedInt(1500), () => {
const isShiny = this.pokemon.isShiny();
if (this.pokemon.species.mythical)
this.scene.validateAchv(achvs.HATCH_MYTHICAL);
if (this.pokemon.species.legendary)
this.scene.validateAchv(achvs.HATCH_LEGENDARY);
if (isShiny)
this.scene.validateAchv(achvs.HATCH_SHINY);
this.eggContainer.setVisible(false);
this.pokemonSprite.play(this.pokemon.getSpriteKey(true));
this.pokemonSprite.pipelineData['ignoreTimeTint'] = true;
this.pokemonSprite.setVisible(true);
this.scene.time.delayedCall(Utils.fixedInt(1000), () => {
this.pokemon.cry();
if (isShiny) {
this.scene.time.delayedCall(Utils.fixedInt(1250), () => {
this.pokemonShinySparkle.play('sparkle');
this.scene.playSound('sparkle');
});
}
this.scene.time.delayedCall(Utils.fixedInt(!isShiny ? 1250 : 1750), () => {
this.scene.tweens.add({
targets: this.infoContainer,
duration: Utils.fixedInt(750),
ease: 'Cubic.easeInOut',
x: this.eggHatchBg.displayWidth - 52
});
this.scene.playSoundWithoutBgm('evolution_fanfare');
this.scene.ui.showText(`${this.pokemon.name} hatched from the egg!`, null, () => {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
2024-02-25 09:45:41 -08:00
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
this.scene.ui.showText(null, 0);
this.end();
});
2024-02-19 21:24:39 -08:00
});
}, null, true, 3000);
//this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
});
});
this.scene.tweens.add({
duration: Utils.fixedInt(3000),
targets: this.eggHatchOverlay,
alpha: 0,
ease: 'Cubic.easeOut'
});
});
}
sin(index: integer, amplitude: integer): number {
return amplitude * Math.sin(index * (Math.PI / 128));
}
doSpray(intensity: integer, offsetY?: number) {
this.scene.tweens.addCounter({
repeat: intensity,
duration: Utils.getFrameMs(1),
onRepeat: () => {
this.doSprayParticle(Utils.randInt(8), offsetY || 0);
}
});
}
doSprayParticle(trigIndex: integer, offsetY: number) {
const initialX = this.eggHatchBg.displayWidth / 2;
const initialY = this.eggHatchBg.displayHeight / 2 + offsetY;
const shardKey = !this.egg.isManaphyEgg() ? this.egg.tier.toString() : '1';
const particle = this.scene.add.image(initialX, initialY, 'egg_shard', `${shardKey}_${Math.floor(trigIndex / 2)}`);
this.eggHatchContainer.add(particle);
let f = 0;
let yOffset = 0;
let speed = 3 - Utils.randInt(8);
let amp = 24 + Utils.randInt(32);
const particleTimer = this.scene.tweens.addCounter({
repeat: -1,
duration: Utils.getFrameMs(1),
onRepeat: () => {
updateParticle();
}
});
const updateParticle = () => {
yOffset++;
if (trigIndex < 160) {
particle.setPosition(initialX + (speed * f) / 3, initialY + yOffset);
particle.y += -this.sin(trigIndex, amp);
if (f > 108)
particle.setScale((1 - (f - 108) / 20));
trigIndex += 2;
f++;
} else {
particle.destroy();
particleTimer.remove();
}
};
updateParticle();
}
generatePokemon(): PlayerPokemon {
let ret: PlayerPokemon;
let speciesOverride: Species;
2024-03-01 19:18:39 -08:00
this.scene.executeWithSeedOffset(() => {
if (this.egg.isManaphyEgg()) {
const rand = Utils.randSeedInt(8);
speciesOverride = rand ? Species.PHIONE : Species.MANAPHY;
2024-03-01 19:18:39 -08:00
} else if (this.egg.tier === EggTier.MASTER
&& this.egg.gachaType === GachaType.LEGENDARY) {
if (!Utils.randSeedInt(2))
speciesOverride = getLegendaryGachaSpeciesForTimestamp(this.scene, this.egg.timestamp);
2024-03-01 19:18:39 -08:00
}
2024-03-01 19:18:39 -08:00
if (speciesOverride) {
2023-12-19 21:35:41 -08:00
const pokemonSpecies = getPokemonSpecies(speciesOverride);
2024-01-07 20:17:24 -08:00
ret = this.scene.addPlayerPokemon(pokemonSpecies, 5, undefined, undefined, undefined, false);
2024-03-01 19:18:39 -08:00
} else {
let minStarterValue: integer;
let maxStarterValue: integer;
2024-03-01 19:18:39 -08:00
switch (this.egg.tier) {
case EggTier.GREAT:
minStarterValue = 4;
maxStarterValue = 5;
break;
case EggTier.ULTRA:
minStarterValue = 6;
maxStarterValue = 7;
break;
case EggTier.MASTER:
minStarterValue = 8;
maxStarterValue = 9;
break;
default:
minStarterValue = 1;
maxStarterValue = 3;
break;
}
2024-03-01 19:18:39 -08:00
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
2024-03-01 19:18:39 -08:00
let speciesPool = Object.keys(speciesStarters)
.filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue)
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
2024-03-01 19:18:39 -08:00
if (this.egg.gachaType === GachaType.TYPE) {
let tryOverrideType = !Utils.randSeedInt(2);
2024-03-01 19:18:39 -08:00
if (tryOverrideType) {
const type = getTypeGachaTypeForTimestamp(this.scene, this.egg.timestamp);
const typeFilteredSpeciesPool = speciesPool
.filter(s => getPokemonSpecies(s).isOfType(type));
if (typeFilteredSpeciesPool.length)
speciesPool = typeFilteredSpeciesPool;
}
}
2024-03-01 19:18:39 -08:00
let totalWeight = 0;
const speciesWeights = [];
for (let speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId);
if (species.isRegional())
weight = Math.floor(weight / (species.isRareRegional() ? 8 : 2));
speciesWeights.push(totalWeight + weight);
totalWeight += weight;
}
2024-03-01 19:18:39 -08:00
let species: Species;
const rand = Utils.randSeedInt(totalWeight);
for (let s = 0; s < speciesWeights.length; s++) {
if (rand < speciesWeights[s]) {
species = speciesPool[s];
break;
}
}
2023-12-19 21:35:41 -08:00
const pokemonSpecies = getPokemonSpecies(species);
2024-01-07 20:17:24 -08:00
ret = this.scene.addPlayerPokemon(pokemonSpecies, 5, undefined, undefined, undefined, false);
2024-03-01 19:18:39 -08:00
}
2024-03-01 19:18:39 -08:00
ret.trySetShiny(this.egg.gachaType === GachaType.SHINY ? 1024 : 512);
const secondaryId = Utils.randSeedInt(4294967295);
const secondaryIvs = [
Utils.binToDec(Utils.decToBin(secondaryId).substring(0, 5)),
Utils.binToDec(Utils.decToBin(secondaryId).substring(5, 10)),
Utils.binToDec(Utils.decToBin(secondaryId).substring(10, 15)),
Utils.binToDec(Utils.decToBin(secondaryId).substring(15, 20)),
Utils.binToDec(Utils.decToBin(secondaryId).substring(20, 25)),
Utils.binToDec(Utils.decToBin(secondaryId).substring(25, 30))
];
2024-03-01 19:18:39 -08:00
for (let s = 0; s < ret.ivs.length; s++)
ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
this.eggMoveIndex = Utils.randSeedInt(6 * Math.pow(2, 3 - this.egg.tier))
? Utils.randSeedInt(3)
: 3;
2024-02-25 09:45:41 -08:00
}, this.egg.id, EGG_SEED.toString());
return ret;
}
}