diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt
index 43a84aa74..5f6c60746 100644
--- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt
+++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt
@@ -19,8 +19,6 @@ package com.lambda.graphics.renderer.esp
import com.lambda.util.world.FastVector
import com.lambda.util.world.offset
-import com.lambda.util.world.toBlockPos
-import com.lambda.util.world.toFastVec
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
@@ -43,8 +41,15 @@ object DirectionMask {
fun Int.exclude(direction: Direction) = exclude(direction.mask)
fun Int.hasDirection(dir: Int) = (this and dir) != 0
- fun buildSideMesh(position: BlockPos, filter: (BlockPos) -> Boolean) =
- buildSideMesh(position.toFastVec()) { filter(it.toBlockPos()) }
+ fun buildSideMesh(position: BlockPos, filter: (BlockPos) -> Boolean): Int {
+ var sides = ALL
+
+ Direction.entries
+ .filter { filter(position.offset(it)) }
+ .forEach { sides = sides.exclude(it.mask) }
+
+ return sides
+ }
fun buildSideMesh(position: FastVector, filter: (FastVector) -> Boolean): Int {
var sides = ALL
diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt
index 74d4930b7..09a849f2e 100644
--- a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt
+++ b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt
@@ -28,8 +28,7 @@ import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.threading.runSafe
import com.lambda.util.extension.blockColor
-import com.lambda.util.extension.blockFilledMesh
-import com.lambda.util.extension.blockOutlineMesh
+import com.lambda.util.extension.outlineShape
import com.lambda.util.extension.getBlockState
import com.lambda.util.world.fastVectorOf
import com.lambda.util.world.toBlockPos
@@ -91,12 +90,11 @@ object BlockESP : Module(
pos: BlockPos,
sides: Int,
) = runSafe {
- val filledMesh = blockFilledMesh(state, pos)
- val outlineMesh = blockOutlineMesh(state, pos)
+ val shape = outlineShape(state, pos)
val blockColor = blockColor(state, pos)
- if (drawFaces) buildFilledMesh(filledMesh, if (useBlockColor) blockColor else faceColor, sides)
- if (drawOutlines) buildOutlineMesh(outlineMesh, if (useBlockColor) blockColor else outlineColor, sides, outlineMode)
+ if (drawFaces) buildFilledMesh(shape, if (useBlockColor) blockColor else faceColor, sides)
+ if (drawOutlines) buildOutlineMesh(shape, if (useBlockColor) blockColor else outlineColor, sides, outlineMode)
}
private fun rebuildMesh(from: Any, to: Any): Unit = esp.rebuild()
diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt
new file mode 100644
index 000000000..5fac5a4de
--- /dev/null
+++ b/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.module.modules.render
+
+import com.lambda.context.SafeContext
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.graphics.renderer.esp.DirectionMask
+import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh
+import com.lambda.graphics.renderer.esp.builders.buildFilled
+import com.lambda.graphics.renderer.esp.builders.buildFilledMesh
+import com.lambda.graphics.renderer.esp.builders.buildOutline
+import com.lambda.graphics.renderer.esp.builders.buildOutlineMesh
+import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.threading.runSafe
+import com.lambda.util.extension.blockColor
+import com.lambda.util.extension.outlineShape
+import com.lambda.util.math.setAlpha
+import com.lambda.util.world.blockEntitySearch
+import com.lambda.util.world.entitySearch
+import net.minecraft.block.entity.BarrelBlockEntity
+import net.minecraft.block.entity.BlastFurnaceBlockEntity
+import net.minecraft.block.entity.BlockEntity
+import net.minecraft.block.entity.BrewingStandBlockEntity
+import net.minecraft.block.entity.ChestBlockEntity
+import net.minecraft.block.entity.DispenserBlockEntity
+import net.minecraft.block.entity.EnderChestBlockEntity
+import net.minecraft.block.entity.FurnaceBlockEntity
+import net.minecraft.block.entity.HopperBlockEntity
+import net.minecraft.block.entity.ShulkerBoxBlockEntity
+import net.minecraft.block.entity.SmokerBlockEntity
+import net.minecraft.entity.Entity
+import net.minecraft.entity.decoration.ItemFrameEntity
+import net.minecraft.entity.vehicle.AbstractMinecartEntity
+import net.minecraft.entity.vehicle.MinecartEntity
+import net.minecraft.util.math.BlockPos
+import java.awt.Color
+
+object StorageESP : Module(
+ name = "StorageESP",
+ description = "Render storage blocks/entities",
+ defaultTags = setOf(ModuleTag.RENDER),
+) {
+ private val page by setting("Page", Page.Render)
+
+ /* General settings */
+ private val distance by setting("Distance", 64.0, 10.0..256.0, 1.0, "Maximum distance for rendering") { page == Page.General }
+
+ /* Render settings */
+ private var drawFaces: Boolean by setting("Draw Faces", true, "Draw faces of blocks") { page == Page.Render }.apply { onValueSet { _, to -> if (!to) drawOutlines = true } }
+ private var drawOutlines: Boolean by setting("Draw Outlines", true, "Draw outlines of blocks") { page == Page.Render }.apply { onValueSet { _, to -> if (!to) drawFaces = true } }
+ private val outlineMode by setting("Outline Mode", DirectionMask.OutlineMode.AND, "Outline mode") { page == Page.Render }
+ private val mesh by setting("Mesh", true, "Connect similar adjacent blocks") { page == Page.Render }
+
+ /* Color settings */
+ private val useBlockColor by setting("Use Block Color", true, "Use the color of the block instead") { page == Page.Color }
+ private val alpha by setting("Alpha", 0.3, 0.1..1.0, 0.05) { page == Page.Color }
+
+ // TODO:
+ // val blockColors by setting("Block Colors", mapOf()) { page == Page.Color && !useBlockColor }
+ // val renders by setting("Render Blocks", mapOf()) { page == Page.General }
+ //
+ // TODO: Create enum of MapColors
+
+ // I used this to extract the colors as rgb format
+ //> function extract(color) {
+ // ... console.log((color >> 16) & 0xFF)
+ // ... console.log((color >> 8) & 0xFF)
+ // ... console.log(color & 0xFF)
+ // ... }
+
+ private val barrelColor by setting("Barrel Color", Color(143, 119, 72)) { page == Page.Color && !useBlockColor }
+ private val blastFurnaceColor by setting("Blast Furnace Color", Color(153, 153, 153)) { page == Page.Color && !useBlockColor }
+ private val brewingStandColor by setting("Brewing Stand Color", Color(167, 167, 167))
+ private val chestColor by setting("Chest Color", Color(216, 127, 51)) { page == Page.Color && !useBlockColor }
+ private val dispenserColor by setting("Dispenser Color", Color(153, 153, 153)) { page == Page.Color && !useBlockColor }
+ private val enderChestColor by setting("Ender Chest Color", Color(127, 63, 178)) { page == Page.Color && !useBlockColor }
+ private val furnaceColor by setting("Furnace Color", Color(153, 153, 153)) { page == Page.Color && !useBlockColor }
+ private val hopperColor by setting("Hopper Color", Color(76, 76, 76)) { page == Page.Color && !useBlockColor }
+ private val smokerColor by setting("Smoker Color", Color(112, 112, 112)) { page == Page.Color && !useBlockColor }
+ private val shulkerColor by setting("Shulker Color", Color(178, 76, 216)) { page == Page.Color && !useBlockColor }
+ private val itemFrameColor by setting("Item Frame Color", Color(216, 127, 51)) { page == Page.Color && !useBlockColor }
+ private val cartColor by setting("Minecart Color", Color(102, 127, 51)) { page == Page.Color && !useBlockColor }
+
+ private val entities = setOf(
+ BarrelBlockEntity::class,
+ BlastFurnaceBlockEntity::class,
+ BrewingStandBlockEntity::class,
+ ChestBlockEntity::class,
+ DispenserBlockEntity::class,
+ EnderChestBlockEntity::class,
+ FurnaceBlockEntity::class,
+ HopperBlockEntity::class,
+ SmokerBlockEntity::class,
+ ShulkerBoxBlockEntity::class,
+ AbstractMinecartEntity::class,
+ ItemFrameEntity::class,
+ MinecartEntity::class,
+ )
+
+ init {
+ listen { event ->
+ blockEntitySearch(distance)
+ .filter { it::class in entities }
+ .forEach { event.renderer.build(it, it.pos, excludedSides(it)) }
+
+ val mineCarts = entitySearch(distance)
+ val itemFrames = entitySearch(distance)
+ (mineCarts + itemFrames)
+ .forEach { event.renderer.build(it, DirectionMask.ALL) }
+ }
+ }
+
+ private fun SafeContext.excludedSides(blockEntity: BlockEntity): Int {
+ val isFullCube = blockEntity.cachedState.isFullCube(world, blockEntity.pos)
+ return if (mesh && isFullCube) {
+ buildSideMesh(blockEntity.pos) { neighbor ->
+ val other = world.getBlockEntity(neighbor) ?: return@buildSideMesh false
+ val otherFullCube = other.cachedState.isFullCube(world, other.pos)
+ val sameType = blockEntity.cachedState.block == other.cachedState.block
+ val searchedFor = other::class in entities
+
+ searchedFor && otherFullCube && sameType
+ }
+ } else DirectionMask.ALL
+ }
+
+ private fun StaticESPRenderer.build(
+ block: BlockEntity,
+ pos: BlockPos,
+ sides: Int,
+ ) = runSafe {
+ val color = if (useBlockColor) {
+ blockColor(block.cachedState, pos)
+ } else getBlockEntityColor(block) ?: return@runSafe
+ val shape = outlineShape(block.cachedState, pos)
+
+ if (drawFaces) buildFilledMesh(shape, color.setAlpha(alpha), sides)
+ if (drawOutlines) buildOutlineMesh(shape, color, sides, outlineMode)
+ }
+
+ private fun StaticESPRenderer.build(
+ entity: Entity,
+ sides: Int,
+ ) = runSafe {
+ val color = getEntityColor(entity) ?: return@runSafe
+
+ if (drawFaces) buildFilled(entity.boundingBox, color.setAlpha(alpha), sides)
+ if (drawOutlines) buildOutline(entity.boundingBox, color, sides, outlineMode)
+ }
+
+ private fun getBlockEntityColor(block: BlockEntity?) =
+ when (block) {
+ is BarrelBlockEntity -> barrelColor
+ is BlastFurnaceBlockEntity -> blastFurnaceColor
+ is BrewingStandBlockEntity -> brewingStandColor
+ is ChestBlockEntity -> chestColor
+ is DispenserBlockEntity -> dispenserColor
+ is EnderChestBlockEntity -> enderChestColor
+ is FurnaceBlockEntity -> furnaceColor
+ is HopperBlockEntity -> hopperColor
+ is SmokerBlockEntity -> smokerColor
+ is ShulkerBoxBlockEntity -> shulkerColor
+ else -> null
+ }
+
+ private fun getEntityColor(entity: Entity?) =
+ when (entity) {
+ is AbstractMinecartEntity -> cartColor
+ is ItemFrameEntity -> itemFrameColor
+ else -> null
+ }
+
+ private enum class Page {
+ General,
+ Render,
+ Color
+ }
+}
diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt
index 623df5a66..de812386a 100644
--- a/common/src/main/kotlin/com/lambda/util/extension/World.kt
+++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt
@@ -36,10 +36,10 @@ import net.minecraft.world.World
import java.awt.Color
import kotlin.experimental.and
-fun SafeContext.blockFilledMesh(state: BlockState, pos: BlockPos) =
+fun SafeContext.collisionShape(state: BlockState, pos: BlockPos) =
state.getCollisionShape(world, pos).offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble())
-fun SafeContext.blockOutlineMesh(state: BlockState, pos: BlockPos) =
+fun SafeContext.outlineShape(state: BlockState, pos: BlockPos) =
state.getOutlineShape(world, pos).offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble())
fun SafeContext.blockColor(state: BlockState, pos: BlockPos) =
@@ -68,6 +68,7 @@ fun World.getFluidState(x: Int, y: Int, z: Int): FluidState {
}
fun World.getBlockState(vec: FastVector): BlockState = getBlockState(vec.x, vec.y, vec.z)
+fun World.getBlockEntity(vec: FastVector) = getBlockEntity(vec.toBlockPos())
fun World.getFluidState(vec: FastVector): FluidState = getFluidState(vec.x, vec.y, vec.z)
private fun positionFromIndex(width: Int, length: Int, index: Int): FastVector {
diff --git a/common/src/main/kotlin/com/lambda/util/world/WorldDsl.kt b/common/src/main/kotlin/com/lambda/util/world/WorldDsl.kt
index aeb148d44..b37145069 100644
--- a/common/src/main/kotlin/com/lambda/util/world/WorldDsl.kt
+++ b/common/src/main/kotlin/com/lambda/util/world/WorldDsl.kt
@@ -124,7 +124,7 @@ inline fun SafeContext.blockEntitySearch(
range: Double = 64.0,
pos: BlockPos = player.blockPos,
noinline filter: (T) -> Boolean = { true },
-) = internalGetBlockEntities(pos.toFastVec(), range, predicate = filter)
+) = internalGetBlockEntities(pos.toFastVec(), range, predicate = filter).toSet()
@DslMarker
annotation class EntityMarker
@@ -201,7 +201,7 @@ inline fun SafeContext.fastEntitySearch(
range: Double,
pos: BlockPos = player.blockPos,
noinline filter: (T) -> Boolean = { true },
-) = internalGetFastEntities(pos.toFastVec(), range, predicate = filter)
+) = internalGetFastEntities(pos.toFastVec(), range, predicate = filter).toSet()
@DslMarker
annotation class FluidMarker