Huge rendering performance improvements and minor other changes, lots of refactorings

pull/573/head
tobspr 4 years ago
parent d1a5dd8c9e
commit b2880700e8

@ -27,6 +27,8 @@ export class BufferMaintainer {
this.iterationIndex = 1; this.iterationIndex = 1;
this.lastIteration = 0; this.lastIteration = 0;
this.root.signals.gameFrameStarted.add(this.update, this);
} }
/** /**

@ -53,6 +53,8 @@ export const globalConfig = {
beltSpeedItemsPerSecond: 2, beltSpeedItemsPerSecond: 2,
minerSpeedItemsPerSecond: 0, // COMPUTED minerSpeedItemsPerSecond: 0, // COMPUTED
defaultItemDiameter: 20,
itemSpacingOnBelts: 0.63, itemSpacingOnBelts: 0.63,
wiresSpeedItemsPerSecond: 6, wiresSpeedItemsPerSecond: 6,

@ -1,3 +1,4 @@
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { BasicSerializableObject } from "../savegame/serialization"; import { BasicSerializableObject } from "../savegame/serialization";
@ -57,7 +58,22 @@ export class BaseItem extends BasicSerializableObject {
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {number=} diameter * @param {number=} diameter
*/ */
drawCentered(x, y, parameters, diameter) {} drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
if (parameters.visibleRect.containsCircle(x, y, diameter / 2)) {
this.drawItemCenteredImpl(x, y, parameters, diameter);
}
}
/**
* INTERNAL
* @param {number} x
* @param {number} y
* @param {DrawParameters} parameters
* @param {number=} diameter
*/
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
abstract;
}
getBackgroundColorAsResource() { getBackgroundColorAsResource() {
abstract; abstract;

@ -1194,9 +1194,13 @@ export class BeltPath extends BasicSerializableObject {
const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile(); const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
const distanceAndItem = this.items[currentItemIndex]; const distanceAndItem = this.items[currentItemIndex];
if (parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, 10)) {
distanceAndItem[_item].drawCentered(worldPos.x, worldPos.y, parameters); distanceAndItem[_item].drawItemCenteredClipped(
} worldPos.x,
worldPos.y,
parameters,
globalConfig.defaultItemDiameter
);
// Check for the next item // Check for the next item
currentItemPos += distanceAndItem[_nextDistance]; currentItemPos += distanceAndItem[_nextDistance];

@ -92,7 +92,7 @@ export class Blueprint {
parameters.context.globalAlpha = 1; parameters.context.globalAlpha = 1;
} }
staticComp.drawSpriteOnFullEntityBounds(parameters, staticComp.getBlueprintSprite(), 0, newPos); staticComp.drawSpriteOnBoundsClipped(parameters, staticComp.getBlueprintSprite(), 0, newPos);
} }
parameters.context.globalAlpha = 1; parameters.context.globalAlpha = 1;
} }

