diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt index c5152c8..3e5ff16 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt @@ -7,8 +7,7 @@ import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity -import net.shadowfacts.phycon.network.packet.ReadInventoryPacket -import net.shadowfacts.phycon.network.packet.RequestInventoryPacket +import net.shadowfacts.phycon.network.packet.* /** * @author shadowfacts @@ -27,18 +26,38 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) { inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option) } + private fun getInventory(): GroupedItemInv? { + // if we don't have an inventory, try to get one + // this happens when readAll is called before a neighbor state changes, such as immediately after world load + if (inventory == null) updateInventory() + return inventory + } + override fun handlePacket(packet: Packet) { when (packet) { is RequestInventoryPacket -> handleRequestInventory(packet) + is LocateStackPacket -> handleLocateStack(packet) + is ExtractStackPacket -> handleExtractStack(packet) } } private fun handleRequestInventory(packet: RequestInventoryPacket) { - // if we don't have an inventory, try to get one - // this happens when readAll is called before a neighbor state changes, such as immediately after world load - if (inventory == null) updateInventory() - inventory?.also { - enqueueToSingle(ReadInventoryPacket(it, macAddress, packet.source)) + getInventory()?.also { inv -> + enqueueToSingle(ReadInventoryPacket(inv, macAddress, packet.source)) + } + } + + private fun handleLocateStack(packet: LocateStackPacket) { + getInventory()?.also { inv -> + val amount = inv.getAmount(packet.stack) + enqueueToSingle(StackLocationPacket(packet.stack, amount, this, macAddress, packet.source)) + } + } + + private fun handleExtractStack(packet: ExtractStackPacket) { + getInventory()?.also { inv -> + val extracted = inv.extract(packet.stack, packet.amount) + enqueueToSingle(ItemStackPacket(extracted, macAddress, packet.source)) } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt index 9aeea2d..1a535f8 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt @@ -2,8 +2,10 @@ package net.shadowfacts.phycon.network.block.terminal import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.ItemStackCollections +import alexiil.mc.lib.attributes.item.ItemStackUtil import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.container.ContainerProviderRegistry +import net.minecraft.entity.ItemEntity import net.minecraft.entity.player.PlayerEntity import net.minecraft.inventory.BasicInventory import net.minecraft.inventory.Inventory @@ -15,20 +17,27 @@ import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity -import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket -import net.shadowfacts.phycon.network.packet.ReadInventoryPacket -import net.shadowfacts.phycon.network.packet.RequestInventoryPacket +import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity +import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.util.fromTag import net.shadowfacts.phycon.util.toTag +import java.util.* +import kotlin.math.min /** * @author shadowfacts */ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryListener, BlockEntityClientSerializable { + companion object { + val LOCATE_REQUEST_TIMEOUT = 40 // ticks + } + private val inventoryCache = mutableMapOf() val internalBuffer = BasicInventory(18) + private val pendingRequests = LinkedList() + private var observers = 0 val cachedNetItems = ItemStackCollections.intMap() var cachedSortedNetItems = listOf() @@ -42,15 +51,47 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento when (packet) { is ReadInventoryPacket -> handleReadInventory(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet) + is StackLocationPacket -> handleStackLocation(packet) + is ItemStackPacket -> handleItemStack(packet) } } private fun handleReadInventory(packet: ReadInventoryPacket) { inventoryCache[packet.source] = packet.inventory + updateNetItems() + sync() } private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { inventoryCache.remove(packet.source) + updateNetItems() + sync() + } + + private fun handleStackLocation(packet: StackLocationPacket) { + val request = pendingRequests.firstOrNull { + ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) + } + if (request != null) { + request.results.add(packet.amount to packet.sourceInterface) + if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) { + pendingRequests.remove(request) + stackLocateRequestCompleted(request) + } + } + } + + private fun handleItemStack(packet: ItemStackPacket) { + // todo: handle merging stacks into the buffer better? + for (i in 0 until internalBuffer.invSize) { + if (internalBuffer.getInvStack(i).isEmpty) { + internalBuffer.setInvStack(i, packet.stack) + return + } + } + // todo: calculate entity spawn point by finding non-obstructed location + val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), packet.stack) + world!!.spawnEntity(entity) } private fun updateNetItems() { @@ -61,6 +102,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento cachedNetItems.mergeInt(stack, amount) { a, b -> a + b } } } + // todo: is the map necessary or is just the sorted list enough? + cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { + val stack = it.key.copy() + stack.count = it.intValue + stack + } } fun addObserver() { @@ -75,7 +122,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento super.tick() if (observers > 0 && (++counter % 20) == 0) { - counter = 0 if (world!!.isClient) { println(cachedNetItems) } else { @@ -87,6 +133,9 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento fun onActivate(player: PlayerEntity) { if (!world!!.isClient) { + updateNetItems() + sync() + inventoryCache.clear() enqueueToSingle(RequestInventoryPacket(macAddress)) ContainerProviderRegistry.INSTANCE.openContainer(TerminalContainer.ID, player) { buf -> @@ -96,8 +145,25 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento addObserver() } + fun requestItem(stack: ItemStack, amount: Int = stack.count) { + pendingRequests.add(StackLocateRequest(stack, amount, counter)) + enqueueToSingle(LocateStackPacket(stack, macAddress)) + } + + private fun stackLocateRequestCompleted(request: StackLocateRequest) { + // todo: also sort results by interface priority + val sortedResults = request.results.sortedByDescending { it.first }.toMutableList() + var amountRequested = 0 + while (amountRequested < request.amount && sortedResults.isNotEmpty()) { + val (sourceAmount, sourceInterface) = sortedResults.removeAt(0) + val amountToRequest = min(sourceAmount, request.amount - amountRequested) + amountRequested += amountToRequest + enqueueToSingle(ExtractStackPacket(request.stack, amountToRequest, macAddress, sourceInterface.macAddress)) + } + } + override fun onInvChange(inv: Inventory) { - if (inv == internalBuffer) { + if (inv == internalBuffer && !world!!.isClient) { markDirty() sync() } @@ -126,7 +192,14 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento } override fun fromClientTag(tag: CompoundTag) { - internalBuffer.fromTag(tag.getList("InternalBuffer", 10)) + tag.getList("InternalBuffer", 10)?.also { list -> + if (list.isNotEmpty()) { + internalBuffer.fromTag(list) + } else { + // todo: should this clear or just do nothing? + internalBuffer.clear() + } + } val list = tag.getList("CachedNetItems", 10) cachedNetItems.clear() for (entryTag in list) { @@ -142,3 +215,13 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento } } + +data class StackLocateRequest( + val stack: ItemStack, + val amount: Int, + val timestamp: Int, + var results: MutableSet> = mutableSetOf() +) { + val totalResultAmount: Int + get() = results.fold(0) { acc, (amount, _) -> acc + amount } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalContainer.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalContainer.kt index 311059d..b29ce4d 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalContainer.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalContainer.kt @@ -2,10 +2,13 @@ package net.shadowfacts.phycon.network.block.terminal import net.minecraft.container.Container import net.minecraft.container.Slot +import net.minecraft.container.SlotActionType import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerInventory +import net.minecraft.item.ItemStack import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity +import kotlin.math.max /** * @author shadowfacts @@ -52,4 +55,19 @@ class TerminalContainer(syncId: Int, playerInv: PlayerInventory, val terminal: T terminal.removeObserver() } + + override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack { + if (actionType == SlotActionType.QUICK_MOVE) { + if (slotId < 54) { + // the slot clicked was one of the network stacks + val slot = slotList[slotId] + val stack = slotList[slotId].stack + if (!stack.isEmpty && !player.world.isClient) { + terminal.requestItem(stack, max(stack.count, stack.maxCount)) + } + return ItemStack.EMPTY + } + } + return super.onSlotClick(slotId, clickData, actionType, player) + } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/ExtractStackPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/ExtractStackPacket.kt new file mode 100644 index 0000000..96d678a --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/ExtractStackPacket.kt @@ -0,0 +1,10 @@ +package net.shadowfacts.phycon.network.packet + +import net.minecraft.item.ItemStack +import net.shadowfacts.phycon.api.util.MACAddress + +/** + * @author shadowfacts + */ +class ExtractStackPacket(val stack: ItemStack, val amount: Int, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/ItemStackPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/ItemStackPacket.kt new file mode 100644 index 0000000..bc41a70 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/ItemStackPacket.kt @@ -0,0 +1,10 @@ +package net.shadowfacts.phycon.network.packet + +import net.minecraft.item.ItemStack +import net.shadowfacts.phycon.api.util.MACAddress + +/** + * @author shadowfacts + */ +class ItemStackPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/LocateStackPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/LocateStackPacket.kt new file mode 100644 index 0000000..e6c77a7 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/LocateStackPacket.kt @@ -0,0 +1,10 @@ +package net.shadowfacts.phycon.network.packet + +import net.minecraft.item.ItemStack +import net.shadowfacts.phycon.api.util.MACAddress + +/** + * @author shadowfacts + */ +class LocateStackPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) { +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/StackLocationPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/StackLocationPacket.kt new file mode 100644 index 0000000..7aff100 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/StackLocationPacket.kt @@ -0,0 +1,18 @@ +package net.shadowfacts.phycon.network.packet + +import net.minecraft.item.ItemStack +import net.shadowfacts.phycon.api.util.MACAddress +import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity + +/** + * @author shadowfacts + */ +// todo: better name with LocateStackPacket +class StackLocationPacket( + val stack: ItemStack, + val amount: Int, + val sourceInterface: InterfaceBlockEntity, + source: MACAddress, + destination: MACAddress +): BasePacket(source, destination) { +}