|
|
|
import { globalConfig } from "../../core/config";
|
|
|
|
import { DrawParameters } from "../../core/draw_parameters";
|
|
|
|
import { drawRotatedSprite } from "../../core/draw_utils";
|
|
|
|
import { Loader } from "../../core/loader";
|
|
|
|
import { STOP_PROPAGATION } from "../../core/signal";
|
|
|
|
import { enumDirectionToAngle, Vector } from "../../core/vector";
|
|
|
|
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
|
|
|
import { Entity } from "../entity";
|
|
|
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
|
|
|
import { MapChunkView } from "../map_chunk_view";
|
|
|
|
|
|
|
|
/** @type {Object<ItemType, number>} */
|
|
|
|
const enumTypeToSize = {
|
|
|
|
boolean: 9,
|
|
|
|
shape: 9,
|
|
|
|
color: 14,
|
|
|
|
};
|
|
|
|
|
|
|
|
export class WiredPinsSystem extends GameSystemWithFilter {
|
|
|
|
constructor(root) {
|
|
|
|
super(root, [WiredPinsComponent]);
|
|
|
|
|
|
|
|
this.pinSprites = {
|
|
|
|
[enumPinSlotType.logicalEjector]: Loader.getSprite("sprites/wires/logical_ejector.png"),
|
|
|
|
[enumPinSlotType.logicalAcceptor]: Loader.getSprite("sprites/wires/logical_acceptor.png"),
|
|
|
|
};
|
|
|
|
|
|
|
|
this.root.signals.prePlacementCheck.add(this.prePlacementCheck, this);
|
|
|
|
this.root.signals.freeEntityAreaBeforeBuild.add(this.freeEntityAreaBeforeBuild, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs pre-placement checks
|
|
|
|
* @param {Entity} entity
|
|
|
|
* @param {Vector} offset
|
|
|
|
*/
|
|
|
|
prePlacementCheck(entity, offset) {
|
|
|
|
// Compute area of the building
|
|
|
|
const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
|
|
|
|
if (offset) {
|
|
|
|
rect.x += offset.x;
|
|
|
|
rect.y += offset.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this entity is placed on the wires layer, make sure we don't
|
|
|
|
// place it above a pin
|
|
|
|
if (entity.layer === "wires") {
|
|
|
|
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
|
|
|
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
|
|
|
// Find which entities are in same tiles of both layers
|
|
|
|
const entities = this.root.map.getLayersContentsMultipleXY(x, y);
|
|
|
|
for (let i = 0; i < entities.length; ++i) {
|
|
|
|
const otherEntity = entities[i];
|
|
|
|
|
|
|
|
// Check if entity has a wired component
|
|
|
|
const pinComponent = otherEntity.components.WiredPins;
|
|
|
|
const staticComp = otherEntity.components.StaticMapEntity;
|
|
|
|
if (!pinComponent) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (staticComp.getMetaBuilding().getIsReplaceable()) {
|
|
|
|
// Don't mind here, even if there would be a collision we
|
|
|
|
// could replace it
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go over all pins and check if they are blocking
|
|
|
|
const pins = pinComponent.slots;
|
|
|
|
for (let pinSlot = 0; pinSlot < pins.length; ++pinSlot) {
|
|
|
|
const pos = staticComp.localTileToWorld(pins[pinSlot].pos);
|
|
|
|
// Occupied by a pin
|
|
|
|
if (pos.x === x && pos.y === y) {
|
|
|
|
return STOP_PROPAGATION;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for collisions on the wires layer
|
|
|
|
if (this.checkEntityPinsCollide(entity, offset)) {
|
|
|
|
return STOP_PROPAGATION;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the pins of the given entity collide on the wires layer
|
|
|
|
* @param {Entity} entity
|
|
|
|
* @param {Vector=} offset Optional, move the entity by the given offset first
|
|
|
|
* @returns {boolean} True if the pins collide
|
|
|
|
*/
|
|
|
|
checkEntityPinsCollide(entity, offset) {
|
|
|
|
const pinsComp = entity.components.WiredPins;
|
|
|
|
if (!pinsComp) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go over all slots
|
|
|
|
for (let slotIndex = 0; slotIndex < pinsComp.slots.length; ++slotIndex) {
|
|
|
|
const slot = pinsComp.slots[slotIndex];
|
|
|
|
|
|
|
|
// Figure out which tile this slot is on
|
|
|
|
const worldPos = entity.components.StaticMapEntity.localTileToWorld(slot.pos);
|
|
|
|
if (offset) {
|
|
|
|
worldPos.x += offset.x;
|
|
|
|
worldPos.y += offset.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there is any entity on that tile (Wired pins are always on the wires layer)
|
|
|
|
const collidingEntity = this.root.map.getLayerContentXY(worldPos.x, worldPos.y, "wires");
|
|
|
|
|
|
|
|
// If there's an entity, and it can't get removed -> That's a collision
|
|
|
|
if (collidingEntity) {
|
|
|
|
if (!collidingEntity.components.StaticMapEntity.getMetaBuilding().getIsReplaceable()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called to free space for the given entity
|
|
|
|
* @param {Entity} entity
|
|
|
|
*/
|
|
|
|
freeEntityAreaBeforeBuild(entity) {
|
|
|
|
const pinsComp = entity.components.WiredPins;
|
|
|
|
if (!pinsComp) {
|
|
|
|
// Entity has no pins
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any stuff which collides with the pins
|
|
|
|
for (let i = 0; i < pinsComp.slots.length; ++i) {
|
|
|
|
const slot = pinsComp.slots[i];
|
|
|
|
const worldPos = entity.components.StaticMapEntity.localTileToWorld(slot.pos);
|
|
|
|
const collidingEntity = this.root.map.getLayerContentXY(worldPos.x, worldPos.y, "wires");
|
|
|
|
if (collidingEntity) {
|
|
|
|
assertAlways(
|
|
|
|
collidingEntity.components.StaticMapEntity.getMetaBuilding().getIsReplaceable(),
|
|
|
|
"Tried to replace non-repleaceable entity for pins"
|
|
|
|
);
|
|
|
|
if (!this.root.logic.tryDeleteBuilding(collidingEntity)) {
|
|
|
|
assertAlways(false, "Tried to replace non-repleaceable entity for pins #2");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws a given entity
|
|
|
|
* @param {DrawParameters} parameters
|
|
|
|
* @param {MapChunkView} chunk
|
|
|
|
*/
|
|
|
|
drawChunk(parameters, chunk) {
|
|
|
|
const contents = chunk.containedEntities;
|
|
|
|
|
|
|
|
for (let i = 0; i < contents.length; ++i) {
|
|
|
|
const entity = contents[i];
|
|
|
|
const pinsComp = entity.components.WiredPins;
|
|
|
|
if (!pinsComp) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const staticComp = entity.components.StaticMapEntity;
|
|
|
|
const slots = pinsComp.slots;
|
|
|
|
|
|
|
|
for (let j = 0; j < slots.length; ++j) {
|
|
|
|
const slot = slots[j];
|
|
|
|
const tile = staticComp.localTileToWorld(slot.pos);
|
|
|
|
|
|
|
|
if (!chunk.tileSpaceRectangle.containsPoint(tile.x, tile.y)) {
|
|
|
|
// Doesn't belong to this chunk
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const worldPos = tile.toWorldSpaceCenterOfTile();
|
|
|
|
|
|
|
|
// Culling
|
|
|
|
if (
|
|
|
|
!parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, globalConfig.halfTileSize)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const effectiveRotation = Math.radians(
|
|
|
|
staticComp.rotation + enumDirectionToAngle[slot.direction]
|
|
|
|
);
|
|
|
|
|
|
|
|
if (staticComp.getMetaBuilding().getRenderPins()) {
|
|
|
|
drawRotatedSprite({
|
|
|
|
parameters,
|
|
|
|
sprite: this.pinSprites[slot.type],
|
|
|
|
x: worldPos.x,
|
|
|
|
y: worldPos.y,
|
|
|
|
angle: effectiveRotation,
|
|
|
|
size: globalConfig.tileSize + 2,
|
|
|
|
offsetX: 0,
|
|
|
|
offsetY: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw contained item to visualize whats emitted
|
|
|
|
const value = slot.value;
|
|
|
|
if (value) {
|
|
|
|
const offset = new Vector(0, -9.1).rotated(effectiveRotation);
|
|
|
|
|
|
|
|
value.drawItemCenteredClipped(
|
|
|
|
worldPos.x + offset.x,
|
|
|
|
worldPos.y + offset.y,
|
|
|
|
parameters,
|
|
|
|
enumTypeToSize[value.getItemType()]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|