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;
@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_PuzzleEditorTitle,
ingame_HUD_PuzzleEditorSettings,
ingame_HUD_PuzzlePlayMetadata
ingame_HUD_PuzzlePlayMetadata,
ingame_HUD_PuzzlePlayTitle,
ingame_HUD_Notifications,
ingame_HUD_DebugInfo,
ingame_HUD_EntityDebugger,

View File

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

View File

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

View File

@ -1,14 +1,21 @@
import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzlePlayMetadata extends BaseHUDPart {
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.titleElement.innerText = T.ingame.PuzzlePlayMetadata.title;
this.titleElement.innerText = "tobspr's first puzzle";
this.puzzleNameElement = makeDiv(this.titleElement, null, ["name"]);
this.puzzleNameElement.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() {}

View File

@ -167,7 +167,7 @@ export class GameLogic {
*/
canDeleteBuilding(building) {
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
* @param {GameRoot} root
* @returns {boolean}
*/
getIsRemovable() {
getIsRemovable(root) {
return true;
}

View File

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

View File

@ -22,6 +22,9 @@ import { MetaTransistorBuilding } from "../buildings/transistor";
import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
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 {
static getId() {
@ -58,9 +61,31 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
MetaTransistorBuilding,
];
this.additionalHudParts.constantSignalEdit = HUDConstantSignalEdit;
this.additionalHudParts.puzzlePlayMetadata = HUDPuzzlePlayMetadata;
root.signals.postLoadHook.add(this.loadPuzzle, this);
console.log("playing 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.strokeStyle = "#64666e";
parameters.context.fillStyle = isValid ? "#8de255" : "#e2555f";
parameters.context.fillStyle = isValid ? "#8de255" : "#ff666a";
parameters.context.beginCircle(10, 11.8, 3);
parameters.context.fill();
parameters.context.stroke();

View File

@ -5,6 +5,19 @@ import { PuzzleGameMode } from "../game/modes/puzzle";
import { enumConstantSignalType } from "../game/components/constant_signal";
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 } 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 {
/**
@ -26,10 +39,10 @@ export class PuzzleSerializer {
if (signalComp) {
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({
type: "emitter",
item: /** @type {ShapeItem} */ (signalComp.signal).definition.getHash(),
item: signalComp.signal.getAsCopyableKey(),
pos: {
x: staticComp.origin.x,
y: staticComp.origin.y,
@ -45,7 +58,7 @@ export class PuzzleSerializer {
assert(goalComp.item.getItemType() === "shape", "goal is not an item");
buildings.push({
type: "goal",
item: /** @type {ShapeItem} */ (goalComp.item).definition.getHash(),
item: goalComp.item.getAsCopyableKey(),
pos: {
x: staticComp.origin.x,
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",
item: "CuCuCuCu",
pos: { x: 0, y: 0, r: 180 },
pos: { x: -2, y: 2, r: 0 },
},
{
type: "emitter",
item: "red",
pos: { x: 2, y: 0, r: 180 },
pos: { x: 1, y: 2, r: 0 },
},
{
type: "goal",
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();

View File

@ -302,6 +302,11 @@ dialogs:
title: Resize not possible
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:
# This is shown in the top left corner and displays useful keybindings in
# every situation