PhysicalConnectivity/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt

172 lines
5.4 KiB
Kotlin

package net.shadowfacts.phycon.block.inserter
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.ItemInsertable
import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.component.ActivationController
import net.shadowfacts.phycon.component.ItemStackPacketHandler
import net.shadowfacts.phycon.component.NetworkStackProvider
import net.shadowfacts.phycon.component.handleItemStack
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.GhostInv
import kotlin.math.min
/**
* @author shadowfacts
*/
class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state),
ItemStackPacketHandler,
ActivationController.ActivatableDevice,
ClientConfigurableDevice,
GhostInv {
companion object {
val SLEEP_TIME = 40L
val REQUEST_TIMEOUT = 40
}
private val facing: Direction
get() = cachedState[FaceDeviceBlock.FACING]
private var inventory: ItemInsertable? = null
private var currentRequest: PendingExtractRequest? = null
var stackToExtract: ItemStack = ItemStack.EMPTY
override var ghostSlotStack: ItemStack
get() = stackToExtract
set(value) { stackToExtract = value }
var amountToExtract = 1
override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.INSERTABLE.getFirstOrNull(world, offsetPos, option)
}
private fun getInventory(): ItemInsertable? {
if (inventory == null) updateInventory()
return inventory
}
override fun handle(packet: Packet) {
when (packet) {
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet)
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory()
return if (inventory != null) {
inventory.attemptInsertion(packet.stack, Simulation.ACTION)
} else {
// no inventory, entire stack remains
packet.stack
}
}
private fun handleStackLocation(packet: StackLocationPacket) {
val request = currentRequest
if (request != null && ItemStackUtil.areEqualIgnoreAmounts(request.stack, packet.stack)) {
request.results.add(packet.amount to packet.stackProvider)
if (request.isFinishable(counter)) {
finishRequest()
}
}
}
override fun tick() {
super.tick()
if (!world!!.isClient) {
controller.tick()
val request = currentRequest
if (request != null) {
if (request.isFinishable(counter)) {
finishRequest()
} else if (counter - request.timestamp >= REQUEST_TIMEOUT && request.totalAmount == 0) {
currentRequest = null
}
}
}
}
override fun activate(): Boolean {
if (currentRequest != null || stackToExtract.isEmpty) {
return false
}
// todo: configure me
currentRequest = PendingExtractRequest(stackToExtract, counter)
sendPacket(LocateStackPacket(stackToExtract, ipAddress))
return true
}
private fun finishRequest() {
val request = currentRequest ?: return
// todo: dedup with TerminalBlockEntity.stackLocateRequestCompleted
val actualAmount = min(min(request.stack.maxCount, request.totalAmount), amountToExtract)
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
var amountRequested = 0
while (amountRequested < actualAmount && sortedResults.isNotEmpty()) {
val (sourceAmount, source) = sortedResults.removeAt(0)
val amountToRequest = min(sourceAmount, actualAmount - amountRequested)
amountRequested += amountToRequest
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, source.ipAddress))
}
currentRequest = null
}
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
tag.put("StackToExtract", stackToExtract.writeNbt(NbtCompound()))
}
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
stackToExtract = ItemStack.fromNbt(tag.getCompound("StackToExtract"))
}
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putString("ActivationMode", controller.activationMode.name)
tag.putInt("AmountToExtract", amountToExtract)
}
override fun loadDeviceConfiguration(tag: NbtCompound) {
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
amountToExtract = tag.getInt("AmountToExtract")
}
class PendingExtractRequest(
val stack: ItemStack,
val timestamp: Long,
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
) {
val totalAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Long): Boolean {
return totalAmount >= stack.maxCount || (currentTimestamp - timestamp >= REQUEST_TIMEOUT && totalAmount > 0)
}
}
}