Rework option select and add game stats

pull/14/head
Flashfyre 2024-01-11 12:26:32 -05:00
parent 13aa20630d
commit fcf1c4f574
14 changed files with 585 additions and 169 deletions

View File

@ -39,7 +39,6 @@ import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account"; import { loggedInUser, updateUserInfo } from "./account";
import { GameDataType } from "./system/game-data"; import { GameDataType } from "./system/game-data";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims";
import { getPokemonSpecies } from "./data/pokemon-species";
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger } from "./data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger } from "./data/pokemon-forms";
export class LoginPhase extends BattlePhase { export class LoginPhase extends BattlePhase {
@ -244,6 +243,10 @@ export class SelectStarterPhase extends BattlePhase {
this.scene.ui.setMode(Mode.MESSAGE).then(() => { this.scene.ui.setMode(Mode.MESSAGE).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true); SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm()); this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode === GameMode.CLASSIC)
this.scene.gameData.gameStats.classicSessionsPlayed++;
else
this.scene.gameData.gameStats.endlessSessionsPlayed++;
this.scene.newBattle(); this.scene.newBattle();
this.end(); this.end();
}); });
@ -1550,6 +1553,12 @@ export class BattleEndPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
this.scene.gameData.gameStats.battles++;
if (this.scene.currentBattle.trainer)
this.scene.gameData.gameStats.trainersDefeated++;
if (this.scene.gameMode === GameMode.ENDLESS && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave)
this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1;
for (let pokemon of this.scene.getField()) { for (let pokemon of this.scene.getField()) {
if (pokemon) if (pokemon)
pokemon.resetBattleSummonData(); pokemon.resetBattleSummonData();
@ -2418,6 +2427,8 @@ export class VictoryPhase extends PokemonPhase {
start() { start() {
super.start(); super.start();
this.scene.gameData.gameStats.pokemonDefeated++;
const participantIds = this.scene.currentBattle.playerParticipantIds; const participantIds = this.scene.currentBattle.playerParticipantIds;
const party = this.scene.getParty(); const party = this.scene.getParty();
const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
@ -2627,8 +2638,11 @@ export class GameOverPhase extends BattlePhase {
this.scene.gameData.clearSession().then(() => { this.scene.gameData.clearSession().then(() => {
this.scene.time.delayedCall(1000, () => { this.scene.time.delayedCall(1000, () => {
if (this.victory) if (this.victory) {
this.scene.validateAchv(achvs.CLASSIC_VICTORY); this.scene.validateAchv(achvs.CLASSIC_VICTORY);
this.scene.gameData.gameStats.sessionsWon++;
}
this.scene.gameData.saveSystem();
const fadeDuration = this.victory ? 10000 : 5000; const fadeDuration = this.victory ? 10000 : 5000;
this.scene.fadeOutBgm(fadeDuration, true); this.scene.fadeOutBgm(fadeDuration, true);
this.scene.ui.fadeOut(fadeDuration).then(() => { this.scene.ui.fadeOut(fadeDuration).then(() => {
@ -2801,6 +2815,9 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
start() { start() {
super.start(); super.start();
if (this.level > this.scene.gameData.gameStats.highestLevel)
this.scene.gameData.gameStats.highestLevel = this.level;
this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level));
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();

View File

@ -402,6 +402,15 @@ export default class BattleScene extends Phaser.Scene {
this.gameData = new GameData(this); this.gameData = new GameData(this);
this.time.addEvent({
delay: Utils.fixedInt(1000),
repeat: -1,
callback: () => {
if (this.gameData)
this.gameData.gameStats.playTime++;
}
})
this.setupControls(); this.setupControls();
this.load.setBaseURL(); this.load.setBaseURL();

View File

@ -1929,6 +1929,7 @@ export class PlayerPokemon extends Pokemon {
this.generateCompatibleTms(); this.generateCompatibleTms();
this.scene.gameData.setPokemonSeen(this, false); this.scene.gameData.setPokemonSeen(this, false);
this.scene.gameData.setPokemonCaught(this, false); this.scene.gameData.setPokemonCaught(this, false);
this.scene.gameData.gameStats.pokemonFused++;
this.loadAssets().then(() => { this.loadAssets().then(() => {
this.calculateStats(); this.calculateStats();
this.scene.updateModifiers(true, true); this.scene.updateModifiers(true, true);

View File

@ -21,6 +21,7 @@ import { AES, enc } from "crypto-js";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";
import { loggedInUser, updateUserInfo } from "../account"; import { loggedInUser, updateUserInfo } from "../account";
import { Nature } from "../data/nature"; import { Nature } from "../data/nature";
import { GameStats } from "./game-stats";
const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary
@ -45,6 +46,7 @@ interface SystemSaveData {
trainerId: integer; trainerId: integer;
secretId: integer; secretId: integer;
dexData: DexData; dexData: DexData;
gameStats: GameStats;
unlocks: Unlocks; unlocks: Unlocks;
achvUnlocks: AchvUnlocks; achvUnlocks: AchvUnlocks;
voucherUnlocks: VoucherUnlocks; voucherUnlocks: VoucherUnlocks;
@ -138,6 +140,8 @@ export class GameData {
public dexData: DexData; public dexData: DexData;
private defaultDexData: DexData; private defaultDexData: DexData;
public gameStats: GameStats;
public unlocks: Unlocks; public unlocks: Unlocks;
public achvUnlocks: AchvUnlocks; public achvUnlocks: AchvUnlocks;
@ -151,6 +155,7 @@ export class GameData {
this.loadSettings(); this.loadSettings();
this.trainerId = Utils.randSeedInt(65536); this.trainerId = Utils.randSeedInt(65536);
this.secretId = Utils.randSeedInt(65536); this.secretId = Utils.randSeedInt(65536);
this.gameStats = new GameStats();
this.unlocks = { this.unlocks = {
[Unlockables.ENDLESS_MODE]: false, [Unlockables.ENDLESS_MODE]: false,
[Unlockables.MINI_BLACK_HOLE]: false, [Unlockables.MINI_BLACK_HOLE]: false,
@ -174,13 +179,14 @@ export class GameData {
if (this.scene.quickStart) if (this.scene.quickStart)
return resolve(true); return resolve(true);
updateUserInfo().then(success => { updateUserInfo().then((success: boolean) => {
if (!success) if (!success)
return resolve(false); return resolve(false);
const data: SystemSaveData = { const data: SystemSaveData = {
trainerId: this.trainerId, trainerId: this.trainerId,
secretId: this.secretId, secretId: this.secretId,
dexData: this.dexData, dexData: this.dexData,
gameStats: this.gameStats,
unlocks: this.unlocks, unlocks: this.unlocks,
achvUnlocks: this.achvUnlocks, achvUnlocks: this.achvUnlocks,
voucherUnlocks: this.voucherUnlocks, voucherUnlocks: this.voucherUnlocks,
@ -233,6 +239,9 @@ export class GameData {
this.trainerId = systemData.trainerId; this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId; this.secretId = systemData.secretId;
if (systemData.gameStats)
this.gameStats = systemData.gameStats;
if (systemData.unlocks) { if (systemData.unlocks) {
for (let key of Object.keys(systemData.unlocks)) { for (let key of Object.keys(systemData.unlocks)) {
if (this.unlocks.hasOwnProperty(key)) if (this.unlocks.hasOwnProperty(key))
@ -290,7 +299,9 @@ export class GameData {
private parseSystemData(dataStr: string): SystemSaveData { private parseSystemData(dataStr: string): SystemSaveData {
return JSON.parse(dataStr, (k: string, v: any) => { return JSON.parse(dataStr, (k: string, v: any) => {
if (k === 'eggs') { if (k === 'gameStats')
return new GameStats(v);
else if (k === 'eggs') {
const ret: EggData[] = []; const ret: EggData[] = [];
if (v === null) if (v === null)
v = []; v = [];
@ -418,6 +429,9 @@ export class GameData {
scene.money = sessionData.money || 0; scene.money = sessionData.money || 0;
scene.updateMoneyText(); scene.updateMoneyText();
if (scene.money > this.gameStats.highestMoney)
this.gameStats.highestMoney = scene.money;
const battleType = sessionData.battleType || 0; const battleType = sessionData.battleType || 0;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfigs[sessionData.trainer.trainerType].isDouble : sessionData.enemyParty.length > 1); const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfigs[sessionData.trainer.trainerType].isDouble : sessionData.enemyParty.length > 1);
@ -706,8 +720,12 @@ export class GameData {
setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true): void { setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true): void {
const dexEntry = this.dexData[pokemon.species.speciesId]; const dexEntry = this.dexData[pokemon.species.speciesId];
dexEntry.seenAttr |= pokemon.getDexAttr(); dexEntry.seenAttr |= pokemon.getDexAttr();
if (incrementCount) if (incrementCount) {
dexEntry.seenCount++; dexEntry.seenCount++;
this.gameStats.pokemonSeen++;
if (pokemon.isShiny())
this.gameStats.shinyPokemonSeen++;
}
} }
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> { setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
@ -721,10 +739,25 @@ export class GameData {
dexEntry.caughtAttr |= pokemon.getDexAttr(); dexEntry.caughtAttr |= pokemon.getDexAttr();
dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1); dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1);
if (incrementCount) { if (incrementCount) {
if (!fromEgg) if (!fromEgg) {
dexEntry.caughtCount++; dexEntry.caughtCount++;
else this.gameStats.pokemonCaught++;
if (pokemon.species.pseudoLegendary || pokemon.species.legendary)
this.gameStats.legendaryPokemonCaught++;
else if (pokemon.species.mythical)
this.gameStats.mythicalPokemonCaught++;
if (pokemon.isShiny())
this.gameStats.shinyPokemonCaught++;
} else {
dexEntry.hatchedCount++; dexEntry.hatchedCount++;
this.gameStats.pokemonHatched++;
if (pokemon.species.pseudoLegendary || pokemon.species.legendary)
this.gameStats.legendaryPokemonHatched++;
else if (pokemon.species.mythical)
this.gameStats.mythicalPokemonHatched++;
if (pokemon.isShiny())
this.gameStats.shinyPokemonHatched++;
}
} }
const hasPrevolution = pokemonPrevolutions.hasOwnProperty(species.speciesId); const hasPrevolution = pokemonPrevolutions.hasOwnProperty(species.speciesId);

64
src/system/game-stats.ts Normal file
View File

@ -0,0 +1,64 @@
// public (.*?): integer;
// this.$1 = source?.$1 || 0;
export class GameStats {
public playTime: integer;
public battles: integer;
public classicSessionsPlayed: integer;
public sessionsWon: integer;
public endlessSessionsPlayed: integer;
public highestEndlessWave: integer;
public highestLevel: integer;
public highestMoney: integer;
public pokemonSeen: integer;
public pokemonDefeated: integer;
public pokemonCaught: integer;
public pokemonHatched: integer;
public legendaryPokemonSeen: integer;
public legendaryPokemonCaught: integer;
public legendaryPokemonHatched: integer;
public mythicalPokemonSeen: integer;
public mythicalPokemonCaught: integer;
public mythicalPokemonHatched: integer;
public shinyPokemonSeen: integer;
public shinyPokemonCaught: integer;
public shinyPokemonHatched: integer;
public pokemonFused: integer;
public trainersDefeated: integer;
public eggsPulled: integer;
public rareEggsPulled: integer;
public epicEggsPulled: integer;
public legendaryEggsPulled: integer;
public manaphyEggsPulled: integer;
constructor(source?: any) {
this.playTime = source?.playTime || 0;
this.battles = source?.battles || 0;
this.classicSessionsPlayed = source?.classicSessionsPlayed || 0;
this.sessionsWon = source?.sessionsWon || 0;
this.endlessSessionsPlayed = source?.endlessSessionsPlayed || 0;
this.highestEndlessWave = source?.highestEndlessWave || 0;
this.highestLevel = source?.highestLevel || 0;
this.highestMoney = source?.highestMoney || 0;
this.pokemonSeen = source?.pokemonSeen || 0;
this.pokemonDefeated = source?.pokemonDefeated || 0;
this.pokemonCaught = source?.pokemonCaught || 0;
this.pokemonHatched = source?.pokemonHatched || 0;
this.legendaryPokemonSeen = source?.legendaryPokemonSeen || 0;
this.legendaryPokemonCaught = source?.legendaryPokemonCaught || 0;
this.legendaryPokemonHatched = source?.legendaryPokemonHatched || 0;
this.mythicalPokemonSeen = source?.mythicalPokemonSeen || 0;
this.mythicalPokemonCaught = source?.mythicalPokemonCaught || 0;
this.mythicalPokemonHatched = source?.mythicalPokemonCaught || 0;
this.shinyPokemonSeen = source?.shinyPokemonSeen || 0;
this.shinyPokemonCaught = source?.shinyPokemonCaught || 0;
this.shinyPokemonHatched = source?.shinyPokemonHatched || 0;
this.pokemonFused = source?.pokemonFused || 0;
this.trainersDefeated = source?.trainersDefeated || 0;
this.eggsPulled = source?.eggsPulled || 0;
this.rareEggsPulled = source?.rareEggsPulled || 0;
this.epicEggsPulled = source?.epicEggsPulled || 0;
this.legendaryEggsPulled = source?.legendaryEggsPulled || 0;
this.manaphyEggsPulled = source?.manaphyEggsPulled || 0;
}
}

View File

@ -4,13 +4,25 @@ import { Mode } from "./ui";
import UiHandler from "./ui-handler"; import UiHandler from "./ui-handler";
import { addWindow } from "./window"; import { addWindow } from "./window";
export default abstract class AbstractOptionSelectUiHandler extends UiHandler { export interface OptionSelectConfig {
protected handlers: Function[]; xOffset?: number;
options: OptionSelectItem[];
}
export interface OptionSelectItem {
label: string;
handler: Function;
keepOpen?: boolean;
overrideSound?: boolean;
}
export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected optionSelectContainer: Phaser.GameObjects.Container; protected optionSelectContainer: Phaser.GameObjects.Container;
protected optionSelectBg: Phaser.GameObjects.NineSlice; protected optionSelectBg: Phaser.GameObjects.NineSlice;
protected optionSelectText: Phaser.GameObjects.Text; protected optionSelectText: Phaser.GameObjects.Text;
protected config: OptionSelectConfig;
private cursorObj: Phaser.GameObjects.Image; private cursorObj: Phaser.GameObjects.Image;
constructor(scene: BattleScene, mode?: Mode) { constructor(scene: BattleScene, mode?: Mode) {
@ -19,9 +31,9 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
abstract getWindowWidth(): integer; abstract getWindowWidth(): integer;
abstract getWindowHeight(): integer; getWindowHeight(): integer {
return ((this.config?.options || []).length + 1) * 16;
abstract getOptions(): string[]; }
setup() { setup() {
const ui = this.getUi(); const ui = this.getUi();
@ -34,19 +46,19 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.optionSelectBg.setOrigin(1, 1); this.optionSelectBg.setOrigin(1, 1);
this.optionSelectContainer.add(this.optionSelectBg); this.optionSelectContainer.add(this.optionSelectBg);
this.setupOptions();
this.setCursor(0); this.setCursor(0);
} }
protected setupOptions() { protected setupOptions() {
const options = this.getOptions(); const options = this.config?.options || [];
if (this.optionSelectText) if (this.optionSelectText)
this.optionSelectText.destroy(); this.optionSelectText.destroy();
this.optionSelectText = addTextObject(this.scene, 0, 0, options.join('\n'), TextStyle.WINDOW, { maxLines: options.length }); this.optionSelectText = addTextObject(this.scene, 0, 0, options.map(o => o.label).join('\n'), TextStyle.WINDOW, { maxLines: options.length });
this.optionSelectText.setLineSpacing(12); this.optionSelectText.setLineSpacing(12);
this.optionSelectContainer.add(this.optionSelectText); this.optionSelectContainer.add(this.optionSelectText);
this.optionSelectContainer.x = (this.scene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0);
this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth()); this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth());
this.optionSelectBg.height = this.getWindowHeight(); this.optionSelectBg.height = this.getWindowHeight();
@ -55,20 +67,20 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
} }
show(args: any[]): boolean { show(args: any[]): boolean {
const options = this.getOptions(); if (!args.length || !args[0].hasOwnProperty('options') || !args[0].options.length)
return false;
if (args.length >= options.length && args.slice(0, options.length).filter(a => a instanceof Function).length === options.length) { super.show(args);
super.show(args);
this.handlers = args.slice(0, options.length) as Function[];
this.optionSelectContainer.setVisible(true); this.config = args[0] as OptionSelectConfig;
this.setCursor(0); this.setupOptions();
return true; this.scene.ui.bringToTop(this.optionSelectContainer);
}
return false; this.optionSelectContainer.setVisible(true);
this.setCursor(0);
return true;
} }
processInput(button: Button): boolean { processInput(button: Button): boolean {
@ -76,15 +88,19 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
let success = false; let success = false;
const options = this.getOptions(); const options = this.config?.options || [];
let playSound = true;
if (button === Button.ACTION || button === Button.CANCEL) { if (button === Button.ACTION || button === Button.CANCEL) {
success = true; success = true;
if (button === Button.CANCEL) if (button === Button.CANCEL)
this.setCursor(options.length - 1); this.setCursor(options.length - 1);
const handler = this.handlers[this.cursor]; const option = options[this.cursor];
handler(); option.handler();
this.clear(); if (!option.keepOpen)
this.clear();
playSound = !option.overrideSound;
} else { } else {
switch (button) { switch (button) {
case Button.UP: case Button.UP:
@ -98,7 +114,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
} }
} }
if (success) if (success && playSound)
ui.playSelect(); ui.playSelect();
return success; return success;
@ -119,8 +135,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
clear() { clear() {
super.clear(); super.clear();
for (let h = 0; h < this.handlers.length; h++) this.config = null;
this.handlers[h] = null;
this.optionSelectContainer.setVisible(false); this.optionSelectContainer.setVisible(false);
this.eraseCursor(); this.eraseCursor();
} }

View File

@ -1,5 +1,5 @@
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; import AbstractOptionSelectUiHandler, { OptionSelectConfig } from "./abstact-option-select-ui-handler";
import { Mode } from "./ui"; import { Mode } from "./ui";
export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
@ -14,17 +14,22 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
return 48; return 48;
} }
getWindowHeight(): integer {
return 48;
}
getOptions(): string[] {
return [ 'Yes', 'No' ];
}
show(args: any[]): boolean { show(args: any[]): boolean {
if (args.length >= 2 && args[0] instanceof Function && args[1] instanceof Function) { if (args.length >= 2 && args[0] instanceof Function && args[1] instanceof Function) {
super.show(args); const config: OptionSelectConfig = {
options: [
{
label: 'Yes',
handler: args[0]
},
{
label: 'No',
handler: args[1]
}
]
};
super.show([ config ]);
this.switchCheck = args.length >= 3 && args[2] as boolean; this.switchCheck = args.length >= 3 && args[2] as boolean;

View File

@ -346,10 +346,25 @@ export default class EggGachaUiHandler extends MessageUiHandler {
for (let tier of tiers) { for (let tier of tiers) {
const egg = new Egg(Utils.randInt(EGG_SEED, EGG_SEED * tier), this.gachaCursor, getEggTierDefaultHatchWaves(tier), timestamp); const egg = new Egg(Utils.randInt(EGG_SEED, EGG_SEED * tier), this.gachaCursor, getEggTierDefaultHatchWaves(tier), timestamp);
if (egg.isManaphyEgg()) if (egg.isManaphyEgg()) {
this.scene.gameData.gameStats.manaphyEggsPulled++;
egg.hatchWaves = getEggTierDefaultHatchWaves(ModifierTier.ULTRA); egg.hatchWaves = getEggTierDefaultHatchWaves(ModifierTier.ULTRA);
} else {
switch (tier) {
case ModifierTier.GREAT:
this.scene.gameData.gameStats.rareEggsPulled++;
break;
case ModifierTier.ULTRA:
this.scene.gameData.gameStats.epicEggsPulled++;
break;
case ModifierTier.MASTER:
this.scene.gameData.gameStats.legendaryEggsPulled++;
break;
}
}
eggs.push(egg); eggs.push(egg);
this.scene.gameData.eggs.push(egg); this.scene.gameData.eggs.push(egg);
this.scene.gameData.gameStats.eggsPulled++;
} }
this.scene.gameData.saveSystem().then(success => { this.scene.gameData.saveSystem().then(success => {

View File

@ -1,7 +1,7 @@
import BattleScene, { Button } from "../battle-scene"; import BattleScene from "../battle-scene";
import { GameMode, gameModeNames } from "../game-mode"; import { GameMode, gameModeNames } from "../game-mode";
import { Unlockables } from "../system/unlockables"; import { Unlockables } from "../system/unlockables";
import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; import AbstractOptionSelectUiHandler, { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler";
import { Mode } from "./ui"; import { Mode } from "./ui";
export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandler { export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandler {
@ -14,28 +14,38 @@ export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandl
return 104; return 104;
} }
getWindowHeight(): number {
return (this.getOptions().length + 1) * 16;
}
getOptions(): string[] {
const ret = [ gameModeNames[GameMode.CLASSIC] ];
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
ret.push(gameModeNames[GameMode.ENDLESS]);
if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE])
ret.push(gameModeNames[GameMode.SPLICED_ENDLESS]);
}
ret.push('Cancel');
return ret;
}
show(args: any[]): boolean { show(args: any[]): boolean {
if (args.length === 2 && args[0] instanceof Function && args[1] instanceof Function) { if (args.length === 2 && args[0] instanceof Function && args[1] instanceof Function) {
this.setupOptions(); const options: OptionSelectItem[] = [
{
label: gameModeNames[GameMode.CLASSIC],
handler: () => args[0](GameMode.CLASSIC)
}
];
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
options.push({
label: gameModeNames[GameMode.ENDLESS],
handler: () => args[0](GameMode.ENDLESS)
});
if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) {
options.push({
label: gameModeNames[GameMode.SPLICED_ENDLESS],
handler: () => args[0](GameMode.SPLICED_ENDLESS)
});
}
}
options.push({
label: 'Cancel',
handler: args[1]
})
const config: OptionSelectConfig = {
options: options
};
super.show(args); super.show([ config ]);
this.handlers = args as Function[];
this.optionSelectContainer.setVisible(true); this.optionSelectContainer.setVisible(true);
this.setCursor(0); this.setCursor(0);
@ -45,25 +55,4 @@ export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandl
return false; return false;
} }
processInput(button: Button): boolean {
const ui = this.getUi();
const options = this.getOptions();
if (button === Button.ACTION || button === Button.CANCEL) {
if (button === Button.CANCEL)
this.setCursor(options.length - 1);
if (this.cursor < options.length - 1) {
const gameMode = Object.values(gameModeNames).indexOf(options[this.cursor]) as GameMode;
this.handlers[0](gameMode);
} else
this.handlers[1]();
this.clear();
ui.playSelect();
} else
return super.processInput(button);
return true;
}
} }

View File

@ -0,0 +1,241 @@
import BattleScene, { Button } from "../battle-scene";
import { GameStats } from "../system/game-stats";
import { TextStyle, addTextObject, getTextColor } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import { addWindow } from "./window";
import * as Utils from "../utils";
import { GameData } from "../system/game-data";
import { speciesStarters } from "../data/pokemon-species";
interface DisplayStat {
label?: string;
sourceFunc?: (gameData: GameData) => string;
hidden?: boolean;
}
interface DisplayStats {
[key: string]: DisplayStat | string
}
const secondsInHour = 3600;
const displayStats: DisplayStats = {
playTime: {
sourceFunc: gameData => {
const totalSeconds = gameData.gameStats.playTime;
const days = `${Math.floor(totalSeconds / (secondsInHour * 24))}`;
const hours = `${Math.floor(totalSeconds % (secondsInHour * 24) / secondsInHour)}`;
const minutes = `${Math.floor(totalSeconds % secondsInHour / 60)}`;
const seconds = `${Math.floor(totalSeconds % 60)}`;
return `${days.padStart(2, '0')}:${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`;
}
},
battles: 'Total Battles',
startersUnlocked: {
label: 'Starters',
sourceFunc: gameData => {
const starterKeys = Object.keys(speciesStarters);
let starterCount = 0;
for (let s of starterKeys) {
if (gameData.dexData[s].caughtAttr)
starterCount++;
}
return `${starterCount} (${Math.floor((starterCount / starterKeys.length) * 1000) / 10}%)`;
}
},
classicSessionsPlayed: 'Runs (Classic)',
sessionsWon: 'Runs (Classic)',
endlessSessionsPlayed: 'Runs (Endless)?',
highestEndlessWave: 'Highest Wave (Endless)?',
highestMoney: 'Highest Money',
pokemonSeen: 'Pokémon Encountered',
pokemonDefeated: 'Pokémon Defeated',
pokemonCaught: 'Pokémon Caught',
pokemonHatched: 'Eggs Hatched',
legendaryPokemonSeen: 'Legendary Encounters?',
legendaryPokemonCaught: 'Legendaries Caught?',
legendaryPokemonHatched: 'Legendaries Hatched?',
mythicalPokemonSeen: 'Mythical Encounters?',
mythicalPokemonCaught: 'Mythicals Caught?',
mythicalPokemonHatched: 'Mythicals Hatched?',
shinyPokemonSeen: 'Shiny Encounters?',
shinyPokemonCaught: 'Shinies Caught?',
shinyPokemonHatched: 'Shinies Hatched?',
pokemonFused: 'Pokémon Fused?',
trainersDefeated: 'Trainers Defeated',
eggsPulled: 'Eggs Pulled',
rareEggsPulled: 'Rare Eggs Pulled?',
epicEggsPulled: 'Epic Eggs Pulled?',
legendaryEggsPulled: 'Legendary Eggs Pulled?',
manaphyEggsPulled: 'Manaphy Eggs Pulled?'
};
export default class GameStatsUiHandler extends UiHandler {
private gameStatsContainer: Phaser.GameObjects.Container;
private statsContainer: Phaser.GameObjects.Container;
private statLabels: Phaser.GameObjects.Text[];
private statValues: Phaser.GameObjects.Text[];
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
this.statLabels = [];
this.statValues = [];
}
setup() {
const ui = this.getUi();
this.gameStatsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.gameStatsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24);
headerBg.setOrigin(0, 0);
const headerText = addTextObject(this.scene, 0, 0, 'Stats', TextStyle.SETTINGS_LABEL);
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
const statsBgWidth = ((this.scene.game.canvas.width / 6) - 2) / 2;
const [ statsBgLeft, statsBgRight ] = new Array(2).fill(null).map((_, i) => {
let width = statsBgWidth;
if (!i)
width += 5;
const statsBg = addWindow(this.scene, statsBgWidth * i, headerBg.height, width, (this.scene.game.canvas.height / 6) - headerBg.height - 2, false, !!i, 2);
statsBg.setOrigin(0, 0);
return statsBg;
});
this.statsContainer = this.scene.add.container(0, 0);
new Array(18).fill(null).map((_, s) => {
const statLabel = addTextObject(this.scene, 8 + (s % 2 === 1 ? statsBgWidth : 0), 28 + Math.floor(s / 2) * 16, '', TextStyle.SETTINGS_LABEL);
statLabel.setOrigin(0, 0);
this.statsContainer.add(statLabel);
this.statLabels.push(statLabel);
const statValue = addTextObject(this.scene, (statsBgWidth * ((s % 2) + 1)) - 8, statLabel.y, '', TextStyle.WINDOW);
statValue.setOrigin(1, 0);
this.statsContainer.add(statValue);
this.statValues.push(statValue);
});
this.gameStatsContainer.add(headerBg);
this.gameStatsContainer.add(headerText);
this.gameStatsContainer.add(statsBgLeft);
this.gameStatsContainer.add(statsBgRight);
this.gameStatsContainer.add(this.statsContainer);
ui.add(this.gameStatsContainer);
this.setCursor(0);
this.gameStatsContainer.setVisible(false);
}
show(args: any[]): boolean {
super.show(args);
this.setCursor(0);
this.updateStats();
this.gameStatsContainer.setVisible(true);
this.getUi().moveTo(this.gameStatsContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
updateStats(): void {
const statKeys = Object.keys(displayStats).slice(this.cursor * 2, this.cursor * 2 + 18);
statKeys.forEach((key, s) => {
const stat = displayStats[key] as DisplayStat;
const value = stat.sourceFunc(this.scene.gameData);
this.statLabels[s].setText(!stat.hidden || isNaN(parseInt(value)) || parseInt(value) ? stat.label : '???');
this.statValues[s].setText(value);
});
if (statKeys.length < 18) {
for (let s = statKeys.length; s < 18; s++) {
this.statLabels[s].setText('');
this.statValues[s].setText('');
}
}
}
processInput(button: Button): boolean {
const ui = this.getUi();
let success = false;
if (button === Button.CANCEL) {
success = true;
this.scene.ui.revertMode();
} else {
switch (button) {
case Button.UP:
if (this.cursor)
success = this.setCursor(this.cursor - 1);
break;
case Button.DOWN:
if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2))
success = this.setCursor(this.cursor + 1);
break;
}
}
if (success)
ui.playSelect();
return success;
}
setCursor(cursor: integer): boolean {
const ret = super.setCursor(cursor);
if (ret)
this.updateStats();
return ret;
}
clear() {
super.clear();
this.gameStatsContainer.setVisible(false);
}
}
(function () {
const statKeys = Object.keys(displayStats);
for (let key of statKeys) {
if (typeof displayStats[key] === 'string') {
let label = displayStats[key] as string;
let hidden = false;
if (label.endsWith('?')) {
label = label.slice(0, -1);
hidden = true;
}
displayStats[key] = {
label: label,
sourceFunc: gameData => gameData.gameStats[key].toString(),
hidden: hidden
};
} else if (displayStats[key] === null) {
displayStats[key] = {
sourceFunc: gameData => gameData.gameStats[key].toString()
};
}
if (!(displayStats[key] as DisplayStat).label) {
const splittableKey = key.replace(/([a-z]{2,})([A-Z]{1}(?:[^A-Z]|$))/g, '$1_$2');
(displayStats[key] as DisplayStat).label = Utils.toReadableString(`${splittableKey[0].toUpperCase()}${splittableKey.slice(1)}`);
}
}
})();

View File

@ -5,17 +5,16 @@ import * as Utils from "../utils";
import { addWindow } from "./window"; import { addWindow } from "./window";
import MessageUiHandler from "./message-ui-handler"; import MessageUiHandler from "./message-ui-handler";
import { GameDataType } from "../system/game-data"; import { GameDataType } from "../system/game-data";
import { OptionSelectConfig } from "./abstact-option-select-ui-handler";
export enum MenuOptions { export enum MenuOptions {
GAME_SETTINGS, GAME_SETTINGS,
ACHIEVEMENTS, ACHIEVEMENTS,
STATS,
VOUCHERS, VOUCHERS,
EGG_LIST, EGG_LIST,
EGG_GACHA, EGG_GACHA,
IMPORT_SESSION, MANAGE_DATA,
EXPORT_SESSION,
IMPORT_DATA,
EXPORT_DATA,
LOG_OUT LOG_OUT
} }
@ -31,12 +30,14 @@ export default class MenuUiHandler extends MessageUiHandler {
protected ignoredMenuOptions: MenuOptions[]; protected ignoredMenuOptions: MenuOptions[];
protected menuOptions: MenuOptions[]; protected menuOptions: MenuOptions[];
protected manageDataConfig: OptionSelectConfig;
constructor(scene: BattleScene, mode?: Mode) { constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode); super(scene, mode);
this.ignoredMenuOptions = /*!bypassLogin */ false this.ignoredMenuOptions = !bypassLogin
? [ MenuOptions.IMPORT_SESSION, MenuOptions.IMPORT_DATA ] ? [ ]
: []; : [ MenuOptions.LOG_OUT ];
this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions).filter(m => this.ignoredMenuOptions.indexOf(m) === -1); this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions).filter(m => this.ignoredMenuOptions.indexOf(m) === -1);
} }
@ -76,6 +77,44 @@ export default class MenuUiHandler extends MessageUiHandler {
this.menuContainer.add(this.menuMessageBoxContainer); this.menuContainer.add(this.menuMessageBoxContainer);
const manageDataOptions = [];
if (bypassLogin) {
manageDataOptions.push({
label: 'Import Session',
handler: () => this.scene.gameData.importData(GameDataType.SESSION),
keepOpen: true
});
}
manageDataOptions.push({
label: 'Export Session',
handler: () => this.scene.gameData.exportData(GameDataType.SESSION),
keepOpen: true
});
if (bypassLogin) {
manageDataOptions.push({
label: 'Import Data',
handler: () => this.scene.gameData.importData(GameDataType.SYSTEM),
keepOpen: true
});
}
manageDataOptions.push(
{
label: 'Export Data',
handler: () => this.scene.gameData.exportData(GameDataType.SYSTEM),
keepOpen: true
},
{
label: 'Cancel',
handler: () => this.scene.ui.revertMode()
}
);
this.manageDataConfig = {
xOffset: 98,
options: manageDataOptions
};
this.setCursor(0); this.setCursor(0);
this.menuContainer.setVisible(false); this.menuContainer.setVisible(false);
@ -112,38 +151,36 @@ export default class MenuUiHandler extends MessageUiHandler {
} }
switch (adjustedCursor) { switch (adjustedCursor) {
case MenuOptions.GAME_SETTINGS: case MenuOptions.GAME_SETTINGS:
this.scene.ui.setOverlayMode(Mode.SETTINGS); ui.setOverlayMode(Mode.SETTINGS);
success = true; success = true;
break; break;
case MenuOptions.ACHIEVEMENTS: case MenuOptions.ACHIEVEMENTS:
this.scene.ui.setOverlayMode(Mode.ACHIEVEMENTS); ui.setOverlayMode(Mode.ACHIEVEMENTS);
success = true;
break;
case MenuOptions.STATS:
ui.setOverlayMode(Mode.GAME_STATS);
success = true; success = true;
break; break;
case MenuOptions.VOUCHERS: case MenuOptions.VOUCHERS:
this.scene.ui.setOverlayMode(Mode.VOUCHERS); ui.setOverlayMode(Mode.VOUCHERS);
success = true; success = true;
break; break;
case MenuOptions.EGG_LIST: case MenuOptions.EGG_LIST:
if (this.scene.gameData.eggs.length) { if (this.scene.gameData.eggs.length) {
this.scene.ui.revertMode(); ui.revertMode();
this.scene.ui.setOverlayMode(Mode.EGG_LIST); ui.setOverlayMode(Mode.EGG_LIST);
success = true; success = true;
} else } else
error = true; error = true;
break; break;
case MenuOptions.EGG_GACHA: case MenuOptions.EGG_GACHA:
this.scene.ui.revertMode(); ui.revertMode();
this.scene.ui.setOverlayMode(Mode.EGG_GACHA); ui.setOverlayMode(Mode.EGG_GACHA);
success = true; success = true;
break; break;
case MenuOptions.IMPORT_SESSION: case MenuOptions.MANAGE_DATA:
case MenuOptions.IMPORT_DATA: ui.setOverlayMode(Mode.OPTION_SELECT, this.manageDataConfig);
this.scene.gameData.importData(this.cursor === MenuOptions.IMPORT_DATA ? GameDataType.SYSTEM : GameDataType.SESSION);
success = true;
break;
case MenuOptions.EXPORT_SESSION:
case MenuOptions.EXPORT_DATA:
this.scene.gameData.exportData(this.cursor === MenuOptions.EXPORT_DATA ? GameDataType.SYSTEM : GameDataType.SESSION);
success = true; success = true;
break; break;
case MenuOptions.LOG_OUT: case MenuOptions.LOG_OUT:
@ -157,10 +194,10 @@ export default class MenuUiHandler extends MessageUiHandler {
}); });
}; };
if (this.scene.currentBattle) { if (this.scene.currentBattle) {
this.scene.ui.showText('You will lose any progress since the beginning of the battle. Proceed?', null, () => { ui.showText('You will lose any progress since the beginning of the battle. Proceed?', null, () => {
this.scene.ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { ui.setOverlayMode(Mode.CONFIRM, doLogout, () => {
this.scene.ui.revertMode(); ui.revertMode();
this.scene.ui.showText(null, 0); ui.showText(null, 0);
}, false, 98); }, false, 98);
}); });
} else } else
@ -169,7 +206,7 @@ export default class MenuUiHandler extends MessageUiHandler {
} }
} else if (button === Button.CANCEL) { } else if (button === Button.CANCEL) {
success = true; success = true;
if (!this.scene.ui.revertMode()) if (!ui.revertMode())
ui.setMode(Mode.MESSAGE); ui.setMode(Mode.MESSAGE);
} else { } else {
switch (button) { switch (button) {

View File

@ -3,8 +3,6 @@ import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler";
import { Mode } from "./ui"; import { Mode } from "./ui";
export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler {
private options: string[] = [];
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene, Mode.OPTION_SELECT); super(scene, Mode.OPTION_SELECT);
} }
@ -12,28 +10,4 @@ export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler
getWindowWidth(): integer { getWindowWidth(): integer {
return 64; return 64;
} }
getWindowHeight(): integer {
return (this.getOptions().length + 1) * 16;
}
getOptions(): string[] {
return this.options;
}
show(args: any[]): boolean {
if (args.length < 2 || args.length % 2 === 1)
return false;
const optionNames: string[] = [];
const optionFuncs: Function[] = [];
args.map((arg, i) => (i % 2 ? optionFuncs : optionNames).push(arg));
this.options = optionNames;
this.setupOptions();
return super.show(optionFuncs);
}
} }

View File

@ -419,38 +419,50 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (!this.speciesStarterDexEntry?.caughtAttr) if (!this.speciesStarterDexEntry?.caughtAttr)
error = true; error = true;
else if (this.starterCursors.length < 6) { else if (this.starterCursors.length < 6) {
ui.setModeWithoutClear(Mode.OPTION_SELECT, 'Add to Party', () => { ui.setModeWithoutClear(Mode.OPTION_SELECT, {
ui.setMode(Mode.STARTER_SELECT); options: [
let isDupe = false; {
for (let s = 0; s < this.starterCursors.length; s++) { label: 'Add to Party',
if (this.starterGens[s] === this.getGenCursorWithScroll() && this.starterCursors[s] === this.cursor) { handler: () => {
isDupe = true; ui.setMode(Mode.STARTER_SELECT);
break; let isDupe = false;
for (let s = 0; s < this.starterCursors.length; s++) {
if (this.starterGens[s] === this.getGenCursorWithScroll() && this.starterCursors[s] === this.cursor) {
isDupe = true;
break;
}
}
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
if (!isDupe && this.tryUpdateValue(speciesStarterValues[species.speciesId])) {
const cursorObj = this.starterCursorObjs[this.starterCursors.length];
cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny));
this.starterGens.push(this.getGenCursorWithScroll());
this.starterCursors.push(this.cursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
if (this.speciesLoaded.get(species.speciesId))
species.cry(this.scene);
if (this.starterCursors.length === 6 || this.value === 10)
this.tryStart();
this.updateInstructions();
ui.playSelect();
} else
ui.playError();
},
overrideSound: true
},
{
label: 'Toggle IVs',
handler: () => {
this.toggleStatsMode();
ui.setMode(Mode.STARTER_SELECT);
}
} }
} ]
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
if (!isDupe && this.tryUpdateValue(speciesStarterValues[species.speciesId])) {
const cursorObj = this.starterCursorObjs[this.starterCursors.length];
cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny));
this.starterGens.push(this.getGenCursorWithScroll());
this.starterCursors.push(this.cursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
if (this.speciesLoaded.get(species.speciesId))
species.cry(this.scene);
if (this.starterCursors.length === 6 || this.value === 10)
this.tryStart();
this.updateInstructions();
ui.playSelect();
} else
ui.playError();
}, 'Toggle IVs', () => {
this.toggleStatsMode();
ui.setMode(Mode.STARTER_SELECT);
}); });
success = true; success = true;
} }

View File

@ -29,6 +29,7 @@ import LoginFormUiHandler from './login-form-ui-handler';
import RegistrationFormUiHandler from './registration-form-ui-handler'; import RegistrationFormUiHandler from './registration-form-ui-handler';
import LoadingModalUiHandler from './loading-modal-ui-handler'; import LoadingModalUiHandler from './loading-modal-ui-handler';
import * as Utils from "../utils"; import * as Utils from "../utils";
import GameStatsUiHandler from './game-stats-ui-handler';
export enum Mode { export enum Mode {
MESSAGE, MESSAGE,
@ -49,6 +50,7 @@ export enum Mode {
MENU, MENU,
SETTINGS, SETTINGS,
ACHIEVEMENTS, ACHIEVEMENTS,
GAME_STATS,
VOUCHERS, VOUCHERS,
EGG_LIST, EGG_LIST,
EGG_GACHA, EGG_GACHA,
@ -74,6 +76,7 @@ const noTransitionModes = [
Mode.MENU, Mode.MENU,
Mode.SETTINGS, Mode.SETTINGS,
Mode.ACHIEVEMENTS, Mode.ACHIEVEMENTS,
Mode.GAME_STATS,
Mode.VOUCHERS, Mode.VOUCHERS,
Mode.LOGIN_FORM, Mode.LOGIN_FORM,
Mode.REGISTRATION_FORM, Mode.REGISTRATION_FORM,
@ -118,6 +121,7 @@ export default class UI extends Phaser.GameObjects.Container {
new MenuUiHandler(scene), new MenuUiHandler(scene),
new SettingsUiHandler(scene), new SettingsUiHandler(scene),
new AchvsUiHandler(scene), new AchvsUiHandler(scene),
new GameStatsUiHandler(scene),
new VouchersUiHandler(scene), new VouchersUiHandler(scene),
new EggListUiHandler(scene), new EggListUiHandler(scene),
new EggGachaUiHandler(scene), new EggGachaUiHandler(scene),