diff --git a/res/ui/building_icons/constant_producer.png b/res/ui/building_icons/constant_producer.png new file mode 100644 index 00000000..ca267ea6 Binary files /dev/null and b/res/ui/building_icons/constant_producer.png differ diff --git a/res/ui/building_tutorials/constant_producer.png b/res/ui/building_tutorials/constant_producer.png new file mode 100644 index 00000000..b0d15387 Binary files /dev/null and b/res/ui/building_tutorials/constant_producer.png differ diff --git a/res_raw/sprites/blueprints/constant_producer.png b/res_raw/sprites/blueprints/constant_producer.png new file mode 100644 index 00000000..2288c07d Binary files /dev/null and b/res_raw/sprites/blueprints/constant_producer.png differ diff --git a/res_raw/sprites/buildings/constant_producer.png b/res_raw/sprites/buildings/constant_producer.png new file mode 100644 index 00000000..573ef372 Binary files /dev/null and b/res_raw/sprites/buildings/constant_producer.png differ diff --git a/src/css/resources.scss b/src/css/resources.scss index 5bb3ea99..041c9765 100644 --- a/src/css/resources.scss +++ b/src/css/resources.scss @@ -1,6 +1,6 @@ $buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire, constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor, reader, storage, - transistor, analyzer, comparator, item_producer; + transistor, analyzer, comparator, item_producer, constant_producer; @each $building in $buildings { [data-icon="building_icons/#{$building}.png"] { @@ -13,7 +13,8 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2, cutter, cutter-quad, rotater, rotater-ccw, stacker, mixer, painter-double, painter-quad, trash, storage, reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not, logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, item_producer, - virtual_processor-stacker, virtual_processor-painter, wire-second, painter, painter-mirrored, comparator; + constant_producer, virtual_processor-stacker, virtual_processor-painter, wire-second, painter, + painter-mirrored, comparator; @each $building in $buildingsAndVariants { [data-icon="building_tutorials/#{$building}.png"] { /* @load-async */ diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 1b63d688..5dbcf0e9 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -242,6 +242,16 @@ align-items: center; } + .modeButtons { + display: grid; + grid-template-columns: repeat(2, 1fr); + @include S(grid-column-gap, 10px); + align-items: start; + height: 100%; + width: 100%; + box-sizing: border-box; + } + .browserWarning { @include S(margin-bottom, 10px); background-color: $colorRedBright; @@ -285,6 +295,18 @@ @include S(margin-left, 15px); } + .playModeButton { + @include IncreasedClickArea(0px); + @include S(margin-top, 15px); + @include S(margin-left, 15px); + } + + .editModeButton { + @include IncreasedClickArea(0px); + @include S(margin-top, 15px); + @include S(margin-left, 15px); + } + .savegames { @include S(max-height, 105px); overflow-y: auto; @@ -452,7 +474,7 @@ .buttons { display: grid; grid-template-columns: repeat(2, 1fr); - grid-column-gap: 10px; + @include S(grid-column-gap, 10px); align-items: start; height: 100%; width: 100%; diff --git a/src/js/game/buildings/constant_producer.js b/src/js/game/buildings/constant_producer.js new file mode 100644 index 00000000..68889674 --- /dev/null +++ b/src/js/game/buildings/constant_producer.js @@ -0,0 +1,35 @@ +/* typehints:start */ +import { Entity } from "../entity"; +/* typehints:end */ + +import { enumDirection, Vector } from "../../core/vector"; +import { ItemEjectorComponent } from "../components/item_ejector"; +import { enumItemProducerType, ItemProducerComponent } from "../components/item_producer"; +import { MetaBuilding } from "../meta_building"; + +export class MetaConstantProducerBuilding extends MetaBuilding { + constructor() { + super("constant_producer"); + } + + getSilhouetteColor() { + return "#bfd630"; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent( + new ItemEjectorComponent({ + slots: [{ pos: new Vector(0, 0), direction: enumDirection.top }], + }) + ); + entity.addComponent( + new ItemProducerComponent({ + type: enumItemProducerType.wireless, + }) + ); + } +} diff --git a/src/js/game/buildings/item_producer.js b/src/js/game/buildings/item_producer.js index 477ed603..1140c8f1 100644 --- a/src/js/game/buildings/item_producer.js +++ b/src/js/game/buildings/item_producer.js @@ -39,6 +39,6 @@ export class MetaItemProducerBuilding extends MetaBuilding { ], }) ); - entity.addComponent(new ItemProducerComponent()); + entity.addComponent(new ItemProducerComponent({})); } } diff --git a/src/js/game/camera.js b/src/js/game/camera.js index e15fd156..a62745e8 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -711,7 +711,6 @@ export class Camera extends BasicSerializableObject { this.didMoveSinceTouchStart = this.didMoveSinceTouchStart || delta.length() > 0; this.center = this.center.add(delta); - this.clampPosition(this.center); this.touchPostMoveVelocity = this.touchPostMoveVelocity .multiplyScalar(velocitySmoothing) @@ -763,16 +762,15 @@ export class Camera extends BasicSerializableObject { * Clamps x, y position within set boundaries * @param {Vector} vector */ - clampPosition(vector) { - if (!this.root.gameMode.hasBoundaries()) { + clampToBounds(vector) { + if (!this.root.gameMode.hasBounds()) { return; } - const width = this.root.gameMode.getBoundaryWidth(); - const height = this.root.gameMode.getBoundaryHeight(); + const bounds = this.root.gameMode.getBounds().allScaled(globalConfig.tileSize); - vector.x = clamp(vector.x, -width, width); - vector.y = clamp(vector.y, -height, height); + vector.x = clamp(vector.x, bounds.x, bounds.x + bounds.w); + vector.y = clamp(vector.y, bounds.y, bounds.y + bounds.h); } /** @@ -878,6 +876,7 @@ export class Camera extends BasicSerializableObject { // Panning this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06); this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel)); + this.clampToBounds(this.center); } } @@ -943,7 +942,7 @@ export class Camera extends BasicSerializableObject { ) ); - this.clampPosition(this.center); + this.clampToBounds(this.center); } /** @@ -1029,6 +1028,8 @@ export class Camera extends BasicSerializableObject { this.center.x += moveAmount * forceX * movementSpeed; this.center.y += moveAmount * forceY * movementSpeed; + + this.clampToBounds(this.center); } } } diff --git a/src/js/game/components/item_producer.js b/src/js/game/components/item_producer.js index ef3571e2..576b6813 100644 --- a/src/js/game/components/item_producer.js +++ b/src/js/game/components/item_producer.js @@ -1,7 +1,22 @@ import { Component } from "../component"; +/** @enum {string} */ +export const enumItemProducerType = { + wired: "wired", + wireless: "wireless", +}; + export class ItemProducerComponent extends Component { static getId() { return "ItemProducer"; } + + /** + * @param {object} options + * @prop {type=} options.type + */ + constructor({ type = enumItemProducerType.wired }) { + super(); + this.type = type; + } } diff --git a/src/js/game/game_mode.js b/src/js/game/game_mode.js index ece52e20..f3aea0f5 100644 --- a/src/js/game/game_mode.js +++ b/src/js/game/game_mode.js @@ -1,5 +1,6 @@ /* typehints:start */ import { GameRoot } from "./root"; +import { Rectangle } from "../core/rectangle"; /* typehints:end */ import { gGameModeRegistry } from "../core/global_registries"; @@ -44,6 +45,8 @@ export class GameMode extends BasicSerializableObject { constructor(root) { super(); this.root = root; + this.hudParts = {}; + this.buildings = {}; } /** @returns {object} */ @@ -71,12 +74,12 @@ export class GameMode extends BasicSerializableObject { return this.constructor.getType(); } - /** - * @param {string} name - Class name of HUD Part - * @returns {boolean} - */ - isHudPartHidden(name) { - return false; + setBuildings(buildings) { + Object.assign(this.buildings, buildings); + } + + setHudParts(parts) { + Object.assign(this.hudParts, parts); } /** @@ -84,7 +87,15 @@ export class GameMode extends BasicSerializableObject { * @returns {boolean} */ isHudPartExcluded(name) { - return false; + return this.hudParts[name] === false; + } + + /** + * @param {string} name - Class name of building + * @returns {boolean} + */ + isBuildingExcluded(name) { + return this.buildings[name] === false; } /** @returns {boolean} */ @@ -92,11 +103,6 @@ export class GameMode extends BasicSerializableObject { return false; } - /** @returns {boolean} */ - hasHints() { - return true; - } - /** @returns {boolean} */ hasHub() { return true; @@ -108,7 +114,7 @@ export class GameMode extends BasicSerializableObject { } /** @returns {boolean} */ - hasBoundaries() { + hasBounds() { return false; } @@ -122,29 +128,24 @@ export class GameMode extends BasicSerializableObject { return 3.5; } - /** @returns {object} */ + /** @returns {Object} */ getUpgrades() { - return {}; + return { + belt: [], + miner: [], + processors: [], + painting: [], + }; } - /** @returns {number} */ - getZoneWidth() { - return 0; + /** @returns {?Rectangle} */ + getZone() { + return null; } - /** @returns {number} */ - getZoneHeight() { - return 0; - } - - /** @returns {number} */ - getBoundaryWidth() { - return Infinity; - } - - /** @returns {number} */ - getBoundaryHeight() { - return Infinity; + /** @returns {?Rectangle} */ + getBounds() { + return null; } /** @returns {array} */ diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index 185d2c06..121a0bcc 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -6,7 +6,6 @@ import { createLogger } from "../core/logging"; import { BeltSystem } from "./systems/belt"; import { ItemEjectorSystem } from "./systems/item_ejector"; import { MapResourcesSystem } from "./systems/map_resources"; -import { MapZoneSystem } from "./systems/map_zone"; import { MinerSystem } from "./systems/miner"; import { ItemProcessorSystem } from "./systems/item_processor"; import { UndergroundBeltSystem } from "./systems/underground_belt"; @@ -25,6 +24,7 @@ import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays"; import { BeltReaderSystem } from "./systems/belt_reader"; import { FilterSystem } from "./systems/filter"; import { ItemProducerSystem } from "./systems/item_producer"; +import { ZoneSystem } from "./systems/zone"; const logger = createLogger("game_system_manager"); @@ -47,9 +47,6 @@ export class GameSystemManager { /** @type {MapResourcesSystem} */ mapResources: null, - /** @type {MapZoneSystem} */ - mapZone: null, - /** @type {MinerSystem} */ miner: null, @@ -104,6 +101,9 @@ export class GameSystemManager { /** @type {ItemProducerSystem} */ itemProducer: null, + /** @type {ZoneSystem} */ + zone: null, + /* typehints:end */ }; this.systemUpdateOrder = []; @@ -142,9 +142,9 @@ export class GameSystemManager { add("itemEjector", ItemEjectorSystem); - add("mapResources", MapResourcesSystem); - - add("mapZone", MapZoneSystem); + if (this.root.gameMode.hasResources()) { + add("mapResources", MapResourcesSystem); + } add("hub", HubSystem); @@ -171,6 +171,10 @@ export class GameSystemManager { add("itemProcessorOverlays", ItemProcessorOverlaysSystem); + if (this.root.gameMode.hasZone()) { + add("zone", ZoneSystem); + } + logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); } diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 8df5c703..17741fc7 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -129,9 +129,14 @@ export class GameHUD { this.parts.changesDebugger = new HUDChangesDebugger(this.root); } - if (this.root.gameMode.hasHints() && this.root.app.settings.getAllSettings().offerHints) { - this.parts.tutorialHints = new HUDPartTutorialHints(this.root); - this.parts.interactiveTutorial = new HUDInteractiveTutorial(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) { @@ -177,7 +182,7 @@ export class GameHUD { for (let key in parts) { const Part = parts[key]; - if (!Part || this.root.gameMode.isHudPartExcluded(Part)) { + if (!Part || this.root.gameMode.isHudPartExcluded(Part.name)) { continue; } diff --git a/src/js/game/hud/parts/base_toolbar.js b/src/js/game/hud/parts/base_toolbar.js index b3f5abfc..01e9fafa 100644 --- a/src/js/game/hud/parts/base_toolbar.js +++ b/src/js/game/hud/parts/base_toolbar.js @@ -23,8 +23,8 @@ export class HUDBaseToolbar extends BaseHUDPart { ) { super(root); - this.primaryBuildings = primaryBuildings; - this.secondaryBuildings = secondaryBuildings; + this.primaryBuildings = this.filterBuildings(primaryBuildings); + this.secondaryBuildings = this.filterBuildings(secondaryBuildings); this.visibilityCondition = visibilityCondition; this.htmlElementId = htmlElementId; this.layer = layer; @@ -47,6 +47,24 @@ export class HUDBaseToolbar extends BaseHUDPart { this.element = makeDiv(parent, this.htmlElementId, ["ingame_buildingsToolbar"], ""); } + /** + * @param {Array} buildings + * @returns {Array} + */ + filterBuildings(buildings) { + const filtered = []; + + for (let i = 0; i < buildings.length; i++) { + if (this.root.gameMode.isBuildingExcluded(buildings[i].name)) { + continue; + } + + filtered.push(buildings[i]); + } + + return filtered; + } + /** * Returns all buildings * @returns {Array} diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index 05ffc795..67da616e 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -15,12 +15,13 @@ import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt"; import { HUDBaseToolbar } from "./base_toolbar"; import { MetaStorageBuilding } from "../../buildings/storage"; import { MetaItemProducerBuilding } from "../../buildings/item_producer"; -import { queryParamOptions } from "../../../core/query_parameters"; +import { MetaConstantProducerBuilding } from "../../buildings/constant_producer"; export class HUDBuildingsToolbar extends HUDBaseToolbar { constructor(root) { super(root, { primaryBuildings: [ + MetaConstantProducerBuilding, MetaBeltBuilding, MetaBalancerBuilding, MetaUndergroundBeltBuilding, @@ -31,7 +32,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar { MetaMixerBuilding, MetaPainterBuilding, MetaTrashBuilding, - ...(queryParamOptions.sandboxMode || G_IS_DEV ? [MetaItemProducerBuilding] : []), + MetaItemProducerBuilding, ], secondaryBuildings: [ MetaStorageBuilding, diff --git a/src/js/game/hud/parts/pinned_shapes.js b/src/js/game/hud/parts/pinned_shapes.js index f2ab6046..4a9fce0d 100644 --- a/src/js/game/hud/parts/pinned_shapes.js +++ b/src/js/game/hud/parts/pinned_shapes.js @@ -153,10 +153,6 @@ export class HUDPinnedShapes extends BaseHUDPart { * Rerenders the whole component */ rerenderFull() { - if (this.root.gameMode.isHudPartHidden(this.constructor.name)) { - return; - } - const currentGoal = this.root.hubGoals.currentGoal; const currentKey = currentGoal.definition.getHash(); diff --git a/src/js/game/hud/parts/waypoints.js b/src/js/game/hud/parts/waypoints.js index b21eeea0..2e0bc159 100644 --- a/src/js/game/hud/parts/waypoints.js +++ b/src/js/game/hud/parts/waypoints.js @@ -102,14 +102,12 @@ export class HUDWaypoints extends BaseHUDPart { /** @type {Array} */ this.waypoints = []; - if (this.root.gameMode.hasHub()) { - this.waypoints.push({ - label: null, - center: { x: 0, y: 0 }, - zoomLevel: 3, - layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(), - }); - } + this.waypoints.push({ + label: null, + center: { x: 0, y: 0 }, + zoomLevel: 3, + layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(), + }); // Create a buffer we can use to measure text this.dummyBuffer = makeOffscreenBuffer(1, 1, { diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 13f33d66..8ed35bfe 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -49,6 +49,9 @@ export const KEYMAPPINGS = { }, buildings: { + // Puzzle + constant_producer: { keyCode: 192 }, // "`" + // Primary Toolbar belt: { keyCode: key("1") }, balancer: { keyCode: key("2") }, @@ -262,6 +265,8 @@ export function getStringForKeyCode(code) { return "."; case 191: return "/"; + case 192: + return "`"; case 219: return "["; case 220: diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index 8543b376..bc9dcfbb 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -42,7 +42,7 @@ export class MapChunkView extends MapChunk { drawBackgroundLayer(parameters) { const systems = this.root.systemMgr.systems; if (this.root.gameMode.hasZone()) { - systems.mapZone.drawChunk(parameters, this); + systems.zone.drawChunk(parameters, this); } if (this.root.gameMode.hasResources()) { diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js index 0613103e..b1c16c2a 100644 --- a/src/js/game/meta_building_registry.js +++ b/src/js/game/meta_building_registry.js @@ -5,6 +5,7 @@ import { MetaAnalyzerBuilding } from "./buildings/analyzer"; import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer"; import { MetaBeltBuilding } from "./buildings/belt"; import { MetaComparatorBuilding } from "./buildings/comparator"; +import { MetaConstantProducerBuilding } from "./buildings/constant_producer"; import { MetaConstantSignalBuilding } from "./buildings/constant_signal"; import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter"; import { MetaDisplayBuilding } from "./buildings/display"; @@ -59,6 +60,7 @@ export function initMetaBuildingRegistry() { gMetaBuildingRegistry.register(MetaAnalyzerBuilding); gMetaBuildingRegistry.register(MetaComparatorBuilding); gMetaBuildingRegistry.register(MetaItemProducerBuilding); + gMetaBuildingRegistry.register(MetaConstantProducerBuilding); // Belt registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0); @@ -165,6 +167,9 @@ export function initMetaBuildingRegistry() { // Item producer registerBuildingVariant(61, MetaItemProducerBuilding); + // Constant producer + registerBuildingVariant(62, MetaConstantProducerBuilding); + // Propagate instances for (const key in gBuildingVariants) { gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass( diff --git a/src/js/game/modes/puzzle.js b/src/js/game/modes/puzzle.js index de3920e1..e9ad44ea 100644 --- a/src/js/game/modes/puzzle.js +++ b/src/js/game/modes/puzzle.js @@ -2,19 +2,22 @@ import { GameRoot } from "../root"; /* typehints:end */ -import { globalConfig } from "../../core/config"; +import { Rectangle } from "../../core/rectangle"; import { types } from "../../savegame/serialization"; -import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; import { enumGameModeTypes, GameMode } from "../game_mode"; +import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial"; +import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; +import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints"; +import { HUDWaypoints } from "../hud/parts/waypoints"; export class PuzzleGameMode extends GameMode { static getType() { return enumGameModeTypes.puzzle; } + /** @returns {object} */ static getSchema() { return { - hiddenHudParts: types.keyValueMap(types.bool), zoneHeight: types.uint, zoneWidth: types.uint, }; @@ -23,18 +26,24 @@ export class PuzzleGameMode extends GameMode { /** @param {GameRoot} root */ constructor(root) { super(root); - } - initialize() { const data = this.getSaveData(); - this.type = this.getType(); - this.hiddenHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts(); - // this.excludedHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts(); - this.zoneHeight = data.zoneHeight || 3 * globalConfig.tileSize; - this.zoneWidth = data.zoneWidth || 4 * globalConfig.tileSize; - this.boundaryHeight = this.zoneHeight * 2; - this.boundaryWidth = this.zoneWidth * 2; + this.setHudParts({ + [HUDInteractiveTutorial.name]: false, + [HUDPartTutorialHints.name]: false, + [HUDPinnedShapes.name]: false, + [HUDWaypoints.name]: false, + }); + + this.setDimensions(data.zoneWidth, data.zoneHeight); + } + + setDimensions(w = 16, h = 9) { + this.zoneWidth = w < 2 ? 2 : w; + this.zoneHeight = h < 2 ? 2 : h; + this.boundsHeight = this.zoneHeight < 8 ? 8 : this.zoneHeight; + this.boundsWidth = this.zoneWidth < 8 ? 8 : this.zoneWidth; } getSaveData() { @@ -47,24 +56,52 @@ export class PuzzleGameMode extends GameMode { return save.gameMode.data; } - getDefaultHiddenHudParts() { - return { - [HUDPinnedShapes.name]: true, - }; + createCenteredRectangle(width, height) { + return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height); } - isHudPartHidden(name) { - return this.hiddenHudParts[name]; + getBounds() { + if (this.bounds) { + return this.bounds; + } + + this.bounds = this.createCenteredRectangle(this.boundsWidth, this.boundsHeight); + + return this.bounds; + } + + getZone() { + if (this.zone) { + return this.zone; + } + + this.zone = this.createCenteredRectangle(this.zoneWidth, this.zoneHeight); + + return this.zone; + } + + /** + * Overrides GameMode's implementation to treat buildings like a whitelist + * instead of a blacklist by default. + * @param {string} name - Class name of building + * @returns {boolean} + */ + isBuildingExcluded(name) { + return this.buildings[name] !== true; + } + + isInBounds(x, y) { + return this.bounds.containsPoint(x, y); + } + + isInZone(x, y) { + return this.zone.containsPoint(x, y); } hasZone() { return true; } - hasHints() { - return false; - } - hasHub() { return false; } @@ -73,27 +110,11 @@ export class PuzzleGameMode extends GameMode { return false; } - hasBoundaries() { + hasBounds() { return true; } getMinimumZoom() { return 1; } - - getBoundaryWidth() { - return this.boundaryWidth; - } - - getBoundaryHeight() { - return this.boundaryHeight; - } - - getZoneWidth() { - return this.zoneWidth; - } - - getZoneHeight() { - return this.zoneHeight; - } } diff --git a/src/js/game/modes/puzzle_edit.js b/src/js/game/modes/puzzle_edit.js index 5e2cf6fd..f2645a75 100644 --- a/src/js/game/modes/puzzle_edit.js +++ b/src/js/game/modes/puzzle_edit.js @@ -2,8 +2,9 @@ import { GameRoot } from "../root"; /* typehints:end */ -import { PuzzleGameMode } from "./puzzle"; +import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; import { enumGameModeIds } from "../game_mode"; +import { PuzzleGameMode } from "./puzzle"; export class PuzzleEditGameMode extends PuzzleGameMode { static getId() { @@ -13,5 +14,9 @@ export class PuzzleEditGameMode extends PuzzleGameMode { /** @param {GameRoot} root */ constructor(root) { super(root); + + this.setBuildings({ + [MetaConstantProducerBuilding.name]: true, + }); } } diff --git a/src/js/game/modes/puzzle_play.js b/src/js/game/modes/puzzle_play.js index a4a778be..a2d090fd 100644 --- a/src/js/game/modes/puzzle_play.js +++ b/src/js/game/modes/puzzle_play.js @@ -13,6 +13,5 @@ export class PuzzlePlayGameMode extends PuzzleGameMode { /** @param {GameRoot} root */ constructor(root) { super(root); - this.initialize(); } } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 5055ed2d..5a9e5f63 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -2,11 +2,13 @@ import { GameRoot } from "../root"; /* typehints:end */ +import { queryParamOptions } from "../../core/query_parameters"; import { findNiceIntegerValue } from "../../core/utils"; +import { MetaConstantProducerBuilding } from "../buildings/constant_producer"; +import { MetaItemProducerBuilding } from "../buildings/item_producer"; import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode"; import { ShapeDefinition } from "../shape_definition"; import { enumHubGoalRewards } from "../tutorial_goals"; -import { types } from "../../savegame/serialization"; /** @typedef {{ * shape: string, @@ -489,6 +491,11 @@ export class RegularGameMode extends GameMode { /** @param {GameRoot} root */ constructor(root) { super(root); + + this.setBuildings({ + [MetaConstantProducerBuilding.name]: false, + [MetaItemProducerBuilding.name]: queryParamOptions.sandboxMode || G_IS_DEV, + }); } /** diff --git a/src/js/game/systems/item_producer.js b/src/js/game/systems/item_producer.js index 52edf5d1..a1ebb331 100644 --- a/src/js/game/systems/item_producer.js +++ b/src/js/game/systems/item_producer.js @@ -1,7 +1,12 @@ -import { ItemProducerComponent } from "../components/item_producer"; +/* typehints:start */ +import { GameRoot } from "../root"; +/* typehints:end */ + +import { enumItemProducerType, ItemProducerComponent } from "../components/item_producer"; import { GameSystemWithFilter } from "../game_system_with_filter"; export class ItemProducerSystem extends GameSystemWithFilter { + /** @param {GameRoot} root */ constructor(root) { super(root, [ItemProducerComponent]); } @@ -9,16 +14,21 @@ export class ItemProducerSystem extends GameSystemWithFilter { update() { for (let i = 0; i < this.allEntities.length; ++i) { const entity = this.allEntities[i]; - const pinsComp = entity.components.WiredPins; - const pin = pinsComp.slots[0]; - const network = pin.linkedNetwork; - if (!network || !network.hasValue()) { - continue; + if (entity.components.ItemProducer.type === enumItemProducerType.wired) { + const pinsComp = entity.components.WiredPins; + const pin = pinsComp.slots[0]; + const network = pin.linkedNetwork; + + if (!network || !network.hasValue()) { + continue; + } + + const ejectorComp = entity.components.ItemEjector; + ejectorComp.tryEject(0, network.currentValue); + } else { + // TODO: entity w/ wireless item producer (e.g. ConstantProducer) } - - const ejectorComp = entity.components.ItemEjector; - ejectorComp.tryEject(0, network.currentValue); } } } diff --git a/src/js/game/systems/map_zone.js b/src/js/game/systems/map_zone.js deleted file mode 100644 index 90e9bf26..00000000 --- a/src/js/game/systems/map_zone.js +++ /dev/null @@ -1,60 +0,0 @@ -/* typehints:start */ -import { DrawParameters } from "../../core/draw_parameters"; -import { MapChunkView } from "../map_chunk_view"; -/* typehints:end */ - -import { globalConfig } from "../../core/config"; -import { drawSpriteClipped } from "../../core/draw_utils"; -import { GameSystem } from "../game_system"; -import { THEME } from "../theme"; - -export class MapZoneSystem extends GameSystem { - /** - * Draws the map resources - * @param {DrawParameters} parameters - * @param {MapChunkView} chunk - */ - drawChunk(parameters, chunk) { - const width = this.root.gameMode.getZoneWidth(); - const height = this.root.gameMode.getZoneHeight(); - - const zoneChunkBackground = this.root.buffers.getForKey({ - key: "mapzonebg", - subKey: chunk.renderKey, - w: width, - h: height, - dpi: 1, - redrawMethod: this.generateChunkBackground.bind(this, chunk), - }); - - parameters.context.imageSmoothingEnabled = false; - drawSpriteClipped({ - parameters, - sprite: zoneChunkBackground, - x: -width, - y: -height, - w: this.root.gameMode.getBoundaryWidth(), - h: this.root.gameMode.getBoundaryHeight(), - originalW: width, - originalH: height, - }); - parameters.context.imageSmoothingEnabled = true; - } - - /** - * @param {MapChunkView} chunk - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} context - * @param {number} w - * @param {number} h - * @param {number} dpi - */ - generateChunkBackground(chunk, canvas, context, w, h, dpi) { - context.clearRect(0, 0, w, h); - - context.fillStyle = THEME.map.zone.background; - context.strokeStyle = THEME.map.zone.border; - context.fillRect(0, 0, w, h); - context.strokeRect(0, 0, w, h); - } -} diff --git a/src/js/game/systems/zone.js b/src/js/game/systems/zone.js new file mode 100644 index 00000000..01154602 --- /dev/null +++ b/src/js/game/systems/zone.js @@ -0,0 +1,53 @@ +/* typehints:start */ +import { DrawParameters } from "../../core/draw_parameters"; +import { MapChunkView } from "../map_chunk_view"; +import { GameRoot } from "../root"; +/* typehints:end */ + +import { globalConfig } from "../../core/config"; +import { STOP_PROPAGATION } from "../../core/signal"; +import { GameSystem } from "../game_system"; +import { THEME } from "../theme"; + +export class ZoneSystem extends GameSystem { + /** @param {GameRoot} root */ + constructor(root) { + super(root); + + this.root.signals.prePlacementCheck.add(this.prePlacementCheck, this); + } + + prePlacementCheck(entity, tile = null) { + const staticComp = entity.components.StaticMapEntity; + + if (!staticComp) { + return; + } + + const zone = this.root.gameMode.getZone().expandedInAllDirections(-1); + const transformed = staticComp.getTileSpaceBounds(); + + if (zone.containsRect(transformed)) { + return STOP_PROPAGATION; + } + } + + /** + * Draws the zone + * @param {DrawParameters} parameters + * @param {MapChunkView} chunk + */ + drawChunk(parameters, chunk) { + const zone = this.root.gameMode.getZone().allScaled(globalConfig.tileSize); + + parameters.context.globalAlpha = 0.1; + parameters.context.fillStyle = THEME.map.zone.background; + parameters.context.fillRect(zone.x, zone.y, zone.w, zone.h); + + parameters.context.globalAlpha = 0.9; + parameters.context.strokeStyle = THEME.map.zone.border; + parameters.context.strokeRect(zone.x, zone.y, zone.w, zone.h); + + parameters.context.globalAlpha = 1; + } +} diff --git a/src/js/savegame/savegame_serializer.js b/src/js/savegame/savegame_serializer.js index 1f2987af..fb36393e 100644 --- a/src/js/savegame/savegame_serializer.js +++ b/src/js/savegame/savegame_serializer.js @@ -2,6 +2,8 @@ import { ExplainedResult } from "../core/explained_result"; import { createLogger } from "../core/logging"; import { gComponentRegistry } from "../core/global_registries"; import { SerializerInternal } from "./serializer_internal"; +import { HUDPinnedShapes } from "../game/hud/parts/pinned_shapes"; +import { HUDWaypoints } from "../game/hud/parts/waypoints"; /** * @typedef {import("../game/component").Component} Component @@ -36,10 +38,14 @@ export class SavegameSerializer { gameMode: root.gameMode.serialize(), entityMgr: root.entityMgr.serialize(), hubGoals: root.hubGoals.serialize(), - pinnedShapes: root.hud.parts.pinnedShapes.serialize(), - waypoints: root.hud.parts.waypoints.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(), }; if (G_IS_DEV) { @@ -133,11 +139,17 @@ export class SavegameSerializer { errorReason = errorReason || root.map.deserialize(savegame.map); errorReason = errorReason || root.gameMode.deserialize(savegame.gameMode); errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root); - errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes); - errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints); errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities); errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths); + if (!root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) { + errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes); + } + + if (!root.gameMode.isHudPartExcluded(HUDWaypoints.name)) { + errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints); + } + // Check for errors if (errorReason) { return ExplainedResult.bad(errorReason); diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index e5f9c20b..3754ee36 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -333,10 +333,13 @@ export class MainMenuState extends GameState { const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons"); removeAllChildren(buttonContainer); - const playButtonElement = makeButtonElement(["playButton", "styledButton"], T.mainMenu.play); + const playButtonElement = makeButtonElement(["playModeButton", "styledButton"], T.puzzleMenu.play); + const editButtonElement = makeButtonElement(["editModeButton", "styledButton"], T.puzzleMenu.edit); buttonContainer.appendChild(playButtonElement); this.trackClicks(playButtonElement, this.onPuzzlePlayButtonClicked); + buttonContainer.appendChild(editButtonElement); + this.trackClicks(editButtonElement, this.onPuzzleEditButtonClicked); const bottomButtonContainer = this.htmlElement.querySelector(".bottomContainer .buttons"); removeAllChildren(bottomButtonContainer); @@ -356,6 +359,15 @@ export class MainMenuState extends GameState { }); } + onPuzzleEditButtonClicked() { + const savegame = this.app.savegameMgr.createNewSavegame(); + + this.moveToState("InGameState", { + gameModeId: enumGameModeIds.puzzleEdit, + savegame, + }); + } + onPuzzleModeButtonClicked() { this.renderPuzzleModeMenu(); } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index e341a06a..e754db8a 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -119,6 +119,10 @@ mainMenu: puzzleMode: Puzzle Mode back: Back +puzzleMenu: + play: Play + edit: Edit + dialogs: buttons: ok: OK @@ -703,6 +707,11 @@ buildings: name: Item Producer description: Available in sandbox mode only, outputs the given signal from the wires layer on the regular layer. + constant_producer: + default: + name: &constant_producer Constant Producer + description: Outputs a shape, color or boolean (1 or 0) as specified. + storyRewards: # Those are the rewards gained from completing the store reward_cutter_and_trash: @@ -1130,6 +1139,7 @@ keybindings: analyzer: *analyzer comparator: *comparator item_producer: Item Producer (Sandbox) + constant_producer: *constant_producer # --- pipette: Pipette