1+ /*
2+ * Copyright 2026 Lambda
3+ *
4+ * This program is free software: you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation, either version 3 of the License, or
7+ * (at your option) any later version.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+ package com.lambda.module.modules.render
19+
20+ import com.lambda.Lambda.mc
21+ import com.lambda.config.applyEdits
22+ import com.lambda.config.groups.WorldLineSettings
23+ import com.lambda.context.SafeContext
24+ import com.lambda.graphics.mc.LineDashStyle
25+ import com.lambda.graphics.mc.RenderBuilder
26+ import com.lambda.graphics.mc.renderer.ChunkedRenderer.Companion.chunkedRenderer
27+ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer
28+ import com.lambda.module.Module
29+ import com.lambda.module.tag.ModuleTag
30+ import com.lambda.threading.runSafe
31+ import com.lambda.util.BlockUtils.blockState
32+ import com.lambda.util.NamedEnum
33+ import com.lambda.util.math.flooredBlockPos
34+ import com.lambda.util.math.setAlpha
35+ import com.lambda.util.math.vec3d
36+ import com.lambda.util.world.toBlockPos
37+ import net.minecraft.block.Blocks
38+ import net.minecraft.block.SnowBlock
39+ import net.minecraft.registry.tag.BlockTags
40+ import net.minecraft.util.math.BlockPos
41+ import net.minecraft.util.math.Direction
42+ import net.minecraft.world.LightType
43+ import java.awt.Color
44+
45+ object LightLevels : Module(
46+ name = " LightLevels" ,
47+ description = " Shows light level. Helpful for mob-proofing areas" ,
48+ tag = ModuleTag .RENDER
49+ ) {
50+ private enum class Group (override val displayName : String ) : NamedEnum {
51+ Fill (" Fill" ),
52+ Line (" Line" )
53+ }
54+
55+ private val mode: Mode by setting(" Mode" , Mode .Chunked )
56+ .onValueChange { _, _ -> chunkedRenderer.clear(); refreshChunkedRenderer(this ) }
57+ private val minLightLevel by setting(" Min Light Level" , 0 , 0 .. 15 ).onValueChange(::refreshChunkedRenderer)
58+ private val renderMode by setting(" Render Mode" , RenderMode .Square ).onValueChange(::refreshChunkedRenderer)
59+ private val color by setting(" Color" , Color .RED ).onValueChange(::refreshChunkedRenderer)
60+ private val size by setting(" Size" , 14 , 1 .. 16 ).onValueChange(::refreshChunkedRenderer)
61+ private val fill by setting(" Fill" , false ) { renderMode == RenderMode .Square }.group(Group .Fill ).onValueChange(::refreshChunkedRenderer)
62+ private val fillAlpha by setting(" Fill Alpha" , 0.2 , 0.0 .. 1.0 , 0.01 ) { renderMode == RenderMode .Square && fill }.group(Group .Fill ).onValueChange(::refreshChunkedRenderer)
63+ private val outline by setting(" Outline" , true ) { renderMode == RenderMode .Square }.group(Group .Line ).onValueChange(::refreshChunkedRenderer)
64+ private val worldLineConfig = WorldLineSettings (c = this , baseGroup = arrayOf(Group .Line )) { renderMode != RenderMode .Square || outline }.apply {
65+ applyEdits {
66+ hide(::startColor, ::endColor)
67+ settings.forEach { it.onValueChange(::refreshChunkedRenderer) }
68+ }
69+ }
70+ private val depthTest by setting(" Depth Test" , false , " Shows renders through terrain" )
71+ private val horizontalRange by setting(" Horizontal Range" , 16 , 1 .. 32 ) { mode == Mode .Radius }
72+ private val verticalRange by setting(" Vertical Range" , 8 , 1 .. 32 ) { mode == Mode .Radius }
73+
74+ private val chunkedRenderer = chunkedRenderer(" LightLevels Chunked Renderer" , { depthTest }, { mode != Mode .Chunked }) { _, pos ->
75+ runSafe { buildRender(pos.toBlockPos(), worldLineConfig.getDashStyle()) }
76+ }
77+
78+ init {
79+ tickedRenderer(" LightLevels Ticked Renderer" , { depthTest }) { safeContext ->
80+ if (mode != Mode .Radius ) return @tickedRenderer
81+ val playerPos = mc.gameRenderer.camera.pos.flooredBlockPos
82+
83+ val dashStyle = worldLineConfig.getDashStyle()
84+ (playerPos.x - horizontalRange.. playerPos.x + horizontalRange).forEach { x ->
85+ (playerPos.z - horizontalRange.. playerPos.z + horizontalRange).forEach { z ->
86+ (playerPos.y - verticalRange.. playerPos.y + verticalRange).forEach { y ->
87+ with (safeContext) { buildRender(BlockPos (x, y, z), dashStyle) }
88+ }
89+ }
90+ }
91+ }
92+ }
93+
94+ context(safeContext: SafeContext )
95+ private fun RenderBuilder.buildRender (pos : BlockPos , dashStyle : LineDashStyle ? ) {
96+ val level = safeContext.world.getLightLevel(LightType .BLOCK , pos)
97+ if (level > minLightLevel || ! safeContext.hasSpawnPotential(pos)) return
98+
99+ val renderVec = pos.vec3d
100+ val trueSize = (16 - size) / 32.0
101+ val corner1 = renderVec.add(trueSize, 0.05 , trueSize)
102+ val corner2 = renderVec.add(1.0 - trueSize, 0.05 , trueSize)
103+ val corner3 = renderVec.add(1.0 - trueSize, 0.05 , 1.0 - trueSize)
104+ val corner4 = renderVec.add(trueSize, 0.05 , 1.0 - trueSize)
105+
106+ when (renderMode) {
107+ RenderMode .Square -> {
108+ if (fill) filledQuad(corner1, corner2, corner3, corner4, color.setAlpha(fillAlpha))
109+ if (outline) polyline(listOf (corner1, corner2, corner3, corner4, corner1), color, worldLineConfig.width, dashStyle)
110+ }
111+ RenderMode .Cross -> {
112+ line(corner1, corner3, color, worldLineConfig.width, dashStyle)
113+ line(corner2, corner4, color, worldLineConfig.width, dashStyle)
114+ }
115+ RenderMode .Circle -> circleLine(renderVec.add(0.5 , 0.0 , 0.5 ), (size / 32.0 ), color, worldLineConfig.width, dashStyle = dashStyle)
116+ }
117+ }
118+
119+ private fun SafeContext.hasSpawnPotential (pos : BlockPos ) =
120+ blockState(pos).let { state ->
121+ (! state.block.collidable || (state.block == = Blocks .SNOW && state.get(SnowBlock .LAYERS ) <= 1 )) &&
122+ ! state.emitsRedstonePower() &&
123+ state.fluidState.isEmpty &&
124+ ! state.isIn(BlockTags .PREVENT_MOB_SPAWNING_INSIDE ) &&
125+ pos.down().let {
126+ val underState = blockState(it)
127+ underState.isSideSolidFullSquare(world, it, Direction .UP ) &&
128+ ! underState.isTransparent &&
129+ underState.block != = Blocks .BEDROCK
130+ }
131+ }
132+
133+ @JvmStatic
134+ fun updateChunk (x : Int , z : Int ) = runSafe {
135+ if (mode == Mode .Chunked ) chunkedRenderer.rebuildChunk(x, z)
136+ }
137+
138+ private fun refreshChunkedRenderer (ctx : SafeContext , from : Any? = null, to : Any? = null) {
139+ if (mode == Mode .Chunked ) chunkedRenderer.rebuild()
140+ }
141+
142+ private enum class Mode {
143+ Chunked ,
144+ Radius
145+ }
146+
147+ private enum class RenderMode {
148+ Square ,
149+ Cross ,
150+ Circle
151+ }
152+ }
0 commit comments