diff --git a/src/js/game/belt_path.js b/src/js/game/belt_path.js index f172b1a3..bb59b19b 100644 --- a/src/js/game/belt_path.js +++ b/src/js/game/belt_path.js @@ -263,16 +263,7 @@ export class BeltPath extends BasicSerializableObject { const matchingSlotIndex = matchingSlot.index; return function (item, startProgress = 0.0) { - const storageComp = targetEntity.components.Storage; - if ( - storageComp && - storageComp.tryAcceptItem(item) && - targetAcceptorComp.tryAcceptItem(matchingSlotIndex, item, startProgress) - ) { - // unique duplicated code for storage - return true; - } - if (targetAcceptorComp.tryAcceptItem(matchingSlotIndex, item, startProgress)) { + if (targetAcceptorComp.tryAcceptItem(targetEntity, matchingSlotIndex, item, startProgress)) { return true; } return false; diff --git a/src/js/game/buildings/painter.js b/src/js/game/buildings/painter.js index 432973d0..da95f4db 100644 --- a/src/js/game/buildings/painter.js +++ b/src/js/game/buildings/painter.js @@ -1,7 +1,7 @@ import { formatItemsPerSecond } from "../../core/utils"; import { enumDirection, Vector } from "../../core/vector"; import { T } from "../../translations"; -import { ItemAcceptorComponent } from "../components/item_acceptor"; +import { enumInputRequirements, ItemAcceptorComponent } from "../components/item_acceptor"; import { ItemEjectorComponent } from "../components/item_ejector"; import { enumItemProcessorTypes, @@ -274,6 +274,7 @@ export class MetaPainterBuilding extends MetaBuilding { filter: "color", }, ]); + entity.components.ItemAcceptor.inputRequirement = enumInputRequirements.quadPainter; entity.components.ItemEjector.setSlots([ { pos: new Vector(0, 0), direction: enumDirection.top }, diff --git a/src/js/game/buildings/storage.js b/src/js/game/buildings/storage.js index 78f398be..92355557 100644 --- a/src/js/game/buildings/storage.js +++ b/src/js/game/buildings/storage.js @@ -1,7 +1,7 @@ import { formatBigNumber } from "../../core/utils"; import { enumDirection, Vector } from "../../core/vector"; import { T } from "../../translations"; -import { ItemAcceptorComponent } from "../components/item_acceptor"; +import { enumInputRequirements, ItemAcceptorComponent } from "../components/item_acceptor"; import { ItemEjectorComponent } from "../components/item_ejector"; import { StorageComponent } from "../components/storage"; import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; @@ -81,6 +81,7 @@ export class MetaStorageBuilding extends MetaBuilding { direction: enumDirection.bottom, }, ], + inputRequirement: enumInputRequirements.storage, }) ); diff --git a/src/js/game/components/item_acceptor.js b/src/js/game/components/item_acceptor.js index 987de239..bc74d9d0 100644 --- a/src/js/game/components/item_acceptor.js +++ b/src/js/game/components/item_acceptor.js @@ -3,6 +3,7 @@ import { types } from "../../savegame/serialization"; import { BaseItem } from "../base_item"; import { Component } from "../component"; import { Entity } from "../entity"; +import { isTruthyItem } from "../items/boolean_item"; import { typeItemSingleton } from "../item_resolver"; import { GameRoot } from "../root"; @@ -49,6 +50,12 @@ import { GameRoot } from "../root"; * }} InputCompletedArgs */ +/** @enum {string} */ +export const enumInputRequirements = { + quadPainter: "quadPainter", + storage: "storage", +}; + export class ItemAcceptorComponent extends Component { static getId() { return "ItemAcceptor"; @@ -78,18 +85,26 @@ export class ItemAcceptorComponent extends Component { * @param {object} param0 * @param {Array} param0.slots The slots from which we accept items * @param {number=} param0.maxSlotInputs The maximum amount of items one slot can accept before it is full + * @param {string|null=} param0.inputRequirement The requirement to accept items */ - constructor({ slots = [], maxSlotInputs = 2 }) { + constructor({ slots = [], maxSlotInputs = 2, inputRequirement = null }) { super(); + this.setSlots(slots); + + this.inputRequirement = inputRequirement; + + // setting this to 1 will cause throughput issues at very high speeds + this.maxSlotInputs = maxSlotInputs; + + this.clear(); + } + + clear() { /** @type {ItemAcceptorInputs} */ this.inputs = []; /** @type {ItemAcceptorCompletedInputs} */ this.completedInputs = []; - this.setSlots(slots); - - // setting this to 1 will cause throughput issues at very high speeds - this.maxSlotInputs = maxSlotInputs; } /** @@ -111,16 +126,74 @@ export class ItemAcceptorComponent extends Component { } } + /** + * + * @param {Entity} entity + * @param {BaseItem} item + * @param {number} slotIndex + * @returns + */ + canAcceptItem(entity, item, slotIndex) { + const slot = this.slots[slotIndex]; + + // make sure there is a slot and we match the filter + if (slot && !(slot.filter && slot.filter != item.getItemType())) { + switch (this.inputRequirement) { + case null: { + return true; + } + case enumInputRequirements.quadPainter: { + const pinsComp = entity.components.WiredPins; + + if (slotIndex === 0) { + // Always accept the shape + return true; + } + + // Check the network value at the given slot + const network = pinsComp.slots[slotIndex - 1].linkedNetwork; + const slotIsEnabled = network && network.hasValue() && isTruthyItem(network.currentValue); + if (!slotIsEnabled) { + return false; + } + return true; + } + case enumInputRequirements.storage: { + const storageComp = entity.components.Storage; + + if (storageComp.storedCount >= storageComp.maximumStorage) { + return false; + } + const itemType = item.getItemType(); + if (storageComp.storedItem && itemType !== storageComp.storedItem.getItemType()) { + return false; + } + + // set the item straight away - this way different kinds of items can't be inq the acceptor + storageComp.storedItem = item; + storageComp.storedCount++; + + return true; + } + default: { + assertAlways(false, "Input requirement is not recognised: " + slot.filter); + break; + } + } + } + return false; + } + /** * Called when trying to input a new item + * @param {Entity} entity * @param {number} slotIndex * @param {BaseItem} item * @param {number} startProgress World space remaining progress, can be set to set the start position of the item * @returns {boolean} if the input was succesful */ - tryAcceptItem(slotIndex, item, startProgress = 0.0) { - const slot = this.slots[slotIndex]; - + tryAcceptItem(entity, slotIndex, item, startProgress = 0.0) { + // make sure we have space to actually accept let existingInputs = 0; for (let i = 0; i < this.inputs.length; i++) { if (this.inputs[i].slotIndex == slotIndex) { @@ -136,8 +209,7 @@ export class ItemAcceptorComponent extends Component { if (existingInputs >= this.maxSlotInputs) { return false; } - - if (slot.filter && slot.filter != item.getItemType()) { + if (!this.canAcceptItem(entity, item, slotIndex)) { return false; } diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 0cc871c5..e7fe22e0 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -202,17 +202,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter { const destSlot = sourceSlot.cachedDestSlot; if (destEntity && destSlot) { const targetAcceptorComp = destEntity.components.ItemAcceptor; - const storageComp = destEntity.components.Storage; - if ( - storageComp && - storageComp.tryAcceptItem(item) && - targetAcceptorComp.tryAcceptItem(destSlot.index, item, extraProgress) - ) { - // unique duplicated code for storage - hacky :( - sourceSlot.item = null; - return; - } - if (targetAcceptorComp.tryAcceptItem(destSlot.index, item, extraProgress)) { + if (targetAcceptorComp.tryAcceptItem(destEntity, destSlot.index, item, extraProgress)) { // Handover successful, clear slot sourceSlot.item = null; } diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index efbdf516..774cfdcc 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -146,7 +146,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } } - // requirements are no longer needed as items will always be accepted, only the next method is. + // input requirements are now handled in the item acceptor, which also fits better with what the acceptor is supposed to do /** * Checks whether it's possible to process something @@ -160,21 +160,35 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // DEFAULT // By default, we can start processing once all inputs are there case null: { - return acceptorComp.completedInputs.length >= processorComp.inputsPerCharge; + // Since each slot might have more than one input, don't check each slot more than once + let usedSlots = []; + for (let i = 0; i < acceptorComp.completedInputs.length; i++) { + const index = acceptorComp.completedInputs[i].slotIndex; + if (!usedSlots.includes(index)) { + usedSlots.push(index); + } + } + return usedSlots.length >= processorComp.inputsPerCharge; } // QUAD PAINTER // For the quad painter, it might be possible to start processing earlier case enumItemProcessorRequirements.painterQuad: { const pinsComp = entity.components.WiredPins; + const inputs = acceptorComp.completedInputs; - const input = acceptorComp.completedInputs[0]; - if (!input) { - return false; + // split inputs efficiently + let items = new Map(); + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + + if (!items.get(input.slotIndex)) { + items.set(input.slotIndex, input.item); + } } // First slot is the shape, so if it's not there we can't do anything - const shapeItem = /** @type {ShapeItem} */ (input.item); + const shapeItem = /** @type {ShapeItem} */ (items.get(0)); if (!shapeItem) { return false; } @@ -203,10 +217,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // Check if all colors of the enabled slots are there for (let i = 0; i < slotStatus.length; ++i) { - if ( - slotStatus[i] && - !acceptorComp.completedInputs.find(input => input.slotIndex == i + 1) // @TODO this is slow - ) { + if (slotStatus[i] && !items.get(1 + i)) { // A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant, // it is not possible to paint, but if there is nothing we can ignore it for (let j = 0; j < 4; ++j) { @@ -217,7 +228,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } } } - return true; } @@ -243,8 +253,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; - items.set(input.slotIndex, input.item); - extraProgress = Math.max(extraProgress, input.extraProgress); + if (!items.get(input.slotIndex)) { + items.set(input.slotIndex, input.item); + extraProgress = Math.max(extraProgress, input.extraProgress); + inputs.splice(i, 1); + i--; + } } /** @type {Array} */ @@ -283,18 +297,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter { items: outItems, remainingTime: timeToProcess, }; - - // only remove one item from each slot - we don't want to delete extra items! - let usedSlots = []; - for (let i = 0; i < acceptorComp.completedInputs.length; i++) { - const index = acceptorComp.completedInputs[i].slotIndex; - - if (!usedSlots.includes(index)) { - usedSlots.push(index); - acceptorComp.completedInputs.splice(i, 1); - i--; - } - } } /**