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.
210 lines
7.7 KiB
210 lines
7.7 KiB
/* typehints:start */
|
|
import { GameRoot } from "../game/root";
|
|
import { PuzzleGameMode } from "../game/modes/puzzle";
|
|
/* typehints:end */
|
|
import { StaticMapEntityComponent } from "../game/components/static_map_entity";
|
|
import { ShapeItem } from "../game/items/shape_item";
|
|
import { Vector } from "../core/vector";
|
|
import { MetaConstantProducerBuilding } from "../game/buildings/constant_producer";
|
|
import { defaultBuildingVariant, MetaBuilding } from "../game/meta_building";
|
|
import { gMetaBuildingRegistry } from "../core/global_registries";
|
|
import { MetaGoalAcceptorBuilding } from "../game/buildings/goal_acceptor";
|
|
import { createLogger } from "../core/logging";
|
|
import { BaseItem } from "../game/base_item";
|
|
import trim from "trim";
|
|
import { enumColors } from "../game/colors";
|
|
import { COLOR_ITEM_SINGLETONS } from "../game/items/color_item";
|
|
import { ShapeDefinition } from "../game/shape_definition";
|
|
import { MetaBlockBuilding } from "../game/buildings/block";
|
|
|
|
const logger = createLogger("puzzle-serializer");
|
|
|
|
export class PuzzleSerializer {
|
|
/**
|
|
* Serializes the game root into a dump
|
|
* @param {GameRoot} root
|
|
* @returns {import("./savegame_typedefs").PuzzleGameData}
|
|
*/
|
|
generateDumpFromGameRoot(root) {
|
|
console.log("serializing", root);
|
|
|
|
/**
|
|
* @type {import("./savegame_typedefs").PuzzleGameData["buildings"]}
|
|
*/
|
|
let buildings = [];
|
|
for (const entity of root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
|
|
const staticComp = entity.components.StaticMapEntity;
|
|
const signalComp = entity.components.ConstantSignal;
|
|
|
|
if (signalComp) {
|
|
assert(["shape", "color"].includes(signalComp.signal.getItemType()), "not a shape signal");
|
|
buildings.push({
|
|
type: "emitter",
|
|
item: signalComp.signal.getAsCopyableKey(),
|
|
pos: {
|
|
x: staticComp.origin.x,
|
|
y: staticComp.origin.y,
|
|
r: staticComp.rotation,
|
|
},
|
|
});
|
|
continue;
|
|
}
|
|
|
|
const goalComp = entity.components.GoalAcceptor;
|
|
if (goalComp) {
|
|
assert(goalComp.item, "goals is missing item");
|
|
assert(goalComp.item.getItemType() === "shape", "goal is not an item");
|
|
buildings.push({
|
|
type: "goal",
|
|
item: goalComp.item.getAsCopyableKey(),
|
|
pos: {
|
|
x: staticComp.origin.x,
|
|
y: staticComp.origin.y,
|
|
r: staticComp.rotation,
|
|
},
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (staticComp.getMetaBuilding().id === gMetaBuildingRegistry.findByClass(MetaBlockBuilding).id) {
|
|
buildings.push({
|
|
type: "block",
|
|
pos: {
|
|
x: staticComp.origin.x,
|
|
y: staticComp.origin.y,
|
|
r: staticComp.rotation,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
const mode = /** @type {PuzzleGameMode} */ (root.gameMode);
|
|
|
|
const handles = root.hud.parts.buildingsToolbar.buildingHandles;
|
|
const ids = gMetaBuildingRegistry.getAllIds();
|
|
|
|
/** @type {Array<string>} */
|
|
let excludedBuildings = [];
|
|
for (let i = 0; i < ids.length; ++i) {
|
|
const handle = handles[ids[i]];
|
|
if (handle && handle.puzzleLocked) {
|
|
// @ts-ignore
|
|
excludedBuildings.push(handle.metaBuilding.getId());
|
|
}
|
|
}
|
|
|
|
return {
|
|
version: 1,
|
|
buildings,
|
|
bounds: {
|
|
w: mode.zoneWidth,
|
|
h: mode.zoneHeight,
|
|
},
|
|
//read from the toolbar when making a puzzle
|
|
excludedBuildings,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Tries to parse a signal code
|
|
* @param {GameRoot} root
|
|
* @param {string} code
|
|
* @returns {BaseItem}
|
|
*/
|
|
parseItemCode(root, code) {
|
|
if (!root || !root.shapeDefinitionMgr) {
|
|
// Stale reference
|
|
return null;
|
|
}
|
|
|
|
code = trim(code);
|
|
const codeLower = code.toLowerCase();
|
|
|
|
if (enumColors[codeLower]) {
|
|
return COLOR_ITEM_SINGLETONS[codeLower];
|
|
}
|
|
|
|
if (ShapeDefinition.isValidShortKey(code)) {
|
|
return root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/**
|
|
* @param {GameRoot} root
|
|
* @param {import("./savegame_typedefs").PuzzleGameData} puzzle
|
|
*/
|
|
deserializePuzzle(root, puzzle) {
|
|
if (puzzle.version !== 1) {
|
|
return "invalid-version";
|
|
}
|
|
|
|
for (const building of puzzle.buildings) {
|
|
switch (building.type) {
|
|
case "emitter": {
|
|
const item = this.parseItemCode(root, building.item);
|
|
if (!item) {
|
|
return "bad-item:" + building.item;
|
|
}
|
|
|
|
const entity = root.logic.tryPlaceBuilding({
|
|
origin: new Vector(building.pos.x, building.pos.y),
|
|
building: gMetaBuildingRegistry.findByClass(MetaConstantProducerBuilding),
|
|
originalRotation: building.pos.r,
|
|
rotation: building.pos.r,
|
|
rotationVariant: 0,
|
|
variant: defaultBuildingVariant,
|
|
});
|
|
if (!entity) {
|
|
logger.warn("Failed to place emitter:", building);
|
|
return "failed-to-place-emitter";
|
|
}
|
|
|
|
entity.components.ConstantSignal.signal = item;
|
|
break;
|
|
}
|
|
case "goal": {
|
|
const item = this.parseItemCode(root, building.item);
|
|
if (!item) {
|
|
return "bad-item:" + building.item;
|
|
}
|
|
const entity = root.logic.tryPlaceBuilding({
|
|
origin: new Vector(building.pos.x, building.pos.y),
|
|
building: gMetaBuildingRegistry.findByClass(MetaGoalAcceptorBuilding),
|
|
originalRotation: building.pos.r,
|
|
rotation: building.pos.r,
|
|
rotationVariant: 0,
|
|
variant: defaultBuildingVariant,
|
|
});
|
|
if (!entity) {
|
|
logger.warn("Failed to place goal:", building);
|
|
return "failed-to-place-goal";
|
|
}
|
|
|
|
entity.components.GoalAcceptor.item = item;
|
|
break;
|
|
}
|
|
case "block": {
|
|
const entity = root.logic.tryPlaceBuilding({
|
|
origin: new Vector(building.pos.x, building.pos.y),
|
|
building: gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
|
|
originalRotation: building.pos.r,
|
|
rotation: building.pos.r,
|
|
rotationVariant: 0,
|
|
variant: defaultBuildingVariant,
|
|
});
|
|
if (!entity) {
|
|
logger.warn("Failed to place block:", building);
|
|
return "failed-to-place-block";
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
// @ts-ignore
|
|
return "invalid-building-type: " + building.type;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|