1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-16 19:51:50 +00:00
This commit is contained in:
Sense101 2021-10-03 11:23:04 +01:00 committed by GitHub
commit cc8b61eab3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 473 additions and 139 deletions

BIN
res/ui/icons/upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -49,96 +49,100 @@
}
.building {
display: flex;
@include S(width, 40px);
position: relative;
@include S(height, 40px);
.icon {
color: $accentColorDark;
&:not(.hidden) {
display: flex;
flex-direction: column-reverse;
@include S(width, 40px);
position: relative;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
@include S(border-radius, $globalBorderRadius);
background: center center / 70% no-repeat;
}
&:not(.unlocked) {
@include S(width, 25px);
@include S(height, 40px);
.icon {
opacity: 0.15;
color: $accentColorDark;
display: flex;
flex-direction: column-reverse;
position: relative;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
@include S(border-radius, $globalBorderRadius);
background: center center / 70% no-repeat;
}
&.editor {
&:not(.unlocked) {
@include S(width, 25px);
.icon {
pointer-events: all;
cursor: pointer;
&:hover {
background-color: rgba(22, 30, 68, 0.1);
opacity: 0.15;
}
&.editor {
.icon {
pointer-events: all;
cursor: pointer;
&:hover {
background-color: rgba(22, 30, 68, 0.1);
}
}
}
&:not(.editor) {
.icon {
background-image: uiResource("locked_building.png") !important;
}
}
}
&:not(.editor) {
&.unlocked {
.icon {
background-image: uiResource("locked_building.png") !important;
pointer-events: all;
transition: all 50ms ease-in-out;
transition-property: background-color, transform;
cursor: pointer;
&:hover {
background-color: rgba(30, 40, 90, 0.1);
}
&.pressed {
transform: scale(0.9) !important;
}
}
}
}
&.selected {
// transform: scale(1.05);
background-color: rgba(lighten($colorBlueBright, 9), 0.4);
@include S(border-radius, 2px);
&.unlocked {
.icon {
pointer-events: all;
transition: all 50ms ease-in-out;
transition-property: background-color, transform;
cursor: pointer;
&:hover {
background-color: rgba(30, 40, 90, 0.1);
.keybinding {
color: #111;
}
}
&.pressed {
transform: scale(0.9) !important;
}
}
&.selected {
// transform: scale(1.05);
background-color: rgba(lighten($colorBlueBright, 9), 0.4);
@include S(border-radius, 2px);
.puzzle-lock {
&.active {
& {
/* @load-async */
background: uiResource("locked_building.png") center center / 90% no-repeat;
}
.keybinding {
color: #111;
}
}
display: grid;
grid-auto-flow: column;
.puzzle-lock {
& {
/* @load-async */
background: uiResource("locked_building.png") center center / 90% no-repeat;
}
position: absolute;
@include S(top, -15px);
left: 50%;
transform: translateX(-50%) !important;
transition: all 0.12s ease-in-out;
transition-property: opacity, transform;
display: grid;
grid-auto-flow: column;
cursor: pointer;
pointer-events: all;
position: absolute;
@include S(top, -15px);
left: 50%;
transform: translateX(-50%) !important;
transition: all 0.12s ease-in-out;
transition-property: opacity, transform;
@include S(width, 12px);
@include S(height, 12px);
cursor: pointer;
pointer-events: all;
@include S(width, 12px);
@include S(height, 12px);
&:hover {
opacity: 0.5;
&:hover {
opacity: 0.5;
}
}
}
}
}

View File

@ -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;
@ -12,6 +12,17 @@
@include S(border-radius, $globalBorderRadius);
> .section {
.disabled {
transition: opacity 0.12s ease-in-out;
opacity: 0.6;
button {
pointer-events: none;
}
}
:not(.disabled) {
transition: opacity 0.12s ease-in-out;
}
> label {
text-transform: uppercase;
}
@ -44,7 +55,7 @@
}
}
> .buttons {
> .mainButtons {
> .buttonBar {
display: flex;
align-items: center;
@ -57,14 +68,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;
}
}
}

View File

@ -0,0 +1,44 @@
#ingame_HUD_PuzzleImportExport {
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, 20px);
@include S(height, 20px);
margin: 8px 5px;
@include DarkThemeInvert;
opacity: 1;
&:hover {
opacity: 0.9 !important;
}
&.pressed {
transform: scale(0.95) !important;
}
&.import {
background: uiResource("icons/upload.png") center center / D(15px) no-repeat !important;
}
&.export {
background: uiResource("icons/download.png") center center / D(15px) no-repeat !important;
}
}
}

View File

@ -13,7 +13,7 @@
> .section {
display: grid;
@include S(grid-gap, 5px);
@include S(grid-gap, 7px);
grid-auto-flow: row;
> button {

View File

@ -66,6 +66,7 @@
@import "ingame_hud/puzzle_play_metadata";
@import "ingame_hud/puzzle_complete_notification";
@import "ingame_hud/puzzle_next";
@import "ingame_hud/puzzle_import_export";
// prettier-ignore
$elements:
@ -86,6 +87,8 @@ ingame_HUD_KeybindingOverlay,
ingame_HUD_PuzzleBackToMenu,
ingame_HUD_PuzzleNextPuzzle,
ingame_HUD_PuzzleEditorReview,
ingame_HUD_PuzzleImportExport,
ingame_HUD_PuzzleNextPuzzle,
ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle,
ingame_HUD_PuzzleEditorSettings,
@ -138,6 +141,11 @@ body.uiHidden {
#ingame_HUD_PuzzleBackToMenu,
#ingame_HUD_PuzzleNextPuzzle,
#ingame_HUD_PuzzleEditorReview,
#ingame_HUD_PuzzleEditorSettings,
#ingame_HUD_PuzzlePlaySettings,
#ingame_HUD_PuzzleEditorControls,
#ingame_HUD_PuzzleImportExport,
#ingame_HUD_PuzzlePlayMetadata,
#ingame_HUD_Notifications,
#ingame_HUD_TutorialHints,
#ingame_HUD_Waypoints,

View File

@ -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);
}
}

View File

@ -19,7 +19,8 @@ export class MetaBlockBuilding extends MetaBuilding {
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
const settings = root.hud.parts.puzzleEditorSettings;
return settings ? !settings.getIsTestMode() : false;
}
/**

View File

@ -23,7 +23,8 @@ export class MetaConstantProducerBuilding extends MetaBuilding {
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
const settings = root.hud.parts.puzzleEditorSettings;
return settings ? !settings.getIsTestMode() : false;
}
/**

View File

@ -23,7 +23,8 @@ export class MetaGoalAcceptorBuilding extends MetaBuilding {
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
const settings = root.hud.parts.puzzleEditorSettings;
return settings ? !settings.getIsTestMode() : false;
}
/**

View File

@ -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,15 @@ 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"]);
const settings = this.root.hud.parts.puzzleEditorSettings;
if (settings && !settings.getIsTestMode()) {
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 +155,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 +261,12 @@ 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();
const settings = this.root.hud.parts.puzzleEditorSettings;
if (settings && !settings.getIsTestMode()) {
handle.puzzleLocked = false;
handle.element.classList.toggle("unlocked", false);
this.root.soundProxy.playUiClick();
}
return;
}
@ -274,9 +285,28 @@ export class HUDBaseToolbar extends BaseHUDPart {
}
/**
* @param {MetaBuilding} metaBuilding
* @param {boolean} testMode
*/
toggleBuildingLock(metaBuilding) {
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("hidden", testMode);
}
}
/**
* @param {MetaBuilding} metaBuilding
* @param {boolean | null} force
*/
toggleBuildingLock(metaBuilding, force = null) {
if (!this.visibilityCondition()) {
// Not active
return;
@ -288,9 +318,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
}
const handle = this.buildingHandles[metaBuilding.getId()];
handle.puzzleLocked = !handle.puzzleLocked;
if (force != null) {
handle.puzzleLocked = force;
} else {
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 +345,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);
}
}

View File

@ -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);

