1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Implement wire networks, add levers

This commit is contained in:
tobspr
2020-08-13 19:23:00 +02:00
parent 75ab655998
commit 52b4d4d742
46 changed files with 1760 additions and 724 deletions

View File

@@ -1,5 +1,5 @@
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
constant_signal, logic_gate;
constant_signal, logic_gate, lever;
@each $building in $buildings {
[data-icon="building_icons/#{$building}.png"] {

View File

@@ -13,7 +13,7 @@ export class DrawParameters {
/** @type {Rectangle} */
this.visibleRect = visibleRect;
/** @type {number} */
/** @type {string} */
this.desiredAtlasScale = desiredAtlasScale;
/** @type {number} */

View File

@@ -43,6 +43,14 @@ export const enumAngleToDirection = {
270: enumDirection.left,
};
/** @type {Array<enumDirection>} */
export const arrayAllDirections = [
enumDirection.top,
enumDirection.right,
enumDirection.bottom,
enumDirection.left,
];
export class Vector {
/**
*

View File

@@ -31,6 +31,29 @@ export class BaseItem extends BasicSerializableObject {
return "";
}
/**
* Returns if the item equals the other itme
* @param {BaseItem} other
* @returns {boolean}
*/
equals(other) {
if (this.getItemType() !== other.getItemType()) {
return false;
}
return this.equalsImpl(other);
}
/**
* Override for custom comparison
* @abstract
* @param {BaseItem} other
* @returns {boolean}
*/
equalsImpl(other) {
abstract;
return false;
}
/**
* Draws the item at the given position
* @param {number} x

View File

@@ -0,0 +1,57 @@
import { enumDirection, Vector } from "../../core/vector";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root";
import { LeverComponent } from "../components/lever";
export class MetaLeverBuilding extends MetaBuilding {
constructor() {
super("lever");
}
getSilhouetteColor() {
// @todo: Render differently based on if its activated or not
return "#1a678b";
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
// @todo
return true;
}
isRotateable() {
return false;
}
getDimensions() {
return new Vector(1, 1);
}
getSprite() {
return null;
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
entity.addComponent(
new WiredPinsComponent({
slots: [
{
pos: new Vector(0, 0),
direction: enumDirection.top,
type: enumPinSlotType.logicalEjector,
},
],
})
);
entity.addComponent(new LeverComponent({}));
}
}

View File

@@ -1,8 +1,17 @@
import { enumDirection, Vector } from "../../core/vector";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity";
import { MetaBuilding } from "../meta_building";
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { enumLayer, GameRoot } from "../root";
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
/** @enum {string} */
export const enumLogicGateVariants = {};
/** @enum {string} */
export const enumVariantToGate = {
[defaultBuildingVariant]: enumLogicGateType.and,
};
export class MetaLogicGateBuilding extends MetaBuilding {
constructor() {
@@ -29,6 +38,15 @@ export class MetaLogicGateBuilding extends MetaBuilding {
return new Vector(1, 1);
}
/**
*
* @param {Entity} entity
* @param {number} rotationVariant
*/
updateVariants(entity, rotationVariant, variant) {
entity.components.LogicGate.type = enumVariantToGate[variant];
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@@ -55,5 +73,7 @@ export class MetaLogicGateBuilding extends MetaBuilding {
],
})
);
entity.addComponent(new LogicGateComponent({}));
}
}

View File

@@ -12,6 +12,8 @@ import { WiredPinsComponent } from "./components/wired_pins";
import { BeltUnderlaysComponent } from "./components/belt_underlays";
import { WireComponent } from "./components/wire";
import { ConstantSignalComponent } from "./components/constant_signal";
import { LogicGateComponent } from "./components/logic_gate";
import { LeverComponent } from "./components/lever";
export function initComponentRegistry() {
gComponentRegistry.register(StaticMapEntityComponent);
@@ -27,6 +29,8 @@ export function initComponentRegistry() {
gComponentRegistry.register(BeltUnderlaysComponent);
gComponentRegistry.register(WireComponent);
gComponentRegistry.register(ConstantSignalComponent);
gComponentRegistry.register(LogicGateComponent);
gComponentRegistry.register(LeverComponent);
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS

View File

@@ -0,0 +1,20 @@
import { Component } from "../component";
export class LeverComponent extends Component {
static getId() {
return "Lever";
}
duplicateWithoutContents() {
return new LeverComponent({ toggled: this.toggled });
}
/**
* @param {object} param0
* @param {boolean=} param0.toggled
*/
constructor({ toggled = false }) {
super();
this.toggled = toggled;
}
}

View File

@@ -0,0 +1,26 @@
import { Component } from "../component";
/** @enum {string} */
export const enumLogicGateType = {
and: "and",
};
export class LogicGateComponent extends Component {
static getId() {
return "LogicGate";
}
duplicateWithoutContents() {
return new LogicGateComponent({ type: this.type });
}
/**
*
* @param {object} param0
* @param {enumLogicGateType=} param0.type
*/
constructor({ type = enumLogicGateType.and }) {
super();
this.type = type;
}
}

View File

@@ -24,6 +24,11 @@ export class WireComponent extends Component {
constructor({ type = enumWireType.regular }) {
super();
this.type = type;
/**
* @type {import("../systems/wire").WireNetwork}
*/
this.linkedNetwork = null;
}
/**

View File

@@ -18,7 +18,8 @@ export const enumPinSlotType = {
* pos: Vector,
* type: enumPinSlotType,
* direction: enumDirection,
* value: BaseItem
* value: BaseItem,
* linkedNetwork: import("../systems/wire").WireNetwork
* }} WirePinSlot */
export class WiredPinsComponent extends Component {
@@ -65,6 +66,7 @@ export class WiredPinsComponent extends Component {
type: slotData.type,
direction: slotData.direction,
value: null,
linkedNetwork: null,
});
}
}

View File

@@ -31,6 +31,7 @@ import { ProductionAnalytics } from "./production_analytics";
import { randomInt } from "../core/utils";
import { defaultBuildingVariant } from "./meta_building";
import { DynamicTickrate } from "./dynamic_tickrate";
import { Rectangle } from "../core/rectangle";
const logger = createLogger("ingame/core");
@@ -450,6 +451,11 @@ export class GameCore {
// Restore to screen space
context.restore();
// Restore parameters
params.zoomLevel = 1;
params.desiredAtlasScale = "1";
params.visibleRect = new Rectangle(0, 0, this.root.gameWidth, this.root.gameHeight);
// Draw overlays, those are screen space
root.hud.drawOverlays(params);

View File

@@ -12,6 +12,8 @@ import { UndergroundBeltComponent } from "./components/underground_belt";
import { WiredPinsComponent } from "./components/wired_pins";
import { WireComponent } from "./components/wire";
import { ConstantSignalComponent } from "./components/constant_signal";
import { LogicGateComponent } from "./components/logic_gate";
import { LeverComponent } from "./components/lever";
/* typehints:end */
/**
@@ -61,6 +63,12 @@ export class EntityComponentStorage {
/** @type {ConstantSignalComponent} */
this.ConstantSignal;
/** @type {LogicGateComponent} */
this.LogicGate;
/** @type {LeverComponent} */
this.Lever;
/* typehints:end */
}
}

View File

@@ -17,6 +17,8 @@ import { WiredPinsSystem } from "./systems/wired_pins";
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
import { WireSystem } from "./systems/wire";
import { ConstantSignalSystem } from "./systems/constant_signal";
import { LogicGateSystem } from "./systems/logic_gate";
import { LeverSystem } from "./systems/lever";
const logger = createLogger("game_system_manager");
@@ -72,6 +74,12 @@ export class GameSystemManager {
/** @type {ConstantSignalSystem} */
constantSignal: null,
/** @type {LogicGateSystem} */
logicGate: null,
/** @type {LeverSystem} */
lever: null,
/* typehints:end */
};
this.systemUpdateOrder = [];
@@ -119,6 +127,14 @@ export class GameSystemManager {
// then would be invalid
add("itemAcceptor", ItemAcceptorSystem);
// WIRES section
add("lever", LeverSystem);
// IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates,
// processors etc. In phase 2 we propagate it through the wires network
add("logicGate", LogicGateSystem);
// Wires must be after all gate, signal etc logic!
add("wire", WireSystem);
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");

View File

@@ -41,6 +41,8 @@ import { HUDChangesDebugger } from "./parts/debug_changes";
import { queryParamOptions } from "../../core/query_parameters";
import { HUDSandboxController } from "./parts/sandbox_controller";
import { HUDWiresToolbar } from "./parts/wires_toolbar";
import { HUDWireInfo } from "./parts/wire_info";
import { HUDLeverToggle } from "./parts/lever_toggle";
export class GameHUD {
/**
@@ -66,6 +68,8 @@ export class GameHUD {
shop: new HUDShop(this.root),
statistics: new HUDStatistics(this.root),
waypoints: new HUDWaypoints(this.root),
wireInfo: new HUDWireInfo(this.root),
leverToggle: new HUDLeverToggle(this.root),
// Must always exist
pinnedShapes: new HUDPinnedShapes(this.root),
@@ -250,7 +254,7 @@ export class GameHUD {
* @param {DrawParameters} parameters
*/
drawOverlays(parameters) {
const partsOrder = ["watermark"];
const partsOrder = ["watermark", "wireInfo"];
for (let i = 0; i < partsOrder.length; ++i) {
if (this.parts[partsOrder[i]]) {

View File

@@ -10,6 +10,7 @@ import { MetaTrashBuilding } from "../../buildings/trash";
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
import { enumLayer } from "../../root";
import { HUDBaseToolbar } from "./base_toolbar";
import { MetaLeverBuilding } from "../../buildings/lever";
const supportedBuildings = [
MetaBeltBaseBuilding,
@@ -22,6 +23,7 @@ const supportedBuildings = [
MetaMixerBuilding,
MetaPainterBuilding,
MetaTrashBuilding,
MetaLeverBuilding,
];
export class HUDBuildingsToolbar extends HUDBaseToolbar {

View File

@@ -0,0 +1,30 @@
import { STOP_PROPAGATION } from "../../../core/signal";
import { Vector } from "../../../core/vector";
import { enumMouseButton } from "../../camera";
import { enumLayer } from "../../root";
import { BaseHUDPart } from "../base_hud_part";
export class HUDLeverToggle extends BaseHUDPart {
initialize() {
this.root.camera.downPreHandler.add(this.downPreHandler, this);
}
/**
* @param {Vector} pos
* @param {enumMouseButton} button
*/
downPreHandler(pos, button) {
if (button === enumMouseButton.left) {
const tile = this.root.camera.screenToWorld(pos).toTileSpace();
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, enumLayer.regular);
if (contents) {
const leverComp = contents.components.Lever;
if (leverComp) {
leverComp.toggled = !leverComp.toggled;
return STOP_PROPAGATION;
}
}
}
}
}

View File

@@ -0,0 +1,58 @@
import { BaseHUDPart } from "../base_hud_part";
import { enumLayer } from "../../root";
import { globalConfig } from "../../../core/config";
export class HUDWireInfo extends BaseHUDPart {
initialize() {}
/**
*
* @param {import("../../../core/draw_utils").DrawParameters} parameters
*/
drawOverlays(parameters) {
if (this.root.currentLayer !== enumLayer.wires) {
// Not in the wires layer
return;
}
const mousePos = this.root.app.mousePosition;
if (!mousePos) {
// No mouse
return;
}
const tile = this.root.camera.screenToWorld(mousePos).toTileSpace();
const entity = this.root.map.getLayerContentXY(tile.x, tile.y, enumLayer.wires);
if (entity) {
const wireComp = entity.components.Wire;
if (wireComp) {
const screenTile = this.root.camera.worldToScreen(tile.toWorldSpace());
parameters.context.fillStyle = "rgba(0, 0, 0, 0.1)";
parameters.context.fillRect(
screenTile.x,
screenTile.y,
globalConfig.tileSize * this.root.camera.zoomLevel,
globalConfig.tileSize * this.root.camera.zoomLevel
);
parameters.context.font = "25px GameFont";
const network = wireComp.linkedNetwork;
if (!network) {
parameters.context.fillStyle = "#333";
parameters.context.fillText("empty", mousePos.x, mousePos.y);
} else {
if (network.valueConflict) {
parameters.context.fillStyle = "#a10";
parameters.context.fillText("conflict", mousePos.x, mousePos.y);
} else if (!network.currentValue) {
parameters.context.fillStyle = "#333";
parameters.context.fillText("empty", mousePos.x, mousePos.y);
} else {
network.currentValue.draw(mousePos.x + 20, mousePos.y, parameters, 40);
}
}
}
}
}
}

View File

@@ -32,6 +32,13 @@ export class BooleanItem extends BaseItem {
this.value = value ? 1 : 0;
}
/**
* @param {BaseItem} other
*/
equalsImpl(other) {
return this.value === /** @type {BooleanItem} */ (other).value;
}
/**
* @param {number} x
* @param {number} y

View File

@@ -27,6 +27,13 @@ export class ColorItem extends BaseItem {
return enumItemType.color;
}
/**
* @param {BaseItem} other
*/
equalsImpl(other) {
return this.color === /** @type {ColorItem} */ (other).color;
}
/**
* @param {enumColors} color
*/

View File

@@ -25,6 +25,13 @@ export class ShapeItem extends BaseItem {
return enumItemType.shape;
}
/**
* @param {BaseItem} other
*/
equalsImpl(other) {
return this.definition.getHash() === /** @type {ShapeItem} */ (other).definition.getHash();
}
/**
* @param {ShapeDefinition} definition
*/

View File

@@ -58,6 +58,7 @@ export const KEYMAPPINGS = {
wire: { keyCode: key("1") },
constant_signal: { keyCode: key("2") },
logic_gate: { keyCode: key("3") },
lever: { keyCode: key("4") },
},
placement: {

View File

@@ -52,6 +52,7 @@ export class MapChunkView extends MapChunk {
const systems = this.root.systemMgr.systems;
systems.miner.drawChunk(parameters, this);
systems.staticMapEntities.drawChunk(parameters, this);
systems.lever.drawChunk(parameters, this);
}
/**

View File

@@ -17,6 +17,7 @@ import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
import { defaultBuildingVariant } from "./meta_building";
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
import { MetaLogicGateBuilding } from "./buildings/logic_gate";
import { MetaLeverBuilding } from "./buildings/lever";
const logger = createLogger("building_registry");
@@ -35,6 +36,7 @@ export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaWireBuilding);
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
gMetaBuildingRegistry.register(MetaLogicGateBuilding);
gMetaBuildingRegistry.register(MetaLeverBuilding);
// Belt
registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
@@ -96,6 +98,9 @@ export function initMetaBuildingRegistry() {
// Logic gate
registerBuildingVariant(32, MetaLogicGateBuilding);
// Lever
registerBuildingVariant(33, MetaLeverBuilding);
// Propagate instances
for (const key in gBuildingVariants) {
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(

View File

@@ -13,6 +13,7 @@ import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunkView } from "../map_chunk_view";
import { defaultBuildingVariant } from "../meta_building";
import { getCodeFromBuildingData } from "../building_codes";
export const BELT_ANIM_COUNT = 28;
@@ -169,6 +170,13 @@ export class BeltSystem extends GameSystemWithFilter {
targetStaticComp.rotation = rotation;
metaBelt.updateVariants(targetEntity, rotationVariant, defaultBuildingVariant);
// Update code as well
targetStaticComp.code = getCodeFromBuildingData(
metaBelt,
defaultBuildingVariant,
rotationVariant
);
// Now add it again
this.addEntityToPaths(targetEntity);

View File

@@ -0,0 +1,48 @@
import { GameSystemWithFilter } from "../game_system_with_filter";
import { LeverComponent } from "../components/lever";
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
import { MapChunkView } from "../map_chunk_view";
import { globalConfig } from "../../core/config";
import { Loader } from "../../core/loader";
export class LeverSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [LeverComponent]);
this.spriteOn = Loader.getSprite("sprites/wires/lever_on.png");
this.spriteOff = Loader.getSprite("sprites/buildings/lever.png");
}
update() {
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const leverComp = entity.components.Lever;
const pinsComp = entity.components.WiredPins;
// Simply sync the status to the first slot
pinsComp.slots[0].value = leverComp.toggled ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
}
}
/**
* Draws a given chunk
* @param {import("../../core/draw_utils").DrawParameters} parameters
* @param {MapChunkView} chunk
*/
drawChunk(parameters, chunk) {
const contents = chunk.contents;
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const entity = contents[x][y];
if (entity && entity.components.Lever) {
const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff;
const staticComp = entity.components.StaticMapEntity;
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BaseItem, enumItemType } from "../base_item";
import { enumPinSlotType } from "../components/wired_pins";
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON, BooleanItem } from "../items/boolean_item";
export class LogicGateSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [LogicGateComponent]);
this.boundOperations = {
[enumLogicGateType.and]: this.compute_AND.bind(this),
};
}
update() {
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const logicComp = entity.components.LogicGate;
const slotComp = entity.components.WiredPins;
const slotValues = [];
for (let i = 0; i < slotComp.slots.length; ++i) {
const slot = slotComp.slots[i];
if (slot.type !== enumPinSlotType.logicalAcceptor) {
continue;
}
if (slot.linkedNetwork) {
slotValues.push(slot.linkedNetwork.currentValue);
} else {
slotValues.push(null);
}
}
const result = this.boundOperations[logicComp.type](slotValues);
// @TODO: For now we hardcode the value to always be slot 0
assert(
slotValues.length === slotComp.slots.length - 1,
"Bad slot config, should have N acceptor slots and 1 ejector"
);
assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
slotComp.slots[0].value = result;
}
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {BaseItem}
*/
compute_AND(parameters) {
assert(parameters.length === 2, "bad parameter count for AND");
const param1 = parameters[0];
const param2 = parameters[1];
if (!param1 || !param2) {
// Not enough params
return null;
}
const itemType = param1.getItemType();
if (itemType !== param2.getItemType()) {
// Differing type
return BOOL_FALSE_SINGLETON;
}
if (itemType === enumItemType.boolean) {
return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
? BOOL_TRUE_SINGLETON
: BOOL_FALSE_SINGLETON;
}
return BOOL_FALSE_SINGLETON;
}
}

View File

@@ -6,8 +6,69 @@ import { Loader } from "../../core/loader";
import { Entity } from "../entity";
import { gMetaBuildingRegistry } from "../../core/global_registries";
import { MetaWireBuilding, arrayWireRotationVariantToType } from "../buildings/wire";
import { Vector } from "../../core/vector";
import {
Vector,
enumDirection,
enumDirectionToVector,
arrayAllDirections,
enumInvertedDirections,
} from "../../core/vector";
import { defaultBuildingVariant } from "../meta_building";
import { createLogger } from "../../core/logging";
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
import { getCodeFromBuildingData } from "../building_codes";
import { BaseItem, enumItemType } from "../base_item";
const logger = createLogger("wires");
let networkUidCounter = 0;
const VERBOSE_WIRES = false;
export class WireNetwork {
constructor() {
/**
* Who contributes to this network
* @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */
this.providers = [];
/**
* Who takes values from this network
* @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */
this.receivers = [];
/**
* All connected slots
* @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>}
*/
this.allSlots = [];
/**
* Which wires are in this network
* @type {Array<Entity>}
*/
this.wires = [];
/**
* The current value of this network
* @type {BaseItem}
*/
this.currentValue = null;
/**
* Whether this network has a value conflict, that is, more than one
* sender has sent a value
* @type {boolean}
*/
this.valueConflict = false;
/**
* Unique network identifier
* @type {number}
*/
this.uid = ++networkUidCounter;
}
}
export class WireSystem extends GameSystemWithFilter {
constructor(root) {
@@ -22,6 +83,327 @@ export class WireSystem extends GameSystemWithFilter {
this.root.signals.entityDestroyed.add(this.updateSurroundingWirePlacement, this);
this.root.signals.entityAdded.add(this.updateSurroundingWirePlacement, this);
this.root.signals.entityDestroyed.add(this.queueRecomputeIfWire, this);
this.root.signals.entityChanged.add(this.queueRecomputeIfWire, this);
this.root.signals.entityAdded.add(this.queueRecomputeIfWire, this);
this.needsRecompute = true;
/**
* @type {Array<WireNetwork>}
*/
this.networks = [];
}
/**
* Invalidates the wires network if the given entity is relevant for it
* @param {Entity} entity
*/
queueRecomputeIfWire(entity) {
if (!this.root.gameInitialized) {
return;
}
if (entity.components.Wire || entity.components.WiredPins) {
this.needsRecompute = true;
this.networks = [];
}
}
/**
* Recomputes the whole wires network
*/
recomputeWiresNetwork() {
this.needsRecompute = false;
logger.log("Recomputing wires network");
this.networks = [];
// Clear all network references
const wireEntities = this.root.entityMgr.getAllWithComponent(WireComponent);
for (let i = 0; i < wireEntities.length; ++i) {
wireEntities[i].components.Wire.linkedNetwork = null;
}
const pinEntities = this.root.entityMgr.getAllWithComponent(WiredPinsComponent);
for (let i = 0; i < pinEntities.length; ++i) {
const slots = pinEntities[i].components.WiredPins.slots;
for (let k = 0; k < slots.length; ++k) {
slots[k].linkedNetwork = null;
}
}
VERBOSE_WIRES && logger.log("Recomputing slots");
// Iterate over all ejector slots
for (let i = 0; i < pinEntities.length; ++i) {
const entity = pinEntities[i];
const slots = entity.components.WiredPins.slots;
for (let k = 0; k < slots.length; ++k) {
const slot = slots[k];
// Ejectors are computed directly, acceptors are just set
if (slot.type === enumPinSlotType.logicalEjector && !slot.linkedNetwork) {
this.findNetworkForEjector(entity, slot);
}
}
}
}
/**
* Finds the network for the given slot
* @param {Entity} initialEntity
* @param {import("../components/wired_pins").WirePinSlot} slot
*/
findNetworkForEjector(initialEntity, slot) {
let currentNetwork = new WireNetwork();
VERBOSE_WIRES &&
logger.log(
"Finding network for entity",
initialEntity.uid,
initialEntity.components.StaticMapEntity.origin.toString(),
"(nw-id:",
currentNetwork.uid,
")"
);
const entitiesToVisit = [
{
entity: initialEntity,
slot,
},
];
while (entitiesToVisit.length > 0) {
const nextData = entitiesToVisit.pop();
const nextEntity = nextData.entity;
const wireComp = nextEntity.components.Wire;
const staticComp = nextEntity.components.StaticMapEntity;
VERBOSE_WIRES && logger.log("Visiting", staticComp.origin.toString(), "(", nextEntity.uid, ")");
// Where to search for neighbours
let newSearchDirections = [];
let newSearchTile = null;
//// WIRE
if (wireComp) {
// Sanity check
assert(
!wireComp.linkedNetwork || wireComp.linkedNetwork === currentNetwork,
"Mismatching wire network on wire entity " +
(wireComp.linkedNetwork ? wireComp.linkedNetwork.uid : "<empty>") +
" vs " +
currentNetwork.uid +
" @ " +
staticComp.origin.toString()
);
if (!wireComp.linkedNetwork) {
// This one is new! :D
VERBOSE_WIRES && logger.log(" Visited new wire:", staticComp.origin.toString());
wireComp.linkedNetwork = currentNetwork;
currentNetwork.wires.push(nextEntity);
newSearchDirections = arrayAllDirections;
newSearchTile = nextEntity.components.StaticMapEntity.origin;
}
}
//// PINS
const pinsComp = nextEntity.components.WiredPins;
if (pinsComp) {
const slot = nextData.slot;
assert(slot, "No slot set for next entity");
if (slot.type === enumPinSlotType.logicalEjector) {
VERBOSE_WIRES &&
logger.log(" Visiting ejector slot", staticComp.origin.toString(), "->", slot.type);
} else if (slot.type === enumPinSlotType.logicalAcceptor) {
VERBOSE_WIRES &&
logger.log(" Visiting acceptor slot", staticComp.origin.toString(), "->", slot.type);
} else {
assertAlways(false, "Bad slot type: " + slot.type);
}
// Sanity check
assert(
!slot.linkedNetwork || slot.linkedNetwork === currentNetwork,
"Mismatching wire network on pin slot entity " +
(slot.linkedNetwork ? slot.linkedNetwork.uid : "<empty>") +
" vs " +
currentNetwork.uid
);
if (!slot.linkedNetwork) {
// This one is new
VERBOSE_WIRES && logger.log(" Visited new slot:", staticComp.origin.toString());
// Add to the right list
if (slot.type === enumPinSlotType.logicalEjector) {
currentNetwork.providers.push({ entity: nextEntity, slot });
} else if (slot.type === enumPinSlotType.logicalAcceptor) {
currentNetwork.receivers.push({ entity: nextEntity, slot });
} else {
assertAlways(false, "unknown slot type:" + slot.type);
}
// Register on the network
currentNetwork.allSlots.push({ entity: nextEntity, slot });
slot.linkedNetwork = currentNetwork;
// Specify where to search next
newSearchDirections = [staticComp.localDirectionToWorld(slot.direction)];
newSearchTile = staticComp.localTileToWorld(slot.pos);
}
}
if (newSearchTile) {
// Find new surrounding wire targets
const newTargets = this.findSurroundingWireTargets(newSearchTile, newSearchDirections);
VERBOSE_WIRES && logger.log(" Found", newTargets, "new targets to visit!");
for (let i = 0; i < newTargets.length; ++i) {
entitiesToVisit.push(newTargets[i]);
}
}
}
if (
currentNetwork.providers.length > 0 &&
(currentNetwork.wires.length > 0 || currentNetwork.receivers.length > 0)
) {
this.networks.push(currentNetwork);
VERBOSE_WIRES && logger.log("Attached new network with uid", currentNetwork);
} else {
// Unregister network again
for (let i = 0; i < currentNetwork.wires.length; ++i) {
currentNetwork.wires[i].components.Wire.linkedNetwork = null;
}
for (let i = 0; i < currentNetwork.allSlots.length; ++i) {
currentNetwork.allSlots[i].slot.linkedNetwork = null;
}
}
}
/**
* Finds surrounding entities which are not yet assigned to a network
* @param {Vector} tile
* @param {Array<enumDirection>} directions
* @returns {Array<any>}
*/
findSurroundingWireTargets(tile, directions) {
let result = [];
VERBOSE_WIRES &&
logger.log(" Searching for new targets at", tile.toString(), "and d=", directions);
for (let i = 0; i < directions.length; ++i) {
const direction = directions[i];
const offset = enumDirectionToVector[direction];
const searchTile = tile.add(offset);
const contents = this.root.map.getLayersContentsMultipleXY(searchTile.x, searchTile.y);
for (let k = 0; k < contents.length; ++k) {
const entity = contents[k];
const wireComp = entity.components.Wire;
// Check for wire
if (wireComp && !wireComp.linkedNetwork) {
// Wires accept connections from everywhere
result.push({
entity,
});
}
// Check for connected slots
const pinComp = entity.components.WiredPins;
if (pinComp) {
const staticComp = entity.components.StaticMapEntity;
// Go over all slots and see if they are connected
const pinSlots = pinComp.slots;
for (let j = 0; j < pinSlots.length; ++j) {
const slot = pinSlots[j];
// Check if the position matches
const pinPos = staticComp.localTileToWorld(slot.pos);
if (!pinPos.equals(searchTile)) {
continue;
}
// Check if the direction (inverted) matches
const pinDirection = staticComp.localDirectionToWorld(slot.direction);
if (pinDirection !== enumInvertedDirections[direction]) {
continue;
}
result.push({
entity,
slot,
});
}
}
}
}
VERBOSE_WIRES && logger.log(" -> Found", result.length);
return result;
}
/**
* Updates the wires network
*/
update() {
if (this.needsRecompute) {
this.recomputeWiresNetwork();
}
// Re-compute values of all networks
for (let i = 0; i < this.networks.length; ++i) {
const network = this.networks[i];
// Reset conflicts
network.valueConflict = false;
// Aggregate values of all senders
const senders = network.providers;
let value = null;
for (let k = 0; k < senders.length; ++k) {
const senderSlot = senders[k];
const slotValue = senderSlot.slot.value;
// The first sender can just put in his value
if (!value) {
value = slotValue;
continue;
}
// If the slot is empty itself, just skip it
if (!slotValue) {
continue;
}
// If there is already an value, compare if it matches ->
// otherwise there is a conflict
if (value.equals(slotValue)) {
// All good
continue;
}
// There is a conflict, this means the value will be null anyways
network.valueConflict = true;
break;
}
// Assign value
if (network.valueConflict) {
network.currentValue = null;
} else {
network.currentValue = value;
}
}
}
/**
@@ -35,11 +417,43 @@ export class WireSystem extends GameSystemWithFilter {
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const entity = contents[x][y];
if (entity && entity.components.Wire) {
const wireType = entity.components.Wire.type;
const sprite = this.wireSprites[wireType];
const wireComp = entity.components.Wire;
const wireType = wireComp.type;
const network = wireComp.linkedNetwork;
let opacity = 1;
let spriteSet = this.wireSprites;
if (!network) {
opacity = 0.3;
} else {
if (network.valueConflict) {
opacity = 1;
// TODO
} else {
if (network.currentValue) {
if (
network.currentValue.getItemType() === enumItemType.boolean &&
// @ts-ignore
network.currentValue.value === 0
) {
opacity = 0.5;
} else {
opacity = 1;
}
} else {
opacity = 0.5;
}
}
}
const sprite = spriteSet[wireType];
assert(sprite, "Unknown wire type: " + wireType);
const staticComp = entity.components.StaticMapEntity;
parameters.context.globalAlpha = opacity;
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
parameters.context.globalAlpha = 1;
if (G_IS_DEV && globalConfig.debug.renderWireRotations) {
parameters.context.fillStyle = "red";
@@ -68,6 +482,25 @@ export class WireSystem extends GameSystemWithFilter {
}
}
}
// DEBUG Rendering
if (G_IS_DEV && globalConfig.debug.renderWireNetworkInfos) {
if (entity) {
const staticComp = entity.components.StaticMapEntity;
const wireComp = entity.components.Wire;
// Draw network info for wires
if (wireComp && wireComp.linkedNetwork) {
parameters.context.fillStyle = "red";
parameters.context.font = "5px Tahoma";
parameters.context.fillText(
"W" + wireComp.linkedNetwork.uid,
(staticComp.origin.x + 0.5) * globalConfig.tileSize,
(staticComp.origin.y + 0.5) * globalConfig.tileSize
);
}
}
}
}
}
}
@@ -86,6 +519,8 @@ export class WireSystem extends GameSystemWithFilter {
return;
}
logger.log("Updating surrounding wire placement");
const metaWire = gMetaBuildingRegistry.findByClass(MetaWireBuilding);
// Compute affected area
@@ -130,6 +565,13 @@ export class WireSystem extends GameSystemWithFilter {
targetStaticComp.rotation = rotation;
metaWire.updateVariants(targetEntity, rotationVariant, defaultBuildingVariant);
// Update code as well
targetStaticComp.code = getCodeFromBuildingData(
metaWire,
defaultBuildingVariant,
rotationVariant
);
// Make sure the chunks know about the update
this.root.signals.entityChanged.dispatch(targetEntity);
}

View File

@@ -8,6 +8,7 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
import { enumLayer } from "../root";
import { STOP_PROPAGATION } from "../../core/signal";
import { drawRotatedSprite } from "../../core/draw_utils";
import { GLOBAL_APP } from "../../core/globals";
export class WiredPinsSystem extends GameSystemWithFilter {
constructor(root) {
@@ -187,9 +188,23 @@ export class WiredPinsSystem extends GameSystemWithFilter {
const value = slot.value;
if (value) {
const offset = new Vector(0, -5).rotated(effectiveRotation);
value.draw(worldPos.x + offset.x, worldPos.y + offset.y, parameters, 12);
}
// Debug view
if (G_IS_DEV && globalConfig.debug.renderWireNetworkInfos) {
const offset = new Vector(0, -10).rotated(effectiveRotation);
const network = slot.linkedNetwork;
parameters.context.fillStyle = "blue";
parameters.context.font = "5px Tahoma";
parameters.context.textAlign = "center";
parameters.context.fillText(
network ? "S" + network.uid : "???",
(tile.x + 0.5) * globalConfig.tileSize + offset.x,
(tile.y + 0.5) * globalConfig.tileSize + offset.y
);
parameters.context.textAlign = "left";
}
}
}
}