From 72530005cbc8fc521926810c946d249f19547b08 Mon Sep 17 00:00:00 2001 From: tobspr Date: Fri, 30 Apr 2021 20:06:37 +0200 Subject: [PATCH] Puzzle mode, Refactor hud --- src/css/ingame_hud/mode_menu.scss | 54 ------- ...enu_back.scss => puzzle_back_to_menu.scss} | 4 +- src/css/ingame_hud/puzzle_dlc_logo.scss | 6 +- .../ingame_hud/puzzle_editor_controls.scss | 6 +- ...nu_next.scss => puzzle_editor_review.scss} | 4 +- ...tings.scss => puzzle_editor_settings.scss} | 13 +- src/css/ingame_hud/puzzle_play_metadata.scss | 19 +++ src/css/main.scss | 20 +-- src/js/core/config.js | 2 + src/js/core/rectangle.js | 18 +++ src/js/game/buildings/balancer.js | 4 + src/js/game/buildings/belt.js | 3 + src/js/game/buildings/cutter.js | 3 + src/js/game/buildings/filter.js | 3 + src/js/game/buildings/miner.js | 3 + src/js/game/buildings/mixer.js | 3 + src/js/game/buildings/painter.js | 3 + src/js/game/buildings/rotater.js | 3 + src/js/game/buildings/stacker.js | 3 + src/js/game/buildings/underground_belt.js | 12 +- src/js/game/core.js | 6 +- src/js/game/game_mode.js | 22 ++- src/js/game/hud/hud.js | 122 ++------------- src/js/game/hud/parts/building_placer.js | 12 +- src/js/game/hud/parts/mode_menu.js | 17 --- src/js/game/hud/parts/mode_settings.js | 73 --------- src/js/game/hud/parts/pinned_shapes.js | 2 +- ...de_menu_back.js => puzzle_back_to_menu.js} | 4 +- .../game/hud/parts/puzzle_editor_controls.js | 12 +- ...zzle_review.js => puzzle_editor_review.js} | 4 +- .../game/hud/parts/puzzle_editor_settings.js | 141 ++++++++++++++++++ src/js/game/hud/parts/puzzle_play_metadata.js | 15 ++ src/js/game/hud/parts/sandbox_controller.js | 17 +-- src/js/game/modes/puzzle.js | 36 ++--- src/js/game/modes/puzzle_edit.js | 34 +---- src/js/game/modes/puzzle_play.js | 55 ++++++- src/js/game/modes/regular.js | 81 ++++++++-- src/js/game/systems/constant_signal.js | 2 +- src/js/game/systems/zone.js | 12 +- src/js/game/themes/light.json | 4 +- src/js/savegame/puzzle_serializer.js | 21 ++- src/js/savegame/savegame_serializer.js | 12 +- src/js/savegame/savegame_typedefs.js | 42 ++++++ src/js/states/ingame.js | 3 + src/js/states/puzzle_menu.js | 62 ++++++-- translations/base-en.yaml | 24 ++- 46 files changed, 592 insertions(+), 429 deletions(-) delete mode 100644 src/css/ingame_hud/mode_menu.scss rename src/css/ingame_hud/{mode_menu_back.scss => puzzle_back_to_menu.scss} (93%) rename src/css/ingame_hud/{mode_menu_next.scss => puzzle_editor_review.scss} (94%) rename src/css/ingame_hud/{mode_settings.scss => puzzle_editor_settings.scss} (76%) create mode 100644 src/css/ingame_hud/puzzle_play_metadata.scss delete mode 100644 src/js/game/hud/parts/mode_menu.js delete mode 100644 src/js/game/hud/parts/mode_settings.js rename src/js/game/hud/parts/{mode_menu_back.js => puzzle_back_to_menu.js} (78%) rename src/js/game/hud/parts/{mode_puzzle_review.js => puzzle_editor_review.js} (98%) create mode 100644 src/js/game/hud/parts/puzzle_editor_settings.js create mode 100644 src/js/game/hud/parts/puzzle_play_metadata.js diff --git a/src/css/ingame_hud/mode_menu.scss b/src/css/ingame_hud/mode_menu.scss deleted file mode 100644 index 69c0adb5..00000000 --- a/src/css/ingame_hud/mode_menu.scss +++ /dev/null @@ -1,54 +0,0 @@ -#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/puzzle_back_to_menu.scss similarity index 93% rename from src/css/ingame_hud/mode_menu_back.scss rename to src/css/ingame_hud/puzzle_back_to_menu.scss index 9f0a6c81..07c15418 100644 --- a/src/css/ingame_hud/mode_menu_back.scss +++ b/src/css/ingame_hud/puzzle_back_to_menu.scss @@ -1,7 +1,7 @@ -#ingame_HUD_ModeMenuBack { +#ingame_HUD_PuzzleBackToMenu { position: absolute; @include S(top, 10px); - @include S(left, 10px); + @include S(left, 0px); display: flex; flex-direction: column; diff --git a/src/css/ingame_hud/puzzle_dlc_logo.scss b/src/css/ingame_hud/puzzle_dlc_logo.scss index fc3b4c76..72d87e51 100644 --- a/src/css/ingame_hud/puzzle_dlc_logo.scss +++ b/src/css/ingame_hud/puzzle_dlc_logo.scss @@ -1,9 +1,9 @@ #ingame_HUD_PuzzleDLCLogo { position: absolute; - @include S(width, 150px); + @include S(width, 120px); @include S(height, 40px); - @include S(left, 50px); - @include S(top, 10px); + @include S(left, 40px); + @include S(top, 7px); & { /* @load-async */ diff --git a/src/css/ingame_hud/puzzle_editor_controls.scss b/src/css/ingame_hud/puzzle_editor_controls.scss index eb402aa2..85acba6a 100644 --- a/src/css/ingame_hud/puzzle_editor_controls.scss +++ b/src/css/ingame_hud/puzzle_editor_controls.scss @@ -11,13 +11,17 @@ > span { @include S(margin-bottom, 10px); + + strong { + font-weight: bold; + } } } #ingame_HUD_PuzzleEditorTitle { position: absolute; - @include S(top, 23px); + @include S(top, 18px); left: 50%; transform: translateX(-50%); text-transform: uppercase; diff --git a/src/css/ingame_hud/mode_menu_next.scss b/src/css/ingame_hud/puzzle_editor_review.scss similarity index 94% rename from src/css/ingame_hud/mode_menu_next.scss rename to src/css/ingame_hud/puzzle_editor_review.scss index 02a6ec66..73f87d7d 100644 --- a/src/css/ingame_hud/mode_menu_next.scss +++ b/src/css/ingame_hud/puzzle_editor_review.scss @@ -1,6 +1,6 @@ -#ingame_HUD_PuzzleReview { +#ingame_HUD_PuzzleEditorReview { position: absolute; - @include S(top, 15px); + @include S(top, 17px); @include S(right, 10px); display: flex; diff --git a/src/css/ingame_hud/mode_settings.scss b/src/css/ingame_hud/puzzle_editor_settings.scss similarity index 76% rename from src/css/ingame_hud/mode_settings.scss rename to src/css/ingame_hud/puzzle_editor_settings.scss index 8df3f468..6bea0a23 100644 --- a/src/css/ingame_hud/mode_settings.scss +++ b/src/css/ingame_hud/puzzle_editor_settings.scss @@ -1,14 +1,15 @@ -#ingame_HUD_ModeSettings { +#ingame_HUD_PuzzleEditorSettings { position: absolute; background: $ingameHudBg; @include S(padding, 10px); - @include S(bottom, 50px); - @include S(left, 15px); + @include S(bottom, 60px); + @include S(left, 10px); @include SuperSmallText; color: #eee; display: flex; flex-direction: column; + @include S(border-radius, $globalBorderRadius); > .section { > label { @@ -37,11 +38,15 @@ @include IncreasedClickArea(0px); } - .value { text-align: center; @include S(min-width, 15px); } } + + > .buttons > button.trim { + @include S(margin-top, 10px); + @include SuperSmallText; + } } } diff --git a/src/css/ingame_hud/puzzle_play_metadata.scss b/src/css/ingame_hud/puzzle_play_metadata.scss new file mode 100644 index 00000000..4cefcb52 --- /dev/null +++ b/src/css/ingame_hud/puzzle_play_metadata.scss @@ -0,0 +1,19 @@ +#ingame_HUD_PuzzleEditorMetadata { + position: absolute; + + @include S(top, 70px); + @include S(left, 10px); + + display: flex; + flex-direction: column; + @include SuperDuperSmallText; + @include S(width, 200px); + + > span { + @include S(margin-bottom, 10px); + + strong { + font-weight: bold; + } + } +} diff --git a/src/css/main.scss b/src/css/main.scss index 9c027403..1c70123c 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -56,12 +56,12 @@ @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"; +@import "ingame_hud/puzzle_back_to_menu"; +@import "ingame_hud/puzzle_editor_review"; @import "ingame_hud/puzzle_dlc_logo"; @import "ingame_hud/puzzle_editor_controls"; +@import "ingame_hud/puzzle_editor_settings"; +@import "ingame_hud/puzzle_play_metadata"; // prettier-ignore $elements: @@ -79,12 +79,12 @@ ingame_HUD_PlacerVariants, ingame_HUD_PinnedShapes, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, -ingame_HUD_ModeMenuBack, -ingame_HUD_PuzzleReview, +ingame_HUD_PuzzleBackToMenu, +ingame_HUD_PuzzleEditorReview, ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorTitle, -ingame_HUD_ModeMenu, -ingame_HUD_ModeSettings, +ingame_HUD_PuzzleEditorSettings, +ingame_HUD_PuzzlePlayMetadata ingame_HUD_Notifications, ingame_HUD_DebugInfo, ingame_HUD_EntityDebugger, @@ -127,8 +127,8 @@ body.uiHidden { #ingame_HUD_PlacementHints, #ingame_HUD_GameMenu, #ingame_HUD_PinnedShapes, - #ingame_HUD_ModeMenuBack, - #ingame_HUD_PuzzleReview, + #ingame_HUD_PuzzleBackToMenu, + #ingame_HUD_PuzzleEditorReview, #ingame_HUD_Notifications, #ingame_HUD_TutorialHints, #ingame_HUD_Waypoints, diff --git a/src/js/core/config.js b/src/js/core/config.js index 9a9a0a3e..0f074949 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -74,6 +74,8 @@ export const globalConfig = { goalAcceptorMinimumDurationSeconds: G_IS_DEV ? 1 : 5, goalAcceptorsPerProducer: G_IS_DEV ? 4 : 4, puzzleModeSpeed: 3, + puzzleMinBoundsSize: 2, + puzzleMaxBoundsSize: 20, buildingSpeeds: { cutter: 1 / 4, diff --git a/src/js/core/rectangle.js b/src/js/core/rectangle.js index 7ff57203..bd3421d9 100644 --- a/src/js/core/rectangle.js +++ b/src/js/core/rectangle.js @@ -44,6 +44,15 @@ export class Rectangle { return new Rectangle(left, top, right - left, bottom - top); } + /** + * + * @param {number} width + * @param {number} height + */ + static centered(width, height) { + return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height); + } + /** * Returns if a intersects b * @param {Rectangle} a @@ -287,6 +296,15 @@ export class Rectangle { return Rectangle.fromTRBL(top, right, bottom, left); } + /** + * Returns whether the rectangle fully intersects the given rectangle + * @param {Rectangle} rect + */ + intersectsFully(rect) { + const intersection = this.getIntersection(rect); + return intersection && Math.abs(intersection.w * intersection.h - rect.w * rect.h) < 0.001; + } + /** * Returns the union of this rectangle with another * @param {Rectangle} rect diff --git a/src/js/game/buildings/balancer.js b/src/js/game/buildings/balancer.js index 99c7e44f..7b31e837 100644 --- a/src/js/game/buildings/balancer.js +++ b/src/js/game/buildings/balancer.js @@ -66,6 +66,10 @@ export class MetaBalancerBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } + let speedMultiplier = 2; switch (variant) { case enumBalancerVariants.merger: diff --git a/src/js/game/buildings/belt.js b/src/js/game/buildings/belt.js index 84646b19..f4e31ba9 100644 --- a/src/js/game/buildings/belt.js +++ b/src/js/game/buildings/belt.js @@ -55,6 +55,9 @@ export class MetaBeltBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const beltSpeed = root.hubGoals.getBeltBaseSpeed(); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]]; } diff --git a/src/js/game/buildings/cutter.js b/src/js/game/buildings/cutter.js index 7dee4449..37264c9d 100644 --- a/src/js/game/buildings/cutter.js +++ b/src/js/game/buildings/cutter.js @@ -38,6 +38,9 @@ export class MetaCutterBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const speed = root.hubGoals.getProcessorBaseSpeed( variant === enumCutterVariants.quad ? enumItemProcessorTypes.cutterQuad diff --git a/src/js/game/buildings/filter.js b/src/js/game/buildings/filter.js index 2d81ce83..08296853 100644 --- a/src/js/game/buildings/filter.js +++ b/src/js/game/buildings/filter.js @@ -40,6 +40,9 @@ export class MetaFilterBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const beltSpeed = root.hubGoals.getBeltBaseSpeed(); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]]; } diff --git a/src/js/game/buildings/miner.js b/src/js/game/buildings/miner.js index f0b837a1..473aa262 100644 --- a/src/js/game/buildings/miner.js +++ b/src/js/game/buildings/miner.js @@ -31,6 +31,9 @@ export class MetaMinerBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const speed = root.hubGoals.getMinerBaseSpeed(); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; } diff --git a/src/js/game/buildings/mixer.js b/src/js/game/buildings/mixer.js index cbde309e..e572bbba 100644 --- a/src/js/game/buildings/mixer.js +++ b/src/js/game/buildings/mixer.js @@ -35,6 +35,9 @@ export class MetaMixerBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.mixer); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; } diff --git a/src/js/game/buildings/painter.js b/src/js/game/buildings/painter.js index 6e941403..a32bec95 100644 --- a/src/js/game/buildings/painter.js +++ b/src/js/game/buildings/painter.js @@ -46,6 +46,9 @@ export class MetaPainterBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } switch (variant) { case defaultBuildingVariant: case enumPainterVariants.mirrored: { diff --git a/src/js/game/buildings/rotater.js b/src/js/game/buildings/rotater.js index 7df94d16..f24fee14 100644 --- a/src/js/game/buildings/rotater.js +++ b/src/js/game/buildings/rotater.js @@ -48,6 +48,9 @@ export class MetaRotaterBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } switch (variant) { case defaultBuildingVariant: { const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater); diff --git a/src/js/game/buildings/stacker.js b/src/js/game/buildings/stacker.js index 40a9c5ae..6b70365d 100644 --- a/src/js/game/buildings/stacker.js +++ b/src/js/game/buildings/stacker.js @@ -28,6 +28,9 @@ export class MetaStackerBuilding extends MetaBuilding { * @returns {Array<[string, string]>} */ getAdditionalStatistics(root, variant) { + if (root.gameMode.throughputDoesNotMatter()) { + return []; + } const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.stacker); return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; } diff --git a/src/js/game/buildings/underground_belt.js b/src/js/game/buildings/underground_belt.js index 2761443d..12e887c9 100644 --- a/src/js/game/buildings/underground_belt.js +++ b/src/js/game/buildings/underground_belt.js @@ -72,13 +72,21 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding { globalConfig.undergroundBeltMaxTilesByTier[enumUndergroundBeltVariantToTier[variant]]; const beltSpeed = root.hubGoals.getUndergroundBeltBaseSpeed(); - return [ + + /** @type {Array<[string, string]>} */ + const stats = [ [ T.ingame.buildingPlacement.infoTexts.range, T.ingame.buildingPlacement.infoTexts.tiles.replace("", "" + rangeTiles), ], - [T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)], ]; + + if (root.gameMode.throughputDoesNotMatter()) { + return stats; + } + stats.push([T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]); + + return stats; } /** diff --git a/src/js/game/core.js b/src/js/game/core.js index 383a08dc..a0ee3713 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -103,7 +103,7 @@ export class GameCore { root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever); // Init game mode - root.gameMode = GameMode.create(root, gameModeId); + root.gameMode = GameMode.create(root, gameModeId, parentState.creationPayload.gameModeParameters); // Needs to come first root.dynamicTickrate = new DynamicTickrate(root); @@ -455,7 +455,9 @@ export class GameCore { systems.hub.draw(params); // Green wires overlay - root.hud.parts.wiresOverlay.draw(params); + if (root.hud.parts.wiresOverlay) { + root.hud.parts.wiresOverlay.draw(params); + } if (this.root.currentLayer === "wires") { // Static map entities diff --git a/src/js/game/game_mode.js b/src/js/game/game_mode.js index ae2fc140..b4e53ba9 100644 --- a/src/js/game/game_mode.js +++ b/src/js/game/game_mode.js @@ -1,12 +1,13 @@ /* typehints:start */ import { GameRoot } from "./root"; -import { Rectangle } from "../core/rectangle"; /* typehints:end */ +import { Rectangle } from "../core/rectangle"; import { gGameModeRegistry } from "../core/global_registries"; import { types, BasicSerializableObject } from "../savegame/serialization"; import { MetaBuilding } from "./meta_building"; import { MetaItemProducerBuilding } from "./buildings/item_producer"; +import { BaseHUDPart } from "./hud/base_hud_part"; /** @enum {string} */ export const enumGameModeIds = { @@ -36,9 +37,10 @@ export class GameMode extends BasicSerializableObject { /** * @param {GameRoot} root * @param {string} [id=Regular] + * @param {object|undefined} payload */ - static create(root, id = enumGameModeIds.regular) { - return new (gGameModeRegistry.findById(id))(root); + static create(root, id = enumGameModeIds.regular, payload = undefined) { + return new (gGameModeRegistry.findById(id))(root, payload); } /** @@ -47,7 +49,11 @@ export class GameMode extends BasicSerializableObject { constructor(root) { super(); this.root = root; - this.hiddenHudParts = {}; + + /** + * @type {Record} + */ + this.additionalHudParts = {}; /** @type {typeof MetaBuilding[]} */ this.hiddenBuildings = [MetaItemProducerBuilding]; @@ -78,14 +84,6 @@ export class GameMode extends BasicSerializableObject { return this.constructor.getType(); } - /** - * @param {string} name - Class name of HUD Part - * @returns {boolean} - */ - isHudPartExcluded(name) { - return this.hiddenHudParts[name] === false; - } - /** * @param {typeof MetaBuilding} building - Class name of building * @returns {boolean} diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index e7a0d8fc..fffecdd5 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -1,4 +1,4 @@ -import { globalConfig, IS_MOBILE } from "../../core/config"; +import { globalConfig } from "../../core/config"; import { DrawParameters } from "../../core/draw_parameters"; import { Signal } from "../../core/signal"; import { KEYMAPPINGS } from "../key_action_mapper"; @@ -6,47 +6,16 @@ import { MetaBuilding } from "../meta_building"; import { GameRoot } from "../root"; import { ShapeDefinition } from "../shape_definition"; import { HUDBetaOverlay } from "./parts/beta_overlay"; -import { HUDBlueprintPlacer } from "./parts/blueprint_placer"; import { HUDBuildingsToolbar } from "./parts/buildings_toolbar"; import { HUDBuildingPlacer } from "./parts/building_placer"; -import { HUDCatMemes } from "./parts/cat_memes"; import { HUDColorBlindHelper } from "./parts/color_blind_helper"; -import { HUDConstantSignalEdit } from "./parts/constant_signal_edit"; import { HUDChangesDebugger } from "./parts/debug_changes"; import { HUDDebugInfo } from "./parts/debug_info"; import { HUDEntityDebugger } from "./parts/entity_debugger"; -import { HUDGameMenu } from "./parts/game_menu"; -import { HUDInteractiveTutorial } from "./parts/interactive_tutorial"; -import { HUDKeybindingOverlay } from "./parts/keybinding_overlay"; -import { HUDLayerPreview } from "./parts/layer_preview"; -import { HUDLeverToggle } from "./parts/lever_toggle"; -import { HUDMassSelector } from "./parts/mass_selector"; -import { HUDMinerHighlight } from "./parts/miner_highlight"; import { HUDModalDialogs } from "./parts/modal_dialogs"; -import { HUDModeMenu } from "./parts/mode_menu"; -import { HUDModeMenuBack } from "./parts/mode_menu_back"; -import { HUDPuzzleReview } from "./parts/mode_puzzle_review"; -import { HUDModeSettings } from "./parts/mode_settings"; -import { enumNotificationType, HUDNotifications } from "./parts/notifications"; -import { HUDPinnedShapes } from "./parts/pinned_shapes"; -import { HUDPuzzleDLCLogo } from "./parts/puzzle_dlc_logo"; -import { HUDPuzzleEditorControls } from "./parts/puzzle_editor_controls"; -import { HUDSandboxController } from "./parts/sandbox_controller"; -import { HUDScreenshotExporter } from "./parts/screenshot_exporter"; +import { enumNotificationType } from "./parts/notifications"; import { HUDSettingsMenu } from "./parts/settings_menu"; -import { HUDShapeViewer } from "./parts/shape_viewer"; -import { HUDShop } from "./parts/shop"; -import { HUDStandaloneAdvantages } from "./parts/standalone_advantages"; -import { HUDStatistics } from "./parts/statistics"; -import { HUDPartTutorialHints } from "./parts/tutorial_hints"; -import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer"; -import { HUDUnlockNotification } from "./parts/unlock_notification"; import { HUDVignetteOverlay } from "./parts/vignette_overlay"; -import { HUDWatermark } from "./parts/watermark"; -import { HUDWaypoints } from "./parts/waypoints"; -import { HUDWiresOverlay } from "./parts/wires_overlay"; -import { HUDWiresToolbar } from "./parts/wires_toolbar"; -import { HUDWireInfo } from "./parts/wire_info"; import { TrailerMaker } from "./trailer_maker"; export class GameHUD { @@ -73,79 +42,30 @@ export class GameHUD { unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()), }; - this.initParts({ - buildingsToolbar: HUDBuildingsToolbar, - wiresToolbar: HUDWiresToolbar, - blueprintPlacer: HUDBlueprintPlacer, - buildingPlacer: HUDBuildingPlacer, - unlockNotification: HUDUnlockNotification, - gameMenu: HUDGameMenu, - massSelector: HUDMassSelector, - shop: HUDShop, - statistics: HUDStatistics, - waypoints: HUDWaypoints, - wireInfo: HUDWireInfo, - leverToggle: HUDLeverToggle, - constantSignalEdit: HUDConstantSignalEdit, - modeMenuBack: HUDModeMenuBack, - PuzzleReview: HUDPuzzleReview, - modeMenu: HUDModeMenu, - modeSettings: HUDModeSettings, - puzzleDlcLogo: HUDPuzzleDLCLogo, - puzzleEditorControls: HUDPuzzleEditorControls, + this.parts = { + buildingsToolbar: new HUDBuildingsToolbar(this.root), + buildingPlacer: new HUDBuildingPlacer(this.root), // Must always exist - pinnedShapes: HUDPinnedShapes, - notifications: HUDNotifications, - settingsMenu: HUDSettingsMenu, - debugInfo: HUDDebugInfo, - dialogs: HUDModalDialogs, - screenshotExporter: HUDScreenshotExporter, - shapeViewer: HUDShapeViewer, - - wiresOverlay: HUDWiresOverlay, - layerPreview: HUDLayerPreview, - - minerHighlight: HUDMinerHighlight, - tutorialVideoOffer: HUDTutorialVideoOffer, + settingsMenu: new HUDSettingsMenu(this.root), + debugInfo: new HUDDebugInfo(this.root), + dialogs: new HUDModalDialogs(this.root), // Typing hints /* typehints:start */ /** @type {HUDChangesDebugger} */ changesDebugger: null, /* typehints:end */ - }); - - if (!IS_MOBILE) { - if (!this.root.gameMode.isHudPartExcluded(HUDKeybindingOverlay.name)) { - this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root); - } - } + }; if (G_IS_DEV && globalConfig.debug.enableEntityInspector) { this.parts.entityDebugger = new HUDEntityDebugger(this.root); } - if (this.root.app.restrictionMgr.getIsStandaloneMarketingActive()) { - this.parts.watermark = new HUDWatermark(this.root); - this.parts.standaloneAdvantages = new HUDStandaloneAdvantages(this.root); - this.parts.catMemes = new HUDCatMemes(this.root); - } - if (G_IS_DEV && globalConfig.debug.renderChanges) { this.parts.changesDebugger = new HUDChangesDebugger(this.root); } - if (this.root.app.settings.getAllSettings().offerHints) { - if (!this.root.gameMode.isHudPartExcluded(HUDPartTutorialHints.name)) { - this.parts.tutorialHints = new HUDPartTutorialHints(this.root); - } - - if (!this.root.gameMode.isHudPartExcluded(HUDInteractiveTutorial.name)) { - this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root); - } - } - if (this.root.app.settings.getAllSettings().vignette) { this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root); } @@ -154,12 +74,17 @@ export class GameHUD { this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root); } - this.parts.sandboxController = new HUDSandboxController(this.root); - if (!G_IS_RELEASE && !G_IS_DEV) { this.parts.betaOverlay = new HUDBetaOverlay(this.root); } + const additionalParts = this.root.gameMode.additionalHudParts; + console.log(additionalParts); + for (const [partId, part] of Object.entries(additionalParts)) { + this.parts[partId] = new part(this.root); + } + console.log(this.parts); + const frag = document.createDocumentFragment(); for (const key in this.parts) { this.parts[key].createElements(frag); @@ -180,21 +105,6 @@ export class GameHUD { /* dev:end*/ } - /** @param {object} parts */ - initParts(parts) { - this.parts = {}; - - for (let key in parts) { - const Part = parts[key]; - - if (!Part || this.root.gameMode.isHudPartExcluded(Part.name)) { - continue; - } - - this.parts[key] = new Part(this.root); - } - } - /** * Attempts to close all overlays */ diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index 2b876726..33e6ebc2 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -275,11 +275,13 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic { const worldPosition = this.root.camera.screenToWorld(mousePosition); // Draw peeker - this.root.hud.parts.layerPreview.renderPreview( - parameters, - worldPosition, - 1 / this.root.camera.zoomLevel - ); + if (this.root.hud.parts.layerPreview) { + this.root.hud.parts.layerPreview.renderPreview( + parameters, + worldPosition, + 1 / this.root.camera.zoomLevel + ); + } } /** diff --git a/src/js/game/hud/parts/mode_menu.js b/src/js/game/hud/parts/mode_menu.js deleted file mode 100644 index cb05d6a2..00000000 --- a/src/js/game/hud/parts/mode_menu.js +++ /dev/null @@ -1,17 +0,0 @@ -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_settings.js b/src/js/game/hud/parts/mode_settings.js deleted file mode 100644 index 094a806e..00000000 --- a/src/js/game/hud/parts/mode_settings.js +++ /dev/null @@ -1,73 +0,0 @@ -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); - - // @fixme - if (this.root.gameMode.getBuildableZones()) { - 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.adjustZone(width, height); - this.updateZoneValues(); - } - - updateZoneValues() { - const zones = this.root.gameMode.getBuildableZones(); - if (!zones || zones.length === 0) { - return; - } - - const zone = zones[0]; - 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/hud/parts/pinned_shapes.js b/src/js/game/hud/parts/pinned_shapes.js index 4a9fce0d..a53ecbe5 100644 --- a/src/js/game/hud/parts/pinned_shapes.js +++ b/src/js/game/hud/parts/pinned_shapes.js @@ -55,7 +55,7 @@ export class HUDPinnedShapes extends BaseHUDPart { */ deserialize(data) { if (!data || !data.shapes || !Array.isArray(data.shapes)) { - return "Invalid pinned shapes data"; + return "Invalid pinned shapes data: " + JSON.stringify(data); } this.pinnedShapes = data.shapes; } diff --git a/src/js/game/hud/parts/mode_menu_back.js b/src/js/game/hud/parts/puzzle_back_to_menu.js similarity index 78% rename from src/js/game/hud/parts/mode_menu_back.js rename to src/js/game/hud/parts/puzzle_back_to_menu.js index ebe8b1e6..bde5b66d 100644 --- a/src/js/game/hud/parts/mode_menu_back.js +++ b/src/js/game/hud/parts/puzzle_back_to_menu.js @@ -1,11 +1,11 @@ import { makeDiv } from "../../../core/utils"; import { BaseHUDPart } from "../base_hud_part"; -export class HUDModeMenuBack extends BaseHUDPart { +export class HUDPuzzleBackToMenu extends BaseHUDPart { createElements(parent) { const key = this.root.gameMode.getId(); - this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack"); + this.element = makeDiv(parent, "ingame_HUD_PuzzleBackToMenu"); this.button = document.createElement("button"); this.button.classList.add("button"); this.element.appendChild(this.button); diff --git a/src/js/game/hud/parts/puzzle_editor_controls.js b/src/js/game/hud/parts/puzzle_editor_controls.js index 2968e107..d8055f11 100644 --- a/src/js/game/hud/parts/puzzle_editor_controls.js +++ b/src/js/game/hud/parts/puzzle_editor_controls.js @@ -1,19 +1,17 @@ import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; import { BaseHUDPart } from "../base_hud_part"; export class HUDPuzzleEditorControls extends BaseHUDPart { createElements(parent) { this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorControls"); - this.element.innerHTML = ` - - 1. Build constant producers to generate resources. - 2. Build goal acceptors and deliver shapes to set the puzzle goals. - 3. Once you are done, press 'Playtest' to validate your puzzle. - `; + this.element.innerHTML = T.ingame.puzzleEditorControls.instructions + .map(text => `${text}`) + .join(""); this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle"); - this.titleElement.innerText = "Puzzle Editor"; + this.titleElement.innerText = T.ingame.puzzleEditorControls.title; } initialize() {} diff --git a/src/js/game/hud/parts/mode_puzzle_review.js b/src/js/game/hud/parts/puzzle_editor_review.js similarity index 98% rename from src/js/game/hud/parts/mode_puzzle_review.js rename to src/js/game/hud/parts/puzzle_editor_review.js index cb2989cb..70129c4a 100644 --- a/src/js/game/hud/parts/mode_puzzle_review.js +++ b/src/js/game/hud/parts/puzzle_editor_review.js @@ -16,7 +16,7 @@ import { BaseHUDPart } from "../base_hud_part"; const trim = require("trim"); const logger = createLogger("puzzle-review"); -export class HUDPuzzleReview extends BaseHUDPart { +export class HUDPuzzleEditorReview extends BaseHUDPart { constructor(root) { super(root); @@ -27,7 +27,7 @@ export class HUDPuzzleReview extends BaseHUDPart { createElements(parent) { const key = this.root.gameMode.getId(); - this.element = makeDiv(parent, "ingame_HUD_PuzzleReview"); + this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorReview"); this.button = document.createElement("button"); this.button.classList.add("button"); this.button.textContent = T.puzzleMenu.reviewPuzzle; diff --git a/src/js/game/hud/parts/puzzle_editor_settings.js b/src/js/game/hud/parts/puzzle_editor_settings.js new file mode 100644 index 00000000..0fb41dde --- /dev/null +++ b/src/js/game/hud/parts/puzzle_editor_settings.js @@ -0,0 +1,141 @@ +/* typehints:start */ +import { PuzzleGameMode } from "../../modes/puzzle"; +/* typehints:end */ + +import { globalConfig } from "../../../core/config"; +import { createLogger } from "../../../core/logging"; +import { Rectangle } from "../../../core/rectangle"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { StaticMapEntityComponent } from "../../components/static_map_entity"; +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"); + + if (this.root.gameMode.getBuildableZones()) { + const bind = (selector, handler) => + this.trackClicks(this.element.querySelector(selector), handler); + 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)); + bind("button.trim", this.trim); + } + } + + trim() { + const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode); + + let w = mode.zoneWidth; + let h = mode.zoneHeight; + if (this.anyBuildingOutsideZone(w, h)) { + logger.error("Trim: Zone is already too small"); + return; + } + + logger.log("Zone trim: Starts at", w, h); + + while (!this.anyBuildingOutsideZone(w - 1, h)) { + --w; + } + + while (!this.anyBuildingOutsideZone(w, h - 1)) { + --h; + } + + logger.log("Zone trim: After height pass at", w, h); + if (this.anyBuildingOutsideZone(w, h)) { + logger.error("Trim: Zone is too small *after* trim"); + return; + } + + mode.zoneWidth = w; + mode.zoneHeight = h; + this.updateZoneValues(); + } + + initialize() { + this.visible = true; + this.updateZoneValues(); + } + + anyBuildingOutsideZone(width, height) { + if (Math.min(width, height) < globalConfig.puzzleMinBoundsSize) { + return true; + } + const newZone = Rectangle.centered(width, height); + const entities = this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent); + + for (const entity of entities) { + const staticComp = entity.components.StaticMapEntity; + const bounds = staticComp.getTileSpaceBounds(); + if (!newZone.intersectsFully(bounds)) { + return true; + } + } + } + + modifyZone(deltaW, deltaH) { + const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode); + + const newWidth = mode.zoneWidth + deltaW; + const newHeight = mode.zoneHeight + deltaH; + + if (Math.min(newWidth, newHeight) < globalConfig.puzzleMinBoundsSize) { + return; + } + + if (Math.max(newWidth, newHeight) > globalConfig.puzzleMaxBoundsSize) { + return; + } + + if (this.anyBuildingOutsideZone(newWidth, newHeight)) { + this.root.hud.parts.dialogs.showWarning( + T.dialogs.puzzleResizeBadBuildings.title, + T.dialogs.puzzleResizeBadBuildings.desc + ); + return; + } + + mode.zoneWidth = newWidth; + mode.zoneHeight = newHeight; + this.updateZoneValues(); + } + + updateZoneValues() { + const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode); + + this.element.querySelector(".zoneWidth > .value").textContent = String(mode.zoneWidth); + this.element.querySelector(".zoneHeight > .value").textContent = String(mode.zoneHeight); + } +} diff --git a/src/js/game/hud/parts/puzzle_play_metadata.js b/src/js/game/hud/parts/puzzle_play_metadata.js new file mode 100644 index 00000000..a2a82648 --- /dev/null +++ b/src/js/game/hud/parts/puzzle_play_metadata.js @@ -0,0 +1,15 @@ +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { BaseHUDPart } from "../base_hud_part"; + +export class HUDPuzzlePlayMetadata extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv(parent, "ingame_HUD_PuzzlePlayMetadata"); + + this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle"); + // this.titleElement.innerText = T.ingame.PuzzlePlayMetadata.title; + this.titleElement.innerText = "tobspr's first puzzle"; + } + + initialize() {} +} diff --git a/src/js/game/hud/parts/sandbox_controller.js b/src/js/game/hud/parts/sandbox_controller.js index d9a57bb1..c8104d99 100644 --- a/src/js/game/hud/parts/sandbox_controller.js +++ b/src/js/game/hud/parts/sandbox_controller.js @@ -118,7 +118,9 @@ export class HUDSandboxController extends BaseHUDPart { // Clear all shapes of this level hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0; - this.root.hud.parts.pinnedShapes.rerenderFull(); + if (this.root.hud.parts.pinnedShapes) { + this.root.hud.parts.pinnedShapes.rerenderFull(); + } // Compute gained rewards hubGoals.gainedRewards = {}; @@ -149,19 +151,8 @@ export class HUDSandboxController extends BaseHUDPart { this.domAttach = new DynamicDomAttach(this.root, this.element); } - isAvailable() { - if (queryParamOptions.sandboxMode || G_IS_DEV) { - return true; - } - // @ts-ignore - if (window.sandboxMode) { - return true; - } - return false; - } - toggle() { - if (!this.visible && !this.isAvailable()) { + if (!this.visible) { return; } this.visible = !this.visible; diff --git a/src/js/game/modes/puzzle.js b/src/js/game/modes/puzzle.js index 847f9563..348ace91 100644 --- a/src/js/game/modes/puzzle.js +++ b/src/js/game/modes/puzzle.js @@ -5,13 +5,8 @@ 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 { 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"; -import { HUDMassSelector } from "../hud/parts/mass_selector"; +import { HUDPuzzleBackToMenu } from "../hud/parts/puzzle_back_to_menu"; +import { HUDPuzzleDLCLogo } from "../hud/parts/puzzle_dlc_logo"; export class PuzzleGameMode extends GameMode { static getType() { @@ -32,22 +27,13 @@ export class PuzzleGameMode extends GameMode { const data = this.getSaveData(); - this.hiddenHudParts = { - [HUDGameMenu.name]: false, - [HUDMassSelector.name]: false, - [HUDInteractiveTutorial.name]: false, - [HUDKeybindingOverlay.name]: false, - [HUDPartTutorialHints.name]: false, - [HUDPinnedShapes.name]: false, - [HUDWaypoints.name]: false, + this.additionalHudParts = { + puzzleBackToMenu: HUDPuzzleBackToMenu, + puzzleDlcLogo: HUDPuzzleDLCLogo, }; - this.setDimensions(data.zoneWidth, data.zoneHeight); - } - - setDimensions(w = 16, h = 9) { - this.zoneWidth = w < 2 ? 2 : w; - this.zoneHeight = h < 2 ? 2 : h; + this.zoneWidth = data.zoneWidth || 8; + this.zoneHeight = data.zoneHeight || 6; } getSaveData() { @@ -58,16 +44,12 @@ export class PuzzleGameMode extends GameMode { return save.gameMode.data; } - createCenteredRectangle(width, height) { - return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height); - } - getCameraBounds() { - return this.createCenteredRectangle(this.zoneWidth + 20, this.zoneHeight + 20); + return Rectangle.centered(this.zoneWidth + 20, this.zoneHeight + 20); } getBuildableZones() { - return [this.createCenteredRectangle(this.zoneWidth, this.zoneHeight)]; + return [Rectangle.centered(this.zoneWidth, this.zoneHeight)]; } hasHub() { diff --git a/src/js/game/modes/puzzle_edit.js b/src/js/game/modes/puzzle_edit.js index d5d91e2b..04d4dad5 100644 --- a/src/js/game/modes/puzzle_edit.js +++ b/src/js/game/modes/puzzle_edit.js @@ -19,6 +19,10 @@ import { MetaVirtualProcessorBuilding } from "../buildings/virtual_processor"; import { MetaAnalyzerBuilding } from "../buildings/analyzer"; import { MetaComparatorBuilding } from "../buildings/comparator"; 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 { HUDPuzzleBackToMenu } from "../hud/parts/puzzle_back_to_menu"; export class PuzzleEditGameMode extends PuzzleGameMode { static getId() { @@ -33,8 +37,6 @@ export class PuzzleEditGameMode extends PuzzleGameMode { constructor(root) { super(root); - this.playtest = false; - this.hiddenBuildings = [ MetaStorageBuilding, MetaReaderBuilding, @@ -53,32 +55,10 @@ export class PuzzleEditGameMode extends PuzzleGameMode { MetaComparatorBuilding, MetaTransistorBuilding, ]; - } - adjustZone(w = 0, h = 0) { - // @todo notify user when zone cannot be shrunk - if (this.zoneWidth + w <= 0) { - return; - } - - if (this.zoneHeight + h <= 0) { - return; - } - - const newZone = this.createCenteredRectangle(this.zoneWidth + w, this.zoneHeight + h); - const entities = this.root.entityMgr.entities; - - // @fixme find a better way to check this - for (const entity of entities) { - const staticComp = entity.components.StaticMapEntity; - const union = newZone.getUnion(staticComp.getTileSpaceBounds()); - if (!union.equalsEpsilon(newZone)) { - return; - } - } - - this.zoneWidth = newZone.w; - this.zoneHeight = newZone.h; + this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls; + this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview; + this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings; } getIsEditor() { diff --git a/src/js/game/modes/puzzle_play.js b/src/js/game/modes/puzzle_play.js index a2d090fd..b4109696 100644 --- a/src/js/game/modes/puzzle_play.js +++ b/src/js/game/modes/puzzle_play.js @@ -2,16 +2,65 @@ import { GameRoot } from "../root"; /* typehints:end */ -import { PuzzleGameMode } from "./puzzle"; import { enumGameModeIds } from "../game_mode"; +import { PuzzleGameMode } from "./puzzle"; +import { MetaStorageBuilding } from "../buildings/storage"; +import { MetaReaderBuilding } from "../buildings/reader"; +import { MetaFilterBuilding } from "../buildings/filter"; +import { MetaDisplayBuilding } from "../buildings/display"; +import { MetaLeverBuilding } from "../buildings/lever"; +import { MetaItemProducerBuilding } from "../buildings/item_producer"; +import { MetaMinerBuilding } from "../buildings/miner"; +import { MetaWireBuilding } from "../buildings/wire"; +import { MetaWireTunnelBuilding } from "../buildings/wire_tunnel"; +import { MetaConstantSignalBuilding } from "../buildings/constant_signal"; +import { MetaLogicGateBuilding } from "../buildings/logic_gate"; +import { MetaVirtualProcessorBuilding } from "../buildings/virtual_processor"; +import { MetaAnalyzerBuilding } from "../buildings/analyzer"; +import { MetaComparatorBuilding } from "../buildings/comparator"; +import { MetaTransistorBuilding } from "../buildings/transistor"; +import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; +import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; +import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit"; export class PuzzlePlayGameMode extends PuzzleGameMode { static getId() { return enumGameModeIds.puzzlePlay; } - /** @param {GameRoot} root */ - constructor(root) { + /** + * @param {GameRoot} root + * @param {object} payload + * @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle + */ + constructor(root, { puzzle }) { super(root); + + this.hiddenBuildings = [ + MetaConstantProducerBuilding, + MetaGoalAcceptorBuilding, + + MetaStorageBuilding, + MetaReaderBuilding, + MetaFilterBuilding, + MetaDisplayBuilding, + MetaLeverBuilding, + MetaItemProducerBuilding, + MetaMinerBuilding, + + MetaWireBuilding, + MetaWireTunnelBuilding, + MetaConstantSignalBuilding, + MetaLogicGateBuilding, + MetaVirtualProcessorBuilding, + MetaAnalyzerBuilding, + MetaComparatorBuilding, + MetaTransistorBuilding, + ]; + + this.additionalHudParts.constantSignalEdit = HUDConstantSignalEdit; + + console.log("playing puzzle:", puzzle); + this.puzzle = puzzle; } } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 0050691a..e3e1e14e 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -5,15 +5,37 @@ import { GameRoot } from "../root"; import { findNiceIntegerValue } from "../../core/utils"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; -import { HUDModeMenuBack } from "../hud/parts/mode_menu_back"; -import { HUDPuzzleReview } from "../hud/parts/mode_puzzle_review"; -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"; -import { HUDPuzzleDLCLogo } from "../hud/parts/puzzle_dlc_logo"; -import { HUDPuzzleEditorControls } from "../hud/parts/puzzle_editor_controls"; +import { HUDWiresToolbar } from "../hud/parts/wires_toolbar"; +import { HUDBlueprintPlacer } from "../hud/parts/blueprint_placer"; +import { HUDUnlockNotification } from "../hud/parts/unlock_notification"; +import { HUDMassSelector } from "../hud/parts/mass_selector"; +import { HUDShop } from "../hud/parts/shop"; +import { HUDWaypoints } from "../hud/parts/waypoints"; +import { HUDStatistics } from "../hud/parts/statistics"; +import { HUDWireInfo } from "../hud/parts/wire_info"; +import { HUDLeverToggle } from "../hud/parts/lever_toggle"; +import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; +import { HUDNotifications } from "../hud/parts/notifications"; +import { HUDScreenshotExporter } from "../hud/parts/screenshot_exporter"; +import { HUDWiresOverlay } from "../hud/parts/wires_overlay"; +import { HUDShapeViewer } from "../hud/parts/shape_viewer"; +import { HUDLayerPreview } from "../hud/parts/layer_preview"; +import { HUDTutorialVideoOffer } from "../hud/parts/tutorial_video_offer"; +import { HUDMinerHighlight } from "../hud/parts/miner_highlight"; +import { HUDGameMenu } from "../hud/parts/game_menu"; +import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit"; +import { IS_MOBILE } from "../../core/config"; +import { HUDKeybindingOverlay } from "../hud/parts/keybinding_overlay"; +import { HUDWatermark } from "../hud/parts/watermark"; +import { HUDStandaloneAdvantages } from "../hud/parts/standalone_advantages"; +import { HUDCatMemes } from "../hud/parts/cat_memes"; +import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints"; +import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial"; +import { HUDSandboxController } from "../hud/parts/sandbox_controller"; +import { queryParamOptions } from "../../core/query_parameters"; /** @typedef {{ * shape: string, @@ -517,15 +539,48 @@ export class RegularGameMode extends GameMode { constructor(root) { super(root); - this.hiddenHudParts = { - [HUDModeMenuBack.name]: false, - [HUDPuzzleReview.name]: false, - [HUDModeMenu.name]: false, - [HUDModeSettings.name]: false, - [HUDPuzzleDLCLogo.name]: false, - [HUDPuzzleEditorControls.name]: false, + this.additionalHudParts = { + wiresToolbar: HUDWiresToolbar, + blueprintPlacer: HUDBlueprintPlacer, + unlockNotification: HUDUnlockNotification, + massSelector: HUDMassSelector, + shop: HUDShop, + statistics: HUDStatistics, + waypoints: HUDWaypoints, + wireInfo: HUDWireInfo, + leverToggle: HUDLeverToggle, + pinnedShapes: HUDPinnedShapes, + notifications: HUDNotifications, + screenshotExporter: HUDScreenshotExporter, + wiresOverlay: HUDWiresOverlay, + shapeViewer: HUDShapeViewer, + layerPreview: HUDLayerPreview, + minerHighlight: HUDMinerHighlight, + tutorialVideoOffer: HUDTutorialVideoOffer, + gameMenu: HUDGameMenu, + constantSignalEdit: HUDConstantSignalEdit, }; + if (!IS_MOBILE) { + this.additionalHudParts.keybindingOverlay = HUDKeybindingOverlay; + } + + if (this.root.app.restrictionMgr.getIsStandaloneMarketingActive()) { + this.additionalHudParts.watermark = HUDWatermark; + this.additionalHudParts.standaloneAdvantages = HUDStandaloneAdvantages; + this.additionalHudParts.catMemes = HUDCatMemes; + } + + if (this.root.app.settings.getAllSettings().offerHints) { + this.additionalHudParts.tutorialHints = HUDPartTutorialHints; + this.additionalHudParts.interactiveTutorial = HUDInteractiveTutorial; + } + + // @ts-ignore + if (queryParamOptions.sandboxMode || window.sandboxMode || G_IS_DEV) { + this.additionalHudParts.sandboxController = HUDSandboxController; + } + this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding]; } diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index 8d033f61..326ef342 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -77,7 +77,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter { ); } - if (!this.root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) { + if (this.root.hud.parts.pinnedShapes) { items.push( ...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key) diff --git a/src/js/game/systems/zone.js b/src/js/game/systems/zone.js index c7c24408..109f5166 100644 --- a/src/js/game/systems/zone.js +++ b/src/js/game/systems/zone.js @@ -49,14 +49,9 @@ export class ZoneSystem extends GameSystem { transformed.y += tile.y; } - for (const zone of zones) { - const intersection = zone.getIntersection(transformed); - if (intersection && intersection.w * intersection.h === transformed.w * transformed.h) { - return; - } + if (!zones.some(zone => zone.intersectsFully(transformed))) { + return STOP_PROPAGATION; } - - return STOP_PROPAGATION; } /** @@ -84,8 +79,7 @@ export class ZoneSystem extends GameSystem { context.lineWidth = 2; context.strokeStyle = THEME.map.zone.borderSolid; context.beginPath(); - context.rect(zone.x, zone.y, zone.w, zone.h); - + context.rect(zone.x - 1, zone.y - 1, zone.w + 2, zone.h + 2); context.stroke(); const outer = zone; diff --git a/src/js/game/themes/light.json b/src/js/game/themes/light.json index c39e19f0..0962eb93 100644 --- a/src/js/game/themes/light.json +++ b/src/js/game/themes/light.json @@ -51,9 +51,7 @@ }, "zone": { - "background": "#fff", - "border": "rgba(23, 192, 255, 0.1)", - "borderSolid": "rgba(23, 192, 255, 0.7)", + "borderSolid": "rgba(23, 192, 255, 1)", "outerColor": "rgba(240, 240, 255, 0.5)" } }, diff --git a/src/js/savegame/puzzle_serializer.js b/src/js/savegame/puzzle_serializer.js index f75226b3..0a3ceb9b 100644 --- a/src/js/savegame/puzzle_serializer.js +++ b/src/js/savegame/puzzle_serializer.js @@ -1,18 +1,23 @@ +/* typehints:start */ +import { GameRoot } from "../game/root"; +import { PuzzleGameMode } from "../game/modes/puzzle"; +/* typehints:end */ import { enumConstantSignalType } from "../game/components/constant_signal"; import { StaticMapEntityComponent } from "../game/components/static_map_entity"; import { ShapeItem } from "../game/items/shape_item"; -import { GameRoot } from "../game/root"; export class PuzzleSerializer { /** * Serializes the game root into a dump * @param {GameRoot} root - * @param {boolean=} sanityChecks Whether to check for validity - * @returns {object} + * @returns {import("./savegame_typedefs").PuzzleGameData} */ - generateDumpFromGameRoot(root, sanityChecks = true) { + generateDumpFromGameRoot(root) { console.log("serializing", root); + /** + * @type {import("./savegame_typedefs").PuzzleGameData["buildings"]} + */ let buildings = []; for (const entity of root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) { @@ -51,9 +56,15 @@ export class PuzzleSerializer { } } + const mode = /** @type {PuzzleGameMode} */ (root.gameMode); + return { + version: 1, buildings, - bounds: root.gameMode.getBuildableZones()[0], + bounds: { + w: mode.zoneWidth, + h: mode.zoneHeight, + }, }; } } diff --git a/src/js/savegame/savegame_serializer.js b/src/js/savegame/savegame_serializer.js index fb36393e..3230cdd5 100644 --- a/src/js/savegame/savegame_serializer.js +++ b/src/js/savegame/savegame_serializer.js @@ -40,12 +40,8 @@ export class SavegameSerializer { hubGoals: root.hubGoals.serialize(), entities: this.internal.serializeEntityArray(root.entityMgr.entities), beltPaths: root.systemMgr.systems.belt.serializePaths(), - pinnedShapes: root.gameMode.isHudPartExcluded(HUDPinnedShapes.name) - ? null - : root.hud.parts.pinnedShapes.serialize(), - waypoints: root.gameMode.isHudPartExcluded(HUDWaypoints.name) - ? null - : root.hud.parts.waypoints.serialize(), + pinnedShapes: root.hud.parts.pinnedShapes ? root.hud.parts.pinnedShapes.serialize() : null, + waypoints: root.hud.parts.waypoints ? root.hud.parts.waypoints.serialize() : null, }; if (G_IS_DEV) { @@ -142,11 +138,11 @@ export class SavegameSerializer { errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities); errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths); - if (!root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) { + if (root.hud.parts.pinnedShapes) { errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes); } - if (!root.gameMode.isHudPartExcluded(HUDWaypoints.name)) { + if (root.hud.parts.waypoints) { errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints); } diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js index 49929f99..b28c222d 100644 --- a/src/js/savegame/savegame_typedefs.js +++ b/src/js/savegame/savegame_typedefs.js @@ -41,4 +41,46 @@ * }} SavegamesData */ +/** + * @typedef {{ + * shortKey: string; + * upvotes: number; + * playcount: number; + * title: string; + * author: string; + * completed: boolean; + * }} PuzzleMetadata + */ + +/** + * @typedef {{ + * type: "emitter"; + * item: string; + * pos: { x: number; y: number; r: number } + * }} PuzzleGameBuildingConstantProducer + */ + +/** + * @typedef {{ + * type: "goal"; + * item: string; + * pos: { x: number; y: number; r: number } + * }} PuzzleGameBuildingGoal + */ + +/** + * @typedef {{ + * version: number; + * bounds: { w: number; h: number; }, + * buildings: (PuzzleGameBuildingGoal | PuzzleGameBuildingConstantProducer)[] + * }} PuzzleGameData + */ + +/** + * @typedef {{ + * meta: PuzzleMetadata, + * game: PuzzleGameData + * }} PuzzleFullData + */ + export default {}; diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js index a872b6f6..9ab38dd1 100644 --- a/src/js/states/ingame.js +++ b/src/js/states/ingame.js @@ -45,6 +45,9 @@ export class GameCreationPayload { /** @type {Savegame} */ this.savegame; + + /** @type {object|undefined} */ + this.gameModeParameters; } } diff --git a/src/js/states/puzzle_menu.js b/src/js/states/puzzle_menu.js index 8b6c8fcb..7f5c6131 100644 --- a/src/js/states/puzzle_menu.js +++ b/src/js/states/puzzle_menu.js @@ -7,17 +7,6 @@ import { T } from "../translations"; const categories = ["levels", "new", "topRated", "myPuzzles"]; -/** - * @typedef {{ - * shortKey: string; - * upvotes: number; - * playcount: number; - * title: string; - * author: string; - * completed: boolean; - * }} PuzzleMetadata - */ - const SAMPLE_PUZZLE = { shortKey: "CuCuCuCu", upvotes: 10000, @@ -26,6 +15,7 @@ const SAMPLE_PUZZLE = { author: "verylongsteamnamewhichbreaks", completed: false, }; + const BUILTIN_PUZZLES = [ { ...SAMPLE_PUZZLE, completed: true }, { ...SAMPLE_PUZZLE, completed: true }, @@ -141,7 +131,7 @@ export class PuzzleMenuState extends TextualGameState { /** * - * @param {PuzzleMetadata[]} puzzles + * @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles */ renderPuzzles(puzzles) { const container = this.htmlElement.querySelector("#mainContainer"); @@ -191,6 +181,8 @@ export class PuzzleMenuState extends TextualGameState { elem.appendChild(icon); container.appendChild(elem); + + this.trackClicks(elem, () => this.playPuzzle(puzzle)); } } @@ -198,6 +190,49 @@ export class PuzzleMenuState extends TextualGameState { return new Promise(resolve => setTimeout(() => resolve(BUILTIN_PUZZLES), 100)); } + /** + * + * @param {import("../savegame/savegame_typedefs").PuzzleMetadata} puzzle + */ + playPuzzle(puzzle) { + /** + * @type {import("../savegame/savegame_typedefs").PuzzleGameData} + */ + const puzzleData = { + version: 1, + buildings: [ + { + type: "emitter", + item: "CuCuCuCu", + pos: { x: 0, y: 0, r: 180 }, + }, + { + type: "emitter", + item: "red", + pos: { x: 2, y: 0, r: 180 }, + }, + { + type: "goal", + item: "CrCrCrCr", + pos: { x: 0, y: 4, r: 0 }, + }, + ], + bounds: { w: 10, h: 10 }, + }; + + const savegame = this.app.savegameMgr.createNewSavegame(); + this.moveToState("InGameState", { + gameModeId: enumGameModeIds.puzzlePlay, + gameModeParameters: { + puzzle: { + meta: puzzle, + game: puzzleData, + }, + }, + savegame, + }); + } + onEnter() { this.selectCategory("levels"); @@ -209,7 +244,8 @@ export class PuzzleMenuState extends TextualGameState { this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), this.createNewPuzzle); if (G_IS_DEV && globalConfig.debug.testPuzzleMode) { - this.createNewPuzzle(); + // this.createNewPuzzle(); + this.playPuzzle(SAMPLE_PUZZLE); } } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index f889cd8d..fe9444e7 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -298,6 +298,10 @@ dialogs: placeholderName: Puzzle Title + puzzleResizeBadBuildings: + title: Resize not possible + desc: You can't make the zone any smaller, because then some buildings would be outside the zone. + ingame: # This is shown in the top left corner and displays useful keybindings in # every situation @@ -522,6 +526,22 @@ ingame: title: Support me desc: I develop the game in my spare time! + # puzzle mode + puzzleEditorSettings: + zoneTitle: Zone + zoneWidth: Width + zoneHeight: Height + trimZone: Trim + + puzzleEditorControls: + title: Puzzle Creator + instructions: + - 1. Place Constant Producers to provide shapes and colors to the player + - 2. Build one or more shapes you want the player to build later and deliver it to one or more Goal Acceptors + - 3. Once a Goal Acceptor receives a shape for a certain amount of time, it saves it as a goal that the player must produce later (Indicated by the green badge). + - 4. Once you click review, your puzzle will be validated and you can publish it. + - 5. Upon release, all buildings will be removed except for the Producers and Goal Acceptors - That's the part that the player is supposed to figure out for themselves, after all :) + # All shop upgrades shopUpgrades: belt: @@ -749,12 +769,12 @@ buildings: constant_producer: default: name: &constant_producer Constant Producer - description: Outputs a shape, color or boolean (1 or 0) as specified. + description: Constantly outputs a specified shape or color. goal_acceptor: default: name: &goal_acceptor Goal Acceptor - description: Accepts items and triggers a goal if the specified item and/or rate criteria are met. + description: Deliver shapes to the goal acceptor to set them as a goal. storyRewards: # Those are the rewards gained from completing the store