1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2024-10-27 20:34:29 +00:00

Allow naming savegames

This commit is contained in:
tobspr 2020-08-28 22:15:12 +02:00
parent a095cd6324
commit 728f4ae253
5 changed files with 1383 additions and 1297 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,38 +1,39 @@
/** /**
* @typedef {import("../game/entity").Entity} Entity * @typedef {import("../game/entity").Entity} Entity
* *
* @typedef {{}} SavegameStats * @typedef {{}} SavegameStats
* *
* @typedef {{ * @typedef {{
* camera: any, * camera: any,
* time: any, * time: any,
* entityMgr: any, * entityMgr: any,
* map: any, * map: any,
* hubGoals: any, * hubGoals: any,
* pinnedShapes: any, * pinnedShapes: any,
* waypoints: any, * waypoints: any,
* entities: Array<Entity>, * entities: Array<Entity>,
* beltPaths: Array<any> * beltPaths: Array<any>
* }} SerializedGame * }} SerializedGame
* *
* @typedef {{ * @typedef {{
* version: number, * version: number,
* dump: SerializedGame, * dump: SerializedGame,
* stats: SavegameStats, * stats: SavegameStats,
* lastUpdate: number, * lastUpdate: number,
* }} SavegameData * }} SavegameData
* *
* @typedef {{ * @typedef {{
* lastUpdate: number, * lastUpdate: number,
* version: number, * version: number,
* internalId: string, * internalId: string,
* level: number * level: number
* }} SavegameMetadata * name: string|null
* * }} SavegameMetadata
* @typedef {{ *
* version: number, * @typedef {{
* savegames: Array<SavegameMetadata> * version: number,
* }} SavegamesData * savegames: Array<SavegameMetadata>
*/ * }} SavegamesData
*/
export default {};
export default {};

File diff suppressed because it is too large Load Diff

View File

@ -157,6 +157,7 @@ mainMenu:
savegameLevel: Level <x> savegameLevel: Level <x>
savegameLevelUnknown: Unknown Level savegameLevelUnknown: Unknown Level
savegameUnnamed: Unnamed
dialogs: dialogs:
buttons: buttons:
@ -274,6 +275,10 @@ dialogs:
title: Export screenshot title: Export screenshot
desc: You requested to export your base as a screenshot. Please note that this can be quite slow for a big base and even crash your game! desc: You requested to export your base as a screenshot. Please note that this can be quite slow for a big base and even crash your game!
renameSavegame:
title: Rename Savegame
desc: You can rename your savegame here.
ingame: ingame:
# This is shown in the top left corner and displays useful keybindings in # This is shown in the top left corner and displays useful keybindings in
# every situation # every situation