You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tobspr_shapez.io/src/js/savegame/savegame_serializer.js

163 lines
5.9 KiB

import { ExplainedResult } from "../core/explained_result";
import { gComponentRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging";
import { MOD_SIGNALS } from "../mods/mod_signals";
import { SerializerInternal } from "./serializer_internal";
/**
* @typedef {import("../game/component").Component} Component
* @typedef {import("../game/component").StaticComponent} StaticComponent
* @typedef {import("../game/entity").Entity} Entity
* @typedef {import("../game/root").GameRoot} GameRoot
* @typedef {import("../savegame/savegame_typedefs").SerializedGame} SerializedGame
*/
const logger = createLogger("savegame_serializer");
/**
* Serializes a savegame
*/
export class SavegameSerializer {
constructor() {
this.internal = new SerializerInternal();
}
/**
* Serializes the game root into a dump
* @param {GameRoot} root
* @param {boolean=} sanityChecks Whether to check for validity
* @returns {object}
*/
generateDumpFromGameRoot(root, sanityChecks = true) {
/** @type {SerializedGame} */
const data = {
camera: root.camera.serialize(),
time: root.time.serialize(),
map: root.map.serialize(),
gameMode: root.gameMode.serialize(),
entityMgr: root.entityMgr.serialize(),
hubGoals: root.hubGoals.serialize(),
entities: this.internal.serializeEntityArray(root.entityMgr.entities),
beltPaths: root.systemMgr.systems.belt.serializePaths(),
pinnedShapes: root.hud.parts.pinnedShapes ? root.hud.parts.pinnedShapes.serialize() : null,
waypoints: root.hud.parts.waypoints ? root.hud.parts.waypoints.serialize() : null,
modExtraData: {},
};
MOD_SIGNALS.gameSerialized.dispatch(root, data);
if (G_IS_DEV) {
if (sanityChecks) {
// Sanity check
const sanity = this.verifyLogicalErrors(data);
if (!sanity.result) {
logger.error("Created invalid savegame:", sanity.reason, "savegame:", data);
return null;
}
}
}
return data;
}
/**
* Verifies if there are logical errors in the savegame
* @param {SerializedGame} savegame
* @returns {ExplainedResult}
*/
verifyLogicalErrors(savegame) {
if (!savegame.entities) {
return ExplainedResult.bad("Savegame has no entities");
}
const seenUids = new Set();
// Check for duplicate UIDS
for (let i = 0; i < savegame.entities.length; ++i) {
/** @type {Entity} */
const entity = savegame.entities[i];
const uid = entity.uid;
if (!Number.isInteger(uid)) {
return ExplainedResult.bad("Entity has invalid uid: " + uid);
}
if (seenUids.has(uid)) {
return ExplainedResult.bad("Duplicate uid " + uid);
}
seenUids.add(uid);
// Verify components
if (!entity.components) {
return ExplainedResult.bad("Entity is missing key 'components': " + JSON.stringify(entity));
}
const components = entity.components;
for (const componentId in components) {
const componentClass = gComponentRegistry.findById(componentId);
// Check component id is known
if (!componentClass) {
return ExplainedResult.bad("Unknown component id: " + componentId);
}
// Verify component data
const componentData = components[componentId];
const componentVerifyError = /** @type {StaticComponent} */ (componentClass).verify(
componentData
);
// Check component data is ok
if (componentVerifyError) {
return ExplainedResult.bad(
"Component " + componentId + " has invalid data: " + componentVerifyError
);
}
}
}
return ExplainedResult.good();
}
/**
* Tries to load the savegame from a given dump
* @param {SerializedGame} savegame
* @param {GameRoot} root
* @returns {ExplainedResult}
*/
deserialize(savegame, root) {
// Sanity
const verifyResult = this.verifyLogicalErrors(savegame);
if (!verifyResult.result) {
return ExplainedResult.bad(verifyResult.reason);
}
let errorReason = null;
errorReason = errorReason || root.entityMgr.deserialize(savegame.entityMgr);
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 || this.internal.deserializeEntityArray(root, savegame.entities);
errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths);
if (root.hud.parts.pinnedShapes) {
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
}
if (root.hud.parts.waypoints) {
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
}
// Check for errors
if (errorReason) {
return ExplainedResult.bad(errorReason);
}
// Mods
MOD_SIGNALS.gameDeserialized.dispatch(root, savegame);
return ExplainedResult.good();
}
}