1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-02-12 02:49:20 +00:00

Added a test mode for the puzzle editor, and added the ability to download and import puzzles to the editor

This commit is contained in:
Sense101 2021-07-06 20:11:42 +01:00
parent aea668d7e3
commit 2add9ba18f
17 changed files with 369 additions and 84 deletions

View File

@ -116,29 +116,31 @@
} }
.puzzle-lock { .puzzle-lock {
& { &.active {
/* @load-async */ & {
background: uiResource("locked_building.png") center center / 90% no-repeat; /* @load-async */
} background: uiResource("locked_building.png") center center / 90% no-repeat;
}
display: grid; display: grid;
grid-auto-flow: column; grid-auto-flow: column;
position: absolute; position: absolute;
@include S(top, -15px); @include S(top, -15px);
left: 50%; left: 50%;
transform: translateX(-50%) !important; transform: translateX(-50%) !important;
transition: all 0.12s ease-in-out; transition: all 0.12s ease-in-out;
transition-property: opacity, transform; transition-property: opacity, transform;
cursor: pointer; cursor: pointer;
pointer-events: all; pointer-events: all;
@include S(width, 12px); @include S(width, 12px);
@include S(height, 12px); @include S(height, 12px);
&:hover { &:hover {
opacity: 0.5; opacity: 0.5;
}
} }
} }
} }

View File

@ -0,0 +1,39 @@
#ingame_HUD_PuzzleEditorDownload {
position: absolute;
@include S(top, 35px);
left: 50%;
transform: translateX(-50%);
backdrop-filter: blur(D(1px));
padding: D(3px);
> .button {
@include PlainText;
pointer-events: all;
cursor: pointer;
position: relative;
color: #333438;
transition: all 0.12s ease-in-out;
transition-property: opacity, transform;
text-transform: uppercase;
@include PlainText;
@include S(width, 30px);
@include S(height, 30px);
@include DarkThemeInvert;
opacity: 1;
&:hover {
opacity: 0.9 !important;
}
&.pressed {
transform: scale(0.95) !important;
}
& {
/* @load-async */
background: uiResource("icons/download.png") center center / D(15px) no-repeat;
}
}
}

View File

@ -2,7 +2,7 @@
position: absolute; position: absolute;
background: $ingameHudBg; background: $ingameHudBg;
@include S(padding, 10px); @include S(padding, 10px);
@include S(bottom, 60px); @include S(bottom, 70px);
@include S(left, 10px); @include S(left, 10px);
@include SuperSmallText; @include SuperSmallText;
@ -44,7 +44,13 @@
} }
} }
> .buttons { > .mainButtons {
&.disabled {
button {
pointer-events: none;
}
}
> .buttonBar { > .buttonBar {
display: flex; display: flex;
align-items: center; align-items: center;
@ -57,14 +63,17 @@
} }
} }
} }
}
> .buildingsButton { > .testToggle {
display: grid; display: grid;
align-items: center; align-items: center;
@include S(margin-top, 4px); @include S(margin-top, 4px);
> button { > button {
@include SuperSmallText; &.disabled {
pointer-events: none;
} }
@include SuperSmallText;
} }
} }
} }

View File

