diff --git a/src/js/game/core.js b/src/js/game/core.js index b8828f45..17013845 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -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); diff --git a/src/js/game/game_mode.js b/src/js/game/game_mode.js index 920cc30a..32d7298d 100644 --- a/src/js/game/game_mode.js +++ b/src/js/game/game_mode.js @@ -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 - * improvement?: number, - * excludePrevious?: boolean - * }} TierRequirement */ - -/** @typedef {Array} 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} - */ - 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} - */ - 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; - } } diff --git a/src/js/game/modes/puzzle_play.js b/src/js/game/modes/puzzle_play.js index 49a9b681..30c817c7 100644 --- a/src/js/game/modes/puzzle_play.js +++ b/src/js/game/modes/puzzle_play.js @@ -5,6 +5,7 @@ export class PuzzlePlayGameMode extends GameMode { return "PuzzlePlay"; } + /** param {GameRoot} root */ constructor(root) { super(root); } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 851b5653..ed1da27e 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -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 + * improvement?: number, + * excludePrevious?: boolean + * }} TierRequirement */ + +/** @typedef {Array} 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} + */ 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} + */ getLevelDefinitions() { return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay() ? fullVersionLevels diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js index e56ae1dc..999b90ec 100644 --- a/src/js/savegame/savegame.js +++ b/src/js/savegame/savegame.js @@ -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(); } diff --git a/src/js/savegame/savegame_interface_registry.js b/src/js/savegame/savegame_interface_registry.js index 395040b3..b4dc4233 100644 --- a/src/js/savegame/savegame_interface_registry.js +++ b/src/js/savegame/savegame_interface_registry.js @@ -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.} */ 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"); diff --git a/src/js/savegame/savegame_serializer.js b/src/js/savegame/savegame_serializer.js index f52c2c70..1f2987af 100644 --- a/src/js/savegame/savegame_serializer.js +++ b/src/js/savegame/savegame_serializer.js @@ -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); diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js index fb872113..6a256056 100644 --- a/src/js/savegame/savegame_typedefs.js +++ b/src/js/savegame/savegame_typedefs.js @@ -12,6 +12,7 @@ * time: any, * entityMgr: any, * map: any, + * gameMode: any, * hubGoals: any, * pinnedShapes: any, * waypoints: any, diff --git a/src/js/savegame/schemas/1009.js b/src/js/savegame/schemas/1009.js new file mode 100644 index 00000000..838f1bd9 --- /dev/null +++ b/src/js/savegame/schemas/1009.js @@ -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: {} + } + }; + } +} diff --git a/src/js/savegame/schemas/1009.json b/src/js/savegame/schemas/1009.json new file mode 100644 index 00000000..6682f615 --- /dev/null +++ b/src/js/savegame/schemas/1009.json @@ -0,0 +1,5 @@ +{ + "type": "object", + "required": [], + "additionalProperties": true +} diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js index 316c536c..9ba3aa3d 100644 --- a/src/js/states/ingame.js +++ b/src/js/states/ingame.js @@ -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(); diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index b2748a00..90f57e7d 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -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, }); }