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:
parent
72530005cb
commit
31811f20b7
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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() {}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user