diff --git a/src/css/ingame_hud/mode_menu.scss b/src/css/ingame_hud/mode_menu.scss new file mode 100644 index 00000000..69c0adb5 --- /dev/null +++ b/src/css/ingame_hud/mode_menu.scss @@ -0,0 +1,54 @@ +#ingame_HUD_ModeMenu { + position: absolute; + @include S(bottom, 10px); + @include S(left, 10px); + display: flex; + + backdrop-filter: blur(D(1px)); + flex-direction: column; + align-items: flex-start; + backdrop-filter: blur(D(1px)); + padding: D(3px); + + > button, + > .button { + @include PlainText; + @include IncreasedClickArea(0px); + background: green; + @include S(width, 30px); + @include S(height, 30px); + + pointer-events: all; + cursor: pointer; + position: relative; + transition: all 0.12s ease-in-out; + transition-property: opacity, transform; + + display: inline-flex; + background: center center / 70% no-repeat; + grid-row: 1; + + &.pressed { + transform: scale(0.9) !important; + } + + opacity: 0.7; + &:hover { + opacity: 0.9 !important; + } + + @include DarkThemeInvert; + + &.settings { + & { + /* @load-async */ + background-image: uiResource("icons/settings_menu_settings.png"); + } + } + + &:hover { + opacity: 0.9; + transform: translateY(0); + } + } +} diff --git a/src/css/ingame_hud/mode_menu_back.scss b/src/css/ingame_hud/mode_menu_back.scss new file mode 100644 index 00000000..27d07592 --- /dev/null +++ b/src/css/ingame_hud/mode_menu_back.scss @@ -0,0 +1,35 @@ +#ingame_HUD_ModeMenuBack { + position: absolute; + @include S(top, 10px); + @include S(left, 10px); + + display: flex; + flex-direction: column; + align-items: flex-start; + backdrop-filter: blur(D(1px)); + padding: D(3px); + + > .button { + @include PlainText; + @include IncreasedClickArea(0px); + pointer-events: all; + cursor: pointer; + position: relative; + color: #333438; + transition: all 0.12s ease-in-out; + transition-property: opacity, transform; + + opacity: 0.8; + &:hover { + opacity: 1 !important; + } + + &.pressed { + transform: scale(0.9) !important; + } + + @include DarkThemeOverride { + color: #fff; + } + } +} diff --git a/src/css/ingame_hud/mode_menu_next.scss b/src/css/ingame_hud/mode_menu_next.scss new file mode 100644 index 00000000..45239d85 --- /dev/null +++ b/src/css/ingame_hud/mode_menu_next.scss @@ -0,0 +1,42 @@ +#ingame_HUD_ModeMenuNext { + position: absolute; + @include S(top, 10px); + @include S(right, 10px); + + display: flex; + flex-direction: column; + align-items: flex-end; + backdrop-filter: blur(D(1px)); + padding: D(3px); + + > .button { + @include ButtonText; + @include IncreasedClickArea(0px); + pointer-events: all; + cursor: pointer; + position: relative; + color: #333438; + transition: all 0.12s ease-in-out; + transition-property: opacity, transform; + + opacity: 0.8; + &:hover { + opacity: 1 !important; + } + + &.pressed { + transform: scale(0.9) !important; + } + + @include DarkThemeOverride { + color: #fff; + } + } + + > .content { + @include SuperSmallText; + @include S(font-size, 7px); + @include S(width, 150px); + text-align: right; + } +} diff --git a/src/css/ingame_hud/mode_settings.scss b/src/css/ingame_hud/mode_settings.scss new file mode 100644 index 00000000..8df3f468 --- /dev/null +++ b/src/css/ingame_hud/mode_settings.scss @@ -0,0 +1,47 @@ +#ingame_HUD_ModeSettings { + position: absolute; + background: $ingameHudBg; + @include S(padding, 10px); + @include S(bottom, 50px); + @include S(left, 15px); + + @include SuperSmallText; + color: #eee; + display: flex; + flex-direction: column; + + > .section { + > label { + text-transform: uppercase; + } + + .plusMinus { + @include S(margin-top, 5px); + display: grid; + grid-template-columns: 1fr auto auto auto; + align-items: center; + @include S(grid-gap, 5px); + + label { + @include S(margin-right, 10px); + } + + button { + @include PlainText; + @include S(padding, 0); + display: flex; + align-items: center; + justify-content: center; + @include S(width, 15px); + @include S(height, 15px); + @include IncreasedClickArea(0px); + } + + + .value { + text-align: center; + @include S(min-width, 15px); + } + } + } +} diff --git a/src/css/main.scss b/src/css/main.scss index 35d54e23..f857050e 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -55,6 +55,10 @@ @import "ingame_hud/sandbox_controller"; @import "ingame_hud/standalone_advantages"; @import "ingame_hud/cat_memes"; +@import "ingame_hud/mode_menu_back"; +@import "ingame_hud/mode_menu_next"; +@import "ingame_hud/mode_menu"; +@import "ingame_hud/mode_settings"; // prettier-ignore $elements: @@ -71,6 +75,10 @@ ingame_HUD_PlacerVariants, ingame_HUD_PinnedShapes, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, +ingame_HUD_ModeMenuBack, +ingame_HUD_ModeMenuNext, +ingame_HUD_ModeMenu, +ingame_HUD_ModeSettings, ingame_HUD_Notifications, ingame_HUD_DebugInfo, ingame_HUD_EntityDebugger, @@ -113,6 +121,8 @@ body.uiHidden { #ingame_HUD_PlacementHints, #ingame_HUD_GameMenu, #ingame_HUD_PinnedShapes, + #ingame_HUD_ModeMenuBack, + #ingame_HUD_ModeMenuNext, #ingame_HUD_Notifications, #ingame_HUD_TutorialHints, #ingame_HUD_Waypoints, diff --git a/src/js/game/game_mode.js b/src/js/game/game_mode.js index f3aea0f5..f2b46c55 100644 --- a/src/js/game/game_mode.js +++ b/src/js/game/game_mode.js @@ -118,6 +118,16 @@ export class GameMode extends BasicSerializableObject { return false; } + /** @returns {boolean} */ + isZoneRestricted() { + return false; + } + + /** @returns {boolean} */ + isBoundaryRestricted() { + return false; + } + /** @returns {number} */ getMinimumZoom() { return 0.1; @@ -143,6 +153,15 @@ export class GameMode extends BasicSerializableObject { return null; } + /** + * @param {number} w + * @param {number} h + */ + expandZone(w = 0, h = 0) { + abstract; + return; + } + /** @returns {?Rectangle} */ getBounds() { return null; diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 17741fc7..0c76437e 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -49,6 +49,10 @@ import { HUDStandaloneAdvantages } from "./parts/standalone_advantages"; import { HUDCatMemes } from "./parts/cat_memes"; import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer"; import { HUDConstantSignalEdit } from "./parts/constant_signal_edit"; +import { HUDModeMenuBack } from "./parts/mode_menu_back"; +import { HUDModeMenuNext } from "./parts/mode_menu_next"; +import { HUDModeMenu } from "./parts/mode_menu"; +import { HUDModeSettings } from "./parts/mode_settings"; export class GameHUD { /** @@ -88,6 +92,10 @@ export class GameHUD { wireInfo: HUDWireInfo, leverToggle: HUDLeverToggle, constantSignalEdit: HUDConstantSignalEdit, + modeMenuBack: HUDModeMenuBack, + modeMenuNext: HUDModeMenuNext, + modeMenu: HUDModeMenu, + modeSettings: HUDModeSettings, // Must always exist pinnedShapes: HUDPinnedShapes, @@ -112,7 +120,9 @@ export class GameHUD { }); if (!IS_MOBILE) { - this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root); + if (!this.root.gameMode.isHudPartExcluded(HUDKeybindingOverlay.name)) { + this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root); + } } if (G_IS_DEV && globalConfig.debug.enableEntityInspector) { diff --git a/src/js/game/hud/parts/mode_menu.js b/src/js/game/hud/parts/mode_menu.js new file mode 100644 index 00000000..cb05d6a2 --- /dev/null +++ b/src/js/game/hud/parts/mode_menu.js @@ -0,0 +1,17 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { makeDiv } from "../../../core/utils"; + +export class HUDModeMenu extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv(parent, "ingame_HUD_ModeMenu"); + + this.settingsButton = makeDiv(this.element, null, ["button", "settings"]); + this.trackClicks(this.settingsButton, this.openSettings); + } + + openSettings() { + this.root.hud.parts.modeSettings.toggle(); + } + + initialize() {} +} diff --git a/src/js/game/hud/parts/mode_menu_back.js b/src/js/game/hud/parts/mode_menu_back.js new file mode 100644 index 00000000..ff729706 --- /dev/null +++ b/src/js/game/hud/parts/mode_menu_back.js @@ -0,0 +1,23 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; + +export class HUDModeMenuBack extends BaseHUDPart { + createElements(parent) { + const key = this.root.gameMode.getId(); + + this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack"); + this.button = document.createElement("button"); + this.button.classList.add("button"); + this.button.textContent = "⬅ " + T.ingame.modeMenu[key].back.title; + this.element.appendChild(this.button); + + this.trackClicks(this.button, this.back); + } + + initialize() {} + + back() { + this.root.gameState.goBackToMenu(); + } +} diff --git a/src/js/game/hud/parts/mode_menu_next.js b/src/js/game/hud/parts/mode_menu_next.js new file mode 100644 index 00000000..a3698269 --- /dev/null +++ b/src/js/game/hud/parts/mode_menu_next.js @@ -0,0 +1,23 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; + +export class HUDModeMenuNext extends BaseHUDPart { + createElements(parent) { + const key = this.root.gameMode.getId(); + + this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext"); + this.button = document.createElement("button"); + this.button.classList.add("button"); + this.button.textContent = T.ingame.modeMenu[key].next.title + " ➡ "; + this.element.appendChild(this.button); + + this.content = makeDiv(this.element, null, ["content"], T.ingame.modeMenu[key].next.desc); + + this.trackClicks(this.button, this.next); + } + + initialize() {} + + next() {} +} diff --git a/src/js/game/hud/parts/mode_settings.js b/src/js/game/hud/parts/mode_settings.js new file mode 100644 index 00000000..a1dd220a --- /dev/null +++ b/src/js/game/hud/parts/mode_settings.js @@ -0,0 +1,67 @@ +import { makeDiv } from "../../../core/utils"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; + +export class HUDModeSettings extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv(parent, "ingame_HUD_ModeSettings"); + + const bind = (selector, handler) => this.trackClicks(this.element.querySelector(selector), handler); + + if (this.root.gameMode.hasZone()) { + this.zone = makeDiv( + this.element, + null, + ["section", "zone"], + ` + + +
` + ); + + bind(".zoneWidth .minus", () => this.modifyZone(-1, 0)); + bind(".zoneWidth .plus", () => this.modifyZone(1, 0)); + bind(".zoneHeight .minus", () => this.modifyZone(0, -1)); + bind(".zoneHeight .plus", () => this.modifyZone(0, 1)); + } + } + + initialize() { + this.visible = false; + this.domAttach = new DynamicDomAttach(this.root, this.element); + this.updateZoneValues(); + } + + modifyZone(width, height) { + this.root.gameMode.expandZone(width, height); + this.updateZoneValues(); + } + + updateZoneValues() { + const zone = this.root.gameMode.getZone(); + this.element.querySelector(".zoneWidth > .value").textContent = String(zone.w); + this.element.querySelector(".zoneHeight > .value").textContent = String(zone.h); + } + + toggle() { + this.visible = !this.visible; + } + + update() { + this.domAttach.update(this.visible); + } +} diff --git a/src/js/game/modes/puzzle.js b/src/js/game/modes/puzzle.js index 4f07abe7..2f7f50ca 100644 --- a/src/js/game/modes/puzzle.js +++ b/src/js/game/modes/puzzle.js @@ -5,9 +5,11 @@ import { GameRoot } from "../root"; import { Rectangle } from "../../core/rectangle"; import { types } from "../../savegame/serialization"; import { enumGameModeTypes, GameMode } from "../game_mode"; +import { HUDGameMenu } from "../hud/parts/game_menu"; import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial"; -import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; +import { HUDKeybindingOverlay } from "../hud/parts/keybinding_overlay"; import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints"; +import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; import { HUDWaypoints } from "../hud/parts/waypoints"; export class PuzzleGameMode extends GameMode { @@ -30,7 +32,9 @@ export class PuzzleGameMode extends GameMode { const data = this.getSaveData(); this.setHudParts({ + [HUDGameMenu.name]: false, [HUDInteractiveTutorial.name]: false, + [HUDKeybindingOverlay.name]: false, [HUDPartTutorialHints.name]: false, [HUDPinnedShapes.name]: false, [HUDWaypoints.name]: false, diff --git a/src/js/game/modes/puzzle_edit.js b/src/js/game/modes/puzzle_edit.js index cf3ad301..f927b001 100644 --- a/src/js/game/modes/puzzle_edit.js +++ b/src/js/game/modes/puzzle_edit.js @@ -14,15 +14,39 @@ export class PuzzleEditGameMode extends PuzzleGameMode { return enumGameModeIds.puzzleEdit; } + static getSchema() { + return {}; + } + /** @param {GameRoot} root */ constructor(root) { super(root); + this.playtest = false; + this.setBuildings({ [MetaConstantProducerBuilding.name]: true, - // [MetaBeltBuilding.name]: true, [MetaGoalAcceptorBuilding.name]: true, - // [MetaItemProducerBuilding.name]: true, }); } + + isZoneRestricted() { + return !this.playtest; + } + + isBoundaryRestricted() { + return this.playtest; + } + + expandZone(w = 0, h = 0) { + if (this.zoneWidth + w > 0) { + this.zoneWidth += w; + } + + if (this.zoneHeight + h > 0) { + this.zoneHeight += h; + } + + this.zone = this.createCenteredRectangle(this.zoneWidth, this.zoneHeight); + } } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index c2923d13..1f3626ef 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -7,6 +7,10 @@ import { findNiceIntegerValue } from "../../core/utils"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; import { MetaItemProducerBuilding } from "../buildings/item_producer"; +import { HUDModeMenuBack } from "../hud/parts/mode_menu_back"; +import { HUDModeMenuNext } from "../hud/parts/mode_menu_next"; +import { HUDModeMenu } from "../hud/parts/mode_menu"; +import { HUDModeSettings } from "../hud/parts/mode_settings"; import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode"; import { ShapeDefinition } from "../shape_definition"; import { enumHubGoalRewards } from "../tutorial_goals"; @@ -493,6 +497,13 @@ export class RegularGameMode extends GameMode { constructor(root) { super(root); + this.setHudParts({ + [HUDModeMenuBack.name]: false, + [HUDModeMenuNext.name]: false, + [HUDModeMenu.name]: false, + [HUDModeSettings.name]: false, + }); + this.setBuildings({ [MetaConstantProducerBuilding.name]: false, [MetaGoalAcceptorBuilding.name]: false, diff --git a/src/js/game/systems/zone.js b/src/js/game/systems/zone.js index f2fecd3f..3dd68804 100644 --- a/src/js/game/systems/zone.js +++ b/src/js/game/systems/zone.js @@ -24,11 +24,18 @@ export class ZoneSystem extends GameSystem { return; } - const zone = this.root.gameMode.getZone().expandedInAllDirections(-1); + const mode = this.root.gameMode; + const zone = mode.getZone().expandedInAllDirections(-1); const transformed = staticComp.getTileSpaceBounds(); if (zone.containsRect(transformed)) { - return STOP_PROPAGATION; + if (mode.isZoneRestricted()) { + return STOP_PROPAGATION; + } + } else { + if (mode.isBoundaryRestricted()) { + return STOP_PROPAGATION; + } } } @@ -38,15 +45,17 @@ export class ZoneSystem extends GameSystem { * @param {MapChunkView} chunk */ drawChunk(parameters, chunk) { - const zone = this.root.gameMode.getZone().allScaled(globalConfig.tileSize); + const mode = this.root.gameMode; + const zone = mode.getZone().allScaled(globalConfig.tileSize); const context = parameters.context; context.globalAlpha = 0.1; context.fillStyle = THEME.map.zone.background; context.fillRect(zone.x, zone.y, zone.w, zone.h); - context.globalAlpha = 0.9; + context.globalAlpha = 1; context.strokeStyle = THEME.map.zone.border; + context.lineWidth = 2; context.strokeRect(zone.x, zone.y, zone.w, zone.h); context.globalAlpha = 1; diff --git a/translations/base-en.yaml b/translations/base-en.yaml index e735c2a3..a2bb4fde 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -483,6 +483,24 @@ ingame: title: Support me desc: I develop the game in my spare time! + modeMenu: + puzzleEditMode: + back: + title: Main Menu + next: + title: Playtest + desc: You will have to complete the puzzle before being able to publish it + puzzleEditTestMode: + back: + title: Edit + next: + title: Publish + puzzlePlayMode: + back: + title: Puzzle Menu + next: + title: Next + # All shop upgrades shopUpgrades: belt: