1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Puzzle mode, Refactor hud

This commit is contained in:
tobspr 2021-04-30 20:06:37 +02:00
parent 36aaf7bfa5
commit 72530005cb
46 changed files with 592 additions and 429 deletions

View File

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

View File

@ -1,7 +1,7 @@
#ingame_HUD_ModeMenuBack { #ingame_HUD_PuzzleBackToMenu {
position: absolute; position: absolute;
@include S(top, 10px); @include S(top, 10px);
@include S(left, 10px); @include S(left, 0px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,9 +1,9 @@
#ingame_HUD_PuzzleDLCLogo { #ingame_HUD_PuzzleDLCLogo {
position: absolute; position: absolute;
@include S(width, 150px); @include S(width, 120px);
@include S(height, 40px); @include S(height, 40px);
@include S(left, 50px); @include S(left, 40px);
@include S(top, 10px); @include S(top, 7px);
& { & {
/* @load-async */ /* @load-async */

View File

@ -11,13 +11,17 @@
> span { > span {
@include S(margin-bottom, 10px); @include S(margin-bottom, 10px);
strong {
font-weight: bold;
}
} }
} }
#ingame_HUD_PuzzleEditorTitle { #ingame_HUD_PuzzleEditorTitle {
position: absolute; position: absolute;
@include S(top, 23px); @include S(top, 18px);
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
text-transform: uppercase; text-transform: uppercase;

View File

@ -1,6 +1,6 @@
#ingame_HUD_PuzzleReview { #ingame_HUD_PuzzleEditorReview {
position: absolute; position: absolute;
@include S(top, 15px); @include S(top, 17px);
@include S(right, 10px); @include S(right, 10px);
display: flex; display: flex;

View File

@ -1,14 +1,15 @@
#ingame_HUD_ModeSettings { #ingame_HUD_PuzzleEditorSettings {
position: absolute; position: absolute;
background: $ingameHudBg; background: $ingameHudBg;
@include S(padding, 10px); @include S(padding, 10px);
@include S(bottom, 50px); @include S(bottom, 60px);
@include S(left, 15px); @include S(left, 10px);
@include SuperSmallText; @include SuperSmallText;
color: #eee; color: #eee;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@include S(border-radius, $globalBorderRadius);
> .section { > .section {
> label { > label {
@ -37,11 +38,15 @@
@include IncreasedClickArea(0px); @include IncreasedClickArea(0px);
} }
.value { .value {
text-align: center; text-align: center;
@include S(min-width, 15px); @include S(min-width, 15px);
} }
} }
> .buttons > button.trim {
@include S(margin-top, 10px);
@include SuperSmallText;
}
} }
} }

View File

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

View File

