parent
665360c5a5
commit
9075841768
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
Before Width: | Height: | Size: 705 KiB After Width: | Height: | Size: 705 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,273 +1,279 @@
|
||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||
import { ExplainedResult } from "../core/explained_result";
|
||||
import { SavegameSerializer } from "./savegame_serializer";
|
||||
import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { getSavegameInterface, savegameInterfaces } from "./savegame_interface_registry";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
import { SavegameInterface_V1003 } from "./schemas/1003";
|
||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
|
||||
const logger = createLogger("savegame");
|
||||
|
||||
/**
|
||||
* @typedef {import("../application").Application} Application
|
||||
* @typedef {import("../game/root").GameRoot} GameRoot
|
||||
* @typedef {import("./savegame_typedefs").SavegameData} SavegameData
|
||||
* @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
|
||||
* @typedef {import("./savegame_typedefs").SavegameStats} SavegameStats
|
||||
* @typedef {import("./savegame_typedefs").SerializedGame} SerializedGame
|
||||
*/
|
||||
|
||||
export class Savegame extends ReadWriteProxy {
|
||||
/**
|
||||
*
|
||||
* @param {Application} app
|
||||
* @param {object} param0
|
||||
* @param {string} param0.internalId
|
||||
* @param {SavegameMetadata} param0.metaDataRef Handle to the meta data
|
||||
*/
|
||||
constructor(app, { internalId, metaDataRef }) {
|
||||
super(app, "savegame-" + internalId + ".bin");
|
||||
this.internalId = internalId;
|
||||
this.metaDataRef = metaDataRef;
|
||||
|
||||
/** @type {SavegameData} */
|
||||
this.currentData = this.getDefaultData();
|
||||
|
||||
assert(
|
||||
savegameInterfaces[Savegame.getCurrentVersion()],
|
||||
"Savegame interface not defined: " + Savegame.getCurrentVersion()
|
||||
);
|
||||
}
|
||||
|
||||
//////// RW Proxy Impl //////////
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
static getCurrentVersion() {
|
||||
return 1005;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {typeof BaseSavegameInterface}
|
||||
*/
|
||||
static getReaderClass() {
|
||||
return savegameInterfaces[Savegame.getCurrentVersion()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
getCurrentVersion() {
|
||||
return /** @type {typeof Savegame} */ (this.constructor).getCurrentVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the savegames default data
|
||||
* @returns {SavegameData}
|
||||
*/
|
||||
getDefaultData() {
|
||||
return {
|
||||
version: this.getCurrentVersion(),
|
||||
dump: null,
|
||||
stats: {},
|
||||
lastUpdate: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the savegames data
|
||||
* @param {SavegameData} data
|
||||
*/
|
||||
migrate(data) {
|
||||
if (data.version < 1000) {
|
||||
return ExplainedResult.bad("Can not migrate savegame, too old");
|
||||
}
|
||||
|
||||
if (data.version === 1000) {
|
||||
SavegameInterface_V1001.migrate1000to1001(data);
|
||||
data.version = 1001;
|
||||
}
|
||||
|
||||
if (data.version === 1001) {
|
||||
SavegameInterface_V1002.migrate1001to1002(data);
|
||||
data.version = 1002;
|
||||
}
|
||||
|
||||
if (data.version === 1002) {
|
||||
SavegameInterface_V1003.migrate1002to1003(data);
|
||||
data.version = 1003;
|
||||
}
|
||||
|
||||
if (data.version === 1003) {
|
||||
SavegameInterface_V1004.migrate1003to1004(data);
|
||||
data.version = 1004;
|
||||
}
|
||||
|
||||
if (data.version === 1004) {
|
||||
SavegameInterface_V1005.migrate1004to1005(data);
|
||||
data.version = 1005;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the savegames data
|
||||
* @param {SavegameData} data
|
||||
*/
|
||||
verify(data) {
|
||||
if (!data.dump) {
|
||||
// Well, guess that works
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
if (!this.getDumpReaderForExternalData(data).validate()) {
|
||||
return ExplainedResult.bad("dump-reader-failed-validation");
|
||||
}
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
//////// Subclasses interface ////////
|
||||
|
||||
/**
|
||||
* Returns if this game can be saved on disc
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSaveable() {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Returns the statistics of the savegame
|
||||
* @returns {SavegameStats}
|
||||
*/
|
||||
getStatistics() {
|
||||
return this.currentData.stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the *real* last update of the savegame, not the one of the metadata
|
||||
* which could also be the servers one
|
||||
*/
|
||||
getRealLastUpdate() {
|
||||
return this.currentData.lastUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this game has a serialized game dump
|
||||
*/
|
||||
hasGameDump() {
|
||||
return !!this.currentData.dump && this.currentData.dump.entities.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current game dump
|
||||
* @returns {SerializedGame}
|
||||
*/
|
||||
getCurrentDump() {
|
||||
return this.currentData.dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reader to access the data
|
||||
* @returns {BaseSavegameInterface}
|
||||
*/
|
||||
getDumpReader() {
|
||||
if (!this.currentData.dump) {
|
||||
logger.warn("Getting reader on null-savegame dump");
|
||||
}
|
||||
|
||||
const cls = /** @type {typeof Savegame} */ (this.constructor).getReaderClass();
|
||||
return new cls(this.currentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reader to access external data
|
||||
* @returns {BaseSavegameInterface}
|
||||
*/
|
||||
getDumpReaderForExternalData(data) {
|
||||
assert(data.version, "External data contains no version");
|
||||
return getSavegameInterface(data);
|
||||
}
|
||||
|
||||
///////// Public Interface ///////////
|
||||
|
||||
/**
|
||||
* Updates the last update field so we can send the savegame to the server,
|
||||
* WITHOUT Saving!
|
||||
*/
|
||||
setLastUpdate(time) {
|
||||
this.currentData.lastUpdate = time;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
updateData(root) {
|
||||
// Construct a new serializer
|
||||
const serializer = new SavegameSerializer();
|
||||
|
||||
// let timer = performance.now();
|
||||
const dump = serializer.generateDumpFromGameRoot(root);
|
||||
if (!dump) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shadowData = Object.assign({}, this.currentData);
|
||||
shadowData.dump = dump;
|
||||
shadowData.lastUpdate = new Date().getTime();
|
||||
shadowData.version = this.getCurrentVersion();
|
||||
|
||||
const reader = this.getDumpReaderForExternalData(shadowData);
|
||||
|
||||
// Validate (not in prod though)
|
||||
if (!G_IS_RELEASE) {
|
||||
const validationResult = reader.validate();
|
||||
if (!validationResult) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Save data
|
||||
this.currentData = shadowData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the savegame as well as its metadata
|
||||
*/
|
||||
writeSavegameAndMetadata() {
|
||||
return this.writeAsync().then(() => this.saveMetadata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the savegames metadata
|
||||
*/
|
||||
saveMetadata() {
|
||||
this.metaDataRef.lastUpdate = new Date().getTime();
|
||||
this.metaDataRef.version = this.getCurrentVersion();
|
||||
if (!this.hasGameDump()) {
|
||||
this.metaDataRef.level = 0;
|
||||
} else {
|
||||
this.metaDataRef.level = this.currentData.dump.hubGoals.level;
|
||||
}
|
||||
|
||||
return this.app.savegameMgr.writeAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ReadWriteProxy.writeAsync
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
writeAsync() {
|
||||
if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return super.writeAsync();
|
||||
}
|
||||
}
|
||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||
import { ExplainedResult } from "../core/explained_result";
|
||||
import { SavegameSerializer } from "./savegame_serializer";
|
||||
import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { getSavegameInterface, savegameInterfaces } from "./savegame_interface_registry";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
import { SavegameInterface_V1003 } from "./schemas/1003";
|
||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||
|
||||
const logger = createLogger("savegame");
|
||||
|
||||
/**
|
||||
* @typedef {import("../application").Application} Application
|
||||
* @typedef {import("../game/root").GameRoot} GameRoot
|
||||
* @typedef {import("./savegame_typedefs").SavegameData} SavegameData
|
||||
* @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
|
||||
* @typedef {import("./savegame_typedefs").SavegameStats} SavegameStats
|
||||
* @typedef {import("./savegame_typedefs").SerializedGame} SerializedGame
|
||||
*/
|
||||
|
||||
export class Savegame extends ReadWriteProxy {
|
||||
/**
|
||||
*
|
||||
* @param {Application} app
|
||||
* @param {object} param0
|
||||
* @param {string} param0.internalId
|
||||
* @param {SavegameMetadata} param0.metaDataRef Handle to the meta data
|
||||
*/
|
||||
constructor(app, { internalId, metaDataRef }) {
|
||||
super(app, "savegame-" + internalId + ".bin");
|
||||
this.internalId = internalId;
|
||||
this.metaDataRef = metaDataRef;
|
||||
|
||||
/** @type {SavegameData} */
|
||||
this.currentData = this.getDefaultData();
|
||||
|
||||
assert(
|
||||
savegameInterfaces[Savegame.getCurrentVersion()],
|
||||
"Savegame interface not defined: " + Savegame.getCurrentVersion()
|
||||
);
|
||||
}
|
||||
|
||||
//////// RW Proxy Impl //////////
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
static getCurrentVersion() {
|
||||
return 1006;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {typeof BaseSavegameInterface}
|
||||
*/
|
||||
static getReaderClass() {
|
||||
return savegameInterfaces[Savegame.getCurrentVersion()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
getCurrentVersion() {
|
||||
return /** @type {typeof Savegame} */ (this.constructor).getCurrentVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the savegames default data
|
||||
* @returns {SavegameData}
|
||||
*/
|
||||
getDefaultData() {
|
||||
return {
|
||||
version: this.getCurrentVersion(),
|
||||
dump: null,
|
||||
stats: {},
|
||||
lastUpdate: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the savegames data
|
||||
* @param {SavegameData} data
|
||||
*/
|
||||
migrate(data) {
|
||||
if (data.version < 1000) {
|
||||
return ExplainedResult.bad("Can not migrate savegame, too old");
|
||||
}
|
||||
|
||||
if (data.version === 1000) {
|
||||
SavegameInterface_V1001.migrate1000to1001(data);
|
||||
data.version = 1001;
|
||||
}
|
||||
|
||||
if (data.version === 1001) {
|
||||
SavegameInterface_V1002.migrate1001to1002(data);
|
||||
data.version = 1002;
|
||||
}
|
||||
|
||||
if (data.version === 1002) {
|
||||
SavegameInterface_V1003.migrate1002to1003(data);
|
||||
data.version = 1003;
|
||||
}
|
||||
|
||||
if (data.version === 1003) {
|
||||
SavegameInterface_V1004.migrate1003to1004(data);
|
||||
data.version = 1004;
|
||||
}
|
||||
|
||||
if (data.version === 1004) {
|
||||
SavegameInterface_V1005.migrate1004to1005(data);
|
||||
data.version = 1005;
|
||||
}
|
||||
|
||||
if (data.version === 1005) {
|
||||
SavegameInterface_V1006.migrate1005to1006(data);
|
||||
data.version = 1006;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the savegames data
|
||||
* @param {SavegameData} data
|
||||
*/
|
||||
verify(data) {
|
||||
if (!data.dump) {
|
||||
// Well, guess that works
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
if (!this.getDumpReaderForExternalData(data).validate()) {
|
||||
return ExplainedResult.bad("dump-reader-failed-validation");
|
||||
}
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
//////// Subclasses interface ////////
|
||||
|
||||
/**
|
||||
* Returns if this game can be saved on disc
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSaveable() {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Returns the statistics of the savegame
|
||||
* @returns {SavegameStats}
|
||||
*/
|
||||
getStatistics() {
|
||||
return this.currentData.stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the *real* last update of the savegame, not the one of the metadata
|
||||
* which could also be the servers one
|
||||
*/
|
||||
getRealLastUpdate() {
|
||||
return this.currentData.lastUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this game has a serialized game dump
|
||||
*/
|
||||
hasGameDump() {
|
||||
return !!this.currentData.dump && this.currentData.dump.entities.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current game dump
|
||||
* @returns {SerializedGame}
|
||||
*/
|
||||
getCurrentDump() {
|
||||
return this.currentData.dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reader to access the data
|
||||
* @returns {BaseSavegameInterface}
|
||||
*/
|
||||
getDumpReader() {
|
||||
if (!this.currentData.dump) {
|
||||
logger.warn("Getting reader on null-savegame dump");
|
||||
}
|
||||
|
||||
const cls = /** @type {typeof Savegame} */ (this.constructor).getReaderClass();
|
||||
return new cls(this.currentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reader to access external data
|
||||
* @returns {BaseSavegameInterface}
|
||||
*/
|
||||
getDumpReaderForExternalData(data) {
|
||||
assert(data.version, "External data contains no version");
|
||||
return getSavegameInterface(data);
|
||||
}
|
||||
|
||||
///////// Public Interface ///////////
|
||||
|
||||
/**
|
||||
* Updates the last update field so we can send the savegame to the server,
|
||||
* WITHOUT Saving!
|
||||
*/
|
||||
setLastUpdate(time) {
|
||||
this.currentData.lastUpdate = time;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
updateData(root) {
|
||||
// Construct a new serializer
|
||||
const serializer = new SavegameSerializer();
|
||||
|
||||
// let timer = performance.now();
|
||||
const dump = serializer.generateDumpFromGameRoot(root);
|
||||
if (!dump) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shadowData = Object.assign({}, this.currentData);
|
||||
shadowData.dump = dump;
|
||||
shadowData.lastUpdate = new Date().getTime();
|
||||
shadowData.version = this.getCurrentVersion();
|
||||
|
||||
const reader = this.getDumpReaderForExternalData(shadowData);
|
||||
|
||||
// Validate (not in prod though)
|
||||
if (!G_IS_RELEASE) {
|
||||
const validationResult = reader.validate();
|
||||
if (!validationResult) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Save data
|
||||
this.currentData = shadowData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the savegame as well as its metadata
|
||||
*/
|
||||
writeSavegameAndMetadata() {
|
||||
return this.writeAsync().then(() => this.saveMetadata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the savegames metadata
|
||||
*/
|
||||
saveMetadata() {
|
||||
this.metaDataRef.lastUpdate = new Date().getTime();
|
||||
this.metaDataRef.version = this.getCurrentVersion();
|
||||
if (!this.hasGameDump()) {
|
||||
this.metaDataRef.level = 0;
|
||||
} else {
|
||||
this.metaDataRef.level = this.currentData.dump.hubGoals.level;
|
||||
}
|
||||
|
||||
return this.app.savegameMgr.writeAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ReadWriteProxy.writeAsync
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
writeAsync() {
|
||||
if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return super.writeAsync();
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,47 @@
|
||||
import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { SavegameInterface_V1000 } from "./schemas/1000";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
import { SavegameInterface_V1003 } from "./schemas/1003";
|
||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
|
||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||
export const savegameInterfaces = {
|
||||
1000: SavegameInterface_V1000,
|
||||
1001: SavegameInterface_V1001,
|
||||
1002: SavegameInterface_V1002,
|
||||
1003: SavegameInterface_V1003,
|
||||
1004: SavegameInterface_V1004,
|
||||
1005: SavegameInterface_V1005,
|
||||
};
|
||||
|
||||
const logger = createLogger("savegame_interface_registry");
|
||||
|
||||
/**
|
||||
* Returns if the given savegame has any supported interface
|
||||
* @param {any} savegame
|
||||
* @returns {BaseSavegameInterface|null}
|
||||
*/
|
||||
export function getSavegameInterface(savegame) {
|
||||
if (!savegame || !savegame.version) {
|
||||
logger.warn("Savegame does not contain a valid version (undefined)");
|
||||
return null;
|
||||
}
|
||||
const version = savegame.version;
|
||||
if (!Number.isInteger(version)) {
|
||||
logger.warn("Savegame does not contain a valid version (non-integer):", version);
|
||||
return null;
|
||||
}
|
||||
|
||||
const interfaceClass = savegameInterfaces[version];
|
||||
if (!interfaceClass) {
|
||||
logger.warn("Version", version, "has no implemented interface!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new interfaceClass(savegame);
|
||||
}
|
||||
import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { SavegameInterface_V1000 } from "./schemas/1000";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
import { SavegameInterface_V1003 } from "./schemas/1003";
|
||||
import { SavegameInterface_V1004 } from "./schemas/1004";
|
||||
import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||
|
||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||
export const savegameInterfaces = {
|
||||
1000: SavegameInterface_V1000,
|
||||
1001: SavegameInterface_V1001,
|
||||
1002: SavegameInterface_V1002,
|
||||
1003: SavegameInterface_V1003,
|
||||
1004: SavegameInterface_V1004,
|
||||
1005: SavegameInterface_V1005,
|
||||
1006: SavegameInterface_V1006,
|
||||
};
|
||||
|
||||
const logger = createLogger("savegame_interface_registry");
|
||||
|
||||
/**
|
||||
* Returns if the given savegame has any supported interface
|
||||
* @param {any} savegame
|
||||
* @returns {BaseSavegameInterface|null}
|
||||
*/
|
||||
export function getSavegameInterface(savegame) {
|
||||
if (!savegame || !savegame.version) {
|
||||
logger.warn("Savegame does not contain a valid version (undefined)");
|
||||
return null;
|
||||
}
|
||||
const version = savegame.version;
|
||||
if (!Number.isInteger(version)) {
|
||||
logger.warn("Savegame does not contain a valid version (non-integer):", version);
|
||||
return null;
|
||||
}
|
||||
|
||||
const interfaceClass = savegameInterfaces[version];
|
||||
if (!interfaceClass) {
|
||||
logger.warn("Version", version, "has no implemented interface!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new interfaceClass(savegame);
|
||||
}
|
||||
|
@ -0,0 +1,265 @@
|
||||
import { gMetaBuildingRegistry } from "../../core/global_registries.js";
|
||||
import { createLogger } from "../../core/logging.js";
|
||||
import { MetaBeltBuilding } from "../../game/buildings/belt.js";
|
||||
import { enumCutterVariants, MetaCutterBuilding } from "../../game/buildings/cutter.js";
|
||||
import { MetaHubBuilding } from "../../game/buildings/hub.js";
|
||||
import { enumMinerVariants, MetaMinerBuilding } from "../../game/buildings/miner.js";
|
||||
import { MetaMixerBuilding } from "../../game/buildings/mixer.js";
|
||||
import { enumPainterVariants, MetaPainterBuilding } from "../../game/buildings/painter.js";
|
||||
import { enumRotaterVariants, MetaRotaterBuilding } from "../../game/buildings/rotater.js";
|
||||
import { enumSplitterVariants, MetaSplitterBuilding } from "../../game/buildings/splitter.js";
|
||||
import { MetaStackerBuilding } from "../../game/buildings/stacker.js";
|
||||
import { enumTrashVariants, MetaTrashBuilding } from "../../game/buildings/trash.js";
|
||||
import {
|
||||
enumUndergroundBeltVariants,
|
||||
MetaUndergroundBeltBuilding,
|
||||
} from "../../game/buildings/underground_belt.js";
|
||||
import { getCodeFromBuildingData } from "../../game/building_codes.js";
|
||||
import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js";
|
||||
import { Entity } from "../../game/entity.js";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js";
|
||||
import { SavegameInterface_V1005 } from "./1005.js";
|
||||
|
||||
const schema = require("./1006.json");
|
||||
const logger = createLogger("savegame_interface/1006");
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {typeof MetaBuilding} metaBuilding
|
||||
* @param {string=} variant
|
||||
* @param {number=} rotationVariant
|
||||
*/
|
||||
function findCode(metaBuilding, variant = defaultBuildingVariant, rotationVariant = 0) {
|
||||
return getCodeFromBuildingData(gMetaBuildingRegistry.findByClass(metaBuilding), variant, rotationVariant);
|
||||
}
|
||||
|
||||
export class SavegameInterface_V1006 extends SavegameInterface_V1005 {
|
||||
getVersion() {
|
||||
return 1006;
|
||||
}
|
||||
|
||||
getSchemaUncached() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
static computeSpriteMapping() {
|
||||
return {
|
||||
// Belt
|
||||
"sprites/blueprints/belt_top.png": findCode(MetaBeltBuilding, defaultBuildingVariant, 0),
|
||||
"sprites/blueprints/belt_left.png": findCode(MetaBeltBuilding, defaultBuildingVariant, 1),
|
||||
"sprites/blueprints/belt_right.png": findCode(MetaBeltBuilding, defaultBuildingVariant, 2),
|
||||
|
||||
// Splitter
|
||||
"sprites/blueprints/splitter.png": findCode(MetaSplitterBuilding),
|
||||
"sprites/blueprints/splitter-compact.png": findCode(
|
||||
MetaSplitterBuilding,
|
||||
enumSplitterVariants.compact
|
||||
),
|
||||
"sprites/blueprints/splitter-compact-inverse.png": findCode(
|
||||
MetaSplitterBuilding,
|
||||
enumSplitterVariants.compactInverse
|
||||
),
|
||||
|
||||
// Underground belt
|
||||
"sprites/blueprints/underground_belt_entry.png": findCode(
|
||||
MetaUndergroundBeltBuilding,
|
||||
defaultBuildingVariant,
|
||||
0
|
||||
),
|
||||
"sprites/blueprints/underground_belt_exit.png": findCode(
|
||||
MetaUndergroundBeltBuilding,
|
||||
defaultBuildingVariant,
|
||||
1
|
||||
),
|
||||
|
||||
"sprites/blueprints/underground_belt_entry-tier2.png": findCode(
|
||||
MetaUndergroundBeltBuilding,
|
||||
enumUndergroundBeltVariants.tier2,
|
||||
0
|
||||
),
|
||||
"sprites/blueprints/underground_belt_exit-tier2.png": findCode(
|
||||
MetaUndergroundBeltBuilding,
|
||||
enumUndergroundBeltVariants.tier2,
|
||||
1
|
||||
),
|
||||
|
||||
// Miner
|
||||
"sprites/blueprints/miner.png": findCode(MetaMinerBuilding),
|
||||
"sprites/blueprints/miner-chainable.png": findCode(
|
||||
MetaMinerBuilding,
|
||||
enumMinerVariants.chainable,
|
||||
0
|
||||
),
|
||||
|
||||
// Cutter
|
||||
"sprites/blueprints/cutter.png": findCode(MetaCutterBuilding),
|
||||
"sprites/blueprints/cutter-quad.png": findCode(MetaCutterBuilding, enumCutterVariants.quad),
|
||||
|
||||
// Rotater
|
||||
"sprites/blueprints/rotater.png": findCode(MetaRotaterBuilding),
|
||||
"sprites/blueprints/rotater-ccw.png": findCode(MetaRotaterBuilding, enumRotaterVariants.ccw),
|
||||
|
||||
// Stacker
|
||||
"sprites/blueprints/stacker.png": findCode(MetaStackerBuilding),
|
||||
|
||||
// Mixer
|
||||
"sprites/blueprints/mixer.png": findCode(MetaMixerBuilding),
|
||||
|
||||
// Painter
|
||||
"sprites/blueprints/painter.png": findCode(MetaPainterBuilding),
|
||||
"sprites/blueprints/painter-mirrored.png": findCode(
|
||||
MetaPainterBuilding,
|
||||
enumPainterVariants.mirrored
|
||||
),
|
||||
"sprites/blueprints/painter-double.png": findCode(
|
||||
MetaPainterBuilding,
|
||||
enumPainterVariants.double
|
||||
),
|
||||
"sprites/blueprints/painter-quad.png": findCode(MetaPainterBuilding, enumPainterVariants.quad),
|
||||
|
||||
// Trash / Storage
|
||||
"sprites/blueprints/trash.png": findCode(MetaTrashBuilding),
|
||||
"sprites/blueprints/trash-storage.png": findCode(MetaTrashBuilding, enumTrashVariants.storage),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../savegame_typedefs.js").SavegameData} data
|
||||
*/
|
||||
static migrate1005to1006(data) {
|
||||
logger.log("Migrating 1005 to 1006");
|
||||
const dump = data.dump;
|
||||
if (!dump) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update entities
|
||||
const entities = dump.entities;
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
const entity = entities[i];
|
||||
const components = entity.components;
|
||||
this.migrateStaticComp1005to1006(entity);
|
||||
|
||||
// HUB
|
||||
if (components.Hub) {
|
||||
// @ts-ignore
|
||||
components.Hub = {};
|
||||
}
|
||||
|
||||
// Item Processor
|
||||
if (components.ItemProcessor) {
|
||||
// @ts-ignore
|
||||
components.ItemProcessor = {
|
||||
nextOutputSlot: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// OLD: Unremovable component
|
||||
// @ts-ignore
|
||||
if (components.Unremovable) {
|
||||
// @ts-ignore
|
||||
delete components.Unremovable;
|
||||
}
|
||||
|
||||
// OLD: ReplaceableMapEntity
|
||||
// @ts-ignore
|
||||
if (components.ReplaceableMapEntity) {
|
||||
// @ts-ignore
|
||||
delete components.ReplaceableMapEntity;
|
||||
}
|
||||
|
||||
// ItemAcceptor
|
||||
if (components.ItemAcceptor) {
|
||||
// @ts-ignore
|
||||
components.ItemAcceptor = {};
|
||||
}
|
||||
|
||||
// Belt
|
||||
if (components.Belt) {
|
||||
// @ts-ignore
|
||||
components.Belt = {};
|
||||
}
|
||||
|
||||
// Item Ejector
|
||||
if (components.ItemEjector) {
|
||||
// @ts-ignore
|
||||
components.ItemEjector = {
|
||||
slots: [],
|
||||
};
|
||||
}
|
||||
|
||||
// UndergroundBelt
|
||||
if (components.UndergroundBelt) {
|
||||
// @ts-ignore
|
||||
components.UndergroundBelt = {
|
||||
pendingItems: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Miner
|
||||
if (components.Miner) {
|
||||
// @ts-ignore
|
||||
delete components.Miner.chainable;
|
||||
|
||||
components.Miner.lastMiningTime = 0;
|
||||
components.Miner.itemChainBuffer = [];
|
||||
}
|
||||
|
||||
// Storage
|
||||
if (components.Storage) {
|
||||
// @ts-ignore
|
||||
components.Storage = {
|
||||
storedCount: 0,
|
||||
storedItem: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
static migrateStaticComp1005to1006(entity) {
|
||||
const spriteMapping = this.computeSpriteMapping();
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
/** @type {StaticMapEntityComponent} */
|
||||
const newStaticComp = {};
|
||||
newStaticComp.origin = staticComp.origin;
|
||||
newStaticComp.originalRotation = staticComp.originalRotation;
|
||||
newStaticComp.rotation = staticComp.rotation;
|
||||
|
||||
// @ts-ignore
|
||||
newStaticComp.code = spriteMapping[staticComp.blueprintSpriteKey];
|
||||
|
||||
// Hub special case
|
||||
if (entity.components.Hub) {
|
||||
newStaticComp.code = findCode(MetaHubBuilding);
|
||||
}
|
||||
|
||||
// Belt special case
|
||||
if (entity.components.Belt) {
|
||||
const actualCode = {
|
||||
top: findCode(MetaBeltBuilding, defaultBuildingVariant, 0),
|
||||
left: findCode(MetaBeltBuilding, defaultBuildingVariant, 1),
|
||||
right: findCode(MetaBeltBuilding, defaultBuildingVariant, 2),
|
||||
}[entity.components.Belt.direction];
|
||||
if (actualCode !== newStaticComp.code) {
|
||||
if (G_IS_DEV) {
|
||||
console.warn("Belt mismatch");
|
||||
}
|
||||
newStaticComp.code = actualCode;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newStaticComp.code) {
|
||||
throw new Error(
|
||||
// @ts-ignore
|
||||
"1006 Migration: Could not reconstruct code for " + staticComp.blueprintSpriteKey
|
||||
);
|
||||
}
|
||||
|
||||
entity.components.StaticMapEntity = newStaticComp;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"additionalProperties": true
|
||||
}
|
Loading…
Reference in new issue