diff --git a/src/js/game/components/miner.js b/src/js/game/components/miner.js index 5321ae11..54ac3b4a 100644 --- a/src/js/game/components/miner.js +++ b/src/js/game/components/miner.js @@ -2,9 +2,13 @@ import { types } from "../../savegame/serialization"; import { BaseItem } from "../base_item"; import { Component } from "../component"; import { Entity } from "../entity"; -import { typeItemSingleton } from "../item_resolver"; -const chainBufferSize = 6; +/** + * @typedef {{ + * item: BaseItem, + * extraProgress?: number, + * }} MinerItem + */ export class MinerComponent extends Component { static getId() { @@ -14,17 +18,17 @@ export class MinerComponent extends Component { static getSchema() { // cachedMinedItem is not serialized. return { - lastMiningTime: types.ufloat, - itemChainBuffer: types.array(typeItemSingleton), + progress: types.ufloat, }; } constructor({ chainable = false }) { super(); - this.lastMiningTime = 0; + this.progress = 0; this.chainable = chainable; /** + * The item we are mining beneath us * @type {BaseItem} */ this.cachedMinedItem = null; @@ -35,30 +39,11 @@ export class MinerComponent extends Component { * @type {Entity|null|false} */ this.cachedChainedMiner = null; - - this.clear(); - } - - clear() { /** - * Stores items from other miners which were chained to this - * miner. - * @type {Array} + * The miner at the end of the chain, which actually ejects the items + * If the value is false, it means there is no entity, and we don't have to re-check + * @type {Entity|null|false} */ - this.itemChainBuffer = []; - } - - /** - * - * @param {BaseItem} item - */ - tryAcceptChainedItem(item) { - if (this.itemChainBuffer.length > chainBufferSize) { - // Well, this one is full - return false; - } - - this.itemChainBuffer.push(item); - return true; + this.cachedExitMiner = null; } } diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index d8c16200..603511b1 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -150,12 +150,12 @@ export class GameSystemManager { add("belt", BeltSystem); - add("miner", MinerSystem); - add("storage", StorageSystem); add("itemEjector", ItemEjectorSystem); + add("miner", MinerSystem); + add("undergroundBelt", UndergroundBeltSystem); add("itemProcessor", ItemProcessorSystem); diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index d2904720..218af719 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -303,17 +303,14 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic { const mouseTile = worldPos.toTileSpace(); // Compute best rotation variant - const { - rotation, - rotationVariant, - connectedEntities, - } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({ - root: this.root, - tile: mouseTile, - rotation: this.currentBaseRotation, - variant: this.currentVariant.get(), - layer: metaBuilding.getLayer(), - }); + const { rotation, rotationVariant, connectedEntities } = + metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({ + root: this.root, + tile: mouseTile, + rotation: this.currentBaseRotation, + variant: this.currentVariant.get(), + layer: metaBuilding.getLayer(), + }); // Check if there are connected entities if (connectedEntities) { @@ -657,8 +654,16 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic { // Connected to a belt isConnected = true; } else if (minerComp && minerComp.chainable && destMiner && destMiner.chainable) { - // Chainable miners connected to eachother - isConnected = true; + const worldTile = staticComp.localTileToWorld(slot.pos); + if ( + this.root.map.getLowerLayerContentXY(worldTile.x, worldTile.y) == + destMiner.cachedMinedItem + ) { + // Chainable miners connected to eachother + isConnected = true; + } else { + isBlocked = true; + } } else { // This one is blocked isBlocked = true; diff --git a/src/js/game/hud/parts/miner_highlight.js b/src/js/game/hud/parts/miner_highlight.js index a0c6919d..0e5faef1 100644 --- a/src/js/game/hud/parts/miner_highlight.js +++ b/src/js/game/hud/parts/miner_highlight.js @@ -134,6 +134,7 @@ export class HUDMinerHighlight extends BaseHUDPart { findConnectedMiners(entity, seenUids = new Set()) { let results = []; const origin = entity.components.StaticMapEntity.origin; + const originMinerComp = entity.components.Miner; if (!seenUids.has(entity.uid)) { seenUids.add(entity.uid); @@ -157,7 +158,11 @@ export class HUDMinerHighlight extends BaseHUDPart { ); if (contents) { const minerComp = contents.components.Miner; - if (minerComp && minerComp.chainable) { + if ( + minerComp && + minerComp.chainable && + originMinerComp.cachedMinedItem == minerComp.cachedMinedItem + ) { // Found a miner connected to this entity if (!seenUids.has(contents.uid)) { if (this.root.systemMgr.systems.miner.findChainedMiner(contents) === entity) { diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index ea8e7381..0cc871c5 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -163,7 +163,14 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - if (sourceSlot.progress < maxProgress) { + // Limit progress here as well + let progressLimit = maxProgress; + const destPath = sourceSlot.cachedBeltPath; + if (destPath) { + progressLimit += destPath.spacingToFirstItem - globalConfig.itemSpacingOnBelts; + } + + if (sourceSlot.progress < progressLimit) { // Advance items on the slot sourceSlot.progress += progressGrowth; } @@ -179,8 +186,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter { const extraProgress = sourceSlot.progress - maxProgress; - // Check if we are ejecting to a belt path - const destPath = sourceSlot.cachedBeltPath; if (destPath) { // Try passing the item over if (destPath.tryAcceptItem(item, extraProgress)) { diff --git a/src/js/game/systems/item_producer.js b/src/js/game/systems/item_producer.js index 8ca29ae1..ec194df0 100644 --- a/src/js/game/systems/item_producer.js +++ b/src/js/game/systems/item_producer.js @@ -24,7 +24,9 @@ export class ItemProducerSystem extends GameSystemWithFilter { } this.item = network.currentValue; - ejectorComp.tryEject(0, this.item); + + // Basically start ejecting at the exit of the ejector. Hacky, but who cares. It works, and its not in the base game :) + ejectorComp.tryEject(0, this.item, 0.5); } } } diff --git a/src/js/game/systems/miner.js b/src/js/game/systems/miner.js index cd478be3..9404c4ca 100644 --- a/src/js/game/systems/miner.js +++ b/src/js/game/systems/miner.js @@ -1,7 +1,6 @@ import { globalConfig } from "../../core/config"; import { DrawParameters } from "../../core/draw_parameters"; import { enumDirectionToVector } from "../../core/vector"; -import { BaseItem } from "../base_item"; import { MinerComponent } from "../components/miner"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; @@ -31,20 +30,18 @@ export class MinerSystem extends GameSystemWithFilter { } update() { - let miningSpeed = this.root.hubGoals.getMinerBaseSpeed(); + let progressGrowth = this.root.dynamicTickrate.deltaSeconds * this.root.hubGoals.getMinerBaseSpeed(); + + const targetProgress = 1; + if (G_IS_DEV && globalConfig.debug.instantMiners) { - miningSpeed *= 100; + progressGrowth = targetProgress; } for (let i = 0; i < this.allEntities.length; ++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 if (!minerComp.cachedMinedItem) { const staticComp = entity.components.StaticMapEntity; @@ -58,25 +55,60 @@ export class MinerSystem extends GameSystemWithFilter { minerComp.cachedMinedItem = tileBelow; } - // First, try to get rid of chained items - if (minerComp.itemChainBuffer.length > 0) { - if (this.tryPerformMinerEject(entity, minerComp.itemChainBuffer[0])) { - minerComp.itemChainBuffer.shift(); + // Reset everything on recompute + if (this.needsRecompute) { + minerComp.cachedChainedMiner = null; + minerComp.cachedExitMiner = null; + } + + // Check if we are a chained miner + if (minerComp.chainable) { + if (!minerComp.cachedChainedMiner) { + minerComp.cachedChainedMiner = this.findChainedMiner(entity); + } + + // don't calculate on the same tick as recompute, or so miners wont have caches yet + if (minerComp.cachedChainedMiner && !minerComp.cachedExitMiner && !this.needsRecompute) { + minerComp.cachedExitMiner = this.findExitMiner(entity); + } + + // Check if we now have a target at the end of the chain - if so, that's what we will progress + const exitEntity = minerComp.cachedExitMiner; + if (exitEntity) { + const exitMinerComp = exitEntity.components.Miner; + exitMinerComp.progress += progressGrowth; continue; } } + //console.log(minerComp.progress); - const mineDuration = 1 / miningSpeed; - const timeSinceMine = this.root.time.now() - minerComp.lastMiningTime; - if (timeSinceMine > mineDuration) { - // Store how much we overflowed - const buffer = Math.min(timeSinceMine - mineDuration, this.root.dynamicTickrate.deltaSeconds); + if (minerComp.progress >= targetProgress) { + // We can try to eject + const extraProgress = minerComp.progress - targetProgress; - if (this.tryPerformMinerEject(entity, minerComp.cachedMinedItem)) { + const ejectorComp = entity.components.ItemEjector; + if (ejectorComp.tryEject(0, minerComp.cachedMinedItem, extraProgress)) { // Analytics hook this.root.signals.itemProduced.dispatch(minerComp.cachedMinedItem); - // Store mining time - minerComp.lastMiningTime = this.root.time.now() - buffer; + + minerComp.progress -= targetProgress; + } + } + + if (minerComp.progress < targetProgress) { + minerComp.progress += progressGrowth; + } + + if (minerComp.progress >= targetProgress) { + // We can try to eject + const extraProgress = minerComp.progress - targetProgress; + + const ejectorComp = entity.components.ItemEjector; + if (ejectorComp.tryEject(0, minerComp.cachedMinedItem, extraProgress)) { + // Analytics hook + this.root.signals.itemProduced.dispatch(minerComp.cachedMinedItem); + + minerComp.progress -= targetProgress; } } } @@ -93,6 +125,7 @@ export class MinerSystem extends GameSystemWithFilter { findChainedMiner(entity) { const ejectComp = entity.components.ItemEjector; const staticComp = entity.components.StaticMapEntity; + const minedItem = entity.components.Miner.cachedMinedItem; const contentsBelow = this.root.map.getLowerLayerContentXY(staticComp.origin.x, staticComp.origin.y); if (!contentsBelow) { // This miner has no contents @@ -109,7 +142,11 @@ export class MinerSystem extends GameSystemWithFilter { // 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) { + if ( + targetMinerComp && + targetMinerComp.chainable && + targetMinerComp.cachedMinedItem == minedItem + ) { const targetLowerLayer = this.root.map.getLowerLayerContentXY(targetTile.x, targetTile.y); if (targetLowerLayer) { return targetContents; @@ -121,39 +158,37 @@ export class MinerSystem extends GameSystemWithFilter { } /** - * + * Finds the target exit miner for a given entity * @param {Entity} entity - * @param {BaseItem} item + * @returns {Entity|false} The exit miner entity or null if not found */ - tryPerformMinerEject(entity, item) { + findExitMiner(entity) { const minerComp = entity.components.Miner; - const ejectComp = entity.components.ItemEjector; + // Recompute exit miner if we are not at the front + let targetEntity = minerComp.cachedChainedMiner; - // Check if we are a chained miner - if (minerComp.chainable) { - const targetEntity = minerComp.cachedChainedMiner; + const ourPosition = entity.components.StaticMapEntity.origin; - // 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)) { - return true; - } else { - return false; - } + /** @type {Entity|null|false} */ + let nextTarget = targetEntity; + while (nextTarget) { + targetEntity = nextTarget; + if (targetEntity.components.StaticMapEntity.origin == ourPosition) { + // we are in a loop, do nothing + targetEntity = null; + break; } + const targetMinerComp = targetEntity.components.Miner; + nextTarget = targetMinerComp.cachedChainedMiner; } - // Seems we are a regular miner or at the end of a row, try actually ejecting - if (ejectComp.tryEject(0, item)) { - return true; + if (targetEntity) { + const targetMinerComp = targetEntity.components.Miner; + if (targetMinerComp.cachedMinedItem == minerComp.cachedMinedItem) { + // only chain the same items + return targetEntity; + } } - return false; }