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;
|
||||
|
||||
@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_PuzzleEditorTitle,
|
||||
ingame_HUD_PuzzleEditorSettings,
|
||||
ingame_HUD_PuzzlePlayMetadata
|
||||
ingame_HUD_PuzzlePlayMetadata,
|
||||
ingame_HUD_PuzzlePlayTitle,
|
||||
ingame_HUD_Notifications,
|
||||
ingame_HUD_DebugInfo,
|
||||
ingame_HUD_EntityDebugger,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
@ -167,7 +167,7 @@ export class GameLogic {
|
||||
*/
|
||||
canDeleteBuilding(building) {
|
||||
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
|
||||
* @param {GameRoot} root
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsRemovable() {
|
||||
getIsRemovable(root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user