diff --git a/res_raw/atlas.tps b/res_raw/atlas.tps
index 95fe82bf..d2332b2d 100644
--- a/res_raw/atlas.tps
+++ b/res_raw/atlas.tps
@@ -277,6 +277,11 @@
sprites/blueprints/underground_belt_entry.png
sprites/blueprints/underground_belt_exit-tier2.png
sprites/blueprints/underground_belt_exit.png
+ sprites/blueprints/virtual_processor-analyzer.png
+ sprites/blueprints/virtual_processor-rotater.png
+ sprites/blueprints/virtual_processor-shapecompare.png
+ sprites/blueprints/virtual_processor-unstacker.png
+ sprites/blueprints/virtual_processor.png
sprites/blueprints/wire_tunnel-coating.png
sprites/blueprints/wire_tunnel.png
sprites/buildings/constant_signal.png
@@ -296,6 +301,11 @@
sprites/buildings/underground_belt_entry.png
sprites/buildings/underground_belt_exit-tier2.png
sprites/buildings/underground_belt_exit.png
+ sprites/buildings/virtual_processor-analyzer.png
+ sprites/buildings/virtual_processor-rotater.png
+ sprites/buildings/virtual_processor-shapecompare.png
+ sprites/buildings/virtual_processor-unstacker.png
+ sprites/buildings/virtual_processor.png
sprites/buildings/wire_tunnel-coating.png
sprites/buildings/wire_tunnel.png
sprites/wires/lever_on.png
diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js
index 0918e7af..1ea04955 100644
--- a/src/js/game/map_chunk_view.js
+++ b/src/js/game/map_chunk_view.js
@@ -1,274 +1,281 @@
-import { globalConfig } from "../core/config";
-import { DrawParameters } from "../core/draw_parameters";
-import { getBuildingDataFromCode } from "./building_codes";
-import { Entity } from "./entity";
-import { MapChunk } from "./map_chunk";
-import { GameRoot } from "./root";
-import { THEME } from "./theme";
-import { drawSpriteClipped } from "../core/draw_utils";
-
-export const CHUNK_OVERLAY_RES = 3;
-
-export class MapChunkView extends MapChunk {
- /**
- *
- * @param {GameRoot} root
- * @param {number} x
- * @param {number} y
- */
- constructor(root, x, y) {
- super(root, x, y);
-
- /**
- * Whenever something changes, we increase this number - so we know we need to redraw
- */
- this.renderIteration = 0;
-
- this.markDirty();
- }
-
- /**
- * Marks this chunk as dirty, rerendering all caches
- */
- markDirty() {
- ++this.renderIteration;
- this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
- }
-
- /**
- * Draws the background layer
- * @param {DrawParameters} parameters
- */
- drawBackgroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
- systems.mapResources.drawChunk(parameters, this);
- systems.beltUnderlays.drawChunk(parameters, this);
- systems.belt.drawChunk(parameters, this);
- }
-
- /**
- * Draws the foreground layer
- * @param {DrawParameters} parameters
- */
- drawForegroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
-
- systems.itemEjector.drawChunk(parameters, this);
- systems.itemAcceptor.drawChunk(parameters, this);
-
- systems.miner.drawChunk(parameters, this);
-
- systems.staticMapEntities.drawChunk(parameters, this);
- systems.lever.drawChunk(parameters, this);
- systems.display.drawChunk(parameters, this);
- systems.storage.drawChunk(parameters, this);
- }
-
- /**
- * Overlay
- * @param {DrawParameters} parameters
- */
- drawOverlay(parameters) {
- const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
- const sprite = this.root.buffers.getForKey({
- key: "chunk@" + this.root.currentLayer,
- subKey: this.renderKey,
- w: overlaySize,
- h: overlaySize,
- dpi: 1,
- redrawMethod: this.generateOverlayBuffer.bind(this),
- });
-
- const dims = globalConfig.mapChunkWorldSize;
-
- // Draw chunk "pixel" art
- parameters.context.imageSmoothingEnabled = false;
- drawSpriteClipped({
- parameters,
- sprite,
- x: this.x * dims,
- y: this.y * dims,
- w: dims,
- h: dims,
- originalW: overlaySize,
- originalH: overlaySize,
- });
-
- parameters.context.imageSmoothingEnabled = true;
-
- // Draw patch items
- if (this.root.currentLayer === "regular") {
- for (let i = 0; i < this.patches.length; ++i) {
- const patch = this.patches[i];
-
- const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
- const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
- const diameter = Math.min(80, 30 / parameters.zoomLevel);
-
- patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
- }
- }
- }
-
- /**
- *
- * @param {HTMLCanvasElement} canvas
- * @param {CanvasRenderingContext2D} context
- * @param {number} w
- * @param {number} h
- * @param {number} dpi
- */
- generateOverlayBuffer(canvas, context, w, h, dpi) {
- context.fillStyle =
- this.containedEntities.length > 0
- ? THEME.map.chunkOverview.filled
- : THEME.map.chunkOverview.empty;
- context.fillRect(0, 0, w, h);
-
- for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
- const lowerArray = this.lowerLayer[x];
- const upperArray = this.contents[x];
- for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
- const upperContent = upperArray[y];
- if (upperContent) {
- const staticComp = upperContent.components.StaticMapEntity;
- const data = getBuildingDataFromCode(staticComp.code);
- const metaBuilding = data.metaInstance;
-
- const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
- staticComp.rotation,
- data.rotationVariant,
- data.variant,
- upperContent
- );
-
- if (overlayMatrix) {
- // Draw lower content first since it "shines" through
- const lowerContent = lowerArray[y];
- if (lowerContent) {
- context.fillStyle = lowerContent.getBackgroundColorAsResource();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
- }
-
- context.fillStyle = metaBuilding.getSilhouetteColor();
- for (let dx = 0; dx < 3; ++dx) {
- for (let dy = 0; dy < 3; ++dy) {
- const isFilled = overlayMatrix[dx + dy * 3];
- if (isFilled) {
- context.fillRect(
- x * CHUNK_OVERLAY_RES + dx,
- y * CHUNK_OVERLAY_RES + dy,
- 1,
- 1
- );
- }
- }
- }
-
- continue;
- } else {
- context.fillStyle = metaBuilding.getSilhouetteColor();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
-
- continue;
- }
- }
-
- const lowerContent = lowerArray[y];
- if (lowerContent) {
- context.fillStyle = lowerContent.getBackgroundColorAsResource();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
- }
- }
- }
-
- if (this.root.currentLayer === "wires") {
- // Draw wires overlay
-
- context.fillStyle = THEME.map.wires.overlayColor;
- context.fillRect(0, 0, w, h);
-
- for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
- const wiresArray = this.wireContents[x];
- for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
- const content = wiresArray[y];
- if (!content) {
- continue;
- }
- MapChunkView.drawSingleWiresOverviewTile({
- context,
- x: x * CHUNK_OVERLAY_RES,
- y: y * CHUNK_OVERLAY_RES,
- entity: content,
- tileSizePixels: CHUNK_OVERLAY_RES,
- });
- }
- }
- }
- }
-
- /**
- * @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, overrideColor = null }) {
- const staticComp = entity.components.StaticMapEntity;
- const data = getBuildingDataFromCode(staticComp.code);
- const metaBuilding = data.metaInstance;
- const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
- staticComp.rotation,
- data.rotationVariant,
- data.variant,
- entity
- );
- context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
- if (overlayMatrix) {
- for (let dx = 0; dx < 3; ++dx) {
- for (let dy = 0; dy < 3; ++dy) {
- const isFilled = overlayMatrix[dx + dy * 3];
- if (isFilled) {
- context.fillRect(
- x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES,
- y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES,
- tileSizePixels / CHUNK_OVERLAY_RES,
- tileSizePixels / CHUNK_OVERLAY_RES
- );
- }
- }
- }
- } else {
- context.fillRect(x, y, tileSizePixels, tileSizePixels);
- }
- }
-
- /**
- * Draws the wires layer
- * @param {DrawParameters} parameters
- */
- drawWiresForegroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
- systems.wire.drawChunk(parameters, this);
- systems.staticMapEntities.drawWiresChunk(parameters, this);
- systems.wiredPins.drawChunk(parameters, this);
- }
-}
+import { globalConfig } from "../core/config";
+import { DrawParameters } from "../core/draw_parameters";
+import { getBuildingDataFromCode } from "./building_codes";
+import { Entity } from "./entity";
+import { MapChunk } from "./map_chunk";
+import { GameRoot } from "./root";
+import { THEME } from "./theme";
+import { drawSpriteClipped } from "../core/draw_utils";
+
+export const CHUNK_OVERLAY_RES = 3;
+
+export class MapChunkView extends MapChunk {
+ /**
+ *
+ * @param {GameRoot} root
+ * @param {number} x
+ * @param {number} y
+ */
+ constructor(root, x, y) {
+ super(root, x, y);
+
+ /**
+ * Whenever something changes, we increase this number - so we know we need to redraw
+ */
+ this.renderIteration = 0;
+
+ this.markDirty();
+ }
+
+ /**
+ * Marks this chunk as dirty, rerendering all caches
+ */
+ markDirty() {
+ ++this.renderIteration;
+ this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
+ }
+
+ /**
+ * Draws the background layer
+ * @param {DrawParameters} parameters
+ */
+ drawBackgroundLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+ systems.mapResources.drawChunk(parameters, this);
+ systems.beltUnderlays.drawChunk(parameters, this);
+ systems.belt.drawChunk(parameters, this);
+ }
+
+ /**
+ * Draws the dynamic foreground layer
+ * @param {DrawParameters} parameters
+ */
+ drawForegroundDynamicLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+
+ systems.itemEjector.drawChunk(parameters, this);
+ systems.itemAcceptor.drawChunk(parameters, this);
+ systems.miner.drawChunk(parameters, this);
+ }
+
+ /**
+ * Draws the static foreground layer
+ * @param {DrawParameters} parameters
+ */
+ drawForegroundStaticLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+
+ systems.staticMapEntities.drawChunk(parameters, this);
+ systems.lever.drawChunk(parameters, this);
+ systems.display.drawChunk(parameters, this);
+ systems.storage.drawChunk(parameters, this);
+ }
+
+ /**
+ * Overlay
+ * @param {DrawParameters} parameters
+ */
+ drawOverlay(parameters) {
+ const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
+ const sprite = this.root.buffers.getForKey({
+ key: "chunk@" + this.root.currentLayer,
+ subKey: this.renderKey,
+ w: overlaySize,
+ h: overlaySize,
+ dpi: 1,
+ redrawMethod: this.generateOverlayBuffer.bind(this),
+ });
+
+ const dims = globalConfig.mapChunkWorldSize;
+
+ // Draw chunk "pixel" art
+ parameters.context.imageSmoothingEnabled = false;
+ drawSpriteClipped({
+ parameters,
+ sprite,
+ x: this.x * dims,
+ y: this.y * dims,
+ w: dims,
+ h: dims,
+ originalW: overlaySize,
+ originalH: overlaySize,
+ });
+
+ parameters.context.imageSmoothingEnabled = true;
+
+ // Draw patch items
+ if (this.root.currentLayer === "regular") {
+ for (let i = 0; i < this.patches.length; ++i) {
+ const patch = this.patches[i];
+
+ const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
+ const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
+ const diameter = Math.min(80, 30 / parameters.zoomLevel);
+
+ patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} w
+ * @param {number} h
+ * @param {number} dpi
+ */
+ generateOverlayBuffer(canvas, context, w, h, dpi) {
+ context.fillStyle =
+ this.containedEntities.length > 0
+ ? THEME.map.chunkOverview.filled
+ : THEME.map.chunkOverview.empty;
+ context.fillRect(0, 0, w, h);
+
+ for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
+ const lowerArray = this.lowerLayer[x];
+ const upperArray = this.contents[x];
+ for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
+ const upperContent = upperArray[y];
+ if (upperContent) {
+ const staticComp = upperContent.components.StaticMapEntity;
+ const data = getBuildingDataFromCode(staticComp.code);
+ const metaBuilding = data.metaInstance;
+
+ const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
+ staticComp.rotation,
+ data.rotationVariant,
+ data.variant,
+ upperContent
+ );
+
+ if (overlayMatrix) {
+ // Draw lower content first since it "shines" through
+ const lowerContent = lowerArray[y];
+ if (lowerContent) {
+ context.fillStyle = lowerContent.getBackgroundColorAsResource();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+ }
+
+ context.fillStyle = metaBuilding.getSilhouetteColor();
+ for (let dx = 0; dx < 3; ++dx) {
+ for (let dy = 0; dy < 3; ++dy) {
+ const isFilled = overlayMatrix[dx + dy * 3];
+ if (isFilled) {
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES + dx,
+ y * CHUNK_OVERLAY_RES + dy,
+ 1,
+ 1
+ );
+ }
+ }
+ }
+
+ continue;
+ } else {
+ context.fillStyle = metaBuilding.getSilhouetteColor();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+
+ continue;
+ }
+ }
+
+ const lowerContent = lowerArray[y];
+ if (lowerContent) {
+ context.fillStyle = lowerContent.getBackgroundColorAsResource();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+ }
+ }
+ }
+
+ if (this.root.currentLayer === "wires") {
+ // Draw wires overlay
+
+ context.fillStyle = THEME.map.wires.overlayColor;
+ context.fillRect(0, 0, w, h);
+
+ for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
+ const wiresArray = this.wireContents[x];
+ for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
+ const content = wiresArray[y];
+ if (!content) {
+ continue;
+ }
+ MapChunkView.drawSingleWiresOverviewTile({
+ context,
+ x: x * CHUNK_OVERLAY_RES,
+ y: y * CHUNK_OVERLAY_RES,
+ entity: content,
+ tileSizePixels: CHUNK_OVERLAY_RES,
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * @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, overrideColor = null }) {
+ const staticComp = entity.components.StaticMapEntity;
+ const data = getBuildingDataFromCode(staticComp.code);
+ const metaBuilding = data.metaInstance;
+ const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
+ staticComp.rotation,
+ data.rotationVariant,
+ data.variant,
+ entity
+ );
+ context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
+ if (overlayMatrix) {
+ for (let dx = 0; dx < 3; ++dx) {
+ for (let dy = 0; dy < 3; ++dy) {
+ const isFilled = overlayMatrix[dx + dy * 3];
+ if (isFilled) {
+ context.fillRect(
+ x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES,
+ y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES,
+ tileSizePixels / CHUNK_OVERLAY_RES,
+ tileSizePixels / CHUNK_OVERLAY_RES
+ );
+ }
+ }
+ }
+ } else {
+ context.fillRect(x, y, tileSizePixels, tileSizePixels);
+ }
+ }
+
+ /**
+ * Draws the wires layer
+ * @param {DrawParameters} parameters
+ */
+ drawWiresForegroundLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+ systems.wire.drawChunk(parameters, this);
+ systems.staticMapEntities.drawWiresChunk(parameters, this);
+ systems.wiredPins.drawChunk(parameters, this);
+ }
+}
diff --git a/src/js/game/map_view.js b/src/js/game/map_view.js
index 178344a7..7eb864cd 100644
--- a/src/js/game/map_view.js
+++ b/src/js/game/map_view.js
@@ -1,252 +1,253 @@
-import { globalConfig } from "../core/config";
-import { DrawParameters } from "../core/draw_parameters";
-import { BaseMap } from "./map";
-import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
-import { Entity } from "./entity";
-import { THEME } from "./theme";
-import { MapChunkView } from "./map_chunk_view";
-
-/**
- * This is the view of the map, it extends the map which is the raw model and allows
- * to draw it
- */
-export class MapView extends BaseMap {
- constructor(root) {
- super(root);
-
- /**
- * DPI of the background cache images, required in some places
- */
- this.backgroundCacheDPI = 2;
-
- /**
- * The cached background sprite, containing the flat background
- * @type {HTMLCanvasElement} */
- this.cachedBackgroundCanvas = null;
-
- /** @type {CanvasRenderingContext2D} */
- this.cachedBackgroundContext = null;
- /**
- * Cached pattern of the stripes background
- * @type {CanvasPattern} */
- this.cachedBackgroundPattern = null;
-
- this.internalInitializeCachedBackgroundCanvases();
- this.root.signals.aboutToDestruct.add(this.cleanup, this);
-
- this.root.signals.entityAdded.add(this.onEntityChanged, this);
- this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
- this.root.signals.entityChanged.add(this.onEntityChanged, this);
- }
-
- cleanup() {
- freeCanvas(this.cachedBackgroundCanvas);
- this.cachedBackgroundCanvas = null;
- this.cachedBackgroundPattern = null;
- }
-
- /**
- * Called when an entity was added, removed or changed
- * @param {Entity} entity
- */
- onEntityChanged(entity) {
- const staticComp = entity.components.StaticMapEntity;
- if (staticComp) {
- const rect = staticComp.getTileSpaceBounds();
- for (let x = rect.x; x <= rect.right(); ++x) {
- for (let y = rect.y; y <= rect.bottom(); ++y) {
- this.root.map.getOrCreateChunkAtTile(x, y).markDirty();
- }
- }
- }
- }
-
- /**
- * Draws all static entities like buildings etc.
- * @param {DrawParameters} drawParameters
- */
- drawStaticEntityDebugOverlays(drawParameters) {
- const cullRange = drawParameters.visibleRect.toTileCullRectangle();
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 1;
-
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border - 1;
-
- // Render y from top down for proper blending
- for (let y = minY; y <= maxY; ++y) {
- for (let x = minX; x <= maxX; ++x) {
- // const content = this.tiles[x][y];
- const chunk = this.getChunkAtTileOrNull(x, y);
- if (!chunk) {
- continue;
- }
- const content = chunk.getTileContentFromWorldCoords(x, y);
- if (content) {
- let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
- if (!isBorder) {
- content.drawDebugOverlays(drawParameters);
- }
- }
- }
- }
- }
-
- /**
- * Initializes all canvases used for background rendering
- */
- internalInitializeCachedBackgroundCanvases() {
- // Background canvas
- const dims = globalConfig.tileSize;
- const dpi = this.backgroundCacheDPI;
- const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, {
- smooth: false,
- label: "map-cached-bg",
- });
- context.scale(dpi, dpi);
-
- context.fillStyle = THEME.map.background;
- context.fillRect(0, 0, dims, dims);
-
- const borderWidth = THEME.map.gridLineWidth;
- context.fillStyle = THEME.map.grid;
- context.fillRect(0, 0, dims, borderWidth);
- context.fillRect(0, borderWidth, borderWidth, dims);
-
- context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth);
- context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth);
-
- this.cachedBackgroundCanvas = canvas;
- this.cachedBackgroundContext = context;
- }
-
- /**
- * Draws the maps foreground
- * @param {DrawParameters} parameters
- */
- drawForeground(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundLayer);
- }
-
- /**
- * Calls a given method on all given chunks
- * @param {DrawParameters} parameters
- * @param {function} method
- */
- drawVisibleChunks(parameters, method) {
- const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 0;
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border;
-
- const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
- const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
-
- const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
- const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
-
- // Render y from top down for proper blending
- for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
- for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
- const chunk = this.root.map.getChunk(chunkX, chunkY, true);
- method.call(chunk, parameters);
- }
- }
- }
-
- /**
- * Draws the wires foreground
- * @param {DrawParameters} parameters
- */
- drawWiresForegroundLayer(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer);
- }
-
- /**
- * Draws the map overlay
- * @param {DrawParameters} parameters
- */
- drawOverlay(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay);
- }
-
- /**
- * Draws the map background
- * @param {DrawParameters} parameters
- */
- drawBackground(parameters) {
- if (!this.cachedBackgroundPattern) {
- this.cachedBackgroundPattern = parameters.context.createPattern(
- this.cachedBackgroundCanvas,
- "repeat"
- );
- }
-
- if (!this.root.app.settings.getAllSettings().disableTileGrid) {
- const dpi = this.backgroundCacheDPI;
- parameters.context.scale(1 / dpi, 1 / dpi);
-
- parameters.context.fillStyle = this.cachedBackgroundPattern;
- parameters.context.fillRect(
- parameters.visibleRect.x * dpi,
- parameters.visibleRect.y * dpi,
- parameters.visibleRect.w * dpi,
- parameters.visibleRect.h * dpi
- );
- parameters.context.scale(dpi, dpi);
- }
-
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
-
- if (G_IS_DEV && globalConfig.debug.showChunkBorders) {
- const cullRange = parameters.visibleRect.toTileCullRectangle();
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 1;
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border - 1;
-
- const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
- const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
-
- const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
- const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
-
- for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
- for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
- parameters.context.fillStyle = "#ffaaaa";
- parameters.context.fillRect(
- chunkX * globalConfig.mapChunkWorldSize,
- chunkY * globalConfig.mapChunkWorldSize,
- globalConfig.mapChunkWorldSize,
- 3
- );
- parameters.context.fillRect(
- chunkX * globalConfig.mapChunkWorldSize,
- chunkY * globalConfig.mapChunkWorldSize,
- 3,
- globalConfig.mapChunkWorldSize
- );
- }
- }
- }
- }
-}
+import { globalConfig } from "../core/config";
+import { DrawParameters } from "../core/draw_parameters";
+import { BaseMap } from "./map";
+import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
+import { Entity } from "./entity";
+import { THEME } from "./theme";
+import { MapChunkView } from "./map_chunk_view";
+
+/**
+ * This is the view of the map, it extends the map which is the raw model and allows
+ * to draw it
+ */
+export class MapView extends BaseMap {
+ constructor(root) {
+ super(root);
+
+ /**
+ * DPI of the background cache images, required in some places
+ */
+ this.backgroundCacheDPI = 2;
+
+ /**
+ * The cached background sprite, containing the flat background
+ * @type {HTMLCanvasElement} */
+ this.cachedBackgroundCanvas = null;
+
+ /** @type {CanvasRenderingContext2D} */
+ this.cachedBackgroundContext = null;
+ /**
+ * Cached pattern of the stripes background
+ * @type {CanvasPattern} */
+ this.cachedBackgroundPattern = null;
+
+ this.internalInitializeCachedBackgroundCanvases();
+ this.root.signals.aboutToDestruct.add(this.cleanup, this);
+
+ this.root.signals.entityAdded.add(this.onEntityChanged, this);
+ this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
+ this.root.signals.entityChanged.add(this.onEntityChanged, this);
+ }
+
+ cleanup() {
+ freeCanvas(this.cachedBackgroundCanvas);
+ this.cachedBackgroundCanvas = null;
+ this.cachedBackgroundPattern = null;
+ }
+
+ /**
+ * Called when an entity was added, removed or changed
+ * @param {Entity} entity
+ */
+ onEntityChanged(entity) {
+ const staticComp = entity.components.StaticMapEntity;
+ if (staticComp) {
+ const rect = staticComp.getTileSpaceBounds();
+ for (let x = rect.x; x <= rect.right(); ++x) {
+ for (let y = rect.y; y <= rect.bottom(); ++y) {
+ this.root.map.getOrCreateChunkAtTile(x, y).markDirty();
+ }
+ }
+ }
+ }
+
+ /**
+ * Draws all static entities like buildings etc.
+ * @param {DrawParameters} drawParameters
+ */
+ drawStaticEntityDebugOverlays(drawParameters) {
+ const cullRange = drawParameters.visibleRect.toTileCullRectangle();
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 1;
+
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border - 1;
+
+ // Render y from top down for proper blending
+ for (let y = minY; y <= maxY; ++y) {
+ for (let x = minX; x <= maxX; ++x) {
+ // const content = this.tiles[x][y];
+ const chunk = this.getChunkAtTileOrNull(x, y);
+ if (!chunk) {
+ continue;
+ }
+ const content = chunk.getTileContentFromWorldCoords(x, y);
+ if (content) {
+ let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
+ if (!isBorder) {
+ content.drawDebugOverlays(drawParameters);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Initializes all canvases used for background rendering
+ */
+ internalInitializeCachedBackgroundCanvases() {
+ // Background canvas
+ const dims = globalConfig.tileSize;
+ const dpi = this.backgroundCacheDPI;
+ const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, {
+ smooth: false,
+ label: "map-cached-bg",
+ });
+ context.scale(dpi, dpi);
+
+ context.fillStyle = THEME.map.background;
+ context.fillRect(0, 0, dims, dims);
+
+ const borderWidth = THEME.map.gridLineWidth;
+ context.fillStyle = THEME.map.grid;
+ context.fillRect(0, 0, dims, borderWidth);
+ context.fillRect(0, borderWidth, borderWidth, dims);
+
+ context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth);
+ context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth);
+
+ this.cachedBackgroundCanvas = canvas;
+ this.cachedBackgroundContext = context;
+ }
+
+ /**
+ * Draws the maps foreground
+ * @param {DrawParameters} parameters
+ */
+ drawForeground(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundDynamicLayer);
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundStaticLayer);
+ }
+
+ /**
+ * Calls a given method on all given chunks
+ * @param {DrawParameters} parameters
+ * @param {function} method
+ */
+ drawVisibleChunks(parameters, method) {
+ const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 0;
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border;
+
+ const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
+ const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
+
+ const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
+ const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
+
+ // Render y from top down for proper blending
+ for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
+ for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
+ const chunk = this.root.map.getChunk(chunkX, chunkY, true);
+ method.call(chunk, parameters);
+ }
+ }
+ }
+
+ /**
+ * Draws the wires foreground
+ * @param {DrawParameters} parameters
+ */
+ drawWiresForegroundLayer(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer);
+ }
+
+ /**
+ * Draws the map overlay
+ * @param {DrawParameters} parameters
+ */
+ drawOverlay(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay);
+ }
+
+ /**
+ * Draws the map background
+ * @param {DrawParameters} parameters
+ */
+ drawBackground(parameters) {
+ if (!this.cachedBackgroundPattern) {
+ this.cachedBackgroundPattern = parameters.context.createPattern(
+ this.cachedBackgroundCanvas,
+ "repeat"
+ );
+ }
+
+ if (!this.root.app.settings.getAllSettings().disableTileGrid) {
+ const dpi = this.backgroundCacheDPI;
+ parameters.context.scale(1 / dpi, 1 / dpi);
+
+ parameters.context.fillStyle = this.cachedBackgroundPattern;
+ parameters.context.fillRect(
+ parameters.visibleRect.x * dpi,
+ parameters.visibleRect.y * dpi,
+ parameters.visibleRect.w * dpi,
+ parameters.visibleRect.h * dpi
+ );
+ parameters.context.scale(dpi, dpi);
+ }
+
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
+
+ if (G_IS_DEV && globalConfig.debug.showChunkBorders) {
+ const cullRange = parameters.visibleRect.toTileCullRectangle();
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 1;
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border - 1;
+
+ const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
+ const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
+
+ const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
+ const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
+
+ for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
+ for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
+ parameters.context.fillStyle = "#ffaaaa";
+ parameters.context.fillRect(
+ chunkX * globalConfig.mapChunkWorldSize,
+ chunkY * globalConfig.mapChunkWorldSize,
+ globalConfig.mapChunkWorldSize,
+ 3
+ );
+ parameters.context.fillRect(
+ chunkX * globalConfig.mapChunkWorldSize,
+ chunkY * globalConfig.mapChunkWorldSize,
+ 3,
+ globalConfig.mapChunkWorldSize
+ );
+ }
+ }
+ }
+ }
+}