@ -162,8 +162,9 @@ export class StaticMapEntityComponent extends Component {
* @returns {Vector} * @returns {Vector}
*/ */
localTileToWorld(localTile) { localTileToWorld(localTile) {
const result = this.applyRotationToVector(localTile); const result = localTile.rotateFastMultipleOf90(this.rotation);
result.addInplace(this.origin); result.x += this.origin.x;
result.y += this.origin.y;
return result; return result;
} }
@ -235,7 +236,7 @@ export class StaticMapEntityComponent extends Component {
* @param {number=} extrudePixels How many pixels to extrude the sprite * @param {number=} extrudePixels How many pixels to extrude the sprite
* @param {Vector=} overridePosition Whether to drwa the entity at a different location * @param {Vector=} overridePosition Whether to drwa the entity at a different location
*/ */
drawSpriteOnFullEntityBounds(parameters, sprite, extrudePixels = 0, overridePosition = null) { drawSpriteOnBoundsClipped(parameters, sprite, extrudePixels = 0, overridePosition = null) {
if (!this.shouldBeDrawn(parameters) && !overridePosition) { if (!this.shouldBeDrawn(parameters) && !overridePosition) {
return; return;
} }

@ -1,6 +1,8 @@
import { enumDirection, Vector } from "../../core/vector"; import { enumDirection, Vector } from "../../core/vector";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { Component } from "../component"; import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { typeItemSingleton } from "../item_resolver";
/** @enum {string} */ /** @enum {string} */
export const enumPinSlotType = { export const enumPinSlotType = {
@ -27,6 +29,16 @@ export class WiredPinsComponent extends Component {
return "WiredPins"; return "WiredPins";
} }
static getSchema() {
return {
slots: types.array(
types.structured({
value: types.nullable(typeItemSingleton),
})
),
};
}
/** /**
* *
* @param {object} param0 * @param {object} param0

@ -329,8 +329,7 @@ export class GameCore {
return; return;
} }
// Update buffers as the very first this.root.signals.gameFrameStarted.dispatch();
root.buffers.update();
root.queue.requireRedraw = false; root.queue.requireRedraw = false;
@ -390,33 +389,24 @@ export class GameCore {
// Map overview // Map overview
root.map.drawOverlay(params); root.map.drawOverlay(params);
} else { } else {
// Background (grid, resources, etc)
root.map.drawBackground(params); root.map.drawBackground(params);
// Belt items // Belt items
systems.belt.drawBeltItems(params); systems.belt.drawBeltItems(params);
// Items being ejected / accepted currently (animations) // Miner & Static map entities etc.
systems.itemEjector.draw(params);
systems.itemAcceptor.draw(params);
// Miner & Static map entities
root.map.drawForeground(params); root.map.drawForeground(params);
// HUB Overlay // HUB Overlay
systems.hub.draw(params); systems.hub.draw(params);
// Storage items
systems.storage.draw(params);
// Green wires overlay // Green wires overlay
root.hud.parts.wiresOverlay.draw(params); root.hud.parts.wiresOverlay.draw(params);
if (this.root.currentLayer === "wires") { if (this.root.currentLayer === "wires") {
// Static map entities // Static map entities
root.map.drawWiresForegroundLayer(params); root.map.drawWiresForegroundLayer(params);
// pins
systems.wiredPins.draw(params);
} }
} }

@ -6,8 +6,7 @@ import { Entity } from "./entity";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { GameSystem } from "./game_system"; import { GameSystem } from "./game_system";
import { arrayDelete, arrayDeleteValue } from "../core/utils"; import { arrayDelete, arrayDeleteValue } from "../core/utils";
import { DrawParameters } from "../core/draw_parameters";
import { globalConfig } from "../core/config";
export class GameSystemWithFilter extends GameSystem { export class GameSystemWithFilter extends GameSystem {
/** /**
* Constructs a new game system with the given component filter. It will process * Constructs a new game system with the given component filter. It will process
@ -35,80 +34,6 @@ export class GameSystemWithFilter extends GameSystem {
this.root.signals.bulkOperationFinished.add(this.refreshCaches, this); this.root.signals.bulkOperationFinished.add(this.refreshCaches, this);
} }
/**
* Calls a function for each matching entity on the screen, useful for drawing them
* @param {DrawParameters} parameters
* @param {function} callback
*/
forEachMatchingEntityOnScreen(parameters, callback) {
const cullRange = parameters.visibleRect.toTileCullRectangle();
if (this.allEntities.length < 100) {
// So, its much quicker to simply perform per-entity checking
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
if (cullRange.containsRect(entity.components.StaticMapEntity.getTileSpaceBounds())) {
callback(parameters, entity);
}
}
return;
}
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 map = this.root.map;
let seenUids = new Set();
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);
const requiredComponents = this.requiredComponentIds;
// Render y from top down for proper blending
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
const chunk = map.getChunk(chunkX, chunkY, false);
if (!chunk) {
continue;
}
// BIG TODO: CULLING ON AN ENTITY BASIS
const entities = chunk.containedEntities;
entityLoop: for (let i = 0; i < entities.length; ++i) {
const entity = entities[i];
// Avoid drawing twice
if (seenUids.has(entity.uid)) {
continue;
}
seenUids.add(entity.uid);
for (let i = 0; i < requiredComponents.length; ++i) {
if (!entity.components[requiredComponents[i]]) {
continue entityLoop;
}
}
callback(parameters, entity);
}
}
}
}
/** /**
* @param {Entity} entity * @param {Entity} entity
*/ */

@ -374,7 +374,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
// HACK to draw the entity sprite // HACK to draw the entity sprite
const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get()); const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get());
staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5); staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite); staticComp.drawSpriteOnBoundsClipped(parameters, previewSprite);
staticComp.origin = mouseTile; staticComp.origin = mouseTile;
// Draw ejectors // Draw ejectors

@ -64,11 +64,16 @@ export class HUDWireInfo extends BaseHUDPart {
const network = networks[0]; const network = networks[0];
if (network.valueConflict) { if (network.valueConflict) {
this.spriteConflict.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40); this.spriteConflict.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
} else if (!network.currentValue) { } else if (!network.currentValue) {
this.spriteEmpty.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40); this.spriteEmpty.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
} else { } else {
network.currentValue.drawCentered(mousePos.x + 20, mousePos.y, parameters, 40); network.currentValue.drawItemCenteredClipped(
mousePos.x + 40,
mousePos.y + 10,
parameters,
60
);
} }
} }
} }

