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

Merge pull request #1 from jimmykarns/master

update removed enum layer with tests and bridge reserves
This commit is contained in:
Jimmy Karns 2020-08-23 10:10:57 -04:00 committed by GitHub
commit e48f8c0449
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 7489 additions and 6808 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 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.2 MiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 254 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 567 KiB

After

Width:  |  Height:  |  Size: 601 KiB

View File

@ -277,6 +277,7 @@
<key type="filename">sprites/blueprints/underground_belt_entry.png</key> <key type="filename">sprites/blueprints/underground_belt_entry.png</key>
<key type="filename">sprites/blueprints/underground_belt_exit-tier2.png</key> <key type="filename">sprites/blueprints/underground_belt_exit-tier2.png</key>
<key type="filename">sprites/blueprints/underground_belt_exit.png</key> <key type="filename">sprites/blueprints/underground_belt_exit.png</key>
<key type="filename">sprites/blueprints/wire_tunnel-coating.png</key>
<key type="filename">sprites/blueprints/wire_tunnel.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/constant_signal.png</key>
<key type="filename">sprites/buildings/display.png</key> <key type="filename">sprites/buildings/display.png</key>
@ -295,6 +296,7 @@
<key type="filename">sprites/buildings/underground_belt_entry.png</key> <key type="filename">sprites/buildings/underground_belt_entry.png</key>
<key type="filename">sprites/buildings/underground_belt_exit-tier2.png</key> <key type="filename">sprites/buildings/underground_belt_exit-tier2.png</key>
<key type="filename">sprites/buildings/underground_belt_exit.png</key> <key type="filename">sprites/buildings/underground_belt_exit.png</key>
<key type="filename">sprites/buildings/wire_tunnel-coating.png</key>
<key type="filename">sprites/buildings/wire_tunnel.png</key> <key type="filename">sprites/buildings/wire_tunnel.png</key>
<key type="filename">sprites/wires/lever_on.png</key> <key type="filename">sprites/wires/lever_on.png</key>
<key type="filename">sprites/wires/sets/color_cross.png</key> <key type="filename">sprites/wires/sets/color_cross.png</key>
@ -533,6 +535,8 @@
</struct> </struct>
<key type="filename">sprites/wires/boolean_false.png</key> <key type="filename">sprites/wires/boolean_false.png</key>
<key type="filename">sprites/wires/boolean_true.png</key> <key type="filename">sprites/wires/boolean_true.png</key>
<key type="filename">sprites/wires/network_conflict.png</key>
<key type="filename">sprites/wires/network_empty.png</key>
<key type="filename">sprites/wires/wires_preview.png</key> <key type="filename">sprites/wires/wires_preview.png</key>
<struct type="IndividualSpriteSettings"> <struct type="IndividualSpriteSettings">
<key>pivotPoint</key> <key>pivotPoint</key>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,38 +1,38 @@
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire, $buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
constant_signal, logic_gate, lever, filter, wire_tunnel, display; constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor;
@each $building in $buildings { @each $building in $buildings {
[data-icon="building_icons/#{$building}.png"] { [data-icon="building_icons/#{$building}.png"] {
background-image: uiResource("res/ui/building_icons/#{$building}.png") !important; background-image: uiResource("res/ui/building_icons/#{$building}.png") !important;
} }
} }
$buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt, $buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt,
underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl, underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl,
stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage; stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage;
@each $building in $buildingsAndVariants { @each $building in $buildingsAndVariants {
[data-icon="building_tutorials/#{$building}.png"] { [data-icon="building_tutorials/#{$building}.png"] {
background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important; background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important;
} }
} }
// Special case // Special case
[data-icon="building_tutorials/painter-mirrored.png"] { [data-icon="building_tutorials/painter-mirrored.png"] {
background-image: uiResource("res/ui/building_tutorials/painter.png") !important; background-image: uiResource("res/ui/building_tutorials/painter.png") !important;
} }
$icons: notification_saved, notification_success, notification_upgrade; $icons: notification_saved, notification_success, notification_upgrade;
@each $icon in $icons { @each $icon in $icons {
[data-icon="icons/#{$icon}.png"] { [data-icon="icons/#{$icon}.png"] {
background-image: uiResource("res/ui/icons/#{$icon}.png") !important; background-image: uiResource("res/ui/icons/#{$icon}.png") !important;
} }
} }
$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi, $languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
th, hu, pl, ja, kor, no, pt-PT; th, hu, pl, ja, kor, no, pt-PT;
@each $language in $languages { @each $language in $languages {
[data-languageicon="#{$language}"] { [data-languageicon="#{$language}"] {
background-image: uiResource("languages/#{$language}.svg") !important; background-image: uiResource("languages/#{$language}.svg") !important;
} }
} }

View File

@ -13,7 +13,7 @@ import { round1Digit } from "./utils";
const logger = createLogger("buffers"); const logger = createLogger("buffers");
const bufferGcDurationSeconds = 10; const bufferGcDurationSeconds = 5;
export class BufferMaintainer { export class BufferMaintainer {
/** /**
@ -27,6 +27,31 @@ export class BufferMaintainer {
this.iterationIndex = 1; this.iterationIndex = 1;
this.lastIteration = 0; 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, beltSpeedItemsPerSecond: 2,
minerSpeedItemsPerSecond: 0, // COMPUTED minerSpeedItemsPerSecond: 0, // COMPUTED
defaultItemDiameter: 20,
itemSpacingOnBelts: 0.63, itemSpacingOnBelts: 0.63,
wiresSpeedItemsPerSecond: 6, wiresSpeedItemsPerSecond: 6,

View File

@ -95,5 +95,14 @@ export default {
// Whether to items / s instead of items / m in stats // Whether to items / s instead of items / m in stats
// detailedStatistics: true, // 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 */ /* dev:end */
}; };

View File

