pokerogue/src/egg-hatch-phase.ts

452 lines
16 KiB
TypeScript
Raw Normal View History

import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import i18next from "i18next";
import { Phase } from "./phase";
import BattleScene, { AnySound } from "./battle-scene";
import * as Utils from "./utils";
import { Mode } from "./ui/ui";
[Feature] [Same species Egg] Egg class rewrite to enable fully parameterized eggs to generate same species eggs + Egg overrides (#1833) * Create variant-tiers enum * Added variant tier override property to the egg class * Added hasVariants function to pokemon species * Implement variant override logic to egg hatching phase * Delete src/enums/variant-tiers * Create variant-tiers enum * Added egg shiny and variant overrides * fixed egg comment in overrides.ts * Added override logic to egg hatch phase * Added species pool filter logic when global override is set * Added global egg tier override logic * Added global egg tier override * Added global gacha pull count override logic * Added global gacha pull count override * Renamed egg hatch override * Renamed egg hatch override * Added gacha pull without voucher global override * Renamed free gacha pull global override * Added free gacha pull override logic * Gacha pull count override name fix * Bugfix * restored defaults + savegame bugfix * eggOptions added to parameterize eggs. Added option to buy eggs of the same species. * Small Bugfix for same species egg generation * Removed translation from translator * Improved the isManaphyEgg() check * Fixed manaphy egg hatch wave count * Added comments to IEggOptions * Added eggOptions for hidden ability and rare egg move override * Merge Fix: Update egg-hatch-phase.ts * Fixed manaphy rates back to 1/256 like in PR #2182 * Renamed override, same species egg unlocks after passive is bought. Added code as comment for custom shiny, HA and rare egg move rates. * Merge fix. Moved enums. * quick fix for the commented out code * Fixed that you can't buy an egg over the 99 egg limit * Fix that you can't buy eternatus * Use already existing randSeedShuffle instead of my own function * Eternatus buyable again. Changed overrides to be able to set common tier/variants. Moved getGuaranteedEggTierFromPullCount(). * Changed eggOption gachaType to sourceType. Replaced eggOption overrideRareEggMove with eggMoveIndex to exatly specify an egg move. Moved egg move unlock logic into the egg class. Simplified shiny calculation. Added same species egg type descriptor. Moved custom rates for same species egg code into egg.ts. * Added 19 unit tests for eggs * Changed unit test description * Added higher rates for same species eggs * Adjusted same species egg cost for 1-3 cost starters and HA rates * Added legacy egg loading unit test. Fixed gachaType legacy value loaded from DB and legacy tier loading * Legacy egg loading from server DB fixed
2024-06-22 02:19:56 +02:00
import { EGG_SEED, Egg } from "./data/egg";
import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler";
2024-02-29 20:08:50 -05:00
import { PlayerPokemon } from "./field/pokemon";
import { achvs } from "./system/achv";
2024-03-07 22:43:15 -05:00
import PokemonInfoContainer from "./ui/pokemon-info-container";
import EggCounterContainer from "./ui/egg-counter-container";
import { EggCountChangedEvent } from "./events/egg";
/**
* Class that represents egg hatching
*/
export class EggHatchPhase extends Phase {
/** The egg that is hatching */
private egg: Egg;
/** The number of eggs that are hatching */
private eggsToHatchCount: integer;
/** The container that lists how many eggs are hatching */
private eggCounterContainer: EggCounterContainer;
/** The scene handler for egg hatching */
2024-04-05 14:04:01 -04:00
private eggHatchHandler: EggHatchSceneHandler;
/** The phaser gameobject container that holds everything */
private eggHatchContainer: Phaser.GameObjects.Container;
/** The phaser image that is the background */
private eggHatchBg: Phaser.GameObjects.Image;
/** The phaser rectangle that overlays during the scene */
private eggHatchOverlay: Phaser.GameObjects.Rectangle;
/** The phaser container that holds the egg */
private eggContainer: Phaser.GameObjects.Container;
/** The phaser sprite of the egg */
private eggSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite of the cracks in an egg */
private eggCrackSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite that represents the overlaid light rays */
private eggLightraysOverlay: Phaser.GameObjects.Sprite;
/** The phaser sprite of the hatched Pokemon */
private pokemonSprite: Phaser.GameObjects.Sprite;
/** The phaser sprite for shiny sparkles */
private pokemonShinySparkle: Phaser.GameObjects.Sprite;
/** The {@link PokemonInfoContainer} of the newly hatched Pokemon */
2024-03-07 22:43:15 -05:00
private infoContainer: PokemonInfoContainer;
/** The newly hatched {@link PlayerPokemon} */
2024-02-20 00:24:39 -05:00
private pokemon: PlayerPokemon;
/** The index of which egg move is unlocked. 0-2 is common, 3 is rare */
2024-02-25 12:45:41 -05:00
private eggMoveIndex: integer;
/** Internal booleans representing if the egg is hatched, able to be skipped, or skipped */
2024-04-04 18:54:50 -04:00
private hatched: boolean;
2024-02-20 00:24:39 -05:00
private canSkip: boolean;
private skipped: boolean;
/** The sound effect being played when the egg is hatched */
2024-02-20 00:24:39 -05:00
private evolutionBgm: AnySound;
constructor(scene: BattleScene, egg: Egg, eggsToHatchCount: integer) {
super(scene);
this.egg = egg;
this.eggsToHatchCount = eggsToHatchCount;
}
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);
2024-04-05 14:04:01 -04:00
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
2024-04-05 14:04:01 -04:00
this.eggHatchContainer = this.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);
this.eggCounterContainer = new EggCounterContainer(this.scene, this.eggsToHatchCount);
this.eggHatchContainer.add(this.eggCounterContainer);
const getPokemonSprite = () => {
const ret = this.scene.add.sprite(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2, "pkmn__sub");
ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
return ret;
};
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-03-07 22:43:15 -05:00
this.infoContainer = new PokemonInfoContainer(this.scene);
this.infoContainer.setup();
2024-01-05 22:24:05 -05:00
this.eggHatchContainer.add(this.infoContainer);
// The game will try to unfuse any Pokemon even though eggs should not generate fused Pokemon in the first place
const pokemon = this.generatePokemon();
if (pokemon.fusionSpecies) {
pokemon.clearFusionSpecies();
}
this.pokemonSprite.setVisible(false);
2024-02-20 00:24:39 -05:00
this.pokemon = pokemon;
pokemon.loadAssets().then(() => {
2024-04-04 18:54:50 -04:00
this.canSkip = true;
2024-02-20 00:24:39 -05:00
this.scene.time.delayedCall(1000, () => {
if (!this.hatched) {
this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution");
}
2024-02-20 00:24:39 -05:00
});
this.scene.time.delayedCall(2000, () => {
if (this.hatched) {
2024-02-20 00:24:39 -05:00
return;
}
this.eggCrackSprite.setVisible(true);
this.doSpray(1, this.eggSprite.displayHeight / -2);
this.doEggShake(2).then(() => {
if (this.hatched) {
2024-04-06 21:48:48 -04:00
return;
}
this.scene.time.delayedCall(1000, () => {
if (this.hatched) {
2024-02-20 00:24:39 -05:00
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(() => {
if (this.hatched) {
2024-02-20 00:24:39 -05:00
return;
}
this.scene.time.delayedCall(1000, () => {
if (this.hatched) {
2024-02-20 00:24:39 -05:00
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(() => {
if (!this.hatched) {
2024-02-20 00:24:39 -05:00
this.doHatch();
}
});
});
});
});
});
});
});
});
}
2024-04-06 21:48:48 -04:00
2024-04-05 14:04:01 -04:00
end() {
if (this.scene.findPhase((p) => p instanceof EggHatchPhase)) {
this.eggHatchHandler.clear();
} else {
2024-04-06 21:48:48 -04:00
this.scene.time.delayedCall(250, () => this.scene.setModifiersVisible(true));
}
super.end();
2024-04-05 14:04:01 -04:00
}
/**
* Function that animates egg shaking
* @param intensity of horizontal shaking. Doubled on the first call (where count is 0)
* @param repeatCount the number of times this function should be called (asynchronous recursion?!?)
* @param count the current number of times this function has been called.
* @returns nothing since it's a Promise<void>
*/
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()
});
}
});
}
});
});
}
/**
* Tries to skip the hatching animation
* @returns false if cannot be skipped or already skipped. True otherwise
*/
2024-02-20 00:24:39 -05:00
trySkip(): boolean {
if (!this.canSkip || this.skipped) {
2024-02-20 00:24:39 -05:00
return false;
}
2024-06-16 00:48:09 -04:00
if (this.eggCounterContainer.eggCountText?.data === undefined) {
return false;
}
2024-02-20 00:24:39 -05:00
this.skipped = true;
if (!this.hatched) {
2024-04-04 18:54:50 -04:00
this.doHatch();
} else {
2024-04-04 18:54:50 -04:00
this.doReveal();
}
2024-02-20 00:24:39 -05:00
return true;
}
/**
* Plays the animation of an egg hatch
*/
2024-02-20 00:24:39 -05:00
doHatch(): void {
this.canSkip = false;
2024-04-04 18:54:50 -04:00
this.hatched = true;
if (this.evolutionBgm) {
2024-04-04 18:54:50 -04:00
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) }));
}
2024-02-20 00:24:39 -05:00
this.eggLightraysOverlay.setVisible(true);
this.eggLightraysOverlay.play("egg_lightrays");
2024-02-20 00:24:39 -05:00
this.scene.tweens.add({
duration: Utils.fixedInt(125),
targets: this.eggHatchOverlay,
alpha: 1,
ease: "Cubic.easeIn",
2024-04-04 18:54:50 -04:00
onComplete: () => {
this.skipped = false;
this.canSkip = true;
}
2024-02-20 00:24:39 -05:00
});
this.scene.time.delayedCall(Utils.fixedInt(1500), () => {
2024-04-04 18:54:50 -04:00
this.canSkip = false;
if (!this.skipped) {
2024-04-04 18:54:50 -04:00
this.doReveal();
}
2024-04-04 18:54:50 -04:00
});
}
/**
* Function to do the logic and animation of completing a hatch and revealing the Pokemon
*/
2024-04-04 18:54:50 -04:00
doReveal(): void {
const isShiny = this.pokemon.isShiny();
if (this.pokemon.species.subLegendary) {
this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
}
if (this.pokemon.species.legendary) {
2024-04-04 18:54:50 -04:00
this.scene.validateAchv(achvs.HATCH_LEGENDARY);
}
if (this.pokemon.species.mythical) {
this.scene.validateAchv(achvs.HATCH_MYTHICAL);
}
if (isShiny) {
2024-04-04 18:54:50 -04:00
this.scene.validateAchv(achvs.HATCH_SHINY);
}
2024-04-04 18:54:50 -04:00
this.eggContainer.setVisible(false);
this.pokemonSprite.play(this.pokemon.getSpriteKey(true));
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
this.pokemonSprite.setPipelineData("variant", this.pokemon.variant);
2024-04-04 18:54:50 -04:00
this.pokemonSprite.setVisible(true);
this.scene.time.delayedCall(Utils.fixedInt(250), () => {
this.eggsToHatchCount--;
this.eggHatchHandler.eventTarget.dispatchEvent(new EggCountChangedEvent(this.eggsToHatchCount));
2024-04-04 18:54:50 -04:00
this.pokemon.cry();
if (isShiny) {
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
this.pokemonShinySparkle.play(`sparkle${this.pokemon.variant ? `_${this.pokemon.variant + 1}` : ""}`);
this.scene.playSound("sparkle");
2024-02-20 00:24:39 -05:00
});
2024-04-04 18:54:50 -04:00
}
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1);
this.scene.playSoundWithoutBgm("evolution_fanfare");
2024-05-24 01:45:04 +02:00
this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: this.pokemon.name }), null, () => {
2024-04-04 18:54:50 -04:00
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
this.scene.ui.showText(null, 0);
this.end();
});
});
}, null, true, 3000);
//this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
2024-02-20 00:24:39 -05:00
});
2024-04-04 18:54:50 -04:00
});
this.scene.tweens.add({
duration: Utils.fixedInt(this.skipped ? 500 : 3000),
targets: this.eggHatchOverlay,
alpha: 0,
ease: "Cubic.easeOut"
2024-02-20 00:24:39 -05:00
});
}
/**
* Helper function to generate sine. (Why is this not a Utils?!?)
* @param index random number from 0-7 being passed in to scale pi/128
* @param amplitude Scaling
* @returns a number
*/
sin(index: integer, amplitude: integer): number {
return amplitude * Math.sin(index * (Math.PI / 128));
}
/**
* Animates spraying
* @param intensity number of times this is repeated (this is a badly named variable)
* @param offsetY how much to offset the Y coordinates
*/
doSpray(intensity: integer, offsetY?: number) {
this.scene.tweens.addCounter({
repeat: intensity,
duration: Utils.getFrameMs(1),
onRepeat: () => {
this.doSprayParticle(Utils.randInt(8), offsetY || 0);
}
});
}
/**
* Animates a particle used in the spray animation
* @param trigIndex Used to modify the particle's vertical speed, is a random number from 0-7
* @param offsetY how much to offset the Y coordinate
*/
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;
const speed = 3 - Utils.randInt(8);
const amp = 24 + Utils.randInt(32);
const particleTimer = this.scene.tweens.addCounter({
repeat: -1,
duration: Utils.getFrameMs(1),
onRepeat: () => {
updateParticle();
}
});
const updateParticle = () => {
2024-04-04 18:54:50 -04:00
const speedMultiplier = this.skipped ? 6 : 1;
yOffset += speedMultiplier;
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));
}
2024-04-04 18:54:50 -04:00
trigIndex += 2 * speedMultiplier;
f += speedMultiplier;
} else {
particle.destroy();
particleTimer.remove();
}
};
updateParticle();
}
/**
* Generates a Pokemon to be hatched by the egg
* @returns the hatched PlayerPokemon
*/
generatePokemon(): PlayerPokemon {
let ret: PlayerPokemon;
2024-03-01 22:18:39 -05:00
this.scene.executeWithSeedOffset(() => {
[Feature] [Same species Egg] Egg class rewrite to enable fully parameterized eggs to generate same species eggs + Egg overrides (#1833) * Create variant-tiers enum * Added variant tier override property to the egg class * Added hasVariants function to pokemon species * Implement variant override logic to egg hatching phase * Delete src/enums/variant-tiers * Create variant-tiers enum * Added egg shiny and variant overrides * fixed egg comment in overrides.ts * Added override logic to egg hatch phase * Added species pool filter logic when global override is set * Added global egg tier override logic * Added global egg tier override * Added global gacha pull count override logic * Added global gacha pull count override * Renamed egg hatch override * Renamed egg hatch override * Added gacha pull without voucher global override * Renamed free gacha pull global override * Added free gacha pull override logic * Gacha pull count override name fix * Bugfix * restored defaults + savegame bugfix * eggOptions added to parameterize eggs. Added option to buy eggs of the same species. * Small Bugfix for same species egg generation * Removed translation from translator * Improved the isManaphyEgg() check * Fixed manaphy egg hatch wave count * Added comments to IEggOptions * Added eggOptions for hidden ability and rare egg move override * Merge Fix: Update egg-hatch-phase.ts * Fixed manaphy rates back to 1/256 like in PR #2182 * Renamed override, same species egg unlocks after passive is bought. Added code as comment for custom shiny, HA and rare egg move rates. * Merge fix. Moved enums. * quick fix for the commented out code * Fixed that you can't buy an egg over the 99 egg limit * Fix that you can't buy eternatus * Use already existing randSeedShuffle instead of my own function * Eternatus buyable again. Changed overrides to be able to set common tier/variants. Moved getGuaranteedEggTierFromPullCount(). * Changed eggOption gachaType to sourceType. Replaced eggOption overrideRareEggMove with eggMoveIndex to exatly specify an egg move. Moved egg move unlock logic into the egg class. Simplified shiny calculation. Added same species egg type descriptor. Moved custom rates for same species egg code into egg.ts. * Added 19 unit tests for eggs * Changed unit test description * Added higher rates for same species eggs * Adjusted same species egg cost for 1-3 cost starters and HA rates * Added legacy egg loading unit test. Fixed gachaType legacy value loaded from DB and legacy tier loading * Legacy egg loading from server DB fixed
2024-06-22 02:19:56 +02:00
ret = this.egg.generatePlayerPokemon(this.scene);
this.eggMoveIndex = this.egg.eggMoveIndex;
2024-02-25 12:45:41 -05:00
}, this.egg.id, EGG_SEED.toString());
2024-05-24 01:45:04 +02:00
return ret;
}
}