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() {
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 {
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
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.ItemAttributes
import net.minecraft.entity.ItemEntity
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
@ -38,6 +40,8 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) {
is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(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.ItemStackCollections
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.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState
@ -37,12 +38,14 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks
val INSERTION_TIMEOUT = 40
}
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = TerminalBufferInventory(18)
private val pendingRequests = LinkedList<StackLocateRequest>()
private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
@ -59,6 +62,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(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)
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() {
@ -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() {
observers++
}
@ -126,6 +160,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
println(cachedNetItems)
} else {
updateNetItems()
beginInsertions()
sync()
}
}
@ -138,7 +173,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress))
val factory = object: ExtendedScreenHandlerFactory {
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
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) {
if (inv == internalBuffer && world != null && !world!!.isClient) {
markDirty()
@ -234,3 +287,13 @@ data class StackLocateRequest(
val totalResultAmount: Int
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
}
// todo: modes should be saved to NBT
private val modes = Array(size) { Mode.UNASSIGNED }
fun insertFromNetwork(stack: ItemStack): ItemStack {
@ -56,4 +57,12 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
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
} 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)
}

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) {
}