mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Highlight connected miners, improve miner performance
This commit is contained in:
parent
3529a5d77f
commit
0377c6d58f
@ -17,6 +17,7 @@ export const CHANGELOG = [
|
|||||||
"Added a button to the statistics dialog to disable the sorting (by squeek502)",
|
"Added a button to the statistics dialog to disable the sorting (by squeek502)",
|
||||||
"Tier 2 tunnels are now 9 tiles wide, so the gap between is 8 tiles (double the tier 1 range)",
|
"Tier 2 tunnels are now 9 tiles wide, so the gap between is 8 tiles (double the tier 1 range)",
|
||||||
"Updated and added new translations (Thanks to all contributors!)",
|
"Updated and added new translations (Thanks to all contributors!)",
|
||||||
|
"Show connected chained miners on hover",
|
||||||
"Added setting to be able to delete buildings while placing (inspired by hexy)",
|
"Added setting to be able to delete buildings while placing (inspired by hexy)",
|
||||||
"You can now adjust the sound and music volumes! (inspired by Yoshie2000)",
|
"You can now adjust the sound and music volumes! (inspired by Yoshie2000)",
|
||||||
"Some hud elements now have reduced opacity when hovering, so you can see through (inspired by mvb005)",
|
"Some hud elements now have reduced opacity when hovering, so you can see through (inspired by mvb005)",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { types } from "../../savegame/serialization";
|
import { types } from "../../savegame/serialization";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { Component } from "../component";
|
import { Component } from "../component";
|
||||||
|
import { Entity } from "../entity";
|
||||||
import { typeItemSingleton } from "../item_resolver";
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
|
|
||||||
const chainBufferSize = 6;
|
const chainBufferSize = 6;
|
||||||
@ -40,6 +41,13 @@ export class MinerComponent extends Component {
|
|||||||
* @type {BaseItem}
|
* @type {BaseItem}
|
||||||
*/
|
*/
|
||||||
this.cachedMinedItem = null;
|
this.cachedMinedItem = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which miner this miner ejects to, in case its a chainable one.
|
||||||
|
* If the value is false, it means there is no entity, and we don't have to re-check
|
||||||
|
* @type {Entity|null|false}
|
||||||
|
*/
|
||||||
|
this.cachedChainedMiner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +43,7 @@ import { HUDWiresToolbar } from "./parts/wires_toolbar";
|
|||||||
import { HUDWireInfo } from "./parts/wire_info";
|
import { HUDWireInfo } from "./parts/wire_info";
|
||||||
import { HUDLeverToggle } from "./parts/lever_toggle";
|
import { HUDLeverToggle } from "./parts/lever_toggle";
|
||||||
import { HUDLayerPreview } from "./parts/layer_preview";
|
import { HUDLayerPreview } from "./parts/layer_preview";
|
||||||
|
import { HUDMinerHighlight } from "./parts/miner_highlight";
|
||||||
|
|
||||||
export class GameHUD {
|
export class GameHUD {
|
||||||
/**
|
/**
|
||||||
@ -83,6 +84,8 @@ export class GameHUD {
|
|||||||
wiresOverlay: new HUDWiresOverlay(this.root),
|
wiresOverlay: new HUDWiresOverlay(this.root),
|
||||||
layerPreview: new HUDLayerPreview(this.root),
|
layerPreview: new HUDLayerPreview(this.root),
|
||||||
|
|
||||||
|
minerHighlight: new HUDMinerHighlight(this.root),
|
||||||
|
|
||||||
// Typing hints
|
// Typing hints
|
||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
/** @type {HUDChangesDebugger} */
|
/** @type {HUDChangesDebugger} */
|
||||||
@ -239,6 +242,7 @@ export class GameHUD {
|
|||||||
"blueprintPlacer",
|
"blueprintPlacer",
|
||||||
"colorBlindHelper",
|
"colorBlindHelper",
|
||||||
"changesDebugger",
|
"changesDebugger",
|
||||||
|
"minerHighlight",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < partsOrder.length; ++i) {
|
for (let i = 0; i < partsOrder.length; ++i) {
|
||||||
|
170
src/js/game/hud/parts/miner_highlight.js
Normal file
170
src/js/game/hud/parts/miner_highlight.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import { globalConfig } from "../../../core/config";
|
||||||
|
import { formatItemsPerSecond, round2Digits } from "../../../core/utils";
|
||||||
|
import { Vector } from "../../../core/vector";
|
||||||
|
import { T } from "../../../translations";
|
||||||
|
import { Entity } from "../../entity";
|
||||||
|
import { THEME } from "../../theme";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
export class HUDMinerHighlight extends BaseHUDPart {
|
||||||
|
initialize() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import("../../../core/draw_utils").DrawParameters} parameters
|
||||||
|
*/
|
||||||
|
draw(parameters) {
|
||||||
|
const mousePos = this.root.app.mousePosition;
|
||||||
|
if (!mousePos) {
|
||||||
|
// Mouse pos not ready
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.root.currentLayer !== "regular") {
|
||||||
|
// Not within the regular layer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.root.camera.getIsMapOverlayActive()) {
|
||||||
|
// Not within the map overlay
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const worldPos = this.root.camera.screenToWorld(mousePos);
|
||||||
|
const hoveredTile = worldPos.toTileSpace();
|
||||||
|
|
||||||
|
const contents = this.root.map.getTileContent(hoveredTile, "regular");
|
||||||
|
if (!contents) {
|
||||||
|
// Empty tile
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minerComp = contents.components.Miner;
|
||||||
|
if (!minerComp || !minerComp.chainable) {
|
||||||
|
// Not a chainable miner
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.context.fillStyle = THEME.map.connectedMiners.overlay;
|
||||||
|
|
||||||
|
const connectedEntities = this.findConnectedMiners(contents);
|
||||||
|
|
||||||
|
for (let i = 0; i < connectedEntities.length; ++i) {
|
||||||
|
const entity = connectedEntities[i];
|
||||||
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
|
||||||
|
parameters.context.beginRoundedRect(
|
||||||
|
staticComp.origin.x * globalConfig.tileSize + 5,
|
||||||
|
staticComp.origin.y * globalConfig.tileSize + 5,
|
||||||
|
globalConfig.tileSize - 10,
|
||||||
|
globalConfig.tileSize - 10,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
parameters.context.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
const throughput = round2Digits(connectedEntities.length * this.root.hubGoals.getMinerBaseSpeed());
|
||||||
|
|
||||||
|
const maxThroughput = this.root.hubGoals.getBeltBaseSpeed();
|
||||||
|
|
||||||
|
const screenPos = this.root.camera.screenToWorld(mousePos);
|
||||||
|
|
||||||
|
const scale = (1 / this.root.camera.zoomLevel) * this.root.app.getEffectiveUiScale();
|
||||||
|
|
||||||
|
const isCapped = throughput > maxThroughput;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
parameters.context.fillStyle = THEME.map.connectedMiners.background;
|
||||||
|
parameters.context.beginRoundedRect(
|
||||||
|
screenPos.x + 5 * scale,
|
||||||
|
screenPos.y - 3 * scale,
|
||||||
|
(isCapped ? 100 : 65) * scale,
|
||||||
|
(isCapped ? 45 : 30) * scale,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
parameters.context.fill();
|
||||||
|
|
||||||
|
// Throughput
|
||||||
|
parameters.context.fillStyle = THEME.map.connectedMiners.textColor;
|
||||||
|
parameters.context.font = "bold " + scale * 10 + "px GameFont";
|
||||||
|
parameters.context.fillText(
|
||||||
|
formatItemsPerSecond(throughput),
|
||||||
|
screenPos.x + 10 * scale,
|
||||||
|
screenPos.y + 10 * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
// Amount of miners
|
||||||
|
parameters.context.globalAlpha = 0.6;
|
||||||
|
parameters.context.font = "bold " + scale * 8 + "px GameFont";
|
||||||
|
parameters.context.fillText(
|
||||||
|
connectedEntities.length === 1
|
||||||
|
? T.ingame.connectedMiners.one_miner
|
||||||
|
: T.ingame.connectedMiners.n_miners.replace("<amount>", String(connectedEntities.length)),
|
||||||
|
screenPos.x + 10 * scale,
|
||||||
|
screenPos.y + 22 * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
parameters.context.globalAlpha = 1;
|
||||||
|
|
||||||
|
if (isCapped) {
|
||||||
|
parameters.context.fillStyle = THEME.map.connectedMiners.textColorCapped;
|
||||||
|
parameters.context.fillText(
|
||||||
|
T.ingame.connectedMiners.limited_items.replace(
|
||||||
|
"<max_throughput>",
|
||||||
|
formatItemsPerSecond(maxThroughput)
|
||||||
|
),
|
||||||
|
screenPos.x + 10 * scale,
|
||||||
|
screenPos.y + 34 * scale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all connected miners to the given entity
|
||||||
|
* @param {Entity} entity
|
||||||
|
* @param {Set<number>} seenUids Which entities have already been processed
|
||||||
|
* @returns {Array<Entity>} The connected miners
|
||||||
|
*/
|
||||||
|
findConnectedMiners(entity, seenUids = new Set()) {
|
||||||
|
let results = [];
|
||||||
|
const origin = entity.components.StaticMapEntity.origin;
|
||||||
|
|
||||||
|
if (!seenUids.has(entity.uid)) {
|
||||||
|
seenUids.add(entity.uid);
|
||||||
|
results.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the miner which we connect to
|
||||||
|
const connectedMiner = this.root.systemMgr.systems.miner.findChainedMiner(entity);
|
||||||
|
if (connectedMiner && !seenUids.has(connectedMiner.uid)) {
|
||||||
|
results.push(connectedMiner);
|
||||||
|
seenUids.add(connectedMiner.uid);
|
||||||
|
results.push(...this.findConnectedMiners(connectedMiner, seenUids));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search within a 1x1 grid - this assumes miners are always 1x1
|
||||||
|
for (let dx = -1; dx <= 1; ++dx) {
|
||||||
|
for (let dy = -1; dy <= 1; ++dy) {
|
||||||
|
const contents = this.root.map.getTileContent(
|
||||||
|
new Vector(origin.x + dx, origin.y + dy),
|
||||||
|
"regular"
|
||||||
|
);
|
||||||
|
if (contents) {
|
||||||
|
const minerComp = contents.components.Miner;
|
||||||
|
if (minerComp && minerComp.chainable) {
|
||||||
|
// Found a miner connected to this entity
|
||||||
|
if (!seenUids.has(contents.uid)) {
|
||||||
|
if (this.root.systemMgr.systems.miner.findChainedMiner(contents) === entity) {
|
||||||
|
results.push(contents);
|
||||||
|
seenUids.add(contents.uid);
|
||||||
|
results.push(...this.findConnectedMiners(contents, seenUids));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
@ -212,43 +212,5 @@ export class MapView extends BaseMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
|
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.showChunkBorders) {
|
|
||||||
const cullRange = parameters.visibleRect.toTileCullRectangle();
|
|
||||||
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 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);
|
|
||||||
|
|
||||||
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
|
|
||||||
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
|
|
||||||
parameters.context.fillStyle = "#ffaaaa";
|
|
||||||
parameters.context.fillRect(
|
|
||||||
chunkX * globalConfig.mapChunkWorldSize,
|
|
||||||
chunkY * globalConfig.mapChunkWorldSize,
|
|
||||||
globalConfig.mapChunkWorldSize,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
parameters.context.fillRect(
|
|
||||||
chunkX * globalConfig.mapChunkWorldSize,
|
|
||||||
chunkY * globalConfig.mapChunkWorldSize,
|
|
||||||
3,
|
|
||||||
globalConfig.mapChunkWorldSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,24 @@ import { MapChunkView } from "../map_chunk_view";
|
|||||||
export class MinerSystem extends GameSystemWithFilter {
|
export class MinerSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [MinerComponent]);
|
super(root, [MinerComponent]);
|
||||||
|
|
||||||
|
this.needsRecompute = true;
|
||||||
|
|
||||||
|
this.root.signals.entityAdded.add(this.onEntityChanged, this);
|
||||||
|
this.root.signals.entityChanged.add(this.onEntityChanged, this);
|
||||||
|
this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever an entity got changed
|
||||||
|
* @param {Entity} entity
|
||||||
|
*/
|
||||||
|
onEntityChanged(entity) {
|
||||||
|
const minerComp = entity.components.Miner;
|
||||||
|
if (minerComp && minerComp.chainable) {
|
||||||
|
// Miner component, need to recompute
|
||||||
|
this.needsRecompute = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@ -20,11 +38,15 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||||
const entity = this.allEntities[i];
|
const entity = this.allEntities[i];
|
||||||
|
const minerComp = entity.components.Miner;
|
||||||
|
|
||||||
|
// Reset everything on recompute
|
||||||
|
if (this.needsRecompute) {
|
||||||
|
minerComp.cachedChainedMiner = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if miner is above an actual tile
|
// Check if miner is above an actual tile
|
||||||
|
|
||||||
const minerComp = entity.components.Miner;
|
|
||||||
|
|
||||||
if (!minerComp.cachedMinedItem) {
|
if (!minerComp.cachedMinedItem) {
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
const tileBelow = this.root.map.getLowerLayerContentXY(
|
const tileBelow = this.root.map.getLowerLayerContentXY(
|
||||||
@ -59,20 +81,20 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After this frame we are done
|
||||||
|
this.needsRecompute = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Finds the target chained miner for a given entity
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
* @param {BaseItem} item
|
* @returns {Entity|false} The chained entity or null if not found
|
||||||
*/
|
*/
|
||||||
tryPerformMinerEject(entity, item) {
|
findChainedMiner(entity) {
|
||||||
const minerComp = entity.components.Miner;
|
|
||||||
const ejectComp = entity.components.ItemEjector;
|
const ejectComp = entity.components.ItemEjector;
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
|
||||||
// Check if we are a chained miner
|
|
||||||
if (minerComp.chainable) {
|
|
||||||
const ejectingSlot = ejectComp.slots[0];
|
const ejectingSlot = ejectComp.slots[0];
|
||||||
const ejectingPos = staticComp.localTileToWorld(ejectingSlot.pos);
|
const ejectingPos = staticComp.localTileToWorld(ejectingSlot.pos);
|
||||||
const ejectingDirection = staticComp.localDirectionToWorld(ejectingSlot.direction);
|
const ejectingDirection = staticComp.localDirectionToWorld(ejectingSlot.direction);
|
||||||
@ -83,7 +105,35 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
// Check if we are connected to another miner and thus do not eject directly
|
// Check if we are connected to another miner and thus do not eject directly
|
||||||
if (targetContents) {
|
if (targetContents) {
|
||||||
const targetMinerComp = targetContents.components.Miner;
|
const targetMinerComp = targetContents.components.Miner;
|
||||||
if (targetMinerComp) {
|
if (targetMinerComp && targetMinerComp.chainable) {
|
||||||
|
return targetContents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Entity} entity
|
||||||
|
* @param {BaseItem} item
|
||||||
|
*/
|
||||||
|
tryPerformMinerEject(entity, item) {
|
||||||
|
const minerComp = entity.components.Miner;
|
||||||
|
const ejectComp = entity.components.ItemEjector;
|
||||||
|
|
||||||
|
// Check if we are a chained miner
|
||||||
|
if (minerComp.chainable) {
|
||||||
|
const targetEntity = minerComp.cachedChainedMiner;
|
||||||
|
|
||||||
|
// Check if the cache has to get recomputed
|
||||||
|
if (targetEntity === null) {
|
||||||
|
minerComp.cachedChainedMiner = this.findChainedMiner(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we now have a target
|
||||||
|
if (targetEntity) {
|
||||||
|
const targetMinerComp = targetEntity.components.Miner;
|
||||||
if (targetMinerComp.tryAcceptChainedItem(item)) {
|
if (targetMinerComp.tryAcceptChainedItem(item)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -91,12 +141,12 @@ export class MinerSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Seems we are a regular miner or at the end of a row, try actually ejecting
|
// Seems we are a regular miner or at the end of a row, try actually ejecting
|
||||||
if (ejectComp.tryEject(0, item)) {
|
if (ejectComp.tryEject(0, item)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,13 @@
|
|||||||
"overlayColor": "rgba(97, 161, 152, 0.75)",
|
"overlayColor": "rgba(97, 161, 152, 0.75)",
|
||||||
"previewColor": "rgb(97, 161, 152, 0.5)",
|
"previewColor": "rgb(97, 161, 152, 0.5)",
|
||||||
"highlightColor": "rgba(0, 0, 255, 0.5)"
|
"highlightColor": "rgba(0, 0, 255, 0.5)"
|
||||||
|
},
|
||||||
|
|
||||||
|
"connectedMiners": {
|
||||||
|
"overlay": "rgba(40, 50, 60, 0.5)",
|
||||||
|
"textColor": "#fff",
|
||||||
|
"textColorCapped": "#ef5072",
|
||||||
|
"background": "rgba(40, 50, 60, 0.8)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -39,7 +39,14 @@
|
|||||||
"wires": {
|
"wires": {
|
||||||
"overlayColor": "rgba(97, 161, 152, 0.75)",
|
"overlayColor": "rgba(97, 161, 152, 0.75)",
|
||||||
"previewColor": "rgb(97, 161, 152, 0.4)",
|
"previewColor": "rgb(97, 161, 152, 0.4)",
|
||||||
"highlightColor": "rgba(72, 137, 255, 0.8)"
|
"highlightColor": "rgba(72, 137, 255, 1)"
|
||||||
|
},
|
||||||
|
|
||||||
|
"connectedMiners": {
|
||||||
|
"overlay": "rgba(40, 50, 60, 0.5)",
|
||||||
|
"textColor": "#fff",
|
||||||
|
"textColorCapped": "#ef5072",
|
||||||
|
"background": "rgba(40, 50, 60, 0.8)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -426,6 +426,12 @@ ingame:
|
|||||||
1_3_expand: >-
|
1_3_expand: >-
|
||||||
This is <strong>NOT</strong> an idle game! Build more extractors and belts to finish the goal quicker.<br><br>Tip: Hold <strong>SHIFT</strong> to place multiple extractors, and use <strong>R</strong> to rotate them.
|
This is <strong>NOT</strong> an idle game! Build more extractors and belts to finish the goal quicker.<br><br>Tip: Hold <strong>SHIFT</strong> to place multiple extractors, and use <strong>R</strong> to rotate them.
|
||||||
|
|
||||||
|
# Connected miners
|
||||||
|
connectedMiners:
|
||||||
|
one_miner: 1 Miner
|
||||||
|
n_miners: <amount> Miners
|
||||||
|
limited_items: Limited to <max_throughput>
|
||||||
|
|
||||||
# All shop upgrades
|
# All shop upgrades
|
||||||
shopUpgrades:
|
shopUpgrades:
|
||||||
belt:
|
belt:
|
||||||
|
Loading…
Reference in New Issue
Block a user