package net.shadowfacts.phycon.block.miner import alexiil.mc.lib.attributes.item.GroupedItemInvView import alexiil.mc.lib.attributes.item.ItemStackUtil import alexiil.mc.lib.attributes.item.filter.ItemFilter import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.nbt.NbtCompound import net.minecraft.server.world.ServerWorld import net.minecraft.text.TranslatableText import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.minecraft.world.World import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity import net.shadowfacts.phycon.component.* import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.ClientConfigurableDevice import net.shadowfacts.phycon.util.copyWithCount import kotlin.math.min /** * @author shadowfacts */ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.MINER, pos, state), NetworkStackProvider, NetworkStackDispatcher, ActivationController.ActivatableDevice, ClientConfigurableDevice { private val facing: Direction get() = cachedState[MinerBlock.FACING] private val invProxy = MinerInvProxy(this) override val pendingInsertions = mutableListOf() override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT override val controller = ActivationController(40L, this) override var providerPriority = 0 var minerMode = MinerMode.ON_DEMAND override fun handle(packet: Packet) { when (packet) { is RequestInventoryPacket -> handleRequestInventory(packet) is LocateStackPacket -> handleLocateStack(packet) is ExtractStackPacket -> handleExtractStack(packet) is CapacityPacket -> handleCapacity(packet) is ItemStackPacket -> handleItemStack(packet) is RemoteActivationPacket -> controller.handleRemoteActivation(packet) } } private fun handleRequestInventory(packet: RequestInventoryPacket) { if (minerMode != MinerMode.ON_DEMAND) { return } sendPacket(ReadInventoryPacket(invProxy, ipAddress, packet.source)) } private fun handleLocateStack(packet: LocateStackPacket) { if (minerMode != MinerMode.ON_DEMAND) { return } val amount = invProxy.getAmount(packet.stack) if (amount > 0) { sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source)) } } private fun handleExtractStack(packet: ExtractStackPacket) { if (minerMode != MinerMode.ON_DEMAND) { return } // always recalculate immediately before breaking val drops = invProxy.getDrops(recalculate = true) if (invProxy.getAmount(packet.stack) > 0) { world!!.breakBlock(pos.offset(facing), false) // send the requested amount back to the requester var remaining = packet.amount for (droppedStack in drops) { if (remaining <= 0) { break } if (!ItemStackUtil.areEqualIgnoreAmounts(droppedStack, packet.stack)) { continue } val toDecr = min(droppedStack.count, remaining) val copy = droppedStack.copyWithCount(toDecr) droppedStack.decrement(toDecr) remaining -= toDecr // todo: should this try to combine stacks and send as few packets as possible? sendPacket(ItemStackPacket(copy, ipAddress, packet.source)) } // dump any remaining drops into the network for (droppedStack in drops) { if (droppedStack.isEmpty) continue dispatchItemStack(droppedStack) } } } override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { // miner can't receive stacks, so remaining is the entire packet stack return packet.stack } override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter) override fun tick() { super.tick() if (!world!!.isClient) { if (minerMode == MinerMode.AUTOMATIC) { controller.tick() } finishTimedOutPendingInsertions() } } override fun activate(): Boolean { if (minerMode == MinerMode.ON_DEMAND) { return false } val drops = invProxy.getDrops(recalculate = true) if (!world!!.getBlockState(pos.offset(facing)).isAir) { world!!.breakBlock(pos.offset(facing), false) for (stack in drops) { if (stack.isEmpty) continue dispatchItemStack(stack) } return true } else { return false } } override fun canConfigureActivationController(): Boolean { return minerMode == MinerMode.AUTOMATIC } override fun canConfigureProviderPriority(): Boolean { return minerMode == MinerMode.ON_DEMAND } override fun toCommonTag(tag: NbtCompound) { super.toCommonTag(tag) writeDeviceConfiguration(tag) } override fun fromCommonTag(tag: NbtCompound) { super.fromCommonTag(tag) loadDeviceConfiguration(tag) } override fun writeDeviceConfiguration(tag: NbtCompound) { tag.putString("MinerMode", minerMode.name) tag.putString("ActivationMode", controller.activationMode.name) tag.putInt("ProviderPriority", providerPriority) } override fun loadDeviceConfiguration(tag: NbtCompound) { minerMode = MinerMode.valueOf(tag.getString("MinerMode")) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) providerPriority = tag.getInt("ProviderPriority") } enum class MinerMode { ON_DEMAND, AUTOMATIC; val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}") } class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView { companion object { val TOOL = ItemStack(Items.DIAMOND_PICKAXE) } private var cachedState: BlockState? = null private var cachedDrops: List? = null private val world: World get() = miner.world!! private val pos: BlockPos get() = miner.pos!! private val facing: Direction get() = miner.facing fun getDrops(recalculate: Boolean = false): List { val targetPos = pos.offset(facing) val realState = world.getBlockState(targetPos) // todo: does BlockState.equals actually work or is reference equality fine for BlockStates? if (cachedDrops == null || realState != cachedState || recalculate) { cachedState = realState val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL) } return cachedDrops!! } override fun getStoredStacks(): Set { if (miner.minerMode != MinerMode.ON_DEMAND) { return setOf() } return getDrops().toSet() } override fun getTotalCapacity(): Int { return Int.MAX_VALUE } override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic { var totalCount = 0 for (s in storedStacks) { if (filter.matches(s)) { totalCount += s.count } } return GroupedItemInvView.ItemInvStatistic(filter, totalCount, 0, Int.MAX_VALUE) } } class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion(stack, timestamp) { } }