@ -2,6 +2,7 @@ import { DrawParameters } from "../../core/draw_parameters";
import { Loader } from "../../core/loader"; import { Loader } from "../../core/loader";
import { types } from "../../savegame/serialization"; import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { globalConfig } from "../../core/config";
export class BooleanItem extends BaseItem { export class BooleanItem extends BaseItem {
static getId() { static getId() {
@ -46,7 +47,7 @@ export class BooleanItem extends BaseItem {
* @param {number} diameter * @param {number} diameter
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
*/ */
drawCentered(x, y, parameters, diameter = 12) { drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
let sprite; let sprite;
if (this.value) { if (this.value) {
sprite = Loader.getSprite("sprites/wires/boolean_true.png"); sprite = Loader.getSprite("sprites/wires/boolean_true.png");

@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { enumColors, enumColorsToHexCode } from "../colors"; import { enumColors, enumColorsToHexCode } from "../colors";
import { THEME } from "../theme"; import { THEME } from "../theme";
import { drawSpriteClipped } from "../../core/draw_utils";
export class ColorItem extends BaseItem { export class ColorItem extends BaseItem {
static getId() { static getId() {
@ -54,23 +55,33 @@ export class ColorItem extends BaseItem {
* @param {number} diameter * @param {number} diameter
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
*/ */
drawCentered(x, y, parameters, diameter = 12) { drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
if (!this.bufferGenerator) { if (!this.bufferGenerator) {
this.bufferGenerator = this.internalGenerateColorBuffer.bind(this); this.bufferGenerator = this.internalGenerateColorBuffer.bind(this);
} }
const realDiameter = diameter * 0.6;
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel); const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
const key = realDiameter + "/" + dpi;
const key = diameter + "/" + dpi;
const canvas = parameters.root.buffers.getForKey({ const canvas = parameters.root.buffers.getForKey({
key, key,
subKey: this.color, subKey: this.color,
w: diameter, w: realDiameter,
h: diameter, h: realDiameter,
dpi, dpi,
redrawMethod: this.bufferGenerator, redrawMethod: this.bufferGenerator,
}); });
parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter);
drawSpriteClipped({
parameters,
sprite: canvas,
x: x - realDiameter / 2,
y: y - realDiameter / 2,
w: realDiameter,
h: realDiameter,
originalW: realDiameter * dpi,
originalH: realDiameter * dpi,
});
} }
/** /**
* *

@ -3,6 +3,7 @@ import { types } from "../../savegame/serialization";
import { BaseItem } from "../base_item"; import { BaseItem } from "../base_item";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { THEME } from "../theme"; import { THEME } from "../theme";
import { globalConfig } from "../../core/config";
export class ShapeItem extends BaseItem { export class ShapeItem extends BaseItem {
static getId() { static getId() {
@ -55,7 +56,7 @@ export class ShapeItem extends BaseItem {
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {number=} diameter * @param {number=} diameter
*/ */
drawCentered(x, y, parameters, diameter) { drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
this.definition.drawCentered(x, y, parameters, diameter); this.definition.drawCentered(x, y, parameters, diameter);
} }
} }

@ -52,10 +52,16 @@ export class MapChunkView extends MapChunk {
*/ */
drawForegroundLayer(parameters) { drawForegroundLayer(parameters) {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
systems.itemEjector.drawChunk(parameters, this);
systems.itemAcceptor.drawChunk(parameters, this);
systems.miner.drawChunk(parameters, this); systems.miner.drawChunk(parameters, this);
systems.staticMapEntities.drawChunk(parameters, this); systems.staticMapEntities.drawChunk(parameters, this);
systems.lever.drawChunk(parameters, this); systems.lever.drawChunk(parameters, this);
systems.display.drawChunk(parameters, this); systems.display.drawChunk(parameters, this);
systems.storage.drawChunk(parameters, this);
} }
/** /**
@ -97,11 +103,9 @@ export class MapChunkView extends MapChunk {
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize; const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize; const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
const destSize = Math.min(80, 30 / parameters.zoomLevel); const diameter = Math.min(80, 30 / parameters.zoomLevel);
if (parameters.visibleRect.containsCircle(destX, destY, destSize)) { patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
patch.item.drawCentered(destX, destY, parameters, destSize);
}
} }
} }
} }
@ -265,5 +269,6 @@ export class MapChunkView extends MapChunk {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
systems.wire.drawChunk(parameters, this); systems.wire.drawChunk(parameters, this);
systems.staticMapEntities.drawWiresChunk(parameters, this); systems.staticMapEntities.drawWiresChunk(parameters, this);
systems.wiredPins.drawChunk(parameters, this);
} }
} }

@ -149,6 +149,8 @@ export class GameRoot {
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()), storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()), upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),

@ -2,7 +2,6 @@ import { makeOffscreenBuffer } from "../core/buffer_utils";
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { smoothenDpi } from "../core/dpi_manager"; import { smoothenDpi } from "../core/dpi_manager";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { createLogger } from "../core/logging";
import { Vector } from "../core/vector"; import { Vector } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization"; import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors"; import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors";

@ -501,7 +501,9 @@ export class BeltSystem extends GameSystemWithFilter {
if (entity.components.Belt) { if (entity.components.Belt) {
const direction = entity.components.Belt.direction; const direction = entity.components.Belt.direction;
const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT]; const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT];
entity.components.StaticMapEntity.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
// Culling happens within the static map entity component
entity.components.StaticMapEntity.drawSpriteOnBoundsClipped(parameters, sprite, 0);
} }
} }
} }

@ -39,10 +39,26 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
const { pos, direction } = underlays[i]; const { pos, direction } = underlays[i];
const transformedPos = staticComp.localTileToWorld(pos); const transformedPos = staticComp.localTileToWorld(pos);
// Culling
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) { if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
continue; continue;
} }
const destX = transformedPos.x * globalConfig.tileSize;
const destY = transformedPos.y * globalConfig.tileSize;
// Culling, #2
if (
parameters.visibleRect.containsRect4Params(
destX,
destY,
globalConfig.tileSize,
globalConfig.tileSize
)
) {
continue;
}
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)]; const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
// SYNC with systems/belt.js:drawSingleEntity! // SYNC with systems/belt.js:drawSingleEntity!
@ -54,8 +70,8 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
drawRotatedSprite({ drawRotatedSprite({
parameters, parameters,
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length], sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
x: (transformedPos.x + 0.5) * globalConfig.tileSize, x: destX + globalConfig.halfTileSize,
y: (transformedPos.y + 0.5) * globalConfig.tileSize, y: destY + globalConfig.halfTileSize,
angle: Math.radians(angle), angle: Math.radians(angle),
size: globalConfig.tileSize, size: globalConfig.tileSize,
}); });

@ -66,9 +66,11 @@ export class DisplaySystem extends GameSystemWithFilter {
if (entity && entity.components.Display) { if (entity && entity.components.Display) {
const pinsComp = entity.components.WiredPins; const pinsComp = entity.components.WiredPins;
const network = pinsComp.slots[0].linkedNetwork; const network = pinsComp.slots[0].linkedNetwork;
if (!network || !network.currentValue) { if (!network || !network.currentValue) {
continue; continue;
} }
const value = this.getDisplayItem(network.currentValue); const value = this.getDisplayItem(network.currentValue);
if (!value) { if (!value) {
@ -84,7 +86,7 @@ export class DisplaySystem extends GameSystemWithFilter {
globalConfig.tileSize globalConfig.tileSize
); );
} else if (value.getItemType() === "shape") { } else if (value.getItemType() === "shape") {
value.drawCentered( value.drawItemCenteredClipped(
(origin.x + 0.5) * globalConfig.tileSize, (origin.x + 0.5) * globalConfig.tileSize,
(origin.y + 0.5) * globalConfig.tileSize, (origin.y + 0.5) * globalConfig.tileSize,
parameters, parameters,

@ -5,6 +5,14 @@ import { T } from "../../translations";
import { HubComponent } from "../components/hub"; import { HubComponent } from "../components/hub";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { globalConfig } from "../../core/config";
import { smoothenDpi } from "../../core/dpi_manager";
import { drawSpriteClipped } from "../../core/draw_utils";
import { Rectangle } from "../../core/rectangle";
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
const HUB_SIZE_TILES = 4;
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
export class HubSystem extends GameSystemWithFilter { export class HubSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
@ -13,8 +21,13 @@ export class HubSystem extends GameSystemWithFilter {
this.hubSprite = Loader.getSprite("sprites/buildings/hub.png"); this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
} }
/**
* @param {DrawParameters} parameters
*/
draw(parameters) { draw(parameters) {
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this)); for (let i = 0; i < this.allEntities.length; ++i) {
this.drawEntity(parameters, this.allEntities[i]);
}
} }
update() { update() {
@ -27,35 +40,42 @@ export class HubSystem extends GameSystemWithFilter {
); );
} }
} }
/** /**
* @param {DrawParameters} parameters *
* @param {Entity} entity * @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context
* @param {number} w
* @param {number} h
* @param {number} dpi
*/ */
drawEntity(parameters, entity) { redrawHubBaseTexture(canvas, context, w, h, dpi) {
const context = parameters.context; // This method is quite ugly, please ignore it!
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) { context.scale(dpi, dpi);
return;
}
const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); const parameters = new DrawParameters({
context,
visibleRect: new Rectangle(0, 0, w, h),
desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
zoomLevel: dpi * 0.75,
root: this.root,
});
// Background context.clearRect(0, 0, w, h);
staticComp.drawSpriteOnFullEntityBounds(parameters, this.hubSprite, 2.2);
const definition = this.root.hubGoals.currentGoal.definition; this.hubSprite.draw(context, 0, 0, w, h);
definition.drawCentered(pos.x - 25, pos.y - 10, parameters, 40); const definition = this.root.hubGoals.currentGoal.definition;
definition.drawCentered(45, 58, parameters, 36);
const goals = this.root.hubGoals.currentGoal; const goals = this.root.hubGoals.currentGoal;
const textOffsetX = 2; const textOffsetX = 70;
const textOffsetY = -6; const textOffsetY = 61;
// Deliver count // Deliver count
const delivered = this.root.hubGoals.getCurrentGoalDelivered(); const delivered = this.root.hubGoals.getCurrentGoalDelivered();
const deliveredText = "" + formatBigNumber(delivered);
if (delivered > 9999) { if (delivered > 9999) {
context.font = "bold 16px GameFont"; context.font = "bold 16px GameFont";
@ -66,52 +86,87 @@ export class HubSystem extends GameSystemWithFilter {
} }
context.fillStyle = "#64666e"; context.fillStyle = "#64666e";
context.textAlign = "left"; context.textAlign = "left";
context.fillText("" + formatBigNumber(delivered), pos.x + textOffsetX, pos.y + textOffsetY); context.fillText(deliveredText, textOffsetX, textOffsetY);
// Required // Required
context.font = "13px GameFont"; context.font = "13px GameFont";
context.fillStyle = "#a4a6b0"; context.fillStyle = "#a4a6b0";
context.fillText( context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
"/ " + formatBigNumber(goals.required),
pos.x + textOffsetX,
pos.y + textOffsetY + 13
);
// Reward // Reward
const rewardText = T.storyRewards[goals.reward].title.toUpperCase(); const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
if (rewardText.length > 12) { if (rewardText.length > 12) {
context.font = "bold 9px GameFont"; context.font = "bold 8px GameFont";
} else { } else {
context.font = "bold 11px GameFont"; context.font = "bold 10px GameFont";
} }
context.fillStyle = "#fd0752"; context.fillStyle = "#fd0752";
context.textAlign = "center"; context.textAlign = "center";
context.fillText(rewardText, pos.x, pos.y + 46); context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
// Level // Level "8"
context.font = "bold 11px GameFont"; context.font = "bold 10px GameFont";
context.fillStyle = "#fff"; context.fillStyle = "#fff";
context.fillText("" + this.root.hubGoals.level, pos.x - 42, pos.y - 36); context.fillText("" + this.root.hubGoals.level, 27, 32);
// Texts // "LVL"
context.textAlign = "center"; context.textAlign = "center";
context.fillStyle = "#fff"; context.fillStyle = "#fff";
context.font = "bold 7px GameFont"; context.font = "bold 6px GameFont";
context.fillText(T.buildings.hub.levelShortcut, pos.x - 42, pos.y - 47); context.fillText(T.buildings.hub.levelShortcut, 27, 22);
// "Deliver"
context.fillStyle = "#64666e"; context.fillStyle = "#64666e";
context.font = "bold 11px GameFont"; context.font = "bold 10px GameFont";
context.fillText(T.buildings.hub.deliver.toUpperCase(), pos.x, pos.y - 40); context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
// "To unlock"
const unlockText = T.buildings.hub.toUnlock.toUpperCase(); const unlockText = T.buildings.hub.toUnlock.toUpperCase();
if (unlockText.length > 15) { if (unlockText.length > 15) {
context.font = "bold 8px GameFont"; context.font = "bold 8px GameFont";
} else { } else {
context.font = "bold 11px GameFont"; context.font = "bold 10px GameFont";
} }
context.fillText(T.buildings.hub.toUnlock.toUpperCase(), pos.x, pos.y + 30); context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
context.textAlign = "left"; context.textAlign = "left";
} }
/**
* @param {DrawParameters} parameters
* @param {Entity} entity
*/
drawEntity(parameters, entity) {
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) {
return;
}
// Deliver count
const delivered = this.root.hubGoals.getCurrentGoalDelivered();
const deliveredText = "" + formatBigNumber(delivered);
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
const canvas = parameters.root.buffers.getForKey({
key: "hub",
subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText,
w: globalConfig.tileSize * 4,
h: globalConfig.tileSize * 4,
dpi,
redrawMethod: this.redrawHubBaseTexture.bind(this),
});
const extrude = 8;
drawSpriteClipped({
parameters,
sprite: canvas,
x: staticComp.origin.x * globalConfig.tileSize - extrude,
y: staticComp.origin.y * globalConfig.tileSize - extrude,
w: HUB_SIZE_PIXELS + 2 * extrude,
h: HUB_SIZE_PIXELS + 2 * extrude,
originalW: HUB_SIZE_PIXELS * dpi,
originalH: HUB_SIZE_PIXELS * dpi,
});
}
} }

