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;
@include S(top, 10px);
@include S(left, 10px);
@include S(left, 0px);
display: flex;
flex-direction: column;

View File

@ -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 */

View File

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

View File

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

View File

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

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/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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: {

View File

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

View File

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

View File

@ -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("<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);
// 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

View File

@ -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<string, typeof BaseHUDPart>}
*/
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}

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 { 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
*/

View File

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

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

View File

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

View File

@ -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 = `
<span>1. Build constant producers to generate resources.</span>
<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.element.innerHTML = T.ingame.puzzleEditorControls.instructions
.map(text => `<span>${text}</span>`)
.join("");
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle");
this.titleElement.innerText = "Puzzle Editor";
this.titleElement.innerText = T.ingame.puzzleEditorControls.title;
}
initialize() {}

View File

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

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

View File

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

View File

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

View File

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

View File

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

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(
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)

View File

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

View File

@ -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)"
}
},

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 { 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,
},
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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