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 {Savegame} savegame
*/
initializeRoot(parentState, savegame) {
initializeRoot(parentState, savegame, gameModeId) {
// Construct the root element, this is the data representation of the game
this.root = new GameRoot(this.app);
this.root.gameState = parentState;
@ -105,7 +105,7 @@ export class GameCore {
root.dynamicTickrate = new DynamicTickrate(root);
// Init game mode
root.gameMode = GameMode.create(root);
root.gameMode = GameMode.create(root, gameModeId);
// Init classes
root.camera = new Camera(root);

View File

@ -1,103 +1,54 @@
/* typehints:start */
import { enumHubGoalRewards } from "./tutorial_goals";
import { GameRoot } from "./root";
/* typehints:end */
import { gGameModeRegistry } from "../core/global_registries";
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 {
/** @returns {string} */
static getId() {
abstract;
return "unknown-mode";
return "Unknown";
}
static getSchema() {
return {
id: types.string
}
return {};
}
static create (root) {
let id;
if (!root.savegame.gameMode || !root.savegame.gameMode.id) {
id = "Regular";
} else {
id = root.savegame.gameMode.id
}
const Mode = gGameModeRegistry.findById(id);
return new Mode(root);
/**
* @param {GameRoot} root
* @param {string} [id=Regular]
*/
static create (root, id = "Regular") {
// id = "Regular"
return new (gGameModeRegistry.findById(id))(root);
}
/** @param {GameRoot} root */
/**
* @param {GameRoot} root
* @param {string} [id=Regular]
*/
constructor(root) {
super();
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() {
// @ts-ignore
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";
}
/** param {GameRoot} root */
constructor(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 { GameMode } from "../game_mode";
import { ShapeDefinition } from "../shape_definition";
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 finalGameShape = "RuCw--Cw:----Ru--";
@ -458,24 +484,48 @@ export class RegularGameMode extends GameMode {
return "Regular";
}
constructor(root) {
super(root);
static getSchema() {
return {
test: types.string
}
}
constructor(root) {
super(root);
this.test = "test";
}
/**
* Should return all available upgrades
* @returns {Object<string, UpgradeTiers>}
*/
getUpgrades() {
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
? fullVersionUpgrades
: demoVersionUpgrades;
}
/**
* Should return whether free play is available or if the game stops
* after the predefined levels
* @returns {boolean}
*/
getIsFreeplayAvailable() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
}
/**
* Returns the blueprint shape key
* @returns {string}
*/
getBlueprintShapeKey() {
return blueprintShape;
}
/**
* Returns the goals for all levels including their reward
* @returns {Array<LevelDefinition>}
*/
getLevelDefinitions() {
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
? fullVersionLevels

View File

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

View File

@ -9,6 +9,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
import { SavegameInterface_V1006 } from "./schemas/1006";
import { SavegameInterface_V1007 } from "./schemas/1007";
import { SavegameInterface_V1008 } from "./schemas/1008";
import { SavegameInterface_V1009 } from "./schemas/1009";
/** @type {Object.<number, typeof BaseSavegameInterface>} */
export const savegameInterfaces = {
@ -21,6 +22,7 @@ export const savegameInterfaces = {
1006: SavegameInterface_V1006,
1007: SavegameInterface_V1007,
1008: SavegameInterface_V1008,
1009: SavegameInterface_V1009,
};
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.camera.deserialize(savegame.camera);
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.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);

View File

@ -12,6 +12,7 @@
* time: any,
* entityMgr: any,
* map: any,
* gameMode: any,
* hubGoals: any,
* pinnedShapes: 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");
this.core = new GameCore(this.app);
this.core.initializeRoot(this, this.savegame);
this.core.initializeRoot(this, this.savegame, this.gameModeId);
if (this.savegame.hasGameDump()) {
this.stage4bResumeGame();
@ -354,6 +354,7 @@ export class InGameState extends GameState {
this.creationPayload = payload;
this.savegame = payload.savegame;
this.gameModeId = payload.gameModeId;
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
this.loadingOverlay.showBasic();

View File

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