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 {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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ export class PuzzlePlayGameMode extends GameMode {
|
||||
return "PuzzlePlay";
|
||||
}
|
||||
|
||||
/** param {GameRoot} root */
|
||||
constructor(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 { 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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -12,6 +12,7 @@
|
||||
* time: any,
|
||||
* entityMgr: any,
|
||||
* map: any,
|
||||
* gameMode: any,
|
||||
* hubGoals: any,
|
||||
* pinnedShapes: 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");
|
||||
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();
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user