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:
parent
6f0a32f054
commit
f478228557
@ -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);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ export class PuzzlePlayGameMode extends GameMode {
|
|||||||
return "PuzzlePlay";
|
return "PuzzlePlay";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** param {GameRoot} root */
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root);
|
super(root);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
34
src/js/savegame/schemas/1009.js
Normal file
34
src/js/savegame/schemas/1009.js
Normal 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: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
5
src/js/savegame/schemas/1009.json
Normal file
5
src/js/savegame/schemas/1009.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
@ -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();
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user