@ -53,27 +53,6 @@ export class Rectangle {
return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom; return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
} }
/**
* Returns a rectangle arround a rotated point
* @param {Array<Vector>} points
* @param {number} angle
* @returns {Rectangle}
*/
static getAroundPointsRotated(points, angle) {
let minX = 1e10;
let minY = 1e10;
let maxX = -1e10;
let maxY = -1e10;
for (let i = 0; i < points.length; ++i) {
const rotated = points[i].rotated(angle);
minX = Math.min(minX, rotated.x);
minY = Math.min(minY, rotated.y);
maxX = Math.max(maxX, rotated.x);
maxY = Math.max(maxY, rotated.y);
}
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/** /**
* Copies this instance * Copies this instance
* @returns {Rectangle} * @returns {Rectangle}
@ -82,28 +61,6 @@ export class Rectangle {
return new Rectangle(this.x, this.y, this.w, this.h); return new Rectangle(this.x, this.y, this.w, this.h);
} }
/**
* Ensures the rectangle contains the given square
* @param {number} centerX
* @param {number} centerY
* @param {number} halfWidth
* @param {number} halfHeight
*/
extendBySquare(centerX, centerY, halfWidth, halfHeight) {
if (this.isEmpty()) {
// Just assign values since this rectangle is empty
this.x = centerX - halfWidth;
this.y = centerY - halfHeight;
this.w = halfWidth * 2;
this.h = halfHeight * 2;
} else {
this.setLeft(Math.min(this.x, centerX - halfWidth));
this.setRight(Math.max(this.right(), centerX + halfWidth));
this.setTop(Math.min(this.y, centerY - halfHeight));
this.setBottom(Math.max(this.bottom(), centerY + halfHeight));
}
}
/** /**
* Returns if this rectangle is empty * Returns if this rectangle is empty
* @returns {boolean} * @returns {boolean}
@ -259,14 +216,6 @@ export class Rectangle {
return new Rectangle(this.x - amount, this.y - amount, this.w + 2 * amount, this.h + 2 * amount); return new Rectangle(this.x - amount, this.y - amount, this.w + 2 * amount, this.h + 2 * amount);
} }
/**
* Helper for computing a culling area. Returns the top left tile
* @returns {Vector}
*/
getMinStartTile() {
return new Vector(this.x, this.y).snapWorldToTile();
}
/** /**
* Returns if the given rectangle is contained * Returns if the given rectangle is contained
* @param {Rectangle} rect * @param {Rectangle} rect
@ -394,7 +343,7 @@ export class Rectangle {
} }
/** /**
* Returns a new recangle in tile space which includes all tiles which are visible in this rect * Returns a new rectangle in tile space which includes all tiles which are visible in this rect
* @returns {Rectangle} * @returns {Rectangle}
*/ */
toTileCullRectangle() { toTileCullRectangle() {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,151 @@
import { Vector, enumDirection } from "../../core/vector";
import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate";
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
import { Entity } from "../entity";
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
import { GameRoot } from "../root";
/** @enum {string} */
export const enumVirtualProcessorVariants = {
analyzer: "analyzer",
rotater: "rotater",
unstacker: "unstacker",
shapecompare: "shapecompare",
};
/** @enum {string} */
export const enumVariantToGate = {
[defaultBuildingVariant]: enumLogicGateType.cutter,
[enumVirtualProcessorVariants.analyzer]: enumLogicGateType.analyzer,
[enumVirtualProcessorVariants.rotater]: enumLogicGateType.rotater,
[enumVirtualProcessorVariants.unstacker]: enumLogicGateType.unstacker,
[enumVirtualProcessorVariants.shapecompare]: enumLogicGateType.shapecompare,
};
export class MetaVirtualProcessorBuilding extends MetaBuilding {
constructor() {
super("virtual_processor");
}
getSilhouetteColor() {
return "#823cab";
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
// @todo
return true;
}
/** @returns {"wires"} **/
getLayer() {
return "wires";
}
getDimensions() {
return new Vector(1, 1);
}
getAvailableVariants() {
return [
defaultBuildingVariant,
enumVirtualProcessorVariants.rotater,
enumVirtualProcessorVariants.unstacker,
enumVirtualProcessorVariants.analyzer,
enumVirtualProcessorVariants.shapecompare,
];
}
getRenderPins() {
// We already have it included
return false;
}
/**
*
* @param {Entity} entity
* @param {number} rotationVariant
*/
updateVariants(entity, rotationVariant, variant) {
const gateType = enumVariantToGate[variant];
entity.components.LogicGate.type = gateType;
const pinComp = entity.components.WiredPins;
switch (gateType) {
case enumLogicGateType.cutter:
case enumLogicGateType.analyzer:
case enumLogicGateType.unstacker: {
pinComp.setSlots([
{
pos: new Vector(0, 0),
direction: enumDirection.left,
type: enumPinSlotType.logicalEjector,
},
{
pos: new Vector(0, 0),
direction: enumDirection.right,
type: enumPinSlotType.logicalEjector,
},
{
pos: new Vector(0, 0),
direction: enumDirection.bottom,
type: enumPinSlotType.logicalAcceptor,
},
]);
break;
}
case enumLogicGateType.rotater: {
pinComp.setSlots([
{
pos: new Vector(0, 0),
direction: enumDirection.top,
type: enumPinSlotType.logicalEjector,
},
{
pos: new Vector(0, 0),
direction: enumDirection.bottom,
type: enumPinSlotType.logicalAcceptor,
},
]);
break;
}
case enumLogicGateType.shapecompare: {
pinComp.setSlots([
{
pos: new Vector(0, 0),
direction: enumDirection.top,
type: enumPinSlotType.logicalEjector,
},
{
pos: new Vector(0, 0),
direction: enumDirection.left,
type: enumPinSlotType.logicalAcceptor,
},
{
pos: new Vector(0, 0),
direction: enumDirection.right,
type: enumPinSlotType.logicalAcceptor,
},
]);
break;
}
default:
assertAlways("unknown logic gate type: " + gateType);
}
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
entity.addComponent(
new WiredPinsComponent({
slots: [],
})
);
entity.addComponent(new LogicGateComponent({}));
}
}

View File

@ -1,30 +1,36 @@
import { Component } from "../component"; import { Component } from "../component";
/** @enum {string} */ /** @enum {string} */
export const enumLogicGateType = { export const enumLogicGateType = {
and: "and", and: "and",
not: "not", not: "not",
xor: "xor", xor: "xor",
or: "or", or: "or",
transistor: "transistor", transistor: "transistor",
};
analyzer: "analyzer",
export class LogicGateComponent extends Component { rotater: "rotater",
static getId() { unstacker: "unstacker",
return "LogicGate"; cutter: "cutter",
} shapecompare: "shapecompare",
};
duplicateWithoutContents() {
return new LogicGateComponent({ type: this.type }); export class LogicGateComponent extends Component {
} static getId() {
return "LogicGate";
/** }
*
* @param {object} param0 duplicateWithoutContents() {
* @param {enumLogicGateType=} param0.type return new LogicGateComponent({ type: this.type });
*/ }
constructor({ type = enumLogicGateType.and }) {
super(); /**
this.type = type; *
} * @param {object} param0
} * @param {enumLogicGateType=} param0.type
*/
constructor({ type = enumLogicGateType.and }) {
super();
this.type = type;
}
}

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import { DrawParameters } from "../core/draw_parameters";
import { gMetaBuildingRegistry } from "../core/global_registries"; import { gMetaBuildingRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle"; import { Rectangle } from "../core/rectangle";
import { randomInt, round2Digits } from "../core/utils"; import { randomInt, round2Digits, round3Digits } from "../core/utils";
import { Vector } from "../core/vector"; import { Vector } from "../core/vector";
import { Savegame } from "../savegame/savegame"; import { Savegame } from "../savegame/savegame";
import { SavegameSerializer } from "../savegame/savegame_serializer"; import { SavegameSerializer } from "../savegame/savegame_serializer";
@ -329,8 +329,7 @@ export class GameCore {
return; return;
} }
// Update buffers as the very first this.root.signals.gameFrameStarted.dispatch();
root.buffers.update();
root.queue.requireRedraw = false; root.queue.requireRedraw = false;
@ -390,33 +389,24 @@ export class GameCore {
// Map overview // Map overview
root.map.drawOverlay(params); root.map.drawOverlay(params);
} else { } else {
// Background (grid, resources, etc)
root.map.drawBackground(params); root.map.drawBackground(params);
// Belt items // Belt items
systems.belt.drawBeltItems(params); systems.belt.drawBeltItems(params);
// Items being ejected / accepted currently (animations) // Miner & Static map entities etc.
systems.itemEjector.draw(params);
systems.itemAcceptor.draw(params);
// Miner & Static map entities
root.map.drawForeground(params); root.map.drawForeground(params);
// HUB Overlay // HUB Overlay
systems.hub.draw(params); systems.hub.draw(params);
// Storage items
systems.storage.draw(params);
// Green wires overlay // Green wires overlay
root.hud.parts.wiresOverlay.draw(params); root.hud.parts.wiresOverlay.draw(params);
if (this.root.currentLayer === "wires") { if (this.root.currentLayer === "wires") {
// Static map entities // Static map entities
root.map.drawWiresForegroundLayer(params); root.map.drawWiresForegroundLayer(params);
// pins
systems.wiredPins.draw(params);
} }
} }
@ -464,7 +454,7 @@ export class GameCore {
if (G_IS_DEV && globalConfig.debug.showAtlasInfo) { if (G_IS_DEV && globalConfig.debug.showAtlasInfo) {
context.font = "13px GameFont"; context.font = "13px GameFont";
context.fillStyle = "yellow"; context.fillStyle = "blue";
context.fillText( context.fillText(
"Atlas: " + "Atlas: " +
desiredAtlasScale + desiredAtlasScale +
@ -472,8 +462,22 @@ export class GameCore {
round2Digits(zoomLevel) + round2Digits(zoomLevel) +
" / Effective Zoom: " + " / Effective Zoom: " +
round2Digits(effectiveZoomLevel), 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
); );
} }

View File

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

View File

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

View File

@ -78,7 +78,7 @@ export class HUDSandboxController extends BaseHUDPart {
if (!this.root.hubGoals.storedShapes[blueprintShape]) { if (!this.root.hubGoals.storedShapes[blueprintShape]) {
this.root.hubGoals.storedShapes[blueprintShape] = 0; this.root.hubGoals.storedShapes[blueprintShape] = 0;
} }
this.root.hubGoals.storedShapes[blueprintShape] += 1e4; this.root.hubGoals.storedShapes[blueprintShape] += 1e9;
} }
maxOutAll() { maxOutAll() {

View File

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

View File

@ -1,25 +1,27 @@
import { HUDBaseToolbar } from "./base_toolbar"; import { HUDBaseToolbar } from "./base_toolbar";
import { MetaWireBuilding } from "../../buildings/wire"; import { MetaWireBuilding } from "../../buildings/wire";
import { MetaConstantSignalBuilding } from "../../buildings/constant_signal"; import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
import { MetaLogicGateBuilding } from "../../buildings/logic_gate"; import { MetaLogicGateBuilding } from "../../buildings/logic_gate";
import { MetaLeverBuilding } from "../../buildings/lever"; import { MetaLeverBuilding } from "../../buildings/lever";
import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel"; import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel";
import { MetaVirtualProcessorBuilding } from "../../buildings/virtual_processor";
const supportedBuildings = [
MetaWireBuilding, const supportedBuildings = [
MetaWireTunnelBuilding, MetaWireBuilding,
MetaConstantSignalBuilding, MetaWireTunnelBuilding,
MetaLogicGateBuilding, MetaConstantSignalBuilding,
MetaLeverBuilding, MetaLogicGateBuilding,
]; MetaLeverBuilding,
MetaVirtualProcessorBuilding,
export class HUDWiresToolbar extends HUDBaseToolbar { ];
constructor(root) {
super(root, { export class HUDWiresToolbar extends HUDBaseToolbar {
supportedBuildings, constructor(root) {
visibilityCondition: () => super(root, {
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires", supportedBuildings,
htmlElementId: "ingame_HUD_wires_toolbar", visibilityCondition: () =>
}); !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
} htmlElementId: "ingame_HUD_wires_toolbar",
} });
}
}

View File

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

View File

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

View File

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

View File

@ -1,456 +1,457 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { InputReceiver } from "../core/input_receiver"; import { InputReceiver } from "../core/input_receiver";
import { Application } from "../application"; import { Application } from "../application";
/* typehints:end */ /* typehints:end */
import { Signal, STOP_PROPAGATION } from "../core/signal"; import { Signal, STOP_PROPAGATION } from "../core/signal";
import { IS_MOBILE } from "../core/config"; import { IS_MOBILE } from "../core/config";
import { T } from "../translations"; import { T } from "../translations";
function key(str) { function key(str) {
return str.toUpperCase().charCodeAt(0); return str.toUpperCase().charCodeAt(0);
} }
export const KEYMAPPINGS = { export const KEYMAPPINGS = {
general: { general: {
confirm: { keyCode: 13 }, // enter confirm: { keyCode: 13 }, // enter
back: { keyCode: 27, builtin: true }, // escape back: { keyCode: 27, builtin: true }, // escape
}, },
ingame: { ingame: {
menuOpenShop: { keyCode: key("F") }, menuOpenShop: { keyCode: key("F") },
menuOpenStats: { keyCode: key("G") }, menuOpenStats: { keyCode: key("G") },
menuClose: { keyCode: key("Q") }, menuClose: { keyCode: key("Q") },
toggleHud: { keyCode: 113 }, // F2 toggleHud: { keyCode: 113 }, // F2
exportScreenshot: { keyCode: 114 }, // F3PS exportScreenshot: { keyCode: 114 }, // F3PS
toggleFPSInfo: { keyCode: 115 }, // F4 toggleFPSInfo: { keyCode: 115 }, // F4
switchLayers: { keyCode: key("Y") }, switchLayers: { keyCode: key("Y") },
}, },
navigation: { navigation: {
mapMoveUp: { keyCode: key("W") }, mapMoveUp: { keyCode: key("W") },
mapMoveRight: { keyCode: key("D") }, mapMoveRight: { keyCode: key("D") },
mapMoveDown: { keyCode: key("S") }, mapMoveDown: { keyCode: key("S") },
mapMoveLeft: { keyCode: key("A") }, mapMoveLeft: { keyCode: key("A") },
mapMoveFaster: { keyCode: 16 }, //shift mapMoveFaster: { keyCode: 16 }, //shift
centerMap: { keyCode: 32 }, // SPACE centerMap: { keyCode: 32 }, // SPACE
mapZoomIn: { keyCode: 187, repeated: true }, // "+" mapZoomIn: { keyCode: 187, repeated: true }, // "+"
mapZoomOut: { keyCode: 189, repeated: true }, // "-" mapZoomOut: { keyCode: 189, repeated: true }, // "-"
createMarker: { keyCode: key("M") }, createMarker: { keyCode: key("M") },
}, },
buildings: { buildings: {
belt: { keyCode: key("1") }, belt: { keyCode: key("1") },
splitter: { keyCode: key("2") }, splitter: { keyCode: key("2") },
underground_belt: { keyCode: key("3") }, underground_belt: { keyCode: key("3") },
miner: { keyCode: key("4") }, miner: { keyCode: key("4") },
cutter: { keyCode: key("5") }, cutter: { keyCode: key("5") },
rotater: { keyCode: key("6") }, rotater: { keyCode: key("6") },
stacker: { keyCode: key("7") }, stacker: { keyCode: key("7") },
mixer: { keyCode: key("8") }, mixer: { keyCode: key("8") },
painter: { keyCode: key("9") }, painter: { keyCode: key("9") },
trash: { keyCode: key("0") }, trash: { keyCode: key("0") },
lever: { keyCode: key("L") }, lever: { keyCode: key("L") },
filter: { keyCode: key("B") }, filter: { keyCode: key("B") },
display: { keyCode: key("N") }, display: { keyCode: key("N") },
wire: { keyCode: key("1") }, wire: { keyCode: key("1") },
wire_tunnel: { keyCode: key("2") }, wire_tunnel: { keyCode: key("2") },
constant_signal: { keyCode: key("3") }, constant_signal: { keyCode: key("3") },
logic_gate: { keyCode: key("4") }, logic_gate: { keyCode: key("4") },
}, virtual_processor: { keyCode: key("5") },
},
placement: {
pipette: { keyCode: key("Q") }, placement: {
rotateWhilePlacing: { keyCode: key("R") }, pipette: { keyCode: key("Q") },
rotateInverseModifier: { keyCode: 16 }, // SHIFT rotateWhilePlacing: { keyCode: key("R") },
cycleBuildingVariants: { keyCode: key("T") }, rotateInverseModifier: { keyCode: 16 }, // SHIFT
cycleBuildings: { keyCode: 9 }, // TAB cycleBuildingVariants: { keyCode: key("T") },
switchDirectionLockSide: { keyCode: key("R") }, cycleBuildings: { keyCode: 9 }, // TAB
}, switchDirectionLockSide: { keyCode: key("R") },
},
massSelect: {
massSelectStart: { keyCode: 17 }, // CTRL massSelect: {
massSelectSelectMultiple: { keyCode: 16 }, // SHIFT massSelectStart: { keyCode: 17 }, // CTRL
massSelectCopy: { keyCode: key("C") }, massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
massSelectCut: { keyCode: key("X") }, massSelectCopy: { keyCode: key("C") },
confirmMassDelete: { keyCode: 46 }, // DEL massSelectCut: { keyCode: key("X") },
pasteLastBlueprint: { keyCode: key("V") }, confirmMassDelete: { keyCode: 46 }, // DEL
}, pasteLastBlueprint: { keyCode: key("V") },
},
placementModifiers: {
lockBeltDirection: { keyCode: 16 }, // SHIFT placementModifiers: {
placementDisableAutoOrientation: { keyCode: 17 }, // CTRL lockBeltDirection: { keyCode: 16 }, // SHIFT
placeMultiple: { keyCode: 16 }, // SHIFT placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
placeInverse: { keyCode: 18 }, // ALT placeMultiple: { keyCode: 16 }, // SHIFT
}, placeInverse: { keyCode: 18 }, // ALT
}; },
};
// Assign ids
for (const categoryId in KEYMAPPINGS) { // Assign ids
for (const mappingId in KEYMAPPINGS[categoryId]) { for (const categoryId in KEYMAPPINGS) {
KEYMAPPINGS[categoryId][mappingId].id = mappingId; for (const mappingId in KEYMAPPINGS[categoryId]) {
} KEYMAPPINGS[categoryId][mappingId].id = mappingId;
} }
}
export const KEYCODE_LMB = 1;
export const KEYCODE_MMB = 2; export const KEYCODE_LMB = 1;
export const KEYCODE_RMB = 3; export const KEYCODE_MMB = 2;
export const KEYCODE_RMB = 3;
/**
* Returns a keycode -> string /**
* @param {number} code * Returns a keycode -> string
* @returns {string} * @param {number} code
*/ * @returns {string}
export function getStringForKeyCode(code) { */
switch (code) { export function getStringForKeyCode(code) {
case KEYCODE_LMB: switch (code) {
return "LMB"; case KEYCODE_LMB:
case KEYCODE_MMB: return "LMB";
return "MMB"; case KEYCODE_MMB:
case KEYCODE_RMB: return "MMB";
return "RMB"; case KEYCODE_RMB:
case 4: return "RMB";
return "MB4"; case 4:
case 5: return "MB4";
return "MB5"; case 5:
case 8: return "MB5";
return "⌫"; case 8:
case 9: return "⌫";
return T.global.keys.tab; case 9:
case 13: return T.global.keys.tab;
return "⏎"; case 13:
case 16: return "⏎";
return "⇪"; case 16:
case 17: return "⇪";
return T.global.keys.control; case 17:
case 18: return T.global.keys.control;
return T.global.keys.alt; case 18:
case 19: return T.global.keys.alt;
return "PAUSE"; case 19:
case 20: return "PAUSE";
return "CAPS"; case 20:
case 27: return "CAPS";
return T.global.keys.escape; case 27:
case 32: return T.global.keys.escape;
return T.global.keys.space; case 32:
case 33: return T.global.keys.space;
return "PGUP"; case 33:
case 34: return "PGUP";
return "PGDOWN"; case 34:
case 35: return "PGDOWN";
return "END"; case 35:
case 36: return "END";
return "HOME"; case 36:
case 37: return "HOME";
return "⬅"; case 37:
case 38: return "⬅";
return "⬆"; case 38:
case 39: return "⬆";
return "➡"; case 39:
case 40: return "➡";
return "⬇"; case 40:
case 44: return "⬇";
return "PRNT"; case 44:
case 45: return "PRNT";
return "INS"; case 45:
case 46: return "INS";
return "DEL"; case 46:
case 93: return "DEL";
return "SEL"; case 93:
case 96: return "SEL";
return "NUM 0"; case 96:
case 97: return "NUM 0";
return "NUM 1"; case 97:
case 98: return "NUM 1";
return "NUM 2"; case 98:
case 99: return "NUM 2";
return "NUM 3"; case 99:
case 100: return "NUM 3";
return "NUM 4"; case 100:
case 101: return "NUM 4";
return "NUM 5"; case 101:
case 102: return "NUM 5";
return "NUM 6"; case 102:
case 103: return "NUM 6";
return "NUM 7"; case 103:
case 104: return "NUM 7";
return "NUM 8"; case 104:
case 105: return "NUM 8";
return "NUM 9"; case 105:
case 106: return "NUM 9";
return "*"; case 106:
case 107: return "*";
return "+"; case 107:
case 109: return "+";
return "-"; case 109:
case 110: return "-";
return "."; case 110:
case 111: return ".";
return "/"; case 111:
case 112: return "/";
return "F1"; case 112:
case 113: return "F1";
return "F2"; case 113:
case 114: return "F2";
return "F3"; case 114:
case 115: return "F3";
return "F4"; case 115:
case 116: return "F4";
return "F4"; case 116:
case 117: return "F4";
return "F5"; case 117:
case 118: return "F5";
return "F6"; case 118:
case 119: return "F6";
return "F7"; case 119:
case 120: return "F7";
return "F8"; case 120:
case 121: return "F8";
return "F9"; case 121:
case 122: return "F9";
return "F10"; case 122:
case 123: return "F10";
return "F11"; case 123:
case 124: return "F11";
return "F12"; case 124:
return "F12";
case 144:
return "NUMLOCK"; case 144:
case 145: return "NUMLOCK";
return "SCRLOCK"; case 145:
case 182: return "SCRLOCK";
return "COMP"; case 182:
case 183: return "COMP";
return "CALC"; case 183:
case 186: return "CALC";
return ";"; case 186:
case 187: return ";";
return "+"; case 187:
case 188: return "+";
return ","; case 188:
case 189: return ",";
return "-"; case 189:
case 191: return "-";
return "/"; case 191:
case 219: return "/";
return "["; case 219:
case 220: return "[";
return "\\"; case 220:
case 221: return "\\";
return "]"; case 221:
case 222: return "]";
return "'"; case 222:
} return "'";
}
return String.fromCharCode(code);
} return String.fromCharCode(code);
}
export class Keybinding {
/** export class Keybinding {
* /**
* @param {KeyActionMapper} keyMapper *
* @param {Application} app * @param {KeyActionMapper} keyMapper
* @param {object} param0 * @param {Application} app
* @param {number} param0.keyCode * @param {object} param0
* @param {boolean=} param0.builtin * @param {number} param0.keyCode
* @param {boolean=} param0.repeated * @param {boolean=} param0.builtin
*/ * @param {boolean=} param0.repeated
constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) { */
assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode); constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
this.keyMapper = keyMapper; assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
this.app = app; this.keyMapper = keyMapper;
this.keyCode = keyCode; this.app = app;
this.builtin = builtin; this.keyCode = keyCode;
this.repeated = repeated; this.builtin = builtin;
this.repeated = repeated;
this.signal = new Signal();
this.toggled = new Signal(); this.signal = new Signal();
} this.toggled = new Signal();
}
/**
* Returns whether this binding is currently pressed /**
* @returns {boolean} * Returns whether this binding is currently pressed
*/ * @returns {boolean}
get pressed() { */
// Check if the key is down get pressed() {
if (this.app.inputMgr.keysDown.has(this.keyCode)) { // Check if the key is down
// Check if it is the top reciever if (this.app.inputMgr.keysDown.has(this.keyCode)) {
const reciever = this.keyMapper.inputReceiver; // Check if it is the top reciever
return this.app.inputMgr.getTopReciever() === reciever; const reciever = this.keyMapper.inputReceiver;
} return this.app.inputMgr.getTopReciever() === reciever;
return false; }
} return false;
}
/**
* Adds an event listener /**
* @param {function() : void} receiver * Adds an event listener
* @param {object=} scope * @param {function() : void} receiver
*/ * @param {object=} scope
add(receiver, scope = null) { */
this.signal.add(receiver, scope); add(receiver, scope = null) {
} this.signal.add(receiver, scope);
}
/**
* @param {Element} elem /**
* @returns {HTMLElement} the created element, or null if the keybindings are not shown * @param {Element} elem
* */ * @returns {HTMLElement} the created element, or null if the keybindings are not shown
appendLabelToElement(elem) { * */
if (IS_MOBILE) { appendLabelToElement(elem) {
return null; if (IS_MOBILE) {
} return null;
const spacer = document.createElement("code"); }
spacer.classList.add("keybinding"); const spacer = document.createElement("code");
spacer.innerHTML = getStringForKeyCode(this.keyCode); spacer.classList.add("keybinding");
elem.appendChild(spacer); spacer.innerHTML = getStringForKeyCode(this.keyCode);
return spacer; elem.appendChild(spacer);
} return spacer;
}
/**
* Returns the key code as a nice string /**
*/ * Returns the key code as a nice string
getKeyCodeString() { */
return getStringForKeyCode(this.keyCode); getKeyCodeString() {
} return getStringForKeyCode(this.keyCode);
}
/**
* Remvoes all signal receivers /**
*/ * Remvoes all signal receivers
clearSignalReceivers() { */
this.signal.removeAll(); clearSignalReceivers() {
} this.signal.removeAll();
} }
}
export class KeyActionMapper {
/** export class KeyActionMapper {
* /**
* @param {GameRoot} root *
* @param {InputReceiver} inputReciever * @param {GameRoot} root
*/ * @param {InputReceiver} inputReciever
constructor(root, inputReciever) { */
this.root = root; constructor(root, inputReciever) {
this.inputReceiver = inputReciever; this.root = root;
this.inputReceiver = inputReciever;
inputReciever.keydown.add(this.handleKeydown, this);
inputReciever.keyup.add(this.handleKeyup, this); inputReciever.keydown.add(this.handleKeydown, this);
inputReciever.keyup.add(this.handleKeyup, this);
/** @type {Object.<string, Keybinding>} */
this.keybindings = {}; /** @type {Object.<string, Keybinding>} */
this.keybindings = {};
const overrides = root.app.settings.getKeybindingOverrides();
const overrides = root.app.settings.getKeybindingOverrides();
for (const category in KEYMAPPINGS) {
for (const key in KEYMAPPINGS[category]) { for (const category in KEYMAPPINGS) {
let payload = Object.assign({}, KEYMAPPINGS[category][key]); for (const key in KEYMAPPINGS[category]) {
if (overrides[key]) { let payload = Object.assign({}, KEYMAPPINGS[category][key]);
payload.keyCode = overrides[key]; if (overrides[key]) {
} payload.keyCode = overrides[key];
}
this.keybindings[key] = new Keybinding(this, this.root.app, payload);
} this.keybindings[key] = new Keybinding(this, this.root.app, payload);
} }
}
inputReciever.pageBlur.add(this.onPageBlur, this);
inputReciever.destroyed.add(this.cleanup, this); inputReciever.pageBlur.add(this.onPageBlur, this);
} inputReciever.destroyed.add(this.cleanup, this);
}
/**
* Returns all keybindings starting with the given id /**
* @param {string} pattern * Returns all keybindings starting with the given id
* @returns {Array<Keybinding>} * @param {string} pattern
*/ * @returns {Array<Keybinding>}
getKeybindingsStartingWith(pattern) { */
let result = []; getKeybindingsStartingWith(pattern) {
for (const key in this.keybindings) { let result = [];
if (key.startsWith(pattern)) { for (const key in this.keybindings) {
result.push(this.keybindings[key]); if (key.startsWith(pattern)) {
} result.push(this.keybindings[key]);
} }
return result; }
} return result;
}
/**
* Forwards the given events to the other mapper (used in tooltips) /**
* @param {KeyActionMapper} receiver * Forwards the given events to the other mapper (used in tooltips)
* @param {Array<string>} bindings * @param {KeyActionMapper} receiver
*/ * @param {Array<string>} bindings
forward(receiver, bindings) { */
for (let i = 0; i < bindings.length; ++i) { forward(receiver, bindings) {
const key = bindings[i]; for (let i = 0; i < bindings.length; ++i) {
this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args)); const key = bindings[i];
} this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
} }
}
cleanup() {
for (const key in this.keybindings) { cleanup() {
this.keybindings[key].signal.removeAll(); for (const key in this.keybindings) {
} this.keybindings[key].signal.removeAll();
} }
}
onPageBlur() {
// Reset all down states onPageBlur() {
// Find mapping // Reset all down states
for (const key in this.keybindings) { // Find mapping
/** @type {Keybinding} */ for (const key in this.keybindings) {
const binding = this.keybindings[key]; /** @type {Keybinding} */
} const binding = this.keybindings[key];
} }
}
/**
* Internal keydown handler /**
* @param {object} param0 * Internal keydown handler
* @param {number} param0.keyCode * @param {object} param0
* @param {boolean} param0.shift * @param {number} param0.keyCode
* @param {boolean} param0.alt * @param {boolean} param0.shift
* @param {boolean=} param0.initial * @param {boolean} param0.alt
*/ * @param {boolean=} param0.initial
handleKeydown({ keyCode, shift, alt, initial }) { */
let stop = false; handleKeydown({ keyCode, shift, alt, initial }) {
let stop = false;
// Find mapping
for (const key in this.keybindings) { // Find mapping
/** @type {Keybinding} */ for (const key in this.keybindings) {
const binding = this.keybindings[key]; /** @type {Keybinding} */
if (binding.keyCode === keyCode && (initial || binding.repeated)) { const binding = this.keybindings[key];
/** @type {Signal} */ if (binding.keyCode === keyCode && (initial || binding.repeated)) {
const signal = this.keybindings[key].signal; /** @type {Signal} */
if (signal.dispatch() === STOP_PROPAGATION) { const signal = this.keybindings[key].signal;
return; if (signal.dispatch() === STOP_PROPAGATION) {
} return;
} }
} }
}
if (stop) {
return STOP_PROPAGATION; if (stop) {
} return STOP_PROPAGATION;
} }
}
/**
* Internal keyup handler /**
* @param {object} param0 * Internal keyup handler
* @param {number} param0.keyCode * @param {object} param0
* @param {boolean} param0.shift * @param {number} param0.keyCode
* @param {boolean} param0.alt * @param {boolean} param0.shift
*/ * @param {boolean} param0.alt
handleKeyup({ keyCode, shift, alt }) { */
// Empty handleKeyup({ keyCode, shift, alt }) {
} // Empty
}
/**
* Returns a given keybinding /**
* @param {{ keyCode: number }} binding * Returns a given keybinding
* @returns {Keybinding} * @param {{ keyCode: number }} binding
*/ * @returns {Keybinding}
getBinding(binding) { */
// @ts-ignore getBinding(binding) {
const id = binding.id; // @ts-ignore
assert(id, "Not a valid keybinding: " + JSON.stringify(binding)); const id = binding.id;
assert(this.keybindings[id], "Keybinding " + id + " not known!"); assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
return this.keybindings[id]; assert(this.keybindings[id], "Keybinding " + id + " not known!");
} return this.keybindings[id];
} }
}

