1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Puzzle mode

This commit is contained in:
tobspr 2021-04-30 21:09:23 +02:00
parent 72530005cb
commit 31811f20b7
13 changed files with 195 additions and 20 deletions

View File

@ -1,4 +1,4 @@
#ingame_HUD_PuzzleEditorMetadata { #ingame_HUD_PuzzlePlayMetadata {
position: absolute; position: absolute;
@include S(top, 70px); @include S(top, 70px);
@ -17,3 +17,22 @@
} }
} }
} }
#ingame_HUD_PuzzlePlayTitle {
position: absolute;
@include S(top, 18px);
left: 50%;
transform: translateX(-50%);
text-transform: uppercase;
@include Heading;
text-align: center;
display: flex;
flex-direction: column;
> .name {
@include PlainText;
opacity: 0.5;
}
}

View File

@ -84,7 +84,8 @@ ingame_HUD_PuzzleEditorReview,
ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle, ingame_HUD_PuzzleEditorTitle,
ingame_HUD_PuzzleEditorSettings, ingame_HUD_PuzzleEditorSettings,
ingame_HUD_PuzzlePlayMetadata ingame_HUD_PuzzlePlayMetadata,
ingame_HUD_PuzzlePlayTitle,
ingame_HUD_Notifications, ingame_HUD_Notifications,
ingame_HUD_DebugInfo, ingame_HUD_DebugInfo,
ingame_HUD_EntityDebugger, ingame_HUD_EntityDebugger,

View File

@ -17,6 +17,15 @@ export class MetaConstantProducerBuilding extends MetaBuilding {
return "#bfd630"; return "#bfd630";
} }
/**
*
* @param {import("../../savegame/savegame_serializer").GameRoot} root
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
}
/** /**
* Creates the entity at the given location * Creates the entity at the given location
* @param {Entity} entity * @param {Entity} entity

View File

@ -19,6 +19,15 @@ export class MetaGoalAcceptorBuilding extends MetaBuilding {
return "#ce418a"; return "#ce418a";
} }
/**
*
* @param {import("../../savegame/savegame_serializer").GameRoot} root
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
}
/** /**
* Creates the entity at the given location * Creates the entity at the given location
* @param {Entity} entity * @param {Entity} entity

View File

@ -1,14 +1,21 @@
import { makeDiv } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzlePlayMetadata extends BaseHUDPart { export class HUDPuzzlePlayMetadata extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzlePlayMetadata"); this.titleElement = makeDiv(parent, "ingame_HUD_PuzzlePlayTitle");
this.titleElement.innerText = "PUZZLE";
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle"); this.puzzleNameElement = makeDiv(this.titleElement, null, ["name"]);
// this.titleElement.innerText = T.ingame.PuzzlePlayMetadata.title; this.puzzleNameElement.innerText = "tobspr's first puzzle";
this.titleElement.innerText = "tobspr's first puzzle";
this.element = makeDiv(parent, "ingame_HUD_PuzzlePlayMetadata");
this.element.innerHTML = `
<div class="author">Author: tobspr</div>
<div class="plays">Plays: 12.000</div>
<div class="likes">Likes: 512</div>
`;
} }
initialize() {} initialize() {}

View File

@ -167,7 +167,7 @@ export class GameLogic {
*/ */
canDeleteBuilding(building) { canDeleteBuilding(building) {
const staticComp = building.components.StaticMapEntity; const staticComp = building.components.StaticMapEntity;
return staticComp.getMetaBuilding().getIsRemovable(); return staticComp.getMetaBuilding().getIsRemovable(this.root);
} }
/** /**

View File

@ -108,9 +108,10 @@ export class MetaBuilding {
/** /**
* Returns whether this building is removable * Returns whether this building is removable
* @param {GameRoot} root
* @returns {boolean} * @returns {boolean}
*/ */
getIsRemovable() { getIsRemovable(root) {
return true; return true;
} }

View File