@ -3,8 +3,8 @@ import { DrawParameters } from "../../core/draw_parameters";
import { fastArrayDelete } from "../../core/utils"; import { fastArrayDelete } from "../../core/utils";
import { enumDirectionToVector } from "../../core/vector"; import { enumDirectionToVector } from "../../core/vector";
import { ItemAcceptorComponent } from "../components/item_acceptor"; import { ItemAcceptorComponent } from "../components/item_acceptor";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunkView } from "../map_chunk_view";
export class ItemAcceptorSystem extends GameSystemWithFilter { export class ItemAcceptorSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
@ -38,43 +38,45 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
} }
/** /**
* Draws the acceptor items
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {MapChunkView} chunk
*/ */
draw(parameters) { drawChunk(parameters, chunk) {
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityRegularLayer.bind(this)); const contents = chunk.containedEntitiesByLayer.regular;
} for (let i = 0; i < contents.length; ++i) {
const entity = contents[i];
const acceptorComp = entity.components.ItemAcceptor;
if (!acceptorComp) {
continue;
}
/** const staticComp = entity.components.StaticMapEntity;
* @param {DrawParameters} parameters for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
* @param {Entity} entity const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
*/ animIndex
drawEntityRegularLayer(parameters, entity) { ];
const staticComp = entity.components.StaticMapEntity;
const acceptorComp = entity.components.ItemAcceptor;
if (!staticComp.shouldBeDrawn(parameters)) { const slotData = acceptorComp.slots[slotIndex];
return; const realSlotPos = staticComp.localTileToWorld(slotData.pos);
}
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) { if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[ // Not within this chunk
animIndex continue;
]; }
const slotData = acceptorComp.slots[slotIndex]; const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
const finalTile = realSlotPos.subScalars(
fadeOutDirection.x * (animProgress / 2 - 0.5),
fadeOutDirection.y * (animProgress / 2 - 0.5)
);
const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin); item.drawItemCenteredClipped(
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)]; (finalTile.x + 0.5) * globalConfig.tileSize,
const finalTile = slotWorldPos.subScalars( (finalTile.y + 0.5) * globalConfig.tileSize,
fadeOutDirection.x * (animProgress / 2 - 0.5), parameters,
fadeOutDirection.y * (animProgress / 2 - 0.5) globalConfig.defaultItemDiameter
); );
item.drawCentered( }
(finalTile.x + 0.5) * globalConfig.tileSize,
(finalTile.y + 0.5) * globalConfig.tileSize,
parameters
);
} }
} }
} }

