pokerogue/src/ui/egg-gacha-ui-handler.ts
2024-05-03 13:04:32 -04:00

660 lines
23 KiB
TypeScript

import BattleScene, { Button } from "../battle-scene";
import { Mode } from "./ui";
import { TextStyle, addTextObject, getEggTierTextTint } from "./text";
import MessageUiHandler from "./message-ui-handler";
import * as Utils from "../utils";
import { EGG_SEED, Egg, GachaType, getEggTierDefaultHatchWaves, getEggDescriptor, getLegendaryGachaSpeciesForTimestamp } from "../data/egg";
import { VoucherType, getVoucherTypeIcon } from "../system/voucher";
import { getPokemonSpecies } from "../data/pokemon-species";
import { addWindow } from "./ui-theme";
import { Tutorial, handleTutorial } from "../tutorial";
import { EggTier } from "../data/enums/egg-type";
const defaultText = 'Select a machine.';
export default class EggGachaUiHandler extends MessageUiHandler {
private eggGachaContainer: Phaser.GameObjects.Container;
private eggGachaMessageBox: Phaser.GameObjects.NineSlice;
private eggGachaOptionsContainer: Phaser.GameObjects.Container;
private eggGachaOptionSelectBg: Phaser.GameObjects.NineSlice;
private gachaContainers: Phaser.GameObjects.Container[];
private gachaKnobs: Phaser.GameObjects.Sprite[];
private gachaHatches: Phaser.GameObjects.Sprite[];
private gachaInfoContainers: Phaser.GameObjects.Container[];
private eggGachaOverlay: Phaser.GameObjects.Rectangle;
private eggGachaSummaryContainer: Phaser.GameObjects.Container;
private voucherCountLabels: Phaser.GameObjects.Text[];
private gachaCursor: integer;
private cursorObj: Phaser.GameObjects.Image;
private transitioning: boolean;
private transitionCancelled: boolean;
constructor(scene: BattleScene) {
super(scene, Mode.EGG_GACHA);
this.gachaContainers = [];
this.gachaKnobs = [];
this.gachaHatches = [];
this.gachaInfoContainers = [];
this.voucherCountLabels = [];
}
setup() {
this.gachaCursor = 0;
const ui = this.getUi();
this.eggGachaContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6);
this.eggGachaContainer.setVisible(false);
ui.add(this.eggGachaContainer);
const bg = this.scene.add.nineslice(0, 0, 'default_bg', null, 320, 180, 0, 0, 16, 0);
bg.setOrigin(0, 0);
this.eggGachaContainer.add(bg);
const hatchFrameNames = this.scene.anims.generateFrameNames('gacha_hatch', { suffix: ".png", start: 1, end: 4 });
this.scene.anims.create({
key: 'open',
frames: hatchFrameNames,
frameRate: 12
});
this.scene.anims.create({
key: 'close',
frames: hatchFrameNames.reverse(),
frameRate: 12
});
Utils.getEnumValues(GachaType).forEach((gachaType, g) => {
const gachaTypeKey = GachaType[gachaType].toString().toLowerCase();
const gachaContainer = this.scene.add.container(180 * g, 18);
const gacha = this.scene.add.sprite(0, 0, `gacha_${gachaTypeKey}`);
gacha.setOrigin(0, 0);
const gachaUnderlay = this.scene.add.sprite(115, 80, `gacha_underlay_${gachaTypeKey}`);
gachaUnderlay.setOrigin(0, 0);
const gachaEggs = this.scene.add.sprite(0, 0, 'gacha_eggs');
gachaEggs.setOrigin(0, 0);
const gachaGlass = this.scene.add.sprite(0, 0, 'gacha_glass');
gachaGlass.setOrigin(0, 0);
const gachaInfoContainer = this.scene.add.container(160, 46);
const gachaUpLabel = addTextObject(this.scene, 4, 0, 'UP!', TextStyle.WINDOW_ALT);
gachaUpLabel.setOrigin(0, 0);
gachaInfoContainer.add(gachaUpLabel);
switch (gachaType as GachaType) {
case GachaType.LEGENDARY:
const pokemonIcon = this.scene.add.sprite(-20, 6, 'pokemon_icons_0');
pokemonIcon.setScale(0.5);
pokemonIcon.setOrigin(0, 0.5);
gachaInfoContainer.add(pokemonIcon);
break;
case GachaType.MOVE:
gachaUpLabel.setText('Move UP!');
gachaUpLabel.setX(0);
gachaUpLabel.setOrigin(0.5, 0);
break;
case GachaType.SHINY:
gachaUpLabel.setText('Shiny UP!');
gachaUpLabel.setX(0);
gachaUpLabel.setOrigin(0.5, 0);
break;
}
const gachaKnob = this.scene.add.sprite(191, 89, 'gacha_knob');
const gachaHatch = this.scene.add.sprite(115, 73, 'gacha_hatch');
gachaHatch.setOrigin(0, 0);
gachaContainer.add(gachaEggs);
gachaContainer.add(gachaUnderlay);
gachaContainer.add(gacha);
gachaContainer.add(gachaGlass);
gachaContainer.add(gachaKnob);
gachaContainer.add(gachaHatch);
gachaContainer.add(gachaInfoContainer);
gachaGlass.setAlpha(0.5);
gachaHatch.setAlpha(0.9);
gachaHatch.on('animationupdate', (_anim, frame) => gachaUnderlay.setFrame(frame.textureFrame === '4.png' ? 'open_hatch' : 'default'));
this.gachaContainers.push(gachaContainer);
this.gachaKnobs.push(gachaKnob);
this.gachaHatches.push(gachaHatch);
this.gachaInfoContainers.push(gachaInfoContainer);
this.eggGachaContainer.add(gachaContainer);
this.updateGachaInfo(g);
});
this.eggGachaOptionsContainer = this.scene.add.container()
this.eggGachaOptionsContainer = this.scene.add.container((this.scene.game.canvas.width / 6), 148);
this.eggGachaContainer.add(this.eggGachaOptionsContainer);
this.eggGachaOptionSelectBg = addWindow(this.scene, 0, 0, 96, 112);
this.eggGachaOptionSelectBg.setOrigin(1, 1);
this.eggGachaOptionsContainer.add(this.eggGachaOptionSelectBg);
const optionText = addTextObject(this.scene, 0, 0, ' x1 1 Pull\n x10 10 Pulls\n x1 5 Pulls\n x1 10 Pulls\n x1 25 Pulls\nCancel', TextStyle.WINDOW);
optionText.setLineSpacing(12);
this.eggGachaOptionsContainer.add(optionText);
optionText.setPositionRelative(this.eggGachaOptionSelectBg, 16, 9);
new Array(5).fill(null).map((_, i) => {
const voucherType = i < 2 ? VoucherType.REGULAR : i === 2 ? VoucherType.PLUS : i === 3 ? VoucherType.PREMIUM : VoucherType.GOLDEN;
const icon = this.scene.add.sprite(0, 0, 'items', getVoucherTypeIcon(voucherType));
icon.setScale(0.5);
icon.setPositionRelative(this.eggGachaOptionSelectBg, 20, 17 + i * 16);
this.eggGachaOptionsContainer.add(icon);
});
this.eggGachaContainer.add(this.eggGachaOptionsContainer);
new Array(Utils.getEnumKeys(VoucherType).length).fill(null).map((_, i) => {
const container = this.scene.add.container((this.scene.game.canvas.width / 6) - 56 * i, 0);
const bg = addWindow(this.scene, 0, 0, 56, 22);
bg.setOrigin(1, 0);
container.add(bg);
const countLabel = addTextObject(this.scene, -48, 3, '0', TextStyle.WINDOW);
countLabel.setOrigin(0, 0);
container.add(countLabel);
this.voucherCountLabels.push(countLabel);
const iconImage = getVoucherTypeIcon(i as VoucherType);
const icon = this.scene.add.sprite(-19, 2, 'items', iconImage);
icon.setOrigin(0, 0);
icon.setScale(0.5);
container.add(icon);
this.eggGachaContainer.add(container);
});
this.eggGachaOverlay = this.scene.add.rectangle(0, 0, bg.displayWidth, bg.displayHeight, 0x000000);
this.eggGachaOverlay.setOrigin(0, 0);
this.eggGachaOverlay.setAlpha(0);
this.eggGachaContainer.add(this.eggGachaOverlay);
this.eggGachaSummaryContainer = this.scene.add.container(0, 0);
this.eggGachaSummaryContainer.setVisible(false);
this.eggGachaContainer.add(this.eggGachaSummaryContainer);
const gachaMessageBoxContainer = this.scene.add.container(0, 148);
this.eggGachaContainer.add(gachaMessageBoxContainer);
const gachaMessageBox = addWindow(this.scene, 0, 0, 320, 32);
gachaMessageBox.setOrigin(0, 0);
gachaMessageBoxContainer.add(gachaMessageBox);
this.eggGachaMessageBox = gachaMessageBox;
const gachaMessageText = addTextObject(this.scene, 8, 8, '', TextStyle.WINDOW, { maxLines: 2 });
gachaMessageText.setOrigin(0, 0);
gachaMessageBoxContainer.add(gachaMessageText);
this.message = gachaMessageText;
this.eggGachaContainer.add(gachaMessageBoxContainer);
this.setCursor(0);
}
show(args: any[]): boolean {
super.show(args);
this.getUi().showText(defaultText, 0);
this.setGachaCursor(1);
for (let g = 0; g < this.gachaContainers.length; g++)
this.updateGachaInfo(g);
this.updateVoucherCounts();
this.getUi().bringToTop(this.eggGachaContainer);
this.eggGachaContainer.setVisible(true);
handleTutorial(this.scene, Tutorial.Egg_Gacha);
return true;
}
getDelayValue(delay: integer) {
if (this.transitioning && this.transitionCancelled)
delay = Math.ceil(delay / 5);
return Utils.fixedInt(delay);
}
pull(pullCount?: integer, count?: integer, eggs?: Egg[]): void {
this.eggGachaOptionsContainer.setVisible(false);
this.setTransitioning(true);
const doPull = () => {
if (this.transitionCancelled)
return this.showSummary(eggs);
const egg = this.scene.add.sprite(127, 75, 'egg', `egg_${eggs[count].getKey()}`);
egg.setScale(0.5);
this.gachaContainers[this.gachaCursor].add(egg);
this.gachaContainers[this.gachaCursor].moveTo(egg, 2);
const doPullAnim = () => {
this.scene.playSound('gacha_running', { loop: true });
this.scene.time.delayedCall(this.getDelayValue(count ? 500 : 1250), () => {
this.scene.playSound('gacha_dispense');
this.scene.time.delayedCall(this.getDelayValue(750), () => {
this.scene.sound.stopByKey('gacha_running');
this.scene.tweens.add({
targets: egg,
duration: this.getDelayValue(350),
y: 95,
ease: 'Bounce.easeOut',
onComplete: () => {
this.scene.time.delayedCall(this.getDelayValue(125), () => {
this.scene.playSound('pb_catch');
this.gachaHatches[this.gachaCursor].play('open');
this.scene.tweens.add({
targets: egg,
duration: this.getDelayValue(350),
scale: 0.75,
ease: 'Sine.easeIn'
});
this.scene.tweens.add({
targets: egg,
y: 110,
duration: this.getDelayValue(350),
ease: 'Back.easeOut',
onComplete: () => {
this.gachaHatches[this.gachaCursor].play('close');
this.scene.tweens.add({
targets: egg,
y: 200,
duration: this.getDelayValue(350),
ease: 'Cubic.easeIn',
onComplete: () => {
if (++count < pullCount)
this.pull(pullCount, count, eggs);
else
this.showSummary(eggs);
}
});
}
});
});
}
});
});
});
};
if (!count) {
this.scene.playSound('gacha_dial');
this.scene.tweens.add({
targets: this.gachaKnobs[this.gachaCursor],
duration: this.getDelayValue(350),
angle: 90,
ease: 'Cubic.easeInOut',
onComplete: () => {
this.scene.tweens.add({
targets: this.gachaKnobs[this.gachaCursor],
duration: this.getDelayValue(350),
angle: 0,
ease: 'Sine.easeInOut'
});
this.scene.time.delayedCall(this.getDelayValue(350), doPullAnim);
}
});
} else
doPullAnim();
};
if (!pullCount)
pullCount = 1;
if (!count)
count = 0;
if (!eggs) {
eggs = [];
const tierValueOffset = this.gachaCursor === GachaType.LEGENDARY ? 1 : 0;
const tiers = new Array(pullCount).fill(null).map(() => {
const tierValue = Utils.randInt(256);
return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
});
if (pullCount >= 25 && !tiers.filter(t => t >= EggTier.ULTRA).length)
tiers[Utils.randInt(tiers.length)] = EggTier.ULTRA;
else if (pullCount >= 10 && !tiers.filter(t => t >= EggTier.GREAT).length)
tiers[Utils.randInt(tiers.length)] = EggTier.GREAT;
const timestamp = new Date().getTime();
for (let tier of tiers) {
const egg = new Egg(Utils.randInt(EGG_SEED, EGG_SEED * tier), this.gachaCursor, getEggTierDefaultHatchWaves(tier), timestamp);
if (egg.isManaphyEgg()) {
this.scene.gameData.gameStats.manaphyEggsPulled++;
egg.hatchWaves = getEggTierDefaultHatchWaves(EggTier.ULTRA);
} else {
switch (tier) {
case EggTier.GREAT:
this.scene.gameData.gameStats.rareEggsPulled++;
break;
case EggTier.ULTRA:
this.scene.gameData.gameStats.epicEggsPulled++;
break;
case EggTier.MASTER:
this.scene.gameData.gameStats.legendaryEggsPulled++;
break;
}
}
eggs.push(egg);
this.scene.gameData.eggs.push(egg);
this.scene.gameData.gameStats.eggsPulled++;
}
this.scene.gameData.saveSystem().then(success => {
if (!success)
return this.scene.reset(true);
doPull();
});
return;
}
doPull();
}
showSummary(eggs: Egg[]): void {
this.transitioning = false;
this.eggGachaSummaryContainer.setVisible(true);
const eggScale = eggs.length < 20 ? 1 : 0.5;
this.scene.tweens.add({
targets: this.eggGachaOverlay,
alpha: 0.5,
ease: 'Sine.easeOut',
duration: 750,
onComplete: () => {
const rowItems = 5;
const rows = Math.ceil(eggs.length / rowItems);
const cols = Math.min(eggs.length, rowItems);
const height = this.eggGachaOverlay.displayHeight - this.eggGachaMessageBox.displayHeight;
const eggContainers = eggs.map((egg, t) => {
const col = t % rowItems;
const row = Math.floor(t / rowItems);
const sliceWidth = this.eggGachaOverlay.displayWidth / (cols + 2);
const sliceHeight = height / (rows + 2);
const yOffset = (sliceHeight / 2 * (row / Math.max(rows - 1, 1))) + sliceHeight / 4;
const ret = this.scene.add.container(sliceWidth * (col + 1) + (sliceWidth * 0.5), sliceHeight * (row + 1) + yOffset);
ret.setScale(0.0001);
const eggSprite = this.scene.add.sprite(0, 0, 'egg', `egg_${egg.getKey()}`);
ret.add(eggSprite);
const eggText = addTextObject(this.scene, 0, 14, getEggDescriptor(egg), TextStyle.PARTY, { align: 'center' });
eggText.setOrigin(0.5, 0);
eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA));
ret.add(eggText);
this.eggGachaSummaryContainer.addAt(ret, 0);
return ret;
});
eggContainers.forEach((eggContainer, e) => {
this.scene.tweens.add({
targets: eggContainer,
delay: this.getDelayValue(e * 100),
duration: this.getDelayValue(350),
scale: eggScale,
ease: 'Sine.easeOut'
});
});
}
});
}
hideSummary() {
this.setTransitioning(true);
this.scene.tweens.add({
targets: [ this.eggGachaOverlay, this.eggGachaSummaryContainer ],
alpha: 0,
duration: this.getDelayValue(250),
ease: 'Cubic.easeIn',
onComplete: () => {
this.eggGachaSummaryContainer.setVisible(false);
this.eggGachaSummaryContainer.setAlpha(1);
this.eggGachaSummaryContainer.removeAll(true);
this.setTransitioning(false);
this.eggGachaOptionsContainer.setVisible(true);
}
});
}
updateGachaInfo(gachaType: GachaType): void {
const infoContainer = this.gachaInfoContainers[gachaType];
switch (gachaType as GachaType) {
case GachaType.LEGENDARY:
const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime()));
const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite;
pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false));
break;
}
}
consumeVouchers(voucherType: VoucherType, count: integer): void {
this.scene.gameData.voucherCounts[voucherType] = Math.max(this.scene.gameData.voucherCounts[voucherType] - count, 0);
this.updateVoucherCounts();
}
updateVoucherCounts(): void {
this.voucherCountLabels.forEach((label, type) => {
label.setText(this.scene.gameData.voucherCounts[type].toString());
});
}
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void {
if (!text)
text = defaultText;
if (text?.indexOf('\n') === -1) {
this.eggGachaMessageBox.setSize(320, 32);
this.eggGachaMessageBox.setY(0);
this.message.setY(8);
} else {
this.eggGachaMessageBox.setSize(320, 46);
this.eggGachaMessageBox.setY(-14);
this.message.setY(-6);
}
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
}
showError(text: string): void {
this.showText(text, null, () => this.showText(defaultText), Utils.fixedInt(1500));
}
setTransitioning(transitioning: boolean): void {
if (this.transitioning === transitioning)
return;
this.transitioning = transitioning;
this.transitionCancelled = false;
}
processInput(button: Button): boolean {
const ui = this.getUi();
let success = false;
let error = false;
if (this.transitioning) {
if (!this.transitionCancelled && (button === Button.ACTION || button === Button.CANCEL)) {
this.transitionCancelled = true;
success = true;
} else
return false;
} else {
if (this.eggGachaSummaryContainer.visible) {
if (button === Button.ACTION || button === Button.CANCEL) {
this.hideSummary();
success = true;
}
} else {
switch (button) {
case Button.ACTION:
switch (this.cursor) {
case 0:
if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR]) {
error = true;
this.showError('You don\'t have enough vouchers!');
} else if (this.scene.gameData.eggs.length < 99) {
this.consumeVouchers(VoucherType.REGULAR, 1);
this.pull();
success = true;
} else {
error = true;
this.showError('You have too many eggs!');
}
break;
case 2:
if (!this.scene.gameData.voucherCounts[VoucherType.PLUS]) {
error = true;
this.showError('You don\'t have enough vouchers!');
} else if (this.scene.gameData.eggs.length < 95) {
this.consumeVouchers(VoucherType.PLUS, 1);
this.pull(5);
success = true;
} else {
error = true;
this.showError('You have too many eggs!');
}
break;
case 1:
case 3:
if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10)
|| (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM])) {
error = true;
this.showError('You don\'t have enough vouchers!');
} else if (this.scene.gameData.eggs.length < 90) {
if (this.cursor === 3)
this.consumeVouchers(VoucherType.PREMIUM, 1);
else
this.consumeVouchers(VoucherType.REGULAR, 10);
this.pull(10);
success = true;
} else {
error = true;
this.showError('You have too many eggs!');
}
break;
case 4:
if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN]) {
error = true;
this.showError('You don\'t have enough vouchers!');
} else if (this.scene.gameData.eggs.length < 75) {
this.consumeVouchers(VoucherType.GOLDEN, 1);
this.pull(25);
success = true;
} else {
error = true;
this.showError('You have too many eggs!');
}
break;
case 5:
ui.revertMode();
success = true;
break;
}
break;
case Button.CANCEL:
this.getUi().revertMode();
success = true;
break;
case Button.UP:
if (this.cursor)
success = this.setCursor(this.cursor - 1);
break;
case Button.DOWN:
if (this.cursor < 5)
success = this.setCursor(this.cursor + 1);
break;
case Button.LEFT:
if (this.gachaCursor)
success = this.setGachaCursor(this.gachaCursor - 1);
break;
case Button.RIGHT:
if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1)
success = this.setGachaCursor(this.gachaCursor + 1);
break;
}
}
}
if (success)
ui.playSelect();
else if (error)
ui.playError();
return success || error;
}
setCursor(cursor: integer): boolean {
const ret = super.setCursor(cursor);
if (!this.cursorObj) {
this.cursorObj = this.scene.add.image(0, 0, 'cursor');
this.eggGachaOptionsContainer.add(this.cursorObj);
}
this.cursorObj.setPositionRelative(this.eggGachaOptionSelectBg, 10, 17 + this.cursor * 16);
return ret;
}
setGachaCursor(cursor: integer): boolean {
let oldCursor = this.gachaCursor;
let changed = oldCursor !== cursor;
if (changed) {
this.gachaCursor = cursor;
this.setTransitioning(true);
this.scene.tweens.add({
targets: this.gachaContainers,
duration: this.eggGachaContainer.visible ? 500 : 0,
x: (_target, _key, _value, index) => 180 * (index - cursor),
ease: 'Cubic.easeInOut',
onComplete: () => this.setTransitioning(false)
});
}
return changed;
}
clear(): void {
super.clear();
this.setGachaCursor(-1);
this.eggGachaContainer.setVisible(false);
}
}