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:
parent
aea668d7e3
commit
2add9ba18f
@ -116,29 +116,31 @@
|
||||
}
|
||||
|
||||
.puzzle-lock {
|
||||
& {
|
||||
/* @load-async */
|
||||
background: uiResource("locked_building.png") center center / 90% no-repeat;
|
||||
}
|
||||
&.active {
|
||||
& {
|
||||
/* @load-async */
|
||||
background: uiResource("locked_building.png") center center / 90% no-repeat;
|
||||
}
|
||||
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
|
||||
position: absolute;
|
||||
@include S(top, -15px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) !important;
|
||||
transition: all 0.12s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
position: absolute;
|
||||
@include S(top, -15px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) !important;
|
||||
transition: all 0.12s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
|
||||
@include S(width, 12px);
|
||||
@include S(height, 12px);
|
||||
@include S(width, 12px);
|
||||
@include S(height, 12px);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
src/css/ingame_hud/puzzle_editor_download.scss
Normal file
39
src/css/ingame_hud/puzzle_editor_download.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
position: absolute;
|
||||
background: $ingameHudBg;
|
||||
@include S(padding, 10px);
|
||||
@include S(bottom, 60px);
|
||||
@include S(bottom, 70px);
|
||||
@include S(left, 10px);
|
||||
|
||||
@include SuperSmallText;
|
||||
@ -44,7 +44,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
> .mainButtons {
|
||||
&.disabled {
|
||||
button {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .buttonBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -57,14 +63,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .buildingsButton {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
@include S(margin-top, 4px);
|
||||
> button {
|
||||
@include SuperSmallText;
|
||||
> .testToggle {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
@include S(margin-top, 4px);
|
||||
> button {
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
@include SuperSmallText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@
|
||||
@import "ingame_hud/puzzle_play_settings";
|
||||
@import "ingame_hud/puzzle_play_metadata";
|
||||
@import "ingame_hud/puzzle_complete_notification";
|
||||
@import "ingame_hud/puzzle_editor_download";
|
||||
|
||||
// prettier-ignore
|
||||
$elements:
|
||||
@ -84,6 +85,7 @@ ingame_HUD_GameMenu,
|
||||
ingame_HUD_KeybindingOverlay,
|
||||
ingame_HUD_PuzzleBackToMenu,
|
||||
ingame_HUD_PuzzleEditorReview,
|
||||
ingame_HUD_PuzzleEditorDownload,
|
||||
ingame_HUD_PuzzleEditorControls,
|
||||
ingame_HUD_PuzzleEditorTitle,
|
||||
ingame_HUD_PuzzleEditorSettings,
|
||||
@ -133,8 +135,11 @@ body.uiHidden {
|
||||
#ingame_HUD_PlacementHints,
|
||||
#ingame_HUD_GameMenu,
|
||||
#ingame_HUD_PinnedShapes,
|
||||
#ingame_HUD_PuzzleBackToMenu,
|
||||
#ingame_HUD_PuzzleEditorReview,
|
||||
#ingame_HUD_PuzzleEditorSettings,
|
||||
#ingame_HUD_PuzzlePlaySettings,
|
||||
#ingame_HUD_PuzzleEditorControls,
|
||||
#ingame_HUD_PuzzleEditorDownload,
|
||||
#ingame_HUD_PuzzlePlayMetadata,
|
||||
#ingame_HUD_Notifications,
|
||||
#ingame_HUD_TutorialHints,
|
||||
#ingame_HUD_Waypoints,
|
||||
|
||||
@ -8,9 +8,13 @@
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
button {
|
||||
@include S(margin-right, 5px);
|
||||
}
|
||||
|
||||
.createPuzzle {
|
||||
background-color: $colorGreenBright;
|
||||
@include S(margin-left, 5px);
|
||||
@include S(margin-right, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
this.htmlElementId = htmlElementId;
|
||||
this.layer = layer;
|
||||
|
||||
this.requiredBuildings = [
|
||||
gMetaBuildingRegistry.findByClass(MetaConstantProducerBuilding),
|
||||
gMetaBuildingRegistry.findByClass(MetaGoalAcceptorBuilding),
|
||||
gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
|
||||
];
|
||||
|
||||
/** @type {Object.<string, {
|
||||
* metaBuilding: MetaBuilding,
|
||||
* unlocked: boolean,
|
||||
@ -60,11 +66,9 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
const filtered = [];
|
||||
|
||||
for (let i = 0; i < buildings.length; i++) {
|
||||
if (this.root.gameMode.isBuildingExcluded(buildings[i])) {
|
||||
continue;
|
||||
if (!this.root.gameMode.isBuildingExcluded(buildings[i])) {
|
||||
filtered.push(buildings[i]);
|
||||
}
|
||||
|
||||
filtered.push(buildings[i]);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
@ -119,13 +123,14 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
});
|
||||
|
||||
//lock icon for puzzle editor
|
||||
if (this.root.gameMode.getIsEditor() && !this.inRequiredBuildings(metaBuilding)) {
|
||||
const puzzleLock = makeDiv(itemContainer, null, ["puzzle-lock"]);
|
||||
|
||||
if (this.root.gameMode.getIsEditor()) {
|
||||
itemContainer.classList.toggle("editor", true);
|
||||
this.trackClicks(puzzleLock, () => this.toggleBuildingLock(metaBuilding), {
|
||||
clickSound: null,
|
||||
});
|
||||
if (!this.inRequiredBuildings(metaBuilding)) {
|
||||
const puzzleLock = makeDiv(itemContainer, null, ["puzzle-lock"]);
|
||||
puzzleLock.classList.add("active");
|
||||
|
||||
this.trackClicks(puzzleLock, () => this.toggleBuildingLock(metaBuilding));
|
||||
}
|
||||
}
|
||||
|
||||
this.buildingHandles[metaBuilding.id] = {
|
||||
@ -149,13 +154,15 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
});
|
||||
this.lastSelectedIndex = 0;
|
||||
actionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildings).add(this.cycleBuildings, this);
|
||||
|
||||
this.switchingTestMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the toolbar
|
||||
*/
|
||||
update() {
|
||||
const visible = this.visibilityCondition();
|
||||
const visible = this.visibilityCondition() && !this.switchingTestMode;
|
||||
this.domAttach.update(visible);
|
||||
|
||||
if (visible) {
|
||||
@ -253,9 +260,11 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
|
||||
const handle = this.buildingHandles[metaBuilding.getId()];
|
||||
if (handle.puzzleLocked) {
|
||||
handle.puzzleLocked = false;
|
||||
handle.element.classList.toggle("unlocked", false);
|
||||
this.root.soundProxy.playUiClick();
|
||||
if (this.root.gameMode.getIsEditor()) {
|
||||
handle.puzzleLocked = false;
|
||||
handle.element.classList.toggle("unlocked", false);
|
||||
this.root.soundProxy.playUiClick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -273,6 +282,24 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
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
|
||||
*/
|
||||
@ -290,7 +317,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
const handle = this.buildingHandles[metaBuilding.getId()];
|
||||
handle.puzzleLocked = !handle.puzzleLocked;
|
||||
handle.element.classList.toggle("unlocked", !handle.puzzleLocked);
|
||||
this.root.soundProxy.playUiClick();
|
||||
|
||||
const entityManager = this.root.entityMgr;
|
||||
for (const entity of entityManager.getAllWithComponent(StaticMapEntityComponent)) {
|
||||
@ -312,11 +338,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
*/
|
||||
inRequiredBuildings(metaBuilding) {
|
||||
const requiredBuildings = [
|
||||
gMetaBuildingRegistry.findByClass(MetaConstantProducerBuilding),
|
||||
gMetaBuildingRegistry.findByClass(MetaGoalAcceptorBuilding),
|
||||
gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
|
||||
];
|
||||
return requiredBuildings.includes(metaBuilding);
|
||||
return this.requiredBuildings.includes(metaBuilding);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
this.root.camera.movePreHandler.add(this.onMouseMove, 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.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);
|
||||
|
||||
@ -131,6 +131,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
this.root.signals.storyGoalCompleted.add(() => this.currentMetaBuilding.set(null));
|
||||
this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
|
||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||
this.root.signals.testModeChanged.add(this.abortPlacement, this);
|
||||
|
||||
// MOUSE BINDINGS
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
@ -385,8 +386,8 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
const buildingCode = contents.components.StaticMapEntity.code;
|
||||
const extracted = getBuildingDataFromCode(buildingCode);
|
||||
|
||||
// Disable pipetting the hub
|
||||
if (extracted.metaInstance.getId() === gMetaBuildingRegistry.findByClass(MetaHubBuilding).getId()) {
|
||||
// Disable pipetting a non removeable building
|
||||
if (!extracted.metaInstance.getIsRemovable(this.root)) {
|
||||
this.currentMetaBuilding.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ export class HUDMassSelector extends BaseHUDPart {
|
||||
|
||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this);
|
||||
this.root.signals.editModeChanged.add(this.clearSelection, this);
|
||||
this.root.signals.testModeChanged.add(this.clearSelection, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
37
src/js/game/hud/parts/puzzle_editor_download.js
Normal file
37
src/js/game/hud/parts/puzzle_editor_download.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,15 @@
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||
import { createLogger } from "../../../core/logging";
|
||||
import { Rectangle } from "../../../core/rectangle";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import { MetaBlockBuilding } from "../../buildings/block";
|
||||
import { MetaConstantProducerBuilding } from "../../buildings/constant_producer";
|
||||
import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor";
|
||||
import { StaticMapEntityComponent } from "../../components/static_map_entity";
|
||||
import { Entity } from "../../entity";
|
||||
import { PuzzleGameMode } from "../../modes/puzzle";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
const logger = createLogger("puzzle-editor");
|
||||
|
||||
export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorSettings");
|
||||
@ -27,7 +24,7 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
||||
`
|
||||
<label>${T.ingame.puzzleEditorSettings.zoneTitle}</label>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="mainButtons">
|
||||
<div class="zoneWidth plusMinus">
|
||||
<label>${T.ingame.puzzleEditorSettings.zoneWidth}</label>
|
||||
<button class="styledButton minus">-</button>
|
||||
@ -35,7 +32,7 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
||||
<button class="styledButton plus">+</button>
|
||||
</div>
|
||||
|
||||
<div class="zoneHeight plusMinus">
|
||||
<div class="zoneHeight plusMinus">
|
||||
<label>${T.ingame.puzzleEditorSettings.zoneHeight}</label>
|
||||
<button class="styledButton minus">-</button>
|
||||
<span class="value"></span>
|
||||
@ -47,10 +44,10 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
||||
<button class="styledButton clearItems">${T.ingame.puzzleEditorSettings.clearItems}</button>
|
||||
</div>
|
||||
|
||||
<div class="buildingsButton">
|
||||
<button class="styledButton resetPuzzle">${T.ingame.puzzleEditorSettings.resetPuzzle}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="testToggle">
|
||||
<button class="styledButton testPuzzle">${T.ingame.puzzleEditorSettings.enableTestMode}</button>
|
||||
</div>`
|
||||
);
|
||||
|
||||
@ -60,7 +57,12 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
||||
bind(".zoneHeight .plus", () => this.modifyZone(0, 1));
|
||||
bind("button.trim", this.trim);
|
||||
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();
|
||||
}
|
||||
|
||||
resetPuzzle() {
|
||||
for (const entity of this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const goalComp = entity.components.GoalAcceptor;
|
||||
toggleTestMode() {
|
||||
this.testMode = !this.testMode;
|
||||
|
||||
if (goalComp) {
|
||||
goalComp.clear();
|
||||
this.element.querySelector(".mainButtons").classList.toggle("disabled", this.testMode);
|
||||
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 (
|
||||
[MetaGoalAcceptorBuilding, MetaConstantProducerBuilding, MetaBlockBuilding]
|
||||
.map(metaClass => gMetaBuildingRegistry.findByClass(metaClass).id)
|
||||
.includes(staticComp.getMetaBuilding().id)
|
||||
) {
|
||||
continue;
|
||||
if (!this.testMode) {
|
||||
for (const entity of this.storedSolution) {
|
||||
const placedEntity = this.root.logic.tryPlaceEntity(entity);
|
||||
|
||||
for (const key in entity.components) {
|
||||
/** @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() {
|
||||
|
||||
@ -116,6 +116,15 @@ export class GameLogic {
|
||||
rotationVariant,
|
||||
variant,
|
||||
});
|
||||
return this.tryPlaceEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to place the given entity
|
||||
* @param {Entity} entity
|
||||
* @returns {Entity}
|
||||
*/
|
||||
tryPlaceEntity(entity) {
|
||||
if (this.checkCanPlaceEntity(entity)) {
|
||||
this.freeEntityAreaBeforeBuild(entity);
|
||||
this.root.map.placeStaticEntity(entity);
|
||||
|
||||
@ -22,6 +22,13 @@ 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 { 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 {
|
||||
static getId() {
|
||||
@ -32,8 +39,13 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
|
||||
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);
|
||||
|
||||
this.hiddenBuildings = [
|
||||
@ -58,9 +70,62 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
|
||||
this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls;
|
||||
this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview;
|
||||
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() {
|
||||
return true;
|
||||
/** @type {HUDPuzzleEditorSettings} */
|
||||
const editSettings = this.root.hud.parts.puzzleEditorSettings;
|
||||
return !editSettings.testMode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +192,7 @@ export class GameRoot {
|
||||
|
||||
// Puzzle mode
|
||||
puzzleComplete: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
testModeChanged: /** @type {TypedSignal<[Boolean]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
// RNG's
|
||||
|
||||
@ -3,6 +3,7 @@ import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { clamp, lerp } from "../../core/utils";
|
||||
import { Vector } from "../../core/vector";
|
||||
import { GoalAcceptorComponent } from "../components/goal_acceptor";
|
||||
import { enumGameModeIds } from "../game_mode";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunk } from "../map_chunk";
|
||||
import { GameRoot } from "../root";
|
||||
@ -42,7 +43,7 @@ export class GoalAcceptorSystem extends GameSystemWithFilter {
|
||||
!this.puzzleCompleted &&
|
||||
this.root.gameInitialized &&
|
||||
allAccepted &&
|
||||
!this.root.gameMode.getIsEditor()
|
||||
!(this.root.gameMode.getId() == enumGameModeIds.puzzleEdit)
|
||||
) {
|
||||
this.root.signals.puzzleComplete.dispatch();
|
||||
this.puzzleCompleted = true;
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { createLogger } from "../core/logging";
|
||||
import { DialogWithForm } from "../core/modal_dialog_elements";
|
||||
import { FormElementInput } from "../core/modal_dialog_forms";
|
||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||
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 { ShapeDefinition } from "../game/shape_definition";
|
||||
import { MUSIC } from "../platform/sound";
|
||||
@ -42,6 +43,7 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||
|
||||
<div class="actions">
|
||||
<button class="styledButton importPuzzle">Import</button>
|
||||
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
|
||||
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
||||
</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.loadPuzzle"), () => this.loadPuzzle());
|
||||
this.trackClicks(this.htmlElement.querySelector("button.importPuzzle"), () => this.importPuzzle());
|
||||
}
|
||||
|
||||
createEmptySavegame() {
|
||||
@ -454,7 +457,44 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
const savegame = this.createEmptySavegame();
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzleEdit,
|
||||
gameModeParameters: {},
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,6 +400,11 @@ dialogs:
|
||||
desc: >-
|
||||
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:
|
||||
# This is shown in the top left corner and displays useful keybindings in
|
||||
# every situation
|
||||
@ -634,6 +639,8 @@ ingame:
|
||||
clearItems: Clear Items
|
||||
clearBuildings: Clear Buildings
|
||||
resetPuzzle: Reset Puzzle
|
||||
enableTestMode: Enable Test Mode
|
||||
disableTestMode: Disable Test Mode
|
||||
share: Share
|
||||
report: Report
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user