@ -65,6 +65,7 @@
@import "ingame_hud/puzzle_play_settings"; @import "ingame_hud/puzzle_play_settings";
@import "ingame_hud/puzzle_play_metadata"; @import "ingame_hud/puzzle_play_metadata";
@import "ingame_hud/puzzle_complete_notification"; @import "ingame_hud/puzzle_complete_notification";
@import "ingame_hud/puzzle_editor_download";
// prettier-ignore // prettier-ignore
$elements: $elements:
@ -84,6 +85,7 @@ ingame_HUD_GameMenu,
ingame_HUD_KeybindingOverlay, ingame_HUD_KeybindingOverlay,
ingame_HUD_PuzzleBackToMenu, ingame_HUD_PuzzleBackToMenu,
ingame_HUD_PuzzleEditorReview, ingame_HUD_PuzzleEditorReview,
ingame_HUD_PuzzleEditorDownload,
ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle, ingame_HUD_PuzzleEditorTitle,
ingame_HUD_PuzzleEditorSettings, ingame_HUD_PuzzleEditorSettings,
@ -133,8 +135,11 @@ body.uiHidden {
#ingame_HUD_PlacementHints, #ingame_HUD_PlacementHints,
#ingame_HUD_GameMenu, #ingame_HUD_GameMenu,
#ingame_HUD_PinnedShapes, #ingame_HUD_PinnedShapes,
#ingame_HUD_PuzzleBackToMenu, #ingame_HUD_PuzzleEditorSettings,
#ingame_HUD_PuzzleEditorReview, #ingame_HUD_PuzzlePlaySettings,
#ingame_HUD_PuzzleEditorControls,
#ingame_HUD_PuzzleEditorDownload,
#ingame_HUD_PuzzlePlayMetadata,
#ingame_HUD_Notifications, #ingame_HUD_Notifications,
#ingame_HUD_TutorialHints, #ingame_HUD_TutorialHints,
#ingame_HUD_Waypoints, #ingame_HUD_Waypoints,

View File

@ -8,9 +8,13 @@
justify-self: start; justify-self: start;
} }
button {
@include S(margin-right, 5px);
}
.createPuzzle { .createPuzzle {
background-color: $colorGreenBright; background-color: $colorGreenBright;
@include S(margin-left, 5px); @include S(margin-right, 0);
} }
} }

View File