@ -56,12 +56,12 @@
@import "ingame_hud/sandbox_controller"; @import "ingame_hud/sandbox_controller";
@import "ingame_hud/standalone_advantages"; @import "ingame_hud/standalone_advantages";
@import "ingame_hud/cat_memes"; @import "ingame_hud/cat_memes";
@import "ingame_hud/mode_menu_back"; @import "ingame_hud/puzzle_back_to_menu";
@import "ingame_hud/mode_menu_next"; @import "ingame_hud/puzzle_editor_review";
@import "ingame_hud/mode_menu";
@import "ingame_hud/mode_settings";
@import "ingame_hud/puzzle_dlc_logo"; @import "ingame_hud/puzzle_dlc_logo";
@import "ingame_hud/puzzle_editor_controls"; @import "ingame_hud/puzzle_editor_controls";
@import "ingame_hud/puzzle_editor_settings";
@import "ingame_hud/puzzle_play_metadata";
// prettier-ignore // prettier-ignore
$elements: $elements:
@ -79,12 +79,12 @@ ingame_HUD_PlacerVariants,
ingame_HUD_PinnedShapes, ingame_HUD_PinnedShapes,
ingame_HUD_GameMenu, ingame_HUD_GameMenu,
ingame_HUD_KeybindingOverlay, ingame_HUD_KeybindingOverlay,
ingame_HUD_ModeMenuBack, ingame_HUD_PuzzleBackToMenu,
ingame_HUD_PuzzleReview, ingame_HUD_PuzzleEditorReview,
ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle, ingame_HUD_PuzzleEditorTitle,
ingame_HUD_ModeMenu, ingame_HUD_PuzzleEditorSettings,
ingame_HUD_ModeSettings, ingame_HUD_PuzzlePlayMetadata
ingame_HUD_Notifications, ingame_HUD_Notifications,
ingame_HUD_DebugInfo, ingame_HUD_DebugInfo,
ingame_HUD_EntityDebugger, ingame_HUD_EntityDebugger,
@ -127,8 +127,8 @@ body.uiHidden {
#ingame_HUD_PlacementHints, #ingame_HUD_PlacementHints,
#ingame_HUD_GameMenu, #ingame_HUD_GameMenu,
#ingame_HUD_PinnedShapes, #ingame_HUD_PinnedShapes,
#ingame_HUD_ModeMenuBack, #ingame_HUD_PuzzleBackToMenu,
#ingame_HUD_PuzzleReview, #ingame_HUD_PuzzleEditorReview,
#ingame_HUD_Notifications, #ingame_HUD_Notifications,
#ingame_HUD_TutorialHints, #ingame_HUD_TutorialHints,
#ingame_HUD_Waypoints, #ingame_HUD_Waypoints,

View File

@ -74,6 +74,8 @@ export const globalConfig = {
goalAcceptorMinimumDurationSeconds: G_IS_DEV ? 1 : 5, goalAcceptorMinimumDurationSeconds: G_IS_DEV ? 1 : 5,
goalAcceptorsPerProducer: G_IS_DEV ? 4 : 4, goalAcceptorsPerProducer: G_IS_DEV ? 4 : 4,
puzzleModeSpeed: 3, puzzleModeSpeed: 3,
puzzleMinBoundsSize: 2,
puzzleMaxBoundsSize: 20,
buildingSpeeds: { buildingSpeeds: {
cutter: 1 / 4, cutter: 1 / 4,

View File

@ -44,6 +44,15 @@ export class Rectangle {
return new Rectangle(left, top, right - left, bottom - top); 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 * Returns if a intersects b
* @param {Rectangle} a * @param {Rectangle} a
@ -287,6 +296,15 @@ export class Rectangle {
return Rectangle.fromTRBL(top, right, bottom, left); 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 * Returns the union of this rectangle with another
* @param {Rectangle} rect * @param {Rectangle} rect

View File

@ -66,6 +66,10 @@ export class MetaBalancerBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
let speedMultiplier = 2; let speedMultiplier = 2;
switch (variant) { switch (variant) {
case enumBalancerVariants.merger: case enumBalancerVariants.merger:

View File

@ -55,6 +55,9 @@ export class MetaBeltBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const beltSpeed = root.hubGoals.getBeltBaseSpeed(); const beltSpeed = root.hubGoals.getBeltBaseSpeed();
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]]; return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]];
} }

View File

@ -38,6 +38,9 @@ export class MetaCutterBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const speed = root.hubGoals.getProcessorBaseSpeed( const speed = root.hubGoals.getProcessorBaseSpeed(
variant === enumCutterVariants.quad variant === enumCutterVariants.quad
? enumItemProcessorTypes.cutterQuad ? enumItemProcessorTypes.cutterQuad

View File

@ -40,6 +40,9 @@ export class MetaFilterBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const beltSpeed = root.hubGoals.getBeltBaseSpeed(); const beltSpeed = root.hubGoals.getBeltBaseSpeed();
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]]; return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(beltSpeed)]];
} }

View File

@ -31,6 +31,9 @@ export class MetaMinerBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const speed = root.hubGoals.getMinerBaseSpeed(); const speed = root.hubGoals.getMinerBaseSpeed();
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
} }

View File

@ -35,6 +35,9 @@ export class MetaMixerBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.mixer); const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.mixer);
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
} }

View File

