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