Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Persistent container cache and renderer #434

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -25,13 +33,66 @@ 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))
private val borderBottomColor by setting("Bottom Border Color", ColorHolder(40, 0, 127, 80))

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

Expand Down Expand Up @@ -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)

Expand Down