PhysicalConnectivity/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt

224 lines
6.7 KiB
Kotlin

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
import net.minecraft.inventory.InventoryListener
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.util.Tickable
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.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.packet.*
import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.insert
import net.shadowfacts.phycon.util.toTag
import java.util.*
import kotlin.math.min
/**
* @author shadowfacts
*/
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryListener, BlockEntityClientSerializable, Tickable {
companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks
}
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = TerminalBufferInventory(18)
private val pendingRequests = LinkedList<StackLocateRequest>()
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var counter = 0
init {
internalBuffer.addListener(this)
}
override fun handlePacket(packet: Packet) {
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) {
val remaining = internalBuffer.insertFromNetwork(packet.stack)
if (!remaining.isEmpty) {
// 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)
}
}
private fun updateNetItems() {
cachedNetItems.clear()
for (inventory in inventoryCache.values) {
for (stack in inventory.storedStacks) {
val amount = inventory.getAmount(stack)
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() {
observers++
}
fun removeObserver() {
observers--
}
override fun tick() {
if (observers > 0 && (++counter % 20) == 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
sync()
}
}
}
fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
updateNetItems()
sync()
inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress))
ContainerProviderRegistry.INSTANCE.openContainer(TerminalContainer.ID, player) { buf ->
buf.writeBlockPos(pos)
}
}
addObserver()
}
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
pendingRequests.add(StackLocateRequest(stack, amount, counter))
sendToSingle(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
sendToSingle(ExtractStackPacket(request.stack, amountToRequest, macAddress, sourceInterface.macAddress))
}
}
override fun onInvChange(inv: Inventory) {
if (inv == internalBuffer && world != null && !world!!.isClient) {
markDirty()
sync()
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag())
return super.toTag(tag)
}
override fun fromTag(tag: CompoundTag) {
super.fromTag(tag)
internalBuffer.fromTag(tag.getList("InternalBuffer", 10))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag())
val list = ListTag()
tag.put("CachedNetItems", list)
for ((stack, amount) in cachedNetItems) {
val entryTag = stack.toTag(CompoundTag())
entryTag.putInt("NetAmount", amount)
list.add(entryTag)
}
return tag
}
override fun fromClientTag(tag: CompoundTag) {
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) {
val stack = ItemStack.fromTag(entryTag as CompoundTag)
val netAmount = entryTag.getInt("NetAmount")
cachedNetItems[stack] = netAmount
}
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
}
}
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 }
}