@ -8,6 +8,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { enumItemProcessorTypes } from "../components/item_processor"; import { enumItemProcessorTypes } from "../components/item_processor";
import { MapChunkView } from "../map_chunk_view";
const logger = createLogger("systems/ejector"); const logger = createLogger("systems/ejector");
@ -336,50 +337,52 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
} }
/** /**
* Draws everything
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {MapChunkView} chunk
*/ */
draw(parameters) { drawChunk(parameters, chunk) {
this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this)); const contents = chunk.containedEntitiesByLayer.regular;
}
/** for (let i = 0; i < contents.length; ++i) {
* @param {DrawParameters} parameters const entity = contents[i];
* @param {Entity} entity const ejectorComp = entity.components.ItemEjector;
*/ if (!ejectorComp) {
drawSingleEntity(parameters, entity) { continue;
const ejectorComp = entity.components.ItemEjector; }
const staticComp = entity.components.StaticMapEntity;
if (!staticComp.shouldBeDrawn(parameters)) { const staticComp = entity.components.StaticMapEntity;
return;
}
for (let i = 0; i < ejectorComp.slots.length; ++i) { for (let i = 0; i < ejectorComp.slots.length; ++i) {
const slot = ejectorComp.slots[i]; const slot = ejectorComp.slots[i];
const ejectedItem = slot.item; const ejectedItem = slot.item;
if (!ejectedItem) { if (!ejectedItem) {
// No item // No item
continue; continue;
} }
const realPosition = slot.pos.rotateFastMultipleOf90(staticComp.rotation); const realPosition = staticComp.localTileToWorld(slot.pos);
const realDirection = Vector.transformDirectionFromMultipleOf90( if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
slot.direction, // Not within this chunk
staticComp.rotation continue;
); }
const realDirectionVector = enumDirectionToVector[realDirection];
const realDirection = staticComp.localDirectionToWorld(slot.direction);
const realDirectionVector = enumDirectionToVector[realDirection];
const tileX = const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
staticComp.origin.x + realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress; const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
const tileY =
staticComp.origin.y + realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
const worldX = tileX * globalConfig.tileSize; const worldX = tileX * globalConfig.tileSize;
const worldY = tileY * globalConfig.tileSize; const worldY = tileY * globalConfig.tileSize;
ejectedItem.drawCentered(worldX, worldY, parameters); ejectedItem.drawItemCenteredClipped(
worldX,
worldY,
parameters,
globalConfig.defaultItemDiameter
);
}
} }
} }
} }

