Add accounts, login, and registration

This commit is contained in:
Flashfyre committed 2023-12-30 20:19:33 -05:00
1 parent 19fec88daa
commit 8063472bac
48 files changed
+1005 -205

No files matched your search

+5 -1
View File
@@ -54,7 +54,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.optionSelectText.setPositionRelative(this.optionSelectBg, 16, 9);
}
show(args: any[]) {
show(args: any[]): boolean {
const options = this.getOptions();
if (args.length >= options.length && args.slice(0, options.length).filter(a => a instanceof Function).length === options.length) {
@@ -64,7 +64,11 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.optionSelectContainer.setVisible(true);
this.setCursor(0);
return true;
}
return false;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -105,7 +105,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
this.achvsContainer.setVisible(false);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
const achvUnlocks = this.scene.gameData.achvUnlocks;
@@ -129,6 +129,8 @@ export default class AchvsUiHandler extends MessageUiHandler {
this.getUi().moveTo(this.achvsContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
protected showAchv(achv: Achv) {
+3 -1
View File
@@ -48,12 +48,14 @@ export default class BallUiHandler extends UiHandler {
this.setCursor(0);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
this.updateCounts();
this.pokeballSelectContainer.setVisible(true);
this.setCursor(this.cursor);
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -120,12 +120,14 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
this.levelUpStatsValuesContent = levelUpStatsValuesContent;
}
show(args: any[]): void {
show(args: any[]): boolean {
super.show(args);
this.commandWindow.setVisible(false);
this.movesWindowContainer.setVisible(false);
this.message.setWordWrapWidth(1780);
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -36,7 +36,7 @@ export default class BiomeSelectUiHandler extends UiHandler {
this.biomeSelectContainer.add(this.biomesText);
}
show(args: any[]) {
show(args: any[]): boolean {
if (args.length >= 2 && typeof(args[0]) === 'number' && args[1] instanceof Function) {
super.show(args);
@@ -59,6 +59,8 @@ export default class BiomeSelectUiHandler extends UiHandler {
this.biomeSelectContainer.setVisible(true);
this.setCursor(0);
}
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -37,7 +37,7 @@ export default class CommandUiHandler extends UiHandler {
}
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
this.fieldIndex = args.length ? args[0] as integer : 0;
@@ -50,6 +50,8 @@ export default class CommandUiHandler extends UiHandler {
messageHandler.message.setWordWrapWidth(1110);
messageHandler.showText(`What will\n${(this.scene.getCurrentPhase() as CommandPhase).getPokemon().name} do?`, 0);
this.setCursor(this.getCursor());
return true;
}
processInput(button: Button): boolean {
+5 -1
View File
@@ -22,7 +22,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
return [ 'Yes', 'No' ];
}
show(args: any[]) {
show(args: any[]): boolean {
if (args.length >= 2 && args[0] instanceof Function && args[1] instanceof Function) {
super.show(args);
@@ -33,7 +33,11 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
this.optionSelectContainer.x = (this.scene.game.canvas.width / 6) - 1 + xOffset;
this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
return true;
}
return false;
}
setCursor(cursor: integer): boolean {
+92 -82
View File
@@ -219,7 +219,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
this.setCursor(0);
}
show(args: any[]): void {
show(args: any[]): boolean {
super.show(args);
this.getUi().showText(defaultText, 0);
@@ -232,6 +232,8 @@ export default class EggGachaUiHandler extends MessageUiHandler {
this.updateVoucherCounts();
this.eggGachaContainer.setVisible(true);
return true;
}
getDelayValue(delay: integer) {
@@ -240,10 +242,90 @@ export default class EggGachaUiHandler extends MessageUiHandler {
return Utils.fixedInt(delay);
}
pull(pullCount?: integer, count?: integer, eggs?: Egg[]) {
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, 1);
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)
@@ -270,87 +352,15 @@ export default class EggGachaUiHandler extends MessageUiHandler {
this.scene.gameData.eggs.push(egg);
}
this.scene.gameData.saveSystem();
}
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, 1);
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);
}
this.scene.gameData.saveSystem().then(success => {
if (!success)
return this.scene.reset(true);
doPull();
});
} else
doPullAnim();
return;
}
doPull();
}
showSummary(eggs: Egg[]): void {
+3 -1
View File
@@ -21,10 +21,12 @@ export default class EggHatchSceneHandler extends UiHandler {
});
}
show(_args: any[]): void {
show(_args: any[]): boolean {
super.show(_args);
this.getUi().showText(null, 0);
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -88,7 +88,7 @@ export default class EggListUiHandler extends MessageUiHandler {
this.cursor = -1;
}
show(args: any[]): void {
show(args: any[]): boolean {
super.show(args);
this.eggListContainer.setVisible(true);
@@ -115,6 +115,8 @@ export default class EggListUiHandler extends MessageUiHandler {
}
this.setCursor(0);
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -19,10 +19,12 @@ export default class EvolutionSceneHandler extends UiHandler {
this.scene.fieldUI.add(this.evolutionContainer);
}
show(_args: any[]): void {
show(_args: any[]): boolean {
super.show(_args);
this.scene.fieldUI.bringToTop(this.evolutionContainer);
return true;
}
processInput(button: Button): boolean {
+3 -1
View File
@@ -36,7 +36,7 @@ export default class FightUiHandler extends UiHandler {
ui.add(this.ppText);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
this.fieldIndex = args.length ? args[0] as integer : 0;
@@ -46,6 +46,8 @@ export default class FightUiHandler extends UiHandler {
messageHandler.movesWindowContainer.setVisible(true);
this.setCursor(this.getCursor());
this.displayMoves();
return true;
}
processInput(button: Button): boolean {
+127
View File
@@ -0,0 +1,127 @@
import BattleScene, { Button } from "../battle-scene";
import { ModalConfig, ModalUiHandler } from "./modal-ui-handler";
import { Mode } from "./ui";
import { TextStyle, addTextInputObject, addTextObject, getTextColor } from "./text";
import { WindowVariant, addWindow } from "./window";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
export interface FormModalConfig extends ModalConfig {
errorMessage?: string;
}
export abstract class FormModalUiHandler extends ModalUiHandler {
protected editing: boolean;
protected inputContainers: Phaser.GameObjects.Container[];
protected inputs: InputText[];
protected errorMessage: Phaser.GameObjects.Text;
protected submitAction: Function;
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
this.editing = false;
this.inputContainers = [];
this.inputs = [];
}
abstract getFields(): string[];
getHeight(config?: ModalConfig): number {
return 20 * this.getFields().length + (this.getModalTitle() ? 26 : 0) + ((config as FormModalConfig)?.errorMessage ? 12 : 0) + 28;
}
getReadableErrorMessage(error: string): string {
if (error?.indexOf('connection refused') > -1)
return 'Could not connect to the server';
return error;
}
setup(): void {
super.setup();
const fields = this.getFields();
const hasTitle = !!this.getModalTitle();
fields.forEach((field, f) => {
const label = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * f, field, TextStyle.TOOLTIP_CONTENT);
this.modalContainer.add(label);
const inputContainer = this.scene.add.container(70, (hasTitle ? 28 : 2) + 20 * f);
const inputBg = addWindow(this.scene, 0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN);
const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: field.indexOf('Password') > -1 ? 'password' : 'text', maxLength: 16 });
input.setOrigin(0, 0);
inputContainer.add(inputBg);
inputContainer.add(input);
this.modalContainer.add(inputContainer);
this.inputContainers.push(inputContainer);
this.inputs.push(input);
});
this.errorMessage = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16, '', TextStyle.TOOLTIP_CONTENT);
this.errorMessage.setColor(getTextColor(TextStyle.SUMMARY_RED));
this.errorMessage.setShadowColor(getTextColor(TextStyle.SUMMARY_RED, true));
this.errorMessage.setVisible(false);
this.modalContainer.add(this.errorMessage);
}
show(args: any[]): boolean {
if (super.show(args)) {
this.inputContainers.map(ic => ic.setVisible(true));
const config = args[0] as FormModalConfig;
this.submitAction = config.buttonActions.length
? config.buttonActions[0]
: null;
if (this.buttonBgs.length) {
this.buttonBgs[0].off('pointerdown');
this.buttonBgs[0].on('pointerdown', () => {
if (this.submitAction)
this.submitAction();
});
}
return true;
}
return false;
}
processInput(button: Button): boolean {
if (button === Button.SUBMIT && this.submitAction) {
this.submitAction();
return true;
}
return false;
}
sanitizeInputs(): void {
for (let input of this.inputs)
input.text = input.text.trim();
}
updateContainer(config?: ModalConfig): void {
super.updateContainer(config);
this.errorMessage.setText(this.getReadableErrorMessage((config as FormModalConfig)?.errorMessage || ''));
this.errorMessage.setVisible(!!this.errorMessage.text);
}
clear(): void {
super.clear();
this.modalContainer.setVisible(false);
this.inputContainers.map(ic => ic.setVisible(false));
this.submitAction = null;
}
}
+5 -1
View File
@@ -29,7 +29,7 @@ export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandl
return ret;
}
show(args: any[]) {
show(args: any[]): boolean {
if (args.length === 2 && args[0] instanceof Function && args[1] instanceof Function) {
this.setupOptions();
@@ -39,7 +39,11 @@ export default class GameModeSelectUiHandler extends AbstractOptionSelectUiHandl
this.optionSelectContainer.setVisible(true);
this.setCursor(0);
return true;
}
return false;
}
processInput(button: Button): boolean {
+39
View File
@@ -0,0 +1,39 @@
import BattleScene from "../battle-scene";
import { ModalUiHandler } from "./modal-ui-handler";
import { addTextObject, TextStyle } from "./text";
import { Mode } from "./ui";
export default class LoadingModalUiHandler extends ModalUiHandler {
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
}
getModalTitle(): string {
return '';
}
getWidth(): number {
return 80;
}
getHeight(): number {
return 32;
}
getMargin(): [number, number, number, number] {
return [ 0, 0, 48, 0 ];
}
getButtonLabels(): string[] {
return [ ];
}
setup(): void {
super.setup();
const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, 'Loading...', TextStyle.WINDOW);
label.setOrigin(0.5, 0.5);
this.modalContainer.add(label);
}
}
+81
View File
@@ -0,0 +1,81 @@
import { FormModalUiHandler } from "./form-modal-ui-handler";
import { ModalConfig } from "./modal-ui-handler";
import * as Utils from "../utils";
import { Mode } from "./ui";
export default class LoginFormUiHandler extends FormModalUiHandler {
getModalTitle(config?: ModalConfig): string {
return 'Login';
}
getFields(config?: ModalConfig): string[] {
return [ 'Username', 'Password' ];
}
getWidth(config?: ModalConfig): number {
return 160;
}
getMargin(config?: ModalConfig): [number, number, number, number] {
return [ 0, 0, 48, 0 ];
}
getButtonLabels(config?: ModalConfig): string[] {
return [ 'Log In', 'Register' ];
}
getReadableErrorMessage(error: string): string {
let colonIndex = error?.indexOf(':');
if (colonIndex > 0)
error = error.slice(0, colonIndex);
switch (error) {
case 'invalid username':
return 'The provided username is invalid';
case 'invalid password':
return 'The provided password is invalid';
case 'account doesn\'t exist':
return 'The provided user does not exist';
case 'password doesn\'t match':
return 'The provided password does not match';
}
return super.getReadableErrorMessage(error);
}
show(args: any[]): boolean {
if (super.show(args)) {
const config = args[0] as ModalConfig;
const originalLoginAction = this.submitAction;
this.submitAction = (_) => {
// Prevent overlapping overrides on action modification
this.submitAction = originalLoginAction;
this.sanitizeInputs();
this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] });
const onFail = error => {
this.scene.ui.setMode(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() }));
this.scene.ui.playError();
};
if (!this.inputs[0].text)
return onFail('Username must not be empty');
Utils.apiPost('account/login', JSON.stringify({ username: this.inputs[0].text, password: this.inputs[1].text }))
.then(response => {
if (!response.ok)
return response.text();
return response.json();
})
.then(response => {
if (response.hasOwnProperty('token')) {
Utils.setCookie(Utils.sessionIdKey, response.token);
originalLoginAction();
} else
onFail(response);
});
};
return true;
}
return false;
}
}
+30 -7
View File
@@ -1,14 +1,14 @@
import BattleScene, { Button } from "../battle-scene";
import { TextStyle, addTextObject } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import * as Utils from "../utils";
import { addWindow } from "./window";
import MessageUiHandler from "./message-ui-handler";
import { GameDataType } from "../system/game-data";
import { CheckLoadPhase, LoginPhase } from "../battle-phases";
export enum MenuOptions {
SETTINGS,
GAME_SETTINGS,
ACHIEVEMENTS,
VOUCHERS,
EGG_LIST,
@@ -16,7 +16,8 @@ export enum MenuOptions {
IMPORT_SESSION,
EXPORT_SESSION,
IMPORT_DATA,
EXPORT_DATA
EXPORT_DATA,
LOG_OUT
}
export default class MenuUiHandler extends MessageUiHandler {
@@ -73,7 +74,7 @@ export default class MenuUiHandler extends MessageUiHandler {
this.menuContainer.setVisible(false);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
this.menuContainer.setVisible(true);
@@ -84,6 +85,8 @@ export default class MenuUiHandler extends MessageUiHandler {
this.getUi().hideTooltip();
this.scene.playSound('menu_open');
return true;
}
processInput(button: Button): boolean {
@@ -94,7 +97,7 @@ export default class MenuUiHandler extends MessageUiHandler {
if (button === Button.ACTION) {
switch (this.cursor as MenuOptions) {
case MenuOptions.SETTINGS:
case MenuOptions.GAME_SETTINGS:
this.scene.ui.setOverlayMode(Mode.SETTINGS);
success = true;
break;
@@ -129,6 +132,26 @@ export default class MenuUiHandler extends MessageUiHandler {
this.scene.gameData.exportData(this.cursor === MenuOptions.EXPORT_DATA ? GameDataType.SYSTEM : GameDataType.SESSION);
success = true;
break;
case MenuOptions.LOG_OUT:
success = true;
const doLogout = () => {
Utils.apiPost('account/logout').then(res => {
if (!res.ok)
console.error(`Log out failed (${res.status}: ${res.statusText})`);
Utils.setCookie(Utils.sessionIdKey, '');
this.scene.reset(true);
});
};
if (this.scene.currentBattle) {
this.scene.ui.showText('You will lose any progress since the beginning of the battle. Proceed?', null, () => {
this.scene.ui.setOverlayMode(Mode.CONFIRM, doLogout, () => {
this.scene.ui.revertMode();
this.scene.ui.showText(null, 0);
}, false, 98);
});
} else
doLogout();
break;
}
} else if (button === Button.CANCEL) {
success = true;
@@ -141,7 +164,7 @@ export default class MenuUiHandler extends MessageUiHandler {
success = this.setCursor(this.cursor - 1);
break;
case Button.DOWN:
if (this.cursor < Utils.getEnumKeys(MenuOptions).length)
if (this.cursor + 1 < Utils.getEnumKeys(MenuOptions).length)
success = this.setCursor(this.cursor + 1);
break;
}
@@ -152,7 +175,7 @@ export default class MenuUiHandler extends MessageUiHandler {
else if (error)
ui.playError();
return true;
return success || error;
}
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void {
+130
View File
@@ -0,0 +1,130 @@
import BattleScene, { Button } from "../battle-scene";
import { TextStyle, addTextObject } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import { WindowVariant, addWindow } from "./window";
export interface ModalConfig {
buttonActions: Function[];
}
export abstract class ModalUiHandler extends UiHandler {
protected modalContainer: Phaser.GameObjects.Container;
protected modalBg: Phaser.GameObjects.NineSlice;
protected titleText: Phaser.GameObjects.Text;
protected buttonContainers: Phaser.GameObjects.Container[];
protected buttonBgs: Phaser.GameObjects.NineSlice[];
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
this.buttonContainers = [];
this.buttonBgs = [];
}
abstract getModalTitle(config?: ModalConfig): string;
abstract getWidth(config?: ModalConfig): number;
abstract getHeight(config?: ModalConfig): number;
abstract getMargin(config?: ModalConfig): [number, number, number, number];
abstract getButtonLabels(config?: ModalConfig): string[];
setup() {
const ui = this.getUi();
this.modalContainer = this.scene.add.container(0, 0);
this.modalContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
this.modalBg = addWindow(this.scene, 0, 0, 0, 0);
this.modalContainer.add(this.modalBg);
this.titleText = addTextObject(this.scene, 0, 4, '', TextStyle.SETTINGS_LABEL);
this.titleText.setOrigin(0.5, 0);
this.modalContainer.add(this.titleText);
ui.add(this.modalContainer);
const buttonLabels = this.getButtonLabels();
for (let label of buttonLabels) {
const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT);
buttonLabel.setOrigin(0.5, 0.5);
const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN);
buttonBg.setOrigin(0.5, 0);
buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains);
const buttonContainer = this.scene.add.container(0, 0);
this.buttonBgs.push(buttonBg);
this.buttonContainers.push(buttonContainer);
buttonContainer.add(buttonBg);
buttonContainer.add(buttonLabel);
this.modalContainer.add(buttonContainer);
}
this.modalContainer.setVisible(false);
}
show(args: any[]): boolean {
if (args.length >= 1 && 'buttonActions' in args[0]) {
super.show(args);
const config = args[0] as ModalConfig;
this.updateContainer(config);
this.modalContainer.setVisible(true);
this.getUi().moveTo(this.modalContainer, this.getUi().length - 1);
for (let a = 0; a < this.buttonBgs.length; a++) {
if (a < this.buttonBgs.length)
this.buttonBgs[a].on('pointerdown', (_) => config.buttonActions[a]());
}
return true;
}
return false;
}
updateContainer(config?: ModalConfig): void {
const [ marginTop, marginRight, marginBottom, marginLeft ] = this.getMargin(config);
const [ width, height ] = [ this.getWidth(config), this.getHeight(config) ];
this.modalContainer.setPosition((((this.scene.game.canvas.width / 6) - (width + (marginRight - marginLeft))) / 2), (((-this.scene.game.canvas.height / 6) - (height + (marginBottom - marginTop))) / 2));
this.modalBg.setSize(width, height);
const title = this.getModalTitle(config);
this.titleText.setText(title);
this.titleText.setX(width / 2);
this.titleText.setVisible(!!title);
for (let b = 0; b < this.buttonContainers.length; b++) {
const sliceWidth = width / (this.buttonContainers.length + 1);
this.buttonContainers[b].setPosition(sliceWidth * (b + 1), this.modalBg.height - (this.buttonBgs[b].height + 8));
}
}
processInput(button: Button): boolean {
return false;
}
clear() {
super.clear();
this.modalContainer.setVisible(false);
this.buttonBgs.map(bg => bg.off('pointerdown'));
}
}
+5 -3
View File
@@ -53,17 +53,17 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.rerollButtonContainer.add(this.rerollCostText);
}
show(args: any[]) {
show(args: any[]): boolean {
if (this.active) {
if (args.length >= 3) {
this.awaitingActionInput = true;
this.onActionInput = args[2];
}
return;
return false;
}
if (args.length !== 4 || !(args[1] instanceof Array) || !args[1].length || !(args[2] instanceof Function))
return;
return false;
super.show(args);
@@ -135,6 +135,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.awaitingActionInput = true;
this.onActionInput = args[2];
});
return true;
}
processInput(button: Button): boolean {
+3 -3
View File
@@ -21,9 +21,9 @@ export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler
return this.options;
}
show(args: any[]) {
show(args: any[]): boolean {
if (args.length < 2 || args.length % 2 === 1)
return;
return false;
const optionNames: string[] = [];
const optionFuncs: Function[] = [];
@@ -34,6 +34,6 @@ export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler
this.setupOptions();
super.show(optionFuncs);
return super.show(optionFuncs);
}
}
+4 -2
View File
@@ -161,9 +161,9 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partySlots = [];
}
show(args: any[]) {
show(args: any[]): boolean {
if (!args.length || this.active)
return;
return false;
super.show(args);
@@ -184,6 +184,8 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? '_double' : ''}`);
this.populatePartySlots();
this.setCursor(this.cursor < 6 ? this.cursor : 0);
return true;
}
processInput(button: Button): boolean {
+90
View File
@@ -0,0 +1,90 @@
import { FormModalUiHandler } from "./form-modal-ui-handler";
import { ModalConfig } from "./modal-ui-handler";
import * as Utils from "../utils";
import { Mode } from "./ui";
export default class RegistrationFormUiHandler extends FormModalUiHandler {
getModalTitle(config?: ModalConfig): string {
return 'Register';
}
getFields(config?: ModalConfig): string[] {
return [ 'Username', 'Password', 'Confirm Password' ];
}
getWidth(config?: ModalConfig): number {
return 160;
}
getMargin(config?: ModalConfig): [number, number, number, number] {
return [ 0, 0, 48, 0 ];
}
getButtonLabels(config?: ModalConfig): string[] {
return [ 'Register', 'Back to Login' ];
}
getReadableErrorMessage(error: string): string {
let colonIndex = error?.indexOf(':');
if (colonIndex > 0)
error = error.slice(0, colonIndex);
switch (error) {
case 'invalid username':
return 'Username must only contain letters, numbers, or underscores';
case 'invalid password':
return 'Password must be 6 characters or longer';
case 'failed to add account record':
return 'The provided username is already in use';
}
return super.getReadableErrorMessage(error);
}
show(args: any[]): boolean {
if (super.show(args)) {
const config = args[0] as ModalConfig;
const originalRegistrationAction = this.submitAction;
this.submitAction = (_) => {
// Prevent overlapping overrides on action modification
this.submitAction = originalRegistrationAction;
this.sanitizeInputs();
this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] });
const onFail = error => {
this.scene.ui.setMode(Mode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() }));
this.scene.ui.playError();
};
if (!this.inputs[0].text)
return onFail('Username must not be empty');
if (!this.inputs[1].text)
return onFail(this.getReadableErrorMessage('invalid password'));
if (this.inputs[1].text !== this.inputs[2].text)
return onFail('Password must match confirm password');
Utils.apiPost('account/register', JSON.stringify({ username: this.inputs[0].text, password: this.inputs[1].text }))
.then(response => response.text())
.then(response => {
if (!response) {
Utils.apiPost('account/login', JSON.stringify({ username: this.inputs[0].text, password: this.inputs[1].text }))
.then(response => {
if (!response.ok)
return response.text();
return response.json();
})
.then(response => {
if (response.hasOwnProperty('token')) {
Utils.setCookie(Utils.sessionIdKey, response.token);
originalRegistrationAction();
} else
onFail(response);
});
} else
onFail(response);
});
};
return true;
}
return false;
}
}
+3 -1
View File
@@ -87,7 +87,7 @@ export default class SettingsUiHandler extends UiHandler {
this.settingsContainer.setVisible(false);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
const settings: object = localStorage.hasOwnProperty('settings') ? JSON.parse(localStorage.getItem('settings')) : {};
@@ -100,6 +100,8 @@ export default class SettingsUiHandler extends UiHandler {
this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
processInput(button: Button): boolean {
+5 -1
View File
@@ -314,7 +314,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.updateInstructions();
}
show(args: any[]): void {
show(args: any[]): boolean {
if (args.length >= 1 && args[0] instanceof Function) {
super.show(args);
@@ -335,7 +335,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.setCursor(0);
this.setGenMode(true);
this.setCursor(0);
return true;
}
return false;
}
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) {
+3 -1
View File
@@ -181,7 +181,7 @@ export default class SummaryUiHandler extends UiHandler {
return `summary_${Page[page].toLowerCase()}`;
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
this.pokemon = args[0] as PlayerPokemon;
@@ -237,6 +237,8 @@ export default class SummaryUiHandler extends UiHandler {
this.status.setFrame(this.pokemon.status ? StatusEffect[this.pokemon.status.effect].toLowerCase() : 'pokerus');
} else
this.hideStatus(!fromSummary);
return true;
}
processInput(button: Button): boolean {
+5 -3
View File
@@ -23,9 +23,9 @@ export default class TargetSelectUiHandler extends UiHandler {
setup(): void { }
show(args: any[]) {
show(args: any[]): boolean {
if (args.length < 3)
return;
return false;
super.show(args);
@@ -36,9 +36,11 @@ export default class TargetSelectUiHandler extends UiHandler {
this.targets = getMoveTargets(this.scene.getPlayerField()[this.fieldIndex], this.move).targets;
if (!this.targets.length)
return;
return false;
this.setCursor(this.targets.indexOf(this.cursor) > -1 ? this.cursor : this.targets[0]);
return true;
}
processInput(button: Button): boolean {
+14 -3
View File
@@ -1,4 +1,5 @@
import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
export enum TextStyle {
MESSAGE,
@@ -22,7 +23,7 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content
const ret = scene.add.text(x, y, content, styleOptions);
ret.setScale(0.1666666667);
ret.setShadow(shadowSize, shadowSize, shadowColor);
if (!styleOptions.lineSpacing)
if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing)
ret.setLineSpacing(5);
return ret;
@@ -35,13 +36,23 @@ export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, c
scene.add.existing(ret);
ret.setScale(0.1666666667);
ret.setShadow(shadowSize, shadowSize, shadowColor);
if (!styleOptions.lineSpacing)
if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing)
ret.setLineSpacing(5);
return ret;
}
function getTextStyleOptions(style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): [ Phaser.Types.GameObjects.Text.TextStyle, string, integer ] {
export function addTextInputObject(scene: Phaser.Scene, x: number, y: number, width: number, height: number, style: TextStyle, extraStyleOptions?: InputText.IConfig): InputText {
const [ styleOptions ] = getTextStyleOptions(style, extraStyleOptions);
const ret = new InputText(scene, x, y, width, height, styleOptions as InputText.IConfig);
scene.add.existing(ret);
ret.setScale(0.1666666667);
return ret;
}
function getTextStyleOptions(style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): [ Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig, string, integer ] {
let shadowColor: string;
let shadowSize = 6;
+3 -1
View File
@@ -14,8 +14,10 @@ export default abstract class UiHandler {
abstract setup(): void;
show(_args: any[]): void {
show(_args: any[]): boolean {
this.active = true;
return true;
}
abstract processInput(button: Button): boolean;
+26 -7
View File
@@ -24,8 +24,11 @@ import EggHatchSceneHandler from './egg-hatch-scene-handler';
import EggListUiHandler from './egg-list-ui-handler';
import EggGachaUiHandler from './egg-gacha-ui-handler';
import VouchersUiHandler from './vouchers-ui-handler';
import VoucherBar from './voucher-bar';
import { addWindow } from './window';
import LoginFormUiHandler from './login-form-ui-handler';
import RegistrationFormUiHandler from './registration-form-ui-handler';
import LoadingModalUiHandler from './loading-modal-ui-handler';
import * as Utils from "../utils";
export enum Mode {
MESSAGE,
@@ -48,7 +51,10 @@ export enum Mode {
ACHIEVEMENTS,
VOUCHERS,
EGG_LIST,
EGG_GACHA
EGG_GACHA,
LOGIN_FORM,
REGISTRATION_FORM,
LOADING
};
const transitionModes = [
@@ -68,7 +74,10 @@ const noTransitionModes = [
Mode.MENU,
Mode.SETTINGS,
Mode.ACHIEVEMENTS,
Mode.VOUCHERS
Mode.VOUCHERS,
Mode.LOGIN_FORM,
Mode.REGISTRATION_FORM,
Mode.LOADING
];
export default class UI extends Phaser.GameObjects.Container {
@@ -77,7 +86,6 @@ export default class UI extends Phaser.GameObjects.Container {
private handlers: UiHandler[];
private overlay: Phaser.GameObjects.Rectangle;
public achvBar: AchvBar;
public voucherBar: VoucherBar;
private tooltipContainer: Phaser.GameObjects.Container;
private tooltipBg: Phaser.GameObjects.NineSlice;
@@ -112,7 +120,10 @@ export default class UI extends Phaser.GameObjects.Container {
new AchvsUiHandler(scene),
new VouchersUiHandler(scene),
new EggListUiHandler(scene),
new EggGachaUiHandler(scene)
new EggGachaUiHandler(scene),
new LoginFormUiHandler(scene),
new RegistrationFormUiHandler(scene),
new LoadingModalUiHandler(scene)
];
}
@@ -135,7 +146,7 @@ export default class UI extends Phaser.GameObjects.Container {
this.tooltipContainer = this.scene.add.container(0, 0);
this.tooltipContainer.setVisible(false);
this.tooltipBg = addWindow(this.scene, 0, 0, 128, 31);
this.tooltipBg = addWindow(this.scene as BattleScene, 0, 0, 128, 31);
this.tooltipBg.setOrigin(0, 0);
this.tooltipTitle = addTextObject(this.scene, 64, 4, '', TextStyle.TOOLTIP_TITLE);
@@ -323,7 +334,7 @@ export default class UI extends Phaser.GameObjects.Container {
revertMode(): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!this.modeChain.length)
if (!this?.modeChain?.length)
return resolve(false);
const lastMode = this.mode;
@@ -349,4 +360,12 @@ export default class UI extends Phaser.GameObjects.Container {
resolve(true);
});
}
revertModes(): Promise<void> {
return new Promise<void>(resolve => {
if (!this?.modeChain?.length)
return resolve();
this.revertMode().then(success => Utils.executeIf(success, this.revertModes).then(() => resolve()));
});
}
}
+3 -1
View File
@@ -95,7 +95,7 @@ export default class VouchersUiHandler extends MessageUiHandler {
this.vouchersContainer.setVisible(false);
}
show(args: any[]) {
show(args: any[]): boolean {
super.show(args);
const voucherUnlocks = this.scene.gameData.voucherUnlocks;
@@ -117,6 +117,8 @@ export default class VouchersUiHandler extends MessageUiHandler {
this.getUi().moveTo(this.vouchersContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
protected showVoucher(voucher: Voucher) {
+26 -6
View File
@@ -1,5 +1,22 @@
import BattleScene from "../battle-scene";
export enum WindowVariant {
NORMAL,
THIN,
XTHIN
}
export function getWindowVariantSuffix(windowVariant: WindowVariant): string {
switch (windowVariant) {
case WindowVariant.THIN:
return '_thin';
case WindowVariant.XTHIN:
return '_xthin';
default:
return '';
}
}
const windowTypeControlColors = {
0: [ '#706880', '#8888c8', '#484868' ],
1: [ '#d04028', '#e0a028', '#902008' ],
@@ -7,8 +24,11 @@ const windowTypeControlColors = {
3: [ '#2068d0', '#80b0e0', '#104888' ]
};
export function addWindow(scene: BattleScene, x: number, y: number, width: number, height: number, mergeMaskTop?: boolean, mergeMaskLeft?: boolean, maskOffsetX?: number, maskOffsetY?: number): Phaser.GameObjects.NineSlice {
const window = scene.add.nineslice(x, y, `window_${scene.windowType}`, null, width, height, 8, 8, 8, 8);
export function addWindow(scene: BattleScene, x: number, y: number, width: number, height: number, mergeMaskTop?: boolean, mergeMaskLeft?: boolean, maskOffsetX?: number, maskOffsetY?: number, windowVariant?: WindowVariant): Phaser.GameObjects.NineSlice {
if (windowVariant === undefined)
windowVariant = WindowVariant.NORMAL;
const window = scene.add.nineslice(x, y, `window_${scene.windowType}${getWindowVariantSuffix(windowVariant)}`, null, width, height, 6, 6, 6, 6);
window.setOrigin(0, 0);
if (mergeMaskTop || mergeMaskLeft) {
@@ -24,7 +44,7 @@ export function addWindow(scene: BattleScene, x: number, y: number, width: numbe
}
export function updateWindowType(scene: BattleScene, windowTypeIndex: integer): void {
const windowObjects: Phaser.GameObjects.NineSlice[] = [];
const windowObjects: [Phaser.GameObjects.NineSlice, WindowVariant][] = [];
const traverse = (object: any) => {
if (object.hasOwnProperty('children')) {
const children = object.children as Phaser.GameObjects.DisplayList;
@@ -35,7 +55,7 @@ export function updateWindowType(scene: BattleScene, windowTypeIndex: integer):
traverse(child);
} else if (object instanceof Phaser.GameObjects.NineSlice) {
if (object.texture.key.startsWith('window_'))
windowObjects.push(object);
windowObjects.push([ object, object.texture.key.endsWith(getWindowVariantSuffix(WindowVariant.XTHIN)) ? WindowVariant.XTHIN : object.texture.key.endsWith(getWindowVariantSuffix(WindowVariant.THIN)) ? WindowVariant.THIN : WindowVariant.NORMAL ]);
}
}
@@ -48,6 +68,6 @@ export function updateWindowType(scene: BattleScene, windowTypeIndex: integer):
const windowKey = `window_${windowTypeIndex}`;
for (let window of windowObjects)
window.setTexture(windowKey);
for (let [ window, variant ] of windowObjects)
window.setTexture(`${windowKey}${getWindowVariantSuffix(variant)}`);
}