View File

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

View File

@ -1,162 +1,171 @@
import { gMetaBuildingRegistry } from "../core/global_registries"; import { gMetaBuildingRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { MetaBeltBuilding } from "./buildings/belt"; import { MetaBeltBuilding } from "./buildings/belt";
import { MetaBeltBaseBuilding } from "./buildings/belt_base"; import { MetaBeltBaseBuilding } from "./buildings/belt_base";
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter"; import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
import { MetaHubBuilding } from "./buildings/hub"; import { MetaHubBuilding } from "./buildings/hub";
import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner"; import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
import { MetaMixerBuilding } from "./buildings/mixer"; import { MetaMixerBuilding } from "./buildings/mixer";
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter"; import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater"; import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter"; import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
import { MetaStackerBuilding } from "./buildings/stacker"; import { MetaStackerBuilding } from "./buildings/stacker";
import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash"; import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt"; import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
import { MetaWireBuilding } from "./buildings/wire"; import { MetaWireBuilding } from "./buildings/wire";
import { gBuildingVariants, registerBuildingVariant } from "./building_codes"; import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
import { defaultBuildingVariant } from "./meta_building"; import { defaultBuildingVariant } from "./meta_building";
import { MetaConstantSignalBuilding } from "./buildings/constant_signal"; import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate"; import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
import { MetaLeverBuilding } from "./buildings/lever"; import { MetaLeverBuilding } from "./buildings/lever";
import { MetaFilterBuilding } from "./buildings/filter"; import { MetaFilterBuilding } from "./buildings/filter";
import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel"; import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
import { MetaDisplayBuilding } from "./buildings/display"; import { MetaDisplayBuilding } from "./buildings/display";
import { MetaVirtualProcessorBuilding, enumVirtualProcessorVariants } from "./buildings/virtual_processor";
const logger = createLogger("building_registry");
const logger = createLogger("building_registry");
export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaSplitterBuilding); export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaMinerBuilding); gMetaBuildingRegistry.register(MetaSplitterBuilding);
gMetaBuildingRegistry.register(MetaCutterBuilding); gMetaBuildingRegistry.register(MetaMinerBuilding);
gMetaBuildingRegistry.register(MetaRotaterBuilding); gMetaBuildingRegistry.register(MetaCutterBuilding);
gMetaBuildingRegistry.register(MetaStackerBuilding); gMetaBuildingRegistry.register(MetaRotaterBuilding);
gMetaBuildingRegistry.register(MetaMixerBuilding); gMetaBuildingRegistry.register(MetaStackerBuilding);
gMetaBuildingRegistry.register(MetaPainterBuilding); gMetaBuildingRegistry.register(MetaMixerBuilding);
gMetaBuildingRegistry.register(MetaTrashBuilding); gMetaBuildingRegistry.register(MetaPainterBuilding);
gMetaBuildingRegistry.register(MetaBeltBuilding); gMetaBuildingRegistry.register(MetaTrashBuilding);
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding); gMetaBuildingRegistry.register(MetaBeltBuilding);
gMetaBuildingRegistry.register(MetaHubBuilding); gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
gMetaBuildingRegistry.register(MetaWireBuilding); gMetaBuildingRegistry.register(MetaHubBuilding);
gMetaBuildingRegistry.register(MetaConstantSignalBuilding); gMetaBuildingRegistry.register(MetaWireBuilding);
gMetaBuildingRegistry.register(MetaLogicGateBuilding); gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
gMetaBuildingRegistry.register(MetaLeverBuilding); gMetaBuildingRegistry.register(MetaLogicGateBuilding);
gMetaBuildingRegistry.register(MetaFilterBuilding); gMetaBuildingRegistry.register(MetaLeverBuilding);
gMetaBuildingRegistry.register(MetaWireTunnelBuilding); gMetaBuildingRegistry.register(MetaFilterBuilding);
gMetaBuildingRegistry.register(MetaDisplayBuilding); gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
gMetaBuildingRegistry.register(MetaDisplayBuilding);
// Belt gMetaBuildingRegistry.register(MetaVirtualProcessorBuilding);
registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1); // Belt
registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2); registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1);
// Splitter registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2);
registerBuildingVariant(4, MetaSplitterBuilding);
registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact); // Splitter
registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse); registerBuildingVariant(4, MetaSplitterBuilding);
registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
// Miner registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
registerBuildingVariant(7, MetaMinerBuilding);
registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable); // Miner
registerBuildingVariant(7, MetaMinerBuilding);
// Cutter registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
registerBuildingVariant(9, MetaCutterBuilding);
registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad); // Cutter
registerBuildingVariant(9, MetaCutterBuilding);
// Rotater registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
registerBuildingVariant(11, MetaRotaterBuilding);
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw); // Rotater
registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl); registerBuildingVariant(11, MetaRotaterBuilding);
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
// Stacker registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
registerBuildingVariant(14, MetaStackerBuilding);
// Stacker
// Mixer registerBuildingVariant(14, MetaStackerBuilding);
registerBuildingVariant(15, MetaMixerBuilding);
// Mixer
// Painter registerBuildingVariant(15, MetaMixerBuilding);
registerBuildingVariant(16, MetaPainterBuilding);
registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored); // Painter
registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double); registerBuildingVariant(16, MetaPainterBuilding);
registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad); registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
// Trash registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
registerBuildingVariant(20, MetaTrashBuilding);
registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage); // Trash
registerBuildingVariant(20, MetaTrashBuilding);
// Underground belt registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage);
registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1); // Underground belt
registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0); registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1); registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
// Hub registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
registerBuildingVariant(26, MetaHubBuilding);
// Hub
// Wire registerBuildingVariant(26, MetaHubBuilding);
registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1); // Wire
registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2); registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3); registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
// Constant signal registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
registerBuildingVariant(31, MetaConstantSignalBuilding);
// Constant signal
// Logic gate registerBuildingVariant(31, MetaConstantSignalBuilding);
registerBuildingVariant(32, MetaLogicGateBuilding);
registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not); // Logic gate
registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor); registerBuildingVariant(32, MetaLogicGateBuilding);
registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or); registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor); registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
// Lever registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor);
registerBuildingVariant(33, MetaLeverBuilding);
// Lever
// Filter registerBuildingVariant(33, MetaLeverBuilding);
registerBuildingVariant(37, MetaFilterBuilding);
// Filter
// Wire tunnel registerBuildingVariant(37, MetaFilterBuilding);
registerBuildingVariant(39, MetaWireTunnelBuilding);
registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating); // Wire tunnel
registerBuildingVariant(39, MetaWireTunnelBuilding);
// Display registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
registerBuildingVariant(40, MetaDisplayBuilding);
// Display
// Propagate instances registerBuildingVariant(40, MetaDisplayBuilding);
for (const key in gBuildingVariants) {
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass( // Virtual Processor
gBuildingVariants[key].metaClass registerBuildingVariant(42, MetaVirtualProcessorBuilding);
); registerBuildingVariant(43, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.analyzer);
} registerBuildingVariant(44, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.rotater);
registerBuildingVariant(45, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.unstacker);
for (const key in gBuildingVariants) { registerBuildingVariant(46, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.shapecompare);
const variant = gBuildingVariants[key];
assert(variant.metaClass, "Variant has no meta: " + key); // Propagate instances
for (const key in gBuildingVariants) {
if (typeof variant.rotationVariant === "undefined") { gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
variant.rotationVariant = 0; gBuildingVariants[key].metaClass
} );
if (typeof variant.variant === "undefined") { }
variant.variant = defaultBuildingVariant;
} for (const key in gBuildingVariants) {
} const variant = gBuildingVariants[key];
assert(variant.metaClass, "Variant has no meta: " + key);
logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes"); if (typeof variant.rotationVariant === "undefined") {
} variant.rotationVariant = 0;
}
/** if (typeof variant.variant === "undefined") {
* Once all sprites are loaded, propagates the cache variant.variant = defaultBuildingVariant;
*/ }
export function initBuildingCodesAfterResourcesLoaded() { }
logger.log("Propagating sprite cache");
for (const key in gBuildingVariants) { logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
const variant = gBuildingVariants[key]; logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes");
}
variant.sprite = variant.metaInstance.getSprite(variant.rotationVariant, variant.variant);
variant.blueprintSprite = variant.metaInstance.getBlueprintSprite( /**
variant.rotationVariant, * Once all sprites are loaded, propagates the cache
variant.variant */
); export function initBuildingCodesAfterResourcesLoaded() {
variant.silhouetteColor = variant.metaInstance.getSilhouetteColor(); logger.log("Propagating sprite cache");
} for (const key in gBuildingVariants) {
} const variant = gBuildingVariants[key];
variant.sprite = variant.metaInstance.getSprite(variant.rotationVariant, variant.variant);
variant.blueprintSprite = variant.metaInstance.getBlueprintSprite(
variant.rotationVariant,
variant.variant
);
variant.silhouetteColor = variant.metaInstance.getSilhouetteColor();
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunkView } from "../map_chunk_view"; import { MapChunkView } from "../map_chunk_view";
import { defaultBuildingVariant } from "../meta_building"; import { defaultBuildingVariant } from "../meta_building";
import { getCodeFromBuildingData } from "../building_codes"; import { getCodeFromBuildingData } from "../building_codes";
import { enumLayer } from "../root";
export const BELT_ANIM_COUNT = 14; export const BELT_ANIM_COUNT = 14;
@ -496,13 +495,15 @@ export class BeltSystem extends GameSystemWithFilter {
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) * ((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
globalConfig.itemSpacingOnBelts globalConfig.itemSpacingOnBelts
); );
const contents = chunk.containedEntitiesByLayer[enumLayer.regular]; const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) { for (let i = 0; i < contents.length; ++i) {
const entity = contents[i]; const entity = contents[i];
if (entity.components.Belt) { if (entity.components.Belt) {
const direction = entity.components.Belt.direction; const direction = entity.components.Belt.direction;
const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT]; const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT];
entity.components.StaticMapEntity.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
// Culling happens within the static map entity component
entity.components.StaticMapEntity.drawSpriteOnBoundsClipped(parameters, sprite, 0);
} }
} }
} }

