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

Add building, HUD exclusion, building exclusion, and refactor

- [WIP] Add ConstantProducer building that combines ConstantSignal
and ItemProducer functionality. Currently using temp assets.
- Add pre-placement check to the zone
- Use Rectangles for zone and boundary
- Simplify zone drawing
- Account for exclusion in savegame data
- [WIP] Add puzzle play and edit buttons in puzzle mode menu
This commit is contained in:
Greg Considine 2021-03-22 10:21:20 -04:00
parent 47abc24436
commit dae1ad3687
30 changed files with 363 additions and 187 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -1,6 +1,6 @@
$buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire, $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, 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 { @each $building in $buildings {
[data-icon="building_icons/#{$building}.png"] { [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, 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, 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, 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 { @each $building in $buildingsAndVariants {
[data-icon="building_tutorials/#{$building}.png"] { [data-icon="building_tutorials/#{$building}.png"] {
/* @load-async */ /* @load-async */

View File

@ -242,6 +242,16 @@
align-items: center; 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 { .browserWarning {
@include S(margin-bottom, 10px); @include S(margin-bottom, 10px);
background-color: $colorRedBright; background-color: $colorRedBright;
@ -285,6 +295,18 @@
@include S(margin-left, 15px); @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 { .savegames {
@include S(max-height, 105px); @include S(max-height, 105px);
overflow-y: auto; overflow-y: auto;
@ -452,7 +474,7 @@
.buttons { .buttons {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
grid-column-gap: 10px; @include S(grid-column-gap, 10px);
align-items: start; align-items: start;
height: 100%; height: 100%;
width: 100%; width: 100%;

View File

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

View File

@ -39,6 +39,6 @@ export class MetaItemProducerBuilding extends MetaBuilding {
], ],
}) })
); );
entity.addComponent(new ItemProducerComponent()); entity.addComponent(new ItemProducerComponent({}));
} }
} }

View File

@ -711,7 +711,6 @@ export class Camera extends BasicSerializableObject {
this.didMoveSinceTouchStart = this.didMoveSinceTouchStart || delta.length() > 0; this.didMoveSinceTouchStart = this.didMoveSinceTouchStart || delta.length() > 0;
this.center = this.center.add(delta); this.center = this.center.add(delta);
this.clampPosition(this.center);
this.touchPostMoveVelocity = this.touchPostMoveVelocity this.touchPostMoveVelocity = this.touchPostMoveVelocity
.multiplyScalar(velocitySmoothing) .multiplyScalar(velocitySmoothing)
@ -763,16 +762,15 @@ export class Camera extends BasicSerializableObject {
* Clamps x, y position within set boundaries * Clamps x, y position within set boundaries
* @param {Vector} vector * @param {Vector} vector
*/ */
clampPosition(vector) { clampToBounds(vector) {
if (!this.root.gameMode.hasBoundaries()) { if (!this.root.gameMode.hasBounds()) {
return; return;
} }
const width = this.root.gameMode.getBoundaryWidth(); const bounds = this.root.gameMode.getBounds().allScaled(globalConfig.tileSize);
const height = this.root.gameMode.getBoundaryHeight();
vector.x = clamp(vector.x, -width, width); vector.x = clamp(vector.x, bounds.x, bounds.x + bounds.w);
vector.y = clamp(vector.y, -height, height); vector.y = clamp(vector.y, bounds.y, bounds.y + bounds.h);
} }
/** /**
@ -878,6 +876,7 @@ export class Camera extends BasicSerializableObject {
// Panning // Panning
this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06); this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06);
this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel)); 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.x += moveAmount * forceX * movementSpeed;
this.center.y += moveAmount * forceY * movementSpeed; this.center.y += moveAmount * forceY * movementSpeed;
this.clampToBounds(this.center);
} }
} }
} }

View File

@ -1,7 +1,22 @@
import { Component } from "../component"; import { Component } from "../component";
/** @enum {string} */
export const enumItemProducerType = {
wired: "wired",
wireless: "wireless",
};
export class ItemProducerComponent extends Component { export class ItemProducerComponent extends Component {
static getId() { static getId() {
return "ItemProducer"; return "ItemProducer";
} }
/**
* @param {object} options
* @prop {type=} options.type
*/
constructor({ type = enumItemProducerType.wired }) {
super();
this.type = type;
}
} }

View File

@ -1,5 +1,6 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { Rectangle } from "../core/rectangle";
/* typehints:end */ /* typehints:end */
import { gGameModeRegistry } from "../core/global_registries"; import { gGameModeRegistry } from "../core/global_registries";
@ -44,6 +45,8 @@ export class GameMode extends BasicSerializableObject {
constructor(root) { constructor(root) {
super(); super();
this.root = root; this.root = root;
this.hudParts = {};
this.buildings = {};
} }
/** @returns {object} */ /** @returns {object} */
@ -71,12 +74,12 @@ export class GameMode extends BasicSerializableObject {
return this.constructor.getType(); return this.constructor.getType();
} }
/** setBuildings(buildings) {
* @param {string} name - Class name of HUD Part Object.assign(this.buildings, buildings);
* @returns {boolean} }
*/
isHudPartHidden(name) { setHudParts(parts) {
return false; Object.assign(this.hudParts, parts);
} }
/** /**
@ -84,7 +87,15 @@ export class GameMode extends BasicSerializableObject {
* @returns {boolean} * @returns {boolean}
*/ */
isHudPartExcluded(name) { 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} */ /** @returns {boolean} */
@ -92,11 +103,6 @@ export class GameMode extends BasicSerializableObject {
return false; return false;
} }
/** @returns {boolean} */
hasHints() {
return true;
}
/** @returns {boolean} */ /** @returns {boolean} */
hasHub() { hasHub() {
return true; return true;
@ -108,7 +114,7 @@ export class GameMode extends BasicSerializableObject {
} }
/** @returns {boolean} */ /** @returns {boolean} */
hasBoundaries() { hasBounds() {
return false; return false;
} }
@ -122,29 +128,24 @@ export class GameMode extends BasicSerializableObject {
return 3.5; return 3.5;
} }
/** @returns {object} */ /** @returns {Object<string, Array>} */
getUpgrades() { getUpgrades() {
return {}; return {
belt: [],
miner: [],
processors: [],
painting: [],
};
} }
/** @returns {number} */ /** @returns {?Rectangle} */
getZoneWidth() { getZone() {
return 0; return null;
} }
/** @returns {number} */ /** @returns {?Rectangle} */
getZoneHeight() { getBounds() {
return 0; return null;
}
/** @returns {number} */
getBoundaryWidth() {
return Infinity;
}
/** @returns {number} */
getBoundaryHeight() {
return Infinity;
} }
/** @returns {array} */ /** @returns {array} */

View File

@ -6,7 +6,6 @@ import { createLogger } from "../core/logging";
import { BeltSystem } from "./systems/belt"; import { BeltSystem } from "./systems/belt";
import { ItemEjectorSystem } from "./systems/item_ejector"; import { ItemEjectorSystem } from "./systems/item_ejector";
import { MapResourcesSystem } from "./systems/map_resources"; import { MapResourcesSystem } from "./systems/map_resources";
import { MapZoneSystem } from "./systems/map_zone";
import { MinerSystem } from "./systems/miner"; import { MinerSystem } from "./systems/miner";
import { ItemProcessorSystem } from "./systems/item_processor"; import { ItemProcessorSystem } from "./systems/item_processor";
import { UndergroundBeltSystem } from "./systems/underground_belt"; import { UndergroundBeltSystem } from "./systems/underground_belt";
@ -25,6 +24,7 @@ import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays";
import { BeltReaderSystem } from "./systems/belt_reader"; import { BeltReaderSystem } from "./systems/belt_reader";
import { FilterSystem } from "./systems/filter"; import { FilterSystem } from "./systems/filter";
import { ItemProducerSystem } from "./systems/item_producer"; import { ItemProducerSystem } from "./systems/item_producer";
import { ZoneSystem } from "./systems/zone";
const logger = createLogger("game_system_manager"); const logger = createLogger("game_system_manager");
@ -47,9 +47,6 @@ export class GameSystemManager {
/** @type {MapResourcesSystem} */ /** @type {MapResourcesSystem} */
mapResources: null, mapResources: null,
/** @type {MapZoneSystem} */
mapZone: null,
/** @type {MinerSystem} */ /** @type {MinerSystem} */
miner: null, miner: null,
@ -104,6 +101,9 @@ export class GameSystemManager {
/** @type {ItemProducerSystem} */ /** @type {ItemProducerSystem} */
itemProducer: null, itemProducer: null,
/** @type {ZoneSystem} */
zone: null,
/* typehints:end */ /* typehints:end */
}; };
this.systemUpdateOrder = []; this.systemUpdateOrder = [];
@ -142,9 +142,9 @@ export class GameSystemManager {
add("itemEjector", ItemEjectorSystem); add("itemEjector", ItemEjectorSystem);
add("mapResources", MapResourcesSystem); if (this.root.gameMode.hasResources()) {
add("mapResources", MapResourcesSystem);
add("mapZone", MapZoneSystem); }
add("hub", HubSystem); add("hub", HubSystem);
@ -171,6 +171,10 @@ export class GameSystemManager {
add("itemProcessorOverlays", ItemProcessorOverlaysSystem); add("itemProcessorOverlays", ItemProcessorOverlaysSystem);
if (this.root.gameMode.hasZone()) {
add("zone", ZoneSystem);
}
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
} }

View File

@ -129,9 +129,14 @@ export class GameHUD {
this.parts.changesDebugger = new HUDChangesDebugger(this.root); this.parts.changesDebugger = new HUDChangesDebugger(this.root);
} }
if (this.root.gameMode.hasHints() && this.root.app.settings.getAllSettings().offerHints) { if (this.root.app.settings.getAllSettings().offerHints) {
this.parts.tutorialHints = new HUDPartTutorialHints(this.root); if (!this.root.gameMode.isHudPartExcluded(HUDPartTutorialHints.name)) {
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root); 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) {
@ -177,7 +182,7 @@ export class GameHUD {
for (let key in parts) { for (let key in parts) {
const Part = parts[key]; const Part = parts[key];
if (!Part || this.root.gameMode.isHudPartExcluded(Part)) { if (!Part || this.root.gameMode.isHudPartExcluded(Part.name)) {
continue; continue;
} }

View File

@ -23,8 +23,8 @@ export class HUDBaseToolbar extends BaseHUDPart {
) { ) {
super(root); super(root);
this.primaryBuildings = primaryBuildings; this.primaryBuildings = this.filterBuildings(primaryBuildings);
this.secondaryBuildings = secondaryBuildings; this.secondaryBuildings = this.filterBuildings(secondaryBuildings);
this.visibilityCondition = visibilityCondition; this.visibilityCondition = visibilityCondition;
this.htmlElementId = htmlElementId; this.htmlElementId = htmlElementId;
this.layer = layer; this.layer = layer;
@ -47,6 +47,24 @@ export class HUDBaseToolbar extends BaseHUDPart {
this.element = makeDiv(parent, this.htmlElementId, ["ingame_buildingsToolbar"], ""); this.element = makeDiv(parent, this.htmlElementId, ["ingame_buildingsToolbar"], "");
} }
/**
* @param {Array<typeof MetaBuilding>} buildings
* @returns {Array<typeof MetaBuilding>}
*/
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 all buildings
* @returns {Array<typeof MetaBuilding>} * @returns {Array<typeof MetaBuilding>}

View File

@ -15,12 +15,13 @@ import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
import { HUDBaseToolbar } from "./base_toolbar"; import { HUDBaseToolbar } from "./base_toolbar";
import { MetaStorageBuilding } from "../../buildings/storage"; import { MetaStorageBuilding } from "../../buildings/storage";
import { MetaItemProducerBuilding } from "../../buildings/item_producer"; import { MetaItemProducerBuilding } from "../../buildings/item_producer";
import { queryParamOptions } from "../../../core/query_parameters"; import { MetaConstantProducerBuilding } from "../../buildings/constant_producer";
export class HUDBuildingsToolbar extends HUDBaseToolbar { export class HUDBuildingsToolbar extends HUDBaseToolbar {
constructor(root) { constructor(root) {
super(root, { super(root, {
primaryBuildings: [ primaryBuildings: [
MetaConstantProducerBuilding,
MetaBeltBuilding, MetaBeltBuilding,
MetaBalancerBuilding, MetaBalancerBuilding,
MetaUndergroundBeltBuilding, MetaUndergroundBeltBuilding,
@ -31,7 +32,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar {
MetaMixerBuilding, MetaMixerBuilding,
MetaPainterBuilding, MetaPainterBuilding,
MetaTrashBuilding, MetaTrashBuilding,
...(queryParamOptions.sandboxMode || G_IS_DEV ? [MetaItemProducerBuilding] : []), MetaItemProducerBuilding,
], ],
secondaryBuildings: [ secondaryBuildings: [
MetaStorageBuilding, MetaStorageBuilding,

View File

@ -153,10 +153,6 @@ export class HUDPinnedShapes extends BaseHUDPart {
* Rerenders the whole component * Rerenders the whole component
*/ */
rerenderFull() { rerenderFull() {
if (this.root.gameMode.isHudPartHidden(this.constructor.name)) {
return;
}
const currentGoal = this.root.hubGoals.currentGoal; const currentGoal = this.root.hubGoals.currentGoal;
const currentKey = currentGoal.definition.getHash(); const currentKey = currentGoal.definition.getHash();

View File

@ -102,14 +102,12 @@ export class HUDWaypoints extends BaseHUDPart {
/** @type {Array<Waypoint>} */ /** @type {Array<Waypoint>} */
this.waypoints = []; this.waypoints = [];
if (this.root.gameMode.hasHub()) { this.waypoints.push({
this.waypoints.push({ label: null,
label: null, center: { x: 0, y: 0 },
center: { x: 0, y: 0 }, zoomLevel: 3,
zoomLevel: 3, layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(), });
});
}
// Create a buffer we can use to measure text // Create a buffer we can use to measure text
this.dummyBuffer = makeOffscreenBuffer(1, 1, { this.dummyBuffer = makeOffscreenBuffer(1, 1, {

View File

@ -49,6 +49,9 @@ export const KEYMAPPINGS = {
}, },
buildings: { buildings: {
// Puzzle
constant_producer: { keyCode: 192 }, // "`"
// Primary Toolbar // Primary Toolbar
belt: { keyCode: key("1") }, belt: { keyCode: key("1") },
balancer: { keyCode: key("2") }, balancer: { keyCode: key("2") },
@ -262,6 +265,8 @@ export function getStringForKeyCode(code) {
return "."; return ".";
case 191: case 191:
return "/"; return "/";
case 192:
return "`";
case 219: case 219:
return "["; return "[";
case 220: case 220:

View File

@ -42,7 +42,7 @@ export class MapChunkView extends MapChunk {
drawBackgroundLayer(parameters) { drawBackgroundLayer(parameters) {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
if (this.root.gameMode.hasZone()) { if (this.root.gameMode.hasZone()) {
systems.mapZone.drawChunk(parameters, this); systems.zone.drawChunk(parameters, this);
} }
if (this.root.gameMode.hasResources()) { if (this.root.gameMode.hasResources()) {

View File

@ -5,6 +5,7 @@ import { MetaAnalyzerBuilding } from "./buildings/analyzer";
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer"; import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
import { MetaBeltBuilding } from "./buildings/belt"; import { MetaBeltBuilding } from "./buildings/belt";
import { MetaComparatorBuilding } from "./buildings/comparator"; import { MetaComparatorBuilding } from "./buildings/comparator";
import { MetaConstantProducerBuilding } from "./buildings/constant_producer";
import { MetaConstantSignalBuilding } from "./buildings/constant_signal"; import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter"; import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
import { MetaDisplayBuilding } from "./buildings/display"; import { MetaDisplayBuilding } from "./buildings/display";
@ -59,6 +60,7 @@ export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaAnalyzerBuilding); gMetaBuildingRegistry.register(MetaAnalyzerBuilding);
gMetaBuildingRegistry.register(MetaComparatorBuilding); gMetaBuildingRegistry.register(MetaComparatorBuilding);
gMetaBuildingRegistry.register(MetaItemProducerBuilding); gMetaBuildingRegistry.register(MetaItemProducerBuilding);
gMetaBuildingRegistry.register(MetaConstantProducerBuilding);
// Belt // Belt
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0); registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
@ -165,6 +167,9 @@ export function initMetaBuildingRegistry() {
// Item producer // Item producer
registerBuildingVariant(61, MetaItemProducerBuilding); registerBuildingVariant(61, MetaItemProducerBuilding);
// Constant producer
registerBuildingVariant(62, MetaConstantProducerBuilding);
// Propagate instances // Propagate instances
for (const key in gBuildingVariants) { for (const key in gBuildingVariants) {
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass( gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(

View File

@ -2,19 +2,22 @@
import { GameRoot } from "../root"; import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
import { globalConfig } from "../../core/config"; import { Rectangle } from "../../core/rectangle";
import { types } from "../../savegame/serialization"; import { types } from "../../savegame/serialization";
import { HUDPinnedShapes } from "../hud/parts/pinned_shapes";
import { enumGameModeTypes, GameMode } from "../game_mode"; 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 { export class PuzzleGameMode extends GameMode {
static getType() { static getType() {
return enumGameModeTypes.puzzle; return enumGameModeTypes.puzzle;
} }
/** @returns {object} */
static getSchema() { static getSchema() {
return { return {
hiddenHudParts: types.keyValueMap(types.bool),
zoneHeight: types.uint, zoneHeight: types.uint,
zoneWidth: types.uint, zoneWidth: types.uint,
}; };
@ -23,18 +26,24 @@ export class PuzzleGameMode extends GameMode {
/** @param {GameRoot} root */ /** @param {GameRoot} root */
constructor(root) { constructor(root) {
super(root); super(root);
}
initialize() {
const data = this.getSaveData(); const data = this.getSaveData();
this.type = this.getType(); this.setHudParts({
this.hiddenHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts(); [HUDInteractiveTutorial.name]: false,
// this.excludedHudParts = data.hiddenHudParts || this.getDefaultHiddenHudParts(); [HUDPartTutorialHints.name]: false,
this.zoneHeight = data.zoneHeight || 3 * globalConfig.tileSize; [HUDPinnedShapes.name]: false,
this.zoneWidth = data.zoneWidth || 4 * globalConfig.tileSize; [HUDWaypoints.name]: false,
this.boundaryHeight = this.zoneHeight * 2; });
this.boundaryWidth = this.zoneWidth * 2;
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() { getSaveData() {
@ -47,24 +56,52 @@ export class PuzzleGameMode extends GameMode {
return save.gameMode.data; return save.gameMode.data;
} }
getDefaultHiddenHudParts() { createCenteredRectangle(width, height) {
return { return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height);
[HUDPinnedShapes.name]: true,
};
} }
isHudPartHidden(name) { getBounds() {
return this.hiddenHudParts[name]; 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() { hasZone() {
return true; return true;
} }
hasHints() {
return false;
}
hasHub() { hasHub() {
return false; return false;
} }
@ -73,27 +110,11 @@ export class PuzzleGameMode extends GameMode {
return false; return false;
} }
hasBoundaries() { hasBounds() {
return true; return true;
} }
getMinimumZoom() { getMinimumZoom() {
return 1; return 1;
} }
getBoundaryWidth() {
return this.boundaryWidth;
}
getBoundaryHeight() {
return this.boundaryHeight;
}
getZoneWidth() {
return this.zoneWidth;
}
getZoneHeight() {
return this.zoneHeight;
}
} }

View File

@ -2,8 +2,9 @@
import { GameRoot } from "../root"; import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
import { PuzzleGameMode } from "./puzzle"; import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { enumGameModeIds } from "../game_mode"; import { enumGameModeIds } from "../game_mode";
import { PuzzleGameMode } from "./puzzle";
export class PuzzleEditGameMode extends PuzzleGameMode { export class PuzzleEditGameMode extends PuzzleGameMode {
static getId() { static getId() {
@ -13,5 +14,9 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
/** @param {GameRoot} root */ /** @param {GameRoot} root */
constructor(root) { constructor(root) {
super(root); super(root);
this.setBuildings({
[MetaConstantProducerBuilding.name]: true,
});
} }
} }

View File

@ -13,6 +13,5 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
/** @param {GameRoot} root */ /** @param {GameRoot} root */
constructor(root) { constructor(root) {
super(root); super(root);
this.initialize();
} }
} }

View File

@ -2,11 +2,13 @@
import { GameRoot } from "../root"; import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
import { queryParamOptions } from "../../core/query_parameters";
import { findNiceIntegerValue } from "../../core/utils"; 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 { 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 { types } from "../../savegame/serialization";
/** @typedef {{ /** @typedef {{
* shape: string, * shape: string,
@ -489,6 +491,11 @@ export class RegularGameMode extends GameMode {
/** @param {GameRoot} root */ /** @param {GameRoot} root */
constructor(root) { constructor(root) {
super(root); super(root);
this.setBuildings({
[MetaConstantProducerBuilding.name]: false,
[MetaItemProducerBuilding.name]: queryParamOptions.sandboxMode || G_IS_DEV,
});
} }
/** /**

View File

@ -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"; import { GameSystemWithFilter } from "../game_system_with_filter";
export class ItemProducerSystem extends GameSystemWithFilter { export class ItemProducerSystem extends GameSystemWithFilter {
/** @param {GameRoot} root */
constructor(root) { constructor(root) {
super(root, [ItemProducerComponent]); super(root, [ItemProducerComponent]);
} }
@ -9,16 +14,21 @@ export class ItemProducerSystem extends GameSystemWithFilter {
update() { update() {
for (let i = 0; i < this.allEntities.length; ++i) { for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i]; const entity = this.allEntities[i];
const pinsComp = entity.components.WiredPins;
const pin = pinsComp.slots[0];
const network = pin.linkedNetwork;
if (!network || !network.hasValue()) { if (entity.components.ItemProducer.type === enumItemProducerType.wired) {
continue; 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);
} }
} }
} }

View File

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

View File

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

View File

@ -2,6 +2,8 @@ import { ExplainedResult } from "../core/explained_result";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { gComponentRegistry } from "../core/global_registries"; import { gComponentRegistry } from "../core/global_registries";
import { SerializerInternal } from "./serializer_internal"; 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 * @typedef {import("../game/component").Component} Component
@ -36,10 +38,14 @@ export class SavegameSerializer {
gameMode: root.gameMode.serialize(), gameMode: root.gameMode.serialize(),
entityMgr: root.entityMgr.serialize(), entityMgr: root.entityMgr.serialize(),
hubGoals: root.hubGoals.serialize(), hubGoals: root.hubGoals.serialize(),
pinnedShapes: root.hud.parts.pinnedShapes.serialize(),
waypoints: root.hud.parts.waypoints.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)
? 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) {
@ -133,11 +139,17 @@ export class SavegameSerializer {
errorReason = errorReason || root.map.deserialize(savegame.map); errorReason = errorReason || root.map.deserialize(savegame.map);
errorReason = errorReason || root.gameMode.deserialize(savegame.gameMode); errorReason = errorReason || root.gameMode.deserialize(savegame.gameMode);
errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root); 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 || 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)) {
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 // Check for errors
if (errorReason) { if (errorReason) {
return ExplainedResult.bad(errorReason); return ExplainedResult.bad(errorReason);

View File

@ -333,10 +333,13 @@ export class MainMenuState extends GameState {
const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons"); const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons");
removeAllChildren(buttonContainer); 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); buttonContainer.appendChild(playButtonElement);
this.trackClicks(playButtonElement, this.onPuzzlePlayButtonClicked); this.trackClicks(playButtonElement, this.onPuzzlePlayButtonClicked);
buttonContainer.appendChild(editButtonElement);
this.trackClicks(editButtonElement, this.onPuzzleEditButtonClicked);
const bottomButtonContainer = this.htmlElement.querySelector(".bottomContainer .buttons"); const bottomButtonContainer = this.htmlElement.querySelector(".bottomContainer .buttons");
removeAllChildren(bottomButtonContainer); 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() { onPuzzleModeButtonClicked() {
this.renderPuzzleModeMenu(); this.renderPuzzleModeMenu();
} }

View File

@ -119,6 +119,10 @@ mainMenu:
puzzleMode: Puzzle Mode puzzleMode: Puzzle Mode
back: Back back: Back
puzzleMenu:
play: Play
edit: Edit
dialogs: dialogs:
buttons: buttons:
ok: OK ok: OK
@ -703,6 +707,11 @@ buildings:
name: Item Producer name: Item Producer
description: Available in sandbox mode only, outputs the given signal from the wires layer on the regular layer. 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: storyRewards:
# Those are the rewards gained from completing the store # Those are the rewards gained from completing the store
reward_cutter_and_trash: reward_cutter_and_trash:
@ -1130,6 +1139,7 @@ keybindings:
analyzer: *analyzer analyzer: *analyzer
comparator: *comparator comparator: *comparator
item_producer: Item Producer (Sandbox) item_producer: Item Producer (Sandbox)
constant_producer: *constant_producer
# --- # ---
pipette: Pipette pipette: Pipette