View File

@ -130,6 +130,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
this.root.signals.storyGoalCompleted.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.testModeChanged.add(this.abortPlacement, this);
// MOUSE BINDINGS
this.root.camera.downPreHandler.add(this.onMouseDown, this);
@ -384,8 +385,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;
}

View File

@ -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);
}
/**

View File

@ -1,6 +1,5 @@
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";
@ -8,11 +7,10 @@ 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 +25,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 +33,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 +45,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 +58,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 +71,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(".section > label").classList.toggle("disabled", this.testMode);
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);
if (this.testMode) {
const newSolution = [];
for (const entity of this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
if (this.isExcludedEntity(entity)) {
continue;
}
newSolution.push(entity.clone());
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
}
if (
[MetaGoalAcceptorBuilding, MetaConstantProducerBuilding, MetaBlockBuilding]
.map(metaClass => gMetaBuildingRegistry.findByClass(metaClass).id)
.includes(staticComp.getMetaBuilding().id)
) {
continue;
}
this.root.entityMgr.processDestroyList();
this.storedSolution = newSolution;
} else if (this.storedSolution.length) {
this.root.logic.performBulkOperation(() => {
for (const entity of this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
if (this.isExcludedEntity(entity)) continue;
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
}
this.root.entityMgr.processDestroyList();
for (let i = 0; i < this.storedSolution.length; ++i) {
const entity = this.storedSolution[i];
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.entityMgr.processDestroyList();
}
trim() {
@ -228,4 +271,21 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
this.element.querySelector(".zoneWidth > .value").textContent = String(mode.zoneWidth);
this.element.querySelector(".zoneHeight > .value").textContent = String(mode.zoneHeight);
}
getIsTestMode() {
return this.testMode;
}
isExcludedEntity(entity) {
const metaBuilding = entity.components.StaticMapEntity.getMetaBuilding();
if (
[MetaConstantProducerBuilding, MetaBlockBuilding, MetaGoalAcceptorBuilding]
.map(metaClass => gMetaBuildingRegistry.findByClass(metaClass).id)
.includes(metaBuilding.id)
) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,132 @@
import { gMetaBuildingRegistry } from "../../../core/global_registries";
import { ReadWriteProxy } from "../../../core/read_write_proxy";
import { generateFileDownload, makeDiv, startFileChoose, waitNextFrame } from "../../../core/utils";
import { PuzzleSerializer } from "../../../savegame/puzzle_serializer";
import { T } from "../../../translations";
import { GoalAcceptorComponent } from "../../components/goal_acceptor";
import { StaticMapEntityComponent } from "../../components/static_map_entity";
import { PuzzleGameMode } from "../../modes/puzzle";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzleImportExport extends BaseHUDPart {
constructor(root) {
super(root);
}
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleImportExport");
this.importButton = document.createElement("button");
this.importButton.classList.add("button", "import");
this.element.appendChild(this.importButton);
this.exportButton = document.createElement("button");
this.exportButton.classList.add("button", "export");
this.element.appendChild(this.exportButton);
this.trackClicks(this.importButton, this.importPuzzle);
this.trackClicks(this.exportButton, () => {
const { yes } = this.root.hud.parts.dialogs.showWarning(
T.dialogs.puzzleExport.title,
T.dialogs.puzzleExport.desc,
["no", "yes:good:enter"]
);
yes.add(() => this.exportPuzzle());
});
}
initialize() {}
importPuzzle() {
startFileChoose(".bin").then(file => {
if (file) {
const closeLoader = this.root.hud.parts.dialogs.showLoadingDialog("Importing Puzzle");
waitNextFrame().then(() => {
const reader = new FileReader();
reader.addEventListener("load", event => {
const fileContents = String(event.target.result);
/** @type {import("../../../savegame/savegame_typedefs").PuzzleGameData} */
let gameData;
try {
gameData = ReadWriteProxy.deserializeObject(fileContents);
} catch (err) {
closeLoader();
this.root.hud.parts.dialogs.showWarning(T.global.error, String(err));
return;
}
const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode);
let errorText;
try {
// set excluded buildings first so if we get an error we haven't removed buildings yet
const toolbar = this.root.hud.parts.buildingsToolbar;
const handles = toolbar.buildingHandles;
const ids = gMetaBuildingRegistry.getAllIds();
for (let i = 0; i < ids.length; ++i) {
const handle = handles[ids[i]];
if (handle && !toolbar.inRequiredBuildings(handle.metaBuilding)) {
const locked = gameData.excludedBuildings.includes(ids[i]);
toolbar.toggleBuildingLock(handle.metaBuilding, locked);
}
}
for (const entity of this.root.entityMgr.getAllWithComponent(
StaticMapEntityComponent
)) {
this.root.map.removeStaticEntity(entity);
this.root.entityMgr.destroyEntity(entity);
}
this.root.entityMgr.processDestroyList();
mode.zoneWidth = gameData.bounds.w;
mode.zoneHeight = gameData.bounds.h;
this.root.hud.parts.puzzleEditorSettings.updateZoneValues();
errorText = new PuzzleSerializer().deserializePuzzle(this.root, gameData);
} catch (ex) {
errorText = ex.message || ex;
}
if (errorText) {
this.root.hud.parts.dialogs.showWarning(
T.dialogs.puzzleLoadError.title,
T.dialogs.puzzleLoadError.desc + " " + errorText
);
} else {
this.root.hud.parts.dialogs.showInfo(
T.dialogs.puzzleImport.title,
T.dialogs.puzzleImport.desc
);
}
closeLoader();
});
reader.readAsText(file);
});
}
});
}
exportPuzzle() {
// Make sure all acceptors have an item
for (const entity of this.root.entityMgr.getAllWithComponent(GoalAcceptorComponent)) {
const goalComp = entity.components.GoalAcceptor;
if (!goalComp.item) {
this.root.hud.parts.dialogs.showWarning(
T.puzzleMenu.validation.title,
T.puzzleMenu.validation.goalAcceptorNoItem
);
return;
}
}
const serialized = new PuzzleSerializer().generateDumpFromGameRoot(this.root);
const data = ReadWriteProxy.serializeObject(serialized);
const filename = "puzzle.bin";
generateFileDownload(filename, data);
}
}

