Merge pull request #1 from jimmykarns/master
update removed enum layer with tests and bridge reserves
BIN
res/ui/building_icons/virtual_processor.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 567 KiB After Width: | Height: | Size: 601 KiB |
@ -277,6 +277,7 @@
|
||||
<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.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/buildings/constant_signal.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_exit-tier2.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/wires/lever_on.png</key>
|
||||
<key type="filename">sprites/wires/sets/color_cross.png</key>
|
||||
@ -533,6 +535,8 @@
|
||||
</struct>
|
||||
<key type="filename">sprites/wires/boolean_false.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>
|
||||
<struct type="IndividualSpriteSettings">
|
||||
<key>pivotPoint</key>
|
||||
|
BIN
res_raw/sprites/blueprints/virtual_processor-analyzer.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
res_raw/sprites/blueprints/virtual_processor-rotater.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
res_raw/sprites/blueprints/virtual_processor-shapecompare.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
res_raw/sprites/blueprints/virtual_processor-unstacker.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
res_raw/sprites/blueprints/virtual_processor.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
res_raw/sprites/buildings/virtual_processor-analyzer.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
res_raw/sprites/buildings/virtual_processor-rotater.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
res_raw/sprites/buildings/virtual_processor-shapecompare.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
res_raw/sprites/buildings/virtual_processor-unstacker.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
res_raw/sprites/buildings/virtual_processor.png
Normal file
After Width: | Height: | Size: 10 KiB |
@ -1,38 +1,38 @@
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
|
||||
constant_signal, logic_gate, lever, filter, wire_tunnel, display;
|
||||
|
||||
@each $building in $buildings {
|
||||
[data-icon="building_icons/#{$building}.png"] {
|
||||
background-image: uiResource("res/ui/building_icons/#{$building}.png") !important;
|
||||
}
|
||||
}
|
||||
|
||||
$buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt,
|
||||
underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl,
|
||||
stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage;
|
||||
@each $building in $buildingsAndVariants {
|
||||
[data-icon="building_tutorials/#{$building}.png"] {
|
||||
background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case
|
||||
[data-icon="building_tutorials/painter-mirrored.png"] {
|
||||
background-image: uiResource("res/ui/building_tutorials/painter.png") !important;
|
||||
}
|
||||
|
||||
$icons: notification_saved, notification_success, notification_upgrade;
|
||||
@each $icon in $icons {
|
||||
[data-icon="icons/#{$icon}.png"] {
|
||||
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,
|
||||
th, hu, pl, ja, kor, no, pt-PT;
|
||||
|
||||
@each $language in $languages {
|
||||
[data-languageicon="#{$language}"] {
|
||||
background-image: uiResource("languages/#{$language}.svg") !important;
|
||||
}
|
||||
}
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
|
||||
constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor;
|
||||
|
||||
@each $building in $buildings {
|
||||
[data-icon="building_icons/#{$building}.png"] {
|
||||
background-image: uiResource("res/ui/building_icons/#{$building}.png") !important;
|
||||
}
|
||||
}
|
||||
|
||||
$buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt,
|
||||
underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl,
|
||||
stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage;
|
||||
@each $building in $buildingsAndVariants {
|
||||
[data-icon="building_tutorials/#{$building}.png"] {
|
||||
background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case
|
||||
[data-icon="building_tutorials/painter-mirrored.png"] {
|
||||
background-image: uiResource("res/ui/building_tutorials/painter.png") !important;
|
||||
}
|
||||
|
||||
$icons: notification_saved, notification_success, notification_upgrade;
|
||||
@each $icon in $icons {
|
||||
[data-icon="icons/#{$icon}.png"] {
|
||||
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,
|
||||
th, hu, pl, ja, kor, no, pt-PT;
|
||||
|
||||
@each $language in $languages {
|
||||
[data-languageicon="#{$language}"] {
|
||||
background-image: uiResource("languages/#{$language}.svg") !important;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { round1Digit } from "./utils";
|
||||
|
||||
const logger = createLogger("buffers");
|
||||
|
||||
const bufferGcDurationSeconds = 10;
|
||||
const bufferGcDurationSeconds = 5;
|
||||
|
||||
export class BufferMaintainer {
|
||||
/**
|
||||
@ -27,6 +27,31 @@ export class BufferMaintainer {
|
||||
|
||||
this.iterationIndex = 1;
|
||||
this.lastIteration = 0;
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.update, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the buffer stats
|
||||
*/
|
||||
getStats() {
|
||||
let stats = {
|
||||
rootKeys: 0,
|
||||
subKeys: 0,
|
||||
vramBytes: 0,
|
||||
};
|
||||
this.cache.forEach((subCache, key) => {
|
||||
++stats.rootKeys;
|
||||
|
||||
subCache.forEach((cacheEntry, subKey) => {
|
||||
++stats.subKeys;
|
||||
|
||||
const canvas = cacheEntry.canvas;
|
||||
stats.vramBytes += canvas.width * canvas.height * 4;
|
||||
});
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,8 @@ export const globalConfig = {
|
||||
beltSpeedItemsPerSecond: 2,
|
||||
minerSpeedItemsPerSecond: 0, // COMPUTED
|
||||
|
||||
defaultItemDiameter: 20,
|
||||
|
||||
itemSpacingOnBelts: 0.63,
|
||||
|
||||
wiresSpeedItemsPerSecond: 6,
|
||||
|
@ -95,5 +95,14 @@ export default {
|
||||
// Whether to items / s instead of items / m in stats
|
||||
// detailedStatistics: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Shows detailed information about which atlas is used
|
||||
// showAtlasInfo: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Renders the rotation of all wires
|
||||
// renderWireRotations: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Renders information about wire networks
|
||||
// renderWireNetworkInfos: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
/* dev:end */
|
||||
};
|
||||
|
@ -53,27 +53,6 @@ export class Rectangle {
|
||||
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
|
||||
* @returns {Rectangle}
|
||||
@ -82,28 +61,6 @@ export class Rectangle {
|
||||
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 {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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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}
|
||||
*/
|
||||
toTileCullRectangle() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { BasicSerializableObject } from "../savegame/serialization";
|
||||
|
||||
@ -57,7 +58,22 @@ export class BaseItem extends BasicSerializableObject {
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
drawCentered(x, y, parameters, diameter) {}
|
||||
drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
if (parameters.visibleRect.containsCircle(x, y, diameter / 2)) {
|
||||
this.drawItemCenteredImpl(x, y, parameters, diameter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
abstract;
|
||||
}
|
||||
|
||||
getBackgroundColorAsResource() {
|
||||
abstract;
|
||||
|
@ -1194,9 +1194,13 @@ export class BeltPath extends BasicSerializableObject {
|
||||
const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
|
||||
|
||||
const distanceAndItem = this.items[currentItemIndex];
|
||||
if (parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, 10)) {
|
||||
distanceAndItem[_item].drawCentered(worldPos.x, worldPos.y, parameters);
|
||||
}
|
||||
|
||||
distanceAndItem[_item].drawItemCenteredClipped(
|
||||
worldPos.x,
|
||||
worldPos.y,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
|
||||
// Check for the next item
|
||||
currentItemPos += distanceAndItem[_nextDistance];
|
||||
|
@ -92,7 +92,7 @@ export class Blueprint {
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, staticComp.getBlueprintSprite(), 0, newPos);
|
||||
staticComp.drawSpriteOnBoundsClipped(parameters, staticComp.getBlueprintSprite(), 0, newPos);
|
||||
}
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
151
src/js/game/buildings/virtual_processor.js
Normal 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({}));
|
||||
}
|
||||
}
|
@ -1,30 +1,36 @@
|
||||
import { Component } from "../component";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumLogicGateType = {
|
||||
and: "and",
|
||||
not: "not",
|
||||
xor: "xor",
|
||||
or: "or",
|
||||
transistor: "transistor",
|
||||
};
|
||||
|
||||
export class LogicGateComponent extends Component {
|
||||
static getId() {
|
||||
return "LogicGate";
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
return new LogicGateComponent({ type: this.type });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {enumLogicGateType=} param0.type
|
||||
*/
|
||||
constructor({ type = enumLogicGateType.and }) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
import { Component } from "../component";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumLogicGateType = {
|
||||
and: "and",
|
||||
not: "not",
|
||||
xor: "xor",
|
||||
or: "or",
|
||||
transistor: "transistor",
|
||||
|
||||
analyzer: "analyzer",
|
||||
rotater: "rotater",
|
||||
unstacker: "unstacker",
|
||||
cutter: "cutter",
|
||||
shapecompare: "shapecompare",
|
||||
};
|
||||
|
||||
export class LogicGateComponent extends Component {
|
||||
static getId() {
|
||||
return "LogicGate";
|
||||
}
|
||||
|
||||
duplicateWithoutContents() {
|
||||
return new LogicGateComponent({ type: this.type });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {enumLogicGateType=} param0.type
|
||||
*/
|
||||
constructor({ type = enumLogicGateType.and }) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
@ -162,8 +162,9 @@ export class StaticMapEntityComponent extends Component {
|
||||
* @returns {Vector}
|
||||
*/
|
||||
localTileToWorld(localTile) {
|
||||
const result = this.applyRotationToVector(localTile);
|
||||
result.addInplace(this.origin);
|
||||
const result = localTile.rotateFastMultipleOf90(this.rotation);
|
||||
result.x += this.origin.x;
|
||||
result.y += this.origin.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -235,7 +236,7 @@ export class StaticMapEntityComponent extends Component {
|
||||
* @param {number=} extrudePixels How many pixels to extrude the sprite
|
||||
* @param {Vector=} overridePosition Whether to drwa the entity at a different location
|
||||
*/
|
||||
drawSpriteOnFullEntityBounds(parameters, sprite, extrudePixels = 0, overridePosition = null) {
|
||||
drawSpriteOnBoundsClipped(parameters, sprite, extrudePixels = 0, overridePosition = null) {
|
||||
if (!this.shouldBeDrawn(parameters) && !overridePosition) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { typeItemSingleton } from "../item_resolver";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumPinSlotType = {
|
||||
@ -27,6 +29,16 @@ export class WiredPinsComponent extends Component {
|
||||
return "WiredPins";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
slots: types.array(
|
||||
types.structured({
|
||||
value: types.nullable(typeItemSingleton),
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
|
@ -9,7 +9,7 @@ import { DrawParameters } from "../core/draw_parameters";
|
||||
import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { randomInt, round2Digits } from "../core/utils";
|
||||
import { randomInt, round2Digits, round3Digits } from "../core/utils";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Savegame } from "../savegame/savegame";
|
||||
import { SavegameSerializer } from "../savegame/savegame_serializer";
|
||||
@ -329,8 +329,7 @@ export class GameCore {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update buffers as the very first
|
||||
root.buffers.update();
|
||||
this.root.signals.gameFrameStarted.dispatch();
|
||||
|
||||
root.queue.requireRedraw = false;
|
||||
|
||||
@ -390,33 +389,24 @@ export class GameCore {
|
||||
// Map overview
|
||||
root.map.drawOverlay(params);
|
||||
} else {
|
||||
// Background (grid, resources, etc)
|
||||
root.map.drawBackground(params);
|
||||
|
||||
// Belt items
|
||||
systems.belt.drawBeltItems(params);
|
||||
|
||||
// Items being ejected / accepted currently (animations)
|
||||
systems.itemEjector.draw(params);
|
||||
systems.itemAcceptor.draw(params);
|
||||
|
||||
// Miner & Static map entities
|
||||
// Miner & Static map entities etc.
|
||||
root.map.drawForeground(params);
|
||||
|
||||
// HUB Overlay
|
||||
systems.hub.draw(params);
|
||||
|
||||
// Storage items
|
||||
systems.storage.draw(params);
|
||||
|
||||
// Green wires overlay
|
||||
root.hud.parts.wiresOverlay.draw(params);
|
||||
|
||||
if (this.root.currentLayer === "wires") {
|
||||
// Static map entities
|
||||
root.map.drawWiresForegroundLayer(params);
|
||||
|
||||
// pins
|
||||
systems.wiredPins.draw(params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,7 +454,7 @@ export class GameCore {
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.showAtlasInfo) {
|
||||
context.font = "13px GameFont";
|
||||
context.fillStyle = "yellow";
|
||||
context.fillStyle = "blue";
|
||||
context.fillText(
|
||||
"Atlas: " +
|
||||
desiredAtlasScale +
|
||||
@ -472,8 +462,22 @@ export class GameCore {
|
||||
round2Digits(zoomLevel) +
|
||||
" / Effective Zoom: " +
|
||||
round2Digits(effectiveZoomLevel),
|
||||
200,
|
||||
20
|
||||
20,
|
||||
600
|
||||
);
|
||||
|
||||
const stats = this.root.buffers.getStats();
|
||||
context.fillText(
|
||||
"Buffers: " +
|
||||
stats.rootKeys +
|
||||
" root keys, " +
|
||||
stats.subKeys +
|
||||
" sub keys / buffers / VRAM: " +
|
||||
round2Digits(stats.vramBytes / (1024 * 1024)) +
|
||||
" MB",
|
||||
|
||||
20,
|
||||
620
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,7 @@ import { Entity } from "./entity";
|
||||
import { GameRoot } from "./root";
|
||||
import { GameSystem } from "./game_system";
|
||||
import { arrayDelete, arrayDeleteValue } from "../core/utils";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { globalConfig } from "../core/config";
|
||||
|
||||
export class GameSystemWithFilter extends GameSystem {
|
||||
/**
|
||||
* Constructs a new game system with the given component filter. It will process
|
||||
@ -35,80 +34,6 @@ export class GameSystemWithFilter extends GameSystem {
|
||||
this.root.signals.bulkOperationFinished.add(this.refreshCaches, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function for each matching entity on the screen, useful for drawing them
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {function} callback
|
||||
*/
|
||||
forEachMatchingEntityOnScreen(parameters, callback) {
|
||||
const cullRange = parameters.visibleRect.toTileCullRectangle();
|
||||
if (this.allEntities.length < 100) {
|
||||
// So, its much quicker to simply perform per-entity checking
|
||||
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
if (cullRange.containsRect(entity.components.StaticMapEntity.getTileSpaceBounds())) {
|
||||
callback(parameters, entity);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const top = cullRange.top();
|
||||
const right = cullRange.right();
|
||||
const bottom = cullRange.bottom();
|
||||
const left = cullRange.left();
|
||||
|
||||
const border = 1;
|
||||
const minY = top - border;
|
||||
const maxY = bottom + border;
|
||||
const minX = left - border;
|
||||
const maxX = right + border - 1;
|
||||
|
||||
const map = this.root.map;
|
||||
|
||||
let seenUids = new Set();
|
||||
|
||||
const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
|
||||
const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
|
||||
|
||||
const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
|
||||
const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
|
||||
|
||||
const requiredComponents = this.requiredComponentIds;
|
||||
|
||||
// Render y from top down for proper blending
|
||||
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
|
||||
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
|
||||
const chunk = map.getChunk(chunkX, chunkY, false);
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// BIG TODO: CULLING ON AN ENTITY BASIS
|
||||
|
||||
const entities = chunk.containedEntities;
|
||||
entityLoop: for (let i = 0; i < entities.length; ++i) {
|
||||
const entity = entities[i];
|
||||
|
||||
// Avoid drawing twice
|
||||
if (seenUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenUids.add(entity.uid);
|
||||
|
||||
for (let i = 0; i < requiredComponents.length; ++i) {
|
||||
if (!entity.components[requiredComponents[i]]) {
|
||||
continue entityLoop;
|
||||
}
|
||||
}
|
||||
callback(parameters, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
|
@ -374,7 +374,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
// HACK to draw the entity sprite
|
||||
const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get());
|
||||
staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite);
|
||||
staticComp.drawSpriteOnBoundsClipped(parameters, previewSprite);
|
||||
staticComp.origin = mouseTile;
|
||||
|
||||
// Draw ejectors
|
||||
|
@ -78,7 +78,7 @@ export class HUDSandboxController extends BaseHUDPart {
|
||||
if (!this.root.hubGoals.storedShapes[blueprintShape]) {
|
||||
this.root.hubGoals.storedShapes[blueprintShape] = 0;
|
||||
}
|
||||
this.root.hubGoals.storedShapes[blueprintShape] += 1e4;
|
||||
this.root.hubGoals.storedShapes[blueprintShape] += 1e9;
|
||||
}
|
||||
|
||||
maxOutAll() {
|
||||
|
@ -64,11 +64,16 @@ export class HUDWireInfo extends BaseHUDPart {
|
||||
const network = networks[0];
|
||||
|
||||
if (network.valueConflict) {
|
||||
this.spriteConflict.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40);
|
||||
this.spriteConflict.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
|
||||
} else if (!network.currentValue) {
|
||||
this.spriteEmpty.draw(parameters.context, mousePos.x + 10, mousePos.y - 10, 40, 40);
|
||||
this.spriteEmpty.draw(parameters.context, mousePos.x + 15, mousePos.y - 10, 60, 60);
|
||||
} else {
|
||||
network.currentValue.drawCentered(mousePos.x + 20, mousePos.y, parameters, 40);
|
||||
network.currentValue.drawItemCenteredClipped(
|
||||
mousePos.x + 40,
|
||||
mousePos.y + 10,
|
||||
parameters,
|
||||
60
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,27 @@
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
import { MetaWireBuilding } from "../../buildings/wire";
|
||||
import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding } from "../../buildings/logic_gate";
|
||||
import { MetaLeverBuilding } from "../../buildings/lever";
|
||||
import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel";
|
||||
|
||||
const supportedBuildings = [
|
||||
MetaWireBuilding,
|
||||
MetaWireTunnelBuilding,
|
||||
MetaConstantSignalBuilding,
|
||||
MetaLogicGateBuilding,
|
||||
MetaLeverBuilding,
|
||||
];
|
||||
|
||||
export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||
constructor(root) {
|
||||
super(root, {
|
||||
supportedBuildings,
|
||||
visibilityCondition: () =>
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
|
||||
htmlElementId: "ingame_HUD_wires_toolbar",
|
||||
});
|
||||
}
|
||||
}
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
import { MetaWireBuilding } from "../../buildings/wire";
|
||||
import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding } from "../../buildings/logic_gate";
|
||||
import { MetaLeverBuilding } from "../../buildings/lever";
|
||||
import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel";
|
||||
import { MetaVirtualProcessorBuilding } from "../../buildings/virtual_processor";
|
||||
|
||||
const supportedBuildings = [
|
||||
MetaWireBuilding,
|
||||
MetaWireTunnelBuilding,
|
||||
MetaConstantSignalBuilding,
|
||||
MetaLogicGateBuilding,
|
||||
MetaLeverBuilding,
|
||||
MetaVirtualProcessorBuilding,
|
||||
];
|
||||
|
||||
export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||
constructor(root) {
|
||||
super(root, {
|
||||
supportedBuildings,
|
||||
visibilityCondition: () =>
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
|
||||
htmlElementId: "ingame_HUD_wires_toolbar",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { globalConfig } from "../../core/config";
|
||||
|
||||
export class BooleanItem extends BaseItem {
|
||||
static getId() {
|
||||
@ -46,7 +47,7 @@ export class BooleanItem extends BaseItem {
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawCentered(x, y, parameters, diameter = 12) {
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
let sprite;
|
||||
if (this.value) {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
|
||||
|
@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors, enumColorsToHexCode } from "../colors";
|
||||
import { THEME } from "../theme";
|
||||
import { drawSpriteClipped } from "../../core/draw_utils";
|
||||
|
||||
export class ColorItem extends BaseItem {
|
||||
static getId() {
|
||||
@ -54,23 +55,33 @@ export class ColorItem extends BaseItem {
|
||||
* @param {number} diameter
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawCentered(x, y, parameters, diameter = 12) {
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
if (!this.bufferGenerator) {
|
||||
this.bufferGenerator = this.internalGenerateColorBuffer.bind(this);
|
||||
}
|
||||
|
||||
const realDiameter = diameter * 0.6;
|
||||
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
|
||||
|
||||
const key = diameter + "/" + dpi;
|
||||
const key = realDiameter + "/" + dpi + "/" + this.color;
|
||||
const canvas = parameters.root.buffers.getForKey({
|
||||
key,
|
||||
subKey: this.color,
|
||||
w: diameter,
|
||||
h: diameter,
|
||||
key: "coloritem",
|
||||
subKey: key,
|
||||
w: realDiameter,
|
||||
h: realDiameter,
|
||||
dpi,
|
||||
redrawMethod: this.bufferGenerator,
|
||||
});
|
||||
parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter);
|
||||
|
||||
drawSpriteClipped({
|
||||
parameters,
|
||||
sprite: canvas,
|
||||
x: x - realDiameter / 2,
|
||||
y: y - realDiameter / 2,
|
||||
w: realDiameter,
|
||||
h: realDiameter,
|
||||
originalW: realDiameter * dpi,
|
||||
originalH: realDiameter * dpi,
|
||||
});
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
@ -3,6 +3,7 @@ import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { THEME } from "../theme";
|
||||
import { globalConfig } from "../../core/config";
|
||||
|
||||
export class ShapeItem extends BaseItem {
|
||||
static getId() {
|
||||
@ -55,7 +56,7 @@ export class ShapeItem extends BaseItem {
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {number=} diameter
|
||||
*/
|
||||
drawCentered(x, y, parameters, diameter) {
|
||||
drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||
this.definition.drawCentered(x, y, parameters, diameter);
|
||||
}
|
||||
}
|
||||
|
@ -1,456 +1,457 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "./root";
|
||||
import { InputReceiver } from "../core/input_receiver";
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { Signal, STOP_PROPAGATION } from "../core/signal";
|
||||
import { IS_MOBILE } from "../core/config";
|
||||
import { T } from "../translations";
|
||||
function key(str) {
|
||||
return str.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
export const KEYMAPPINGS = {
|
||||
general: {
|
||||
confirm: { keyCode: 13 }, // enter
|
||||
back: { keyCode: 27, builtin: true }, // escape
|
||||
},
|
||||
|
||||
ingame: {
|
||||
menuOpenShop: { keyCode: key("F") },
|
||||
menuOpenStats: { keyCode: key("G") },
|
||||
menuClose: { keyCode: key("Q") },
|
||||
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
exportScreenshot: { keyCode: 114 }, // F3PS
|
||||
toggleFPSInfo: { keyCode: 115 }, // F4
|
||||
|
||||
switchLayers: { keyCode: key("Y") },
|
||||
},
|
||||
|
||||
navigation: {
|
||||
mapMoveUp: { keyCode: key("W") },
|
||||
mapMoveRight: { keyCode: key("D") },
|
||||
mapMoveDown: { keyCode: key("S") },
|
||||
mapMoveLeft: { keyCode: key("A") },
|
||||
mapMoveFaster: { keyCode: 16 }, //shift
|
||||
|
||||
centerMap: { keyCode: 32 }, // SPACE
|
||||
mapZoomIn: { keyCode: 187, repeated: true }, // "+"
|
||||
mapZoomOut: { keyCode: 189, repeated: true }, // "-"
|
||||
|
||||
createMarker: { keyCode: key("M") },
|
||||
},
|
||||
|
||||
buildings: {
|
||||
belt: { keyCode: key("1") },
|
||||
splitter: { keyCode: key("2") },
|
||||
underground_belt: { keyCode: key("3") },
|
||||
miner: { keyCode: key("4") },
|
||||
cutter: { keyCode: key("5") },
|
||||
rotater: { keyCode: key("6") },
|
||||
stacker: { keyCode: key("7") },
|
||||
mixer: { keyCode: key("8") },
|
||||
painter: { keyCode: key("9") },
|
||||
trash: { keyCode: key("0") },
|
||||
|
||||
lever: { keyCode: key("L") },
|
||||
filter: { keyCode: key("B") },
|
||||
display: { keyCode: key("N") },
|
||||
|
||||
wire: { keyCode: key("1") },
|
||||
wire_tunnel: { keyCode: key("2") },
|
||||
constant_signal: { keyCode: key("3") },
|
||||
logic_gate: { keyCode: key("4") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
pipette: { keyCode: key("Q") },
|
||||
rotateWhilePlacing: { keyCode: key("R") },
|
||||
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
||||
cycleBuildingVariants: { keyCode: key("T") },
|
||||
cycleBuildings: { keyCode: 9 }, // TAB
|
||||
switchDirectionLockSide: { keyCode: key("R") },
|
||||
},
|
||||
|
||||
massSelect: {
|
||||
massSelectStart: { keyCode: 17 }, // CTRL
|
||||
massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
|
||||
massSelectCopy: { keyCode: key("C") },
|
||||
massSelectCut: { keyCode: key("X") },
|
||||
confirmMassDelete: { keyCode: 46 }, // DEL
|
||||
pasteLastBlueprint: { keyCode: key("V") },
|
||||
},
|
||||
|
||||
placementModifiers: {
|
||||
lockBeltDirection: { keyCode: 16 }, // SHIFT
|
||||
placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
|
||||
placeMultiple: { keyCode: 16 }, // SHIFT
|
||||
placeInverse: { keyCode: 18 }, // ALT
|
||||
},
|
||||
};
|
||||
|
||||
// Assign ids
|
||||
for (const categoryId in KEYMAPPINGS) {
|
||||
for (const mappingId in KEYMAPPINGS[categoryId]) {
|
||||
KEYMAPPINGS[categoryId][mappingId].id = mappingId;
|
||||
}
|
||||
}
|
||||
|
||||
export const KEYCODE_LMB = 1;
|
||||
export const KEYCODE_MMB = 2;
|
||||
export const KEYCODE_RMB = 3;
|
||||
|
||||
/**
|
||||
* Returns a keycode -> string
|
||||
* @param {number} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getStringForKeyCode(code) {
|
||||
switch (code) {
|
||||
case KEYCODE_LMB:
|
||||
return "LMB";
|
||||
case KEYCODE_MMB:
|
||||
return "MMB";
|
||||
case KEYCODE_RMB:
|
||||
return "RMB";
|
||||
case 4:
|
||||
return "MB4";
|
||||
case 5:
|
||||
return "MB5";
|
||||
case 8:
|
||||
return "⌫";
|
||||
case 9:
|
||||
return T.global.keys.tab;
|
||||
case 13:
|
||||
return "⏎";
|
||||
case 16:
|
||||
return "⇪";
|
||||
case 17:
|
||||
return T.global.keys.control;
|
||||
case 18:
|
||||
return T.global.keys.alt;
|
||||
case 19:
|
||||
return "PAUSE";
|
||||
case 20:
|
||||
return "CAPS";
|
||||
case 27:
|
||||
return T.global.keys.escape;
|
||||
case 32:
|
||||
return T.global.keys.space;
|
||||
case 33:
|
||||
return "PGUP";
|
||||
case 34:
|
||||
return "PGDOWN";
|
||||
case 35:
|
||||
return "END";
|
||||
case 36:
|
||||
return "HOME";
|
||||
case 37:
|
||||
return "⬅";
|
||||
case 38:
|
||||
return "⬆";
|
||||
case 39:
|
||||
return "➡";
|
||||
case 40:
|
||||
return "⬇";
|
||||
case 44:
|
||||
return "PRNT";
|
||||
case 45:
|
||||
return "INS";
|
||||
case 46:
|
||||
return "DEL";
|
||||
case 93:
|
||||
return "SEL";
|
||||
case 96:
|
||||
return "NUM 0";
|
||||
case 97:
|
||||
return "NUM 1";
|
||||
case 98:
|
||||
return "NUM 2";
|
||||
case 99:
|
||||
return "NUM 3";
|
||||
case 100:
|
||||
return "NUM 4";
|
||||
case 101:
|
||||
return "NUM 5";
|
||||
case 102:
|
||||
return "NUM 6";
|
||||
case 103:
|
||||
return "NUM 7";
|
||||
case 104:
|
||||
return "NUM 8";
|
||||
case 105:
|
||||
return "NUM 9";
|
||||
case 106:
|
||||
return "*";
|
||||
case 107:
|
||||
return "+";
|
||||
case 109:
|
||||
return "-";
|
||||
case 110:
|
||||
return ".";
|
||||
case 111:
|
||||
return "/";
|
||||
case 112:
|
||||
return "F1";
|
||||
case 113:
|
||||
return "F2";
|
||||
case 114:
|
||||
return "F3";
|
||||
case 115:
|
||||
return "F4";
|
||||
case 116:
|
||||
return "F4";
|
||||
case 117:
|
||||
return "F5";
|
||||
case 118:
|
||||
return "F6";
|
||||
case 119:
|
||||
return "F7";
|
||||
case 120:
|
||||
return "F8";
|
||||
case 121:
|
||||
return "F9";
|
||||
case 122:
|
||||
return "F10";
|
||||
case 123:
|
||||
return "F11";
|
||||
case 124:
|
||||
return "F12";
|
||||
|
||||
case 144:
|
||||
return "NUMLOCK";
|
||||
case 145:
|
||||
return "SCRLOCK";
|
||||
case 182:
|
||||
return "COMP";
|
||||
case 183:
|
||||
return "CALC";
|
||||
case 186:
|
||||
return ";";
|
||||
case 187:
|
||||
return "+";
|
||||
case 188:
|
||||
return ",";
|
||||
case 189:
|
||||
return "-";
|
||||
case 191:
|
||||
return "/";
|
||||
case 219:
|
||||
return "[";
|
||||
case 220:
|
||||
return "\\";
|
||||
case 221:
|
||||
return "]";
|
||||
case 222:
|
||||
return "'";
|
||||
}
|
||||
|
||||
return String.fromCharCode(code);
|
||||
}
|
||||
|
||||
export class Keybinding {
|
||||
/**
|
||||
*
|
||||
* @param {KeyActionMapper} keyMapper
|
||||
* @param {Application} app
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @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);
|
||||
this.keyMapper = keyMapper;
|
||||
this.app = app;
|
||||
this.keyCode = keyCode;
|
||||
this.builtin = builtin;
|
||||
this.repeated = repeated;
|
||||
|
||||
this.signal = new Signal();
|
||||
this.toggled = new Signal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this binding is currently pressed
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get pressed() {
|
||||
// Check if the key is down
|
||||
if (this.app.inputMgr.keysDown.has(this.keyCode)) {
|
||||
// Check if it is the top reciever
|
||||
const reciever = this.keyMapper.inputReceiver;
|
||||
return this.app.inputMgr.getTopReciever() === reciever;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener
|
||||
* @param {function() : void} receiver
|
||||
* @param {object=} 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
|
||||
* */
|
||||
appendLabelToElement(elem) {
|
||||
if (IS_MOBILE) {
|
||||
return null;
|
||||
}
|
||||
const spacer = document.createElement("code");
|
||||
spacer.classList.add("keybinding");
|
||||
spacer.innerHTML = getStringForKeyCode(this.keyCode);
|
||||
elem.appendChild(spacer);
|
||||
return spacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key code as a nice string
|
||||
*/
|
||||
getKeyCodeString() {
|
||||
return getStringForKeyCode(this.keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remvoes all signal receivers
|
||||
*/
|
||||
clearSignalReceivers() {
|
||||
this.signal.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyActionMapper {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
* @param {InputReceiver} inputReciever
|
||||
*/
|
||||
constructor(root, inputReciever) {
|
||||
this.root = root;
|
||||
this.inputReceiver = inputReciever;
|
||||
|
||||
inputReciever.keydown.add(this.handleKeydown, this);
|
||||
inputReciever.keyup.add(this.handleKeyup, this);
|
||||
|
||||
/** @type {Object.<string, Keybinding>} */
|
||||
this.keybindings = {};
|
||||
|
||||
const overrides = root.app.settings.getKeybindingOverrides();
|
||||
|
||||
for (const category in KEYMAPPINGS) {
|
||||
for (const key in KEYMAPPINGS[category]) {
|
||||
let payload = Object.assign({}, KEYMAPPINGS[category][key]);
|
||||
if (overrides[key]) {
|
||||
payload.keyCode = overrides[key];
|
||||
}
|
||||
|
||||
this.keybindings[key] = new Keybinding(this, this.root.app, payload);
|
||||
}
|
||||
}
|
||||
|
||||
inputReciever.pageBlur.add(this.onPageBlur, this);
|
||||
inputReciever.destroyed.add(this.cleanup, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all keybindings starting with the given id
|
||||
* @param {string} pattern
|
||||
* @returns {Array<Keybinding>}
|
||||
*/
|
||||
getKeybindingsStartingWith(pattern) {
|
||||
let result = [];
|
||||
for (const key in this.keybindings) {
|
||||
if (key.startsWith(pattern)) {
|
||||
result.push(this.keybindings[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the given events to the other mapper (used in tooltips)
|
||||
* @param {KeyActionMapper} receiver
|
||||
* @param {Array<string>} bindings
|
||||
*/
|
||||
forward(receiver, bindings) {
|
||||
for (let i = 0; i < bindings.length; ++i) {
|
||||
const key = bindings[i];
|
||||
this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
for (const key in this.keybindings) {
|
||||
this.keybindings[key].signal.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
onPageBlur() {
|
||||
// Reset all down states
|
||||
// Find mapping
|
||||
for (const key in this.keybindings) {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal keydown handler
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
* @param {boolean=} param0.initial
|
||||
*/
|
||||
handleKeydown({ keyCode, shift, alt, initial }) {
|
||||
let stop = false;
|
||||
|
||||
// Find mapping
|
||||
for (const key in this.keybindings) {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
if (binding.keyCode === keyCode && (initial || binding.repeated)) {
|
||||
/** @type {Signal} */
|
||||
const signal = this.keybindings[key].signal;
|
||||
if (signal.dispatch() === STOP_PROPAGATION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal keyup handler
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
*/
|
||||
handleKeyup({ keyCode, shift, alt }) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a given keybinding
|
||||
* @param {{ keyCode: number }} binding
|
||||
* @returns {Keybinding}
|
||||
*/
|
||||
getBinding(binding) {
|
||||
// @ts-ignore
|
||||
const id = binding.id;
|
||||
assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
|
||||
assert(this.keybindings[id], "Keybinding " + id + " not known!");
|
||||
return this.keybindings[id];
|
||||
}
|
||||
}
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "./root";
|
||||
import { InputReceiver } from "../core/input_receiver";
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { Signal, STOP_PROPAGATION } from "../core/signal";
|
||||
import { IS_MOBILE } from "../core/config";
|
||||
import { T } from "../translations";
|
||||
function key(str) {
|
||||
return str.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
export const KEYMAPPINGS = {
|
||||
general: {
|
||||
confirm: { keyCode: 13 }, // enter
|
||||
back: { keyCode: 27, builtin: true }, // escape
|
||||
},
|
||||
|
||||
ingame: {
|
||||
menuOpenShop: { keyCode: key("F") },
|
||||
menuOpenStats: { keyCode: key("G") },
|
||||
menuClose: { keyCode: key("Q") },
|
||||
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
exportScreenshot: { keyCode: 114 }, // F3PS
|
||||
toggleFPSInfo: { keyCode: 115 }, // F4
|
||||
|
||||
switchLayers: { keyCode: key("Y") },
|
||||
},
|
||||
|
||||
navigation: {
|
||||
mapMoveUp: { keyCode: key("W") },
|
||||
mapMoveRight: { keyCode: key("D") },
|
||||
mapMoveDown: { keyCode: key("S") },
|
||||
mapMoveLeft: { keyCode: key("A") },
|
||||
mapMoveFaster: { keyCode: 16 }, //shift
|
||||
|
||||
centerMap: { keyCode: 32 }, // SPACE
|
||||
mapZoomIn: { keyCode: 187, repeated: true }, // "+"
|
||||
mapZoomOut: { keyCode: 189, repeated: true }, // "-"
|
||||
|
||||
createMarker: { keyCode: key("M") },
|
||||
},
|
||||
|
||||
buildings: {
|
||||
belt: { keyCode: key("1") },
|
||||
splitter: { keyCode: key("2") },
|
||||
underground_belt: { keyCode: key("3") },
|
||||
miner: { keyCode: key("4") },
|
||||
cutter: { keyCode: key("5") },
|
||||
rotater: { keyCode: key("6") },
|
||||
stacker: { keyCode: key("7") },
|
||||
mixer: { keyCode: key("8") },
|
||||
painter: { keyCode: key("9") },
|
||||
trash: { keyCode: key("0") },
|
||||
|
||||
lever: { keyCode: key("L") },
|
||||
filter: { keyCode: key("B") },
|
||||
display: { keyCode: key("N") },
|
||||
|
||||
wire: { keyCode: key("1") },
|
||||
wire_tunnel: { keyCode: key("2") },
|
||||
constant_signal: { keyCode: key("3") },
|
||||
logic_gate: { keyCode: key("4") },
|
||||
virtual_processor: { keyCode: key("5") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
pipette: { keyCode: key("Q") },
|
||||
rotateWhilePlacing: { keyCode: key("R") },
|
||||
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
||||
cycleBuildingVariants: { keyCode: key("T") },
|
||||
cycleBuildings: { keyCode: 9 }, // TAB
|
||||
switchDirectionLockSide: { keyCode: key("R") },
|
||||
},
|
||||
|
||||
massSelect: {
|
||||
massSelectStart: { keyCode: 17 }, // CTRL
|
||||
massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
|
||||
massSelectCopy: { keyCode: key("C") },
|
||||
massSelectCut: { keyCode: key("X") },
|
||||
confirmMassDelete: { keyCode: 46 }, // DEL
|
||||
pasteLastBlueprint: { keyCode: key("V") },
|
||||
},
|
||||
|
||||
placementModifiers: {
|
||||
lockBeltDirection: { keyCode: 16 }, // SHIFT
|
||||
placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
|
||||
placeMultiple: { keyCode: 16 }, // SHIFT
|
||||
placeInverse: { keyCode: 18 }, // ALT
|
||||
},
|
||||
};
|
||||
|
||||
// Assign ids
|
||||
for (const categoryId in KEYMAPPINGS) {
|
||||
for (const mappingId in KEYMAPPINGS[categoryId]) {
|
||||
KEYMAPPINGS[categoryId][mappingId].id = mappingId;
|
||||
}
|
||||
}
|
||||
|
||||
export const KEYCODE_LMB = 1;
|
||||
export const KEYCODE_MMB = 2;
|
||||
export const KEYCODE_RMB = 3;
|
||||
|
||||
/**
|
||||
* Returns a keycode -> string
|
||||
* @param {number} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getStringForKeyCode(code) {
|
||||
switch (code) {
|
||||
case KEYCODE_LMB:
|
||||
return "LMB";
|
||||
case KEYCODE_MMB:
|
||||
return "MMB";
|
||||
case KEYCODE_RMB:
|
||||
return "RMB";
|
||||
case 4:
|
||||
return "MB4";
|
||||
case 5:
|
||||
return "MB5";
|
||||
case 8:
|
||||
return "⌫";
|
||||
case 9:
|
||||
return T.global.keys.tab;
|
||||
case 13:
|
||||
return "⏎";
|
||||
case 16:
|
||||
return "⇪";
|
||||
case 17:
|
||||
return T.global.keys.control;
|
||||
case 18:
|
||||
return T.global.keys.alt;
|
||||
case 19:
|
||||
return "PAUSE";
|
||||
case 20:
|
||||
return "CAPS";
|
||||
case 27:
|
||||
return T.global.keys.escape;
|
||||
case 32:
|
||||
return T.global.keys.space;
|
||||
case 33:
|
||||
return "PGUP";
|
||||
case 34:
|
||||
return "PGDOWN";
|
||||
case 35:
|
||||
return "END";
|
||||
case 36:
|
||||
return "HOME";
|
||||
case 37:
|
||||
return "⬅";
|
||||
case 38:
|
||||
return "⬆";
|
||||
case 39:
|
||||
return "➡";
|
||||
case 40:
|
||||
return "⬇";
|
||||
case 44:
|
||||
return "PRNT";
|
||||
case 45:
|
||||
return "INS";
|
||||
case 46:
|
||||
return "DEL";
|
||||
case 93:
|
||||
return "SEL";
|
||||
case 96:
|
||||
return "NUM 0";
|
||||
case 97:
|
||||
return "NUM 1";
|
||||
case 98:
|
||||
return "NUM 2";
|
||||
case 99:
|
||||
return "NUM 3";
|
||||
case 100:
|
||||
return "NUM 4";
|
||||
case 101:
|
||||
return "NUM 5";
|
||||
case 102:
|
||||
return "NUM 6";
|
||||
case 103:
|
||||
return "NUM 7";
|
||||
case 104:
|
||||
return "NUM 8";
|
||||
case 105:
|
||||
return "NUM 9";
|
||||
case 106:
|
||||
return "*";
|
||||
case 107:
|
||||
return "+";
|
||||
case 109:
|
||||
return "-";
|
||||
case 110:
|
||||
return ".";
|
||||
case 111:
|
||||
return "/";
|
||||
case 112:
|
||||
return "F1";
|
||||
case 113:
|
||||
return "F2";
|
||||
case 114:
|
||||
return "F3";
|
||||
case 115:
|
||||
return "F4";
|
||||
case 116:
|
||||
return "F4";
|
||||
case 117:
|
||||
return "F5";
|
||||
case 118:
|
||||
return "F6";
|
||||
case 119:
|
||||
return "F7";
|
||||
case 120:
|
||||
return "F8";
|
||||
case 121:
|
||||
return "F9";
|
||||
case 122:
|
||||
return "F10";
|
||||
case 123:
|
||||
return "F11";
|
||||
case 124:
|
||||
return "F12";
|
||||
|
||||
case 144:
|
||||
return "NUMLOCK";
|
||||
case 145:
|
||||
return "SCRLOCK";
|
||||
case 182:
|
||||
return "COMP";
|
||||
case 183:
|
||||
return "CALC";
|
||||
case 186:
|
||||
return ";";
|
||||
case 187:
|
||||
return "+";
|
||||
case 188:
|
||||
return ",";
|
||||
case 189:
|
||||
return "-";
|
||||
case 191:
|
||||
return "/";
|
||||
case 219:
|
||||
return "[";
|
||||
case 220:
|
||||
return "\\";
|
||||
case 221:
|
||||
return "]";
|
||||
case 222:
|
||||
return "'";
|
||||
}
|
||||
|
||||
return String.fromCharCode(code);
|
||||
}
|
||||
|
||||
export class Keybinding {
|
||||
/**
|
||||
*
|
||||
* @param {KeyActionMapper} keyMapper
|
||||
* @param {Application} app
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @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);
|
||||
this.keyMapper = keyMapper;
|
||||
this.app = app;
|
||||
this.keyCode = keyCode;
|
||||
this.builtin = builtin;
|
||||
this.repeated = repeated;
|
||||
|
||||
this.signal = new Signal();
|
||||
this.toggled = new Signal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this binding is currently pressed
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get pressed() {
|
||||
// Check if the key is down
|
||||
if (this.app.inputMgr.keysDown.has(this.keyCode)) {
|
||||
// Check if it is the top reciever
|
||||
const reciever = this.keyMapper.inputReceiver;
|
||||
return this.app.inputMgr.getTopReciever() === reciever;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener
|
||||
* @param {function() : void} receiver
|
||||
* @param {object=} 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
|
||||
* */
|
||||
appendLabelToElement(elem) {
|
||||
if (IS_MOBILE) {
|
||||
return null;
|
||||
}
|
||||
const spacer = document.createElement("code");
|
||||
spacer.classList.add("keybinding");
|
||||
spacer.innerHTML = getStringForKeyCode(this.keyCode);
|
||||
elem.appendChild(spacer);
|
||||
return spacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key code as a nice string
|
||||
*/
|
||||
getKeyCodeString() {
|
||||
return getStringForKeyCode(this.keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remvoes all signal receivers
|
||||
*/
|
||||
clearSignalReceivers() {
|
||||
this.signal.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyActionMapper {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
* @param {InputReceiver} inputReciever
|
||||
*/
|
||||
constructor(root, inputReciever) {
|
||||
this.root = root;
|
||||
this.inputReceiver = inputReciever;
|
||||
|
||||
inputReciever.keydown.add(this.handleKeydown, this);
|
||||
inputReciever.keyup.add(this.handleKeyup, this);
|
||||
|
||||
/** @type {Object.<string, Keybinding>} */
|
||||
this.keybindings = {};
|
||||
|
||||
const overrides = root.app.settings.getKeybindingOverrides();
|
||||
|
||||
for (const category in KEYMAPPINGS) {
|
||||
for (const key in KEYMAPPINGS[category]) {
|
||||
let payload = Object.assign({}, KEYMAPPINGS[category][key]);
|
||||
if (overrides[key]) {
|
||||
payload.keyCode = overrides[key];
|
||||
}
|
||||
|
||||
this.keybindings[key] = new Keybinding(this, this.root.app, payload);
|
||||
}
|
||||
}
|
||||
|
||||
inputReciever.pageBlur.add(this.onPageBlur, this);
|
||||
inputReciever.destroyed.add(this.cleanup, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all keybindings starting with the given id
|
||||
* @param {string} pattern
|
||||
* @returns {Array<Keybinding>}
|
||||
*/
|
||||
getKeybindingsStartingWith(pattern) {
|
||||
let result = [];
|
||||
for (const key in this.keybindings) {
|
||||
if (key.startsWith(pattern)) {
|
||||
result.push(this.keybindings[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the given events to the other mapper (used in tooltips)
|
||||
* @param {KeyActionMapper} receiver
|
||||
* @param {Array<string>} bindings
|
||||
*/
|
||||
forward(receiver, bindings) {
|
||||
for (let i = 0; i < bindings.length; ++i) {
|
||||
const key = bindings[i];
|
||||
this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
for (const key in this.keybindings) {
|
||||
this.keybindings[key].signal.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
onPageBlur() {
|
||||
// Reset all down states
|
||||
// Find mapping
|
||||
for (const key in this.keybindings) {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal keydown handler
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
* @param {boolean=} param0.initial
|
||||
*/
|
||||
handleKeydown({ keyCode, shift, alt, initial }) {
|
||||
let stop = false;
|
||||
|
||||
// Find mapping
|
||||
for (const key in this.keybindings) {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
if (binding.keyCode === keyCode && (initial || binding.repeated)) {
|
||||
/** @type {Signal} */
|
||||
const signal = this.keybindings[key].signal;
|
||||
if (signal.dispatch() === STOP_PROPAGATION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal keyup handler
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
*/
|
||||
handleKeyup({ keyCode, shift, alt }) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a given keybinding
|
||||
* @param {{ keyCode: number }} binding
|
||||
* @returns {Keybinding}
|
||||
*/
|
||||
getBinding(binding) {
|
||||
// @ts-ignore
|
||||
const id = binding.id;
|
||||
assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
|
||||
assert(this.keybindings[id], "Keybinding " + id + " not known!");
|
||||
return this.keybindings[id];
|
||||
}
|
||||
}
|
||||
|
@ -52,10 +52,16 @@ export class MapChunkView extends MapChunk {
|
||||
*/
|
||||
drawForegroundLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
|
||||
systems.itemEjector.drawChunk(parameters, this);
|
||||
systems.itemAcceptor.drawChunk(parameters, this);
|
||||
|
||||
systems.miner.drawChunk(parameters, this);
|
||||
|
||||
systems.staticMapEntities.drawChunk(parameters, this);
|
||||
systems.lever.drawChunk(parameters, this);
|
||||
systems.display.drawChunk(parameters, this);
|
||||
systems.storage.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,11 +103,9 @@ export class MapChunkView extends MapChunk {
|
||||
|
||||
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
|
||||
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
|
||||
const destSize = Math.min(80, 30 / parameters.zoomLevel);
|
||||
const diameter = Math.min(80, 30 / parameters.zoomLevel);
|
||||
|
||||
if (parameters.visibleRect.containsCircle(destX, destY, destSize)) {
|
||||
patch.item.drawCentered(destX, destY, parameters, destSize);
|
||||
}
|
||||
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,5 +269,6 @@ export class MapChunkView extends MapChunk {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
systems.wire.drawChunk(parameters, this);
|
||||
systems.staticMapEntities.drawWiresChunk(parameters, this);
|
||||
systems.wiredPins.drawChunk(parameters, this);
|
||||
}
|
||||
}
|
||||
|
@ -1,162 +1,171 @@
|
||||
import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { MetaBeltBuilding } from "./buildings/belt";
|
||||
import { MetaBeltBaseBuilding } from "./buildings/belt_base";
|
||||
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
||||
import { MetaHubBuilding } from "./buildings/hub";
|
||||
import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
|
||||
import { MetaMixerBuilding } from "./buildings/mixer";
|
||||
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
|
||||
import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
|
||||
import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
|
||||
import { MetaStackerBuilding } from "./buildings/stacker";
|
||||
import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
|
||||
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { MetaWireBuilding } from "./buildings/wire";
|
||||
import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
|
||||
import { MetaLeverBuilding } from "./buildings/lever";
|
||||
import { MetaFilterBuilding } from "./buildings/filter";
|
||||
import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
|
||||
import { MetaDisplayBuilding } from "./buildings/display";
|
||||
|
||||
const logger = createLogger("building_registry");
|
||||
|
||||
export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaSplitterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMinerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaCutterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaRotaterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaStackerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMixerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaPainterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaTrashBuilding);
|
||||
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLogicGateBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLeverBuilding);
|
||||
gMetaBuildingRegistry.register(MetaFilterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
|
||||
gMetaBuildingRegistry.register(MetaDisplayBuilding);
|
||||
|
||||
// Belt
|
||||
registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2);
|
||||
|
||||
// Splitter
|
||||
registerBuildingVariant(4, MetaSplitterBuilding);
|
||||
registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
|
||||
registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
|
||||
|
||||
// Miner
|
||||
registerBuildingVariant(7, MetaMinerBuilding);
|
||||
registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
|
||||
|
||||
// Cutter
|
||||
registerBuildingVariant(9, MetaCutterBuilding);
|
||||
registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
|
||||
|
||||
// Rotater
|
||||
registerBuildingVariant(11, MetaRotaterBuilding);
|
||||
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
|
||||
registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
|
||||
|
||||
// Stacker
|
||||
registerBuildingVariant(14, MetaStackerBuilding);
|
||||
|
||||
// Mixer
|
||||
registerBuildingVariant(15, MetaMixerBuilding);
|
||||
|
||||
// Painter
|
||||
registerBuildingVariant(16, MetaPainterBuilding);
|
||||
registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
|
||||
registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
|
||||
registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
|
||||
|
||||
// Trash
|
||||
registerBuildingVariant(20, MetaTrashBuilding);
|
||||
registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage);
|
||||
|
||||
// Underground belt
|
||||
registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
|
||||
registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
|
||||
|
||||
// Hub
|
||||
registerBuildingVariant(26, MetaHubBuilding);
|
||||
|
||||
// Wire
|
||||
registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
|
||||
registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
|
||||
|
||||
// Constant signal
|
||||
registerBuildingVariant(31, MetaConstantSignalBuilding);
|
||||
|
||||
// Logic gate
|
||||
registerBuildingVariant(32, MetaLogicGateBuilding);
|
||||
registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
|
||||
registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
|
||||
registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
|
||||
registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor);
|
||||
|
||||
// Lever
|
||||
registerBuildingVariant(33, MetaLeverBuilding);
|
||||
|
||||
// Filter
|
||||
registerBuildingVariant(37, MetaFilterBuilding);
|
||||
|
||||
// Wire tunnel
|
||||
registerBuildingVariant(39, MetaWireTunnelBuilding);
|
||||
registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
|
||||
|
||||
// Display
|
||||
registerBuildingVariant(40, MetaDisplayBuilding);
|
||||
|
||||
// Propagate instances
|
||||
for (const key in gBuildingVariants) {
|
||||
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
|
||||
gBuildingVariants[key].metaClass
|
||||
);
|
||||
}
|
||||
|
||||
for (const key in gBuildingVariants) {
|
||||
const variant = gBuildingVariants[key];
|
||||
assert(variant.metaClass, "Variant has no meta: " + key);
|
||||
|
||||
if (typeof variant.rotationVariant === "undefined") {
|
||||
variant.rotationVariant = 0;
|
||||
}
|
||||
if (typeof variant.variant === "undefined") {
|
||||
variant.variant = defaultBuildingVariant;
|
||||
}
|
||||
}
|
||||
|
||||
logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
|
||||
logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Once all sprites are loaded, propagates the cache
|
||||
*/
|
||||
export function initBuildingCodesAfterResourcesLoaded() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { MetaBeltBuilding } from "./buildings/belt";
|
||||
import { MetaBeltBaseBuilding } from "./buildings/belt_base";
|
||||
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
||||
import { MetaHubBuilding } from "./buildings/hub";
|
||||
import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
|
||||
import { MetaMixerBuilding } from "./buildings/mixer";
|
||||
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
|
||||
import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
|
||||
import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
|
||||
import { MetaStackerBuilding } from "./buildings/stacker";
|
||||
import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
|
||||
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { MetaWireBuilding } from "./buildings/wire";
|
||||
import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
|
||||
import { MetaLeverBuilding } from "./buildings/lever";
|
||||
import { MetaFilterBuilding } from "./buildings/filter";
|
||||
import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
|
||||
import { MetaDisplayBuilding } from "./buildings/display";
|
||||
import { MetaVirtualProcessorBuilding, enumVirtualProcessorVariants } from "./buildings/virtual_processor";
|
||||
|
||||
const logger = createLogger("building_registry");
|
||||
|
||||
export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaSplitterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMinerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaCutterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaRotaterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaStackerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMixerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaPainterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaTrashBuilding);
|
||||
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLogicGateBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLeverBuilding);
|
||||
gMetaBuildingRegistry.register(MetaFilterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
|
||||
gMetaBuildingRegistry.register(MetaDisplayBuilding);
|
||||
gMetaBuildingRegistry.register(MetaVirtualProcessorBuilding);
|
||||
|
||||
// Belt
|
||||
registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2);
|
||||
|
||||
// Splitter
|
||||
registerBuildingVariant(4, MetaSplitterBuilding);
|
||||
registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
|
||||
registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
|
||||
|
||||
// Miner
|
||||
registerBuildingVariant(7, MetaMinerBuilding);
|
||||
registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
|
||||
|
||||
// Cutter
|
||||
registerBuildingVariant(9, MetaCutterBuilding);
|
||||
registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
|
||||
|
||||
// Rotater
|
||||
registerBuildingVariant(11, MetaRotaterBuilding);
|
||||
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
|
||||
registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
|
||||
|
||||
// Stacker
|
||||
registerBuildingVariant(14, MetaStackerBuilding);
|
||||
|
||||
// Mixer
|
||||
registerBuildingVariant(15, MetaMixerBuilding);
|
||||
|
||||
// Painter
|
||||
registerBuildingVariant(16, MetaPainterBuilding);
|
||||
registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
|
||||
registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
|
||||
registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
|
||||
|
||||
// Trash
|
||||
registerBuildingVariant(20, MetaTrashBuilding);
|
||||
registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage);
|
||||
|
||||
// Underground belt
|
||||
registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
|
||||
registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
|
||||
|
||||
// Hub
|
||||
registerBuildingVariant(26, MetaHubBuilding);
|
||||
|
||||
// Wire
|
||||
registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
|
||||
registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
|
||||
|
||||
// Constant signal
|
||||
registerBuildingVariant(31, MetaConstantSignalBuilding);
|
||||
|
||||
// Logic gate
|
||||
registerBuildingVariant(32, MetaLogicGateBuilding);
|
||||
registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
|
||||
registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
|
||||
registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
|
||||
registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor);
|
||||
|
||||
// Lever
|
||||
registerBuildingVariant(33, MetaLeverBuilding);
|
||||
|
||||
// Filter
|
||||
registerBuildingVariant(37, MetaFilterBuilding);
|
||||
|
||||
// Wire tunnel
|
||||
registerBuildingVariant(39, MetaWireTunnelBuilding);
|
||||
registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
|
||||
|
||||
// Display
|
||||
registerBuildingVariant(40, MetaDisplayBuilding);
|
||||
|
||||
// Virtual Processor
|
||||
registerBuildingVariant(42, MetaVirtualProcessorBuilding);
|
||||
registerBuildingVariant(43, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.analyzer);
|
||||
registerBuildingVariant(44, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.rotater);
|
||||
registerBuildingVariant(45, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.unstacker);
|
||||
registerBuildingVariant(46, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.shapecompare);
|
||||
|
||||
// Propagate instances
|
||||
for (const key in gBuildingVariants) {
|
||||
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
|
||||
gBuildingVariants[key].metaClass
|
||||
);
|
||||
}
|
||||
|
||||
for (const key in gBuildingVariants) {
|
||||
const variant = gBuildingVariants[key];
|
||||
assert(variant.metaClass, "Variant has no meta: " + key);
|
||||
|
||||
if (typeof variant.rotationVariant === "undefined") {
|
||||
variant.rotationVariant = 0;
|
||||
}
|
||||
if (typeof variant.variant === "undefined") {
|
||||
variant.variant = defaultBuildingVariant;
|
||||
}
|
||||
}
|
||||
|
||||
logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
|
||||
logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes");
|
||||
}
|
||||
|
||||
/**
|
||||
* Once all sprites are loaded, propagates the cache
|
||||
*/
|
||||
export function initBuildingCodesAfterResourcesLoaded() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,8 @@ export class GameRoot {
|
||||
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
|
||||
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
|
||||
|
||||
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
|
||||
|
||||
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
|
||||
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),
|
||||
|
||||
|
@ -14,7 +14,6 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { defaultBuildingVariant } from "../meta_building";
|
||||
import { getCodeFromBuildingData } from "../building_codes";
|
||||
import { enumLayer } from "../root";
|
||||
|
||||
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) *
|
||||
globalConfig.itemSpacingOnBelts
|
||||
);
|
||||
const contents = chunk.containedEntitiesByLayer[enumLayer.regular];
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
if (entity.components.Belt) {
|
||||
const direction = entity.components.Belt.direction;
|
||||
const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT];
|
||||
entity.components.StaticMapEntity.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
|
||||
|
||||
// Culling happens within the static map entity component
|
||||
entity.components.StaticMapEntity.drawSpriteOnBoundsClipped(parameters, sprite, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BELT_ANIM_COUNT } from "./belt";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { enumLayer } from "../root";
|
||||
|
||||
export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -29,38 +28,56 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
// Limit speed to avoid belts going backwards
|
||||
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) {
|
||||
const entity = contents[i];
|
||||
const underlayComp = entity.components.BeltUnderlays;
|
||||
if (underlayComp) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const underlays = underlayComp.underlays;
|
||||
for (let i = 0; i < underlays.length; ++i) {
|
||||
const { pos, direction } = underlays[i];
|
||||
const transformedPos = staticComp.localTileToWorld(pos);
|
||||
if (!underlayComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
|
||||
continue;
|
||||
}
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const underlays = underlayComp.underlays;
|
||||
for (let i = 0; i < underlays.length; ++i) {
|
||||
const { pos, direction } = underlays[i];
|
||||
const transformedPos = staticComp.localTileToWorld(pos);
|
||||
|
||||
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: (transformedPos.x + 0.5) * globalConfig.tileSize,
|
||||
y: (transformedPos.y + 0.5) * globalConfig.tileSize,
|
||||
angle: Math.radians(angle),
|
||||
size: globalConfig.tileSize,
|
||||
});
|
||||
// Culling
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(transformedPos.x, transformedPos.y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const destX = transformedPos.x * globalConfig.tileSize;
|
||||
const destY = transformedPos.y * globalConfig.tileSize;
|
||||
|
||||
// Culling, #2
|
||||
if (
|
||||
!parameters.visibleRect.containsRect4Params(
|
||||
destX,
|
||||
destY,
|
||||
globalConfig.tileSize,
|
||||
globalConfig.tileSize
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
|
||||
|
||||
// SYNC with systems/belt.js:drawSingleEntity!
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,9 +66,11 @@ export class DisplaySystem extends GameSystemWithFilter {
|
||||
if (entity && entity.components.Display) {
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
const network = pinsComp.slots[0].linkedNetwork;
|
||||
|
||||
if (!network || !network.currentValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = this.getDisplayItem(network.currentValue);
|
||||
|
||||
if (!value) {
|
||||
@ -84,7 +86,7 @@ export class DisplaySystem extends GameSystemWithFilter {
|
||||
globalConfig.tileSize
|
||||
);
|
||||
} else if (value.getItemType() === "shape") {
|
||||
value.draw(
|
||||
value.drawItemCenteredClipped(
|
||||
(origin.x + 0.5) * globalConfig.tileSize,
|
||||
(origin.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
|
@ -5,6 +5,14 @@ import { T } from "../../translations";
|
||||
import { HubComponent } from "../components/hub";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { smoothenDpi } from "../../core/dpi_manager";
|
||||
import { drawSpriteClipped } from "../../core/draw_utils";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
|
||||
|
||||
const HUB_SIZE_TILES = 4;
|
||||
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
|
||||
|
||||
export class HubSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -13,8 +21,13 @@ export class HubSystem extends GameSystemWithFilter {
|
||||
this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
this.drawEntity(parameters, this.allEntities[i]);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
@ -27,35 +40,42 @@ export class HubSystem extends GameSystemWithFilter {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} w
|
||||
* @param {number} h
|
||||
* @param {number} dpi
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const context = parameters.context;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
redrawHubBaseTexture(canvas, context, w, h, dpi) {
|
||||
// This method is quite ugly, please ignore it!
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
context.scale(dpi, dpi);
|
||||
|
||||
const pos = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
const parameters = new DrawParameters({
|
||||
context,
|
||||
visibleRect: new Rectangle(0, 0, w, h),
|
||||
desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
|
||||
zoomLevel: dpi * 0.75,
|
||||
root: this.root,
|
||||
});
|
||||
|
||||
// Background
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, this.hubSprite, 2.2);
|
||||
context.clearRect(0, 0, w, h);
|
||||
|
||||
this.hubSprite.draw(context, 0, 0, w, h);
|
||||
|
||||
const definition = this.root.hubGoals.currentGoal.definition;
|
||||
|
||||
definition.drawCentered(pos.x - 25, pos.y - 10, parameters, 40);
|
||||
definition.drawCentered(45, 58, parameters, 36);
|
||||
|
||||
const goals = this.root.hubGoals.currentGoal;
|
||||
|
||||
const textOffsetX = 2;
|
||||
const textOffsetY = -6;
|
||||
const textOffsetX = 70;
|
||||
const textOffsetY = 61;
|
||||
|
||||
// Deliver count
|
||||
const delivered = this.root.hubGoals.getCurrentGoalDelivered();
|
||||
const deliveredText = "" + formatBigNumber(delivered);
|
||||
|
||||
if (delivered > 9999) {
|
||||
context.font = "bold 16px GameFont";
|
||||
@ -66,52 +86,87 @@ export class HubSystem extends GameSystemWithFilter {
|
||||
}
|
||||
context.fillStyle = "#64666e";
|
||||
context.textAlign = "left";
|
||||
context.fillText("" + formatBigNumber(delivered), pos.x + textOffsetX, pos.y + textOffsetY);
|
||||
context.fillText(deliveredText, textOffsetX, textOffsetY);
|
||||
|
||||
// Required
|
||||
context.font = "13px GameFont";
|
||||
context.fillStyle = "#a4a6b0";
|
||||
context.fillText(
|
||||
"/ " + formatBigNumber(goals.required),
|
||||
pos.x + textOffsetX,
|
||||
pos.y + textOffsetY + 13
|
||||
);
|
||||
context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
|
||||
|
||||
// Reward
|
||||
const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
|
||||
if (rewardText.length > 12) {
|
||||
context.font = "bold 9px GameFont";
|
||||
context.font = "bold 8px GameFont";
|
||||
} else {
|
||||
context.font = "bold 11px GameFont";
|
||||
context.font = "bold 10px GameFont";
|
||||
}
|
||||
context.fillStyle = "#fd0752";
|
||||
context.textAlign = "center";
|
||||
|
||||
context.fillText(rewardText, pos.x, pos.y + 46);
|
||||
context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
|
||||
|
||||
// Level
|
||||
context.font = "bold 11px GameFont";
|
||||
// Level "8"
|
||||
context.font = "bold 10px GameFont";
|
||||
context.fillStyle = "#fff";
|
||||
context.fillText("" + this.root.hubGoals.level, pos.x - 42, pos.y - 36);
|
||||
context.fillText("" + this.root.hubGoals.level, 27, 32);
|
||||
|
||||
// Texts
|
||||
// "LVL"
|
||||
context.textAlign = "center";
|
||||
context.fillStyle = "#fff";
|
||||
context.font = "bold 7px GameFont";
|
||||
context.fillText(T.buildings.hub.levelShortcut, pos.x - 42, pos.y - 47);
|
||||
context.font = "bold 6px GameFont";
|
||||
context.fillText(T.buildings.hub.levelShortcut, 27, 22);
|
||||
|
||||
// "Deliver"
|
||||
context.fillStyle = "#64666e";
|
||||
context.font = "bold 11px GameFont";
|
||||
context.fillText(T.buildings.hub.deliver.toUpperCase(), pos.x, pos.y - 40);
|
||||
context.font = "bold 10px GameFont";
|
||||
context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
|
||||
|
||||
// "To unlock"
|
||||
const unlockText = T.buildings.hub.toUnlock.toUpperCase();
|
||||
if (unlockText.length > 15) {
|
||||
context.font = "bold 8px GameFont";
|
||||
} else {
|
||||
context.font = "bold 11px GameFont";
|
||||
context.font = "bold 10px GameFont";
|
||||
}
|
||||
context.fillText(T.buildings.hub.toUnlock.toUpperCase(), pos.x, pos.y + 30);
|
||||
context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
|
||||
|
||||
context.textAlign = "left";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deliver count
|
||||
const delivered = this.root.hubGoals.getCurrentGoalDelivered();
|
||||
const deliveredText = "" + formatBigNumber(delivered);
|
||||
|
||||
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
|
||||
const canvas = parameters.root.buffers.getForKey({
|
||||
key: "hub",
|
||||
subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText,
|
||||
w: globalConfig.tileSize * 4,
|
||||
h: globalConfig.tileSize * 4,
|
||||
dpi,
|
||||
redrawMethod: this.redrawHubBaseTexture.bind(this),
|
||||
});
|
||||
|
||||
const extrude = 8;
|
||||
drawSpriteClipped({
|
||||
parameters,
|
||||
sprite: canvas,
|
||||
x: staticComp.origin.x * globalConfig.tileSize - extrude,
|
||||
y: staticComp.origin.y * globalConfig.tileSize - extrude,
|
||||
w: HUB_SIZE_PIXELS + 2 * extrude,
|
||||
h: HUB_SIZE_PIXELS + 2 * extrude,
|
||||
originalW: HUB_SIZE_PIXELS * dpi,
|
||||
originalH: HUB_SIZE_PIXELS * dpi,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { fastArrayDelete } from "../../core/utils";
|
||||
import { enumDirectionToVector } from "../../core/vector";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -38,43 +38,45 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the acceptor items
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityRegularLayer.bind(this));
|
||||
}
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const acceptorComp = entity.components.ItemAcceptor;
|
||||
if (!acceptorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawEntityRegularLayer(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const acceptorComp = entity.components.ItemAcceptor;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
|
||||
animIndex
|
||||
];
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
const slotData = acceptorComp.slots[slotIndex];
|
||||
const realSlotPos = staticComp.localTileToWorld(slotData.pos);
|
||||
|
||||
for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
|
||||
const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
|
||||
animIndex
|
||||
];
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
|
||||
// Not within this chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
const slotData = acceptorComp.slots[slotIndex];
|
||||
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
|
||||
const finalTile = realSlotPos.subScalars(
|
||||
fadeOutDirection.x * (animProgress / 2 - 0.5),
|
||||
fadeOutDirection.y * (animProgress / 2 - 0.5)
|
||||
);
|
||||
|
||||
const slotWorldPos = staticComp.applyRotationToVector(slotData.pos).add(staticComp.origin);
|
||||
const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
|
||||
const finalTile = slotWorldPos.subScalars(
|
||||
fadeOutDirection.x * (animProgress / 2 - 0.5),
|
||||
fadeOutDirection.y * (animProgress / 2 - 0.5)
|
||||
);
|
||||
item.drawCentered(
|
||||
(finalTile.x + 0.5) * globalConfig.tileSize,
|
||||
(finalTile.y + 0.5) * globalConfig.tileSize,
|
||||
parameters
|
||||
);
|
||||
item.drawItemCenteredClipped(
|
||||
(finalTile.x + 0.5) * globalConfig.tileSize,
|
||||
(finalTile.y + 0.5) * globalConfig.tileSize,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { enumItemProcessorTypes } from "../components/item_processor";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
const logger = createLogger("systems/ejector");
|
||||
|
||||
@ -336,50 +337,52 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws everything
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
|
||||
}
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
drawSingleEntity(parameters, entity) {
|
||||
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
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
if (!ejectorComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const realPosition = slot.pos.rotateFastMultipleOf90(staticComp.rotation);
|
||||
const realDirection = Vector.transformDirectionFromMultipleOf90(
|
||||
slot.direction,
|
||||
staticComp.rotation
|
||||
);
|
||||
const realDirectionVector = enumDirectionToVector[realDirection];
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const tileX =
|
||||
staticComp.origin.x + realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress;
|
||||
const tileY =
|
||||
staticComp.origin.y + realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress;
|
||||
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||
const slot = ejectorComp.slots[i];
|
||||
const ejectedItem = slot.item;
|
||||
|
||||
const worldX = tileX * globalConfig.tileSize;
|
||||
const worldY = tileY * globalConfig.tileSize;
|
||||
if (!ejectedItem) {
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,9 @@ export class LeverSystem extends GameSystemWithFilter {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
if (entity && entity.components.Lever) {
|
||||
const sprite = entity.components.Lever.toggled ? this.spriteOn : this.spriteOff;
|
||||
const leverComp = entity.components.Lever;
|
||||
if (leverComp) {
|
||||
const sprite = leverComp.toggled ? this.spriteOn : this.spriteOff;
|
||||
const origin = entity.components.StaticMapEntity.origin;
|
||||
sprite.drawCached(
|
||||
parameters,
|
||||
|
@ -1,180 +1,326 @@
|
||||
import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumPinSlotType } from "../components/wired_pins";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON, BooleanItem } from "../items/boolean_item";
|
||||
import { enumItemProcessorTypes } from "../components/item_processor";
|
||||
|
||||
export class LogicGateSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [LogicGateComponent]);
|
||||
|
||||
this.boundOperations = {
|
||||
[enumLogicGateType.and]: this.compute_AND.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) {
|
||||
const entity = this.allEntities[i];
|
||||
const logicComp = entity.components.LogicGate;
|
||||
const slotComp = entity.components.WiredPins;
|
||||
|
||||
const slotValues = [];
|
||||
|
||||
for (let i = 0; i < slotComp.slots.length; ++i) {
|
||||
const slot = slotComp.slots[i];
|
||||
if (slot.type !== enumPinSlotType.logicalAcceptor) {
|
||||
continue;
|
||||
}
|
||||
if (slot.linkedNetwork) {
|
||||
slotValues.push(slot.linkedNetwork.currentValue);
|
||||
} else {
|
||||
slotValues.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
const result = this.boundOperations[logicComp.type](slotValues);
|
||||
|
||||
// @TODO: For now we hardcode the value to always be slot 0
|
||||
assert(
|
||||
slotValues.length === slotComp.slots.length - 1,
|
||||
"Bad slot config, should have N acceptor slots and 1 ejector"
|
||||
);
|
||||
assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
|
||||
|
||||
slotComp.slots[0].value = result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_AND(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for AND");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 || !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const itemType = param1.getItemType();
|
||||
|
||||
if (itemType !== param2.getItemType()) {
|
||||
// Differing type
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (itemType === "boolean") {
|
||||
return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_NOT(parameters) {
|
||||
const item = parameters[0];
|
||||
if (!item) {
|
||||
return BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
if (item.getItemType() !== "boolean") {
|
||||
// Not a boolean actually
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const value = /** @type {BooleanItem} */ (item).value;
|
||||
return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_XOR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for XOR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
// Check for the right types
|
||||
if (param1 && param1.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (param2 && param2.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
|
||||
|
||||
return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_OR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for OR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 =
|
||||
param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 =
|
||||
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}
|
||||
*/
|
||||
compute_IF(parameters) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
import { enumPinSlotType } from "../components/wired_pins";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem } from "../items/boolean_item";
|
||||
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
|
||||
export class LogicGateSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [LogicGateComponent]);
|
||||
|
||||
this.boundOperations = {
|
||||
[enumLogicGateType.and]: this.compute_AND.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),
|
||||
|
||||
[enumLogicGateType.rotater]: this.compute_ROTATE.bind(this),
|
||||
[enumLogicGateType.analyzer]: this.compute_ANALYZE.bind(this),
|
||||
[enumLogicGateType.cutter]: this.compute_CUT.bind(this),
|
||||
[enumLogicGateType.unstacker]: this.compute_UNSTACK.bind(this),
|
||||
[enumLogicGateType.shapecompare]: this.compute_SHAPECOMPARE.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const logicComp = entity.components.LogicGate;
|
||||
const slotComp = entity.components.WiredPins;
|
||||
|
||||
const slotValues = [];
|
||||
|
||||
for (let i = 0; i < slotComp.slots.length; ++i) {
|
||||
const slot = slotComp.slots[i];
|
||||
if (slot.type !== enumPinSlotType.logicalAcceptor) {
|
||||
continue;
|
||||
}
|
||||
if (slot.linkedNetwork) {
|
||||
slotValues.push(slot.linkedNetwork.currentValue);
|
||||
} else {
|
||||
slotValues.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
const result = this.boundOperations[logicComp.type](slotValues);
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
let resultIndex = 0;
|
||||
for (let i = 0; i < slotComp.slots.length; ++i) {
|
||||
const slot = slotComp.slots[i];
|
||||
if (slot.type !== enumPinSlotType.logicalEjector) {
|
||||
continue;
|
||||
}
|
||||
slot.value = result[resultIndex++];
|
||||
}
|
||||
} else {
|
||||
// @TODO: For now we hardcode the value to always be slot 0
|
||||
assert(
|
||||
slotValues.length === slotComp.slots.length - 1,
|
||||
"Bad slot config, should have N acceptor slots and 1 ejector"
|
||||
);
|
||||
assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
|
||||
slotComp.slots[0].value = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_AND(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for AND");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 || !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const itemType = param1.getItemType();
|
||||
|
||||
if (itemType !== param2.getItemType()) {
|
||||
// Differing type
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (itemType === "boolean") {
|
||||
return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_NOT(parameters) {
|
||||
const item = parameters[0];
|
||||
if (!item) {
|
||||
return BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
if (item.getItemType() !== "boolean") {
|
||||
// Not a boolean actually
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const value = /** @type {BooleanItem} */ (item).value;
|
||||
return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_XOR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for XOR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
// Check for the right types
|
||||
if (param1 && param1.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (param2 && param2.getItemType() !== "boolean") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
|
||||
|
||||
return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<BaseItem|null>} parameters
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
compute_OR(parameters) {
|
||||
assert(parameters.length === 2, "bad parameter count for OR");
|
||||
|
||||
const param1 = parameters[0];
|
||||
const param2 = parameters[1];
|
||||
if (!param1 && !param2) {
|
||||
// Not enough params
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
const valueParam1 =
|
||||
param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
|
||||
const valueParam2 =
|
||||
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}
|
||||
*/
|
||||
compute_IF(parameters) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export class MapResourcesSystem extends GameSystem {
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const basicChunkBackground = this.root.buffers.getForKey({
|
||||
key: "chunkres",
|
||||
key: "mapresourcebg",
|
||||
subKey: chunk.renderKey,
|
||||
w: globalConfig.mapChunkSize,
|
||||
h: globalConfig.mapChunkSize,
|
||||
@ -42,10 +42,9 @@ export class MapResourcesSystem extends GameSystem {
|
||||
const patch = chunk.patches[i];
|
||||
const destX = chunk.x * globalConfig.mapChunkWorldSize + patch.pos.x * globalConfig.tileSize;
|
||||
const destY = chunk.y * globalConfig.mapChunkWorldSize + patch.pos.y * globalConfig.tileSize;
|
||||
const destSize = Math.min(80, 40 / parameters.zoomLevel);
|
||||
if (parameters.visibleRect.containsCircle(destX, destY, destSize / 2)) {
|
||||
patch.item.drawCentered(destX, destY, parameters, destSize);
|
||||
}
|
||||
const diameter = Math.min(80, 40 / parameters.zoomLevel);
|
||||
|
||||
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
||||
}
|
||||
} else {
|
||||
// HIGH QUALITY: Draw all items
|
||||
@ -61,9 +60,12 @@ export class MapResourcesSystem extends GameSystem {
|
||||
const destX = worldX + globalConfig.halfTileSize;
|
||||
const destY = worldY + globalConfig.halfTileSize;
|
||||
|
||||
if (parameters.visibleRect.containsCircle(destX, destY, globalConfig.tileSize / 2)) {
|
||||
lowerItem.drawCentered(destX, destY, parameters);
|
||||
}
|
||||
lowerItem.drawItemCenteredClipped(
|
||||
destX,
|
||||
destY,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,41 +102,39 @@ export class MinerSystem extends GameSystemWithFilter {
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.contents;
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const entity = contents[x][y];
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
|
||||
if (entity && entity.components.Miner) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const minerComp = entity.components.Miner;
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const minerComp = entity.components.Miner;
|
||||
if (!minerComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,18 @@ import { MapChunkView } from "../map_chunk_view";
|
||||
export class StaticMapEntitySystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
/** @type {Set<number>} */
|
||||
this.drawnUids = new Set();
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.clearUidList, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the uid list when a new frame started
|
||||
*/
|
||||
clearUidList() {
|
||||
this.drawnUids.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,25 +30,21 @@ export class StaticMapEntitySystem extends GameSystem {
|
||||
return;
|
||||
}
|
||||
|
||||
const drawnUids = new Set();
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
|
||||
const contents = chunk.contents;
|
||||
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
|
||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||
const entity = contents[x][y];
|
||||
|
||||
if (entity) {
|
||||
if (drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
drawnUids.add(entity.uid);
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const sprite = staticComp.getSprite();
|
||||
if (sprite) {
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2);
|
||||
}
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const sprite = staticComp.getSprite();
|
||||
if (sprite) {
|
||||
// Avoid drawing an entity twice which has been drawn for
|
||||
// another chunk already
|
||||
if (this.drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.drawnUids.add(entity.uid);
|
||||
staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,7 +73,7 @@ export class StaticMapEntitySystem extends GameSystem {
|
||||
|
||||
const sprite = staticComp.getSprite();
|
||||
if (sprite) {
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 2);
|
||||
staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,28 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { Entity } from "../entity";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { formatBigNumber, lerp } from "../../core/utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class StorageSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [StorageComponent]);
|
||||
|
||||
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
|
||||
|
||||
/**
|
||||
* Stores which uids were already drawn to avoid drawing entities twice
|
||||
* @type {Set<number>}
|
||||
*/
|
||||
this.drawnUids = new Set();
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
||||
}
|
||||
|
||||
clearDrawnUids() {
|
||||
this.drawnUids.clear();
|
||||
}
|
||||
|
||||
update() {
|
||||
@ -43,38 +55,46 @@ export class StorageSystem extends GameSystemWithFilter {
|
||||
}
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntity.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawEntity(parameters, entity) {
|
||||
const context = parameters.context;
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const storageComp = entity.components.Storage;
|
||||
if (!storageComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||
return;
|
||||
}
|
||||
const storedItem = storageComp.storedItem;
|
||||
if (!storedItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storageComp = entity.components.Storage;
|
||||
if (this.drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storedItem = storageComp.storedItem;
|
||||
if (storedItem !== null) {
|
||||
this.drawnUids.add(entity.uid);
|
||||
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const context = parameters.context;
|
||||
context.globalAlpha = storageComp.overlayOpacity;
|
||||
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
storedItem.drawCentered(center.x, center.y, parameters, 30);
|
||||
storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
|
||||
|
||||
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
|
||||
|
||||
context.font = "bold 10px GameFont";
|
||||
context.textAlign = "center";
|
||||
context.fillStyle = "#64666e";
|
||||
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
||||
|
||||
context.textAlign = "left";
|
||||
if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
|
||||
context.font = "bold 10px GameFont";
|
||||
context.textAlign = "center";
|
||||
context.fillStyle = "#64666e";
|
||||
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
||||
context.textAlign = "left";
|
||||
}
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
|
@ -317,7 +317,9 @@ export class WireSystem extends GameSystemWithFilter {
|
||||
|
||||
if (
|
||||
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);
|
||||
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);
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
parameters.context.globalAlpha = opacity;
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, sprite, 0);
|
||||
staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0);
|
||||
parameters.context.globalAlpha = 1;
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.renderWireRotations) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { drawRotatedSprite } from "../../core/draw_utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { Vector, enumDirectionToAngle } from "../../core/vector";
|
||||
import { STOP_PROPAGATION } from "../../core/signal";
|
||||
import { enumDirectionToAngle, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { STOP_PROPAGATION } from "../../core/signal";
|
||||
import { drawRotatedSprite } from "../../core/draw_utils";
|
||||
import { GLOBAL_APP } from "../../core/globals";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class WiredPinsSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -146,65 +146,84 @@ export class WiredPinsSystem extends GameSystemWithFilter {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the pins
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(parameters) {
|
||||
this.forEachMatchingEntityOnScreen(parameters, this.drawSingleEntity.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a given entity
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {Entity} entity
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawSingleEntity(parameters, entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const slots = entity.components.WiredPins.slots;
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntities;
|
||||
|
||||
for (let i = 0; i < slots.length; ++i) {
|
||||
const slot = slots[i];
|
||||
const tile = staticComp.localTileToWorld(slot.pos);
|
||||
|
||||
const worldPos = tile.toWorldSpaceCenterOfTile();
|
||||
const effectiveRotation = Math.radians(
|
||||
staticComp.rotation + enumDirectionToAngle[slot.direction]
|
||||
);
|
||||
|
||||
if (staticComp.getMetaBuilding().getRenderPins()) {
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: this.pinSprites[slot.type],
|
||||
x: worldPos.x,
|
||||
y: worldPos.y,
|
||||
angle: effectiveRotation,
|
||||
size: globalConfig.tileSize + 2,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
if (!pinsComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Draw contained item to visualize whats emitted
|
||||
const value = slot.value;
|
||||
if (value) {
|
||||
const offset = new Vector(0, -9).rotated(effectiveRotation);
|
||||
value.drawCentered(worldPos.x + offset.x, worldPos.y + offset.y, parameters, 9);
|
||||
}
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const slots = pinsComp.slots;
|
||||
|
||||
// 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
|
||||
for (let j = 0; j < slots.length; ++j) {
|
||||
const slot = slots[j];
|
||||
const tile = staticComp.localTileToWorld(slot.pos);
|
||||
|
||||
if (!chunk.tileSpaceRectangle.containsPoint(tile.x, tile.y)) {
|
||||
// Doesn't belong to this chunk
|
||||
continue;
|
||||
}
|
||||
const worldPos = tile.toWorldSpaceCenterOfTile();
|
||||
|
||||
// Culling
|
||||
if (
|
||||
!parameters.visibleRect.containsCircle(worldPos.x, worldPos.y, globalConfig.halfTileSize)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const effectiveRotation = Math.radians(
|
||||
staticComp.rotation + enumDirectionToAngle[slot.direction]
|
||||
);
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!
|
||||
|
||||
|
||||
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]
|
||||
@ -296,6 +297,7 @@ ingame:
|
||||
copySelection: Kopieren
|
||||
clearSelection: Auswahl aufheben
|
||||
pipette: Pipette
|
||||
|
||||
switchLayers: Ebenen wechseln
|
||||
|
||||
# Names of the colors, used for the color blind mode
|
||||
@ -832,7 +834,6 @@ keybindings:
|
||||
Modifikator: stattdessen gegen den UZS rotieren
|
||||
cycleBuildingVariants: Variante wählen
|
||||
confirmMassDelete: Massenlöschung bestätigen
|
||||
pasteLastBlueprint: Letzte Blaupause einfügen
|
||||
cycleBuildings: Gebäude rotieren
|
||||
lockBeltDirection: Bandplaner aktivieren
|
||||
switchDirectionLockSide: >-
|
||||
@ -846,8 +847,10 @@ keybindings:
|
||||
placementDisableAutoOrientation: Automatische Orientierung deaktivieren
|
||||
placeMultiple: Im Platziermodus bleiben
|
||||
placeInverse: Automatische Förderbandorientierung invertieren
|
||||
advanced_processor: Farbnivertierer
|
||||
pasteLastBlueprint: Letzte Blaupause einfügen
|
||||
advanced_processor: Farbinvertierer
|
||||
energy_generator: Energiegenerator
|
||||
wire: Energiekabel
|
||||
|
||||
about:
|
||||
title: Über dieses Spiel
|
||||
|