This repository has been archived on 2021-02-20. You can view files and clone it, but cannot push or open issues or pull requests.
shapez.io/src/js/savegame/savegame_manager.js

249 lines
6.9 KiB
JavaScript

import { ExplainedResult } from "../core/explained_result";
import { createLogger } from "../core/logging";
import { ReadWriteProxy } from "../core/read_write_proxy";
import { globalConfig } from "../core/config";
import { Savegame } from "./savegame";
const logger = createLogger("savegame_manager");
const Rusha = require("rusha");
/**
* @typedef {import("./savegame_typedefs").SavegamesData} SavegamesData
* @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
*/
/** @enum {string} */
export const enumLocalSavegameStatus = {
offline: "offline",
synced: "synced",
};
export class SavegameManager extends ReadWriteProxy {
constructor(app) {
super(app, "savegames.bin");
this.currentData = this.getDefaultData();
}
// RW Proxy Impl
/**
* @returns {SavegamesData}
*/
getDefaultData() {
return {
version: this.getCurrentVersion(),
savegames: [],
};
}
getCurrentVersion() {
return 1002;
}
verify(data) {
// @TODO
return ExplainedResult.good();
}
/**
*
* @param {SavegamesData} data
*/
migrate(data) {
if (data.version < 1001) {
data.savegames.forEach(savegame => {
savegame.level = 0;
});
data.version = 1001;
}
if (data.version < 1002) {
data.savegames.forEach(savegame => {
savegame.name = null;
});
data.version = 1002;
}
return ExplainedResult.good();
}
// End rw proxy
/**
* @returns {Array<SavegameMetadata>}
*/
getSavegamesMetaData() {
return this.currentData.savegames;
}
/**
*
* @param {string} internalId
* @returns {Savegame}
*/
getSavegameById(internalId) {
const metadata = this.getGameMetaDataByInternalId(internalId);
if (!metadata) {
return null;
}
return new Savegame(this.app, { internalId, metaDataRef: metadata });
}
/**
* Returns if this manager has any savegame of a 1.1.19 version, which
* enables all levels
*/
getHasAnyLegacySavegames() {
return this.currentData.savegames.some(savegame => savegame.version === 1005 || savegame.level > 14);
}
/**
* Deletes a savegame
* @param {SavegameMetadata} game
*/
deleteSavegame(game) {
const handle = new Savegame(this.app, {
internalId: game.internalId,
metaDataRef: game,
});
return handle.deleteAsync().then(() => {
for (let i = 0; i < this.currentData.savegames.length; ++i) {
const potentialGame = this.currentData.savegames[i];
if (potentialGame.internalId === handle.internalId) {
this.currentData.savegames.splice(i, 1);
break;
}
}
return this.writeAsync();
});
}
/**
* Returns a given games metadata by id
* @param {string} id
* @returns {SavegameMetadata}
*/
getGameMetaDataByInternalId(id) {
for (let i = 0; i < this.currentData.savegames.length; ++i) {
const data = this.currentData.savegames[i];
if (data.internalId === id) {
return data;
}
}
logger.error("Savegame internal id not found:", id);
return null;
}
/**
* Creates a new savegame
* @returns {Savegame}
*/
createNewSavegame() {
const id = this.generateInternalId();
const metaData = /** @type {SavegameMetadata} */ ({
lastUpdate: Date.now(),
version: Savegame.getCurrentVersion(),
internalId: id,
});
this.currentData.savegames.push(metaData);
// Notice: This is async and happening in the background
this.updateAfterSavegamesChanged();
return new Savegame(this.app, {
internalId: id,
metaDataRef: metaData,
});
}
/**
* Attempts to import a savegame
* @param {object} data
*/
importSavegame(data) {
const savegame = this.createNewSavegame();
// Track legacy savegames
const isOldSavegame = data.version < 1006;
const migrationResult = savegame.migrate(data);
if (migrationResult.isBad()) {
return Promise.reject("Failed to migrate: " + migrationResult.reason);
}
savegame.currentData = data;
const verification = savegame.verify(data);
if (verification.isBad()) {
return Promise.reject("Verification failed: " + verification.result);
}
return savegame
.writeSavegameAndMetadata()
.then(() => this.updateAfterSavegamesChanged())
.then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(isOldSavegame));
}
/**
* Hook after the savegames got changed
*/
updateAfterSavegamesChanged() {
return this.sortSavegames()
.then(() => this.writeAsync())
.then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(this.getHasAnyLegacySavegames()));
}
/**
* Sorts all savegames by their creation time descending
* @returns {Promise<any>}
*/
sortSavegames() {
this.currentData.savegames.sort((a, b) => b.lastUpdate - a.lastUpdate);
let promiseChain = Promise.resolve();
while (this.currentData.savegames.length > 30) {
const toRemove = this.currentData.savegames.pop();
// Try to remove the savegame since its no longer available
const game = new Savegame(this.app, {
internalId: toRemove.internalId,
metaDataRef: toRemove,
});
promiseChain = promiseChain
.then(() => game.deleteAsync())
.then(
() => {},
err => {
logger.error(this, "Failed to remove old savegame:", toRemove, ":", err);
}
);
}
return promiseChain;
}
/**
* Helper method to generate a new internal savegame id
*/
generateInternalId() {
return Rusha.createHash()
.update(Date.now() + "/" + Math.random())
.digest("hex");
}
// End
initialize() {
// First read, then directly write to ensure we have the latest data
// @ts-ignore
return this.readAsync().then(() => {
if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
return Promise.resolve();
}
return this.updateAfterSavegamesChanged();
});
}
}