From 2cd11be465fb97f63e639b2e894a1add640b886a Mon Sep 17 00:00:00 2001 From: Constructor <fractalminds@protonmail.com> Date: Tue, 13 Dec 2022 01:45:00 +0100 Subject: [PATCH] feature: Persistent container cache and renderer --- .../managers/CachedContainerManager.kt | 194 ++++++++++++++++++ .../module/modules/render/ContainerPreview.kt | 73 ++++++- 2 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt diff --git a/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt b/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt new file mode 100644 index 000000000..7e9fe9839 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/manager/managers/CachedContainerManager.kt @@ -0,0 +1,194 @@ +package com.lambda.client.manager.managers + +import com.lambda.client.LambdaMod +import com.lambda.client.event.listener.listener +import com.lambda.client.manager.Manager +import com.lambda.client.module.modules.player.PacketLogger +import com.lambda.client.module.modules.render.ContainerPreview.cacheContainers +import com.lambda.client.util.FolderUtils +import com.lambda.client.util.math.Direction +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.safeListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.minecraft.inventory.ItemStackHelper +import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompressedStreamTools +import net.minecraft.nbt.NBTTagByte +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagInt +import net.minecraft.nbt.NBTTagList +import net.minecraft.tileentity.TileEntityChest +import net.minecraft.tileentity.TileEntityDispenser +import net.minecraft.tileentity.TileEntityHopper +import net.minecraft.tileentity.TileEntityLockableLoot +import net.minecraft.tileentity.TileEntityShulkerBox +import net.minecraft.util.EnumFacing +import net.minecraft.util.NonNullList +import net.minecraft.util.math.BlockPos +import net.minecraftforge.event.entity.player.PlayerContainerEvent +import net.minecraftforge.event.entity.player.PlayerInteractEvent +import net.minecraftforge.event.world.WorldEvent +import java.io.* +import java.nio.file.Paths +import kotlin.collections.HashMap + +object CachedContainerManager : Manager { + private val directory = Paths.get(FolderUtils.lambdaFolder, "cached-containers").toFile() + private val containerWorlds = HashMap<File, NBTTagCompound>() + private var currentFile: File? = null + private var currentTileEntityLockableLoot: TileEntityLockableLoot? = null + + init { + listener<WorldEvent.Load> { + if (!cacheContainers) return@listener + + val serverDirectory = mc.currentServerData?.serverIP?.replace(":", "_") ?: return@listener + val folder = File(directory, serverDirectory) + currentFile = File(folder, "${it.world.provider.dimension}.nbt") + + currentFile?.let { file -> + if (containerWorlds[file] != null) return@listener + + defaultScope.launch(Dispatchers.IO) { + try { + CompressedStreamTools.read(file)?.let { compound -> + containerWorlds[file] = compound + LambdaMod.LOG.info("Container DB loaded from $file") + } ?: run { + if (!folder.exists()) folder.mkdirs() + val containerDB = NBTTagCompound().apply { + setTag("Containers", NBTTagList()) + } + containerWorlds[file] = containerDB + CompressedStreamTools.write(containerDB, file) + LambdaMod.LOG.info("New container DB created in $file") + } + } catch (e: IOException) { + LambdaMod.LOG.error("Failed to load container DB from $file", e) + } + } + } + } + + safeListener<PlayerInteractEvent.RightClickBlock> { + if (!cacheContainers) return@safeListener + + currentTileEntityLockableLoot = (world.getTileEntity(it.pos) as? TileEntityLockableLoot) ?: return@safeListener + } + + safeListener<PlayerContainerEvent.Close> { event -> + if (!cacheContainers) return@safeListener + + val tileEntityLockableLoot = currentTileEntityLockableLoot ?: return@safeListener + currentTileEntityLockableLoot = null + + val tileEntityTag = tileEntityLockableLoot.serializeNBT() + + val matrix = getContainerMatrix(tileEntityLockableLoot) + + if (tileEntityLockableLoot is TileEntityChest && event.container.inventory.size == 90) { + var otherChest: TileEntityChest? = null + var facing: EnumFacing? = null + + tileEntityLockableLoot.adjacentChestXNeg?.let { + otherChest = it + facing = EnumFacing.WEST + } + + tileEntityLockableLoot.adjacentChestXPos?.let { + otherChest = it + facing = EnumFacing.EAST + } + + tileEntityLockableLoot.adjacentChestZNeg?.let { + otherChest = it + facing = EnumFacing.NORTH + } + + tileEntityLockableLoot.adjacentChestZPos?.let { + otherChest = it + facing = EnumFacing.SOUTH + } + + otherChest?.let { other -> + facing?.let { face -> + val slotCount = matrix.first * matrix.second * 2 + val inventory = event.container.inventory.take(slotCount) + + safeInventoryToDB(inventory, tileEntityTag, tileEntityLockableLoot.pos, face) + safeInventoryToDB(inventory, other.serializeNBT(), other.pos, face.opposite) + } + } + + } else { + val slotCount = matrix.first * matrix.second + val inventory = event.container.inventory.take(slotCount) + + safeInventoryToDB(inventory, tileEntityTag, tileEntityLockableLoot.pos, null) + } + } + } + + private fun safeInventoryToDB(inventory: List<ItemStack>, tileEntityTag: NBTTagCompound, pos: BlockPos, facing: EnumFacing?) { + val file = currentFile ?: return + val containerDB = containerWorlds[file] ?: return + val containerList = containerDB.getContainerList() ?: return + + val nonNullList = NonNullList.withSize(inventory.size, ItemStack.EMPTY) + + inventory.forEachIndexed { index, itemStack -> + nonNullList[index] = itemStack + } + + ItemStackHelper.saveAllItems(tileEntityTag, nonNullList) + + facing?.let { + tileEntityTag.setTag("adjacentChest", NBTTagByte(it.index.toByte())) + } + + findContainer(pos)?.let { containerTag -> + containerList.removeAll { containerTag == it } + containerList.appendTag(tileEntityTag) + } ?: run { + containerList.appendTag(tileEntityTag) + } + + defaultScope.launch(Dispatchers.IO) { + try { + CompressedStreamTools.write(containerDB, file) + } catch (e: IOException) { + LambdaMod.LOG.warn("${PacketLogger.chatName} Failed saving containers!", e) + } + } + } + + fun findContainer(pos: BlockPos) = containerWorlds[currentFile] + ?.getContainerList() + ?.filterIsInstance<NBTTagCompound>() + ?.firstOrNull { + pos.x == it.getInteger("x") + && pos.y == it.getInteger("y") + && pos.z == it.getInteger("z") + } + + fun getInventoryOfContainer(tag: NBTTagCompound): NonNullList<ItemStack>? { + val inventory = NonNullList.withSize(54, ItemStack.EMPTY) + ItemStackHelper.loadAllItems(tag, inventory) + return inventory + } + + fun getAllContainers() = containerWorlds[currentFile]?.getContainerList()?.filterIsInstance<NBTTagCompound>() + + fun getContainerMatrix(type: TileEntityLockableLoot): Pair<Int, Int> { + return when (type) { + is TileEntityChest -> Pair(9, 3) + is TileEntityDispenser -> Pair(3, 3) + is TileEntityHopper -> Pair(5, 1) + is TileEntityShulkerBox -> Pair(9, 3) + else -> Pair(0, 0) // Should never happen + } + } + + private fun NBTTagCompound.getContainerList() = getTag("Containers") as? NBTTagList +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt index 53ac4006d..f470feb0d 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/render/ContainerPreview.kt @@ -1,21 +1,29 @@ package com.lambda.client.module.modules.render import com.lambda.client.commons.extension.ceilToInt +import com.lambda.client.commons.extension.floorToInt +import com.lambda.client.event.events.RenderOverlayEvent +import com.lambda.client.manager.managers.CachedContainerManager import com.lambda.client.module.Category import com.lambda.client.module.Module import com.lambda.client.util.color.ColorHolder -import com.lambda.client.util.graphics.GlStateUtils -import com.lambda.client.util.graphics.RenderUtils2D -import com.lambda.client.util.graphics.VertexHelper +import com.lambda.client.util.graphics.* import com.lambda.client.util.graphics.font.FontRenderAdapter import com.lambda.client.util.items.block import com.lambda.client.util.math.Vec2d +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.threads.safeListener +import com.lambda.client.util.world.getHitVec import net.minecraft.client.renderer.GlStateManager import net.minecraft.init.Blocks import net.minecraft.inventory.IInventory import net.minecraft.item.ItemShulkerBox import net.minecraft.item.ItemStack -import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.* +import net.minecraft.tileentity.TileEntity +import net.minecraft.tileentity.TileEntityLockableLoot +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.BlockPos import org.lwjgl.opengl.GL11.GL_LINE_LOOP import org.lwjgl.opengl.GL11.glLineWidth import org.spongepowered.asm.mixin.injection.callback.CallbackInfo @@ -25,6 +33,8 @@ object ContainerPreview : Module( description = "Previews shulkers and ender chests in the game GUI", category = Category.RENDER ) { + val cacheContainers by setting("Cache Containers", true) + private val renderCachedContainers by setting("Render Cached Containers", true, { cacheContainers }) private val useCustomFont by setting("Use Custom Font", false) private val backgroundColor by setting("Background Color", ColorHolder(16, 0, 16, 255)) private val borderTopColor by setting("Top Border Color", ColorHolder(144, 101, 237, 54)) @@ -32,6 +42,57 @@ object ContainerPreview : Module( var enderChest: IInventory? = null + init { + safeListener<RenderOverlayEvent> { + if (!renderCachedContainers) return@safeListener + + var indexH = 0 + + // Preprocessing needs to be done in the manager to reduce strain on the render thread + CachedContainerManager.getAllContainers()?.forEach { tag -> + CachedContainerManager.getInventoryOfContainer(tag)?.let { container -> + val thisPos = BlockPos(tag.getInteger("x"), tag.getInteger("y"), tag.getInteger("z")) + val type = (TileEntity.create(world, tag) as? TileEntityLockableLoot) ?: return@safeListener + var matrix = CachedContainerManager.getContainerMatrix(type) + + var renderPos = thisPos.toVec3dCenter() + + (tag.getTag("adjacentChest") as? NBTTagByte)?.byte?.toInt()?.let { index -> + renderPos = getHitVec(thisPos, EnumFacing.byIndex(index)) + matrix = Pair(9, 6) + } + + val screenPos = ProjectionUtils.toScaledScreenPos(renderPos) + + val width = matrix.first * 16 + val height = matrix.second * 16 + + val vertexHelper = VertexHelper(GlStateUtils.useVbo()) + + val color = backgroundColor.clone().apply { a = 50 } + + val newX = screenPos.x - width / 2 + val newY = screenPos.y - height / 2 + + RenderUtils2D.drawRoundedRectFilled( + vertexHelper, + Vec2d(newX, newY), + Vec2d(newX + width, newY + height), + 1.0, + color = color + ) + + container.forEachIndexed { index, itemStack -> + val x = newX + (index % matrix.first) * 16 + val y = newY + (index / matrix.first) * 16 + RenderUtils2D.drawItem(itemStack, x.floorToInt(), y.floorToInt()) + } + } + indexH += 60 + } + } + } + fun renderTooltips(itemStack: ItemStack, x: Int, y: Int, ci: CallbackInfo) { val item = itemStack.item @@ -110,12 +171,12 @@ object ContainerPreview : Module( color = backgroundColor ) - drawRectOutline(vertexHelper, x, y, width, height) + drawRectOutline(vertexHelper, x, y, width, height.floorToInt()) FontRenderAdapter.drawString(stack.displayName, x.toFloat(), y.toFloat() - 2.0f, customFont = useCustomFont) } - private fun drawRectOutline(vertexHelper: VertexHelper, x: Double, y: Double, width: Int, height: Float) { + private fun drawRectOutline(vertexHelper: VertexHelper, x: Double, y: Double, width: Int, height: Int) { RenderUtils2D.prepareGl() glLineWidth(5.0f)