@ -22,7 +22,6 @@ import { MetaTransistorBuilding } from "../buildings/transistor";
import { HUDPuzzleEditorControls } from "../hud/parts/puzzle_editor_controls"; import { HUDPuzzleEditorControls } from "../hud/parts/puzzle_editor_controls";
import { HUDPuzzleEditorReview } from "../hud/parts/puzzle_editor_review"; import { HUDPuzzleEditorReview } from "../hud/parts/puzzle_editor_review";
import { HUDPuzzleEditorSettings } from "../hud/parts/puzzle_editor_settings"; import { HUDPuzzleEditorSettings } from "../hud/parts/puzzle_editor_settings";
import { HUDPuzzleBackToMenu } from "../hud/parts/puzzle_back_to_menu";
export class PuzzleEditGameMode extends PuzzleGameMode { export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() { static getId() {

View File

@ -22,6 +22,9 @@ import { MetaTransistorBuilding } from "../buildings/transistor";
import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit"; import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit";
import { PuzzleSerializer } from "../../savegame/puzzle_serializer";
import { T } from "../../translations";
import { HUDPuzzlePlayMetadata } from "../hud/parts/puzzle_play_metadata";
export class PuzzlePlayGameMode extends PuzzleGameMode { export class PuzzlePlayGameMode extends PuzzleGameMode {
static getId() { static getId() {
@ -58,9 +61,31 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
MetaTransistorBuilding, MetaTransistorBuilding,
]; ];
this.additionalHudParts.constantSignalEdit = HUDConstantSignalEdit; this.additionalHudParts.puzzlePlayMetadata = HUDPuzzlePlayMetadata;
root.signals.postLoadHook.add(this.loadPuzzle, this);
console.log("playing puzzle:", puzzle);
this.puzzle = puzzle; this.puzzle = puzzle;
} }
loadPuzzle() {
let errorText;
try {
errorText = new PuzzleSerializer().deserializePuzzle(this.root, this.puzzle.game);
this.zoneWidth = this.puzzle.game.bounds.w;
this.zoneHeight = this.puzzle.game.bounds.h;
} catch (ex) {
errorText = ex.message || ex;
}
if (errorText) {
const signals = this.root.hud.parts.dialogs.showWarning(
T.dialogs.puzzleLoadError.title,
T.dialogs.puzzleLoadError.desc + " " + errorText
);
signals.ok.add(() => this.root.gameState.moveToState("PuzzleMenuState"));
}
}
} }

View File

@ -102,7 +102,7 @@ export class GoalAcceptorSystem extends GameSystemWithFilter {
parameters.context.lineWidth = 1; parameters.context.lineWidth = 1;
parameters.context.strokeStyle = "#64666e"; parameters.context.strokeStyle = "#64666e";
parameters.context.fillStyle = isValid ? "#8de255" : "#e2555f"; parameters.context.fillStyle = isValid ? "#8de255" : "#ff666a";
parameters.context.beginCircle(10, 11.8, 3); parameters.context.beginCircle(10, 11.8, 3);
parameters.context.fill(); parameters.context.fill();
parameters.context.stroke(); parameters.context.stroke();

View File

@ -5,6 +5,19 @@ import { PuzzleGameMode } from "../game/modes/puzzle";
import { enumConstantSignalType } from "../game/components/constant_signal"; import { enumConstantSignalType } from "../game/components/constant_signal";
import { StaticMapEntityComponent } from "../game/components/static_map_entity"; import { StaticMapEntityComponent } from "../game/components/static_map_entity";
import { ShapeItem } from "../game/items/shape_item"; import { ShapeItem } from "../game/items/shape_item";
import { Vector } from "../core/vector";
import { MetaConstantProducerBuilding } from "../game/buildings/constant_producer";
import { defaultBuildingVariant } 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";
const logger = createLogger("puzzle-serializer");
export class PuzzleSerializer { export class PuzzleSerializer {
/** /**
@ -26,10 +39,10 @@ export class PuzzleSerializer {
if (signalComp) { if (signalComp) {
assert(signalComp.type === enumConstantSignalType.wireless, "not a wireless signal"); assert(signalComp.type === enumConstantSignalType.wireless, "not a wireless signal");
assert(signalComp.signal.getItemType() === "shape", "not a shape signal"); assert(["shape", "color"].includes(signalComp.signal.getItemType()), "not a shape signal");
buildings.push({ buildings.push({
type: "emitter", type: "emitter",
item: /** @type {ShapeItem} */ (signalComp.signal).definition.getHash(), item: signalComp.signal.getAsCopyableKey(),
pos: { pos: {
x: staticComp.origin.x, x: staticComp.origin.x,
y: staticComp.origin.y, y: staticComp.origin.y,
@ -45,7 +58,7 @@ export class PuzzleSerializer {
assert(goalComp.item.getItemType() === "shape", "goal is not an item"); assert(goalComp.item.getItemType() === "shape", "goal is not an item");
buildings.push({ buildings.push({
type: "goal", type: "goal",
item: /** @type {ShapeItem} */ (goalComp.item).definition.getHash(), item: goalComp.item.getAsCopyableKey(),
pos: { pos: {
x: staticComp.origin.x, x: staticComp.origin.x,
y: staticComp.origin.y, y: staticComp.origin.y,
@ -67,4 +80,91 @@ export class PuzzleSerializer {
}, },
}; };
} }
/**
* 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;
}
default: {
// @ts-ignore
return "invalid-building-type: " + building.type;
}
}
}
}
} }

View File

@ -204,20 +204,20 @@ export class PuzzleMenuState extends TextualGameState {
{ {
type: "emitter", type: "emitter",
item: "CuCuCuCu", item: "CuCuCuCu",
pos: { x: 0, y: 0, r: 180 }, pos: { x: -2, y: 2, r: 0 },
}, },
{ {
type: "emitter", type: "emitter",
item: "red", item: "red",
pos: { x: 2, y: 0, r: 180 }, pos: { x: 1, y: 2, r: 0 },
}, },
{ {
type: "goal", type: "goal",
item: "CrCrCrCr", item: "CrCrCrCr",
pos: { x: 0, y: 4, r: 0 }, pos: { x: 0, y: -3, r: 0 },
}, },
], ],
bounds: { w: 10, h: 10 }, bounds: { w: 4, h: 6 },
}; };
const savegame = this.app.savegameMgr.createNewSavegame(); const savegame = this.app.savegameMgr.createNewSavegame();

View File

@ -302,6 +302,11 @@ dialogs:
title: Resize not possible title: Resize not possible
desc: You can't make the zone any smaller, because then some buildings would be outside the zone. desc: You can't make the zone any smaller, because then some buildings would be outside the zone.
puzzleLoadError:
title: Bad Puzzle
desc: >-
The puzzle failed to load:
ingame: ingame:
# This is shown in the top left corner and displays useful keybindings in # This is shown in the top left corner and displays useful keybindings in
# every situation # every situation