Highlight connected miners, improve miner performance

This commit is contained in:
tobspr 2020-09-13 18:39:06 +02:00
parent 3529a5d77f
commit 0377c6d58f
9 changed files with 548 additions and 333 deletions

View File

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

View File

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

View File

@ -1,277 +1,281 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "../root"; import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
/* dev:start */ /* dev:start */
import { TrailerMaker } from "./trailer_maker"; import { TrailerMaker } from "./trailer_maker";
/* dev:end */ /* dev:end */
import { Signal } from "../../core/signal"; import { Signal } from "../../core/signal";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { HUDBuildingsToolbar } from "./parts/buildings_toolbar"; import { HUDBuildingsToolbar } from "./parts/buildings_toolbar";
import { HUDBuildingPlacer } from "./parts/building_placer"; import { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDBlueprintPlacer } from "./parts/blueprint_placer"; import { HUDBlueprintPlacer } from "./parts/blueprint_placer";
import { HUDKeybindingOverlay } from "./parts/keybinding_overlay"; import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
import { HUDUnlockNotification } from "./parts/unlock_notification"; import { HUDUnlockNotification } from "./parts/unlock_notification";
import { HUDGameMenu } from "./parts/game_menu"; import { HUDGameMenu } from "./parts/game_menu";
import { HUDShop } from "./parts/shop"; import { HUDShop } from "./parts/shop";
import { IS_MOBILE, globalConfig, IS_DEMO } from "../../core/config"; import { IS_MOBILE, globalConfig, IS_DEMO } from "../../core/config";
import { HUDMassSelector } from "./parts/mass_selector"; import { HUDMassSelector } from "./parts/mass_selector";
import { HUDVignetteOverlay } from "./parts/vignette_overlay"; import { HUDVignetteOverlay } from "./parts/vignette_overlay";
import { HUDStatistics } from "./parts/statistics"; import { HUDStatistics } from "./parts/statistics";
import { MetaBuilding } from "../meta_building"; import { MetaBuilding } from "../meta_building";
import { HUDPinnedShapes } from "./parts/pinned_shapes"; import { HUDPinnedShapes } from "./parts/pinned_shapes";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { HUDNotifications, enumNotificationType } from "./parts/notifications"; import { HUDNotifications, enumNotificationType } from "./parts/notifications";
import { HUDSettingsMenu } from "./parts/settings_menu"; import { HUDSettingsMenu } from "./parts/settings_menu";
import { HUDDebugInfo } from "./parts/debug_info"; import { HUDDebugInfo } from "./parts/debug_info";
import { HUDEntityDebugger } from "./parts/entity_debugger"; import { HUDEntityDebugger } from "./parts/entity_debugger";
import { KEYMAPPINGS } from "../key_action_mapper"; import { KEYMAPPINGS } from "../key_action_mapper";
import { HUDWatermark } from "./parts/watermark"; import { HUDWatermark } from "./parts/watermark";
import { HUDModalDialogs } from "./parts/modal_dialogs"; import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDPartTutorialHints } from "./parts/tutorial_hints"; import { HUDPartTutorialHints } from "./parts/tutorial_hints";
import { HUDWaypoints } from "./parts/waypoints"; import { HUDWaypoints } from "./parts/waypoints";
import { HUDInteractiveTutorial } from "./parts/interactive_tutorial"; import { HUDInteractiveTutorial } from "./parts/interactive_tutorial";
import { HUDScreenshotExporter } from "./parts/screenshot_exporter"; import { HUDScreenshotExporter } from "./parts/screenshot_exporter";
import { HUDColorBlindHelper } from "./parts/color_blind_helper"; import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDShapeViewer } from "./parts/shape_viewer"; import { HUDShapeViewer } from "./parts/shape_viewer";
import { HUDWiresOverlay } from "./parts/wires_overlay"; import { HUDWiresOverlay } from "./parts/wires_overlay";
import { HUDChangesDebugger } from "./parts/debug_changes"; import { HUDChangesDebugger } from "./parts/debug_changes";
import { queryParamOptions } from "../../core/query_parameters"; import { queryParamOptions } from "../../core/query_parameters";
import { HUDSandboxController } from "./parts/sandbox_controller"; import { HUDSandboxController } from "./parts/sandbox_controller";
import { HUDWiresToolbar } from "./parts/wires_toolbar"; 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 {
* @param {GameRoot} root /**
*/ * @param {GameRoot} root
constructor(root) { */
this.root = root; constructor(root) {
} this.root = root;
}
/**
* Initializes the hud parts /**
*/ * Initializes the hud parts
initialize() { */
this.parts = { initialize() {
buildingsToolbar: new HUDBuildingsToolbar(this.root), this.parts = {
wiresToolbar: new HUDWiresToolbar(this.root), buildingsToolbar: new HUDBuildingsToolbar(this.root),
blueprintPlacer: new HUDBlueprintPlacer(this.root), wiresToolbar: new HUDWiresToolbar(this.root),
buildingPlacer: new HUDBuildingPlacer(this.root), blueprintPlacer: new HUDBlueprintPlacer(this.root),
unlockNotification: new HUDUnlockNotification(this.root), buildingPlacer: new HUDBuildingPlacer(this.root),
gameMenu: new HUDGameMenu(this.root), unlockNotification: new HUDUnlockNotification(this.root),
massSelector: new HUDMassSelector(this.root), gameMenu: new HUDGameMenu(this.root),
shop: new HUDShop(this.root), massSelector: new HUDMassSelector(this.root),
statistics: new HUDStatistics(this.root), shop: new HUDShop(this.root),
waypoints: new HUDWaypoints(this.root), statistics: new HUDStatistics(this.root),
wireInfo: new HUDWireInfo(this.root), waypoints: new HUDWaypoints(this.root),
leverToggle: new HUDLeverToggle(this.root), wireInfo: new HUDWireInfo(this.root),
leverToggle: new HUDLeverToggle(this.root),
// Must always exist
pinnedShapes: new HUDPinnedShapes(this.root), // Must always exist
notifications: new HUDNotifications(this.root), pinnedShapes: new HUDPinnedShapes(this.root),
settingsMenu: new HUDSettingsMenu(this.root), notifications: new HUDNotifications(this.root),
// betaOverlay: new HUDBetaOverlay(this.root), settingsMenu: new HUDSettingsMenu(this.root),
debugInfo: new HUDDebugInfo(this.root), // betaOverlay: new HUDBetaOverlay(this.root),
dialogs: new HUDModalDialogs(this.root), debugInfo: new HUDDebugInfo(this.root),
screenshotExporter: new HUDScreenshotExporter(this.root), dialogs: new HUDModalDialogs(this.root),
shapeViewer: new HUDShapeViewer(this.root), screenshotExporter: new HUDScreenshotExporter(this.root),
shapeViewer: new HUDShapeViewer(this.root),
wiresOverlay: new HUDWiresOverlay(this.root),
layerPreview: new HUDLayerPreview(this.root), wiresOverlay: new HUDWiresOverlay(this.root),
layerPreview: new HUDLayerPreview(this.root),
// Typing hints
/* typehints:start */ minerHighlight: new HUDMinerHighlight(this.root),
/** @type {HUDChangesDebugger} */
changesDebugger: null, // Typing hints
/* typehints:end */ /* typehints:start */
}; /** @type {HUDChangesDebugger} */
changesDebugger: null,
this.signals = { /* typehints:end */
buildingSelectedForPlacement: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()), };
selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()),
shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), this.signals = {
shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()), buildingSelectedForPlacement: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()),
notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()), selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()),
buildingsSelectedForCopy: /** @type {TypedSignal<[Array<number>]>} */ (new Signal()), shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
pasteBlueprintRequested: /** @type {TypedSignal<[]>} */ (new Signal()), shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()),
viewShapeDetailsRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()),
}; buildingsSelectedForCopy: /** @type {TypedSignal<[Array<number>]>} */ (new Signal()),
pasteBlueprintRequested: /** @type {TypedSignal<[]>} */ (new Signal()),
if (!IS_MOBILE) { viewShapeDetailsRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root); };
}
if (!IS_MOBILE) {
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) { this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root);
this.parts.entityDebugger = new HUDEntityDebugger(this.root); }
}
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
if (IS_DEMO) { this.parts.entityDebugger = new HUDEntityDebugger(this.root);
this.parts.watermark = new HUDWatermark(this.root); }
}
if (IS_DEMO) {
if (G_IS_DEV && globalConfig.debug.renderChanges) { this.parts.watermark = new HUDWatermark(this.root);
this.parts.changesDebugger = new HUDChangesDebugger(this.root); }
}
if (G_IS_DEV && globalConfig.debug.renderChanges) {
if (this.root.app.settings.getAllSettings().offerHints) { this.parts.changesDebugger = new HUDChangesDebugger(this.root);
this.parts.tutorialHints = new HUDPartTutorialHints(this.root); }
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
} if (this.root.app.settings.getAllSettings().offerHints) {
this.parts.tutorialHints = new HUDPartTutorialHints(this.root);
if (this.root.app.settings.getAllSettings().vignette) { this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root); }
}
if (this.root.app.settings.getAllSettings().vignette) {
if (this.root.app.settings.getAllSettings().enableColorBlindHelper) { this.parts.vignetteOverlay = new HUDVignetteOverlay(this.root);
this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root); }
}
if (this.root.app.settings.getAllSettings().enableColorBlindHelper) {
if (queryParamOptions.sandboxMode || G_IS_DEV) { this.parts.colorBlindHelper = new HUDColorBlindHelper(this.root);
this.parts.sandboxController = new HUDSandboxController(this.root); }
}
if (queryParamOptions.sandboxMode || G_IS_DEV) {
const frag = document.createDocumentFragment(); this.parts.sandboxController = new HUDSandboxController(this.root);
for (const key in this.parts) { }
this.parts[key].createElements(frag);
} const frag = document.createDocumentFragment();
for (const key in this.parts) {
document.body.appendChild(frag); this.parts[key].createElements(frag);
}
for (const key in this.parts) {
this.parts[key].initialize(); document.body.appendChild(frag);
}
for (const key in this.parts) {
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this); this.parts[key].initialize();
}
/* dev:start */
if (G_IS_DEV && globalConfig.debug.renderForTrailer) { this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this);
this.trailerMaker = new TrailerMaker(this.root);
} /* dev:start */
/* dev:end*/ if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
} this.trailerMaker = new TrailerMaker(this.root);
}
/** /* dev:end*/
* Attempts to close all overlays }
*/
closeAllOverlays() { /**
for (const key in this.parts) { * Attempts to close all overlays
this.parts[key].close(); */
} closeAllOverlays() {
} for (const key in this.parts) {
this.parts[key].close();
/** }
* Returns true if the game logic should be paused }
*/
shouldPauseGame() { /**
for (const key in this.parts) { * Returns true if the game logic should be paused
if (this.parts[key].shouldPauseGame()) { */
return true; shouldPauseGame() {
} for (const key in this.parts) {
} if (this.parts[key].shouldPauseGame()) {
return false; return true;
} }
}
/** return false;
* Returns true if the rendering can be paused }
*/
shouldPauseRendering() { /**
for (const key in this.parts) { * Returns true if the rendering can be paused
if (this.parts[key].shouldPauseRendering()) { */
return true; shouldPauseRendering() {
} for (const key in this.parts) {
} if (this.parts[key].shouldPauseRendering()) {
return false; return true;
} }
}
/** return false;
* Returns true if the rendering can be paused }
*/
hasBlockingOverlayOpen() { /**
if (this.root.camera.getIsMapOverlayActive()) { * Returns true if the rendering can be paused
return true; */
} hasBlockingOverlayOpen() {
for (const key in this.parts) { if (this.root.camera.getIsMapOverlayActive()) {
if (this.parts[key].isBlockingOverlay()) { return true;
return true; }
} for (const key in this.parts) {
} if (this.parts[key].isBlockingOverlay()) {
return false; return true;
} }
}
/** return false;
* Toggles the ui }
*/
toggleUi() { /**
document.body.classList.toggle("uiHidden"); * Toggles the ui
} */
toggleUi() {
/** document.body.classList.toggle("uiHidden");
* Updates all parts }
*/
update() { /**
if (!this.root.gameInitialized) { * Updates all parts
return; */
} update() {
if (!this.root.gameInitialized) {
for (const key in this.parts) { return;
this.parts[key].update(); }
}
for (const key in this.parts) {
/* dev:start */ this.parts[key].update();
if (this.trailerMaker) { }
this.trailerMaker.update();
} /* dev:start */
/* dev:end*/ if (this.trailerMaker) {
} this.trailerMaker.update();
}
/** /* dev:end*/
* Draws all parts }
* @param {DrawParameters} parameters
*/ /**
draw(parameters) { * Draws all parts
const partsOrder = [ * @param {DrawParameters} parameters
"massSelector", */
"buildingPlacer", draw(parameters) {
"blueprintPlacer", const partsOrder = [
"colorBlindHelper", "massSelector",
"changesDebugger", "buildingPlacer",
]; "blueprintPlacer",
"colorBlindHelper",
for (let i = 0; i < partsOrder.length; ++i) { "changesDebugger",
if (this.parts[partsOrder[i]]) { "minerHighlight",
this.parts[partsOrder[i]].draw(parameters); ];
}
} for (let i = 0; i < partsOrder.length; ++i) {
} if (this.parts[partsOrder[i]]) {
this.parts[partsOrder[i]].draw(parameters);
/** }
* Draws all part overlays }
* @param {DrawParameters} parameters }
*/
drawOverlays(parameters) { /**
const partsOrder = ["waypoints", "watermark", "wireInfo"]; * Draws all part overlays
* @param {DrawParameters} parameters
for (let i = 0; i < partsOrder.length; ++i) { */
if (this.parts[partsOrder[i]]) { drawOverlays(parameters) {
this.parts[partsOrder[i]].drawOverlays(parameters); const partsOrder = ["waypoints", "watermark", "wireInfo"];
}
} for (let i = 0; i < partsOrder.length; ++i) {
} if (this.parts[partsOrder[i]]) {
this.parts[partsOrder[i]].drawOverlays(parameters);
/** }
* Cleans up everything }
*/ }
cleanup() {
for (const key in this.parts) { /**
this.parts[key].cleanup(); * Cleans up everything
} */
cleanup() {
for (const key in this.signals) { for (const key in this.parts) {
this.signals[key].removeAll(); this.parts[key].cleanup();
} }
}
} for (const key in this.signals) {
this.signals[key].removeAll();
}
}
}

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

View File

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

View File

@ -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,6 +81,36 @@ 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
* @returns {Entity|false} The chained entity or null if not found
*/
findChainedMiner(entity) {
const ejectComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity;
const ejectingSlot = ejectComp.slots[0];
const ejectingPos = staticComp.localTileToWorld(ejectingSlot.pos);
const ejectingDirection = staticComp.localDirectionToWorld(ejectingSlot.direction);
const targetTile = ejectingPos.add(enumDirectionToVector[ejectingDirection]);
const targetContents = this.root.map.getTileContent(targetTile, "regular");
// Check if we are connected to another miner and thus do not eject directly
if (targetContents) {
const targetMinerComp = targetContents.components.Miner;
if (targetMinerComp && targetMinerComp.chainable) {
return targetContents;
}
}
return false;
} }
/** /**
@ -69,26 +121,23 @@ export class MinerSystem extends GameSystemWithFilter {
tryPerformMinerEject(entity, item) { tryPerformMinerEject(entity, item) {
const minerComp = entity.components.Miner; const minerComp = entity.components.Miner;
const ejectComp = entity.components.ItemEjector; const ejectComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity;
// Check if we are a chained miner // Check if we are a chained miner
if (minerComp.chainable) { if (minerComp.chainable) {
const ejectingSlot = ejectComp.slots[0]; const targetEntity = minerComp.cachedChainedMiner;
const ejectingPos = staticComp.localTileToWorld(ejectingSlot.pos);
const ejectingDirection = staticComp.localDirectionToWorld(ejectingSlot.direction);
const targetTile = ejectingPos.add(enumDirectionToVector[ejectingDirection]); // Check if the cache has to get recomputed
const targetContents = this.root.map.getTileContent(targetTile, "regular"); if (targetEntity === null) {
minerComp.cachedChainedMiner = this.findChainedMiner(entity);
}
// Check if we are connected to another miner and thus do not eject directly // Check if we now have a target
if (targetContents) { if (targetEntity) {
const targetMinerComp = targetContents.components.Miner; const targetMinerComp = targetEntity.components.Miner;
if (targetMinerComp) { if (targetMinerComp.tryAcceptChainedItem(item)) {
if (targetMinerComp.tryAcceptChainedItem(item)) { return true;
return true; } else {
} else { return false;
return false;
}
} }
} }
} }
@ -97,6 +146,7 @@ export class MinerSystem extends GameSystemWithFilter {
if (ejectComp.tryEject(0, item)) { if (ejectComp.tryEject(0, item)) {
return true; return true;
} }
return false; return false;
} }

View File

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

View File

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

View File

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