@ -34,8 +34,9 @@ export class LeverSystem extends GameSystemWithFilter {
const contents = chunk.containedEntitiesByLayer.regular; const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) { for (let i = 0; i < contents.length; ++i) {
const entity = contents[i]; const entity = contents[i];
if (entity && entity.components.Lever) { const leverComp = entity.components.Lever;
const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff; if (leverComp) {
const sprite = leverComp.toggled ? this.spriteOn : this.spriteOff;
const origin = entity.components.StaticMapEntity.origin; const origin = entity.components.StaticMapEntity.origin;
sprite.drawCached( sprite.drawCached(
parameters, parameters,

@ -42,10 +42,9 @@ export class MapResourcesSystem extends GameSystem {
const patch = chunk.patches[i]; const patch = chunk.patches[i];
const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize; const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize;
const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize; const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize;
const destSize = Math.min(80, 40 / parameters.zoomLevel); const diameter = Math.min(80, 40 / parameters.zoomLevel);
if (parameters.visibleRect.containsCircle(destX, destY, destSize / 2)) {
patch.item.drawCentered(destX, destY, parameters, destSize); patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
}
} }
} else { } else {
// HIGH QUALITY: Draw all items // HIGH QUALITY: Draw all items
@ -61,9 +60,12 @@ export class MapResourcesSystem extends GameSystem {
const destX = worldX + globalConfig.halfTileSize; const destX = worldX + globalConfig.halfTileSize;
const destY = worldY + globalConfig.halfTileSize; const destY = worldY + globalConfig.halfTileSize;
if (parameters.visibleRect.containsCircle(destX, destY, globalConfig.tileSize / 2)) { lowerItem.drawItemCenteredClipped(
lowerItem.drawCentered(destX, destY, parameters); destX,
} destY,
parameters,
globalConfig.defaultItemDiameter
);
} }
} }
} }