View File

@ -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);

View File

@ -22,6 +22,10 @@ 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 { HUDPuzzleImportExport } from "../hud/parts/puzzle_import_export";
const logger = createLogger("puzzle-edit");
export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() {
@ -32,7 +36,9 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
return {};
}
/** @param {GameRoot} root */
/**
* @param {GameRoot} root
*/
constructor(root) {
super(root);
@ -58,6 +64,7 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls;
this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview;
this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings;
this.additionalHudParts.puzzleEditorDownload = HUDPuzzleImportExport;
}
getIsEditor() {

View File

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

View File

@ -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;

View File

@ -589,7 +589,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
);
}
if (this.root.gameMode.getIsEditor()) {
const settings = this.root.hud.parts.puzzleEditorSettings;
if (settings && !settings.getIsTestMode()) {
// while playing in editor, assign the item
goalComp.item = item;
}

View File

@ -609,6 +609,7 @@ export class PuzzleMenuState extends TextualGameState {
const savegame = Savegame.createPuzzleSavegame(this.app);
this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzleEdit,
gameModeParameters: {},
savegame,
});
}

View File

@ -207,6 +207,8 @@ dialogs:
retry: Retry
continue: Continue
playOffline: Play Offline
yes: Yes
no: No
importSavegameError:
title: Import Error
@ -418,6 +420,16 @@ dialogs:
desc: >-
Are you sure you want to delete '<title>'? This can not be undone!
puzzleImport:
title: Puzzle Imported
desc: >-
Your puzzle has been successfully imported.
puzzleExport:
title: Export 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
@ -652,6 +664,8 @@ ingame:
clearItems: Clear Items
clearBuildings: Clear Buildings
resetPuzzle: Reset Puzzle
enableTestMode: Enable Test Mode
disableTestMode: Disable Test Mode
share: Share
report: Report