1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Merge remote-tracking branch 'upstream/master' into logic-pain-fix

This commit is contained in:
dengr1065 2020-08-16 16:59:51 +03:00
commit 55ad849b83
118 changed files with 3084 additions and 1795 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 238 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

After

Width:  |  Height:  |  Size: 567 KiB

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -318,9 +318,9 @@ input {
canvas {
pointer-events: all;
image-rendering: pixelated;
// image-rendering: pixelated;
// &.smoothed {
// }
// }1
// &.unsmoothed {
// }
letter-spacing: 0 !important;

View File

@ -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"] {

View File

@ -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;
}
/**

View File

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

View File

@ -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 */
};

View File

@ -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);
}
}
}

View File

@ -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
);
}

View File

@ -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)
);
}
}

View File

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

View File

@ -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;
}
}
}

View File

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

View File

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

View File

@ -1,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;

View File

@ -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];

View File

@ -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;
}

View File

@ -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 }) {

View File

@ -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() {

View File

@ -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",
},
],
})

View 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());
}
}

View File

@ -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

View File

@ -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",
},
],
})

View File

@ -35,6 +35,10 @@ export class MetaLeverBuilding extends MetaBuilding {
return null;
}
getShowWiresLayerPreview() {
return true;
}
/**
* Creates the entity at the given location
* @param {Entity} entity

View File

@ -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() {

View File

@ -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",
},
],
})

View File

@ -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",
},
]);

View File

@ -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",
},
],
})

View File

@ -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",
},
],
})

View File

@ -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) {

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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

View File

@ -0,0 +1,11 @@
import { Component } from "../component";
export class DisplayComponent extends Component {
static getId() {
return "Display";
}
duplicateWithoutContents() {
return new DisplayComponent();
}
}

View File

@ -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,
});
}

View File

@ -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);
}

View File

@ -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()

View File

@ -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 = [];
}
}

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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 */
}
}

View File

@ -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");
}

View File

@ -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
*/

View File

@ -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";

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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",
});
}

View File

@ -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;
}
}

View File

@ -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,

View 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;
}
}

View File

@ -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) {

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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",
});
}

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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,
});
}
/**
*

View File

@ -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);
}
}

View File

@ -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: {

View File

@ -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 = [];

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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";

View File

@ -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 }) {

View File

@ -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) {

View File

@ -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];

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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);
}
}
}

View File

@ -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,
});
}
}
}
}

View 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
);
}
}
}
}
}

View File

@ -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,
});
}
}

View File

@ -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
);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More