@ -102,41 +102,39 @@ export class MinerSystem extends GameSystemWithFilter {
* @param {MapChunkView} chunk * @param {MapChunkView} chunk
*/ */
drawChunk(parameters, chunk) { drawChunk(parameters, chunk) {
const contents = chunk.contents; const contents = chunk.containedEntitiesByLayer.regular;
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.Miner) {
const staticComp = entity.components.StaticMapEntity;
const minerComp = entity.components.Miner;
if (!staticComp.shouldBeDrawn(parameters)) {
continue;
}
if (!minerComp.cachedMinedItem) {
continue;
}
if (minerComp.cachedMinedItem) { for (let i = 0; i < contents.length; ++i) {
const padding = 3; const entity = contents[i];
parameters.context.fillStyle = minerComp.cachedMinedItem.getBackgroundColorAsResource(); const minerComp = entity.components.Miner;
parameters.context.fillRect( if (!minerComp) {
staticComp.origin.x * globalConfig.tileSize + padding, continue;
staticComp.origin.y * globalConfig.tileSize + padding, }
globalConfig.tileSize - 2 * padding,
globalConfig.tileSize - 2 * padding
);
}
if (minerComp.cachedMinedItem) { const staticComp = entity.components.StaticMapEntity;
minerComp.cachedMinedItem.drawCentered( if (!minerComp.cachedMinedItem) {
(0.5 + staticComp.origin.x) * globalConfig.tileSize, continue;
(0.5 + staticComp.origin.y) * globalConfig.tileSize, }
parameters
); // Draw the item background - this is to hide the ejected item animation from
} // the item ejecto
}
const padding = 3;
const destX = staticComp.origin.x * globalConfig.tileSize + padding;
const destY = staticComp.origin.y * globalConfig.tileSize + padding;
const dimensions = globalConfig.tileSize - 2 * padding;
if (parameters.visibleRect.containsRect4Params(destX, destY, dimensions, dimensions)) {
parameters.context.fillStyle = minerComp.cachedMinedItem.getBackgroundColorAsResource();
parameters.context.fillRect(destX, destY, dimensions, dimensions);
} }
minerComp.cachedMinedItem.drawItemCenteredClipped(
(0.5 + staticComp.origin.x) * globalConfig.tileSize,
(0.5 + staticComp.origin.y) * globalConfig.tileSize,
parameters,
globalConfig.defaultItemDiameter
);
} }
} }
} }

@ -6,6 +6,18 @@ import { MapChunkView } from "../map_chunk_view";
export class StaticMapEntitySystem extends GameSystem { export class StaticMapEntitySystem extends GameSystem {
constructor(root) { constructor(root) {
super(root); super(root);
/** @type {Set<number>} */
this.drawnUids = new Set();
this.root.signals.gameFrameStarted.add(this.clearUidList, this);
}
/**
* Clears the uid list when a new frame started
*/
clearUidList() {
this.drawnUids.clear();
} }
/** /**
@ -18,25 +30,21 @@ export class StaticMapEntitySystem extends GameSystem {
return; return;
} }
const drawnUids = new Set(); const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) {
const contents = chunk.contents; const entity = contents[i];
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const entity = contents[x][y];
if (entity) {
if (drawnUids.has(entity.uid)) {
continue;
}
drawnUids.add(entity.uid);
const staticComp = entity.components.StaticMapEntity;
const sprite = staticComp.getSprite(); const staticComp = entity.components.StaticMapEntity;
if (sprite) { const sprite = staticComp.getSprite();
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2); if (sprite) {
} // Avoid drawing an entity twice which has been drawn for
// another chunk already
if (this.drawnUids.has(entity.uid)) {
continue;
} }
this.drawnUids.add(entity.uid);
staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
} }
} }
} }
@ -65,7 +73,7 @@ export class StaticMapEntitySystem extends GameSystem {
const sprite = staticComp.getSprite(); const sprite = staticComp.getSprite();
if (sprite) { if (sprite) {
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2); staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
} }
} }
} }

@ -1,16 +1,28 @@
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { StorageComponent } from "../components/storage"; import { StorageComponent } from "../components/storage";
import { Entity } from "../entity";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { formatBigNumber, lerp } from "../../core/utils"; import { formatBigNumber, lerp } from "../../core/utils";
import { Loader } from "../../core/loader"; import { Loader } from "../../core/loader";
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item"; import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
import { MapChunkView } from "../map_chunk_view";
export class StorageSystem extends GameSystemWithFilter { export class StorageSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
super(root, [StorageComponent]); super(root, [StorageComponent]);
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png"); this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
/**
* Stores which uids were already drawn to avoid drawing entities twice
* @type {Set<number>}
*/
this.drawnUids = new Set();
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
}
clearDrawnUids() {
this.drawnUids.clear();
} }
update() { update() {
@ -43,38 +55,46 @@ export class StorageSystem extends GameSystemWithFilter {
} }
} }
draw(parameters) {
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
}
/** /**
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {Entity} entity * @param {MapChunkView} chunk
*/ */
drawEntity(parameters, entity) { drawChunk(parameters, chunk) {
const context = parameters.context; const contents = chunk.containedEntitiesByLayer.regular;
const staticComp = entity.components.StaticMapEntity; for (let i = 0; i < contents.length; ++i) {
const entity = contents[i];
const storageComp = entity.components.Storage;
if (!storageComp) {
continue;
}
if (!staticComp.shouldBeDrawn(parameters)) { const storedItem = storageComp.storedItem;
return; if (!storedItem) {
} continue;
}
if (this.drawnUids.has(entity.uid)) {
continue;
}
const storageComp = entity.components.Storage; this.drawnUids.add(entity.uid);
const storedItem = storageComp.storedItem; const staticComp = entity.components.StaticMapEntity;
if (storedItem !== null) {
const context = parameters.context;
context.globalAlpha = storageComp.overlayOpacity; context.globalAlpha = storageComp.overlayOpacity;
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
storedItem.drawCentered(center.x, center.y, parameters, 30); storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15); this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
context.font = "bold 10px GameFont"; if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
context.textAlign = "center"; context.font = "bold 10px GameFont";
context.fillStyle = "#64666e"; context.textAlign = "center";
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5); context.fillStyle = "#64666e";
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
context.textAlign = "left"; context.textAlign = "left";
}
context.globalAlpha = 1; context.globalAlpha = 1;
} }
} }

