2021-03-27 14:22:18 +00:00
|
|
|
package net.shadowfacts.phycon.block.terminal
|
|
|
|
|
|
|
|
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
|
|
|
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
|
|
|
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
2021-12-22 23:59:51 +00:00
|
|
|
import net.minecraft.block.BlockState
|
2021-03-27 14:22:18 +00:00
|
|
|
import net.minecraft.block.entity.BlockEntityType
|
|
|
|
import net.minecraft.entity.player.PlayerEntity
|
|
|
|
import net.minecraft.inventory.Inventory
|
|
|
|
import net.minecraft.inventory.InventoryChangedListener
|
|
|
|
import net.minecraft.item.ItemStack
|
2021-12-22 23:59:51 +00:00
|
|
|
import net.minecraft.nbt.NbtCompound
|
2021-03-27 14:22:18 +00:00
|
|
|
import net.minecraft.util.ItemScatterer
|
2021-12-22 23:59:51 +00:00
|
|
|
import net.minecraft.util.math.BlockPos
|
2021-03-27 14:22:18 +00:00
|
|
|
import net.minecraft.util.math.Direction
|
|
|
|
import net.shadowfacts.phycon.api.Interface
|
|
|
|
import net.shadowfacts.phycon.api.packet.Packet
|
|
|
|
import net.shadowfacts.phycon.api.util.IPAddress
|
|
|
|
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
|
|
|
import net.shadowfacts.phycon.component.*
|
|
|
|
import net.shadowfacts.phycon.packet.*
|
2021-12-22 23:59:51 +00:00
|
|
|
import net.shadowfacts.phycon.util.NetworkUtil
|
2021-03-27 14:22:18 +00:00
|
|
|
import java.lang.ref.WeakReference
|
|
|
|
import java.util.*
|
2021-12-22 23:59:51 +00:00
|
|
|
import java.util.function.IntBinaryOperator
|
2021-03-27 14:22:18 +00:00
|
|
|
import kotlin.math.min
|
|
|
|
import kotlin.properties.Delegates
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author shadowfacts
|
|
|
|
*/
|
2021-12-22 23:59:51 +00:00
|
|
|
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state),
|
2021-03-27 14:22:18 +00:00
|
|
|
InventoryChangedListener,
|
|
|
|
ItemStackPacketHandler,
|
|
|
|
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
|
|
|
|
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
|
|
|
|
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
|
|
|
|
val INSERTION_TIMEOUT: Long = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
|
|
|
val internalBuffer = TerminalBufferInventory(18)
|
|
|
|
|
2021-03-28 17:50:11 +00:00
|
|
|
protected val pendingRequests = LinkedList<StackLocateRequest>()
|
2021-03-27 14:22:18 +00:00
|
|
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
|
|
|
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
|
|
|
|
|
|
|
private var observers = 0
|
|
|
|
val cachedNetItems = ItemStackCollections.intMap()
|
|
|
|
|
|
|
|
// todo: multiple players could have the terminal open simultaneously
|
|
|
|
var netItemObserver: WeakReference<NetItemObserver>? = null
|
|
|
|
|
|
|
|
init {
|
|
|
|
internalBuffer.addListener(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun findDestination(): Interface? {
|
|
|
|
for (dir in Direction.values()) {
|
|
|
|
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
|
|
|
if (itf != null) {
|
|
|
|
return itf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun handle(packet: Packet) {
|
|
|
|
when (packet) {
|
2021-12-24 17:34:16 +00:00
|
|
|
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
|
2021-03-27 14:22:18 +00:00
|
|
|
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
|
|
|
is StackLocationPacket -> handleStackLocation(packet)
|
|
|
|
is ItemStackPacket -> handleItemStack(packet)
|
|
|
|
is CapacityPacket -> handleCapacity(packet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-24 17:34:16 +00:00
|
|
|
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
|
2021-03-27 14:22:18 +00:00
|
|
|
inventoryCache[packet.source] = packet.inventory
|
|
|
|
updateAndSync()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
|
|
|
inventoryCache.remove(packet.source)
|
|
|
|
updateAndSync()
|
|
|
|
}
|
|
|
|
|
|
|
|
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.stackProvider)
|
|
|
|
if (request.isFinishable(counter)) {
|
|
|
|
stackLocateRequestCompleted(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
2021-03-28 17:48:56 +00:00
|
|
|
val mode =
|
|
|
|
if (packet.bounceCount > 0) {
|
|
|
|
// if this stack bounced from an inventory, that means we previously tried to send it to the network, so retry
|
|
|
|
TerminalBufferInventory.Mode.TO_NETWORK
|
|
|
|
} else {
|
|
|
|
TerminalBufferInventory.Mode.FROM_NETWORK
|
|
|
|
}
|
|
|
|
|
|
|
|
val remaining = internalBuffer.insert(packet.stack, mode)
|
2021-03-27 14:22:18 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
updateAndSync()
|
|
|
|
|
|
|
|
return remaining
|
|
|
|
}
|
|
|
|
|
|
|
|
protected fun updateAndSync() {
|
|
|
|
updateNetItems()
|
|
|
|
// syncs the internal buffer to the client
|
2021-12-22 23:59:51 +00:00
|
|
|
markUpdate()
|
2021-03-27 14:22:18 +00:00
|
|
|
// syncs the open container (if any) to the client
|
|
|
|
netItemObserver?.get()?.netItemsChanged()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateNetItems() {
|
|
|
|
cachedNetItems.clear()
|
|
|
|
for (inventory in inventoryCache.values) {
|
|
|
|
for (stack in inventory.storedStacks) {
|
|
|
|
val amount = inventory.getAmount(stack)
|
2021-12-22 23:59:51 +00:00
|
|
|
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b })
|
2021-03-27 14:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun beginInsertions() {
|
|
|
|
if (world!!.isClient) return
|
|
|
|
|
|
|
|
for (slot in 0 until internalBuffer.size()) {
|
|
|
|
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
|
|
|
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
|
|
|
val stack = internalBuffer.getStack(slot)
|
|
|
|
dispatchItemStack(stack) { insertion ->
|
|
|
|
insertion.bufferSlot = slot
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun finishPendingRequests() {
|
|
|
|
if (world!!.isClient) return
|
|
|
|
if (pendingRequests.isEmpty()) return
|
|
|
|
|
|
|
|
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
|
|
|
// stackLocateRequestCompleted removes the object from pendingRequests
|
|
|
|
finishable.forEach(::stackLocateRequestCompleted)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun tick() {
|
|
|
|
super.tick()
|
|
|
|
|
|
|
|
if (!world!!.isClient) {
|
|
|
|
finishPendingRequests()
|
|
|
|
finishTimedOutPendingInsertions()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (counter % 20 == 0L && !world!!.isClient) {
|
|
|
|
beginInsertions()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun onActivate(player: PlayerEntity) {
|
|
|
|
}
|
|
|
|
|
|
|
|
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
|
|
|
val request = StackLocateRequest(stack, amount, counter)
|
|
|
|
pendingRequests.add(request)
|
|
|
|
// locate packets are sent immediately instead of being added to a queue
|
|
|
|
// otherwise the terminal UI feels sluggish and unresponsive
|
|
|
|
sendPacket(LocateStackPacket(stack, ipAddress))
|
|
|
|
}
|
|
|
|
|
2021-03-28 17:50:11 +00:00
|
|
|
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
2021-03-27 14:22:18 +00:00
|
|
|
pendingRequests.remove(request)
|
|
|
|
|
|
|
|
val sortedResults = request.results.toMutableList()
|
|
|
|
sortedResults.sortWith { a, b ->
|
|
|
|
// sort results first by provider priority, and then by the count that it can provide
|
|
|
|
if (a.second.providerPriority == b.second.providerPriority) {
|
|
|
|
b.first - a.first
|
|
|
|
} else {
|
|
|
|
b.second.providerPriority - a.second.providerPriority
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var amountRequested = 0
|
|
|
|
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
|
|
|
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
|
|
|
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
|
|
|
amountRequested += amountToRequest
|
|
|
|
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
|
|
|
|
|
|
|
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
|
|
|
val remaining = super.finishInsertion(insertion)
|
|
|
|
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
|
|
|
|
|
|
|
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
|
|
|
updateAndSync()
|
|
|
|
|
|
|
|
return remaining
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onInventoryChanged(inv: Inventory) {
|
|
|
|
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
2021-12-22 23:59:51 +00:00
|
|
|
markUpdate()
|
2021-03-27 14:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun dropItems() {
|
|
|
|
ItemScatterer.spawn(world, pos, internalBuffer)
|
|
|
|
}
|
|
|
|
|
2021-12-22 23:59:51 +00:00
|
|
|
override fun toCommonTag(tag: NbtCompound) {
|
2021-03-27 14:22:18 +00:00
|
|
|
super.toCommonTag(tag)
|
|
|
|
tag.put("InternalBuffer", internalBuffer.toTag())
|
|
|
|
}
|
|
|
|
|
2021-12-22 23:59:51 +00:00
|
|
|
override fun fromCommonTag(tag: NbtCompound) {
|
2021-03-27 14:22:18 +00:00
|
|
|
super.fromCommonTag(tag)
|
|
|
|
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
|
|
|
}
|
|
|
|
|
|
|
|
interface NetItemObserver {
|
|
|
|
fun netItemsChanged()
|
|
|
|
}
|
|
|
|
|
|
|
|
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
|
|
|
var bufferSlot by Delegates.notNull<Int>()
|
|
|
|
}
|
|
|
|
|
2021-03-28 17:50:11 +00:00
|
|
|
open class StackLocateRequest(
|
2021-03-27 14:22:18 +00:00
|
|
|
val stack: ItemStack,
|
|
|
|
val amount: Int,
|
|
|
|
val timestamp: Long,
|
|
|
|
) {
|
2021-03-28 17:50:11 +00:00
|
|
|
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
|
|
|
|
2021-03-27 14:22:18 +00:00
|
|
|
val totalResultAmount: Int
|
|
|
|
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
|
|
|
|
|
|
|
fun isFinishable(currentTimestamp: Long): Boolean {
|
|
|
|
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
|
|
|
|
// correctly sort by priority
|
2021-03-28 17:50:11 +00:00
|
|
|
return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT
|
2021-03-27 14:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|