Terminal: Rudimentary stack extraction

This commit is contained in:
Shadowfacts 2019-10-29 21:58:25 -04:00
parent 5d62e60bb2
commit f4f4c7ff03
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
7 changed files with 181 additions and 13 deletions

View File

@ -7,8 +7,7 @@ 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
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.ReadInventoryPacket import net.shadowfacts.phycon.network.packet.*
import net.shadowfacts.phycon.network.packet.RequestInventoryPacket
/** /**
* @author shadowfacts * @author shadowfacts
@ -27,18 +26,38 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) {
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option) 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) { override fun handlePacket(packet: Packet) {
when (packet) { when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet) is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet)
is ExtractStackPacket -> handleExtractStack(packet)
} }
} }
private fun handleRequestInventory(packet: RequestInventoryPacket) { private fun handleRequestInventory(packet: RequestInventoryPacket) {
// if we don't have an inventory, try to get one getInventory()?.also { inv ->
// this happens when readAll is called before a neighbor state changes, such as immediately after world load enqueueToSingle(ReadInventoryPacket(inv, macAddress, packet.source))
if (inventory == null) updateInventory() }
inventory?.also { }
enqueueToSingle(ReadInventoryPacket(it, 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))
} }
} }

View File

@ -2,8 +2,10 @@ 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 net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.fabricmc.fabric.api.container.ContainerProviderRegistry import net.fabricmc.fabric.api.container.ContainerProviderRegistry
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.BasicInventory import net.minecraft.inventory.BasicInventory
import net.minecraft.inventory.Inventory 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.api.util.MACAddress
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.packet.ReadInventoryPacket import net.shadowfacts.phycon.network.packet.*
import net.shadowfacts.phycon.network.packet.RequestInventoryPacket
import net.shadowfacts.phycon.util.fromTag import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.toTag import net.shadowfacts.phycon.util.toTag
import java.util.*
import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryListener, BlockEntityClientSerializable { class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryListener, BlockEntityClientSerializable {
companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks
}
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>() private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = BasicInventory(18) val internalBuffer = BasicInventory(18)
private val pendingRequests = LinkedList<StackLocateRequest>()
private var observers = 0 private var observers = 0
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>() var cachedSortedNetItems = listOf<ItemStack>()
@ -42,15 +51,47 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
when (packet) { when (packet) {
is ReadInventoryPacket -> handleReadInventory(packet) is ReadInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet)
} }
} }
private fun handleReadInventory(packet: ReadInventoryPacket) { private fun handleReadInventory(packet: ReadInventoryPacket) {
inventoryCache[packet.source] = packet.inventory inventoryCache[packet.source] = packet.inventory
updateNetItems()
sync()
} }
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
inventoryCache.remove(packet.source) 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() { private fun updateNetItems() {
@ -61,6 +102,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b } 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() { fun addObserver() {
@ -75,7 +122,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
super.tick() super.tick()
if (observers > 0 && (++counter % 20) == 0) { if (observers > 0 && (++counter % 20) == 0) {
counter = 0
if (world!!.isClient) { if (world!!.isClient) {
println(cachedNetItems) println(cachedNetItems)
} else { } else {
@ -87,6 +133,9 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
fun onActivate(player: PlayerEntity) { fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) { if (!world!!.isClient) {
updateNetItems()
sync()
inventoryCache.clear() inventoryCache.clear()
enqueueToSingle(RequestInventoryPacket(macAddress)) enqueueToSingle(RequestInventoryPacket(macAddress))
ContainerProviderRegistry.INSTANCE.openContainer(TerminalContainer.ID, player) { buf -> ContainerProviderRegistry.INSTANCE.openContainer(TerminalContainer.ID, player) { buf ->
@ -96,8 +145,25 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
addObserver() 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) { override fun onInvChange(inv: Inventory) {
if (inv == internalBuffer) { if (inv == internalBuffer && !world!!.isClient) {
markDirty() markDirty()
sync() sync()
} }
@ -126,7 +192,14 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
override fun fromClientTag(tag: CompoundTag) { 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) val list = tag.getList("CachedNetItems", 10)
cachedNetItems.clear() cachedNetItems.clear()
for (entryTag in list) { 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<Pair<Int, InterfaceBlockEntity>> = mutableSetOf()
) {
val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
}

View File

@ -2,10 +2,13 @@ package net.shadowfacts.phycon.network.block.terminal
import net.minecraft.container.Container import net.minecraft.container.Container
import net.minecraft.container.Slot import net.minecraft.container.Slot
import net.minecraft.container.SlotActionType
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import kotlin.math.max
/** /**
* @author shadowfacts * @author shadowfacts
@ -52,4 +55,19 @@ class TerminalContainer(syncId: Int, playerInv: PlayerInventory, val terminal: T
terminal.removeObserver() 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)
}
} }

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 ExtractStackPacket(val stack: ItemStack, val amount: Int, 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 ItemStackPacket(val stack: ItemStack, 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 LocateStackPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) {
}

View File

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