Added local system save & sync with server every 5 waves

This commit is contained in:
gamray 2024-05-09 09:53:16 +02:00
parent b231b887aa
commit 4b077e6b21
3 changed files with 220 additions and 10 deletions

View File

@ -116,13 +116,26 @@ export class LoginPhase extends Phase {
}
return null;
} else {
this.scene.gameData.loadSystem().then(success => {
if (success || bypassLogin)
this.end();
else {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t('menu:failedToLoadSaveData'));
this.scene.gameData.getLastSystemRemoteSave().then(timestamp => {
let localLastSave = this.scene.gameData.getLastSystemLocalSave();
let loadSource = undefined
if (localLastSave > timestamp) {
console.log("LocalSave is the most recent, loading from it.")
loadSource = () => this.scene.gameData.loadSystemLocal()
} else {
console.log("LocalSave is outdated, loading from remote.")
loadSource = () => this.scene.gameData.loadSystem()
}
loadSource().then(success => {
if (success || bypassLogin)
this.end();
else {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t('menu:failedToLoadSaveData'));
}
});
});
}
});
@ -400,7 +413,7 @@ export class ReloadSessionPhase extends Phase {
else
delayElapsed = true;
});
// This is called when previous sync with the server failed, no need to fetch local data
this.scene.gameData.loadSystem().then(() => {
if (delayElapsed)
this.end();
@ -436,7 +449,8 @@ export class SelectGenderPhase extends Phase {
handler: () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.scene.gameData.saveSetting(Setting.Player_Gender, 0);
this.scene.gameData.saveSystem().then(() => this.end());
this.scene.gameData.saveSystem().then(() => console.log("System data synced"));
this.scene.gameData.saveSystemLocal().then(() => this.end());
return true;
}
},
@ -445,7 +459,8 @@ export class SelectGenderPhase extends Phase {
handler: () => {
this.scene.gameData.gender = PlayerGender.FEMALE;
this.scene.gameData.saveSetting(Setting.Player_Gender, 1);
this.scene.gameData.saveSystem().then(() => this.end());
this.scene.gameData.saveSystem().then(() => console.log("System data synced"));
this.scene.gameData.saveSystemLocal().then(() => this.end());
return true;
}
}
@ -771,12 +786,23 @@ export class EncounterPhase extends BattlePhase {
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
if (!this.loaded) {
this.scene.gameData.saveSystem().then(success => {
this.scene.gameData.saveSystemLocal().then(success => {
this.scene.disableMenu = false;
if (!success)
return this.scene.reset(true);
this.scene.gameData.saveSession(this.scene, true).then(() => this.doEncounter());
});
// Syncing with the server every 3 waves
if (battle.waveIndex % 5 == 0) {
this.scene.gameData.saveSystem().then(success => {
if (!success) {
console.log("Failed to sync system with server")
} else {
console.log("Synced system with server.")
}
})
}
} else
this.doEncounter();
});

View File

@ -247,6 +247,39 @@ export class GameData {
this.initStarterData();
}
public saveSystemLocal(): Promise<boolean> {
return new Promise<boolean>(resolve => {
this.scene.ui.savingIcon.show();
const data: SystemSaveData = {
trainerId: this.trainerId,
secretId: this.secretId,
gender: this.gender,
dexData: this.dexData,
starterData: this.starterData,
gameStats: this.gameStats,
unlocks: this.unlocks,
achvUnlocks: this.achvUnlocks,
voucherUnlocks: this.voucherUnlocks,
voucherCounts: this.voucherCounts,
eggs: this.eggs.map(e => new EggData(e)),
gameVersion: this.scene.game.config.gameVersion,
timestamp: new Date().getTime()
};
console.log(data)
const maxIntAttrValue = Math.pow(2, 31);
const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === 'bigint' ? v <= maxIntAttrValue ? Number(v) : v.toString() : v);
localStorage.setItem('data_bak', localStorage.getItem('data'));
localStorage.setItem('data', btoa(systemData));
this.scene.ui.savingIcon.hide();
return resolve(true);
});
}
public saveSystem(): Promise<boolean> {
return new Promise<boolean>(resolve => {
this.scene.ui.savingIcon.show();
@ -299,6 +332,153 @@ export class GameData {
});
}
public getLastSystemLocalSave(): int {
if (!localStorage.hasOwnProperty('data'))
return -1
return this.parseSystemData(atob(localStorage.getItem('data'))).timestamp;
}
public getLastSystemRemoteSave(): Promise<int> {
return new Promise<int>(resolve => {
Utils.apiFetch(`savedata/get?datatype=${GameDataType.SYSTEM}`, true)
.then(response => response.text())
.then(response => {
if (!response.length || response[0] !== '{') {
if (response.startsWith('failed to open save file')) {
this.scene.queueMessage('Save data could not be found. If this is a new account, you can safely ignore this message.', null, true);
return resolve(-1);
} else if (response.indexOf('Too many connections') > -1) {
this.scene.queueMessage('Too many people are trying to connect and the server is overloaded. Please try again later.', null, true);
return resolve(-1);
}
console.error(response);
return resolve(-1);
}
return resolve(this.parseSystemData(response).timestamp)
});
});
}
public loadSystemLocal(): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!localStorage.hasOwnProperty('data'))
return resolve(false)
const handleSystemData = (systemDataStr: string) => {
try {
const systemData = this.parseSystemData(systemDataStr);
console.debug(systemData);
/*const versions = [ this.scene.game.config.gameVersion, data.gameVersion || '0.0.0' ];
if (versions[0] !== versions[1]) {
const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v)));
}*/
this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId;
this.gender = systemData.gender;
this.saveSetting(Setting.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
const initStarterData = !systemData.starterData;
if (initStarterData) {
this.initStarterData();
if (systemData['starterMoveData']) {
const starterMoveData = systemData['starterMoveData'];
for (let s of Object.keys(starterMoveData))
this.starterData[s].moveset = starterMoveData[s];
}
if (systemData['starterEggMoveData']) {
const starterEggMoveData = systemData['starterEggMoveData'];
for (let s of Object.keys(starterEggMoveData))
this.starterData[s].eggMoves = starterEggMoveData[s];
}
this.migrateStarterAbilities(systemData, this.starterData);
} else {
if ([ '1.0.0', '1.0.1' ].includes(systemData.gameVersion))
this.migrateStarterAbilities(systemData);
//this.fixVariantData(systemData);
this.fixStarterData(systemData);
// Migrate ability starter data if empty for caught species
Object.keys(systemData.starterData).forEach(sd => {
if (systemData.dexData[sd].caughtAttr && !systemData.starterData[sd].abilityAttr)
systemData.starterData[sd].abilityAttr = 1;
});
this.starterData = systemData.starterData;
}
if (systemData.gameStats) {
if (systemData.gameStats.legendaryPokemonCaught !== undefined && systemData.gameStats.subLegendaryPokemonCaught === undefined)
this.fixLegendaryStats(systemData);
this.gameStats = systemData.gameStats;
}
if (systemData.unlocks) {
for (let key of Object.keys(systemData.unlocks)) {
if (this.unlocks.hasOwnProperty(key))
this.unlocks[key] = systemData.unlocks[key];
}
}
if (systemData.achvUnlocks) {
for (let a of Object.keys(systemData.achvUnlocks)) {
if (achvs.hasOwnProperty(a))
this.achvUnlocks[a] = systemData.achvUnlocks[a];
}
}
if (systemData.voucherUnlocks) {
for (let v of Object.keys(systemData.voucherUnlocks)) {
if (vouchers.hasOwnProperty(v))
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
}
}
if (systemData.voucherCounts) {
Utils.getEnumKeys(VoucherType).forEach(key => {
const index = VoucherType[key];
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
});
}
this.eggs = systemData.eggs
? systemData.eggs.map(e => e.toEgg())
: [];
this.dexData = Object.assign(this.dexData, systemData.dexData);
this.consolidateDexData(this.dexData);
this.defaultDexData = null;
if (initStarterData) {
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
for (let s of starterIds) {
this.starterData[s].candyCount += this.dexData[s].caughtCount;
this.starterData[s].candyCount += this.dexData[s].hatchedCount * 2;
if (this.dexData[s].caughtAttr & DexAttr.SHINY)
this.starterData[s].candyCount += 4;
}
}
resolve(true);
} catch (err) {
console.error(err);
resolve(false);
}
}
handleSystemData(atob(localStorage.getItem('data')));
return resolve(true)
});
}
public loadSystem(): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (bypassLogin && !localStorage.hasOwnProperty('data'))

View File

@ -310,6 +310,10 @@ export default class MenuUiHandler extends MessageUiHandler {
error = true;
break;
case MenuOptions.LOG_OUT:
// Syncing system to the server before logout
// this way we can safely overwrite local save later
this.scene.gameData.saveSystem().then(() => console.log("System data synced."));
success = true;
const doLogout = () => {
Utils.apiFetch('account/logout', true).then(res => {