From 2add9ba18f60f6443bc2d801ef1293fd92025eee Mon Sep 17 00:00:00 2001 From: Sense101 <67970865+Sense101@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:11:42 +0100 Subject: [PATCH] Added a test mode for the puzzle editor, and added the ability to download and import puzzles to the editor --- src/css/ingame_hud/buildings_toolbar.scss | 38 ++++---- .../ingame_hud/puzzle_editor_download.scss | 39 ++++++++ .../ingame_hud/puzzle_editor_settings.scss | 25 +++-- src/css/main.scss | 9 +- src/css/states/puzzle_menu.scss | 6 +- src/js/game/hud/parts/base_toolbar.js | 63 ++++++++---- src/js/game/hud/parts/blueprint_placer.js | 1 + .../game/hud/parts/building_placer_logic.js | 5 +- src/js/game/hud/parts/mass_selector.js | 1 + .../game/hud/parts/puzzle_editor_download.js | 37 +++++++ .../game/hud/parts/puzzle_editor_settings.js | 96 +++++++++++++------ src/js/game/logic.js | 9 ++ src/js/game/modes/puzzle_edit.js | 71 +++++++++++++- src/js/game/root.js | 1 + src/js/game/systems/goal_acceptor.js | 3 +- src/js/states/puzzle_menu.js | 42 +++++++- translations/base-en.yaml | 7 ++ 17 files changed, 369 insertions(+), 84 deletions(-) create mode 100644 src/css/ingame_hud/puzzle_editor_download.scss create mode 100644 src/js/game/hud/parts/puzzle_editor_download.js diff --git a/src/css/ingame_hud/buildings_toolbar.scss b/src/css/ingame_hud/buildings_toolbar.scss index 3af5edf4..66261892 100644 --- a/src/css/ingame_hud/buildings_toolbar.scss +++ b/src/css/ingame_hud/buildings_toolbar.scss @@ -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; + } } } } diff --git a/src/css/ingame_hud/puzzle_editor_download.scss b/src/css/ingame_hud/puzzle_editor_download.scss new file mode 100644 index 00000000..910247f1 --- /dev/null +++ b/src/css/ingame_hud/puzzle_editor_download.scss @@ -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; + } + } +} diff --git a/src/css/ingame_hud/puzzle_editor_settings.scss b/src/css/ingame_hud/puzzle_editor_settings.scss index 9d093c42..b9291f64 100644 --- a/src/css/ingame_hud/puzzle_editor_settings.scss +++ b/src/css/ingame_hud/puzzle_editor_settings.scss @@ -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; } } } diff --git a/src/css/main.scss b/src/css/main.scss index 7419a5df..c611e905 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -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, diff --git a/src/css/states/puzzle_menu.scss b/src/css/states/puzzle_menu.scss index e2deacf0..f013b0c0 100644 --- a/src/css/states/puzzle_menu.scss +++ b/src/css/states/puzzle_menu.scss @@ -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); } } diff --git a/src/js/game/hud/parts/base_toolbar.js b/src/js/game/hud/parts/base_toolbar.js index 15faad66..d1bd8013 100644 --- a/src/js/game/hud/parts/base_toolbar.js +++ b/src/js/game/hud/parts/base_toolbar.js @@ -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. 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); } } diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index 4b2bafb2..96934585 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -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); diff --git a/src/js/game/hud/parts/building_placer_logic.js b/src/js/game/hud/parts/building_placer_logic.js index 9e91f372..a6bb5391 100644 --- a/src/js/game/hud/parts/building_placer_logic.js +++ b/src/js/game/hud/parts/building_placer_logic.js @@ -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; } diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index b8283d55..67e68209 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -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); } /** diff --git a/src/js/game/hud/parts/puzzle_editor_download.js b/src/js/game/hud/parts/puzzle_editor_download.js new file mode 100644 index 00000000..99f7fbdd --- /dev/null +++ b/src/js/game/hud/parts/puzzle_editor_download.js @@ -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); + } +} diff --git a/src/js/game/hud/parts/puzzle_editor_settings.js b/src/js/game/hud/parts/puzzle_editor_settings.js index 13564da4..dcb54d3c 100644 --- a/src/js/game/hud/parts/puzzle_editor_settings.js +++ b/src/js/game/hud/parts/puzzle_editor_settings.js @@ -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 { ` -
+
@@ -35,7 +32,7 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
-
+
@@ -47,10 +44,10 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
-
- -
+
+
+
` ); @@ -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() { diff --git a/src/js/game/logic.js b/src/js/game/logic.js index 79104958..74caa251 100644 --- a/src/js/game/logic.js +++ b/src/js/game/logic.js @@ -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); diff --git a/src/js/game/modes/puzzle_edit.js b/src/js/game/modes/puzzle_edit.js index e3d2e40d..aacdfd60 100644 --- a/src/js/game/modes/puzzle_edit.js +++ b/src/js/game/modes/puzzle_edit.js @@ -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; } } diff --git a/src/js/game/root.js b/src/js/game/root.js index 64004e9d..6ddda395 100644 --- a/src/js/game/root.js +++ b/src/js/game/root.js @@ -192,6 +192,7 @@ export class GameRoot { // Puzzle mode puzzleComplete: /** @type {TypedSignal<[]>} */ (new Signal()), + testModeChanged: /** @type {TypedSignal<[Boolean]>} */ (new Signal()), }; // RNG's diff --git a/src/js/game/systems/goal_acceptor.js b/src/js/game/systems/goal_acceptor.js index 60d4a984..959cff16 100644 --- a/src/js/game/systems/goal_acceptor.js +++ b/src/js/game/systems/goal_acceptor.js @@ -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; diff --git a/src/js/states/puzzle_menu.js b/src/js/states/puzzle_menu.js index bded8e1e..86e02c92 100644 --- a/src/js/states/puzzle_menu.js +++ b/src/js/states/puzzle_menu.js @@ -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 {

${this.getStateHeaderTitle()}

+
@@ -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); + }); + } + }); + } } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index c37a4610..841ef3eb 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -400,6 +400,11 @@ dialogs: desc: >- Are you sure you want to delete ''? 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