@ -33,6 +33,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
this.htmlElementId = htmlElementId; this.htmlElementId = htmlElementId;
this.layer = layer; this.layer = layer;
this.requiredBuildings = [
gMetaBuildingRegistry.findByClass(MetaConstantProducerBuilding),
gMetaBuildingRegistry.findByClass(MetaGoalAcceptorBuilding),
gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
];
/** @type {Object.<string, { /** @type {Object.<string, {
* metaBuilding: MetaBuilding, * metaBuilding: MetaBuilding,
* unlocked: boolean, * unlocked: boolean,
@ -60,11 +66,9 @@ export class HUDBaseToolbar extends BaseHUDPart {
const filtered = []; const filtered = [];
for (let i = 0; i < buildings.length; i++) { for (let i = 0; i < buildings.length; i++) {
if (this.root.gameMode.isBuildingExcluded(buildings[i])) { if (!this.root.gameMode.isBuildingExcluded(buildings[i])) {
continue; filtered.push(buildings[i]);
} }
filtered.push(buildings[i]);
} }
return filtered; return filtered;
@ -119,13 +123,14 @@ export class HUDBaseToolbar extends BaseHUDPart {
}); });
//lock icon for puzzle editor //lock icon for puzzle editor
if (this.root.gameMode.getIsEditor() && !this.inRequiredBuildings(metaBuilding)) { if (this.root.gameMode.getIsEditor()) {
const puzzleLock = makeDiv(itemContainer, null, ["puzzle-lock"]);
itemContainer.classList.toggle("editor", true); itemContainer.classList.toggle("editor", true);
this.trackClicks(puzzleLock, () => this.toggleBuildingLock(metaBuilding), { if (!this.inRequiredBuildings(metaBuilding)) {
clickSound: null, const puzzleLock = makeDiv(itemContainer, null, ["puzzle-lock"]);
}); puzzleLock.classList.add("active");
this.trackClicks(puzzleLock, () => this.toggleBuildingLock(metaBuilding));
}
} }
this.buildingHandles[metaBuilding.id] = { this.buildingHandles[metaBuilding.id] = {
@ -149,13 +154,15 @@ export class HUDBaseToolbar extends BaseHUDPart {
}); });
this.lastSelectedIndex = 0; this.lastSelectedIndex = 0;
actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this); actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this);
this.switchingTestMode = false;
} }
/** /**
* Updates the toolbar * Updates the toolbar
*/ */
update() { update() {
const visible = this.visibilityCondition(); const visible = this.visibilityCondition() && !this.switchingTestMode;
this.domAttach.update(visible); this.domAttach.update(visible);
if (visible) { if (visible) {
@ -253,9 +260,11 @@ export class HUDBaseToolbar extends BaseHUDPart {
const handle = this.buildingHandles[metaBuilding.getId()]; const handle = this.buildingHandles[metaBuilding.getId()];
if (handle.puzzleLocked) { if (handle.puzzleLocked) {
handle.puzzleLocked = false; if (this.root.gameMode.getIsEditor()) {
handle.element.classList.toggle("unlocked", false); handle.puzzleLocked = false;
this.root.soundProxy.playUiClick(); handle.element.classList.toggle("unlocked", false);
this.root.soundProxy.playUiClick();
}
return; return;
} }
@ -273,6 +282,24 @@ export class HUDBaseToolbar extends BaseHUDPart {
this.onSelectedPlacementBuildingChanged(metaBuilding); this.onSelectedPlacementBuildingChanged(metaBuilding);
} }
/**
* @param {boolean} testMode
*/
toggleTestMode(testMode) {
// toggle the puzzle lock buttons and the editor-only buildings
this.element.querySelectorAll(".building > .puzzle-lock").forEach(element => {
element.classList.toggle("active", !testMode);
});
for (let i = 0; i < this.requiredBuildings.length; ++i) {
const metaBuilding = this.requiredBuildings[i];
const handle = this.buildingHandles[metaBuilding.getId()];
handle.puzzleLocked = testMode;
handle.element.classList.toggle("unlocked", !testMode);
}
}
/** /**
* @param {MetaBuilding} metaBuilding * @param {MetaBuilding} metaBuilding
*/ */
@ -290,7 +317,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
const handle = this.buildingHandles[metaBuilding.getId()]; const handle = this.buildingHandles[metaBuilding.getId()];
handle.puzzleLocked = !handle.puzzleLocked; handle.puzzleLocked = !handle.puzzleLocked;
handle.element.classList.toggle("unlocked", !handle.puzzleLocked); handle.element.classList.toggle("unlocked", !handle.puzzleLocked);
this.root.soundProxy.playUiClick();
const entityManager = this.root.entityMgr; const entityManager = this.root.entityMgr;
for (const entity of entityManager.getAllWithComponent(StaticMapEntityComponent)) { for (const entity of entityManager.getAllWithComponent(StaticMapEntityComponent)) {
@ -312,11 +338,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
* @param {MetaBuilding} metaBuilding * @param {MetaBuilding} metaBuilding
*/ */
inRequiredBuildings(metaBuilding) { inRequiredBuildings(metaBuilding) {
const requiredBuildings = [ return this.requiredBuildings.includes(metaBuilding);
gMetaBuildingRegistry.findByClass(MetaConstantProducerBuilding),
gMetaBuildingRegistry.findByClass(MetaGoalAcceptorBuilding),
gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
];
return requiredBuildings.includes(metaBuilding);
} }
} }

View File

@ -44,6 +44,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
this.root.camera.movePreHandler.add(this.onMouseMove, this); this.root.camera.movePreHandler.add(this.onMouseMove, this);
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this); this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
this.root.signals.testModeChanged.add(this.abortPlacement, this);
this.root.signals.editModeChanged.add(this.onEditModeChanged, this); this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent); this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);

View File

@ -131,6 +131,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
this.root.signals.storyGoalCompleted.add(() => this.currentMetaBuilding.set(null)); this.root.signals.storyGoalCompleted.add(() => this.currentMetaBuilding.set(null));
this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch()); this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
this.root.signals.editModeChanged.add(this.onEditModeChanged, this); this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
this.root.signals.testModeChanged.add(this.abortPlacement, this);
// MOUSE BINDINGS // MOUSE BINDINGS
this.root.camera.downPreHandler.add(this.onMouseDown, this); this.root.camera.downPreHandler.add(this.onMouseDown, this);
@ -385,8 +386,8 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
const buildingCode = contents.components.StaticMapEntity.code; const buildingCode = contents.components.StaticMapEntity.code;
const extracted = getBuildingDataFromCode(buildingCode); const extracted = getBuildingDataFromCode(buildingCode);
// Disable pipetting the hub // Disable pipetting a non removeable building
if (extracted.metaInstance.getId() === gMetaBuildingRegistry.findByClass(MetaHubBuilding).getId()) { if (!extracted.metaInstance.getIsRemovable(this.root)) {
this.currentMetaBuilding.set(null); this.currentMetaBuilding.set(null);
return; return;
} }