View File

@ -7,7 +7,6 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
import { BELT_ANIM_COUNT } from "./belt"; import { BELT_ANIM_COUNT } from "./belt";
import { MapChunkView } from "../map_chunk_view"; import { MapChunkView } from "../map_chunk_view";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { enumLayer } from "../root";
export class BeltUnderlaysSystem extends GameSystemWithFilter { export class BeltUnderlaysSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
@ -29,38 +28,56 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
// Limit speed to avoid belts going backwards // Limit speed to avoid belts going backwards
const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(), 10); const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(), 10);
const contents = chunk.containedEntitiesByLayer[enumLayer.regular]; const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) { for (let i = 0; i < contents.length; ++i) {
const entity = contents[i]; const entity = contents[i];
const underlayComp = entity.components.BeltUnderlays; const underlayComp = entity.components.BeltUnderlays;
if (underlayComp) { if (!underlayComp) {
const staticComp = entity.components.StaticMapEntity; continue;
const underlays = underlayComp.underlays; }
for (let i = 0; i < underlays.length; ++i) {
const { pos, direction } = underlays[i];
const transformedPos = staticComp.localTileToWorld(pos);
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) { const staticComp = entity.components.StaticMapEntity;
continue; const underlays = underlayComp.underlays;
} for (let i = 0; i < underlays.length; ++i) {
const { pos, direction } = underlays[i];
const transformedPos = staticComp.localTileToWorld(pos);
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)]; // Culling
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
// SYNC with systems/belt.js:drawSingleEntity! continue;
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: (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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,180 +1,326 @@
import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate"; import { BaseItem } from "../base_item";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { enumColors } from "../colors";
import { BaseItem } from "../base_item"; import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
import { enumPinSlotType } from "../components/wired_pins"; import { enumPinSlotType } from "../components/wired_pins";
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON, BooleanItem } from "../items/boolean_item"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { enumItemProcessorTypes } from "../components/item_processor"; import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
export class LogicGateSystem extends GameSystemWithFilter { import { ShapeDefinition } from "../shape_definition";
constructor(root) { import { ShapeItem } from "../items/shape_item";
super(root, [LogicGateComponent]);
export class LogicGateSystem extends GameSystemWithFilter {
this.boundOperations = { constructor(root) {
[enumLogicGateType.and]: this.compute_AND.bind(this), super(root, [LogicGateComponent]);
[enumLogicGateType.not]: this.compute_NOT.bind(this),
[enumLogicGateType.xor]: this.compute_XOR.bind(this), this.boundOperations = {
[enumLogicGateType.or]: this.compute_OR.bind(this), [enumLogicGateType.and]: this.compute_AND.bind(this),
[enumLogicGateType.transistor]: this.compute_IF.bind(this), [enumLogicGateType.not]: this.compute_NOT.bind(this),
}; [enumLogicGateType.xor]: this.compute_XOR.bind(this),
} [enumLogicGateType.or]: this.compute_OR.bind(this),
[enumLogicGateType.transistor]: this.compute_IF.bind(this),
update() {
for (let i = 0; i < this.allEntities.length; ++i) { [enumLogicGateType.rotater]: this.compute_ROTATE.bind(this),
const entity = this.allEntities[i]; [enumLogicGateType.analyzer]: this.compute_ANALYZE.bind(this),
const logicComp = entity.components.LogicGate; [enumLogicGateType.cutter]: this.compute_CUT.bind(this),
const slotComp = entity.components.WiredPins; [enumLogicGateType.unstacker]: this.compute_UNSTACK.bind(this),
[enumLogicGateType.shapecompare]: this.compute_SHAPECOMPARE.bind(this),
const slotValues = []; };
}
for (let i = 0; i < slotComp.slots.length; ++i) {
const slot = slotComp.slots[i]; update() {
if (slot.type !== enumPinSlotType.logicalAcceptor) { for (let i = 0; i < this.allEntities.length; ++i) {
continue; const entity = this.allEntities[i];
} const logicComp = entity.components.LogicGate;
if (slot.linkedNetwork) { const slotComp = entity.components.WiredPins;
slotValues.push(slot.linkedNetwork.currentValue);
} else { const slotValues = [];
slotValues.push(null);
} for (let i = 0; i < slotComp.slots.length; ++i) {
} const slot = slotComp.slots[i];
if (slot.type !== enumPinSlotType.logicalAcceptor) {
const result = this.boundOperations[logicComp.type](slotValues); continue;
}
// @TODO: For now we hardcode the value to always be slot 0 if (slot.linkedNetwork) {
assert( slotValues.push(slot.linkedNetwork.currentValue);
slotValues.length === slotComp.slots.length - 1, } else {
"Bad slot config, should have N acceptor slots and 1 ejector" slotValues.push(null);
); }
assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector"); }
slotComp.slots[0].value = result; const result = this.boundOperations[logicComp.type](slotValues);
}
} if (Array.isArray(result)) {
let resultIndex = 0;
/** for (let i = 0; i < slotComp.slots.length; ++i) {
* @param {Array<BaseItem|null>} parameters const slot = slotComp.slots[i];
* @returns {BaseItem} if (slot.type !== enumPinSlotType.logicalEjector) {
*/ continue;
compute_AND(parameters) { }
assert(parameters.length === 2, "bad parameter count for AND"); slot.value = result[resultIndex++];
}
const param1 = parameters[0]; } else {
const param2 = parameters[1]; // @TODO: For now we hardcode the value to always be slot 0
if (!param1 || !param2) { assert(
// Not enough params slotValues.length === slotComp.slots.length - 1,
return BOOL_FALSE_SINGLETON; "Bad slot config, should have N acceptor slots and 1 ejector"
} );
assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
const itemType = param1.getItemType(); slotComp.slots[0].value = result;
}
if (itemType !== param2.getItemType()) { }
// Differing type }
return BOOL_FALSE_SINGLETON;
} /**
* @param {Array<BaseItem|null>} parameters
if (itemType === "boolean") { * @returns {BaseItem}
return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value */
? BOOL_TRUE_SINGLETON compute_AND(parameters) {
: BOOL_FALSE_SINGLETON; assert(parameters.length === 2, "bad parameter count for AND");
}
const param1 = parameters[0];
return BOOL_FALSE_SINGLETON; const param2 = parameters[1];
} if (!param1 || !param2) {
// Not enough params
/** return BOOL_FALSE_SINGLETON;
* @param {Array<BaseItem|null>} parameters }
* @returns {BaseItem}
*/ const itemType = param1.getItemType();
compute_NOT(parameters) {
const item = parameters[0]; if (itemType !== param2.getItemType()) {
if (!item) { // Differing type
return BOOL_TRUE_SINGLETON; return BOOL_FALSE_SINGLETON;
} }
if (item.getItemType() !== "boolean") { if (itemType === "boolean") {
// Not a boolean actually return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
return BOOL_FALSE_SINGLETON; ? BOOL_TRUE_SINGLETON
} : BOOL_FALSE_SINGLETON;
}
const value = /** @type {BooleanItem} */ (item).value;
return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON; return BOOL_FALSE_SINGLETON;
} }
/** /**
* @param {Array<BaseItem|null>} parameters * @param {Array<BaseItem|null>} parameters
* @returns {BaseItem} * @returns {BaseItem}
*/ */
compute_XOR(parameters) { compute_NOT(parameters) {
assert(parameters.length === 2, "bad parameter count for XOR"); const item = parameters[0];
if (!item) {
const param1 = parameters[0]; return BOOL_TRUE_SINGLETON;
const param2 = parameters[1]; }
if (!param1 && !param2) {
// Not enough params if (item.getItemType() !== "boolean") {
return BOOL_FALSE_SINGLETON; // Not a boolean actually
} return BOOL_FALSE_SINGLETON;
}
// Check for the right types
if (param1 && param1.getItemType() !== "boolean") { const value = /** @type {BooleanItem} */ (item).value;
return BOOL_FALSE_SINGLETON; return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
} }
if (param2 && param2.getItemType() !== "boolean") { /**
return BOOL_FALSE_SINGLETON; * @param {Array<BaseItem|null>} parameters
} * @returns {BaseItem}
*/
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0; compute_XOR(parameters) {
const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0; assert(parameters.length === 2, "bad parameter count for XOR");
return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON; const param1 = parameters[0];
} const param2 = parameters[1];
if (!param1 && !param2) {
/** // Not enough params
* @param {Array<BaseItem|null>} parameters return BOOL_FALSE_SINGLETON;
* @returns {BaseItem} }
*/
compute_OR(parameters) { // Check for the right types
assert(parameters.length === 2, "bad parameter count for OR"); if (param1 && param1.getItemType() !== "boolean") {
return BOOL_FALSE_SINGLETON;
const param1 = parameters[0]; }
const param2 = parameters[1];
if (!param1 && !param2) { if (param2 && param2.getItemType() !== "boolean") {
// Not enough params return BOOL_FALSE_SINGLETON;
return BOOL_FALSE_SINGLETON; }
}
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
const valueParam1 = const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
const valueParam2 = return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0; }
return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON; /**
} * @param {Array<BaseItem|null>} parameters
* @returns {BaseItem}
/** */
* @param {Array<BaseItem|null>} parameters compute_OR(parameters) {
* @returns {BaseItem} assert(parameters.length === 2, "bad parameter count for OR");
*/
compute_IF(parameters) { const param1 = parameters[0];
assert(parameters.length === 2, "bad parameter count for IF"); const param2 = parameters[1];
if (!param1 && !param2) {
const flag = parameters[0]; // Not enough params
const value = parameters[1]; return BOOL_FALSE_SINGLETON;
if (!flag || !value) { }
// Not enough params
return null; const valueParam1 =
} param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
const valueParam2 =
if (flag.getItemType() !== "boolean") { param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0;
// Flag is not a boolean
return null; return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
} }
// pass through item /**
if (/** @type {BooleanItem} */ (flag).value) { * @param {Array<BaseItem|null>} parameters
return value; * @returns {BaseItem}
} */
compute_IF(parameters) {
return null; assert(parameters.length === 2, "bad parameter count for IF");
}
} const flag = parameters[0];
const value = parameters[1];
if (!flag || !value) {
// Not enough params
return null;
}
if (flag.getItemType() !== "boolean") {
// Flag is not a boolean
return null;
}
// pass through item
if (/** @type {BooleanItem} */ (flag).value) {
return value;
}
return null;
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {BaseItem}
*/
compute_ROTATE(parameters) {
const item = parameters[0];
if (!item || item.getItemType() !== "shape") {
// Not a shape
return null;
}
const definition = /** @type {ShapeItem} */ (item).definition;
const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition);
return this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition);
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {[BaseItem, BaseItem]}
*/
compute_ANALYZE(parameters) {
const item = parameters[0];
if (!item || item.getItemType() !== "shape") {
// Not a shape
return [null, null];
}
const definition = /** @type {ShapeItem} */ (item).definition;
const lowerLayer = /** @type {import("../shape_definition").ShapeLayer} */ (definition.layers[0]);
if (!lowerLayer) {
return [null, null];
}
const topRightContent = lowerLayer[0];
if (!topRightContent || topRightContent.subShape === null) {
return [null, null];
}
const newDefinition = new ShapeDefinition({
layers: [
[
{ subShape: topRightContent.subShape, color: enumColors.uncolored },
{ subShape: topRightContent.subShape, color: enumColors.uncolored },
{ subShape: topRightContent.subShape, color: enumColors.uncolored },
{ subShape: topRightContent.subShape, color: enumColors.uncolored },
],
],
});
return [
COLOR_ITEM_SINGLETONS[topRightContent.color],
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(newDefinition),
];
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {[BaseItem, BaseItem]}
*/
compute_CUT(parameters) {
const item = parameters[0];
if (!item || item.getItemType() !== "shape") {
// Not a shape
return [null, null];
}
const definition = /** @type {ShapeItem} */ (item).definition;
const result = this.root.shapeDefinitionMgr.shapeActionCutHalf(definition);
return [
result[0].isEntirelyEmpty()
? null
: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(result[0]),
result[1].isEntirelyEmpty()
? null
: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(result[1]),
];
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {[BaseItem, BaseItem]}
*/
compute_UNSTACK(parameters) {
const item = parameters[0];
if (!item || item.getItemType() !== "shape") {
// Not a shape
return [null, null];
}
const definition = /** @type {ShapeItem} */ (item).definition;
const layers = /** @type {Array<import("../shape_definition").ShapeLayer>} */ (definition.layers);
const upperLayerDefinition = new ShapeDefinition({
layers: [layers[layers.length - 1]],
});
const lowerLayers = layers.slice(0, layers.length - 1);
const lowerLayerDefinition =
lowerLayers.length > 0 ? new ShapeDefinition({ layers: lowerLayers }) : null;
return [
lowerLayerDefinition
? this.root.shapeDefinitionMgr.getShapeItemFromDefinition(lowerLayerDefinition)
: null,
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(upperLayerDefinition),
];
}
/**
* @param {Array<BaseItem|null>} parameters
* @returns {BaseItem}
*/
compute_SHAPECOMPARE(parameters) {
const itemA = parameters[0];
const itemB = parameters[1];
return itemA &&
itemB &&
itemA.getItemType() === "shape" &&
itemB.getItemType() === "shape" &&
/** @type {ShapeItem} */ (itemA).definition.getHash() ===
/** @type {ShapeItem} */ (itemB).definition.getHash()
? BOOL_TRUE_SINGLETON
: BOOL_FALSE_SINGLETON;
}
}

View File

@ -13,7 +13,7 @@ export class MapResourcesSystem extends GameSystem {
*/ */
drawChunk(parameters, chunk) { drawChunk(parameters, chunk) {
const basicChunkBackground = this.root.buffers.getForKey({ const basicChunkBackground = this.root.buffers.getForKey({
key: "chunkres", key: "mapresourcebg",
subKey: chunk.renderKey, subKey: chunk.renderKey,
w: globalConfig.mapChunkSize, w: globalConfig.mapChunkSize,
h: globalConfig.mapChunkSize, h: globalConfig.mapChunkSize,
@ -42,10 +42,9 @@ export class MapResourcesSystem extends GameSystem {
const patch = chunk.patches[i]; const patch = chunk.patches[i];
const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize; const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize;
const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize; const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize;
const destSize = Math.min(80, 40 / parameters.zoomLevel); const diameter = Math.min(80, 40 / parameters.zoomLevel);
if (parameters.visibleRect.containsCircle(destX, destY, destSize / 2)) {
patch.item.drawCentered(destX, destY, parameters, destSize); patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
}
} }
} else { } else {
// HIGH QUALITY: Draw all items // HIGH QUALITY: Draw all items
@ -61,9 +60,12 @@ export class MapResourcesSystem extends GameSystem {
const destX = worldX + globalConfig.halfTileSize; const destX = worldX + globalConfig.halfTileSize;
const destY = worldY + globalConfig.halfTileSize; const destY = worldY + globalConfig.halfTileSize;
if (parameters.visibleRect.containsCircle(destX, destY, globalConfig.tileSize / 2)) { lowerItem.drawItemCenteredClipped(
lowerItem.drawCentered(destX, destY, parameters); destX,
} destY,
parameters,
globalConfig.defaultItemDiameter
);
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -317,7 +317,9 @@ export class WireSystem extends GameSystemWithFilter {
if ( if (
currentNetwork.providers.length > 0 && currentNetwork.providers.length > 0 &&
(currentNetwork.wires.length > 0 || currentNetwork.receivers.length > 0) (currentNetwork.wires.length > 0 ||
currentNetwork.receivers.length > 0 ||
currentNetwork.tunnels.length > 0)
) { ) {
this.networks.push(currentNetwork); this.networks.push(currentNetwork);
VERBOSE_WIRES && logger.log("Attached new network with uid", currentNetwork); VERBOSE_WIRES && logger.log("Attached new network with uid", currentNetwork);
@ -624,7 +626,7 @@ export class WireSystem extends GameSystemWithFilter {
assert(sprite, "Unknown wire type: " + wireType); assert(sprite, "Unknown wire type: " + wireType);
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
parameters.context.globalAlpha = opacity; parameters.context.globalAlpha = opacity;
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0); staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0);
parameters.context.globalAlpha = 1; parameters.context.globalAlpha = 1;
if (G_IS_DEV && globalConfig.debug.renderWireRotations) { if (G_IS_DEV && globalConfig.debug.renderWireRotations) {

View File

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

View File

@ -44,6 +44,7 @@ steamPage:
Nutze dein gesammeltes Wissen über die Maschinen und lasse deine Fabriken die gewünschten Formen der 18 verschiedenen Level abliefern. Schalte mit jedem Level neue Arbeitsschritte oder Gebäude frei. Das sollte dich schon für Stunden beschäftigt halten! Danach werden im Freispielmodus zufällige Formen generiert, die du ebenfalls abliefern kannst. Ich füge regelmäßig neue Funktionen hinzu und davon sind eine ganze Menge geplant! Nutze dein gesammeltes Wissen über die Maschinen und lasse deine Fabriken die gewünschten Formen der 18 verschiedenen Level abliefern. Schalte mit jedem Level neue Arbeitsschritte oder Gebäude frei. Das sollte dich schon für Stunden beschäftigt halten! Danach werden im Freispielmodus zufällige Formen generiert, die du ebenfalls abliefern kannst. Ich füge regelmäßig neue Funktionen hinzu und davon sind eine ganze Menge geplant!
Wenn du das Spiel erwirbst, erhälst du Zugriff auf die zusätzlichen Features der Standalone-Version. Das bedeutet, du kannst unter anderem die neuesten Updates zuerst spielen! Wenn du das Spiel erwirbst, erhälst du Zugriff auf die zusätzlichen Features der Standalone-Version. Das bedeutet, du kannst unter anderem die neuesten Updates zuerst spielen!
[b]Vorteile der Standalone[/b] [b]Vorteile der Standalone[/b]
@ -296,6 +297,7 @@ ingame:
copySelection: Kopieren copySelection: Kopieren
clearSelection: Auswahl aufheben clearSelection: Auswahl aufheben
pipette: Pipette pipette: Pipette
switchLayers: Ebenen wechseln switchLayers: Ebenen wechseln
# Names of the colors, used for the color blind mode # Names of the colors, used for the color blind mode
@ -832,7 +834,6 @@ keybindings:
Modifikator: stattdessen gegen den UZS rotieren Modifikator: stattdessen gegen den UZS rotieren
cycleBuildingVariants: Variante wählen cycleBuildingVariants: Variante wählen
confirmMassDelete: Massenlöschung bestätigen confirmMassDelete: Massenlöschung bestätigen
pasteLastBlueprint: Letzte Blaupause einfügen
cycleBuildings: Gebäude rotieren cycleBuildings: Gebäude rotieren
lockBeltDirection: Bandplaner aktivieren lockBeltDirection: Bandplaner aktivieren
switchDirectionLockSide: >- switchDirectionLockSide: >-
@ -846,8 +847,10 @@ keybindings:
placementDisableAutoOrientation: Automatische Orientierung deaktivieren placementDisableAutoOrientation: Automatische Orientierung deaktivieren
placeMultiple: Im Platziermodus bleiben placeMultiple: Im Platziermodus bleiben
placeInverse: Automatische Förderbandorientierung invertieren placeInverse: Automatische Förderbandorientierung invertieren
advanced_processor: Farbnivertierer pasteLastBlueprint: Letzte Blaupause einfügen
advanced_processor: Farbinvertierer
energy_generator: Energiegenerator energy_generator: Energiegenerator
wire: Energiekabel
about: about:
title: Über dieses Spiel title: Über dieses Spiel

File diff suppressed because it is too large Load Diff