@ -46,6 +46,9 @@ export class MetaPainterBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
switch (variant) { switch (variant) {
case defaultBuildingVariant: case defaultBuildingVariant:
case enumPainterVariants.mirrored: { case enumPainterVariants.mirrored: {

View File

@ -48,6 +48,9 @@ export class MetaRotaterBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
switch (variant) { switch (variant) {
case defaultBuildingVariant: { case defaultBuildingVariant: {
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater); const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.rotater);

View File

@ -28,6 +28,9 @@ export class MetaStackerBuilding extends MetaBuilding {
* @returns {Array<[string, string]>} * @returns {Array<[string, string]>}
*/ */
getAdditionalStatistics(root, variant) { getAdditionalStatistics(root, variant) {
if (root.gameMode.throughputDoesNotMatter()) {
return [];
}
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.stacker); const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.stacker);
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]]; return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
} }

View File

@ -72,13 +72,21 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
globalConfig.undergroundBeltMaxTilesByTier[enumUndergroundBeltVariantToTier[variant]]; globalConfig.undergroundBeltMaxTilesByTier[enumUndergroundBeltVariantToTier[variant]];
const beltSpeed = root.hubGoals.getUndergroundBeltBaseSpeed(); const beltSpeed = root.hubGoals.getUndergroundBeltBaseSpeed();
return [
/** @type {Array<[string, string]>} */
const stats = [
[ [
T.ingame.buildingPlacement.infoTexts.range, T.ingame.buildingPlacement.infoTexts.range,
T.ingame.buildingPlacement.infoTexts.tiles.replace("<x>", "" + rangeTiles), T.ingame.buildingPlacement.infoTexts.tiles.replace("<x>", "" + 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;
} }
/** /**

View File

@ -103,7 +103,7 @@ export class GameCore {
root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever); root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever);
// Init game mode // Init game mode
root.gameMode = GameMode.create(root, gameModeId); root.gameMode = GameMode.create(root, gameModeId, parentState.creationPayload.gameModeParameters);
// Needs to come first // Needs to come first
root.dynamicTickrate = new DynamicTickrate(root); root.dynamicTickrate = new DynamicTickrate(root);
@ -455,7 +455,9 @@ export class GameCore {
systems.hub.draw(params); systems.hub.draw(params);
// Green wires overlay // Green wires overlay
if (root.hud.parts.wiresOverlay) {
root.hud.parts.wiresOverlay.draw(params); root.hud.parts.wiresOverlay.draw(params);
}
if (this.root.currentLayer === "wires") { if (this.root.currentLayer === "wires") {
// Static map entities // Static map entities

View File

@ -1,12 +1,13 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { Rectangle } from "../core/rectangle";
/* typehints:end */ /* typehints:end */
import { Rectangle } from "../core/rectangle";
import { gGameModeRegistry } from "../core/global_registries"; import { gGameModeRegistry } from "../core/global_registries";
import { types, BasicSerializableObject } from "../savegame/serialization"; import { types, BasicSerializableObject } from "../savegame/serialization";
import { MetaBuilding } from "./meta_building"; import { MetaBuilding } from "./meta_building";
import { MetaItemProducerBuilding } from "./buildings/item_producer"; import { MetaItemProducerBuilding } from "./buildings/item_producer";
import { BaseHUDPart } from "./hud/base_hud_part";
/** @enum {string} */ /** @enum {string} */
export const enumGameModeIds = { export const enumGameModeIds = {
@ -36,9 +37,10 @@ export class GameMode extends BasicSerializableObject {
/** /**
* @param {GameRoot} root * @param {GameRoot} root
* @param {string} [id=Regular] * @param {string} [id=Regular]
* @param {object|undefined} payload
*/ */
static create(root, id = enumGameModeIds.regular) { static create(root, id = enumGameModeIds.regular, payload = undefined) {
return new (gGameModeRegistry.findById(id))(root); return new (gGameModeRegistry.findById(id))(root, payload);
} }
/** /**
@ -47,7 +49,11 @@ export class GameMode extends BasicSerializableObject {
constructor(root) { constructor(root) {
super(); super();
this.root = root; this.root = root;
this.hiddenHudParts = {};
/**
* @type {Record<string, typeof BaseHUDPart>}
*/
this.additionalHudParts = {};
/** @type {typeof MetaBuilding[]} */ /** @type {typeof MetaBuilding[]} */
this.hiddenBuildings = [MetaItemProducerBuilding]; this.hiddenBuildings = [MetaItemProducerBuilding];
@ -78,14 +84,6 @@ export class GameMode extends BasicSerializableObject {
return this.constructor.getType(); 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 * @param {typeof MetaBuilding} building - Class name of building
* @returns {boolean} * @returns {boolean}

View File

@ -1,4 +1,4 @@
import { globalConfig, IS_MOBILE } from "../../core/config"; import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { Signal } from "../../core/signal"; import { Signal } from "../../core/signal";
import { KEYMAPPINGS } from "../key_action_mapper"; import { KEYMAPPINGS } from "../key_action_mapper";
@ -6,47 +6,16 @@ import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root"; import { GameRoot } from "../root";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { HUDBetaOverlay } from "./parts/beta_overlay"; import { HUDBetaOverlay } from "./parts/beta_overlay";
import { HUDBlueprintPlacer } from "./parts/blueprint_placer";
import { HUDBuildingsToolbar } from "./parts/buildings_toolbar"; import { HUDBuildingsToolbar } from "./parts/buildings_toolbar";
import { HUDBuildingPlacer } from "./parts/building_placer"; import { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDCatMemes } from "./parts/cat_memes";
import { HUDColorBlindHelper } from "./parts/color_blind_helper"; import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
import { HUDChangesDebugger } from "./parts/debug_changes"; import { HUDChangesDebugger } from "./parts/debug_changes";
import { HUDDebugInfo } from "./parts/debug_info"; import { HUDDebugInfo } from "./parts/debug_info";
import { HUDEntityDebugger } from "./parts/entity_debugger"; 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 { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDModeMenu } from "./parts/mode_menu"; import { enumNotificationType } from "./parts/notifications";
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 { HUDSettingsMenu } from "./parts/settings_menu"; 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 { 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"; import { TrailerMaker } from "./trailer_maker";
export class GameHUD { export class GameHUD {
@ -73,79 +42,30 @@ export class GameHUD {
unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()), unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
}; };
this.initParts({ this.parts = {
buildingsToolbar: HUDBuildingsToolbar, buildingsToolbar: new HUDBuildingsToolbar(this.root),
wiresToolbar: HUDWiresToolbar, buildingPlacer: new HUDBuildingPlacer(this.root),
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,
// Must always exist // Must always exist
pinnedShapes: HUDPinnedShapes, settingsMenu: new HUDSettingsMenu(this.root),
notifications: HUDNotifications, debugInfo: new HUDDebugInfo(this.root),
settingsMenu: HUDSettingsMenu, dialogs: new HUDModalDialogs(this.root),
debugInfo: HUDDebugInfo,
dialogs: HUDModalDialogs,
screenshotExporter: HUDScreenshotExporter,
shapeViewer: HUDShapeViewer,
wiresOverlay: HUDWiresOverlay,
layerPreview: HUDLayerPreview,
minerHighlight: HUDMinerHighlight,
tutorialVideoOffer: HUDTutorialVideoOffer,
// Typing hints // Typing hints
/* typehints:start */ /* typehints:start */
/** @type {HUDChangesDebugger} */ /** @type {HUDChangesDebugger} */
changesDebugger: null, changesDebugger: null,
/* typehints:end */ /* 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) { if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
this.parts.entityDebugger = new HUDEntityDebugger(this.root); 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) { if (G_IS_DEV && globalConfig.debug.renderChanges) {
this.parts.changesDebugger = new HUDChangesDebugger(this.root); 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) { if (this.root.app.settings.getAllSettings().vignette) {
this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root); this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root);
} }
@ -154,12 +74,17 @@ export class GameHUD {
this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root); this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root);
} }
this.parts.sandboxController = new HUDSandboxController(this.root);
if (!G_IS_RELEASE && !G_IS_DEV) { if (!G_IS_RELEASE && !G_IS_DEV) {
this.parts.betaOverlay = new HUDBetaOverlay(this.root); 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(); const frag = document.createDocumentFragment();
for (const key in this.parts) { for (const key in this.parts) {
this.parts[key].createElements(frag); this.parts[key].createElements(frag);
@ -180,21 +105,6 @@ export class GameHUD {
/* dev:end*/ /* 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 * Attempts to close all overlays
*/ */

View File

@ -275,12 +275,14 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
const worldPosition = this.root.camera.screenToWorld(mousePosition); const worldPosition = this.root.camera.screenToWorld(mousePosition);
// Draw peeker // Draw peeker
if (this.root.hud.parts.layerPreview) {
this.root.hud.parts.layerPreview.renderPreview( this.root.hud.parts.layerPreview.renderPreview(
parameters, parameters,
worldPosition, worldPosition,
1 / this.root.camera.zoomLevel 1 / this.root.camera.zoomLevel
); );
} }
}
/** /**
* @param {DrawParameters} parameters * @param {DrawParameters} parameters

View File

@ -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() {}
}

View File

@ -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"],
`
<label>Zone</label>
<div class="buttons">
<div class="zoneWidth plusMinus">
<label>Width</label>
<button class="styledButton minus">-</button>
<span class="value"></span>
<button class="styledButton plus">+</button>
</div>
<div class="zoneHeight plusMinus">
<label>Height</label>
<button class="styledButton minus">-</button>
<span class="value"></span>
<button class="styledButton plus">+</button>
</div>
</div>`
);
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);
}
}

View File

@ -55,7 +55,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
*/ */
deserialize(data) { deserialize(data) {
if (!data || !data.shapes || !Array.isArray(data.shapes)) { 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; this.pinnedShapes = data.shapes;
} }

View File

@ -1,11 +1,11 @@
import { makeDiv } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
export class HUDModeMenuBack extends BaseHUDPart { export class HUDPuzzleBackToMenu extends BaseHUDPart {
createElements(parent) { createElements(parent) {
const key = this.root.gameMode.getId(); 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 = document.createElement("button");
this.button.classList.add("button"); this.button.classList.add("button");
this.element.appendChild(this.button); this.element.appendChild(this.button);

View File

@ -1,19 +1,17 @@
import { makeDiv } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzleEditorControls extends BaseHUDPart { export class HUDPuzzleEditorControls extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorControls"); this.element = makeDiv(parent, "ingame_HUD_PuzzleEditorControls");
this.element.innerHTML = ` this.element.innerHTML = T.ingame.puzzleEditorControls.instructions
.map(text => `<span>${text}</span>`)
<span>1. Build constant producers to generate resources.</span> .join("");
<span>2. Build goal acceptors and deliver shapes to set the puzzle goals.</span>
<span>3. Once you are done, press 'Playtest' to validate your puzzle.</span>
`;
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle"); this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle");
this.titleElement.innerText = "Puzzle Editor"; this.titleElement.innerText = T.ingame.puzzleEditorControls.title;
} }
initialize() {} initialize() {}

View File

@ -16,7 +16,7 @@ import { BaseHUDPart } from "../base_hud_part";
const trim = require("trim"); const trim = require("trim");
const logger = createLogger("puzzle-review"); const logger = createLogger("puzzle-review");
export class HUDPuzzleReview extends BaseHUDPart { export class HUDPuzzleEditorReview extends BaseHUDPart {
constructor(root) { constructor(root) {
super(root); super(root);
@ -27,7 +27,7 @@ export class HUDPuzzleReview extends BaseHUDPart {
createElements(parent) { createElements(parent) {
const key = this.root.gameMode.getId(); 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 = document.createElement("button");
this.button.classList.add("button"); this.button.classList.add("button");
this.button.textContent = T.puzzleMenu.reviewPuzzle; this.button.textContent = T.puzzleMenu.reviewPuzzle;

View File

@ -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"],
`
<label>${T.ingame.puzzleEditorSettings.zoneTitle}</label>
<div class="buttons">
<div class="zoneWidth plusMinus">
<label>${T.ingame.puzzleEditorSettings.zoneWidth}</label>
<button class="styledButton minus">-</button>
<span class="value"></span>
<button class="styledButton plus">+</button>
</div>
<div class="zoneHeight plusMinus">
<label>${T.ingame.puzzleEditorSettings.zoneHeight}</label>
<button class="styledButton minus">-</button>
<span class="value"></span>
<button class="styledButton plus">+</button>
</div>
<button class="styledButton trim">${T.ingame.puzzleEditorSettings.trimZone}</button>
</div>`
);
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);
}
}

View File

@ -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() {}
}

View File

@ -118,7 +118,9 @@ export class HUDSandboxController extends BaseHUDPart {
// Clear all shapes of this level // Clear all shapes of this level
hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0; hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0;
if (this.root.hud.parts.pinnedShapes) {
this.root.hud.parts.pinnedShapes.rerenderFull(); this.root.hud.parts.pinnedShapes.rerenderFull();
}
// Compute gained rewards // Compute gained rewards
hubGoals.gainedRewards = {}; hubGoals.gainedRewards = {};
@ -149,19 +151,8 @@ export class HUDSandboxController extends BaseHUDPart {
this.domAttach = new DynamicDomAttach(this.root, this.element); 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() { toggle() {
if (!this.visible && !this.isAvailable()) { if (!this.visible) {
return; return;
} }
this.visible = !this.visible; this.visible = !this.visible;

View File

@ -5,13 +5,8 @@ import { GameRoot } from "../root";
import { Rectangle } from "../../core/rectangle"; import { Rectangle } from "../../core/rectangle";
import { types } from "../../savegame/serialization"; import { types } from "../../savegame/serialization";
import { enumGameModeTypes, GameMode } from "../game_mode"; import { enumGameModeTypes, GameMode } from "../game_mode";
import { HUDGameMenu } from "../hud/parts/game_menu"; import { HUDPuzzleBackToMenu } from "../hud/parts/puzzle_back_to_menu";
import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial"; import { HUDPuzzleDLCLogo } from "../hud/parts/puzzle_dlc_logo";
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";
export class PuzzleGameMode extends GameMode { export class PuzzleGameMode extends GameMode {
static getType() { static getType() {
@ -32,22 +27,13 @@ export class PuzzleGameMode extends GameMode {
const data = this.getSaveData(); const data = this.getSaveData();
this.hiddenHudParts = { this.additionalHudParts = {
[HUDGameMenu.name]: false, puzzleBackToMenu: HUDPuzzleBackToMenu,
[HUDMassSelector.name]: false, puzzleDlcLogo: HUDPuzzleDLCLogo,
[HUDInteractiveTutorial.name]: false,
[HUDKeybindingOverlay.name]: false,
[HUDPartTutorialHints.name]: false,
[HUDPinnedShapes.name]: false,
[HUDWaypoints.name]: false,
}; };
this.setDimensions(data.zoneWidth, data.zoneHeight); this.zoneWidth = data.zoneWidth || 8;
} this.zoneHeight = data.zoneHeight || 6;
setDimensions(w = 16, h = 9) {
this.zoneWidth = w < 2 ? 2 : w;
this.zoneHeight = h < 2 ? 2 : h;
} }
getSaveData() { getSaveData() {
@ -58,16 +44,12 @@ export class PuzzleGameMode extends GameMode {
return save.gameMode.data; return save.gameMode.data;
} }
createCenteredRectangle(width, height) {
return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height);
}
getCameraBounds() { getCameraBounds() {
return this.createCenteredRectangle(this.zoneWidth + 20, this.zoneHeight + 20); return Rectangle.centered(this.zoneWidth + 20, this.zoneHeight + 20);
} }
getBuildableZones() { getBuildableZones() {
return [this.createCenteredRectangle(this.zoneWidth, this.zoneHeight)]; return [Rectangle.centered(this.zoneWidth, this.zoneHeight)];
} }
hasHub() { hasHub() {

View File

@ -19,6 +19,10 @@ import { MetaVirtualProcessorBuilding } from "../buildings/virtual_processor";
import { MetaAnalyzerBuilding } from "../buildings/analyzer"; import { MetaAnalyzerBuilding } from "../buildings/analyzer";
import { MetaComparatorBuilding } from "../buildings/comparator"; import { MetaComparatorBuilding } from "../buildings/comparator";
import { MetaTransistorBuilding } from "../buildings/transistor"; 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 { export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() { static getId() {
@ -33,8 +37,6 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
constructor(root) { constructor(root) {
super(root); super(root);
this.playtest = false;
this.hiddenBuildings = [ this.hiddenBuildings = [
MetaStorageBuilding, MetaStorageBuilding,
MetaReaderBuilding, MetaReaderBuilding,
@ -53,32 +55,10 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
MetaComparatorBuilding, MetaComparatorBuilding,
MetaTransistorBuilding, MetaTransistorBuilding,
]; ];
}
adjustZone(w = 0, h = 0) { this.additionalHudParts.puzzleEditorControls = HUDPuzzleEditorControls;
// @todo notify user when zone cannot be shrunk this.additionalHudParts.puzzleEditorReview = HUDPuzzleEditorReview;
if (this.zoneWidth + w <= 0) { this.additionalHudParts.puzzleEditorSettings = HUDPuzzleEditorSettings;
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;
} }
getIsEditor() { getIsEditor() {

View File

@ -2,16 +2,65 @@
import { GameRoot } from "../root"; import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
import { PuzzleGameMode } from "./puzzle";
import { enumGameModeIds } from "../game_mode"; 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 { export class PuzzlePlayGameMode extends PuzzleGameMode {
static getId() { static getId() {
return enumGameModeIds.puzzlePlay; 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); 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;
} }
} }

View File

@ -5,15 +5,37 @@ import { GameRoot } from "../root";
import { findNiceIntegerValue } from "../../core/utils"; import { findNiceIntegerValue } from "../../core/utils";
import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor"; 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 { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { enumHubGoalRewards } from "../tutorial_goals"; import { enumHubGoalRewards } from "../tutorial_goals";
import { HUDPuzzleDLCLogo } from "../hud/parts/puzzle_dlc_logo"; import { HUDWiresToolbar } from "../hud/parts/wires_toolbar";
import { HUDPuzzleEditorControls } from "../hud/parts/puzzle_editor_controls"; 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 {{ /** @typedef {{
* shape: string, * shape: string,
@ -517,15 +539,48 @@ export class RegularGameMode extends GameMode {
constructor(root) { constructor(root) {
super(root); super(root);
this.hiddenHudParts = { this.additionalHudParts = {
[HUDModeMenuBack.name]: false, wiresToolbar: HUDWiresToolbar,
[HUDPuzzleReview.name]: false, blueprintPlacer: HUDBlueprintPlacer,
[HUDModeMenu.name]: false, unlockNotification: HUDUnlockNotification,
[HUDModeSettings.name]: false, massSelector: HUDMassSelector,
[HUDPuzzleDLCLogo.name]: false, shop: HUDShop,
[HUDPuzzleEditorControls.name]: false, 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]; this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding];
} }

View File

@ -77,7 +77,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
); );
} }
if (!this.root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) { if (this.root.hud.parts.pinnedShapes) {
items.push( items.push(
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key => ...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key) this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)

View File

@ -49,15 +49,10 @@ export class ZoneSystem extends GameSystem {
transformed.y += tile.y; transformed.y += tile.y;
} }
for (const zone of zones) { if (!zones.some(zone => zone.intersectsFully(transformed))) {
const intersection = zone.getIntersection(transformed);
if (intersection && intersection.w * intersection.h === transformed.w * transformed.h) {
return;
}
}
return STOP_PROPAGATION; return STOP_PROPAGATION;
} }
}
/** /**
* Draws the zone * Draws the zone
@ -84,8 +79,7 @@ export class ZoneSystem extends GameSystem {
context.lineWidth = 2; context.lineWidth = 2;
context.strokeStyle = THEME.map.zone.borderSolid; context.strokeStyle = THEME.map.zone.borderSolid;
context.beginPath(); 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(); context.stroke();
const outer = zone; const outer = zone;

View File

@ -51,9 +51,7 @@
}, },
"zone": { "zone": {
"background": "#fff", "borderSolid": "rgba(23, 192, 255, 1)",
"border": "rgba(23, 192, 255, 0.1)",
"borderSolid": "rgba(23, 192, 255, 0.7)",
"outerColor": "rgba(240, 240, 255, 0.5)" "outerColor": "rgba(240, 240, 255, 0.5)"
} }
}, },

View File

@ -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 { enumConstantSignalType } from "../game/components/constant_signal";
import { StaticMapEntityComponent } from "../game/components/static_map_entity"; import { StaticMapEntityComponent } from "../game/components/static_map_entity";
import { ShapeItem } from "../game/items/shape_item"; import { ShapeItem } from "../game/items/shape_item";
import { GameRoot } from "../game/root";
export class PuzzleSerializer { export class PuzzleSerializer {
/** /**
* Serializes the game root into a dump * Serializes the game root into a dump
* @param {GameRoot} root * @param {GameRoot} root
* @param {boolean=} sanityChecks Whether to check for validity * @returns {import("./savegame_typedefs").PuzzleGameData}
* @returns {object}
*/ */
generateDumpFromGameRoot(root, sanityChecks = true) { generateDumpFromGameRoot(root) {
console.log("serializing", root); console.log("serializing", root);
/**
* @type {import("./savegame_typedefs").PuzzleGameData["buildings"]}
*/
let buildings = []; let buildings = [];
for (const entity of root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) { for (const entity of root.entityMgr.getAllWithComponent(StaticMapEntityComponent)) {
@ -51,9 +56,15 @@ export class PuzzleSerializer {
} }
} }
const mode = /** @type {PuzzleGameMode} */ (root.gameMode);
return { return {
version: 1,
buildings, buildings,
bounds: root.gameMode.getBuildableZones()[0], bounds: {
w: mode.zoneWidth,
h: mode.zoneHeight,
},
}; };
} }
} }

View File

@ -40,12 +40,8 @@ export class SavegameSerializer {
hubGoals: root.hubGoals.serialize(), hubGoals: root.hubGoals.serialize(),
entities: this.internal.serializeEntityArray(root.entityMgr.entities), entities: this.internal.serializeEntityArray(root.entityMgr.entities),
beltPaths: root.systemMgr.systems.belt.serializePaths(), beltPaths: root.systemMgr.systems.belt.serializePaths(),
pinnedShapes: root.gameMode.isHudPartExcluded(HUDPinnedShapes.name) pinnedShapes: root.hud.parts.pinnedShapes ? root.hud.parts.pinnedShapes.serialize() : null,
? null waypoints: root.hud.parts.waypoints ? root.hud.parts.waypoints.serialize() : null,
: root.hud.parts.pinnedShapes.serialize(),
waypoints: root.gameMode.isHudPartExcluded(HUDWaypoints.name)
? null
: root.hud.parts.waypoints.serialize(),
}; };
if (G_IS_DEV) { if (G_IS_DEV) {
@ -142,11 +138,11 @@ export class SavegameSerializer {
errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities); errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities);
errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths); 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); 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); errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
} }

View File

@ -41,4 +41,46 @@
* }} SavegamesData * }} 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 {}; export default {};

View File

@ -45,6 +45,9 @@ export class GameCreationPayload {
/** @type {Savegame} */ /** @type {Savegame} */
this.savegame; this.savegame;
/** @type {object|undefined} */
this.gameModeParameters;
} }
} }

View File

@ -7,17 +7,6 @@ import { T } from "../translations";
const categories = ["levels", "new", "topRated", "myPuzzles"]; const categories = ["levels", "new", "topRated", "myPuzzles"];
/**
* @typedef {{
* shortKey: string;
* upvotes: number;
* playcount: number;
* title: string;
* author: string;
* completed: boolean;
* }} PuzzleMetadata
*/
const SAMPLE_PUZZLE = { const SAMPLE_PUZZLE = {
shortKey: "CuCuCuCu", shortKey: "CuCuCuCu",
upvotes: 10000, upvotes: 10000,
@ -26,6 +15,7 @@ const SAMPLE_PUZZLE = {
author: "verylongsteamnamewhichbreaks", author: "verylongsteamnamewhichbreaks",
completed: false, completed: false,
}; };
const BUILTIN_PUZZLES = [ const BUILTIN_PUZZLES = [
{ ...SAMPLE_PUZZLE, completed: true }, { ...SAMPLE_PUZZLE, completed: true },
{ ...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) { renderPuzzles(puzzles) {
const container = this.htmlElement.querySelector("#mainContainer"); const container = this.htmlElement.querySelector("#mainContainer");
@ -191,6 +181,8 @@ export class PuzzleMenuState extends TextualGameState {
elem.appendChild(icon); elem.appendChild(icon);
container.appendChild(elem); 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)); 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() { onEnter() {
this.selectCategory("levels"); this.selectCategory("levels");
@ -209,7 +244,8 @@ export class PuzzleMenuState extends TextualGameState {
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), this.createNewPuzzle); this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), this.createNewPuzzle);
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) { if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
this.createNewPuzzle(); // this.createNewPuzzle();
this.playPuzzle(SAMPLE_PUZZLE);
} }
} }

View File

@ -298,6 +298,10 @@ dialogs:
placeholderName: Puzzle Title 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: ingame:
# This is shown in the top left corner and displays useful keybindings in # This is shown in the top left corner and displays useful keybindings in
# every situation # every situation
@ -522,6 +526,22 @@ ingame:
title: Support me title: Support me
desc: I develop the game in my spare time! 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 <strong>Constant Producers</strong> 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 <strong>Goal Acceptors</strong>
- 3. Once a Goal Acceptor receives a shape for a certain amount of time, it <strong>saves it as a goal</strong> that the player must produce later (Indicated by the <strong>green badge</strong>).
- 4. Once you click review, your puzzle will be validated and you can publish it.
- 5. Upon release, <strong>all buildings will be removed</strong> 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 # All shop upgrades
shopUpgrades: shopUpgrades:
belt: belt:
@ -749,12 +769,12 @@ buildings:
constant_producer: constant_producer:
default: default:
name: &constant_producer Constant Producer 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: goal_acceptor:
default: default:
name: &goal_acceptor Goal Acceptor 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: storyRewards:
# Those are the rewards gained from completing the store # Those are the rewards gained from completing the store