Add data save and load
parent
97124c2710
commit
e107349a98
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"json-stable-stringify": "^1.1.0",
|
||||
"phaser": "^3.70.0",
|
||||
"phaser3-rex-plugins": "^1.1.84"
|
||||
|
@ -849,6 +850,11 @@
|
|||
"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": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"json-stable-stringify": "^1.1.0",
|
||||
"phaser": "^3.70.0",
|
||||
"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');
|
||||
|
||||
populateAnims();
|
||||
|
||||
//this.load.plugin('rexfilechooserplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexfilechooserplugin.min.js', true);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
|
@ -17,6 +17,27 @@ import { achvs } from "./achv";
|
|||
import EggData from "./egg-data";
|
||||
import { Egg } from "../data/egg";
|
||||
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 {
|
||||
trainerId: integer;
|
||||
|
@ -163,16 +184,7 @@ export class GameData {
|
|||
if (!localStorage.hasOwnProperty('data'))
|
||||
return false;
|
||||
|
||||
const data = JSON.parse(atob(localStorage.getItem('data')), (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;
|
||||
const data = this.parseSystemData(atob(localStorage.getItem('data')));
|
||||
|
||||
console.debug(data);
|
||||
|
||||
|
@ -225,6 +237,19 @@ export class GameData {
|
|||
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 {
|
||||
let settings: object = {};
|
||||
if (localStorage.hasOwnProperty('settings'))
|
||||
|
@ -289,36 +314,8 @@ export class GameData {
|
|||
return resolve(false);
|
||||
|
||||
try {
|
||||
const sessionData = JSON.parse(atob(localStorage.getItem('sessionData')), (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;
|
||||
const sessionDataStr = atob(localStorage.getItem('sessionData'));
|
||||
const sessionData = this.parseSessionData(sessionDataStr);
|
||||
|
||||
console.debug(sessionData);
|
||||
|
||||
|
@ -346,10 +343,6 @@ export class GameData {
|
|||
scene.money = sessionData.money || 0;
|
||||
scene.updateMoneyText();
|
||||
|
||||
// TODO: Remove this
|
||||
if (sessionData.enemyField)
|
||||
sessionData.enemyParty = sessionData.enemyField;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -398,6 +391,126 @@ export class GameData {
|
|||
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 {
|
||||
const data: DexData = {};
|
||||
|
||||
|
|
|
@ -28,6 +28,10 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,10 +117,6 @@ export default class EggListUiHandler extends MessageUiHandler {
|
|||
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 {
|
||||
const ui = this.getUi();
|
||||
|
||||
|
|
|
@ -4,17 +4,24 @@ 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";
|
||||
|
||||
export enum MenuOptions {
|
||||
SETTINGS,
|
||||
ACHIEVEMENTS,
|
||||
VOUCHERS,
|
||||
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 menuMessageBoxContainer: Phaser.GameObjects.Container;
|
||||
|
||||
private menuBg: Phaser.GameObjects.NineSlice;
|
||||
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.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.menuContainer.add(this.menuBg);
|
||||
|
@ -44,6 +51,23 @@ export default class MenuUiHandler extends UiHandler {
|
|||
|
||||
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.menuContainer.setVisible(false);
|
||||
|
@ -95,7 +119,16 @@ export default class MenuUiHandler extends UiHandler {
|
|||
this.scene.ui.setOverlayMode(Mode.EGG_GACHA);
|
||||
success = true;
|
||||
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) {
|
||||
success = true;
|
||||
|
@ -122,6 +155,12 @@ export default class MenuUiHandler extends UiHandler {
|
|||
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 {
|
||||
const ret = super.setCursor(cursor);
|
||||
|
||||
|
|
Loading…
Reference in New Issue