View File

@ -45,6 +45,7 @@ export class HUDMassSelector extends BaseHUDPart {
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this); this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this);
this.root.signals.editModeChanged.add(this.clearSelection, this); this.root.signals.editModeChanged.add(this.clearSelection, this);
this.root.signals.testModeChanged.add(this.clearSelection, this);
} }
/** /**

View File

@ -0,0 +1,37 @@
import { ReadWriteProxy } from "../../../core/read_write_proxy";
import { generateFileDownload, makeDiv } from "../../../core/utils";
import { PuzzleSerializer } from "../../../savegame/puzzle_serializer";
import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzleEditorDownload extends BaseHUDPart {
constructor(root) {
super(root);
}
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorDownload");
this.button = document.createElement("button");
this.button.classList.add("button");
this.element.appendChild(this.button);
this.trackClicks(this.button, () => {
const { ok } = this.root.hud.parts.dialogs.showWarning(
T.dialogs.puzzleDownload.title,
T.dialogs.puzzleDownload.desc,
["cancel", "ok:good:enter"]
);
ok.add(() => this.downloadPuzzle());
});
}
initialize() {}
downloadPuzzle() {
const serialized = new PuzzleSerializer().generateDumpFromGameRoot(this.root);
const data = ReadWriteProxy.serializeObject(serialized);
const filename = "puzzle.bin";
generateFileDownload(filename, data);
}
}

View File

@ -1,18 +1,15 @@
import { globalConfig } from "../../../core/config"; import { globalConfig } from "../../../core/config";
import { gMetaBuildingRegistry } from "../../../core/global_registries"; import { gMetaBuildingRegistry } from "../../../core/global_registries";
import { createLogger } from "../../../core/logging";
import { Rectangle } from "../../../core/rectangle"; import { Rectangle } from "../../../core/rectangle";
import { makeDiv } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { MetaBlockBuilding } from "../../buildings/block"; import { MetaBlockBuilding } from "../../buildings/block";
import { MetaConstantProducerBuilding } from "../../buildings/constant_producer"; import { MetaConstantProducerBuilding } from "../../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor";
import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { StaticMapEntityComponent } from "../../components/static_map_entity";
import { Entity } from "../../entity";
import { PuzzleGameMode } from "../../modes/puzzle"; import { PuzzleGameMode } from "../../modes/puzzle";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
const logger = createLogger("puzzle-editor");
export class HUDPuzzleEditorSettings extends BaseHUDPart { export class HUDPuzzleEditorSettings extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorSettings"); this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorSettings");
@ -27,7 +24,7 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
` `
<label>${T.ingame.puzzleEditorSettings.zoneTitle}</label> <label>${T.ingame.puzzleEditorSettings.zoneTitle}</label>
<div class="buttons"> <div class="mainButtons">
<div class="zoneWidth plusMinus"> <div class="zoneWidth plusMinus">
<label>${T.ingame.puzzleEditorSettings.zoneWidth}</label> <label>${T.ingame.puzzleEditorSettings.zoneWidth}</label>
<button class="styledButton minus">-</button> <button class="styledButton minus">-</button>
@ -35,7 +32,7 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
<button class="styledButton plus">+</button> <button class="styledButton plus">+</button>
</div> </div>
<div class="zoneHeight plusMinus"> <div class="zoneHeight plusMinus">
<label>${T.ingame.puzzleEditorSettings.zoneHeight}</label> <label>${T.ingame.puzzleEditorSettings.zoneHeight}</label>
<button class="styledButton minus">-</button> <button class="styledButton minus">-</button>
<span class="value"></span> <span class="value"></span>
@ -47,10 +44,10 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
<button class="styledButton clearItems">${T.ingame.puzzleEditorSettings.clearItems}</button> <button class="styledButton clearItems">${T.ingame.puzzleEditorSettings.clearItems}</button>
</div> </div>
<div class="buildingsButton"> </div>
<button class="styledButton resetPuzzle">${T.ingame.puzzleEditorSettings.resetPuzzle}</button>
</div>
<div class="testToggle">
<button class="styledButton testPuzzle">${T.ingame.puzzleEditorSettings.enableTestMode}</button>
</div>` </div>`
); );
@ -60,7 +57,12 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
bind(".zoneHeight .plus", () => this.modifyZone(0, 1)); bind(".zoneHeight .plus", () => this.modifyZone(0, 1));
bind("button.trim", this.trim); bind("button.trim", this.trim);
bind("button.clearItems", this.clearItems); bind("button.clearItems", this.clearItems);
bind("button.resetPuzzle", this.resetPuzzle); bind("button.testPuzzle", this.toggleTestMode);
this.testMode = false;
/** @type {Entity[]} */
this.storedSolution = [];
} }
} }
@ -68,27 +70,67 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
this.root.logic.clearAllBeltsAndItems(); this.root.logic.clearAllBeltsAndItems();
} }
resetPuzzle() { toggleTestMode() {
for (const entity of this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) { this.testMode = !this.testMode;
const staticComp = entity.components.StaticMapEntity;
const goalComp = entity.components.GoalAcceptor;
if (goalComp) { this.element.querySelector(".mainButtons").classList.toggle("disabled", this.testMode);
goalComp.clear(); const testButton = this.element.querySelector(".testToggle > .testPuzzle");
testButton.textContent = this.testMode
? T.ingame.puzzleEditorSettings.disableTestMode
: T.ingame.puzzleEditorSettings.enableTestMode;
testButton.classList.toggle("disabled", true);
const buildingsToolbar = this.root.hud.parts.buildingsToolbar;
buildingsToolbar.switchingTestMode = true;
this.root.signals.testModeChanged.dispatch(this.testMode);
setTimeout(() => {
buildingsToolbar.switchingTestMode = false;
buildingsToolbar.toggleTestMode(this.testMode);
testButton.classList.toggle("disabled", false);
}, 140);
this.root.logic.performBulkOperation(() => {
for (const entity of this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
if (this.testMode) {
this.storedSolution.push(entity.clone());
const metaBuilding = entity.components.StaticMapEntity.getMetaBuilding();
const goalComp = entity.components.GoalAcceptor;
if (goalComp) {
goalComp.clear();
continue;
}
if (
[MetaConstantProducerBuilding, MetaBlockBuilding]
.map(metaClass => gMetaBuildingRegistry.findByClass(metaClass).id)
.includes(metaBuilding.id)
) {
continue;
}
}
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
} }
this.root.entityMgr.processDestroyList();
if ( if (!this.testMode) {
[MetaGoalAcceptorBuilding, MetaConstantProducerBuilding, MetaBlockBuilding] for (const entity of this.storedSolution) {
.map(metaClass => gMetaBuildingRegistry.findByClass(metaClass).id) const placedEntity = this.root.logic.tryPlaceEntity(entity);
.includes(staticComp.getMetaBuilding().id)
) { for (const key in entity.components) {
continue; /** @type {import("../../../core/global_registries").Component} */ (entity.components[
key
]).copyAdditionalStateTo(placedEntity.components[key]);
}
}
this.storedSolution = [];
} }
});
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
}
this.root.entityMgr.processDestroyList();
} }
trim() { trim() {

View File

@ -116,6 +116,15 @@ export class GameLogic {
rotationVariant, rotationVariant,
variant, variant,
}); });
return this.tryPlaceEntity(entity);
}
/**
* Attempts to place the given entity
* @param {Entity} entity
* @returns {Entity}
*/
tryPlaceEntity(entity) {
if (this.checkCanPlaceEntity(entity)) { if (this.checkCanPlaceEntity(entity)) {
this.freeEntityAreaBeforeBuild(entity); this.freeEntityAreaBeforeBuild(entity);
this.root.map.placeStaticEntity(entity); this.root.map.placeStaticEntity(entity);

View File

@ -22,6 +22,13 @@ 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 { createLogger } from "../../core/logging";
import { PuzzleSerializer } from "../../savegame/puzzle_serializer";
import { T } from "../../translations";
import { gMetaBuildingRegistry } from "../../core/global_registries";
import { HUDPuzzleEditorDownload } from "../hud/parts/puzzle_editor_download";
const logger = createLogger("puzzle-edit");
export class PuzzleEditGameMode extends PuzzleGameMode { export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() { static getId() {
@ -32,8 +39,13 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
return {}; return {};
} }
/** @param {GameRoot} root */ /**
constructor(root) { * @param {GameRoot} root
* @param {object} payload
* @param {import("../../savegame/savegame_typedefs").PuzzleGameData} payload.gameData
* @param {boolean} payload.startInTestMode
*/
constructor(root, { gameData = null, startInTestMode = false }) {
super(root); super(root);
this.hiddenBuildings = [ this.hiddenBuildings = [
@ -58,9 +70,62 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls; this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls;
this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview; this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview;
this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings; this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings;
this.additionalHudParts.puzzleEditorDownload = HUDPuzzleEditorDownload;
this.gameData = gameData;
if (gameData) {
root.signals.postLoadHook.add(() => this.loadPuzzle(gameData), this);
}
this.startInTestMode = startInTestMode;
}
/**
* @param {import("../../savegame/savegame_typedefs").PuzzleGameData} puzzle
*/
loadPuzzle(puzzle) {
let errorText;
logger.log("Loading puzzle", puzzle);
// set zone and add buildings
try {
this.zoneWidth = puzzle.bounds.w;
this.zoneHeight = puzzle.bounds.h;
errorText = new PuzzleSerializer().deserializePuzzle(this.root, puzzle);
} catch (ex) {
errorText = ex.message || ex;
}
if (errorText) {
this.root.gameState.moveToState("PuzzleMenuState", {
error: {
title: T.dialogs.puzzleLoadError.title,
desc: T.dialogs.puzzleLoadError.desc + " " + errorText,
},
});
}
const toolbar = this.root.hud.parts.buildingsToolbar;
// lock excluded buildings
for (let i = 0; i < this.gameData.excludedBuildings.length; ++i) {
const id = this.gameData.excludedBuildings[i];
if (!gMetaBuildingRegistry.hasId(id)) {
continue;
}
toolbar.toggleBuildingLock(gMetaBuildingRegistry.findById(id));
}
if (this.startInTestMode) {
this.root.hud.parts.puzzleEditorSettings.toggleTestMode();
}
} }
getIsEditor() { getIsEditor() {
return true; /** @type {HUDPuzzleEditorSettings} */
const editSettings = this.root.hud.parts.puzzleEditorSettings;
return !editSettings.testMode;
} }
} }

View File

@ -192,6 +192,7 @@ export class GameRoot {
// Puzzle mode // Puzzle mode
puzzleComplete: /** @type {TypedSignal<[]>} */ (new Signal()), puzzleComplete: /** @type {TypedSignal<[]>} */ (new Signal()),
testModeChanged: /** @type {TypedSignal<[Boolean]>} */ (new Signal()),
}; };
// RNG's // RNG's

View File

@ -3,6 +3,7 @@ import { DrawParameters } from "../../core/draw_parameters";
import { clamp, lerp } from "../../core/utils"; import { clamp, lerp } from "../../core/utils";
import { Vector } from "../../core/vector"; import { Vector } from "../../core/vector";
import { GoalAcceptorComponent } from "../components/goal_acceptor"; import { GoalAcceptorComponent } from "../components/goal_acceptor";
import { enumGameModeIds } from "../game_mode";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunk } from "../map_chunk"; import { MapChunk } from "../map_chunk";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
@ -42,7 +43,7 @@ export class GoalAcceptorSystem extends GameSystemWithFilter {
!this.puzzleCompleted && !this.puzzleCompleted &&
this.root.gameInitialized && this.root.gameInitialized &&
allAccepted && allAccepted &&
!this.root.gameMode.getIsEditor() !(this.root.gameMode.getId() == enumGameModeIds.puzzleEdit)
) { ) {
this.root.signals.puzzleComplete.dispatch(); this.root.signals.puzzleComplete.dispatch();
this.puzzleCompleted = true; this.puzzleCompleted = true;

View File

@ -1,8 +1,9 @@
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { DialogWithForm } from "../core/modal_dialog_elements"; import { DialogWithForm } from "../core/modal_dialog_elements";
import { FormElementInput } from "../core/modal_dialog_forms"; import { FormElementInput } from "../core/modal_dialog_forms";
import { ReadWriteProxy } from "../core/read_write_proxy";
import { TextualGameState } from "../core/textual_game_state"; import { TextualGameState } from "../core/textual_game_state";
import { formatBigNumberFull } from "../core/utils"; import { formatBigNumberFull, startFileChoose, waitNextFrame } from "../core/utils";
import { enumGameModeIds } from "../game/game_mode"; import { enumGameModeIds } from "../game/game_mode";
import { ShapeDefinition } from "../game/shape_definition"; import { ShapeDefinition } from "../game/shape_definition";
import { MUSIC } from "../platform/sound"; import { MUSIC } from "../platform/sound";
@ -42,6 +43,7 @@ export class PuzzleMenuState extends TextualGameState {
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1> <h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
<div class="actions"> <div class="actions">
<button class="styledButton importPuzzle">Import</button>
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button> <button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button> <button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
</div> </div>
@ -388,6 +390,7 @@ export class PuzzleMenuState extends TextualGameState {
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle()); this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle()); this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
this.trackClicks(this.htmlElement.querySelector("button.importPuzzle"), () => this.importPuzzle());
} }
createEmptySavegame() { createEmptySavegame() {
@ -454,7 +457,44 @@ export class PuzzleMenuState extends TextualGameState {
const savegame = this.createEmptySavegame(); const savegame = this.createEmptySavegame();
this.moveToState("InGameState", { this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzleEdit, gameModeId: enumGameModeIds.puzzleEdit,
gameModeParameters: {},
savegame, savegame,
}); });
} }
importPuzzle() {
startFileChoose(".bin").then(file => {
if (file) {
const closeLoader = this.dialogs.showLoadingDialog("Importing Puzzle");
waitNextFrame().then(() => {
const reader = new FileReader();
reader.addEventListener("load", event => {
const fileContents = event.target.result.toString();
/** @type {import("../savegame/savegame_typedefs").PuzzleGameData} */
let gameData;
try {
gameData = ReadWriteProxy.deserializeObject(fileContents);
} catch (err) {
closeLoader();
this.dialogs.showWarning(T.global.error, String(err));
return;
}
const savegame = this.createEmptySavegame();
this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzleEdit,
gameModeParameters: {
gameData,
startInTestMode: true,
},
savegame,
});
});
reader.readAsText(file);
});
}
});
}
} }

View File

@ -400,6 +400,11 @@ dialogs:
desc: >- desc: >-
Are you sure you want to delete '<title>'? This can not be undone! Are you sure you want to delete '<title>'? This can not be undone!
puzzleDownload:
title: Download Puzzle
desc: >-
Do you want to download this puzzle?
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
@ -634,6 +639,8 @@ ingame:
clearItems: Clear Items clearItems: Clear Items
clearBuildings: Clear Buildings clearBuildings: Clear Buildings
resetPuzzle: Reset Puzzle resetPuzzle: Reset Puzzle
enableTestMode: Enable Test Mode
disableTestMode: Disable Test Mode
share: Share share: Share
report: Report report: Report