Add data save and load
parent
97124c2710
commit
e107349a98
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"json-stable-stringify": "^1.1.0",
|
"json-stable-stringify": "^1.1.0",
|
||||||
"phaser": "^3.70.0",
|
"phaser": "^3.70.0",
|
||||||
"phaser3-rex-plugins": "^1.1.84"
|
"phaser3-rex-plugins": "^1.1.84"
|
||||||
|
@ -849,6 +850,11 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypto-js": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"json-stable-stringify": "^1.1.0",
|
"json-stable-stringify": "^1.1.0",
|
||||||
"phaser": "^3.70.0",
|
"phaser": "^3.70.0",
|
||||||
"phaser3-rex-plugins": "^1.1.84"
|
"phaser3-rex-plugins": "^1.1.84"
|
||||||
|
|
|
@ -373,6 +373,8 @@ export default class BattleScene extends Phaser.Scene {
|
||||||
this.loadBgm('evolution_fanfare', 'bw/evolution_fanfare.mp3');
|
this.loadBgm('evolution_fanfare', 'bw/evolution_fanfare.mp3');
|
||||||
|
|
||||||
populateAnims();
|
populateAnims();
|
||||||
|
|
||||||
|
//this.load.plugin('rexfilechooserplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexfilechooserplugin.min.js', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
|
@ -17,6 +17,27 @@ import { achvs } from "./achv";
|
||||||
import EggData from "./egg-data";
|
import EggData from "./egg-data";
|
||||||
import { Egg } from "../data/egg";
|
import { Egg } from "../data/egg";
|
||||||
import { VoucherType, vouchers } from "./voucher";
|
import { VoucherType, vouchers } from "./voucher";
|
||||||
|
import { AES, enc } from "crypto-js";
|
||||||
|
import { Mode } from "../ui/ui";
|
||||||
|
|
||||||
|
const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary
|
||||||
|
|
||||||
|
export enum GameDataType {
|
||||||
|
SYSTEM,
|
||||||
|
SESSION,
|
||||||
|
SETTINGS
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataTypeKey(dataType: GameDataType): string {
|
||||||
|
switch (dataType) {
|
||||||
|
case GameDataType.SYSTEM:
|
||||||
|
return 'data';
|
||||||
|
case GameDataType.SESSION:
|
||||||
|
return 'sessionData';
|
||||||
|
case GameDataType.SETTINGS:
|
||||||
|
return 'settings';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SystemSaveData {
|
interface SystemSaveData {
|
||||||
trainerId: integer;
|
trainerId: integer;
|
||||||
|
@ -163,16 +184,7 @@ export class GameData {
|
||||||
if (!localStorage.hasOwnProperty('data'))
|
if (!localStorage.hasOwnProperty('data'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const data = JSON.parse(atob(localStorage.getItem('data')), (k: string, v: any) => {
|
const data = this.parseSystemData(atob(localStorage.getItem('data')));
|
||||||
if (k === 'eggs') {
|
|
||||||
const ret: EggData[] = [];
|
|
||||||
for (let e of v)
|
|
||||||
ret.push(new EggData(e));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return k.endsWith('Attr') ? BigInt(v) : v;
|
|
||||||
}) as SystemSaveData;
|
|
||||||
|
|
||||||
console.debug(data);
|
console.debug(data);
|
||||||
|
|
||||||
|
@ -225,6 +237,19 @@ export class GameData {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseSystemData(dataStr: string): SystemSaveData {
|
||||||
|
return JSON.parse(dataStr, (k: string, v: any) => {
|
||||||
|
if (k === 'eggs') {
|
||||||
|
const ret: EggData[] = [];
|
||||||
|
for (let e of v)
|
||||||
|
ret.push(new EggData(e));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.endsWith('Attr') ? BigInt(v) : v;
|
||||||
|
}) as SystemSaveData;
|
||||||
|
}
|
||||||
|
|
||||||
public saveSetting(setting: Setting, valueIndex: integer): boolean {
|
public saveSetting(setting: Setting, valueIndex: integer): boolean {
|
||||||
let settings: object = {};
|
let settings: object = {};
|
||||||
if (localStorage.hasOwnProperty('settings'))
|
if (localStorage.hasOwnProperty('settings'))
|
||||||
|
@ -289,36 +314,8 @@ export class GameData {
|
||||||
return resolve(false);
|
return resolve(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sessionData = JSON.parse(atob(localStorage.getItem('sessionData')), (k: string, v: any) => {
|
const sessionDataStr = atob(localStorage.getItem('sessionData'));
|
||||||
/*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ];
|
const sessionData = this.parseSessionData(sessionDataStr);
|
||||||
|
|
||||||
if (versions[0] !== versions[1]) {
|
|
||||||
const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v)));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (k === 'party' || k === 'enemyParty' || k === 'enemyField') {
|
|
||||||
const ret: PokemonData[] = [];
|
|
||||||
for (let pd of v)
|
|
||||||
ret.push(new PokemonData(pd));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k === 'trainer')
|
|
||||||
return v ? new TrainerData(v) : null;
|
|
||||||
|
|
||||||
if (k === 'modifiers' || k === 'enemyModifiers') {
|
|
||||||
const player = k === 'modifiers';
|
|
||||||
const ret: PersistentModifierData[] = [];
|
|
||||||
for (let md of v)
|
|
||||||
ret.push(new PersistentModifierData(md, player));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k === 'arena')
|
|
||||||
return new ArenaData(v);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}) as SessionSaveData;
|
|
||||||
|
|
||||||
console.debug(sessionData);
|
console.debug(sessionData);
|
||||||
|
|
||||||
|
@ -346,10 +343,6 @@ export class GameData {
|
||||||
scene.money = sessionData.money || 0;
|
scene.money = sessionData.money || 0;
|
||||||
scene.updateMoneyText();
|
scene.updateMoneyText();
|
||||||
|
|
||||||
// TODO: Remove this
|
|
||||||
if (sessionData.enemyField)
|
|
||||||
sessionData.enemyParty = sessionData.enemyField;
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
@ -398,6 +391,126 @@ export class GameData {
|
||||||
localStorage.removeItem('sessionData');
|
localStorage.removeItem('sessionData');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseSessionData(dataStr: string): SessionSaveData {
|
||||||
|
return JSON.parse(dataStr, (k: string, v: any) => {
|
||||||
|
/*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ];
|
||||||
|
|
||||||
|
if (versions[0] !== versions[1]) {
|
||||||
|
const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v)));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (k === 'party' || k === 'enemyParty' || k === 'enemyField') {
|
||||||
|
const ret: PokemonData[] = [];
|
||||||
|
for (let pd of v)
|
||||||
|
ret.push(new PokemonData(pd));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k === 'trainer')
|
||||||
|
return v ? new TrainerData(v) : null;
|
||||||
|
|
||||||
|
if (k === 'modifiers' || k === 'enemyModifiers') {
|
||||||
|
const player = k === 'modifiers';
|
||||||
|
const ret: PersistentModifierData[] = [];
|
||||||
|
for (let md of v)
|
||||||
|
ret.push(new PersistentModifierData(md, player));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k === 'arena')
|
||||||
|
return new ArenaData(v);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}) as SessionSaveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public exportData(dataType: GameDataType): void {
|
||||||
|
const dataKey: string = getDataTypeKey(dataType);
|
||||||
|
const dataStr = atob(localStorage.getItem(dataKey));
|
||||||
|
console.log(dataStr);
|
||||||
|
const encryptedData = AES.encrypt(dataStr, saveKey);
|
||||||
|
const blob = new Blob([ encryptedData.toString() ], {type: 'text/json'});
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `${dataKey}.prsv`;
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public importData(dataType: GameDataType): void {
|
||||||
|
const dataKey = getDataTypeKey(dataType);
|
||||||
|
|
||||||
|
let saveFile: any = document.getElementById('saveFile');
|
||||||
|
if (saveFile)
|
||||||
|
saveFile.remove();
|
||||||
|
|
||||||
|
saveFile = document.createElement('input');
|
||||||
|
saveFile.id = 'saveFile';
|
||||||
|
saveFile.type = 'file';
|
||||||
|
saveFile.accept = '.prsv';
|
||||||
|
saveFile.style.display = 'none';
|
||||||
|
saveFile.addEventListener('change',
|
||||||
|
e => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (_ => {
|
||||||
|
return e => {
|
||||||
|
const dataStr = AES.decrypt(e.target.result.toString(), saveKey).toString(enc.Utf8);
|
||||||
|
let valid = false;
|
||||||
|
try {
|
||||||
|
switch (dataType) {
|
||||||
|
case GameDataType.SYSTEM:
|
||||||
|
const systemData = this.parseSystemData(dataStr);
|
||||||
|
valid = !!systemData.dexData && !!systemData.timestamp;
|
||||||
|
break;
|
||||||
|
case GameDataType.SESSION:
|
||||||
|
const sessionData = this.parseSessionData(dataStr);
|
||||||
|
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
|
||||||
|
break;
|
||||||
|
case GameDataType.SETTINGS:
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataName: string;
|
||||||
|
switch (dataType) {
|
||||||
|
case GameDataType.SYSTEM:
|
||||||
|
dataName = 'save';
|
||||||
|
break;
|
||||||
|
case GameDataType.SESSION:
|
||||||
|
dataName = 'session';
|
||||||
|
break;
|
||||||
|
case GameDataType.SETTINGS:
|
||||||
|
dataName = 'settings';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500));
|
||||||
|
this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => {
|
||||||
|
this.scene.ui.setOverlayMode(Mode.CONFIRM, () => {
|
||||||
|
localStorage.setItem(dataKey, btoa(dataStr));
|
||||||
|
window.location = window.location;
|
||||||
|
}, () => {
|
||||||
|
this.scene.ui.revertMode();
|
||||||
|
this.scene.ui.showText(null, 0);
|
||||||
|
}, false, 98);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})((e.target as any).files[0]);
|
||||||
|
|
||||||
|
reader.readAsText((e.target as any).files[0]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
saveFile.click();
|
||||||
|
/*(this.scene.plugins.get('rexfilechooserplugin') as FileChooserPlugin).open({ accept: '.prsv' })
|
||||||
|
.then(result => {
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
private initDexData(): void {
|
private initDexData(): void {
|
||||||
const data: DexData = {};
|
const data: DexData = {};
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,10 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
|
||||||
|
|
||||||
this.switchCheck = args.length >= 3 && args[2] as boolean;
|
this.switchCheck = args.length >= 3 && args[2] as boolean;
|
||||||
|
|
||||||
|
const xOffset = (args.length >= 4 ? -args[3] as number : 0);
|
||||||
|
|
||||||
|
this.optionSelectContainer.x = (this.scene.game.canvas.width / 6) - 1 + xOffset;
|
||||||
|
|
||||||
this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
|
this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,10 +117,6 @@ export default class EggListUiHandler extends MessageUiHandler {
|
||||||
this.setCursor(0);
|
this.setCursor(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) {
|
|
||||||
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
processInput(button: Button): boolean {
|
processInput(button: Button): boolean {
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,24 @@ import { Mode } from "./ui";
|
||||||
import UiHandler from "./ui-handler";
|
import UiHandler from "./ui-handler";
|
||||||
import * as Utils from "../utils";
|
import * as Utils from "../utils";
|
||||||
import { addWindow } from "./window";
|
import { addWindow } from "./window";
|
||||||
|
import MessageUiHandler from "./message-ui-handler";
|
||||||
|
import { GameDataType } from "../system/game-data";
|
||||||
|
|
||||||
export enum MenuOptions {
|
export enum MenuOptions {
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
ACHIEVEMENTS,
|
ACHIEVEMENTS,
|
||||||
VOUCHERS,
|
VOUCHERS,
|
||||||
EGG_LIST,
|
EGG_LIST,
|
||||||
EGG_GACHA
|
EGG_GACHA,
|
||||||
|
IMPORT_SESSION,
|
||||||
|
EXPORT_SESSION,
|
||||||
|
IMPORT_DATA,
|
||||||
|
EXPORT_DATA
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MenuUiHandler extends UiHandler {
|
export default class MenuUiHandler extends MessageUiHandler {
|
||||||
private menuContainer: Phaser.GameObjects.Container;
|
private menuContainer: Phaser.GameObjects.Container;
|
||||||
|
private menuMessageBoxContainer: Phaser.GameObjects.Container;
|
||||||
|
|
||||||
private menuBg: Phaser.GameObjects.NineSlice;
|
private menuBg: Phaser.GameObjects.NineSlice;
|
||||||
protected optionSelectText: Phaser.GameObjects.Text;
|
protected optionSelectText: Phaser.GameObjects.Text;
|
||||||
|
@ -32,7 +39,7 @@ export default class MenuUiHandler extends UiHandler {
|
||||||
|
|
||||||
this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
|
this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
|
||||||
|
|
||||||
this.menuBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - 92, 0, 90, (this.scene.game.canvas.height / 6) - 2);
|
this.menuBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - 100, 0, 98, (this.scene.game.canvas.height / 6) - 2);
|
||||||
this.menuBg.setOrigin(0, 0);
|
this.menuBg.setOrigin(0, 0);
|
||||||
|
|
||||||
this.menuContainer.add(this.menuBg);
|
this.menuContainer.add(this.menuBg);
|
||||||
|
@ -44,6 +51,23 @@ export default class MenuUiHandler extends UiHandler {
|
||||||
|
|
||||||
ui.add(this.menuContainer);
|
ui.add(this.menuContainer);
|
||||||
|
|
||||||
|
this.menuMessageBoxContainer = this.scene.add.container(0, 130);
|
||||||
|
this.menuMessageBoxContainer.setVisible(false);
|
||||||
|
this.menuContainer.add(this.menuMessageBoxContainer);
|
||||||
|
|
||||||
|
const menuMessageBox = addWindow(this.scene, 0, -0, 220, 48);
|
||||||
|
menuMessageBox.setOrigin(0, 0);
|
||||||
|
this.menuMessageBoxContainer.add(menuMessageBox);
|
||||||
|
|
||||||
|
const menuMessageText = addTextObject(this.scene, 8, 8, '', TextStyle.WINDOW, { maxLines: 2 });
|
||||||
|
menuMessageText.setWordWrapWidth(1224);
|
||||||
|
menuMessageText.setOrigin(0, 0);
|
||||||
|
this.menuMessageBoxContainer.add(menuMessageText);
|
||||||
|
|
||||||
|
this.message = menuMessageText;
|
||||||
|
|
||||||
|
this.menuContainer.add(this.menuMessageBoxContainer);
|
||||||
|
|
||||||
this.setCursor(0);
|
this.setCursor(0);
|
||||||
|
|
||||||
this.menuContainer.setVisible(false);
|
this.menuContainer.setVisible(false);
|
||||||
|
@ -95,7 +119,16 @@ export default class MenuUiHandler extends UiHandler {
|
||||||
this.scene.ui.setOverlayMode(Mode.EGG_GACHA);
|
this.scene.ui.setOverlayMode(Mode.EGG_GACHA);
|
||||||
success = true;
|
success = true;
|
||||||
break;
|
break;
|
||||||
|
case MenuOptions.IMPORT_SESSION:
|
||||||
|
case MenuOptions.IMPORT_DATA:
|
||||||
|
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;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (button === Button.CANCEL) {
|
} else if (button === Button.CANCEL) {
|
||||||
success = true;
|
success = true;
|
||||||
|
@ -122,6 +155,12 @@ export default class MenuUiHandler extends UiHandler {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void {
|
||||||
|
this.menuMessageBoxContainer.setVisible(!!text);
|
||||||
|
|
||||||
|
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
|
||||||
|
}
|
||||||
|
|
||||||
setCursor(cursor: integer): boolean {
|
setCursor(cursor: integer): boolean {
|
||||||
const ret = super.setCursor(cursor);
|
const ret = super.setCursor(cursor);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue