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

Improve information when hovering wires

This commit is contained in:
tobspr
2020-08-15 17:51:28 +02:00
parent 61e7481c8f
commit 7279b75187
20 changed files with 487 additions and 255 deletions

View File

@@ -4,7 +4,7 @@ import { round3Digits } from "./utils";
const floorSpriteCoordinates = false;
export const ORIGINAL_SPRITE_SCALE = "0.5";
export const ORIGINAL_SPRITE_SCALE = "0.75";
export class BaseSprite {
/**

View File

@@ -713,3 +713,12 @@ export function rotateDirectionalObject(obj, rotation) {
left: queue[3],
};
}
/**
* Modulo which works for negative numbers
* @param {number} n
* @param {number} m
*/
export function safeModulo(n, m) {
return ((n % m) + m) % m;
}

View File

@@ -1,4 +1,5 @@
import { globalConfig } from "./config";
import { safeModulo } from "./utils";
const tileSize = globalConfig.tileSize;
const halfTileSize = globalConfig.halfTileSize;
@@ -287,6 +288,15 @@ export class Vector {
return dx * dx + dy * dy;
}
/**
* Returns x % f, y % f
* @param {number} f
* @returns {Vector} new vector
*/
modScalar(f) {
return new Vector(safeModulo(this.x, f), safeModulo(this.y, f));
}
/**
* Computes and returns the center between both points
* @param {Vector} v

View File

@@ -1,4 +1,5 @@
import { Component } from "../component";
import { WireNetwork } from "../systems/wire";
export class WireTunnelComponent extends Component {
static getId() {
@@ -16,5 +17,11 @@ export class WireTunnelComponent extends Component {
constructor({ multipleDirections = true }) {
super();
this.multipleDirections = multipleDirections;
/**
* Linked network, only if its not multiple directions
* @type {Array<WireNetwork>}
*/
this.linkedNetworks = [];
}
}

View File

@@ -78,13 +78,13 @@ export class HUDLayerPreview extends BaseHUDPart {
const content = this.root.map.getLayerContentXY(tileX, tileY, enumLayer.wires);
if (content) {
MapChunkView.drawSingleWiresOverviewTile(
this.context,
dx * globalConfig.tileSize,
dy * globalConfig.tileSize,
content,
globalConfig.tileSize
);
MapChunkView.drawSingleWiresOverviewTile({
context: this.context,
x: dx * globalConfig.tileSize,
y: dy * globalConfig.tileSize,
entity: content,
tileSizePixels: globalConfig.tileSize,
});
}
}
}

View File

@@ -1,9 +1,16 @@
import { BaseHUDPart } from "../base_hud_part";
import { enumLayer } from "../../root";
import { globalConfig } from "../../../core/config";
import { MapChunkView } from "../../map_chunk_view";
import { enumLayer } from "../../root";
import { WireNetwork } from "../../systems/wire";
import { THEME } from "../../theme";
import { BaseHUDPart } from "../base_hud_part";
import { Loader } from "../../../core/loader";
export class HUDWireInfo extends BaseHUDPart {
initialize() {}
initialize() {
this.spriteEmpty = Loader.getSprite("sprites/wires/network_empty.png");
this.spriteConflict = Loader.getSprite("sprites/wires/network_conflict.png");
}
/**
*
@@ -21,38 +28,90 @@ export class HUDWireInfo extends BaseHUDPart {
return;
}
const tile = this.root.camera.screenToWorld(mousePos).toTileSpace();
const worldPos = this.root.camera.screenToWorld(mousePos);
const tile = worldPos.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
);
if (!entity) {
// No entity
return;
}
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);
}
}
if (
!this.root.camera.getIsMapOverlayActive() &&
!this.root.logic.getIsEntityIntersectedWithMatrix(entity, worldPos)
) {
// Detailed intersection check
return;
}
const networks = this.root.logic.getEntityWireNetworks(entity, tile);
if (networks === null) {
// This entity will never be able to be connected
return;
}
if (networks.length === 0) {
// No network at all
parameters.context.fillStyle = "#333";
this.spriteEmpty.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40);
return;
}
for (let i = 0; i < networks.length; ++i) {
const network = networks[i];
this.drawHighlightedNetwork(parameters, network);
}
if (networks.length === 1) {
const network = networks[0];
if (network.valueConflict) {
this.spriteConflict.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40);
} else if (!network.currentValue) {
this.spriteEmpty.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40);
} else {
network.currentValue.draw(mousePos.x + 20, mousePos.y, parameters, 40);
}
}
}
/**
*
*
* @param {import("../../../core/draw_utils").DrawParameters} parameters
* @param {WireNetwork} network
*/
drawHighlightedNetwork(parameters, network) {
parameters.context.globalAlpha = 0.5;
for (let i = 0; i < network.wires.length; ++i) {
const wire = network.wires[i];
const staticComp = wire.components.StaticMapEntity;
const screenTile = this.root.camera.worldToScreen(staticComp.origin.toWorldSpace());
MapChunkView.drawSingleWiresOverviewTile({
context: parameters.context,
x: screenTile.x,
y: screenTile.y,
entity: wire,
tileSizePixels: globalConfig.tileSize * this.root.camera.zoomLevel,
overrideColor: THEME.map.wires.highlightColor,
});
}
for (let i = 0; i < network.tunnels.length; ++i) {
const tunnel = network.tunnels[i];
const staticComp = tunnel.components.StaticMapEntity;
const screenTile = this.root.camera.worldToScreen(staticComp.origin.toWorldSpace());
MapChunkView.drawSingleWiresOverviewTile({
context: parameters.context,
x: screenTile.x,
y: screenTile.y,
entity: tunnel,
tileSizePixels: globalConfig.tileSize * this.root.camera.zoomLevel,
overrideColor: THEME.map.wires.highlightColor,
});
}
parameters.context.globalAlpha = 1;
}
}

View File

@@ -2,9 +2,13 @@ import { createLogger } from "../core/logging";
import { STOP_PROPAGATION } from "../core/signal";
import { round2Digits } from "../core/utils";
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
import { getBuildingDataFromCode } from "./building_codes";
import { Entity } from "./entity";
import { MetaBuilding } from "./meta_building";
import { enumLayer, GameRoot } from "./root";
import { WireNetwork } from "./systems/wire";
import { globalConfig } from "../core/config";
import { CHUNK_OVERLAY_RES } from "./map_chunk_view";
const logger = createLogger("ingame/logic");
@@ -213,6 +217,92 @@ export class GameLogic {
}
}
/**
* Returns all wire networks this entity participates in on the given tile
* @param {Entity} entity
* @param {Vector} tile
* @returns {Array<WireNetwork>|null} Null if the entity is never able to be connected at the given tile
*/
getEntityWireNetworks(entity, tile) {
let canConnectAtAll = false;
/** @type {Set<WireNetwork>} */
const networks = new Set();
const staticComp = entity.components.StaticMapEntity;
const wireComp = entity.components.Wire;
if (wireComp) {
canConnectAtAll = true;
if (wireComp.linkedNetwork) {
networks.add(wireComp.linkedNetwork);
}
}
const tunnelComp = entity.components.WireTunnel;
if (tunnelComp) {
canConnectAtAll = true;
for (let i = 0; i < tunnelComp.linkedNetworks.length; ++i) {
networks.add(tunnelComp.linkedNetworks[i]);
}
}
const pinsComp = entity.components.WiredPins;
if (pinsComp) {
const slots = pinsComp.slots;
for (let i = 0; i < slots.length; ++i) {
const slot = slots[i];
const slotLocalPos = staticComp.localTileToWorld(slot.pos);
if (slotLocalPos.equals(tile)) {
canConnectAtAll = true;
if (slot.linkedNetwork) {
networks.add(slot.linkedNetwork);
}
}
}
}
if (!canConnectAtAll) {
return null;
}
return Array.from(networks);
}
/**
* Returns if the entities tile *and* his overlay matrix is intersected
* @param {Entity} entity
* @param {Vector} worldPos
*/
getIsEntityIntersectedWithMatrix(entity, worldPos) {
const staticComp = entity.components.StaticMapEntity;
const tile = worldPos.toTileSpace();
if (!staticComp.getTileSpaceBounds().containsPoint(tile.x, tile.y)) {
// No intersection at all
return;
}
const data = getBuildingDataFromCode(staticComp.code);
const overlayMatrix = data.metaInstance.getSpecialOverlayRenderMatrix(
staticComp.rotation,
data.rotationVariant,
data.variant,
entity
);
// Always the same
if (!overlayMatrix) {
return true;
}
const localPosition = worldPos
.divideScalar(globalConfig.tileSize)
.modScalar(1)
.multiplyScalar(CHUNK_OVERLAY_RES)
.floor();
return !!overlayMatrix[localPosition.x + localPosition.y * 3];
}
/**
* Gets the flag at the given tile
* @param {Vector} tile

View File

@@ -192,26 +192,28 @@ export class MapChunkView extends MapChunk {
if (!content) {
continue;
}
MapChunkView.drawSingleWiresOverviewTile(
MapChunkView.drawSingleWiresOverviewTile({
context,
x * CHUNK_OVERLAY_RES,
y * CHUNK_OVERLAY_RES,
content,
CHUNK_OVERLAY_RES
);
x: x * CHUNK_OVERLAY_RES,
y: y * CHUNK_OVERLAY_RES,
entity: content,
tileSizePixels: CHUNK_OVERLAY_RES,
});
}
}
}
}
/**
* @param {CanvasRenderingContext2D} context
* @param {number} x
* @param {number} y
* @param {Entity} entity
* @param {number} tileSizePixels
* @param {object} param0
* @param {CanvasRenderingContext2D} param0.context
* @param {number} param0.x
* @param {number} param0.y
* @param {Entity} param0.entity
* @param {number} param0.tileSizePixels
* @param {string=} param0.overrideColor Optionally override the color to be rendered
*/
static drawSingleWiresOverviewTile(context, x, y, entity, tileSizePixels) {
static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) {
const staticComp = entity.components.StaticMapEntity;
const data = getBuildingDataFromCode(staticComp.code);
const metaBuilding = data.metaInstance;
@@ -221,7 +223,7 @@ export class MapChunkView extends MapChunk {
data.variant,
entity
);
context.fillStyle = metaBuilding.getSilhouetteColor();
context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
if (overlayMatrix) {
for (let dx = 0; dx < 3; ++dx) {
for (let dy = 0; dy < 3; ++dy) {

View File

@@ -62,7 +62,7 @@ export class LogicGateSystem extends GameSystemWithFilter {
const param2 = parameters[1];
if (!param1 || !param2) {
// Not enough params
return null;
return BOOL_FALSE_SINGLETON;
}
const itemType = param1.getItemType();
@@ -88,7 +88,7 @@ export class LogicGateSystem extends GameSystemWithFilter {
compute_NOT(parameters) {
const item = parameters[0];
if (!item) {
return BOOL_FALSE_SINGLETON;
return BOOL_TRUE_SINGLETON;
}
if (item.getItemType() !== enumItemType.boolean) {
@@ -109,25 +109,24 @@ export class LogicGateSystem extends GameSystemWithFilter {
const param1 = parameters[0];
const param2 = parameters[1];
if (!param1 || !param2) {
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;
// Check for the right types
if (param1 && param1.getItemType() !== enumItemType.boolean) {
return BOOL_FALSE_SINGLETON;
}
return BOOL_FALSE_SINGLETON;
if (param2 && param2.getItemType() !== enumItemType.boolean) {
return BOOL_FALSE_SINGLETON;
}
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
}
/**
@@ -139,25 +138,21 @@ export class LogicGateSystem extends GameSystemWithFilter {
const param1 = parameters[0];
const param2 = parameters[1];
if (!param1 || !param2) {
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;
}
const valueParam1 =
param1 && param1.getItemType() === enumItemType.boolean
? /** @type {BooleanItem} */ (param1).value
: 0;
const valueParam2 =
param2 && param2.getItemType() === enumItemType.boolean
? /** @type {BooleanItem} */ (param2).value
: 0;
return BOOL_FALSE_SINGLETON;
return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
}
/**

View File

@@ -1,23 +1,25 @@
import { GameSystemWithFilter } from "../game_system_with_filter";
import { WireComponent, enumWireType } from "../components/wire";
import { MapChunkView } from "../map_chunk_view";
import { globalConfig } from "../../core/config";
import { Loader } from "../../core/loader";
import { Entity } from "../entity";
import { gMetaBuildingRegistry } from "../../core/global_registries";
import { MetaWireBuilding, arrayWireRotationVariantToType } from "../buildings/wire";
import { Loader } from "../../core/loader";
import { createLogger } from "../../core/logging";
import {
Vector,
arrayAllDirections,
enumDirection,
enumDirectionToVector,
arrayAllDirections,
enumInvertedDirections,
Vector,
} 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";
import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire";
import { getCodeFromBuildingData } from "../building_codes";
import { enumWireType, WireComponent } from "../components/wire";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunkView } from "../map_chunk_view";
import { defaultBuildingVariant } from "../meta_building";
import { WireTunnelComponent } from "../components/wire_tunnel";
import { fastArrayDeleteValueIfContained } from "../../core/utils";
import { BooleanItem } from "../items/boolean_item";
const logger = createLogger("wires");
@@ -44,6 +46,12 @@ export class WireNetwork {
*/
this.allSlots = [];
/**
* All connected tunnels
* @type {Array<Entity>}
*/
this.tunnels = [];
/**
* Which wires are in this network
* @type {Array<Entity>}
@@ -125,6 +133,7 @@ export class WireSystem extends GameSystemWithFilter {
if (!this.root.gameInitialized) {
return;
}
if (entity.components.Wire || entity.components.WiredPins || entity.components.WireTunnel) {
this.needsRecompute = true;
this.networks = [];
@@ -146,6 +155,11 @@ export class WireSystem extends GameSystemWithFilter {
wireEntities[i].components.Wire.linkedNetwork = null;
}
const tunnelEntities = this.root.entityMgr.getAllWithComponent(WireTunnelComponent);
for (let i = 0; i < tunnelEntities.length; ++i) {
tunnelEntities[i].components.WireTunnel.linkedNetworks = [];
}
const pinEntities = this.root.entityMgr.getAllWithComponent(WiredPinsComponent);
for (let i = 0; i < pinEntities.length; ++i) {
const slots = pinEntities[i].components.WiredPins.slots;
@@ -280,7 +294,11 @@ export class WireSystem extends GameSystemWithFilter {
if (newSearchTile) {
// Find new surrounding wire targets
const newTargets = this.findSurroundingWireTargets(newSearchTile, newSearchDirections);
const newTargets = this.findSurroundingWireTargets(
newSearchTile,
newSearchDirections,
currentNetwork
);
VERBOSE_WIRES && logger.log(" Found", newTargets, "new targets to visit!");
for (let i = 0; i < newTargets.length; ++i) {
@@ -301,6 +319,13 @@ export class WireSystem extends GameSystemWithFilter {
currentNetwork.wires[i].components.Wire.linkedNetwork = null;
}
for (let i = 0; i < currentNetwork.tunnels.length; ++i) {
fastArrayDeleteValueIfContained(
currentNetwork.tunnels[i].components.WireTunnel.linkedNetworks,
currentNetwork
);
}
for (let i = 0; i < currentNetwork.allSlots.length; ++i) {
currentNetwork.allSlots[i].slot.linkedNetwork = null;
}
@@ -311,9 +336,10 @@ export class WireSystem extends GameSystemWithFilter {
* Finds surrounding entities which are not yet assigned to a network
* @param {Vector} initialTile
* @param {Array<enumDirection>} directions
* @param {WireNetwork} network
* @returns {Array<any>}
*/
findSurroundingWireTargets(initialTile, directions) {
findSurroundingWireTargets(initialTile, directions, network) {
let result = [];
VERBOSE_WIRES &&
@@ -436,6 +462,14 @@ export class WireSystem extends GameSystemWithFilter {
});
}
// Add the tunnel to the network
if (tunnelComp.linkedNetworks.indexOf(network) < 0) {
tunnelComp.linkedNetworks.push(network);
}
if (network.tunnels.indexOf(entity) < 0) {
network.tunnels.push(entity);
}
// Remember this tunnel
visitedTunnels.add(entity.uid);
}
@@ -572,33 +606,9 @@ export class WireSystem extends GameSystemWithFilter {
if (entity && entity.components.Wire) {
const wireComp = entity.components.Wire;
const wireType = wireComp.type;
const network = wireComp.linkedNetwork;
const { opacity, spriteSet } = this.getSpriteSetAndOpacityForWire(wireComp);
// 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);

View File

@@ -35,7 +35,8 @@
"wires": {
"overlayColor": "rgba(97, 161, 152, 0.75)",
"previewColor": "rgb(97, 161, 152, 0.5)"
"previewColor": "rgb(97, 161, 152, 0.5)",
"highlightColor": "rgba(0, 0, 255, 0.5)"
}
},

View File

@@ -36,7 +36,8 @@
"wires": {
"overlayColor": "rgba(97, 161, 152, 0.75)",
"previewColor": "rgb(97, 161, 152, 0.4)"
"previewColor": "rgb(97, 161, 152, 0.4)",
"highlightColor": "rgba(72, 137, 255, 0.8)"
}
},