1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Add savefile migration, serialize, deserialize

This commit is contained in:
Greg Considine 2021-03-15 15:49:34 -04:00
parent 6f0a32f054
commit f478228557
12 changed files with 136 additions and 81 deletions

View File

@ -83,7 +83,7 @@ export class GameCore {
* @param {import("../states/ingame").InGameState} parentState * @param {import("../states/ingame").InGameState} parentState
* @param {Savegame} savegame * @param {Savegame} savegame
*/ */
initializeRoot(parentState, savegame) { initializeRoot(parentState, savegame, gameModeId) {
// Construct the root element, this is the data representation of the game // Construct the root element, this is the data representation of the game
this.root = new GameRoot(this.app); this.root = new GameRoot(this.app);
this.root.gameState = parentState; this.root.gameState = parentState;
@ -105,7 +105,7 @@ export class GameCore {
root.dynamicTickrate = new DynamicTickrate(root); root.dynamicTickrate = new DynamicTickrate(root);
// Init game mode // Init game mode
root.gameMode = GameMode.create(root); root.gameMode = GameMode.create(root, gameModeId);
// Init classes // Init classes
root.camera = new Camera(root); root.camera = new Camera(root);

View File

@ -1,103 +1,54 @@
/* typehints:start */ /* typehints:start */
import { enumHubGoalRewards } from "./tutorial_goals";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
/* typehints:end */ /* typehints:end */
import { gGameModeRegistry } from "../core/global_registries"; import { gGameModeRegistry } from "../core/global_registries";
import { types, BasicSerializableObject } from "../savegame/serialization"; import { types, BasicSerializableObject } from "../savegame/serialization";
/** @typedef {{
* shape: string,
* amount: number
* }} UpgradeRequirement */
/** @typedef {{
* required: Array<UpgradeRequirement>
* improvement?: number,
* excludePrevious?: boolean
* }} TierRequirement */
/** @typedef {Array<TierRequirement>} UpgradeTiers */
/** @typedef {{
* shape: string,
* required: number,
* reward: enumHubGoalRewards,
* throughputOnly?: boolean
* }} LevelDefinition */
export class GameMode extends BasicSerializableObject { export class GameMode extends BasicSerializableObject {
/** @returns {string} */ /** @returns {string} */
static getId() { static getId() {
abstract; abstract;
return "unknown-mode"; return "Unknown";
} }
static getSchema() { static getSchema() {
return { return {};
id: types.string
}
} }
static create (root) { /**
let id; * @param {GameRoot} root
* @param {string} [id=Regular]
if (!root.savegame.gameMode || !root.savegame.gameMode.id) { */
id = "Regular"; static create (root, id = "Regular") {
} else { // id = "Regular"
id = root.savegame.gameMode.id return new (gGameModeRegistry.findById(id))(root);
}
const Mode = gGameModeRegistry.findById(id);
return new Mode(root);
} }
/** @param {GameRoot} root */ /**
* @param {GameRoot} root
* @param {string} [id=Regular]
*/
constructor(root) { constructor(root) {
super(); super();
this.root = root; this.root = root;
this.id = this.getId(); }
serialize() {
return {
$: this.getId(),
data: super.serialize()
}
}
deserialize({ $, data }) {
const Mode = gGameModeRegistry.findById($);
return super.deserialize(data, Mode, gGameModeRegistry.getId(), this.root);
} }
getId() { getId() {
// @ts-ignore // @ts-ignore
return this.constructor.getId(); return this.constructor.getId();
} }
/**
* Should return all available upgrades
* @returns {Object<string, UpgradeTiers>}
*/
getUpgrades() {
abstract;
return null;
}
/**
* Returns the blueprint shape key
* @returns {string}
*/
getBlueprintShapeKey() {
abstract;
return null;
}
/**
* Returns the goals for all levels including their reward
* @returns {Array<LevelDefinition>}
*/
getLevelDefinitions() {
abstract;
return null;
}
/**
* Should return whether free play is available or if the game stops
* after the predefined levels
* @returns {boolean}
*/
getIsFreeplayAvailable() {
return true;
}
} }

View File

@ -5,6 +5,7 @@ export class PuzzlePlayGameMode extends GameMode {
return "PuzzlePlay"; return "PuzzlePlay";
} }
/** param {GameRoot} root */
constructor(root) { constructor(root) {
super(root); super(root);
} }

View File

@ -1,7 +1,33 @@
/* typehints:start */
import { enumHubGoalRewards } from "../tutorial_goals";
import { GameRoot } from "../root";
/* typehints:end */
import { findNiceIntegerValue } from "../../core/utils"; import { findNiceIntegerValue } from "../../core/utils";
import { GameMode } from "../game_mode"; import { GameMode } from "../game_mode";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { enumHubGoalRewards } from "../tutorial_goals"; import { enumHubGoalRewards } from "../tutorial_goals";
import { types } from "../../savegame/serialization";
/** @typedef {{
* shape: string,
* amount: number
* }} UpgradeRequirement */
/** @typedef {{
* required: Array<UpgradeRequirement>
* improvement?: number,
* excludePrevious?: boolean
* }} TierRequirement */
/** @typedef {Array<TierRequirement>} UpgradeTiers */
/** @typedef {{
* shape: string,
* required: number,
* reward: enumHubGoalRewards,
* throughputOnly?: boolean
* }} LevelDefinition */
const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw"; const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
const finalGameShape = "RuCw--Cw:----Ru--"; const finalGameShape = "RuCw--Cw:----Ru--";
@ -458,24 +484,48 @@ export class RegularGameMode extends GameMode {
return "Regular"; return "Regular";
} }
constructor(root) { static getSchema() {
super(root); return {
test: types.string
}
} }
constructor(root) {
super(root);
this.test = "test";
}
/**
* Should return all available upgrades
* @returns {Object<string, UpgradeTiers>}
*/
getUpgrades() { getUpgrades() {
return this.root.app.restrictionMgr.getHasExtendedUpgrades() return this.root.app.restrictionMgr.getHasExtendedUpgrades()
? fullVersionUpgrades ? fullVersionUpgrades
: demoVersionUpgrades; : demoVersionUpgrades;
} }
/**
* Should return whether free play is available or if the game stops
* after the predefined levels
* @returns {boolean}
*/
getIsFreeplayAvailable() { getIsFreeplayAvailable() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay(); return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
} }
/**
* Returns the blueprint shape key
* @returns {string}
*/
getBlueprintShapeKey() { getBlueprintShapeKey() {
return blueprintShape; return blueprintShape;
} }
/**
* Returns the goals for all levels including their reward
* @returns {Array<LevelDefinition>}
*/
getLevelDefinitions() { getLevelDefinitions() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay() return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
? fullVersionLevels ? fullVersionLevels

View File

@ -13,6 +13,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
import { SavegameInterface_V1006 } from "./schemas/1006"; import { SavegameInterface_V1006 } from "./schemas/1006";
import { SavegameInterface_V1007 } from "./schemas/1007"; import { SavegameInterface_V1007 } from "./schemas/1007";
import { SavegameInterface_V1008 } from "./schemas/1008"; import { SavegameInterface_V1008 } from "./schemas/1008";
import { SavegameInterface_V1009 } from "./schemas/1009";
const logger = createLogger("savegame"); const logger = createLogger("savegame");
@ -53,7 +54,7 @@ export class Savegame extends ReadWriteProxy {
* @returns {number} * @returns {number}
*/ */
static getCurrentVersion() { static getCurrentVersion() {
return 1008; return 1009;
} }
/** /**
@ -136,6 +137,11 @@ export class Savegame extends ReadWriteProxy {
data.version = 1008; data.version = 1008;
} }
if (data.version === 1008) {
SavegameInterface_V1009.migrate1008to1009(data);
data.version = 1009;
}
return ExplainedResult.good(); return ExplainedResult.good();
} }

View File

@ -9,6 +9,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
import { SavegameInterface_V1006 } from "./schemas/1006"; import { SavegameInterface_V1006 } from "./schemas/1006";
import { SavegameInterface_V1007 } from "./schemas/1007"; import { SavegameInterface_V1007 } from "./schemas/1007";
import { SavegameInterface_V1008 } from "./schemas/1008"; import { SavegameInterface_V1008 } from "./schemas/1008";
import { SavegameInterface_V1009 } from "./schemas/1009";
/** @type {Object.<number, typeof BaseSavegameInterface>} */ /** @type {Object.<number, typeof BaseSavegameInterface>} */
export const savegameInterfaces = { export const savegameInterfaces = {
@ -21,6 +22,7 @@ export const savegameInterfaces = {
1006: SavegameInterface_V1006, 1006: SavegameInterface_V1006,
1007: SavegameInterface_V1007, 1007: SavegameInterface_V1007,
1008: SavegameInterface_V1008, 1008: SavegameInterface_V1008,
1009: SavegameInterface_V1009,
}; };
const logger = createLogger("savegame_interface_registry"); const logger = createLogger("savegame_interface_registry");

View File

@ -131,6 +131,7 @@ export class SavegameSerializer {
errorReason = errorReason || root.time.deserialize(savegame.time); errorReason = errorReason || root.time.deserialize(savegame.time);
errorReason = errorReason || root.camera.deserialize(savegame.camera); errorReason = errorReason || root.camera.deserialize(savegame.camera);
errorReason = errorReason || root.map.deserialize(savegame.map); errorReason = errorReason || root.map.deserialize(savegame.map);
errorReason = errorReason || root.gameMode.deserialize(savegame.gameMode);
errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root); errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root);
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes); errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints); errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);

