Merge remote-tracking branch 'upstream/master' into logic-pain-fix
BIN
res/ui/building_icons/display.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 567 KiB |
@ -258,6 +258,7 @@
|
||||
<key type="filename">sprites/belt/built/right_8.png</key>
|
||||
<key type="filename">sprites/belt/built/right_9.png</key>
|
||||
<key type="filename">sprites/blueprints/constant_signal.png</key>
|
||||
<key type="filename">sprites/blueprints/display.png</key>
|
||||
<key type="filename">sprites/blueprints/lever.png</key>
|
||||
<key type="filename">sprites/blueprints/logic_gate-not.png</key>
|
||||
<key type="filename">sprites/blueprints/logic_gate-or.png</key>
|
||||
@ -278,6 +279,7 @@
|
||||
<key type="filename">sprites/blueprints/underground_belt_exit.png</key>
|
||||
<key type="filename">sprites/blueprints/wire_tunnel.png</key>
|
||||
<key type="filename">sprites/buildings/constant_signal.png</key>
|
||||
<key type="filename">sprites/buildings/display.png</key>
|
||||
<key type="filename">sprites/buildings/lever.png</key>
|
||||
<key type="filename">sprites/buildings/logic_gate-not.png</key>
|
||||
<key type="filename">sprites/buildings/logic_gate-or.png</key>
|
||||
@ -531,6 +533,7 @@
|
||||
</struct>
|
||||
<key type="filename">sprites/wires/boolean_false.png</key>
|
||||
<key type="filename">sprites/wires/boolean_true.png</key>
|
||||
<key type="filename">sprites/wires/wires_preview.png</key>
|
||||
<struct type="IndividualSpriteSettings">
|
||||
<key>pivotPoint</key>
|
||||
<point_f>0.5,0.5</point_f>
|
||||
@ -545,6 +548,27 @@
|
||||
<key>scale9FromFile</key>
|
||||
<false/>
|
||||
</struct>
|
||||
<key type="filename">sprites/wires/display/blue.png</key>
|
||||
<key type="filename">sprites/wires/display/cyan.png</key>
|
||||
<key type="filename">sprites/wires/display/green.png</key>
|
||||
<key type="filename">sprites/wires/display/purple.png</key>
|
||||
<key type="filename">sprites/wires/display/red.png</key>
|
||||
<key type="filename">sprites/wires/display/white.png</key>
|
||||
<key type="filename">sprites/wires/display/yellow.png</key>
|
||||
<struct type="IndividualSpriteSettings">
|
||||
<key>pivotPoint</key>
|
||||
<point_f>0.5,0.5</point_f>
|
||||
<key>spriteScale</key>
|
||||
<double>1</double>
|
||||
<key>scale9Enabled</key>
|
||||
<false/>
|
||||
<key>scale9Borders</key>
|
||||
<rect>11,11,22,22</rect>
|
||||
<key>scale9Paddings</key>
|
||||
<rect>11,11,22,22</rect>
|
||||
<key>scale9FromFile</key>
|
||||
<false/>
|
||||
</struct>
|
||||
</map>
|
||||
<key>fileList</key>
|
||||
<array>
|
||||
|
BIN
res_raw/sprites/blueprints/display.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/blueprints/wire_tunnel-coating.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res_raw/sprites/buildings/display.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
res_raw/sprites/buildings/wire_tunnel-coating.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
res_raw/sprites/wires/display/blue.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/display/cyan.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res_raw/sprites/wires/display/green.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/display/purple.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/display/red.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/display/white.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/display/yellow.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res_raw/sprites/wires/network_conflict.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
res_raw/sprites/wires/network_empty.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
res_raw/sprites/wires/wires_preview.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
@ -318,9 +318,9 @@ input {
|
||||
|
||||
canvas {
|
||||
pointer-events: all;
|
||||
image-rendering: pixelated;
|
||||
// image-rendering: pixelated;
|
||||
// &.smoothed {
|
||||
// }
|
||||
// }1
|
||||
// &.unsmoothed {
|
||||
// }
|
||||
letter-spacing: 0 !important;
|
||||
|
@ -1,5 +1,5 @@
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
|
||||
constant_signal, logic_gate, lever, filter, wire_tunnel;
|
||||
constant_signal, logic_gate, lever, filter, wire_tunnel, display;
|
||||
|
||||
@each $building in $buildings {
|
||||
[data-icon="building_icons/#{$building}.png"] {
|
||||
|
@ -13,7 +13,7 @@ import { round1Digit } from "./utils";
|
||||
|
||||
const logger = createLogger("buffers");
|
||||
|
||||
const bufferGcDurationSeconds = 10;
|
||||
const bufferGcDurationSeconds = 5;
|
||||
|
||||
export class BufferMaintainer {
|
||||
/**
|
||||
@ -27,6 +27,31 @@ export class BufferMaintainer {
|
||||
|
||||
this.iterationIndex = 1;
|
||||
this.lastIteration = 0;
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.update, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the buffer stats
|
||||
*/
|
||||
getStats() {
|
||||
let stats = {
|
||||
rootKeys: 0,
|
||||
subKeys: 0,
|
||||
vramBytes: 0,
|
||||
};
|
||||
this.cache.forEach((subCache, key) => {
|
||||
++stats.rootKeys;
|
||||
|
||||
subCache.forEach((cacheEntry, subKey) => {
|
||||
++stats.subKeys;
|
||||
|
||||
const canvas = cacheEntry.canvas;
|
||||
stats.vramBytes += canvas.width * canvas.height * 4;
|
||||
});
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,8 @@ export const globalConfig = {
|
||||
beltSpeedItemsPerSecond: 2,
|
||||
minerSpeedItemsPerSecond: 0, // COMPUTED
|
||||
|
||||
defaultItemDiameter: 20,
|
||||
|
||||
itemSpacingOnBelts: 0.63,
|
||||
|
||||
wiresSpeedItemsPerSecond: 6,
|
||||
|
@ -95,5 +95,14 @@ export default {
|
||||
// Whether to items / s instead of items / m in stats
|
||||
// detailedStatistics: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Shows detailed information about which atlas is used
|
||||
// showAtlasInfo: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Renders the rotation of all wires
|
||||
// renderWireRotations: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Renders information about wire networks
|
||||
// renderWireNetworkInfos: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
/* dev:end */
|
||||
};
|
||||
|
@ -22,9 +22,5 @@ export class DrawParameters {
|
||||
// FIXME: Not really nice
|
||||
/** @type {GameRoot} */
|
||||
this.root = root;
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||
this.visibleRect = this.visibleRect.expandedInAllDirections(-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,12 @@
|
||||
* @typedef {import("./draw_parameters").DrawParameters} DrawParameters
|
||||
*/
|
||||
|
||||
import { globalConfig } from "./config";
|
||||
import { createLogger } from "./logging";
|
||||
import { Rectangle } from "./rectangle";
|
||||
|
||||
const logger = createLogger("draw_utils");
|
||||
|
||||
export function initDrawUtils() {
|
||||
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
|
||||
this.beginPath();
|
||||
@ -52,9 +58,64 @@ export function initDrawUtils() {
|
||||
* @param {number=} param0.offsetY
|
||||
*/
|
||||
export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offsetX = 0, offsetY = 0 }) {
|
||||
if (angle === 0) {
|
||||
sprite.drawCachedCentered(parameters, x + offsetX, y + offsetY, size);
|
||||
return;
|
||||
}
|
||||
|
||||
parameters.context.translate(x, y);
|
||||
parameters.context.rotate(angle);
|
||||
sprite.drawCachedCentered(parameters, offsetX, offsetY, size, false);
|
||||
parameters.context.rotate(-angle);
|
||||
parameters.context.translate(-x, -y);
|
||||
}
|
||||
|
||||
let warningsShown = 0;
|
||||
|
||||
/**
|
||||
* Draws a sprite with clipping
|
||||
* @param {object} param0
|
||||
* @param {DrawParameters} param0.parameters
|
||||
* @param {HTMLCanvasElement} param0.sprite
|
||||
* @param {number} param0.x
|
||||
* @param {number} param0.y
|
||||
* @param {number} param0.w
|
||||
* @param {number} param0.h
|
||||
* @param {number} param0.originalW
|
||||
* @param {number} param0.originalH
|
||||
*/
|
||||
export function drawSpriteClipped({ parameters, sprite, x, y, w, h, originalW, originalH }) {
|
||||
const rect = new Rectangle(x, y, w, h);
|
||||
const intersection = rect.getIntersection(parameters.visibleRect);
|
||||
if (!intersection) {
|
||||
// Clipped
|
||||
if (++warningsShown % 200 === 1) {
|
||||
logger.warn(
|
||||
"Sprite drawn clipped but it's not on screen - perform culling before (",
|
||||
warningsShown,
|
||||
"warnings)"
|
||||
);
|
||||
}
|
||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||
parameters.context.fillStyle = "yellow";
|
||||
parameters.context.fillRect(x, y, w, h);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parameters.context.drawImage(
|
||||
sprite,
|
||||
|
||||
// src pos and size
|
||||
((intersection.x - x) / w) * originalW,
|
||||
((intersection.y - y) / h) * originalH,
|
||||
(originalW * intersection.w) / w,
|
||||
(originalH * intersection.h) / h,
|
||||
|
||||
// dest pos and size
|
||||
intersection.x,
|
||||
intersection.y,
|
||||
intersection.w,
|
||||
intersection.h
|
||||
);
|
||||
}
|
||||
|
@ -376,24 +376,33 @@ export class Rectangle {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Good for printing stuff
|
||||
*/
|
||||
toString() {
|
||||
return (
|
||||
"[x:" +
|
||||
round2Digits(this.x) +
|
||||
"| y:" +
|
||||
round2Digits(this.y) +
|
||||
"| w:" +
|
||||
round2Digits(this.w) +
|
||||
"| h:" +
|
||||
round2Digits(this.h) +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new recangle in tile space which includes all tiles which are visible in this rect
|
||||
* @param {boolean=} includeHalfTiles
|
||||
* @returns {Rectangle}
|
||||
*/
|
||||
toTileCullRectangle(includeHalfTiles = true) {
|
||||
let scaled = this.allScaled(1.0 / globalConfig.tileSize);
|
||||
|
||||
if (includeHalfTiles) {
|
||||
// Increase rectangle size
|
||||
scaled = Rectangle.fromTRBL(
|
||||
Math.floor(scaled.y),
|
||||
Math.ceil(scaled.right()),
|
||||
Math.ceil(scaled.bottom()),
|
||||
Math.floor(scaled.x)
|
||||
);
|
||||
}
|
||||
|
||||
return scaled;
|
||||
toTileCullRectangle() {
|
||||
return new Rectangle(
|
||||
Math.floor(this.x / globalConfig.tileSize),
|
||||
Math.floor(this.y / globalConfig.tileSize),
|
||||
Math.ceil(this.w / globalConfig.tileSize),
|
||||
Math.ceil(this.h / globalConfig.tileSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { round3Digits } from "./utils";
|
||||
|
||||
const floorSpriteCoordinates = false;
|
||||
|
||||
export const ORIGINAL_SPRITE_SCALE = "0.5";
|
||||
export const ORIGINAL_SPRITE_SCALE = "0.75";
|
||||
|
||||
export class BaseSprite {
|
||||
/**
|
||||
|
50
src/js/core/stale_area_detector.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { createLogger } from "./logging";
|
||||
import { Rectangle } from "./rectangle";
|
||||
import { globalConfig } from "./config";
|
||||
|
||||
const logger = createLogger("stale_areas");
|
||||
|
||||
export class StaleAreaDetector {
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {import("../game/root").GameRoot} param0.root
|
||||
* @param {string} param0.name The name for reference
|
||||
* @param {(Rectangle) => void} param0.recomputeMethod Method which recomputes the given area
|
||||
*/
|
||||
constructor({ root, name, recomputeMethod }) {
|
||||
this.root = root;
|
||||
this.name = name;
|
||||
this.recomputeMethod = recomputeMethod;
|
||||
|
||||
/** @type {Rectangle} */
|
||||
this.staleArea = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the given area
|
||||
* @param {Rectangle} area
|
||||
*/
|
||||
invalidate(area) {
|
||||
// logger.log(this.name, "invalidated", area.toString());
|
||||
if (this.staleArea) {
|
||||
this.staleArea = this.staleArea.getUnion(area);
|
||||
} else {
|
||||
this.staleArea = area.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stale area
|
||||
*/
|
||||
update() {
|
||||
if (this.staleArea) {
|
||||
logger.log(this.name, "is recomputing", this.staleArea.toString());
|
||||
if (G_IS_DEV && globalConfig.debug.renderChanges) {
|
||||
this.root.hud.parts.changesDebugger.renderChange(this.name, this.staleArea, "#fd145b");
|
||||
}
|
||||
this.recomputeMethod(this.staleArea);
|
||||
this.staleArea = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -713,3 +713,12 @@ export function rotateDirectionalObject(obj, rotation) {
|
||||
left: queue[3],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Modulo which works for negative numbers
|
||||
* @param {number} n
|
||||
* @param {number} m
|
||||
*/
|
||||
export function safeModulo(n, m) {
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { globalConfig } from "./config";
|
||||
import { safeModulo } from "./utils";
|
||||
|
||||
const tileSize = globalConfig.tileSize;
|
||||
const halfTileSize = globalConfig.halfTileSize;
|
||||
@ -287,6 +288,15 @@ export class Vector {
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns x % f, y % f
|
||||
* @param {number} f
|
||||
* @returns {Vector} new vector
|
||||
*/
|
||||
modScalar(f) {
|
||||
return new Vector(safeModulo(this.x, f), safeModulo(this.y, f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and returns the center between both points
|
||||
* @param {Vector} v
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { BasicSerializableObject } from "../savegame/serialization";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumItemType = {
|
||||
shape: "shape",
|
||||
color: "color",
|
||||
boolean: "boolean",
|
||||
};
|
||||
/** @type {ItemType[]} **/
|
||||
export const itemTypes = ["shape", "color", "boolean"];
|
||||
|
||||
/**
|
||||
* Class for items on belts etc. Not an entity for performance reasons
|
||||
@ -25,10 +22,10 @@ export class BaseItem extends BasicSerializableObject {
|
||||
return {};
|
||||
}
|
||||
|
||||
/** @returns {enumItemType} */
|
||||
/** @returns {ItemType} **/
|
||||
getItemType() {
|
||||
abstract;
|
||||
return "";
|
||||
return "shape";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,9 +56,24 @@ export class BaseItem extends BasicSerializableObject {
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} size
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
draw(x, y, parameters, size) {}
|
||||
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;
|
||||
|
@ -8,7 +8,7 @@ import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { Entity } from "./entity";
|
||||
import { typeItemSingleton } from "./item_resolver";
|
||||
import { enumLayer, GameRoot } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
const logger = createLogger("belt_path");
|
||||
|
||||
@ -203,7 +203,7 @@ export class BeltPath extends BasicSerializableObject {
|
||||
const targetEntity = this.root.map.getLayerContentXY(
|
||||
ejectSlotTargetWsTile.x,
|
||||
ejectSlotTargetWsTile.y,
|
||||
enumLayer.regular
|
||||
"regular"
|
||||
);
|
||||
|
||||
if (targetEntity) {
|
||||
@ -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].draw(worldPos.x, worldPos.y, parameters);
|
||||
}
|
||||
|
||||
distanceAndItem[_item].drawItemCenteredClipped(
|
||||
worldPos.x,
|
||||
worldPos.y,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
|
||||
// Check for the next item
|
||||
currentItemPos += distanceAndItem[_nextDistance];
|
||||
|
@ -3,7 +3,7 @@ import { Loader } from "../core/loader";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Entity } from "./entity";
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { findNiceIntegerValue } from "../core/utils";
|
||||
import { blueprintShape } from "./upgrades";
|
||||
import { globalConfig } from "../core/config";
|
||||
@ -20,11 +20,11 @@ export class Blueprint {
|
||||
|
||||
/**
|
||||
* Returns the layer of this blueprint
|
||||
* @returns {enumLayer}
|
||||
* @returns {Layer}
|
||||
*/
|
||||
get layer() {
|
||||
if (this.entities.length === 0) {
|
||||
return enumLayer.regular;
|
||||
return "regular";
|
||||
}
|
||||
return this.entities[0].layer;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
* @param {Vector} param0.tile
|
||||
* @param {number} param0.rotation
|
||||
* @param {string} param0.variant
|
||||
* @param {string} param0.layer
|
||||
* @param {Layer} param0.layer
|
||||
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }}
|
||||
*/
|
||||
computeOptimalDirectionAndRotationVariantAtTile({ root, tile, rotation, variant, layer }) {
|
||||
|
@ -2,7 +2,7 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { enumLayer, GameRoot } from "../root";
|
||||
import { GameRoot } from "../root";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
|
||||
export class MetaConstantSignalBuilding extends MetaBuilding {
|
||||
@ -22,8 +22,9 @@ export class MetaConstantSignalBuilding extends MetaBuilding {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {"wires"} **/
|
||||
getLayer() {
|
||||
return enumLayer.wires;
|
||||
return "wires";
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
|
@ -8,7 +8,6 @@ import { Entity } from "../entity";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { enumItemType } from "../base_item";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumCutterVariants = { quad: "quad" };
|
||||
@ -82,7 +81,7 @@ export class MetaCutterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
51
src/js/game/buildings/display.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
|
||||
export class MetaDisplayBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("display");
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#aaaaaa";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
getIsUnlocked(root) {
|
||||
// @todo
|
||||
return true;
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(1, 1);
|
||||
}
|
||||
|
||||
getShowWiresLayerPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new WiredPinsComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.bottom,
|
||||
type: enumPinSlotType.logicalAcceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
entity.addComponent(new DisplayComponent());
|
||||
}
|
||||
}
|
@ -29,6 +29,10 @@ export class MetaFilterBuilding extends MetaBuilding {
|
||||
return new Vector(2, 1);
|
||||
}
|
||||
|
||||
getShowWiresLayerPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumItemType } from "../base_item";
|
||||
import { HubComponent } from "../components/hub";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
@ -68,72 +67,72 @@ export class MetaHubBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.top, enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
directions: [enumDirection.top, enumDirection.right],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 3),
|
||||
directions: [enumDirection.bottom, enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 3),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 3),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 3),
|
||||
directions: [enumDirection.bottom, enumDirection.right],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 2),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 3),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 1),
|
||||
directions: [enumDirection.right],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 2),
|
||||
directions: [enumDirection.right],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 3),
|
||||
directions: [enumDirection.right],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -35,6 +35,10 @@ export class MetaLeverBuilding extends MetaBuilding {
|
||||
return null;
|
||||
}
|
||||
|
||||
getShowWiresLayerPreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
|
@ -2,7 +2,7 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
||||
import { enumLayer, GameRoot } from "../root";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
|
||||
/** @enum {string} */
|
||||
@ -39,8 +39,9 @@ export class MetaLogicGateBuilding extends MetaBuilding {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {"wires"} **/
|
||||
getLayer() {
|
||||
return enumLayer.wires;
|
||||
return "wires";
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { formatItemsPerSecond } from "../../core/utils";
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { T } from "../../translations";
|
||||
import { enumItemType } from "../base_item";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
@ -63,12 +62,12 @@ export class MetaMixerBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -8,7 +8,6 @@ import { Entity } from "../entity";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { enumItemType } from "../base_item";
|
||||
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
|
||||
import { ProcessingRequirementComponent } from "../components/processing_requirement";
|
||||
|
||||
@ -100,12 +99,12 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -133,14 +132,14 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [
|
||||
variant === defaultBuildingVariant ? enumDirection.top : enumDirection.bottom,
|
||||
],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
|
||||
@ -163,17 +162,17 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
|
||||
@ -223,27 +222,27 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.color,
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -8,7 +8,6 @@ import { Entity } from "../entity";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { enumItemType } from "../base_item";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumRotaterVariants = { ccw: "ccw", fl: "fl" };
|
||||
@ -89,7 +88,7 @@ export class MetaRotaterBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -8,7 +8,6 @@ import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { enumItemType } from "../base_item";
|
||||
|
||||
export class MetaStackerBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
@ -63,12 +62,12 @@ export class MetaStackerBuilding extends MetaBuilding {
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: enumItemType.shape,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -5,7 +5,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumUndergroundBeltMode, UndergroundBeltComponent } from "../components/underground_belt";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
||||
import { GameRoot, enumLayer } from "../root";
|
||||
import { GameRoot } from "../root";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
import { formatItemsPerSecond, generateMatrixRotations } from "../../core/utils";
|
||||
@ -171,7 +171,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
* @param {Vector} param0.tile
|
||||
* @param {number} param0.rotation
|
||||
* @param {string} param0.variant
|
||||
* @param {string} param0.layer
|
||||
* @param {Layer} param0.layer
|
||||
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }}
|
||||
*/
|
||||
computeOptimalDirectionAndRotationVariantAtTile({ root, tile, rotation, variant, layer }) {
|
||||
@ -190,7 +190,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
tile = tile.addScalars(searchVector.x, searchVector.y);
|
||||
|
||||
/* WIRES: FIXME */
|
||||
const contents = root.map.getTileContent(tile, enumLayer.regular);
|
||||
const contents = root.map.getTileContent(tile, "regular");
|
||||
if (contents) {
|
||||
const undergroundComp = contents.components.UndergroundBelt;
|
||||
if (undergroundComp && undergroundComp.tier === tier) {
|
||||
|
@ -5,7 +5,7 @@ import { SOUNDS } from "../../platform/sound";
|
||||
import { enumWireType, WireComponent } from "../components/wire";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { enumLayer, GameRoot } from "../root";
|
||||
import { GameRoot } from "../root";
|
||||
|
||||
export const arrayWireRotationVariantToType = [
|
||||
enumWireType.regular,
|
||||
@ -50,8 +50,9 @@ export class MetaWireBuilding extends MetaBuilding {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {"wires"} **/
|
||||
getLayer() {
|
||||
return enumLayer.wires;
|
||||
return "wires";
|
||||
}
|
||||
|
||||
getSprite() {
|
||||
|
@ -1,8 +1,19 @@
|
||||
import { Vector } from "../../core/vector";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot, enumLayer } from "../root";
|
||||
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { WireTunnelComponent } from "../components/wire_tunnel";
|
||||
import { generateMatrixRotations } from "../../core/utils";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumWireTunnelVariants = {
|
||||
coating: "coating",
|
||||
};
|
||||
|
||||
const wireTunnelOverlayMatrices = {
|
||||
[defaultBuildingVariant]: generateMatrixRotations([0, 1, 0, 1, 1, 1, 0, 1, 0]),
|
||||
[enumWireTunnelVariants.coating]: generateMatrixRotations([0, 1, 0, 0, 1, 0, 0, 1, 0]),
|
||||
};
|
||||
|
||||
export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
@ -10,7 +21,7 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#25fff2";
|
||||
return "#777a86";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -21,16 +32,40 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
return true;
|
||||
}
|
||||
|
||||
getIsRotateable() {
|
||||
return false;
|
||||
/**
|
||||
*
|
||||
* @param {number} rotation
|
||||
* @param {number} rotationVariant
|
||||
* @param {string} variant
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
getSpecialOverlayRenderMatrix(rotation, rotationVariant, variant, entity) {
|
||||
return wireTunnelOverlayMatrices[variant][rotation];
|
||||
}
|
||||
|
||||
getIsRotateable(variant) {
|
||||
return variant !== defaultBuildingVariant;
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(1, 1);
|
||||
}
|
||||
|
||||
getAvailableVariants() {
|
||||
return [defaultBuildingVariant, enumWireTunnelVariants.coating];
|
||||
}
|
||||
|
||||
/** @returns {"wires"} **/
|
||||
getLayer() {
|
||||
return enumLayer.wires;
|
||||
return "wires";
|
||||
}
|
||||
|
||||
getRotateAutomaticallyWhilePlacing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getStayInPlacementMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,6 +73,15 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(new WireTunnelComponent());
|
||||
entity.addComponent(new WireTunnelComponent({}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Entity} entity
|
||||
* @param {number} rotationVariant
|
||||
* @param {string} variant
|
||||
*/
|
||||
updateVariants(entity, rotationVariant, variant) {
|
||||
entity.components.WireTunnel.multipleDirections = variant === defaultBuildingVariant;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,11 @@ import { ConstantSignalComponent } from "./components/constant_signal";
|
||||
import { LogicGateComponent } from "./components/logic_gate";
|
||||
import { LeverComponent } from "./components/lever";
|
||||
import { WireTunnelComponent } from "./components/wire_tunnel";
|
||||
<<<<<<< HEAD
|
||||
import { ProcessingRequirementComponent } from "./components/processing_requirement";
|
||||
=======
|
||||
import { DisplayComponent } from "./components/display";
|
||||
>>>>>>> upstream/master
|
||||
|
||||
export function initComponentRegistry() {
|
||||
gComponentRegistry.register(StaticMapEntityComponent);
|
||||
@ -35,6 +39,7 @@ export function initComponentRegistry() {
|
||||
gComponentRegistry.register(LeverComponent);
|
||||
gComponentRegistry.register(WireTunnelComponent);
|
||||
gComponentRegistry.register(ProcessingRequirementComponent);
|
||||
gComponentRegistry.register(DisplayComponent);
|
||||
|
||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||
|
||||
|
11
src/js/game/components/display.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from "../component";
|
||||
|
||||
export class DisplayComponent extends Component {
|
||||
static getId() {
|
||||
return "Display";
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
return new DisplayComponent();
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { enumDirection, enumInvertedDirections, Vector } from "../../core/vector";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem, enumItemType } from "../base_item";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
|
||||
/** @typedef {{
|
||||
* pos: Vector,
|
||||
* directions: enumDirection[],
|
||||
* filter?: enumItemType
|
||||
* filter?: ItemType
|
||||
* }} ItemAcceptorSlot */
|
||||
|
||||
/**
|
||||
@ -20,7 +20,7 @@ import { Component } from "../component";
|
||||
/** @typedef {{
|
||||
* pos: Vector,
|
||||
* directions: enumDirection[],
|
||||
* filter?: enumItemType
|
||||
* filter?: ItemType
|
||||
* }} ItemAcceptorSlotConfig */
|
||||
|
||||
export class ItemAcceptorComponent extends Component {
|
||||
@ -74,7 +74,7 @@ export class ItemAcceptorComponent extends Component {
|
||||
pos: slot.pos,
|
||||
directions: slot.directions,
|
||||
|
||||
// Which type of item to accept (shape | color | all) @see enumItemType
|
||||
// Which type of item to accept (shape | color | all) @see ItemType
|
||||
filter: slot.filter,
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
@ -255,8 +256,7 @@ export class StaticMapEntityComponent extends Component {
|
||||
worldX - extrudePixels * size.x,
|
||||
worldY - extrudePixels * size.y,
|
||||
globalConfig.tileSize * size.x + 2 * extrudePixels * size.x,
|
||||
globalConfig.tileSize * size.y + 2 * extrudePixels * size.y,
|
||||
false
|
||||
globalConfig.tileSize * size.y + 2 * extrudePixels * size.y
|
||||
);
|
||||
} else {
|
||||
const rotationCenterX = worldX + globalConfig.halfTileSize;
|
||||
@ -264,16 +264,14 @@ export class StaticMapEntityComponent extends Component {
|
||||
|
||||
parameters.context.translate(rotationCenterX, rotationCenterY);
|
||||
parameters.context.rotate(Math.radians(this.rotation));
|
||||
|
||||
sprite.drawCached(
|
||||
parameters,
|
||||
-globalConfig.halfTileSize - extrudePixels * size.x,
|
||||
-globalConfig.halfTileSize - extrudePixels * size.y,
|
||||
globalConfig.tileSize * size.x + 2 * extrudePixels * size.x,
|
||||
globalConfig.tileSize * size.y + 2 * extrudePixels * size.y,
|
||||
false
|
||||
false // no clipping possible here
|
||||
);
|
||||
|
||||
parameters.context.rotate(-Math.radians(this.rotation));
|
||||
parameters.context.translate(-rotationCenterX, -rotationCenterY);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem, enumItemType } from "../base_item";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
import { typeItemSingleton } from "../item_resolver";
|
||||
import { ColorItem } from "../items/color_item";
|
||||
@ -65,11 +65,11 @@ export class StorageComponent extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemType === enumItemType.color) {
|
||||
if (itemType === "color") {
|
||||
return /** @type {ColorItem} */ (this.storedItem).color === /** @type {ColorItem} */ (item).color;
|
||||
}
|
||||
|
||||
if (itemType === enumItemType.shape) {
|
||||
if (itemType === "shape") {
|
||||
return (
|
||||
/** @type {ShapeItem} */ (this.storedItem).definition.getHash() ===
|
||||
/** @type {ShapeItem} */ (item).definition.getHash()
|
||||
|
@ -6,6 +6,21 @@ export class WireTunnelComponent extends Component {
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
return new WireTunnelComponent();
|
||||
return new WireTunnelComponent({ multipleDirections: this.multipleDirections });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {boolean=} param0.multipleDirections
|
||||
*/
|
||||
constructor({ multipleDirections = true }) {
|
||||
super();
|
||||
this.multipleDirections = multipleDirections;
|
||||
|
||||
/**
|
||||
* Linked network, only if its not multiple directions
|
||||
* @type {Array<import("../systems/wire").WireNetwork>}
|
||||
*/
|
||||
this.linkedNetworks = [];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -9,7 +9,7 @@ import { DrawParameters } from "../core/draw_parameters";
|
||||
import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { randomInt, round2Digits } from "../core/utils";
|
||||
import { randomInt, round2Digits, round3Digits } from "../core/utils";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Savegame } from "../savegame/savegame";
|
||||
import { SavegameSerializer } from "../savegame/savegame_serializer";
|
||||
@ -26,7 +26,7 @@ import { GameLogic } from "./logic";
|
||||
import { MapView } from "./map_view";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { ProductionAnalytics } from "./production_analytics";
|
||||
import { enumLayer, GameRoot } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { GameTime } from "./time/game_time";
|
||||
@ -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;
|
||||
|
||||
@ -369,6 +368,13 @@ export class GameCore {
|
||||
}
|
||||
|
||||
// Transform to world space
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||
params.visibleRect = params.visibleRect.expandedInAllDirections(
|
||||
-200 / this.root.camera.zoomLevel
|
||||
);
|
||||
}
|
||||
|
||||
root.camera.transform(context);
|
||||
|
||||
assert(context.globalAlpha === 1.0, "Global alpha not 1 on frame start");
|
||||
@ -383,36 +389,24 @@ export class GameCore {
|
||||
// Map overview
|
||||
root.map.drawOverlay(params);
|
||||
} else {
|
||||
// Background (grid, resources, etc)
|
||||
root.map.drawBackground(params);
|
||||
|
||||
// Underlays for splitters / balancers
|
||||
systems.beltUnderlays.drawUnderlays(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 === enumLayer.wires) {
|
||||
if (this.root.currentLayer === "wires") {
|
||||
// Static map entities
|
||||
root.map.drawWiresForegroundLayer(params);
|
||||
|
||||
// pins
|
||||
systems.wiredPins.draw(params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,6 +433,9 @@ export class GameCore {
|
||||
params.zoomLevel = 1;
|
||||
params.desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
|
||||
params.visibleRect = new Rectangle(0, 0, this.root.gameWidth, this.root.gameHeight);
|
||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||
params.visibleRect = params.visibleRect.expandedInAllDirections(-200);
|
||||
}
|
||||
|
||||
// Draw overlays, those are screen space
|
||||
root.hud.drawOverlays(params);
|
||||
@ -457,7 +454,7 @@ export class GameCore {
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.showAtlasInfo) {
|
||||
context.font = "13px GameFont";
|
||||
context.fillStyle = "yellow";
|
||||
context.fillStyle = "blue";
|
||||
context.fillText(
|
||||
"Atlas: " +
|
||||
desiredAtlasScale +
|
||||
@ -465,9 +462,31 @@ export class GameCore {
|
||||
round2Digits(zoomLevel) +
|
||||
" / Effective Zoom: " +
|
||||
round2Digits(effectiveZoomLevel),
|
||||
200,
|
||||
20
|
||||
20,
|
||||
600
|
||||
);
|
||||
|
||||
const stats = this.root.buffers.getStats();
|
||||
context.fillText(
|
||||
"Buffers: " +
|
||||
stats.rootKeys +
|
||||
" root keys, " +
|
||||
stats.subKeys +
|
||||
" sub keys / buffers / VRAM: " +
|
||||
round2Digits(stats.vramBytes / (1024 * 1024)) +
|
||||
" MB",
|
||||
|
||||
20,
|
||||
620
|
||||
);
|
||||
}
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||
context.strokeStyle = "red";
|
||||
context.lineWidth = 1;
|
||||
context.beginPath();
|
||||
context.rect(200, 200, this.root.gameWidth - 400, this.root.gameHeight - 400);
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { DrawParameters } from "../core/draw_parameters";
|
||||
import { Component } from "./component";
|
||||
/* typehints:end */
|
||||
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { enumDirectionToVector, enumDirectionToAngle } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
@ -36,8 +36,9 @@ export class Entity extends BasicSerializableObject {
|
||||
|
||||
/**
|
||||
* On which layer this entity is
|
||||
* @type {Layer}
|
||||
*/
|
||||
this.layer = enumLayer.regular;
|
||||
this.layer = "regular";
|
||||
|
||||
/**
|
||||
* Internal entity unique id, set by the @see EntityManager
|
||||
|
@ -16,6 +16,7 @@ import { LogicGateComponent } from "./components/logic_gate";
|
||||
import { LeverComponent } from "./components/lever";
|
||||
import { WireTunnelComponent } from "./components/wire_tunnel";
|
||||
import { ProcessingRequirementComponent } from "./components/processing_requirement";
|
||||
import { DisplayComponent } from "./components/display";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
@ -77,6 +78,9 @@ export class EntityComponentStorage {
|
||||
/** @type {ProcessingRequirementComponent} */
|
||||
this.ProcessingRequirement;
|
||||
|
||||
/** @type {DisplayComponent} */
|
||||
this.Display;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { WireSystem } from "./systems/wire";
|
||||
import { ConstantSignalSystem } from "./systems/constant_signal";
|
||||
import { LogicGateSystem } from "./systems/logic_gate";
|
||||
import { LeverSystem } from "./systems/lever";
|
||||
import { DisplaySystem } from "./systems/display";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
@ -80,6 +81,9 @@ export class GameSystemManager {
|
||||
/** @type {LeverSystem} */
|
||||
lever: null,
|
||||
|
||||
/** @type {DisplaySystem} */
|
||||
display: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
@ -137,6 +141,8 @@ export class GameSystemManager {
|
||||
// Wires must be after all gate, signal etc logic!
|
||||
add("wire", WireSystem);
|
||||
|
||||
add("display", DisplaySystem);
|
||||
|
||||
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -3,7 +3,7 @@ import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/ut
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { enumColors } from "./colors";
|
||||
import { enumItemProcessorTypes } from "./components/item_processor";
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
|
||||
import { UPGRADES } from "./upgrades";
|
||||
|
@ -42,6 +42,7 @@ import { HUDSandboxController } from "./parts/sandbox_controller";
|
||||
import { HUDWiresToolbar } from "./parts/wires_toolbar";
|
||||
import { HUDWireInfo } from "./parts/wire_info";
|
||||
import { HUDLeverToggle } from "./parts/lever_toggle";
|
||||
import { HUDLayerPreview } from "./parts/layer_preview";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -80,6 +81,7 @@ export class GameHUD {
|
||||
shapeViewer: new HUDShapeViewer(this.root),
|
||||
|
||||
wiresOverlay: new HUDWiresOverlay(this.root),
|
||||
layerPreview: new HUDLayerPreview(this.root),
|
||||
|
||||
// Typing hints
|
||||
/* typehints:start */
|
||||
|
@ -11,7 +11,6 @@ import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { Blueprint } from "../../blueprint";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { enumLayer } from "../../root";
|
||||
|
||||
export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
@ -60,7 +59,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
|
||||
/**
|
||||
* Called when the layer was changed
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
onEditModeChanged(layer) {
|
||||
// Check if the layer of the blueprint differs and thus we have to deselect it
|
||||
|
@ -18,7 +18,7 @@ import { THEME } from "../../theme";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { HUDBuildingPlacerLogic } from "./building_placer_logic";
|
||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { enumLayer } from "../../root";
|
||||
import { layers } from "../../root";
|
||||
import { getCodeFromBuildingData } from "../../building_codes";
|
||||
|
||||
export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
@ -61,9 +61,9 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
this.currentInterpolatedCornerTile = new Vector();
|
||||
|
||||
this.lockIndicatorSprites = {};
|
||||
for (const layerId in enumLayer) {
|
||||
this.lockIndicatorSprites[layerId] = this.makeLockIndicatorSprite(layerId);
|
||||
}
|
||||
layers.forEach(layer => {
|
||||
this.lockIndicatorSprites[layer] = this.makeLockIndicatorSprite(layer);
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@ -76,7 +76,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
|
||||
/**
|
||||
* Makes the lock indicator sprite for the given layer
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
makeLockIndicatorSprite(layer) {
|
||||
const dims = 48;
|
||||
@ -247,6 +247,31 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
} else {
|
||||
this.drawRegularPlacement(parameters);
|
||||
}
|
||||
|
||||
if (metaBuilding.getShowWiresLayerPreview()) {
|
||||
this.drawLayerPeek(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawLayerPeek(parameters) {
|
||||
const mousePosition = this.root.app.mousePosition;
|
||||
if (!mousePosition) {
|
||||
// Not on screen
|
||||
return;
|
||||
}
|
||||
|
||||
const worldPosition = this.root.camera.screenToWorld(mousePosition);
|
||||
|
||||
// Draw peeker
|
||||
this.root.hud.parts.layerPreview.renderPreview(
|
||||
parameters,
|
||||
worldPosition,
|
||||
1 / this.root.camera.zoomLevel
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,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
|
||||
|
@ -12,7 +12,6 @@ import { BaseHUDPart } from "../base_hud_part";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { enumLayer } from "../../root";
|
||||
import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
|
||||
import { MetaHubBuilding } from "../../buildings/hub";
|
||||
|
||||
@ -133,12 +132,12 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
|
||||
/**
|
||||
* Called when the edit mode got changed
|
||||
* @param {enumLayer} editMode
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
onEditModeChanged(editMode) {
|
||||
onEditModeChanged(layer) {
|
||||
const metaBuilding = this.currentMetaBuilding.get();
|
||||
if (metaBuilding) {
|
||||
if (metaBuilding.getLayer() !== editMode) {
|
||||
if (metaBuilding.getLayer() !== layer) {
|
||||
// This layer doesn't fit the edit mode anymore
|
||||
this.currentMetaBuilding.set(null);
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import { MetaSplitterBuilding } from "../../buildings/splitter";
|
||||
import { MetaStackerBuilding } from "../../buildings/stacker";
|
||||
import { MetaTrashBuilding } from "../../buildings/trash";
|
||||
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
|
||||
import { enumLayer } from "../../root";
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
import { MetaLeverBuilding } from "../../buildings/lever";
|
||||
import { MetaFilterBuilding } from "../../buildings/filter";
|
||||
import { MetaDisplayBuilding } from "../../buildings/display";
|
||||
|
||||
const supportedBuildings = [
|
||||
MetaBeltBaseBuilding,
|
||||
@ -26,6 +26,7 @@ const supportedBuildings = [
|
||||
MetaTrashBuilding,
|
||||
MetaLeverBuilding,
|
||||
MetaFilterBuilding,
|
||||
MetaDisplayBuilding,
|
||||
];
|
||||
|
||||
export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||
@ -33,7 +34,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||
super(root, {
|
||||
supportedBuildings,
|
||||
visibilityCondition: () =>
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === enumLayer.regular,
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "regular",
|
||||
htmlElementId: "ingame_HUD_buildings_toolbar",
|
||||
});
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { THEME } from "../../theme";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { T } from "../../../translations";
|
||||
import { enumItemType } from "../../base_item";
|
||||
import { enumLayer } from "../../root";
|
||||
|
||||
export class HUDColorBlindHelper extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
@ -41,7 +39,7 @@ export class HUDColorBlindHelper extends BaseHUDPart {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.root.currentLayer !== enumLayer.regular) {
|
||||
if (this.root.currentLayer !== "regular") {
|
||||
// Not in regular mode
|
||||
return null;
|
||||
}
|
||||
@ -56,7 +54,7 @@ export class HUDColorBlindHelper extends BaseHUDPart {
|
||||
// Check if the belt has a color item
|
||||
if (beltComp) {
|
||||
const item = beltComp.assignedPath.findItemAtTile(tile);
|
||||
if (item && item.getItemType() === enumItemType.color) {
|
||||
if (item && item.getItemType() === "color") {
|
||||
return /** @type {ColorItem} */ (item).color;
|
||||
}
|
||||
}
|
||||
@ -66,7 +64,7 @@ export class HUDColorBlindHelper extends BaseHUDPart {
|
||||
if (ejectorComp) {
|
||||
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||
const slot = ejectorComp.slots[i];
|
||||
if (slot.item && slot.item.getItemType() === enumItemType.color) {
|
||||
if (slot.item && slot.item.getItemType() === "color") {
|
||||
return /** @type {ColorItem} */ (slot.item).color;
|
||||
}
|
||||
}
|
||||
@ -74,7 +72,7 @@ export class HUDColorBlindHelper extends BaseHUDPart {
|
||||
} else {
|
||||
// We hovered a lower layer, show the color there
|
||||
const lowerLayer = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
|
||||
if (lowerLayer && lowerLayer.getItemType() === enumItemType.color) {
|
||||
if (lowerLayer && lowerLayer.getItemType() === "color") {
|
||||
return /** @type {ColorItem} */ (lowerLayer).color;
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export class HUDChangesDebugger extends BaseHUDPart {
|
||||
for (let i = 0; i < this.changes.length; ++i) {
|
||||
const change = this.changes[i];
|
||||
parameters.context.fillStyle = change.fillColor;
|
||||
parameters.context.globalAlpha = 0.5;
|
||||
parameters.context.globalAlpha = 0.2;
|
||||
parameters.context.fillRect(
|
||||
change.area.x * globalConfig.tileSize,
|
||||
change.area.y * globalConfig.tileSize,
|
||||
|
123
src/js/game/hud/parts/layer_preview.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { freeCanvas, makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { Loader } from "../../../core/loader";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { MapChunkView } from "../../map_chunk_view";
|
||||
import { THEME } from "../../theme";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
/**
|
||||
* Helper class which allows peaking through to the wires layer
|
||||
*/
|
||||
export class HUDLayerPreview extends BaseHUDPart {
|
||||
initialize() {
|
||||
this.initializeCanvas();
|
||||
this.root.signals.aboutToDestruct.add(() => freeCanvas(this.canvas));
|
||||
this.root.signals.resized.add(this.initializeCanvas, this);
|
||||
this.previewOverlay = Loader.getSprite("sprites/wires/wires_preview.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* (re) initializes the canvas
|
||||
*/
|
||||
initializeCanvas() {
|
||||
if (this.canvas) {
|
||||
freeCanvas(this.canvas);
|
||||
delete this.canvas;
|
||||
delete this.context;
|
||||
}
|
||||
|
||||
// Compute how big the preview should be
|
||||
this.previewSize = Math.round(
|
||||
Math.min(1024, Math.min(this.root.gameWidth, this.root.gameHeight) * 0.8)
|
||||
);
|
||||
|
||||
const [canvas, context] = makeOffscreenBuffer(this.previewSize, this.previewSize, {
|
||||
smooth: true,
|
||||
label: "layerPeeker",
|
||||
reusable: true,
|
||||
});
|
||||
|
||||
context.clearRect(0, 0, this.previewSize, this.previewSize);
|
||||
this.canvas = canvas;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the canvas to render at the given worldPos and the given camera scale
|
||||
*
|
||||
* @param {Vector} worldPos
|
||||
* @param {number} scale 1 / zoomLevel
|
||||
*/
|
||||
prepareCanvasForPreview(worldPos, scale) {
|
||||
this.context.clearRect(0, 0, this.previewSize, this.previewSize);
|
||||
this.context.fillStyle = THEME.map.wires.previewColor;
|
||||
this.context.fillRect(0, 0, this.previewSize, this.previewSize);
|
||||
|
||||
const dimensions = scale * this.previewSize;
|
||||
|
||||
const startWorldX = worldPos.x - dimensions / 2;
|
||||
const startWorldY = worldPos.y - dimensions / 2;
|
||||
|
||||
const startTileX = Math.floor(startWorldX / globalConfig.tileSize);
|
||||
const startTileY = Math.floor(startWorldY / globalConfig.tileSize);
|
||||
const tileDimensions = Math.ceil(dimensions / globalConfig.tileSize);
|
||||
|
||||
this.context.save();
|
||||
this.context.scale(1 / scale, 1 / scale);
|
||||
this.context.translate(
|
||||
startTileX * globalConfig.tileSize - startWorldX,
|
||||
startTileY * globalConfig.tileSize - startWorldY
|
||||
);
|
||||
|
||||
for (let dx = 0; dx < tileDimensions; ++dx) {
|
||||
for (let dy = 0; dy < tileDimensions; ++dy) {
|
||||
const tileX = dx + startTileX;
|
||||
const tileY = dy + startTileY;
|
||||
|
||||
const content = this.root.map.getLayerContentXY(tileX, tileY, "wires");
|
||||
if (content) {
|
||||
MapChunkView.drawSingleWiresOverviewTile({
|
||||
context: this.context,
|
||||
x: dx * globalConfig.tileSize,
|
||||
y: dy * globalConfig.tileSize,
|
||||
entity: content,
|
||||
tileSizePixels: globalConfig.tileSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.context.restore();
|
||||
this.context.globalCompositeOperation = "destination-in";
|
||||
this.previewOverlay.draw(this.context, 0, 0, this.previewSize, this.previewSize);
|
||||
this.context.globalCompositeOperation = "source-over";
|
||||
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the preview at the given position
|
||||
* @param {import("../../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {Vector} worldPos
|
||||
* @param {number} scale 1 / zoomLevel
|
||||
*/
|
||||
renderPreview(parameters, worldPos, scale) {
|
||||
if (this.root.currentLayer !== "regular") {
|
||||
// Only supporting wires right now
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas = this.prepareCanvasForPreview(worldPos, scale);
|
||||
|
||||
parameters.context.globalAlpha = 0.3;
|
||||
parameters.context.drawImage(
|
||||
canvas,
|
||||
worldPos.x - (scale * this.previewSize) / 2,
|
||||
worldPos.y - (scale * this.previewSize) / 2,
|
||||
scale * this.previewSize,
|
||||
scale * this.previewSize
|
||||
);
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { enumMouseButton } from "../../camera";
|
||||
import { enumLayer } from "../../root";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
export class HUDLeverToggle extends BaseHUDPart {
|
||||
@ -15,7 +14,7 @@ export class HUDLeverToggle extends BaseHUDPart {
|
||||
*/
|
||||
downPreHandler(pos, button) {
|
||||
const tile = this.root.camera.screenToWorld(pos).toTileSpace();
|
||||
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, enumLayer.regular);
|
||||
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, "regular");
|
||||
if (contents) {
|
||||
const leverComp = contents.components.Lever;
|
||||
if (leverComp) {
|
||||
|
@ -78,7 +78,7 @@ export class HUDSandboxController extends BaseHUDPart {
|
||||
if (!this.root.hubGoals.storedShapes[blueprintShape]) {
|
||||
this.root.hubGoals.storedShapes[blueprintShape] = 0;
|
||||
}
|
||||
this.root.hubGoals.storedShapes[blueprintShape] += 1e4;
|
||||
this.root.hubGoals.storedShapes[blueprintShape] += 1e9;
|
||||
}
|
||||
|
||||
maxOutAll() {
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { enumLayer } from "../../root";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { MapChunkView } from "../../map_chunk_view";
|
||||
import { WireNetwork } from "../../systems/wire";
|
||||
import { THEME } from "../../theme";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { Loader } from "../../../core/loader";
|
||||
|
||||
export class HUDWireInfo extends BaseHUDPart {
|
||||
initialize() {}
|
||||
initialize() {
|
||||
this.spriteEmpty = Loader.getSprite("sprites/wires/network_empty.png");
|
||||
this.spriteConflict = Loader.getSprite("sprites/wires/network_conflict.png");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../../../core/draw_utils").DrawParameters} parameters
|
||||
*/
|
||||
drawOverlays(parameters) {
|
||||
if (this.root.currentLayer !== enumLayer.wires) {
|
||||
if (this.root.currentLayer !== "wires") {
|
||||
// Not in the wires layer
|
||||
return;
|
||||
}
|
||||
@ -21,38 +27,93 @@ export class HUDWireInfo extends BaseHUDPart {
|
||||
return;
|
||||
}
|
||||
|
||||
const tile = this.root.camera.screenToWorld(mousePos).toTileSpace();
|
||||
const entity = this.root.map.getLayerContentXY(tile.x, tile.y, enumLayer.wires);
|
||||
const worldPos = this.root.camera.screenToWorld(mousePos);
|
||||
const tile = worldPos.toTileSpace();
|
||||
const entity = this.root.map.getLayerContentXY(tile.x, tile.y, "wires");
|
||||
|
||||
if (entity) {
|
||||
const wireComp = entity.components.Wire;
|
||||
if (wireComp) {
|
||||
const screenTile = this.root.camera.worldToScreen(tile.toWorldSpace());
|
||||
parameters.context.fillStyle = "rgba(0, 0, 0, 0.1)";
|
||||
parameters.context.fillRect(
|
||||
screenTile.x,
|
||||
screenTile.y,
|
||||
globalConfig.tileSize * this.root.camera.zoomLevel,
|
||||
globalConfig.tileSize * this.root.camera.zoomLevel
|
||||
if (!entity) {
|
||||
// No entity
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.root.camera.getIsMapOverlayActive() &&
|
||||
!this.root.logic.getIsEntityIntersectedWithMatrix(entity, worldPos)
|
||||
) {
|
||||
// Detailed intersection check
|
||||
return;
|
||||
}
|
||||
|
||||
const networks = this.root.logic.getEntityWireNetworks(entity, tile);
|
||||
if (networks === null) {
|
||||
// This entity will never be able to be connected
|
||||
return;
|
||||
}
|
||||
|
||||
if (networks.length === 0) {
|
||||
// No network at all
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < networks.length; ++i) {
|
||||
const network = networks[i];
|
||||
this.drawHighlightedNetwork(parameters, network);
|
||||
}
|
||||
|
||||
if (networks.length === 1) {
|
||||
const network = networks[0];
|
||||
|
||||
if (network.valueConflict) {
|
||||
this.spriteConflict.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
|
||||
} else if (!network.currentValue) {
|
||||
this.spriteEmpty.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
|
||||
} else {
|
||||
network.currentValue.drawItemCenteredClipped(
|
||||
mousePos.x + 40,
|
||||
mousePos.y + 10,
|
||||
parameters,
|
||||
60
|
||||
);
|
||||
|
||||
parameters.context.font = "25px GameFont";
|
||||
const network = wireComp.linkedNetwork;
|
||||
if (!network) {
|
||||
parameters.context.fillStyle = "#333";
|
||||
parameters.context.fillText("empty", mousePos.x, mousePos.y);
|
||||
} else {
|
||||
if (network.valueConflict) {
|
||||
parameters.context.fillStyle = "#a10";
|
||||
parameters.context.fillText("conflict", mousePos.x, mousePos.y);
|
||||
} else if (!network.currentValue) {
|
||||
parameters.context.fillStyle = "#333";
|
||||
parameters.context.fillText("empty", mousePos.x, mousePos.y);
|
||||
} else {
|
||||
network.currentValue.draw(mousePos.x + 20, mousePos.y, parameters, 40);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {import("../../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {WireNetwork} network
|
||||
*/
|
||||
drawHighlightedNetwork(parameters, network) {
|
||||
parameters.context.globalAlpha = 0.5;
|
||||
|
||||
for (let i = 0; i < network.wires.length; ++i) {
|
||||
const wire = network.wires[i];
|
||||
const staticComp = wire.components.StaticMapEntity;
|
||||
const screenTile = this.root.camera.worldToScreen(staticComp.origin.toWorldSpace());
|
||||
MapChunkView.drawSingleWiresOverviewTile({
|
||||
context: parameters.context,
|
||||
x: screenTile.x,
|
||||
y: screenTile.y,
|
||||
entity: wire,
|
||||
tileSizePixels: globalConfig.tileSize * this.root.camera.zoomLevel,
|
||||
overrideColor: THEME.map.wires.highlightColor,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < network.tunnels.length; ++i) {
|
||||
const tunnel = network.tunnels[i];
|
||||
const staticComp = tunnel.components.StaticMapEntity;
|
||||
const screenTile = this.root.camera.worldToScreen(staticComp.origin.toWorldSpace());
|
||||
MapChunkView.drawSingleWiresOverviewTile({
|
||||
context: parameters.context,
|
||||
x: screenTile.x,
|
||||
y: screenTile.y,
|
||||
entity: tunnel,
|
||||
tileSizePixels: globalConfig.tileSize * this.root.camera.zoomLevel,
|
||||
overrideColor: THEME.map.wires.highlightColor,
|
||||
});
|
||||
}
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { enumLayer } from "../../root";
|
||||
import { THEME } from "../../theme";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { Loader } from "../../../core/loader";
|
||||
@ -26,10 +25,10 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
||||
* Switches between layers
|
||||
*/
|
||||
switchLayers() {
|
||||
if (this.root.currentLayer === enumLayer.regular) {
|
||||
this.root.currentLayer = enumLayer.wires;
|
||||
if (this.root.currentLayer === "regular") {
|
||||
this.root.currentLayer = "wires";
|
||||
} else {
|
||||
this.root.currentLayer = enumLayer.regular;
|
||||
this.root.currentLayer = "regular";
|
||||
}
|
||||
this.root.signals.editModeChanged.dispatch(this.root.currentLayer);
|
||||
}
|
||||
@ -51,7 +50,7 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
||||
}
|
||||
|
||||
update() {
|
||||
const desiredAlpha = this.root.currentLayer === enumLayer.wires ? 1.0 : 0.0;
|
||||
const desiredAlpha = this.root.currentLayer === "wires" ? 1.0 : 0.0;
|
||||
this.currentAlpha = lerp(this.currentAlpha, desiredAlpha, 0.12);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { enumLayer } from "../../root";
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
import { MetaWireBuilding } from "../../buildings/wire";
|
||||
import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
|
||||
@ -19,7 +18,7 @@ export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||
super(root, {
|
||||
supportedBuildings,
|
||||
visibilityCondition: () =>
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === enumLayer.wires,
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
|
||||
htmlElementId: "ingame_HUD_wires_toolbar",
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GameRoot, enumLayer } from "../root";
|
||||
import { GameRoot } from "../root";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Vector, mixVector } from "../../core/vector";
|
||||
import { lerp } from "../../core/utils";
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem, enumItemType } from "../base_item";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { globalConfig } from "../../core/config";
|
||||
|
||||
export class BooleanItem extends BaseItem {
|
||||
static getId() {
|
||||
@ -20,8 +21,9 @@ export class BooleanItem extends BaseItem {
|
||||
this.value = data;
|
||||
}
|
||||
|
||||
/** @returns {"boolean"} **/
|
||||
getItemType() {
|
||||
return enumItemType.boolean;
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,17 +44,17 @@ export class BooleanItem extends BaseItem {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} size
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(x, y, parameters, size = 12) {
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
let sprite;
|
||||
if (this.value) {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
|
||||
} else {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_false.png");
|
||||
}
|
||||
sprite.drawCachedCentered(parameters, x, y, size * 1.5);
|
||||
sprite.drawCachedCentered(parameters, x, y, diameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,10 @@ import { globalConfig } from "../../core/config";
|
||||
import { smoothenDpi } from "../../core/dpi_manager";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem, enumItemType } from "../base_item";
|
||||
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() {
|
||||
@ -23,8 +24,9 @@ export class ColorItem extends BaseItem {
|
||||
this.color = data;
|
||||
}
|
||||
|
||||
/** @returns {"color"} **/
|
||||
getItemType() {
|
||||
return enumItemType.color;
|
||||
return "color";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,26 +52,36 @@ export class ColorItem extends BaseItem {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} size
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(x, y, parameters, size = 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 = size + "/" + dpi;
|
||||
const key = realDiameter + "/" + dpi + "/" + this.color;
|
||||
const canvas = parameters.root.buffers.getForKey({
|
||||
key,
|
||||
subKey: this.color,
|
||||
w: size,
|
||||
h: size,
|
||||
key: "coloritem",
|
||||
subKey: key,
|
||||
w: realDiameter,
|
||||
h: realDiameter,
|
||||
dpi,
|
||||
redrawMethod: this.bufferGenerator,
|
||||
});
|
||||
parameters.context.drawImage(canvas, x - size / 2, y - size / 2, size, size);
|
||||
|
||||
drawSpriteClipped({
|
||||
parameters,
|
||||
sprite: canvas,
|
||||
x: x - realDiameter / 2,
|
||||
y: y - realDiameter / 2,
|
||||
w: realDiameter,
|
||||
h: realDiameter,
|
||||
originalW: realDiameter * dpi,
|
||||
originalH: realDiameter * dpi,
|
||||
});
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem, enumItemType } from "../base_item";
|
||||
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() {
|
||||
@ -21,8 +22,9 @@ export class ShapeItem extends BaseItem {
|
||||
this.definition = ShapeDefinition.fromShortKey(data);
|
||||
}
|
||||
|
||||
/** @returns {"shape"} **/
|
||||
getItemType() {
|
||||
return enumItemType.shape;
|
||||
return "shape";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,9 +54,9 @@ export class ShapeItem extends BaseItem {
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} size
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
draw(x, y, parameters, size) {
|
||||
this.definition.draw(x, y, parameters, size);
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
this.definition.drawCentered(x, y, parameters, diameter);
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,14 @@ export const KEYMAPPINGS = {
|
||||
painter: { keyCode: key("9") },
|
||||
trash: { keyCode: key("0") },
|
||||
|
||||
lever: { keyCode: key("L") },
|
||||
filter: { keyCode: key("B") },
|
||||
display: { keyCode: key("N") },
|
||||
|
||||
wire: { keyCode: key("1") },
|
||||
wire_tunnel: { keyCode: key("2") },
|
||||
constant_signal: { keyCode: key("3") },
|
||||
logic_gate: { keyCode: key("4") },
|
||||
lever: { keyCode: key("5") },
|
||||
filter: { keyCode: key("6") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
|
@ -1,23 +1,20 @@
|
||||
import { createLogger } from "../core/logging";
|
||||
import { STOP_PROPAGATION } from "../core/signal";
|
||||
import { round2Digits } from "../core/utils";
|
||||
import {
|
||||
enumDirection,
|
||||
enumDirectionToAngle,
|
||||
enumDirectionToVector,
|
||||
enumInvertedDirections,
|
||||
Vector,
|
||||
} from "../core/vector";
|
||||
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
|
||||
import { getBuildingDataFromCode } from "./building_codes";
|
||||
import { Entity } from "./entity";
|
||||
import { MetaBuilding } from "./meta_building";
|
||||
import { enumLayer, GameRoot } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { WireNetwork } from "./systems/wire";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { CHUNK_OVERLAY_RES } from "./map_chunk_view";
|
||||
|
||||
const logger = createLogger("ingame/logic");
|
||||
|
||||
/** @enum {number} */
|
||||
export const enumWireEdgeFlag = {
|
||||
empty: 0,
|
||||
filled: 1,
|
||||
connected: 2,
|
||||
};
|
||||
|
||||
@ -215,15 +212,97 @@ export class GameLogic {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (neighbourStatus === enumWireEdgeFlag.filled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (neighbourStatus === enumWireEdgeFlag.connected) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all wire networks this entity participates in on the given tile
|
||||
* @param {Entity} entity
|
||||
* @param {Vector} tile
|
||||
* @returns {Array<WireNetwork>|null} Null if the entity is never able to be connected at the given tile
|
||||
*/
|
||||
getEntityWireNetworks(entity, tile) {
|
||||
let canConnectAtAll = false;
|
||||
|
||||
/** @type {Set<WireNetwork>} */
|
||||
const networks = new Set();
|
||||
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const wireComp = entity.components.Wire;
|
||||
if (wireComp) {
|
||||
canConnectAtAll = true;
|
||||
if (wireComp.linkedNetwork) {
|
||||
networks.add(wireComp.linkedNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
const tunnelComp = entity.components.WireTunnel;
|
||||
if (tunnelComp) {
|
||||
canConnectAtAll = true;
|
||||
for (let i = 0; i < tunnelComp.linkedNetworks.length; ++i) {
|
||||
networks.add(tunnelComp.linkedNetworks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
if (pinsComp) {
|
||||
const slots = pinsComp.slots;
|
||||
for (let i = 0; i < slots.length; ++i) {
|
||||
const slot = slots[i];
|
||||
const slotLocalPos = staticComp.localTileToWorld(slot.pos);
|
||||
if (slotLocalPos.equals(tile)) {
|
||||
canConnectAtAll = true;
|
||||
if (slot.linkedNetwork) {
|
||||
networks.add(slot.linkedNetwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canConnectAtAll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Array.from(networks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the entities tile *and* his overlay matrix is intersected
|
||||
* @param {Entity} entity
|
||||
* @param {Vector} worldPos
|
||||
*/
|
||||
getIsEntityIntersectedWithMatrix(entity, worldPos) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const tile = worldPos.toTileSpace();
|
||||
|
||||
if (!staticComp.getTileSpaceBounds().containsPoint(tile.x, tile.y)) {
|
||||
// No intersection at all
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getBuildingDataFromCode(staticComp.code);
|
||||
const overlayMatrix = data.metaInstance.getSpecialOverlayRenderMatrix(
|
||||
staticComp.rotation,
|
||||
data.rotationVariant,
|
||||
data.variant,
|
||||
entity
|
||||
);
|
||||
// Always the same
|
||||
if (!overlayMatrix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const localPosition = worldPos
|
||||
.divideScalar(globalConfig.tileSize)
|
||||
.modScalar(1)
|
||||
.multiplyScalar(CHUNK_OVERLAY_RES)
|
||||
.floor();
|
||||
|
||||
return !!overlayMatrix[localPosition.x + localPosition.y * 3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag at the given tile
|
||||
* @param {Vector} tile
|
||||
@ -268,15 +347,26 @@ export class GameLogic {
|
||||
}
|
||||
|
||||
// Now check if there's a connectable wire
|
||||
const targetEntity = this.root.map.getTileContent(tile, enumLayer.wires);
|
||||
const targetEntity = this.root.map.getTileContent(tile, "wires");
|
||||
if (!targetEntity) {
|
||||
return enumWireEdgeFlag.empty;
|
||||
}
|
||||
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
|
||||
// Check if its a crossing
|
||||
const wireTunnelComp = targetEntity.components.WireTunnel;
|
||||
if (wireTunnelComp) {
|
||||
return enumWireEdgeFlag.filled;
|
||||
// Check if the crossing is connected
|
||||
if (wireTunnelComp.multipleDirections) {
|
||||
return enumWireEdgeFlag.connected;
|
||||
} else {
|
||||
// Its a coating, check if it matches the direction
|
||||
const referenceDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
|
||||
return referenceDirection === edge || enumInvertedDirections[referenceDirection] === edge
|
||||
? enumWireEdgeFlag.connected
|
||||
: enumWireEdgeFlag.empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if its a wire
|
||||
@ -285,15 +375,9 @@ export class GameLogic {
|
||||
return enumWireEdgeFlag.empty;
|
||||
}
|
||||
|
||||
const refAngle = enumDirectionToAngle[edge];
|
||||
const refRotation = targetEntity.components.StaticMapEntity.originalRotation;
|
||||
const canConnectRemotely = refRotation === refAngle || (refRotation + 180) % 360 === refAngle;
|
||||
|
||||
// Check if the wire points towards the right direction
|
||||
if (!canConnectRemotely) {
|
||||
// Seems its not the right direction - well, still its filled
|
||||
return enumWireEdgeFlag.filled;
|
||||
}
|
||||
// const refAngle = enumDirectionToAngle[edge];
|
||||
// const refRotation = targetEntity.components.StaticMapEntity.originalRotation;
|
||||
// const canConnectRemotely = refRotation === refAngle || (refRotation + 180) % 360 === refAngle;
|
||||
|
||||
// Actually connected
|
||||
return enumWireEdgeFlag.connected;
|
||||
@ -317,7 +401,7 @@ export class GameLogic {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entity = this.root.map.getLayerContentXY(tile.x + dx, tile.y + dy, enumLayer.regular);
|
||||
const entity = this.root.map.getLayerContentXY(tile.x + dx, tile.y + dy, "regular");
|
||||
if (entity) {
|
||||
let ejectorSlots = [];
|
||||
let acceptorSlots = [];
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Entity } from "./entity";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { MapChunkView } from "./map_chunk_view";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
|
||||
const logger = createLogger("map");
|
||||
import { BaseItem } from "./base_item";
|
||||
import { Entity } from "./entity";
|
||||
import { MapChunkView } from "./map_chunk_view";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
export class BaseMap extends BasicSerializableObject {
|
||||
static getId() {
|
||||
@ -97,7 +94,7 @@ export class BaseMap extends BasicSerializableObject {
|
||||
/**
|
||||
* Returns the tile content of a given tile
|
||||
* @param {Vector} tile
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getTileContent(tile, layer) {
|
||||
@ -122,7 +119,7 @@ export class BaseMap extends BasicSerializableObject {
|
||||
* Returns the tile content of a given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getLayerContentXY(x, y, layer) {
|
||||
@ -147,7 +144,7 @@ export class BaseMap extends BasicSerializableObject {
|
||||
/**
|
||||
* Checks if the tile is used
|
||||
* @param {Vector} tile
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsed(tile, layer) {
|
||||
@ -162,7 +159,7 @@ export class BaseMap extends BasicSerializableObject {
|
||||
* Checks if the tile is used
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsedXY(x, y, layer) {
|
||||
|
@ -7,8 +7,9 @@ import { BaseItem } from "./base_item";
|
||||
import { enumColors } from "./colors";
|
||||
import { Entity } from "./entity";
|
||||
import { COLOR_ITEM_SINGLETONS } from "./items/color_item";
|
||||
import { enumLayer, GameRoot } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { enumSubShape } from "./shape_definition";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
|
||||
const logger = createLogger("map_chunk");
|
||||
|
||||
@ -26,25 +27,54 @@ export class MapChunk {
|
||||
this.tileX = x * globalConfig.mapChunkSize;
|
||||
this.tileY = y * globalConfig.mapChunkSize;
|
||||
|
||||
/** @type {Array<Array<?Entity>>} */
|
||||
/**
|
||||
* Stores the contents of the lower (= map resources) layer
|
||||
* @type {Array<Array<?BaseItem>>}
|
||||
*/
|
||||
this.lowerLayer = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
|
||||
|
||||
/**
|
||||
* Stores the contents of the regular layer
|
||||
* @type {Array<Array<?Entity>>}
|
||||
*/
|
||||
this.contents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
|
||||
|
||||
/** @type {Array<Array<?Entity>>} */
|
||||
/**
|
||||
* Stores the contents of the wires layer
|
||||
* @type {Array<Array<?Entity>>}
|
||||
*/
|
||||
this.wireContents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
|
||||
|
||||
/** @type {Array<Array<?BaseItem>>} */
|
||||
this.lowerLayer = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
|
||||
|
||||
/** @type {Array<Entity>} */
|
||||
this.containedEntities = [];
|
||||
|
||||
/**
|
||||
* World space rectangle, can be used for culling
|
||||
*/
|
||||
this.worldSpaceRectangle = new Rectangle(
|
||||
this.tileX * globalConfig.tileSize,
|
||||
this.tileY * globalConfig.tileSize,
|
||||
globalConfig.mapChunkWorldSize,
|
||||
globalConfig.mapChunkWorldSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Tile space rectangle, can be used for culling
|
||||
*/
|
||||
this.tileSpaceRectangle = new Rectangle(
|
||||
this.tileX,
|
||||
this.tileY,
|
||||
globalConfig.mapChunkSize,
|
||||
globalConfig.mapChunkSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Which entities this chunk contains, sorted by layer
|
||||
* @type {Object<enumLayer, Array<Entity>>}
|
||||
* @type {Record<Layer, Array<Entity>>}
|
||||
*/
|
||||
this.containedEntitiesByLayer = {
|
||||
[enumLayer.regular]: [],
|
||||
[enumLayer.wires]: [],
|
||||
regular: [],
|
||||
wires: [],
|
||||
};
|
||||
|
||||
/**
|
||||
@ -332,7 +362,7 @@ export class MapChunk {
|
||||
* Returns the contents of this chunk from the given world space coordinates
|
||||
* @param {number} worldX
|
||||
* @param {number} worldY
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity=}
|
||||
*/
|
||||
getLayerContentFromWorldCoords(worldX, worldY, layer) {
|
||||
@ -342,7 +372,7 @@ export class MapChunk {
|
||||
assert(localY >= 0, "Local Y is < 0");
|
||||
assert(localX < globalConfig.mapChunkSize, "Local X is >= chunk size");
|
||||
assert(localY < globalConfig.mapChunkSize, "Local Y is >= chunk size");
|
||||
if (layer === enumLayer.regular) {
|
||||
if (layer === "regular") {
|
||||
return this.contents[localX][localY] || null;
|
||||
} else {
|
||||
return this.wireContents[localX][localY] || null;
|
||||
@ -395,7 +425,7 @@ export class MapChunk {
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @param {Entity=} contents
|
||||
* @param {enumLayer} layer
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
setLayerContentFromWorldCords(tileX, tileY, contents, layer) {
|
||||
const localX = tileX - this.tileX;
|
||||
@ -406,7 +436,7 @@ export class MapChunk {
|
||||
assert(localY < globalConfig.mapChunkSize, "Local Y is >= chunk size");
|
||||
|
||||
let oldContents;
|
||||
if (layer === enumLayer.regular) {
|
||||
if (layer === "regular") {
|
||||
oldContents = this.contents[localX][localY];
|
||||
} else {
|
||||
oldContents = this.wireContents[localX][localY];
|
||||
@ -420,7 +450,7 @@ export class MapChunk {
|
||||
fastArrayDeleteValueIfContained(this.containedEntitiesByLayer[layer], oldContents);
|
||||
}
|
||||
|
||||
if (layer === enumLayer.regular) {
|
||||
if (layer === "regular") {
|
||||
this.contents[localX][localY] = contents;
|
||||
} else {
|
||||
this.wireContents[localX][localY] = contents;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { MapChunk } from "./map_chunk";
|
||||
import { GameRoot, enumLayer } from "./root";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { smoothenDpi } from "../core/dpi_manager";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { THEME } from "./theme";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { getBuildingDataFromCode } from "./building_codes";
|
||||
import { Entity } from "./entity";
|
||||
import { MapChunk } from "./map_chunk";
|
||||
import { GameRoot } from "./root";
|
||||
import { THEME } from "./theme";
|
||||
import { drawSpriteClipped } from "../core/draw_utils";
|
||||
|
||||
const CHUNK_OVERLAY_RES = 3;
|
||||
export const CHUNK_OVERLAY_RES = 3;
|
||||
|
||||
export class MapChunkView extends MapChunk {
|
||||
/**
|
||||
@ -41,6 +42,7 @@ export class MapChunkView extends MapChunk {
|
||||
drawBackgroundLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
systems.mapResources.drawChunk(parameters, this);
|
||||
systems.beltUnderlays.drawChunk(parameters, this);
|
||||
systems.belt.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
@ -50,9 +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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,11 +69,12 @@ export class MapChunkView extends MapChunk {
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawOverlay(parameters) {
|
||||
const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
|
||||
const sprite = this.root.buffers.getForKey({
|
||||
key: "chunk@" + this.root.currentLayer,
|
||||
subKey: this.renderKey,
|
||||
w: globalConfig.mapChunkSize * CHUNK_OVERLAY_RES,
|
||||
h: globalConfig.mapChunkSize * CHUNK_OVERLAY_RES,
|
||||
w: overlaySize,
|
||||
h: overlaySize,
|
||||
dpi: 1,
|
||||
redrawMethod: this.generateOverlayBuffer.bind(this),
|
||||
});
|
||||
@ -73,20 +83,29 @@ export class MapChunkView extends MapChunk {
|
||||
|
||||
// Draw chunk "pixel" art
|
||||
parameters.context.imageSmoothingEnabled = false;
|
||||
parameters.context.drawImage(sprite, this.x * dims, this.y * dims, dims, dims);
|
||||
drawSpriteClipped({
|
||||
parameters,
|
||||
sprite,
|
||||
x: this.x * dims,
|
||||
y: this.y * dims,
|
||||
w: dims,
|
||||
h: dims,
|
||||
originalW: overlaySize,
|
||||
originalH: overlaySize,
|
||||
});
|
||||
|
||||
parameters.context.imageSmoothingEnabled = true;
|
||||
|
||||
// Draw patch items
|
||||
if (this.root.currentLayer === enumLayer.regular) {
|
||||
if (this.root.currentLayer === "regular") {
|
||||
for (let i = 0; i < this.patches.length; ++i) {
|
||||
const patch = this.patches[i];
|
||||
|
||||
patch.item.draw(
|
||||
this.x * dims + patch.pos.x * globalConfig.tileSize,
|
||||
this.y * dims + patch.pos.y * globalConfig.tileSize,
|
||||
parameters,
|
||||
Math.min(80, 30 / parameters.zoomLevel)
|
||||
);
|
||||
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
|
||||
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
|
||||
const diameter = Math.min(80, 30 / parameters.zoomLevel);
|
||||
|
||||
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,7 +197,7 @@ export class MapChunkView extends MapChunk {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.root.currentLayer === enumLayer.wires) {
|
||||
if (this.root.currentLayer === "wires") {
|
||||
// Draw wires overlay
|
||||
|
||||
context.fillStyle = THEME.map.wires.overlayColor;
|
||||
@ -191,46 +210,54 @@ export class MapChunkView extends MapChunk {
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
const staticComp = content.components.StaticMapEntity;
|
||||
const data = getBuildingDataFromCode(staticComp.code);
|
||||
const metaBuilding = data.metaInstance;
|
||||
MapChunkView.drawSingleWiresOverviewTile({
|
||||
context,
|
||||
x: x * CHUNK_OVERLAY_RES,
|
||||
y: y * CHUNK_OVERLAY_RES,
|
||||
entity: content,
|
||||
tileSizePixels: CHUNK_OVERLAY_RES,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
|
||||
staticComp.rotation,
|
||||
data.rotationVariant,
|
||||
data.variant,
|
||||
content
|
||||
);
|
||||
|
||||
context.fillStyle = metaBuilding.getSilhouetteColor();
|
||||
if (overlayMatrix) {
|
||||
for (let dx = 0; dx < 3; ++dx) {
|
||||
for (let dy = 0; dy < 3; ++dy) {
|
||||
const isFilled = overlayMatrix[dx + dy * 3];
|
||||
if (isFilled) {
|
||||
context.fillRect(
|
||||
x * CHUNK_OVERLAY_RES + dx,
|
||||
y * CHUNK_OVERLAY_RES + dy,
|
||||
1,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {CanvasRenderingContext2D} param0.context
|
||||
* @param {number} param0.x
|
||||
* @param {number} param0.y
|
||||
* @param {Entity} param0.entity
|
||||
* @param {number} param0.tileSizePixels
|
||||
* @param {string=} param0.overrideColor Optionally override the color to be rendered
|
||||
*/
|
||||
static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const data = getBuildingDataFromCode(staticComp.code);
|
||||
const metaBuilding = data.metaInstance;
|
||||
const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
|
||||
staticComp.rotation,
|
||||
data.rotationVariant,
|
||||
data.variant,
|
||||
entity
|
||||
);
|
||||
context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
|
||||
if (overlayMatrix) {
|
||||
for (let dx = 0; dx < 3; ++dx) {
|
||||
for (let dy = 0; dy < 3; ++dy) {
|
||||
const isFilled = overlayMatrix[dx + dy * 3];
|
||||
if (isFilled) {
|
||||
context.fillRect(
|
||||
x * CHUNK_OVERLAY_RES,
|
||||
y * CHUNK_OVERLAY_RES,
|
||||
CHUNK_OVERLAY_RES,
|
||||
CHUNK_OVERLAY_RES
|
||||
x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES,
|
||||
y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES,
|
||||
tileSizePixels / CHUNK_OVERLAY_RES,
|
||||
tileSizePixels / CHUNK_OVERLAY_RES
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.fillRect(x, y, tileSizePixels, tileSizePixels);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,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);
|
||||
}
|
||||
}
|
||||
|
@ -140,23 +140,23 @@ export class MapView extends BaseMap {
|
||||
* @param {function} method
|
||||
*/
|
||||
drawVisibleChunks(parameters, method) {
|
||||
const cullRange = parameters.visibleRect.toTileCullRectangle();
|
||||
const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
|
||||
const top = cullRange.top();
|
||||
const right = cullRange.right();
|
||||
const bottom = cullRange.bottom();
|
||||
const left = cullRange.left();
|
||||
|
||||
const border = 1;
|
||||
const border = 0;
|
||||
const minY = top - border;
|
||||
const maxY = bottom + border;
|
||||
const minX = left - border;
|
||||
const maxX = right + border - 1;
|
||||
const maxX = right + border;
|
||||
|
||||
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 chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
|
||||
const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
|
||||
|
||||
// Render y from top down for proper blending
|
||||
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
|
||||
@ -230,7 +230,6 @@ export class MapView extends BaseMap {
|
||||
const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
|
||||
const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
|
||||
|
||||
// Render y from top down for proper blending
|
||||
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
|
||||
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
|
||||
parameters.context.fillStyle = "#ffaaaa";
|
||||
|
@ -4,7 +4,7 @@ import { Vector } from "../core/vector";
|
||||
import { SOUNDS } from "../platform/sound";
|
||||
import { StaticMapEntityComponent } from "./components/static_map_entity";
|
||||
import { Entity } from "./entity";
|
||||
import { enumLayer, GameRoot } from "./root";
|
||||
import { GameRoot } from "./root";
|
||||
import { getCodeFromBuildingData } from "./building_codes";
|
||||
|
||||
export const defaultBuildingVariant = "default";
|
||||
@ -27,10 +27,10 @@ export class MetaBuilding {
|
||||
|
||||
/**
|
||||
* Returns the edit layer of the building
|
||||
* @returns {enumLayer}
|
||||
* @returns {Layer}
|
||||
*/
|
||||
getLayer() {
|
||||
return enumLayer.regular;
|
||||
return "regular";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,6 +91,13 @@ export class MetaBuilding {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show a preview of the wires layer when placing the building
|
||||
*/
|
||||
getShowWiresLayerPreview() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to rotate automatically in the dragging direction while placing
|
||||
* @param {string} variant
|
||||
@ -229,7 +236,7 @@ export class MetaBuilding {
|
||||
* @param {Vector} param0.tile
|
||||
* @param {number} param0.rotation
|
||||
* @param {string} param0.variant
|
||||
* @param {string} param0.layer
|
||||
* @param {Layer} param0.layer
|
||||
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }}
|
||||
*/
|
||||
computeOptimalDirectionAndRotationVariantAtTile({ root, tile, rotation, variant, layer }) {
|
||||
|
@ -19,7 +19,8 @@ import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
|
||||
import { MetaLeverBuilding } from "./buildings/lever";
|
||||
import { MetaFilterBuilding } from "./buildings/filter";
|
||||
import { MetaWireTunnelBuilding } from "./buildings/wire_tunnel";
|
||||
import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
|
||||
import { MetaDisplayBuilding } from "./buildings/display";
|
||||
|
||||
const logger = createLogger("building_registry");
|
||||
|
||||
@ -41,6 +42,7 @@ export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaLeverBuilding);
|
||||
gMetaBuildingRegistry.register(MetaFilterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
|
||||
gMetaBuildingRegistry.register(MetaDisplayBuilding);
|
||||
|
||||
// Belt
|
||||
registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
|
||||
@ -114,6 +116,10 @@ export function initMetaBuildingRegistry() {
|
||||
|
||||
// Wire tunnel
|
||||
registerBuildingVariant(39, MetaWireTunnelBuilding);
|
||||
registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
|
||||
|
||||
// Display
|
||||
registerBuildingVariant(40, MetaDisplayBuilding);
|
||||
|
||||
// Propagate instances
|
||||
for (const key in gBuildingVariants) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { GameRoot } from "./root";
|
||||
import { ShapeDefinition } from "./shape_definition";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { BaseItem, enumItemType } from "./base_item";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { ShapeItem } from "./items/shape_item";
|
||||
import { BasicSerializableObject } from "../savegame/serialization";
|
||||
|
||||
@ -53,7 +53,7 @@ export class ProductionAnalytics extends BasicSerializableObject {
|
||||
* @param {BaseItem} item
|
||||
*/
|
||||
onItemProduced(item) {
|
||||
if (item.getItemType() === enumItemType.shape) {
|
||||
if (item.getItemType() === "shape") {
|
||||
const definition = /** @type {ShapeItem} */ (item).definition;
|
||||
const key = definition.getHash();
|
||||
const entry = this.history[enumAnalyticsDataSource.produced];
|
||||
|
@ -1,5 +1,4 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import { Signal } from "../core/signal";
|
||||
import { RandomNumberGenerator } from "../core/rng";
|
||||
import { createLogger } from "../core/logging";
|
||||
@ -32,14 +31,8 @@ import { Vector } from "../core/vector";
|
||||
|
||||
const logger = createLogger("game/root");
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumLayer = {
|
||||
regular: "regular",
|
||||
wires: "wires",
|
||||
};
|
||||
|
||||
/** @type {Array<enumLayer>} */
|
||||
export const arrayLayers = [enumLayer.regular, enumLayer.wires];
|
||||
/** @type {Array<Layer>} */
|
||||
export const layers = ["regular", "wires"];
|
||||
|
||||
/**
|
||||
* The game root is basically the whole game state at a given point,
|
||||
@ -134,8 +127,8 @@ export class GameRoot {
|
||||
/** @type {DynamicTickrate} */
|
||||
this.dynamicTickrate = null;
|
||||
|
||||
/** @type {enumLayer} */
|
||||
this.currentLayer = enumLayer.regular;
|
||||
/** @type {Layer} */
|
||||
this.currentLayer = "regular";
|
||||
|
||||
this.signals = {
|
||||
// Entities
|
||||
@ -156,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()),
|
||||
|
||||
@ -167,7 +162,7 @@ export class GameRoot {
|
||||
|
||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
editModeChanged: /** @type {TypedSignal<[enumLayer]>} */ (new Signal()),
|
||||
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
||||
|
||||
// Called to check if an entity can be placed, second parameter is an additional offset.
|
||||
// Use to introduce additional placement checks
|
||||
|
@ -2,16 +2,11 @@ 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";
|
||||
import { THEME } from "./theme";
|
||||
|
||||
const rusha = require("rusha");
|
||||
|
||||
const logger = createLogger("shape_definition");
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* subShape: enumSubShape,
|
||||
@ -280,24 +275,25 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
draw(x, y, parameters, size = 20) {
|
||||
drawCentered(x, y, parameters, diameter = 20) {
|
||||
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
|
||||
|
||||
if (!this.bufferGenerator) {
|
||||
this.bufferGenerator = this.internalGenerateShapeBuffer.bind(this);
|
||||
}
|
||||
|
||||
const key = size + "/" + dpi;
|
||||
const key = diameter + "/" + dpi + "/" + this.cachedHash;
|
||||
const canvas = parameters.root.buffers.getForKey({
|
||||
key,
|
||||
subKey: this.cachedHash,
|
||||
w: size,
|
||||
h: size,
|
||||
key: "shapedef",
|
||||
subKey: key,
|
||||
w: diameter,
|
||||
h: diameter,
|
||||
dpi,
|
||||
redrawMethod: this.bufferGenerator,
|
||||
});
|
||||
parameters.context.drawImage(canvas, x - size / 2, y - size / 2, size, size);
|
||||
parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -495,17 +495,15 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
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;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
if (entity.components.Belt) {
|
||||
const direction = entity.components.Belt.direction;
|
||||
const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT];
|
||||
|
||||
if (entity && 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ import { drawRotatedSprite } from "../../core/draw_utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { enumDirectionToAngle } from "../../core/vector";
|
||||
import { BeltUnderlaysComponent } from "../components/belt_underlays";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BELT_ANIM_COUNT } from "./belt";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
|
||||
export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -19,49 +20,65 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the acceptor underlays
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* Draws a given chunk
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawUnderlays(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityUnderlays(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const underlayComp = entity.components.BeltUnderlays;
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawChunk(parameters, chunk) {
|
||||
// Limit speed to avoid belts going backwards
|
||||
const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(), 10);
|
||||
|
||||
const underlays = underlayComp.underlays;
|
||||
for (let i = 0; i < underlays.length; ++i) {
|
||||
const { pos, direction } = underlays[i];
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const underlayComp = entity.components.BeltUnderlays;
|
||||
if (!underlayComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const transformedPos = staticComp.localTileToWorld(pos);
|
||||
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const underlays = underlayComp.underlays;
|
||||
for (let i = 0; i < underlays.length; ++i) {
|
||||
const { pos, direction } = underlays[i];
|
||||
const transformedPos = staticComp.localTileToWorld(pos);
|
||||
|
||||
// SYNC with systems/belt.js:drawSingleEntity!
|
||||
const animationIndex = Math.floor(
|
||||
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
// Culling
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
|
||||
x: (transformedPos.x + 0.5) * globalConfig.tileSize,
|
||||
y: (transformedPos.y + 0.5) * globalConfig.tileSize,
|
||||
angle: Math.radians(angle),
|
||||
size: globalConfig.tileSize,
|
||||
});
|
||||
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!
|
||||
const animationIndex = Math.floor(
|
||||
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
|
||||
x: destX + globalConfig.halfTileSize,
|
||||
y: destY + globalConfig.halfTileSize,
|
||||
angle: Math.radians(angle),
|
||||
size: globalConfig.tileSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
99
src/js/game/systems/display.js
Normal file
@ -0,0 +1,99 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { BooleanItem } from "../items/boolean_item";
|
||||
|
||||
export class DisplaySystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [DisplayComponent]);
|
||||
|
||||
/** @type {Object<string, import("../../core/draw_utils").AtlasSprite>} */
|
||||
this.displaySprites = {};
|
||||
|
||||
for (const colorId in enumColors) {
|
||||
if (colorId === enumColors.uncolored) {
|
||||
continue;
|
||||
}
|
||||
this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color / value a display should show
|
||||
* @param {BaseItem} value
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
getDisplayItem(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (value.getItemType()) {
|
||||
case "boolean": {
|
||||
return /** @type {BooleanItem} */ (value).value
|
||||
? COLOR_ITEM_SINGLETONS[enumColors.white]
|
||||
: null;
|
||||
}
|
||||
|
||||
case "color": {
|
||||
const item = /**@type {ColorItem} */ (value);
|
||||
return item.color === enumColors.uncolored ? null : item;
|
||||
}
|
||||
|
||||
case "shape": {
|
||||
return value;
|
||||
}
|
||||
|
||||
default:
|
||||
assertAlways(false, "Unknown item type: " + value.getItemType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a given chunk
|
||||
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const origin = entity.components.StaticMapEntity.origin;
|
||||
if (value.getItemType() === "color") {
|
||||
this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered(
|
||||
parameters,
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
globalConfig.tileSize
|
||||
);
|
||||
} else if (value.getItemType() === "shape") {
|
||||
value.drawItemCenteredClipped(
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
30
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.draw(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,9 +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 { enumLayer } from "../root";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -39,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));
|
||||
}
|
||||
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 (!acceptorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityRegularLayer(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const acceptorComp = entity.components.ItemAcceptor;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
|
||||
animIndex
|
||||
];
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
const slotData = acceptorComp.slots[slotIndex];
|
||||
const realSlotPos = staticComp.localTileToWorld(slotData.pos);
|
||||
|
||||
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
|
||||
animIndex
|
||||
];
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
|
||||
// Not within this chunk
|
||||
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);
|
||||
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
|
||||
const finalTile = slotWorldPos.subScalars(
|
||||
fadeOutDirection.x * (animProgress / 2 - 0.5),
|
||||
fadeOutDirection.y * (animProgress / 2 - 0.5)
|
||||
);
|
||||
item.draw(
|
||||
(finalTile.x + 0.5) * globalConfig.tileSize,
|
||||
(finalTile.y + 0.5) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
item.drawItemCenteredClipped(
|
||||
(finalTile.x + 0.5) * globalConfig.tileSize,
|
||||
(finalTile.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|