diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt index 59fe56f..7d83833 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt @@ -1,26 +1,31 @@ package net.shadowfacts.phycon.network.block.extractor import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.item.GroupedItemInv +import alexiil.mc.lib.attributes.Simulation +import alexiil.mc.lib.attributes.item.FixedItemInv import alexiil.mc.lib.attributes.item.ItemAttributes +import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter import net.minecraft.block.BlockState +import net.minecraft.item.ItemStack import net.minecraft.nbt.CompoundTag import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet -import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.component.ActivationController +import net.shadowfacts.phycon.network.component.NetworkStackDispatcher +import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.packet.CapacityPacket -import net.shadowfacts.phycon.network.packet.CheckCapacityPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket import net.shadowfacts.phycon.network.packet.RemoteActivationPacket import net.shadowfacts.phycon.util.ActivationMode +import kotlin.properties.Delegates /** * @author shadowfacts */ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), + NetworkStackDispatcher, ActivationController.ActivatableDevice { companion object { @@ -30,17 +35,18 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), private val facing: Direction get() = cachedState[ExtractorBlock.FACING] - private var inventory: GroupedItemInv? = null - private var shouldExtract = false + private var inventory: FixedItemInv? = null + override val pendingInsertions = mutableListOf() + override val dispatchStackTimeout = 40L override val controller = ActivationController(SLEEP_TIME, this) fun updateInventory() { val offsetPos = pos.offset(facing) val option = SearchOptions.inDirection(facing) - inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option) + inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option) } - private fun getInventory(): GroupedItemInv? { + private fun getInventory(): FixedItemInv? { if (inventory == null) updateInventory() return inventory } @@ -48,18 +54,27 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), override fun handle(packet: Packet) { when (packet) { is CapacityPacket -> handleCapacity(packet) + is ItemStackPacket -> handleItemStack(packet) is RemoteActivationPacket -> controller.handleRemoteActivation(packet) } } - private fun handleCapacity(packet: CapacityPacket) { - if (shouldExtract && packet.capacity > 0) { - getInventory()?.also { inv -> - shouldExtract = false - val extracted = inv.extract(packet.stack, packet.capacity) - sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress)) - } - } + override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { + // we can't insert things back into the inventory, so just let them spawn + return packet.stack + } + + override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter) + + override fun finishInsertion(insertion: PendingInsertion): ItemStack { + val inventory = getInventory() ?: return insertion.stack + // if the inventory has changed, the old slot index is meaningless + if (inventory !== insertion.inventory) return insertion.stack + val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION) + if (extracted.isEmpty) return insertion.stack + // if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have + insertion.stack = extracted + return super.finishInsertion(insertion) } override fun tick() { @@ -72,10 +87,16 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), override fun activate(): Boolean { val inventory = getInventory() ?: return false - val stack = inventory.storedStacks.firstOrNull() ?: return false - shouldExtract = true - sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) - return true + for (slot in 0 until inventory.slotCount) { + val slotStack = inventory.getInvStack(slot) + if (slotStack.isEmpty) continue + dispatchItemStack(slotStack) { insertion -> + insertion.inventory = inventory + insertion.inventorySlot = slot + } + return true + } + return false } override fun toTag(tag: CompoundTag): CompoundTag { @@ -97,4 +118,9 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), super.fromClientTag(tag) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) } + + class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion(stack, timestamp) { + lateinit var inventory: FixedItemInv + var inventorySlot by Delegates.notNull() + } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt index 1b6d7b8..6d2f3f6 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt @@ -33,7 +33,8 @@ interface NetworkStackDispatcher>( - val stack: ItemStack, + var stack: ItemStack, val timestamp: Long ) { val results = mutableSetOf>() @@ -77,5 +79,6 @@ fun > Self.f val finishable = pendingInsertions.filter { it.isFinishable(this) } // finishInsertion removes the object from pendingInsertions finishable.forEach(::finishInsertion) + // todo: do something with remaining? // todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?) }