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:
parent
a095cd6324
commit
728f4ae253
File diff suppressed because it is too large
Load Diff
@ -1,218 +1,225 @@
|
||||
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 1001;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SavegamesData}
|
||||
*/
|
||||
getCurrentData() {
|
||||
return super.getCurrentData();
|
||||
}
|
||||
|
||||
verify(data) {
|
||||
// TODO / FIXME!!!!
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SavegamesData} data
|
||||
*/
|
||||
migrate(data) {
|
||||
if (data.version < 1001) {
|
||||
data.savegames.forEach(savegame => {
|
||||
savegame.level = 0;
|
||||
});
|
||||
data.version = 1001;
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
this.sortSavegames();
|
||||
|
||||
return new Savegame(this.app, {
|
||||
internalId: id,
|
||||
metaDataRef: metaData,
|
||||
});
|
||||
}
|
||||
|
||||
importSavegame(data) {
|
||||
const savegame = this.createNewSavegame();
|
||||
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.sortSavegames());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.sortSavegames().then(() => this.writeAsync());
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SavegamesData}
|
||||
*/
|
||||
getCurrentData() {
|
||||
return super.getCurrentData();
|
||||
}
|
||||
|
||||
verify(data) {
|
||||
// TODO / FIXME!!!!
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
this.sortSavegames();
|
||||
|
||||
return new Savegame(this.app, {
|
||||
internalId: id,
|
||||
metaDataRef: metaData,
|
||||
});
|
||||
}
|
||||
|
||||
importSavegame(data) {
|
||||
const savegame = this.createNewSavegame();
|
||||
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.sortSavegames());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.sortSavegames().then(() => this.writeAsync());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,39 @@
|
||||
/**
|
||||
* @typedef {import("../game/entity").Entity} Entity
|
||||
*
|
||||
* @typedef {{}} SavegameStats
|
||||
*
|
||||
* @typedef {{
|
||||
* camera: any,
|
||||
* time: any,
|
||||
* entityMgr: any,
|
||||
* map: any,
|
||||
* hubGoals: any,
|
||||
* pinnedShapes: any,
|
||||
* waypoints: any,
|
||||
* entities: Array<Entity>,
|
||||
* beltPaths: Array<any>
|
||||
* }} SerializedGame
|
||||
*
|
||||
* @typedef {{
|
||||
* version: number,
|
||||
* dump: SerializedGame,
|
||||
* stats: SavegameStats,
|
||||
* lastUpdate: number,
|
||||
* }} SavegameData
|
||||
*
|
||||
* @typedef {{
|
||||
* lastUpdate: number,
|
||||
* version: number,
|
||||
* internalId: string,
|
||||
* level: number
|
||||
* }} SavegameMetadata
|
||||
*
|
||||
* @typedef {{
|
||||
* version: number,
|
||||
* savegames: Array<SavegameMetadata>
|
||||
* }} SavegamesData
|
||||
*/
|
||||
|
||||
export default {};
|
||||
/**
|
||||
* @typedef {import("../game/entity").Entity} Entity
|
||||
*
|
||||
* @typedef {{}} SavegameStats
|
||||
*
|
||||
* @typedef {{
|
||||
* camera: any,
|
||||
* time: any,
|
||||
* entityMgr: any,
|
||||
* map: any,
|
||||
* hubGoals: any,
|
||||
* pinnedShapes: any,
|
||||
* waypoints: any,
|
||||
* entities: Array<Entity>,
|
||||
* beltPaths: Array<any>
|
||||
* }} SerializedGame
|
||||
*
|
||||
* @typedef {{
|
||||
* version: number,
|
||||
* dump: SerializedGame,
|
||||
* stats: SavegameStats,
|
||||
* lastUpdate: number,
|
||||
* }} SavegameData
|
||||
*
|
||||
* @typedef {{
|
||||
* lastUpdate: number,
|
||||
* version: number,
|
||||
* internalId: string,
|
||||
* level: number
|
||||
* name: string|null
|
||||
* }} SavegameMetadata
|
||||
*
|
||||
* @typedef {{
|
||||
* version: number,
|
||||
* savegames: Array<SavegameMetadata>
|
||||
* }} SavegamesData
|
||||
*/
|
||||
|
||||
export default {};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -157,6 +157,7 @@ mainMenu:
|
||||
|
||||
savegameLevel: Level <x>
|
||||
savegameLevelUnknown: Unknown Level
|
||||
savegameUnnamed: Unnamed
|
||||
|
||||
dialogs:
|
||||
buttons:
|
||||
@ -274,6 +275,10 @@ dialogs:
|
||||
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!
|
||||
|
||||
renameSavegame:
|
||||
title: Rename Savegame
|
||||
desc: You can rename your savegame here.
|
||||
|
||||
ingame:
|
||||
# This is shown in the top left corner and displays useful keybindings in
|
||||
# every situation
|
||||
|
Loading…
Reference in New Issue
Block a user