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

View File

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

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

View File

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

View File

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

View File

@ -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);
if (this.root.gameMode.hasResources()) {
add("mapResources", MapResourcesSystem);
add("mapZone", MapZoneSystem);
}
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");
}

View File

@ -129,10 +129,15 @@ export class GameHUD {
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) {
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);
@ -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;
}

View File

@ -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<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 {Array<typeof MetaBuilding>}

View File

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

View File

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

View File

@ -102,14 +102,12 @@ export class HUDWaypoints extends BaseHUDPart {
/** @type {Array<Waypoint>} */
this.waypoints = [];
if (this.root.gameMode.hasHub()) {
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, {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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";
export class ItemProducerSystem extends GameSystemWithFilter {
/** @param {GameRoot} root */
constructor(root) {
super(root, [ItemProducerComponent]);
}
@ -9,6 +14,8 @@ export class ItemProducerSystem extends GameSystemWithFilter {
update() {
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
if (entity.components.ItemProducer.type === enumItemProducerType.wired) {
const pinsComp = entity.components.WiredPins;
const pin = pinsComp.slots[0];
const network = pin.linkedNetwork;
@ -19,6 +26,9 @@ export class ItemProducerSystem extends GameSystemWithFilter {
const ejectorComp = entity.components.ItemEjector;
ejectorComp.tryEject(0, network.currentValue);
} else {
// TODO: entity w/ wireless item producer (e.g. ConstantProducer)
}
}
}
}

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

View File

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

View File

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