View File

@ -12,6 +12,7 @@
* time: any, * time: any,
* entityMgr: any, * entityMgr: any,
* map: any, * map: any,
* gameMode: any,
* hubGoals: any, * hubGoals: any,
* pinnedShapes: any, * pinnedShapes: any,
* waypoints: any, * waypoints: any,

View File

@ -0,0 +1,34 @@
import { createLogger } from "../../core/logging.js";
import { RegularGameMode } from "../../game/modes/regular.js";
import { SavegameInterface_V1008 } from "./1008.js";
const schema = require("./1009.json");
const logger = createLogger("savegame_interface/1009");
export class SavegameInterface_V1009 extends SavegameInterface_V1008 {
getVersion() {
return 1009;
}
getSchemaUncached() {
return schema;
}
/**
* @param {import("../savegame_typedefs.js").SavegameData} data
*/
static migrate1008to1009(data) {
logger.log("Migrating 1008 to 1009");
const dump = data.dump;
if (!dump) {
return true;
}
dump.gameMode = {
mode: {
id: RegularGameMode.getId(),
data: {}
}
};
}
}

View File

@ -0,0 +1,5 @@
{
"type": "object",
"required": [],
"additionalProperties": true
}

View File

@ -220,7 +220,7 @@ export class InGameState extends GameState {
logger.log("Creating new game core"); logger.log("Creating new game core");
this.core = new GameCore(this.app); this.core = new GameCore(this.app);
this.core.initializeRoot(this, this.savegame); this.core.initializeRoot(this, this.savegame, this.gameModeId);
if (this.savegame.hasGameDump()) { if (this.savegame.hasGameDump()) {
this.stage4bResumeGame(); this.stage4bResumeGame();
@ -354,6 +354,7 @@ export class InGameState extends GameState {
this.creationPayload = payload; this.creationPayload = payload;
this.savegame = payload.savegame; this.savegame = payload.savegame;
this.gameModeId = payload.gameModeId;
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement()); this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
this.loadingOverlay.showBasic(); this.loadingOverlay.showBasic();

View File

@ -15,6 +15,7 @@ import {
startFileChoose, startFileChoose,
waitNextFrame, waitNextFrame,
} from "../core/utils"; } from "../core/utils";
import { gGameModeRegistry } from "../core/global_registries";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { getApplicationSettingById } from "../profile/application_settings"; import { getApplicationSettingById } from "../profile/application_settings";
import { T } from "../translations"; import { T } from "../translations";
@ -359,8 +360,10 @@ export class MainMenuState extends GameState {
onPuzzlePlayButtonClicked() { onPuzzlePlayButtonClicked() {
const savegame = this.app.savegameMgr.createNewSavegame(); const savegame = this.app.savegameMgr.createNewSavegame();
const gameModeId = gGameModeRegistry.idToEntry.PuzzlePlay.getId();
this.moveToState("InGameState", { this.moveToState("InGameState", {
gameModeId,
savegame, savegame,
}); });
} }