@ -624,7 +624,7 @@ export class WireSystem extends GameSystemWithFilter {
assert(sprite, "Unknown wire type: " + wireType); assert(sprite, "Unknown wire type: " + wireType);
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
parameters.context.globalAlpha = opacity; parameters.context.globalAlpha = opacity;
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0); staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0);
parameters.context.globalAlpha = 1; parameters.context.globalAlpha = 1;
if (G_IS_DEV && globalConfig.debug.renderWireRotations) { if (G_IS_DEV && globalConfig.debug.renderWireRotations) {

@ -1,13 +1,13 @@
import { globalConfig } from "../../core/config"; import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { drawRotatedSprite } from "../../core/draw_utils";
import { Loader } from "../../core/loader"; import { Loader } from "../../core/loader";
import { Vector, enumDirectionToAngle } from "../../core/vector"; import { STOP_PROPAGATION } from "../../core/signal";
import { enumDirectionToAngle, Vector } from "../../core/vector";
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { STOP_PROPAGATION } from "../../core/signal"; import { MapChunkView } from "../map_chunk_view";
import { drawRotatedSprite } from "../../core/draw_utils";
import { GLOBAL_APP } from "../../core/globals";
export class WiredPinsSystem extends GameSystemWithFilter { export class WiredPinsSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
@ -146,65 +146,84 @@ export class WiredPinsSystem extends GameSystemWithFilter {
// TODO // TODO
} }
/**
* Draws the pins
* @param {DrawParameters} parameters
*/
draw(parameters) {
this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
}
/** /**
* Draws a given entity * Draws a given entity
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
* @param {Entity} entity * @param {MapChunkView} chunk
*/ */
drawSingleEntity(parameters, entity) { drawChunk(parameters, chunk) {
const staticComp = entity.components.StaticMapEntity; const contents = chunk.containedEntities;
const slots = entity.components.WiredPins.slots;
for (let i = 0; i < contents.length; ++i) {
for (let i = 0; i < slots.length; ++i) { const entity = contents[i];
const slot = slots[i]; const pinsComp = entity.components.WiredPins;
const tile = staticComp.localTileToWorld(slot.pos); if (!pinsComp) {
continue;
const worldPos = tile.toWorldSpaceCenterOfTile();
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 staticComp = entity.components.StaticMapEntity;
const value = slot.value; const slots = pinsComp.slots;
if (value) {
const offset = new Vector(0, -9).rotated(effectiveRotation);
value.drawCentered(worldPos.x + offset.x, worldPos.y + offset.y, parameters, 9);
}
// Debug view for (let j = 0; j < slots.length; ++j) {
if (G_IS_DEV && globalConfig.debug.renderWireNetworkInfos) { const slot = slots[j];
const offset = new Vector(0, -10).rotated(effectiveRotation); const tile = staticComp.localTileToWorld(slot.pos);
const network = slot.linkedNetwork;
parameters.context.fillStyle = "blue"; if (!chunk.tileSpaceRectangle.containsPoint(tile.x, tile.y)) {
parameters.context.font = "5px Tahoma"; // Doesn't belong to this chunk
parameters.context.textAlign = "center"; continue;
parameters.context.fillText( }
network ? "S" + network.uid : "???", const worldPos = tile.toWorldSpaceCenterOfTile();
(tile.x + 0.5) * globalConfig.tileSize + offset.x,
(tile.y + 0.5) * globalConfig.tileSize + offset.y // Culling
if (
!parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, globalConfig.halfTileSize)
) {
continue;
}
const effectiveRotation = Math.radians(
staticComp.rotation + enumDirectionToAngle[slot.direction]
); );
parameters.context.textAlign = "left";
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).rotated(effectiveRotation);
value.drawItemCenteredClipped(
worldPos.x + offset.x,
worldPos.y + offset.y,
parameters,
9
);
}
// 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";
}
} }
} }
} }

Loading…
Cancel
Save