mirror of
				https://github.com/tobspr/shapez.io.git
				synced 2025-06-13 13:04:03 +00:00 
			
		
		
		
	Huge rendering performance improvements and minor other changes, lots of refactorings
This commit is contained in:
		
							parent
							
								
									d1a5dd8c9e
								
							
						
					
					
						commit
						b2880700e8
					
				@ -27,6 +27,8 @@ export class BufferMaintainer {
 | 
			
		||||
 | 
			
		||||
        this.iterationIndex = 1;
 | 
			
		||||
        this.lastIteration = 0;
 | 
			
		||||
 | 
			
		||||
        this.root.signals.gameFrameStarted.add(this.update, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,8 @@ export const globalConfig = {
 | 
			
		||||
    beltSpeedItemsPerSecond: 2,
 | 
			
		||||
    minerSpeedItemsPerSecond: 0, // COMPUTED
 | 
			
		||||
 | 
			
		||||
    defaultItemDiameter: 20,
 | 
			
		||||
 | 
			
		||||
    itemSpacingOnBelts: 0.63,
 | 
			
		||||
 | 
			
		||||
    wiresSpeedItemsPerSecond: 6,
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { globalConfig } from "../core/config";
 | 
			
		||||
import { DrawParameters } from "../core/draw_parameters";
 | 
			
		||||
import { BasicSerializableObject } from "../savegame/serialization";
 | 
			
		||||
 | 
			
		||||
@ -57,7 +58,22 @@ export class BaseItem extends BasicSerializableObject {
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @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() {
 | 
			
		||||
        abstract;
 | 
			
		||||
 | 
			
		||||
@ -1194,9 +1194,13 @@ export class BeltPath extends BasicSerializableObject {
 | 
			
		||||
                const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
 | 
			
		||||
 | 
			
		||||
                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
 | 
			
		||||
                currentItemPos += distanceAndItem[_nextDistance];
 | 
			
		||||
 | 
			
		||||
@ -92,7 +92,7 @@ export class Blueprint {
 | 
			
		||||
                parameters.context.globalAlpha = 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            staticComp.drawSpriteOnFullEntityBounds(parameters, staticComp.getBlueprintSprite(), 0, newPos);
 | 
			
		||||
            staticComp.drawSpriteOnBoundsClipped(parameters, staticComp.getBlueprintSprite(), 0, newPos);
 | 
			
		||||
        }
 | 
			
		||||
        parameters.context.globalAlpha = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -162,8 +162,9 @@ export class StaticMapEntityComponent extends Component {
 | 
			
		||||
     * @returns {Vector}
 | 
			
		||||
     */
 | 
			
		||||
    localTileToWorld(localTile) {
 | 
			
		||||
        const result = this.applyRotationToVector(localTile);
 | 
			
		||||
        result.addInplace(this.origin);
 | 
			
		||||
        const result = localTile.rotateFastMultipleOf90(this.rotation);
 | 
			
		||||
        result.x += this.origin.x;
 | 
			
		||||
        result.y += this.origin.y;
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -235,7 +236,7 @@ export class StaticMapEntityComponent extends Component {
 | 
			
		||||
     * @param {number=} extrudePixels How many pixels to extrude the sprite
 | 
			
		||||
     * @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) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import { enumDirection, Vector } from "../../core/vector";
 | 
			
		||||
import { BaseItem } from "../base_item";
 | 
			
		||||
import { Component } from "../component";
 | 
			
		||||
import { types } from "../../savegame/serialization";
 | 
			
		||||
import { typeItemSingleton } from "../item_resolver";
 | 
			
		||||
 | 
			
		||||
/** @enum {string} */
 | 
			
		||||
export const enumPinSlotType = {
 | 
			
		||||
@ -27,6 +29,16 @@ export class WiredPinsComponent extends Component {
 | 
			
		||||
        return "WiredPins";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getSchema() {
 | 
			
		||||
        return {
 | 
			
		||||
            slots: types.array(
 | 
			
		||||
                types.structured({
 | 
			
		||||
                    value: types.nullable(typeItemSingleton),
 | 
			
		||||
                })
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param {object} param0
 | 
			
		||||
 | 
			
		||||
@ -329,8 +329,7 @@ export class GameCore {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update buffers as the very first
 | 
			
		||||
        root.buffers.update();
 | 
			
		||||
        this.root.signals.gameFrameStarted.dispatch();
 | 
			
		||||
 | 
			
		||||
        root.queue.requireRedraw = false;
 | 
			
		||||
 | 
			
		||||
@ -390,33 +389,24 @@ export class GameCore {
 | 
			
		||||
            // Map overview
 | 
			
		||||
            root.map.drawOverlay(params);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Background (grid, resources, etc)
 | 
			
		||||
            root.map.drawBackground(params);
 | 
			
		||||
 | 
			
		||||
            // Belt items
 | 
			
		||||
            systems.belt.drawBeltItems(params);
 | 
			
		||||
 | 
			
		||||
            // Items being ejected / accepted currently (animations)
 | 
			
		||||
            systems.itemEjector.draw(params);
 | 
			
		||||
            systems.itemAcceptor.draw(params);
 | 
			
		||||
 | 
			
		||||
            // Miner & Static map entities
 | 
			
		||||
            // Miner & Static map entities etc.
 | 
			
		||||
            root.map.drawForeground(params);
 | 
			
		||||
 | 
			
		||||
            // HUB Overlay
 | 
			
		||||
            systems.hub.draw(params);
 | 
			
		||||
 | 
			
		||||
            // Storage items
 | 
			
		||||
            systems.storage.draw(params);
 | 
			
		||||
 | 
			
		||||
            // Green wires overlay
 | 
			
		||||
            root.hud.parts.wiresOverlay.draw(params);
 | 
			
		||||
 | 
			
		||||
            if (this.root.currentLayer === "wires") {
 | 
			
		||||
                // Static map entities
 | 
			
		||||
                root.map.drawWiresForegroundLayer(params);
 | 
			
		||||
 | 
			
		||||
                // pins
 | 
			
		||||
                systems.wiredPins.draw(params);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,7 @@ import { Entity } from "./entity";
 | 
			
		||||
import { GameRoot } from "./root";
 | 
			
		||||
import { GameSystem } from "./game_system";
 | 
			
		||||
import { arrayDelete, arrayDeleteValue } from "../core/utils";
 | 
			
		||||
import { DrawParameters } from "../core/draw_parameters";
 | 
			
		||||
import { globalConfig } from "../core/config";
 | 
			
		||||
 | 
			
		||||
export class GameSystemWithFilter extends GameSystem {
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -374,7 +374,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
 | 
			
		||||
        // HACK to draw the entity sprite
 | 
			
		||||
        const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get());
 | 
			
		||||
        staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
 | 
			
		||||
        staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite);
 | 
			
		||||
        staticComp.drawSpriteOnBoundsClipped(parameters, previewSprite);
 | 
			
		||||
        staticComp.origin = mouseTile;
 | 
			
		||||
 | 
			
		||||
        // Draw ejectors
 | 
			
		||||
 | 
			
		||||
@ -64,11 +64,16 @@ export class HUDWireInfo extends BaseHUDPart {
 | 
			
		||||
            const network = networks[0];
 | 
			
		||||
 | 
			
		||||
            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) {
 | 
			
		||||
                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 {
 | 
			
		||||
                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 { types } from "../../savegame/serialization";
 | 
			
		||||
import { BaseItem } from "../base_item";
 | 
			
		||||
import { globalConfig } from "../../core/config";
 | 
			
		||||
 | 
			
		||||
export class BooleanItem extends BaseItem {
 | 
			
		||||
    static getId() {
 | 
			
		||||
@ -46,7 +47,7 @@ export class BooleanItem extends BaseItem {
 | 
			
		||||
     * @param {number} diameter
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     */
 | 
			
		||||
    drawCentered(x, y, parameters, diameter = 12) {
 | 
			
		||||
    drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
 | 
			
		||||
        let sprite;
 | 
			
		||||
        if (this.value) {
 | 
			
		||||
            sprite = Loader.getSprite("sprites/wires/boolean_true.png");
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
 | 
			
		||||
import { BaseItem } from "../base_item";
 | 
			
		||||
import { enumColors, enumColorsToHexCode } from "../colors";
 | 
			
		||||
import { THEME } from "../theme";
 | 
			
		||||
import { drawSpriteClipped } from "../../core/draw_utils";
 | 
			
		||||
 | 
			
		||||
export class ColorItem extends BaseItem {
 | 
			
		||||
    static getId() {
 | 
			
		||||
@ -54,23 +55,33 @@ export class ColorItem extends BaseItem {
 | 
			
		||||
     * @param {number} diameter
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     */
 | 
			
		||||
    drawCentered(x, y, parameters, diameter = 12) {
 | 
			
		||||
    drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
 | 
			
		||||
        if (!this.bufferGenerator) {
 | 
			
		||||
            this.bufferGenerator = this.internalGenerateColorBuffer.bind(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const realDiameter = diameter * 0.6;
 | 
			
		||||
        const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
 | 
			
		||||
 | 
			
		||||
        const key = diameter + "/" + dpi;
 | 
			
		||||
        const key = realDiameter + "/" + dpi;
 | 
			
		||||
        const canvas = parameters.root.buffers.getForKey({
 | 
			
		||||
            key,
 | 
			
		||||
            subKey: this.color,
 | 
			
		||||
            w: diameter,
 | 
			
		||||
            h: diameter,
 | 
			
		||||
            w: realDiameter,
 | 
			
		||||
            h: realDiameter,
 | 
			
		||||
            dpi,
 | 
			
		||||
            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 { ShapeDefinition } from "../shape_definition";
 | 
			
		||||
import { THEME } from "../theme";
 | 
			
		||||
import { globalConfig } from "../../core/config";
 | 
			
		||||
 | 
			
		||||
export class ShapeItem extends BaseItem {
 | 
			
		||||
    static getId() {
 | 
			
		||||
@ -55,7 +56,7 @@ export class ShapeItem extends BaseItem {
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {number=} diameter
 | 
			
		||||
     */
 | 
			
		||||
    drawCentered(x, y, parameters, diameter) {
 | 
			
		||||
    drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
 | 
			
		||||
        this.definition.drawCentered(x, y, parameters, diameter);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -52,10 +52,16 @@ export class MapChunkView extends MapChunk {
 | 
			
		||||
     */
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -97,11 +103,9 @@ export class MapChunkView extends MapChunk {
 | 
			
		||||
 | 
			
		||||
                const destX = this.x * dims + patch.pos.x * 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.drawCentered(destX, destY, parameters, destSize);
 | 
			
		||||
                }
 | 
			
		||||
                patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -265,5 +269,6 @@ export class MapChunkView extends MapChunk {
 | 
			
		||||
        const systems = this.root.systemMgr.systems;
 | 
			
		||||
        systems.wire.drawChunk(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
 | 
			
		||||
            gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
 | 
			
		||||
 | 
			
		||||
            gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
 | 
			
		||||
 | 
			
		||||
            storyGoalCompleted: /** @type {TypedSignal<[number, 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 { smoothenDpi } from "../core/dpi_manager";
 | 
			
		||||
import { DrawParameters } from "../core/draw_parameters";
 | 
			
		||||
import { createLogger } from "../core/logging";
 | 
			
		||||
import { Vector } from "../core/vector";
 | 
			
		||||
import { BasicSerializableObject, types } from "../savegame/serialization";
 | 
			
		||||
import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors";
 | 
			
		||||
 | 
			
		||||
@ -501,7 +501,9 @@ export class BeltSystem extends GameSystemWithFilter {
 | 
			
		||||
            if (entity.components.Belt) {
 | 
			
		||||
                const direction = entity.components.Belt.direction;
 | 
			
		||||
                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 transformedPos = staticComp.localTileToWorld(pos);
 | 
			
		||||
 | 
			
		||||
                    // Culling
 | 
			
		||||
                    if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
 | 
			
		||||
                        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)];
 | 
			
		||||
 | 
			
		||||
                    // SYNC with systems/belt.js:drawSingleEntity!
 | 
			
		||||
@ -54,8 +70,8 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
 | 
			
		||||
                    drawRotatedSprite({
 | 
			
		||||
                        parameters,
 | 
			
		||||
                        sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
 | 
			
		||||
                        x: (transformedPos.x + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                        y: (transformedPos.y + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                        x: destX + globalConfig.halfTileSize,
 | 
			
		||||
                        y: destY + globalConfig.halfTileSize,
 | 
			
		||||
                        angle: Math.radians(angle),
 | 
			
		||||
                        size: globalConfig.tileSize,
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
@ -66,9 +66,11 @@ export class DisplaySystem extends GameSystemWithFilter {
 | 
			
		||||
            if (entity && entity.components.Display) {
 | 
			
		||||
                const pinsComp = entity.components.WiredPins;
 | 
			
		||||
                const network = pinsComp.slots[0].linkedNetwork;
 | 
			
		||||
 | 
			
		||||
                if (!network || !network.currentValue) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const value = this.getDisplayItem(network.currentValue);
 | 
			
		||||
 | 
			
		||||
                if (!value) {
 | 
			
		||||
@ -84,7 +86,7 @@ export class DisplaySystem extends GameSystemWithFilter {
 | 
			
		||||
                        globalConfig.tileSize
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (value.getItemType() === "shape") {
 | 
			
		||||
                    value.drawCentered(
 | 
			
		||||
                    value.drawItemCenteredClipped(
 | 
			
		||||
                        (origin.x + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                        (origin.y + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                        parameters,
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,14 @@ import { T } from "../../translations";
 | 
			
		||||
import { HubComponent } from "../components/hub";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
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 {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
@ -13,8 +21,13 @@ export class HubSystem extends GameSystemWithFilter {
 | 
			
		||||
        this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} 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() {
 | 
			
		||||
@ -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) {
 | 
			
		||||
        const context = parameters.context;
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
    redrawHubBaseTexture(canvas, context, w, h, dpi) {
 | 
			
		||||
        // This method is quite ugly, please ignore it!
 | 
			
		||||
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        context.scale(dpi, dpi);
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        staticComp.drawSpriteOnFullEntityBounds(parameters, this.hubSprite, 2.2);
 | 
			
		||||
        context.clearRect(0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        this.hubSprite.draw(context, 0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        const definition = this.root.hubGoals.currentGoal.definition;
 | 
			
		||||
 | 
			
		||||
        definition.drawCentered(pos.x - 25, pos.y - 10, parameters, 40);
 | 
			
		||||
        definition.drawCentered(45, 58, parameters, 36);
 | 
			
		||||
 | 
			
		||||
        const goals = this.root.hubGoals.currentGoal;
 | 
			
		||||
 | 
			
		||||
        const textOffsetX = 2;
 | 
			
		||||
        const textOffsetY = -6;
 | 
			
		||||
        const textOffsetX = 70;
 | 
			
		||||
        const textOffsetY = 61;
 | 
			
		||||
 | 
			
		||||
        // Deliver count
 | 
			
		||||
        const delivered = this.root.hubGoals.getCurrentGoalDelivered();
 | 
			
		||||
        const deliveredText = "" + formatBigNumber(delivered);
 | 
			
		||||
 | 
			
		||||
        if (delivered > 9999) {
 | 
			
		||||
            context.font = "bold 16px GameFont";
 | 
			
		||||
@ -66,52 +86,87 @@ export class HubSystem extends GameSystemWithFilter {
 | 
			
		||||
        }
 | 
			
		||||
        context.fillStyle = "#64666e";
 | 
			
		||||
        context.textAlign = "left";
 | 
			
		||||
        context.fillText("" + formatBigNumber(delivered), pos.x + textOffsetX, pos.y + textOffsetY);
 | 
			
		||||
        context.fillText(deliveredText, textOffsetX, textOffsetY);
 | 
			
		||||
 | 
			
		||||
        // Required
 | 
			
		||||
        context.font = "13px GameFont";
 | 
			
		||||
        context.fillStyle = "#a4a6b0";
 | 
			
		||||
        context.fillText(
 | 
			
		||||
            "/ " + formatBigNumber(goals.required),
 | 
			
		||||
            pos.x + textOffsetX,
 | 
			
		||||
            pos.y + textOffsetY + 13
 | 
			
		||||
        );
 | 
			
		||||
        context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
 | 
			
		||||
 | 
			
		||||
        // Reward
 | 
			
		||||
        const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
 | 
			
		||||
        if (rewardText.length > 12) {
 | 
			
		||||
            context.font = "bold 9px GameFont";
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 11px GameFont";
 | 
			
		||||
            context.font = "bold 10px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillStyle = "#fd0752";
 | 
			
		||||
        context.textAlign = "center";
 | 
			
		||||
 | 
			
		||||
        context.fillText(rewardText, pos.x, pos.y + 46);
 | 
			
		||||
        context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
 | 
			
		||||
 | 
			
		||||
        // Level
 | 
			
		||||
        context.font = "bold 11px GameFont";
 | 
			
		||||
        // Level "8"
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        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.fillStyle = "#fff";
 | 
			
		||||
        context.font = "bold 7px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.levelShortcut, pos.x - 42, pos.y - 47);
 | 
			
		||||
        context.font = "bold 6px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.levelShortcut, 27, 22);
 | 
			
		||||
 | 
			
		||||
        // "Deliver"
 | 
			
		||||
        context.fillStyle = "#64666e";
 | 
			
		||||
        context.font = "bold 11px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.deliver.toUpperCase(), pos.x, pos.y - 40);
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
 | 
			
		||||
 | 
			
		||||
        // "To unlock"
 | 
			
		||||
        const unlockText = T.buildings.hub.toUnlock.toUpperCase();
 | 
			
		||||
        if (unlockText.length > 15) {
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } 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";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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 { enumDirectionToVector } from "../../core/vector";
 | 
			
		||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
import { MapChunkView } from "../map_chunk_view";
 | 
			
		||||
 | 
			
		||||
export class ItemAcceptorSystem extends GameSystemWithFilter {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
@ -38,43 +38,45 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws the acceptor items
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {MapChunkView} chunk
 | 
			
		||||
     */
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        this.forEachMatchingEntityOnScreen(parameters, this.drawEntityRegularLayer.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     */
 | 
			
		||||
    drawEntityRegularLayer(parameters, entity) {
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
    drawChunk(parameters, chunk) {
 | 
			
		||||
        const contents = chunk.containedEntitiesByLayer.regular;
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            const acceptorComp = entity.components.ItemAcceptor;
 | 
			
		||||
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
            if (!acceptorComp) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
            for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
 | 
			
		||||
                const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
 | 
			
		||||
                    animIndex
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                const slotData = acceptorComp.slots[slotIndex];
 | 
			
		||||
                const realSlotPos = staticComp.localTileToWorld(slotData.pos);
 | 
			
		||||
 | 
			
		||||
                if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
 | 
			
		||||
                    // Not within this chunk
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin);
 | 
			
		||||
                const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
 | 
			
		||||
            const finalTile = slotWorldPos.subScalars(
 | 
			
		||||
                const finalTile = realSlotPos.subScalars(
 | 
			
		||||
                    fadeOutDirection.x * (animProgress / 2 - 0.5),
 | 
			
		||||
                    fadeOutDirection.y * (animProgress / 2 - 0.5)
 | 
			
		||||
                );
 | 
			
		||||
            item.drawCentered(
 | 
			
		||||
 | 
			
		||||
                item.drawItemCenteredClipped(
 | 
			
		||||
                    (finalTile.x + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                    (finalTile.y + 0.5) * globalConfig.tileSize,
 | 
			
		||||
                parameters
 | 
			
		||||
                    parameters,
 | 
			
		||||
                    globalConfig.defaultItemDiameter
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
import { enumItemProcessorTypes } from "../components/item_processor";
 | 
			
		||||
import { MapChunkView } from "../map_chunk_view";
 | 
			
		||||
 | 
			
		||||
const logger = createLogger("systems/ejector");
 | 
			
		||||
 | 
			
		||||
@ -336,25 +337,21 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws everything
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {MapChunkView} chunk
 | 
			
		||||
     */
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
    drawChunk(parameters, chunk) {
 | 
			
		||||
        const contents = chunk.containedEntitiesByLayer.regular;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     */
 | 
			
		||||
    drawSingleEntity(parameters, entity) {
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            const ejectorComp = entity.components.ItemEjector;
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
            if (!ejectorComp) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
 | 
			
		||||
            for (let i = 0; i < ejectorComp.slots.length; ++i) {
 | 
			
		||||
                const slot = ejectorComp.slots[i];
 | 
			
		||||
                const ejectedItem = slot.item;
 | 
			
		||||
@ -364,22 +361,28 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            const realPosition = slot.pos.rotateFastMultipleOf90(staticComp.rotation);
 | 
			
		||||
            const realDirection = Vector.transformDirectionFromMultipleOf90(
 | 
			
		||||
                slot.direction,
 | 
			
		||||
                staticComp.rotation
 | 
			
		||||
            );
 | 
			
		||||
                const realPosition = staticComp.localTileToWorld(slot.pos);
 | 
			
		||||
                if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) {
 | 
			
		||||
                    // Not within this chunk
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const realDirection = staticComp.localDirectionToWorld(slot.direction);
 | 
			
		||||
                const realDirectionVector = enumDirectionToVector[realDirection];
 | 
			
		||||
 | 
			
		||||
            const tileX =
 | 
			
		||||
                staticComp.origin.x + realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
 | 
			
		||||
            const tileY =
 | 
			
		||||
                staticComp.origin.y + realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
 | 
			
		||||
                const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
 | 
			
		||||
                const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
 | 
			
		||||
 | 
			
		||||
                const worldX = tileX * 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;
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            if (entity && entity.components.Lever) {
 | 
			
		||||
                const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff;
 | 
			
		||||
            const leverComp = entity.components.Lever;
 | 
			
		||||
            if (leverComp) {
 | 
			
		||||
                const sprite = leverComp.toggled ? this.spriteOn : this.spriteOff;
 | 
			
		||||
                const origin = entity.components.StaticMapEntity.origin;
 | 
			
		||||
                sprite.drawCached(
 | 
			
		||||
                    parameters,
 | 
			
		||||
 | 
			
		||||
@ -42,10 +42,9 @@ export class MapResourcesSystem extends GameSystem {
 | 
			
		||||
                const patch = chunk.patches[i];
 | 
			
		||||
                const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize;
 | 
			
		||||
                const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize;
 | 
			
		||||
                const destSize = Math.min(80, 40 / parameters.zoomLevel);
 | 
			
		||||
                if (parameters.visibleRect.containsCircle(destX, destY, destSize / 2)) {
 | 
			
		||||
                    patch.item.drawCentered(destX, destY, parameters, destSize);
 | 
			
		||||
                }
 | 
			
		||||
                const diameter = Math.min(80, 40 / parameters.zoomLevel);
 | 
			
		||||
 | 
			
		||||
                patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // HIGH QUALITY: Draw all items
 | 
			
		||||
@ -61,9 +60,12 @@ export class MapResourcesSystem extends GameSystem {
 | 
			
		||||
                        const destX = worldX + globalConfig.halfTileSize;
 | 
			
		||||
                        const destY = worldY + globalConfig.halfTileSize;
 | 
			
		||||
 | 
			
		||||
                        if (parameters.visibleRect.containsCircle(destX, destY, globalConfig.tileSize / 2)) {
 | 
			
		||||
                            lowerItem.drawCentered(destX, destY, parameters);
 | 
			
		||||
                        }
 | 
			
		||||
                        lowerItem.drawItemCenteredClipped(
 | 
			
		||||
                            destX,
 | 
			
		||||
                            destY,
 | 
			
		||||
                            parameters,
 | 
			
		||||
                            globalConfig.defaultItemDiameter
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -102,41 +102,39 @@ export class MinerSystem extends GameSystemWithFilter {
 | 
			
		||||
     * @param {MapChunkView} chunk
 | 
			
		||||
     */
 | 
			
		||||
    drawChunk(parameters, chunk) {
 | 
			
		||||
        const contents = chunk.contents;
 | 
			
		||||
        for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
 | 
			
		||||
            for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
 | 
			
		||||
                const entity = contents[x][y];
 | 
			
		||||
        const contents = chunk.containedEntitiesByLayer.regular;
 | 
			
		||||
 | 
			
		||||
                if (entity && entity.components.Miner) {
 | 
			
		||||
                    const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            const minerComp = entity.components.Miner;
 | 
			
		||||
                    if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            if (!minerComp) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
            if (!minerComp.cachedMinedItem) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                    if (minerComp.cachedMinedItem) {
 | 
			
		||||
            // 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(
 | 
			
		||||
                            staticComp.origin.x * globalConfig.tileSize + padding,
 | 
			
		||||
                            staticComp.origin.y * globalConfig.tileSize + padding,
 | 
			
		||||
                            globalConfig.tileSize - 2 * padding,
 | 
			
		||||
                            globalConfig.tileSize - 2 * padding
 | 
			
		||||
                        );
 | 
			
		||||
                parameters.context.fillRect(destX, destY, dimensions, dimensions);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                    if (minerComp.cachedMinedItem) {
 | 
			
		||||
                        minerComp.cachedMinedItem.drawCentered(
 | 
			
		||||
            minerComp.cachedMinedItem.drawItemCenteredClipped(
 | 
			
		||||
                (0.5 + staticComp.origin.x) * globalConfig.tileSize,
 | 
			
		||||
                (0.5 + staticComp.origin.y) * globalConfig.tileSize,
 | 
			
		||||
                            parameters
 | 
			
		||||
                parameters,
 | 
			
		||||
                globalConfig.defaultItemDiameter
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,18 @@ import { MapChunkView } from "../map_chunk_view";
 | 
			
		||||
export class StaticMapEntitySystem extends GameSystem {
 | 
			
		||||
    constructor(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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const drawnUids = new Set();
 | 
			
		||||
        const contents = chunk.containedEntitiesByLayer.regular;
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
 | 
			
		||||
        const contents = chunk.contents;
 | 
			
		||||
        for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
 | 
			
		||||
            for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
 | 
			
		||||
                const entity = contents[x][y];
 | 
			
		||||
 | 
			
		||||
                if (entity) {
 | 
			
		||||
                    if (drawnUids.has(entity.uid)) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    drawnUids.add(entity.uid);
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
 | 
			
		||||
            const sprite = staticComp.getSprite();
 | 
			
		||||
            if (sprite) {
 | 
			
		||||
                        staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2);
 | 
			
		||||
                    }
 | 
			
		||||
                // 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();
 | 
			
		||||
                    if (sprite) {
 | 
			
		||||
                        staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2);
 | 
			
		||||
                        staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,28 @@
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
import { StorageComponent } from "../components/storage";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
import { DrawParameters } from "../../core/draw_parameters";
 | 
			
		||||
import { formatBigNumber, lerp } from "../../core/utils";
 | 
			
		||||
import { Loader } from "../../core/loader";
 | 
			
		||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
 | 
			
		||||
import { MapChunkView } from "../map_chunk_view";
 | 
			
		||||
 | 
			
		||||
export class StorageSystem extends GameSystemWithFilter {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
        super(root, [StorageComponent]);
 | 
			
		||||
 | 
			
		||||
        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() {
 | 
			
		||||
@ -43,38 +55,46 @@ export class StorageSystem extends GameSystemWithFilter {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     * @param {MapChunkView} chunk
 | 
			
		||||
     */
 | 
			
		||||
    drawEntity(parameters, entity) {
 | 
			
		||||
        const context = parameters.context;
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
    drawChunk(parameters, chunk) {
 | 
			
		||||
        const contents = chunk.containedEntitiesByLayer.regular;
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            const storageComp = entity.components.Storage;
 | 
			
		||||
            if (!storageComp) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        const storageComp = entity.components.Storage;
 | 
			
		||||
 | 
			
		||||
            const storedItem = storageComp.storedItem;
 | 
			
		||||
        if (storedItem !== null) {
 | 
			
		||||
            if (!storedItem) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.drawnUids.has(entity.uid)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.drawnUids.add(entity.uid);
 | 
			
		||||
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
 | 
			
		||||
            const context = parameters.context;
 | 
			
		||||
            context.globalAlpha = storageComp.overlayOpacity;
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
            if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
 | 
			
		||||
                context.font = "bold 10px GameFont";
 | 
			
		||||
                context.textAlign = "center";
 | 
			
		||||
                context.fillStyle = "#64666e";
 | 
			
		||||
                context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
 | 
			
		||||
 | 
			
		||||
                context.textAlign = "left";
 | 
			
		||||
            }
 | 
			
		||||
            context.globalAlpha = 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -624,7 +624,7 @@ export class WireSystem extends GameSystemWithFilter {
 | 
			
		||||
                    assert(sprite, "Unknown wire type: " + wireType);
 | 
			
		||||
                    const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
                    parameters.context.globalAlpha = opacity;
 | 
			
		||||
                    staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
 | 
			
		||||
                    staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0);
 | 
			
		||||
                    parameters.context.globalAlpha = 1;
 | 
			
		||||
 | 
			
		||||
                    if (G_IS_DEV && globalConfig.debug.renderWireRotations) {
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { globalConfig } from "../../core/config";
 | 
			
		||||
import { DrawParameters } from "../../core/draw_parameters";
 | 
			
		||||
import { drawRotatedSprite } from "../../core/draw_utils";
 | 
			
		||||
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 { Entity } from "../entity";
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
import { STOP_PROPAGATION } from "../../core/signal";
 | 
			
		||||
import { drawRotatedSprite } from "../../core/draw_utils";
 | 
			
		||||
import { GLOBAL_APP } from "../../core/globals";
 | 
			
		||||
import { MapChunkView } from "../map_chunk_view";
 | 
			
		||||
 | 
			
		||||
export class WiredPinsSystem extends GameSystemWithFilter {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
@ -146,28 +146,41 @@ export class WiredPinsSystem extends GameSystemWithFilter {
 | 
			
		||||
        // TODO
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws the pins
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     */
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Draws a given entity
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     * @param {MapChunkView} chunk
 | 
			
		||||
     */
 | 
			
		||||
    drawSingleEntity(parameters, entity) {
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
        const slots = entity.components.WiredPins.slots;
 | 
			
		||||
    drawChunk(parameters, chunk) {
 | 
			
		||||
        const contents = chunk.containedEntities;
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < slots.length; ++i) {
 | 
			
		||||
            const slot = slots[i];
 | 
			
		||||
        for (let i = 0; i < contents.length; ++i) {
 | 
			
		||||
            const entity = contents[i];
 | 
			
		||||
            const pinsComp = entity.components.WiredPins;
 | 
			
		||||
            if (!pinsComp) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
            const slots = pinsComp.slots;
 | 
			
		||||
 | 
			
		||||
            for (let j = 0; j < slots.length; ++j) {
 | 
			
		||||
                const slot = slots[j];
 | 
			
		||||
                const tile = staticComp.localTileToWorld(slot.pos);
 | 
			
		||||
 | 
			
		||||
                if (!chunk.tileSpaceRectangle.containsPoint(tile.x, tile.y)) {
 | 
			
		||||
                    // Doesn't belong to this chunk
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                const worldPos = tile.toWorldSpaceCenterOfTile();
 | 
			
		||||
 | 
			
		||||
                // Culling
 | 
			
		||||
                if (
 | 
			
		||||
                    !parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, globalConfig.halfTileSize)
 | 
			
		||||
                ) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const effectiveRotation = Math.radians(
 | 
			
		||||
                    staticComp.rotation + enumDirectionToAngle[slot.direction]
 | 
			
		||||
                );
 | 
			
		||||
@ -189,7 +202,12 @@ export class WiredPinsSystem extends GameSystemWithFilter {
 | 
			
		||||
                const value = slot.value;
 | 
			
		||||
                if (value) {
 | 
			
		||||
                    const offset = new Vector(0, -9).rotated(effectiveRotation);
 | 
			
		||||
                value.drawCentered(worldPos.x + offset.x, worldPos.y + offset.y, parameters, 9);
 | 
			
		||||
                    value.drawItemCenteredClipped(
 | 
			
		||||
                        worldPos.x + offset.x,
 | 
			
		||||
                        worldPos.y + offset.y,
 | 
			
		||||
                        parameters,
 | 
			
		||||
                        9
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Debug view
 | 
			
		||||
@ -208,4 +226,5 @@ export class WiredPinsSystem extends GameSystemWithFilter {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user