diff --git a/src/js/game/belt_path.js b/src/js/game/belt_path.js index 0b6da48c..3fd33aae 100644 --- a/src/js/game/belt_path.js +++ b/src/js/game/belt_path.js @@ -1,14 +1,14 @@ import { globalConfig } from "../core/config"; import { DrawParameters } from "../core/draw_parameters"; +import { gItemRegistry } from "../core/global_registries"; import { createLogger } from "../core/logging"; +import { Rectangle } from "../core/rectangle"; import { epsilonCompare, round4Digits } from "../core/utils"; -import { Vector } from "../core/vector"; +import { enumDirection, enumDirectionToVector, Vector } from "../core/vector"; +import { BasicSerializableObject, types } from "../savegame/serialization"; import { BaseItem } from "./base_item"; import { Entity } from "./entity"; -import { GameRoot, enumLayer } from "./root"; -import { Rectangle } from "../core/rectangle"; -import { BasicSerializableObject, types } from "../savegame/serialization"; -import { gItemRegistry } from "../core/global_registries"; +import { GameRoot } from "./root"; const logger = createLogger("belt_path"); @@ -125,14 +125,6 @@ export class BeltPath extends BasicSerializableObject { return globalConfig.beltItemSpacingByLayer[this.layer]; } - /** - * Returns the layer of the this path - * @returns {enumLayer} - */ - getLayer() { - return this.entityPath[0].layer; - } - /** * Tries to accept the item * @param {BaseItem} item @@ -167,7 +159,7 @@ export class BeltPath extends BasicSerializableObject { * @returns {BaseItem|null} */ findItemAtTile(tile) { - // TODO: This breaks color blind mode otherwise + // @TODO: This breaks color blind mode otherwise return null; } @@ -186,27 +178,82 @@ export class BeltPath extends BasicSerializableObject { } /** - * Updates all ejectors on the path, so that only the last ejector + * Recomputes the layer of the path and the target acceptor */ onPathChanged() { - this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector; - this.ejectorSlot = this.ejectorComp.slots[0]; + this.layer = this.entityPath[0].layer; + this.acceptorTarget = this.computeAcceptingEntityAndSlot(); + } - for (let i = 0; i < this.entityPath.length; ++i) { - const ejectorComp = this.entityPath[i].components.ItemEjector; - const isLast = i === this.entityPath.length - 1; - ejectorComp.enabled = isLast; + /** + * Called by the belt system when the surroundings changed + */ + onSurroundingsChanged() { + this.onPathChanged(); + } - // Clear all slots of non-end entities - if (!isLast) { - for (let k = 0; k < ejectorComp.slots.length; ++k) { - ejectorComp.slots[k].item = null; - ejectorComp.slots[k].progress = 0.0; + /** + * Finds the entity which accepts our items + * @return {{ entity: Entity, slot: number }} + */ + computeAcceptingEntityAndSlot() { + const lastEntity = this.entityPath[this.entityPath.length - 1]; + const lastStatic = lastEntity.components.StaticMapEntity; + const lastBeltComp = lastEntity.components.Belt; + + // Figure out where and into which direction we eject items + const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0)); + const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction); + const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection]; + const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector); + + // Try to find the given acceptor component to take the item + // Since there can be cross layer dependencies, check on all layers + const targetEntities = this.root.map.getLayersContentsMultipleXY( + ejectSlotTargetWsTile.x, + ejectSlotTargetWsTile.y + ); + + for (let i = 0; i < targetEntities.length; ++i) { + const targetEntity = targetEntities[i]; + + const targetStaticComp = targetEntity.components.StaticMapEntity; + const targetBeltComp = targetEntity.components.Belt; + + // Check for belts (special case) + if (targetBeltComp) { + const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top); + if (ejectSlotWsDirection === beltAcceptingDirection) { + return { + entity: targetEntity, + slot: 0, + }; } } - } - this.layer = this.entityPath[0].layer; + // Check for item acceptors + const targetAcceptorComp = targetEntity.components.ItemAcceptor; + if (!targetAcceptorComp) { + // Entity doesn't accept items + continue; + } + + const matchingSlot = targetAcceptorComp.findMatchingSlot( + targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile), + targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection), + lastEntity.layer + ); + + if (!matchingSlot) { + // No matching slot found + continue; + } + + return { + entity: targetEntity, + slot: matchingSlot.index, + }; + } } // Following code will be compiled out outside of dev versions @@ -251,11 +298,6 @@ export class BeltPath extends BasicSerializableObject { return fail("Reference to destroyed entity " + entity.uid); } - const enabledState = i === this.entityPath.length - 1; - if (entity.components.ItemEjector.enabled !== enabledState) { - return fail("Item ejector enabled state is not synchronized (index =" + i + ")"); - } - const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity); if (!followUp) { return fail( @@ -283,20 +325,6 @@ export class BeltPath extends BasicSerializableObject { } } - // Check for right ejector component and slot - if (this.ejectorComp !== this.entityPath[this.entityPath.length - 1].components.ItemEjector) { - return fail("Stale ejectorComp handle"); - } - if (this.ejectorSlot !== this.ejectorComp.slots[0]) { - return fail("Stale ejector slot handle"); - } - if (!this.ejectorComp) { - return fail("Ejector comp not set"); - } - if (!this.ejectorSlot) { - return fail("Ejector slot not set"); - } - // Check spacing if (this.spacingToFirstItem > this.totalLength + 0.005) { return fail( @@ -370,14 +398,6 @@ export class BeltPath extends BasicSerializableObject { const beltComp = entity.components.Belt; - // If the last belt has something on its ejector, put that into the path first - const pendingItem = this.ejectorComp.takeSlotItem(0); - if (pendingItem) { - // Ok, so we have a pending item - DEBUG && logger.log("Taking pending item and putting it back on the path"); - this.items.push([0, pendingItem]); - } - // Append the entity this.entityPath.push(entity); this.onPathChanged(); @@ -970,7 +990,7 @@ export class BeltPath extends BasicSerializableObject { beltSpeed *= 100; } - let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0; + let minimumDistance = 0; // Try to reduce spacing let remainingAmount = beltSpeed; @@ -994,10 +1014,17 @@ export class BeltPath extends BasicSerializableObject { minimumDistance = this.getItemSpacing(); } + // Check if we have an item which is ready to be emitted const lastItem = this.items[this.items.length - 1]; - if (lastItem && lastItem[_nextDistance] === 0) { - // Take over - if (this.ejectorComp.tryEject(0, lastItem[_item])) { + if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) { + // Pass over the item + if ( + this.root.systemMgr.systems.itemEjector.tryPassOverItem( + lastItem[_item], + this.acceptorTarget.entity, + this.acceptorTarget.slot + ) + ) { this.items.pop(); } } @@ -1134,7 +1161,7 @@ export class BeltPath extends BasicSerializableObject { const beltLength = beltComp.getEffectiveLengthTiles(this.layer); // Check if the current items are on the belt - while (trackPos + beltLength >= currentItemPos) { + while (trackPos + beltLength >= currentItemPos - 1e-51) { // Its on the belt, render it now const staticComp = entity.components.StaticMapEntity; assert( diff --git a/src/js/game/building_codes.js b/src/js/game/building_codes.js index ea331272..370660bc 100644 --- a/src/js/game/building_codes.js +++ b/src/js/game/building_codes.js @@ -1,5 +1,7 @@ -import { MetaBuilding, defaultBuildingVariant } from "./meta_building"; +/* typehints:start */ +import { MetaBuilding } from "./meta_building"; import { AtlasSprite } from "../core/sprites"; +/* typehints:end */ /** * @typedef {{ @@ -22,13 +24,18 @@ export const gBuildingVariants = { }; /** - * - * @param {*} id - * @param {*} meta - * @param {*} variant - * @param {*} rotationVariant + * Registers a new variant + * @param {number} id + * @param {typeof MetaBuilding} meta + * @param {string} variant + * @param {number} rotationVariant */ -export function registerBuildingVariant(id, meta, variant = defaultBuildingVariant, rotationVariant = 0) { +export function registerBuildingVariant( + id, + meta, + variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */, + rotationVariant = 0 +) { assert(!gBuildingVariants[id], "Duplicate id: " + id); gBuildingVariants[id] = { metaClass: meta, diff --git a/src/js/game/buildings/belt_base.js b/src/js/game/buildings/belt_base.js index a87589a3..796b27b5 100644 --- a/src/js/game/buildings/belt_base.js +++ b/src/js/game/buildings/belt_base.js @@ -53,34 +53,8 @@ export class MetaBeltBaseBuilding extends MetaBuilding { direction: enumDirection.top, // updated later }) ); - // Make this entity replaceabel + // Make this entity replaceable entity.addComponent(new ReplaceableMapEntityComponent()); - - entity.addComponent( - new ItemAcceptorComponent({ - slots: [ - { - pos: new Vector(0, 0), - directions: [enumDirection.bottom], - layer: this.getLayer(), - }, - ], - animated: false, - }) - ); - - entity.addComponent( - new ItemEjectorComponent({ - slots: [ - { - pos: new Vector(0, 0), - direction: enumDirection.top, // updated later - layer: this.getLayer(), - }, - ], - instantEject: true, - }) - ); } /** @@ -90,7 +64,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding { */ updateVariants(entity, rotationVariant) { entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant]; - entity.components.ItemEjector.slots[0].direction = arrayBeltVariantToRotation[rotationVariant]; } /** diff --git a/src/js/game/components/belt.js b/src/js/game/components/belt.js index e9a0cd80..f2e990f0 100644 --- a/src/js/game/components/belt.js +++ b/src/js/game/components/belt.js @@ -7,6 +7,40 @@ import { enumLayer } from "../root"; export const curvedBeltLength = /* Math.PI / 4 */ 0.78; +/** @type {import("./item_acceptor").ItemAcceptorSlot} */ +export const FAKE_BELT_ACCEPTOR_SLOT = { + pos: new Vector(0, 0), + directions: [enumDirection.bottom], + layer: enumLayer.regular, +}; + +/** @type {Object} */ +export const FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION = { + [enumDirection.top]: { + pos: new Vector(0, 0), + direction: enumDirection.top, + item: null, + layer: enumLayer.regular, + progress: 0, + }, + + [enumDirection.right]: { + pos: new Vector(0, 0), + direction: enumDirection.right, + item: null, + layer: enumLayer.regular, + progress: 0, + }, + + [enumDirection.left]: { + pos: new Vector(0, 0), + direction: enumDirection.left, + item: null, + layer: enumLayer.regular, + progress: 0, + }, +}; + export class BeltComponent extends Component { static getId() { return "Belt"; @@ -56,6 +90,27 @@ export class BeltComponent extends Component { return this.direction === enumDirection.top ? 1.0 : curvedBeltLength; } + /** + * Returns fake acceptor slot used for matching + * @returns {import("./item_acceptor").ItemAcceptorSlot} + */ + getFakeAcceptorSlot() { + return FAKE_BELT_ACCEPTOR_SLOT; + } + + /** + * Returns fake acceptor slot used for matching + * @returns {import("./item_ejector").ItemEjectorSlot} + */ + getFakeEjectorSlots() { + assert( + FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction], + "Invalid belt direction: ", + this.direction + ); + return FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction]; + } + /** * Converts from belt space (0 = start of belt ... 1 = end of belt) to the local * belt coordinates (-0.5|-0.5 to 0.5|0.5) diff --git a/src/js/game/components/item_ejector.js b/src/js/game/components/item_ejector.js index ee661078..d6c79eb1 100644 --- a/src/js/game/components/item_ejector.js +++ b/src/js/game/components/item_ejector.js @@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization"; import { gItemRegistry } from "../../core/global_registries"; import { Entity } from "../entity"; import { enumLayer } from "../root"; +import { BeltPath } from "../belt_path"; /** * @typedef {{ @@ -14,6 +15,7 @@ import { enumLayer } from "../root"; * layer: enumLayer, * progress: number?, * cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot, + * cachedBeltPath?: BeltPath, * cachedTargetEntity?: Entity * }} ItemEjectorSlot */ @@ -24,7 +26,7 @@ export class ItemEjectorComponent extends Component { } static getSchema() { - // The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields + // The cachedDestSlot, cachedTargetEntity fields // are not serialized. return { instantEject: types.bool, @@ -73,9 +75,6 @@ export class ItemEjectorComponent extends Component { this.setSlots(slots); - /** @type {ItemEjectorSlot[]} */ - this.cachedConnectedSlots = null; - /** * Whether this ejector slot is enabled */ diff --git a/src/js/game/logic.js b/src/js/game/logic.js index 403fa15b..f703691e 100644 --- a/src/js/game/logic.js +++ b/src/js/game/logic.js @@ -198,50 +198,66 @@ export class GameLogic { for (let i = 0; i < entities.length; ++i) { const entity = entities[i]; + let ejectorSlots = []; + let acceptorSlots = []; + const staticComp = entity.components.StaticMapEntity; const itemEjector = entity.components.ItemEjector; + const itemAcceptor = entity.components.ItemAcceptor; + const beltComp = entity.components.Belt; + if (itemEjector) { - for (let ejectorSlot = 0; ejectorSlot < itemEjector.slots.length; ++ejectorSlot) { - const slot = itemEjector.slots[ejectorSlot]; - if (slot.layer !== layer) { - continue; - } - const wsTile = staticComp.localTileToWorld(slot.pos); - const wsDirection = staticComp.localDirectionToWorld(slot.direction); - const targetTile = wsTile.add(enumDirectionToVector[wsDirection]); - if (targetTile.equals(tile)) { - ejectors.push({ - entity, - slot, - fromTile: wsTile, - toDirection: wsDirection, - }); - } + ejectorSlots = itemEjector.slots.slice(); + } + + if (itemAcceptor) { + acceptorSlots = itemAcceptor.slots.slice(); + } + + if (beltComp) { + const fakeEjectorSlot = beltComp.getFakeEjectorSlots(); + const fakeAcceptorSlot = beltComp.getFakeAcceptorSlot(); + ejectorSlots.push(fakeEjectorSlot); + acceptorSlots.push(fakeAcceptorSlot); + } + + for (let ejectorSlot = 0; ejectorSlot < ejectorSlots.length; ++ejectorSlot) { + const slot = ejectorSlots[ejectorSlot]; + if (slot.layer !== layer) { + continue; + } + const wsTile = staticComp.localTileToWorld(slot.pos); + const wsDirection = staticComp.localDirectionToWorld(slot.direction); + const targetTile = wsTile.add(enumDirectionToVector[wsDirection]); + if (targetTile.equals(tile)) { + ejectors.push({ + entity, + slot, + fromTile: wsTile, + toDirection: wsDirection, + }); } } - const itemAcceptor = entity.components.ItemAcceptor; - if (itemAcceptor) { - for (let acceptorSlot = 0; acceptorSlot < itemAcceptor.slots.length; ++acceptorSlot) { - const slot = itemAcceptor.slots[acceptorSlot]; - if (slot.layer !== layer) { - continue; - } + for (let acceptorSlot = 0; acceptorSlot < acceptorSlots.length; ++acceptorSlot) { + const slot = acceptorSlots[acceptorSlot]; + if (slot.layer !== layer) { + continue; + } - const wsTile = staticComp.localTileToWorld(slot.pos); - for (let k = 0; k < slot.directions.length; ++k) { - const direction = slot.directions[k]; - const wsDirection = staticComp.localDirectionToWorld(direction); + const wsTile = staticComp.localTileToWorld(slot.pos); + for (let k = 0; k < slot.directions.length; ++k) { + const direction = slot.directions[k]; + const wsDirection = staticComp.localDirectionToWorld(direction); - const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]); - if (sourceTile.equals(tile)) { - acceptors.push({ - entity, - slot, - toTile: wsTile, - fromDirection: wsDirection, - }); - } + const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]); + if (sourceTile.equals(tile)) { + acceptors.push({ + entity, + slot, + toTile: wsTile, + fromDirection: wsDirection, + }); } } } diff --git a/src/js/game/systems/belt.js b/src/js/game/systems/belt.js index db538a56..08832e16 100644 --- a/src/js/game/systems/belt.js +++ b/src/js/game/systems/belt.js @@ -137,6 +137,9 @@ export class BeltSystem extends GameSystemWithFilter { const originalRect = staticComp.getTileSpaceBounds(); const affectedArea = originalRect.expandedInAllDirections(1); + /** @type {Set} */ + const changedPaths = new Set(); + for (let x = affectedArea.x; x < affectedArea.right(); ++x) { for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) { if (originalRect.containsPoint(x, y)) { @@ -189,10 +192,17 @@ export class BeltSystem extends GameSystemWithFilter { // Make sure the chunks know about the update this.root.signals.entityChanged.dispatch(targetEntity); } + + if (targetBeltComp.assignedPath) { + changedPaths.add(targetBeltComp.assignedPath); + } } } } + // notify all paths *afterwards* to avoid multi-updates + changedPaths.forEach(path => path.onSurroundingsChanged()); + if (G_IS_DEV && globalConfig.debug.checkBeltPaths) { this.debug_verifyBeltPaths(); } @@ -361,24 +371,10 @@ export class BeltSystem extends GameSystemWithFilter { const followUpBeltComp = followUpEntity.components.Belt; if (followUpBeltComp) { const followUpStatic = followUpEntity.components.StaticMapEntity; - const followUpAcceptor = followUpEntity.components.ItemAcceptor; - // Check if the belt accepts items from our direction - const acceptorSlots = followUpAcceptor.slots; - for (let i = 0; i < acceptorSlots.length; ++i) { - const slot = acceptorSlots[i]; - - // Make sure the acceptor slot is on the same layer - if (slot.layer !== entity.layer) { - continue; - } - - for (let k = 0; k < slot.directions.length; ++k) { - const localDirection = followUpStatic.localDirectionToWorld(slot.directions[k]); - if (enumInvertedDirections[localDirection] === followUpDirection) { - return followUpEntity; - } - } + const acceptedDirection = followUpStatic.localDirectionToWorld(enumDirection.top); + if (acceptedDirection === followUpDirection) { + return followUpEntity; } } } @@ -405,21 +401,12 @@ export class BeltSystem extends GameSystemWithFilter { const supplyBeltComp = supplyEntity.components.Belt; if (supplyBeltComp) { const supplyStatic = supplyEntity.components.StaticMapEntity; - const supplyEjector = supplyEntity.components.ItemEjector; + const otherDirection = supplyStatic.localDirectionToWorld( + enumInvertedDirections[supplyBeltComp.direction] + ); - // Check if the belt accepts items from our direction - const ejectorSlots = supplyEjector.slots; - for (let i = 0; i < ejectorSlots.length; ++i) { - const slot = ejectorSlots[i]; - - // Make sure the ejector slot is on the same layer - if (slot.layer !== entity.layer) { - continue; - } - const localDirection = supplyStatic.localDirectionToWorld(slot.direction); - if (enumInvertedDirections[localDirection] === supplyDirection) { - return supplyEntity; - } + if (otherDirection === supplyDirection) { + return supplyEntity; } } } diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 316dc053..c0c53f04 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -2,8 +2,8 @@ import { globalConfig } from "../../core/config"; import { DrawParameters } from "../../core/draw_parameters"; import { createLogger } from "../../core/logging"; import { Rectangle } from "../../core/rectangle"; -import { enumDirectionToVector, Vector } from "../../core/vector"; -import { BaseItem, enumItemType, enumItemTypeToLayer } from "../base_item"; +import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector"; +import { BaseItem, enumItemTypeToLayer } from "../base_item"; import { ItemEjectorComponent } from "../components/item_ejector"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; @@ -120,15 +120,13 @@ export class ItemEjectorSystem extends GameSystemWithFilter { const ejectorComp = entity.components.ItemEjector; const staticComp = entity.components.StaticMapEntity; - // Clear the old cache. - ejectorComp.cachedConnectedSlots = null; - - for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorComp.slots.length; ++ejectorSlotIndex) { - const ejectorSlot = ejectorComp.slots[ejectorSlotIndex]; + for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) { + const ejectorSlot = ejectorComp.slots[slotIndex]; // Clear the old cache. ejectorSlot.cachedDestSlot = null; ejectorSlot.cachedTargetEntity = null; + ejectorSlot.cachedBeltPath = null; // Figure out where and into which direction we eject items const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos); @@ -146,8 +144,21 @@ export class ItemEjectorSystem extends GameSystemWithFilter { for (let i = 0; i < targetEntities.length; ++i) { const targetEntity = targetEntities[i]; - const targetAcceptorComp = targetEntity.components.ItemAcceptor; const targetStaticComp = targetEntity.components.StaticMapEntity; + const targetBeltComp = targetEntity.components.Belt; + + // Check for belts (special case) + if (targetBeltComp) { + const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top); + if (ejectSlotWsDirection === beltAcceptingDirection) { + ejectorSlot.cachedTargetEntity = targetEntity; + ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath; + break; + } + } + + // Check for item acceptors + const targetAcceptorComp = targetEntity.components.ItemAcceptor; if (!targetAcceptorComp) { // Entity doesn't accept items continue; @@ -164,13 +175,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - // Ok we found a connection - if (ejectorComp.cachedConnectedSlots) { - ejectorComp.cachedConnectedSlots.push(ejectorSlot); - } else { - ejectorComp.cachedConnectedSlots = [ejectorSlot]; - } - // A slot can always be connected to one other slot only ejectorSlot.cachedTargetEntity = targetEntity; ejectorSlot.cachedDestSlot = matchingSlot; @@ -199,11 +203,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - if (!sourceEjectorComp.cachedConnectedSlots) { - continue; - } - - const slots = sourceEjectorComp.cachedConnectedSlots; + const slots = sourceEjectorComp.slots; for (let j = 0; j < slots.length; ++j) { const sourceSlot = slots[j]; const item = sourceSlot.item; @@ -212,7 +212,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - const destSlot = sourceSlot.cachedDestSlot; const targetEntity = sourceSlot.cachedTargetEntity; // Advance items on the slot @@ -229,18 +228,34 @@ export class ItemEjectorSystem extends GameSystemWithFilter { continue; } - // Check if the target acceptor can actually accept this item - const targetAcceptorComp = targetEntity.components.ItemAcceptor; - if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) { + // Check if we are ejecting to a belt path + const destPath = sourceSlot.cachedBeltPath; + if (destPath) { + // Try passing the item over + if (destPath.tryAcceptItem(item)) { + sourceSlot.item = null; + } + + // Always stop here, since there can *either* be a belt path *or* + // a slot continue; } - // Try to hand over the item - if (this.tryPassOverItem(item, targetEntity, destSlot.index)) { - // Handover successful, clear slot - targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item); - sourceSlot.item = null; - continue; + // Check if the target acceptor can actually accept this item + const destSlot = sourceSlot.cachedDestSlot; + if (destSlot) { + const targetAcceptorComp = targetEntity.components.ItemAcceptor; + if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) { + continue; + } + + // Try to hand over the item + if (this.tryPassOverItem(item, targetEntity, destSlot.index)) { + // Handover successful, clear slot + targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item); + sourceSlot.item = null; + continue; + } } } }