Terminal: Add basic insertion into inventories

This commit is contained in:
Shadowfacts 2021-02-10 22:40:01 -05:00
parent b1c37595ab
commit dedfcae79b
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
8 changed files with 133 additions and 3 deletions

View File

@ -15,6 +15,6 @@ object PhysicalConnectivityClient: ClientModInitializer {
override fun onInitializeClient() { override fun onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent()) BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent())
ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen) ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
} }
} }

View File

@ -7,6 +7,6 @@ import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
object PhyScreens { object PhyScreens {
val TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler) val TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler)
} }

View File

@ -1,8 +1,10 @@
package net.shadowfacts.phycon.network.block.netinterface package net.shadowfacts.phycon.network.block.netinterface
import alexiil.mc.lib.attributes.SearchOptions import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.entity.ItemEntity
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
@ -38,6 +40,8 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) {
is RequestInventoryPacket -> handleRequestInventory(packet) is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet) is LocateStackPacket -> handleLocateStack(packet)
is ExtractStackPacket -> handleExtractStack(packet) is ExtractStackPacket -> handleExtractStack(packet)
is CheckCapacityPacket -> handleCheckCapacity(packet)
is ItemStackPacket -> handleItemStack(packet)
} }
} }
@ -61,4 +65,30 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) {
} }
} }
private fun handleCheckCapacity(packet: CheckCapacityPacket) {
getInventory()?.also { inv ->
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
val couldAccept = packet.stack.count - remaining.count
sendToSingle(CapacityPacket(packet.stack, couldAccept, this, macAddress, packet.source))
}
}
private fun handleItemStack(packet: ItemStackPacket) {
val inventory = getInventory()
if (inventory != null) {
val remaining = inventory.insert(packet.stack)
if (!remaining.isEmpty) {
// todo: should this send whatever was left back to the terminal instead of dropping it?
// todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), remaining)
world!!.spawnEntity(entity)
}
} else {
// todo: should the stack back to the terminal instead of dropping it?
// 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)
}
}
} }

View File

@ -3,6 +3,7 @@ package net.shadowfacts.phycon.network.block.terminal
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemStackCollections import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil import alexiil.mc.lib.attributes.item.ItemStackUtil
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
@ -37,12 +38,14 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
companion object { companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks val LOCATE_REQUEST_TIMEOUT = 40 // ticks
val INSERTION_TIMEOUT = 40
} }
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>() private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = TerminalBufferInventory(18) val internalBuffer = TerminalBufferInventory(18)
private val pendingRequests = LinkedList<StackLocateRequest>() private val pendingRequests = LinkedList<StackLocateRequest>()
private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
private var observers = 0 private var observers = 0
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = ItemStackCollections.intMap()
@ -59,6 +62,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
is DeviceRemovedPacket -> handleDeviceRemoved(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(packet) is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet) is ItemStackPacket -> handleItemStack(packet)
is CapacityPacket -> handleCapacity(packet)
} }
} }
@ -94,6 +98,24 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), remaining) val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), remaining)
world!!.spawnEntity(entity) world!!.spawnEntity(entity)
} }
// this happens outside the normal update loop because by receiving the item stack packet
// we "know" how much the count in the source inventory has changed
updateNetItems()
sync()
}
private fun handleCapacity(packet: CapacityPacket) {
val insertion = pendingInsertions.values.firstOrNull {
ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack)
}
if (insertion != null) {
insertion.results.add(packet.capacity to packet.receivingInterface)
if (insertion.totalCapacity >= insertion.stack.count || counter - insertion.timestamp >= INSERTION_TIMEOUT || insertion.results.size >= inventoryCache.size) {
pendingInsertions.remove(insertion.bufferSlot)
completeInsertion(insertion)
}
}
} }
private fun updateNetItems() { private fun updateNetItems() {
@ -112,6 +134,18 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
} }
private fun beginInsertions() {
if (world!!.isClient) return
for (slot in 0 until internalBuffer.size()) {
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
if (slot in pendingInsertions) continue
val stack = internalBuffer.getStack(slot)
pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter)
sendToSingle(CheckCapacityPacket(stack, macAddress, MACAddress.BROADCAST))
}
}
fun addObserver() { fun addObserver() {
observers++ observers++
} }
@ -126,6 +160,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
println(cachedNetItems) println(cachedNetItems)
} else { } else {
updateNetItems() updateNetItems()
beginInsertions()
sync() sync()
} }
} }
@ -138,7 +173,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
inventoryCache.clear() inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress)) sendToSingle(RequestInventoryPacket(macAddress))
val factory = object: ExtendedScreenHandlerFactory { val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity) return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity)
} }
@ -171,6 +206,24 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
} }
private fun completeInsertion(insertion: PendingStackInsertion) {
// todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
val remaining = insertion.stack
while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeAt(0)
if (capacity <= 0) continue
sendToSingle(ItemStackPacket(remaining.copy(), macAddress, receivingInterface.macAddress))
// todo: the interface should confirm how much was actually inserted, in case of race condition
remaining.count -= capacity
}
internalBuffer.setStack(insertion.bufferSlot, remaining)
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
updateNetItems()
sync()
}
override fun onInventoryChanged(inv: Inventory) { override fun onInventoryChanged(inv: Inventory) {
if (inv == internalBuffer && world != null && !world!!.isClient) { if (inv == internalBuffer && world != null && !world!!.isClient) {
markDirty() markDirty()
@ -234,3 +287,13 @@ data class StackLocateRequest(
val totalResultAmount: Int val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
} }
data class PendingStackInsertion(
val bufferSlot: Int,
val stack: ItemStack,
val timestamp: Int,
val results: MutableSet<Pair<Int, InterfaceBlockEntity>> = mutableSetOf(),
) {
val totalCapacity: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
}

View File

@ -14,6 +14,7 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
TO_NETWORK, FROM_NETWORK, UNASSIGNED TO_NETWORK, FROM_NETWORK, UNASSIGNED
} }
// todo: modes should be saved to NBT
private val modes = Array(size) { Mode.UNASSIGNED } private val modes = Array(size) { Mode.UNASSIGNED }
fun insertFromNetwork(stack: ItemStack): ItemStack { fun insertFromNetwork(stack: ItemStack): ItemStack {
@ -56,4 +57,12 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
super.setStack(slot, stack) super.setStack(slot, stack)
} }
fun getMode(slot: Int): Mode {
return modes[slot]
}
fun markSlot(slot: Int, mode: Mode) {
modes[slot] = mode
}
} }

View File

@ -83,6 +83,13 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
} }
} }
return ItemStack.EMPTY return ItemStack.EMPTY
} else if (slotId in 54 until 72) {
// internal buffer
if (actionType == SlotActionType.PICKUP && !player.inventory.cursorStack.isEmpty) {
// placing cursor stack into buffer
val bufferSlot = slotId - 54 // subtract 54 to convert the handler slot ID to a valid buffer index
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
}
} }
return super.onSlotClick(slotId, clickData, actionType, player) return super.onSlotClick(slotId, clickData, actionType, player)
} }

View File

@ -0,0 +1,11 @@
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
*/
class CapacityPacket(val stack: ItemStack, val capacity: Int, val receivingInterface: InterfaceBlockEntity, source: MACAddress, destination: MACAddress): BasePacket(source, destination) {
}

View File

@ -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 CheckCapacityPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress): BasePacket(source, destination) {
}