mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-16 19:51:50 +00:00
Merge 9f47684d66 into a7a2aad2b6
This commit is contained in:
commit
cc8b61eab3
BIN
res/ui/icons/upload.png
Normal file
BIN
res/ui/icons/upload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/css/ingame_hud/puzzle_import_export.scss
Normal file
44
src/css/ingame_hud/puzzle_import_export.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
> .section {
|
||||
display: grid;
|
||||
@include S(grid-gap, 5px);
|
||||
@include S(grid-gap, 7px);
|
||||
grid-auto-flow: row;
|
||||
|
||||
> button {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
132
src/js/game/hud/parts/puzzle_import_export.js
Normal file
132
src/js/game/hud/parts/puzzle_import_export.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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,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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -609,6 +609,7 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
const savegame = Savegame.createPuzzleSavegame(this.app);
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzleEdit,
|
||||
gameModeParameters: {},
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user