]> git.lizzy.rs Git - LightOverlay.git/blob - fabric/src/main/java/me/shedaniel/lightoverlay/fabric/LightOverlay.java
e6cae4a74b35dabad4ecb1a3883a6efa592db1dc
[LightOverlay.git] / fabric / src / main / java / me / shedaniel / lightoverlay / fabric / LightOverlay.java
1 package me.shedaniel.lightoverlay.fabric;
2
3 import com.google.common.collect.Lists;
4 import com.google.common.collect.Maps;
5 import com.mojang.blaze3d.platform.GlStateManager;
6 import com.mojang.blaze3d.systems.RenderSystem;
7 import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
8 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
9 import me.shedaniel.cloth.api.client.events.v0.ClothClientHooks;
10 import net.fabricmc.api.ClientModInitializer;
11 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
12 import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
13 import net.fabricmc.loader.api.FabricLoader;
14 import net.minecraft.block.Block;
15 import net.minecraft.block.BlockState;
16 import net.minecraft.client.MinecraftClient;
17 import net.minecraft.client.font.TextRenderer;
18 import net.minecraft.client.network.ClientPlayerEntity;
19 import net.minecraft.client.options.KeyBinding;
20 import net.minecraft.client.render.Camera;
21 import net.minecraft.client.render.Frustum;
22 import net.minecraft.client.render.Tessellator;
23 import net.minecraft.client.render.VertexConsumerProvider;
24 import net.minecraft.client.util.InputUtil;
25 import net.minecraft.client.util.math.Rotation3;
26 import net.minecraft.client.world.ClientWorld;
27 import net.minecraft.entity.Entity;
28 import net.minecraft.entity.EntityCategory;
29 import net.minecraft.entity.EntityContext;
30 import net.minecraft.entity.EntityType;
31 import net.minecraft.entity.player.PlayerEntity;
32 import net.minecraft.tag.BlockTags;
33 import net.minecraft.util.Identifier;
34 import net.minecraft.util.math.*;
35 import net.minecraft.util.shape.VoxelShape;
36 import net.minecraft.world.BlockView;
37 import net.minecraft.world.LightType;
38 import net.minecraft.world.World;
39 import net.minecraft.world.biome.SpawnSettings;
40 import net.minecraft.world.chunk.ChunkStatus;
41 import net.minecraft.world.chunk.WorldChunk;
42 import net.minecraft.world.chunk.light.ChunkLightingView;
43 import org.apache.logging.log4j.LogManager;
44 import org.lwjgl.opengl.GL11;
45
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.FileOutputStream;
49 import java.io.IOException;
50 import java.text.DecimalFormat;
51 import java.util.Comparator;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Properties;
55 import java.util.concurrent.Executors;
56 import java.util.concurrent.ThreadPoolExecutor;
57
58 public class LightOverlay implements ClientModInitializer {
59     
60     static final DecimalFormat FORMAT = new DecimalFormat("#.#");
61     private static final String KEYBIND_CATEGORY = "key.lightoverlay.category";
62     private static final Identifier ENABLE_OVERLAY_KEYBIND = new Identifier("lightoverlay", "enable_overlay");
63     static int reach = 12;
64     static int crossLevel = 7;
65     static int secondaryLevel = -1;
66     static int lowerCrossLevel = -1;
67     static int higherCrossLevel = -1;
68     static boolean caching = false;
69     static boolean showNumber = false;
70     static boolean smoothLines = true;
71     static boolean underwater = false;
72     static float lineWidth = 1.0F;
73     static int yellowColor = 0xFFFF00, redColor = 0xFF0000, secondaryColor = 0x0000FF;
74     static File configFile = new File(FabricLoader.getInstance().getConfigDir().toFile(), "lightoverlay.properties");
75     private static final KeyBinding ENABLE_OVERLAY = createKeyBinding(ENABLE_OVERLAY_KEYBIND, InputUtil.Type.KEYSYM, 296, KEYBIND_CATEGORY);
76     private static boolean enabled = false;
77     private static EntityType<Entity> testingEntityType;
78     private static int threadNumber = 0;
79     public static Frustum frustum;
80     private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
81         Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
82         thread.setDaemon(true);
83         return thread;
84     });
85     private static final List<ChunkPos> POS = Lists.newCopyOnWriteArrayList();
86     private static final Map<ChunkPos, Long2ReferenceMap<Object>> CHUNK_MAP = Maps.newConcurrentMap();
87     private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
88     private static long ticks = 0;
89     
90     static {
91         ClientTickEvents.END_CLIENT_TICK.register(client -> {
92             try {
93                 ticks++;
94                 if (CLIENT.player == null || !enabled) {
95                     POS.clear();
96                     CHUNK_MAP.clear();
97                 } else {
98                     if (!caching) {
99                         POS.clear();
100                         CHUNK_MAP.clear();
101                         ClientPlayerEntity player = CLIENT.player;
102                         ClientWorld world = CLIENT.world;
103                         BlockPos playerPos = player.getSenseCenterPos();
104                         EntityContext entityContext = EntityContext.of(player);
105                         ChunkLightingView block = world.getLightingProvider().get(LightType.BLOCK);
106                         ChunkLightingView sky = showNumber ? null : world.getLightingProvider().get(LightType.SKY);
107                         BlockPos.Mutable downPos = new BlockPos.Mutable();
108                         Iterable<BlockPos> iterate = BlockPos.iterate(playerPos.getX() - reach, playerPos.getY() - reach, playerPos.getZ() - reach,
109                                 playerPos.getX() + reach, playerPos.getY() + reach, playerPos.getZ() + reach);
110                         Long2ReferenceMap<Object> map = new Long2ReferenceOpenHashMap<>();
111                         CHUNK_MAP.put(new ChunkPos(0, 0), map);
112                         for (BlockPos blockPos : iterate) {
113                             downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
114                             if (showNumber) {
115                                 int level = getCrossLevel(blockPos, downPos, world, block, entityContext);
116                                 if (level >= 0) {
117                                     map.put(blockPos.asLong(), Integer.valueOf(level));
118                                 }
119                             } else {
120                                 SpawnSettings spawnSettings = world.getBiomeAccess().getBiome(blockPos).getSpawnSettings();
121                                 if (spawnSettings.getCreatureSpawnProbability() > 0 && !spawnSettings.getSpawnEntry(EntityCategory.MONSTER).isEmpty()) {
122                                     CrossType type = getCrossType(blockPos, downPos, world, block, sky, entityContext);
123                                     if (type != CrossType.NONE) {
124                                         map.put(blockPos.asLong(), type);
125                                     }
126                                 }
127                             }
128                         }
129                     } else {
130                         ClientPlayerEntity player = CLIENT.player;
131                         ClientWorld world = CLIENT.world;
132                         EntityContext entityContext = EntityContext.of(player);
133                         Vec3d[] playerPos = {null};
134                         int playerPosX = ((int) player.getX()) >> 4;
135                         int playerPosZ = ((int) player.getZ()) >> 4;
136                         if (ticks % 20 == 0) {
137                             for (int chunkX = playerPosX - getChunkRange(); chunkX <= playerPosX + getChunkRange(); chunkX++) {
138                                 for (int chunkZ = playerPosZ - getChunkRange(); chunkZ <= playerPosZ + getChunkRange(); chunkZ++) {
139                                     ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
140                                     if (!CHUNK_MAP.containsKey(chunkPos))
141                                         queueChunk(chunkPos);
142                                 }
143                             }
144                         }
145                         POS.removeIf(pos -> MathHelper.abs(pos.x - playerPosX) > getChunkRange() || MathHelper.abs(pos.z - playerPosZ) > getChunkRange());
146                         for (int k = 0; k < 2; k++) {
147                             if (!POS.isEmpty()) {
148                                 if (playerPos[0] == null) {
149                                     playerPos[0] = player.getPos();
150                                 }
151                                 ChunkPos pos = POS.stream().min(Comparator.comparingDouble(value -> {
152                                     int i = Math.abs(value.x - playerPosX);
153                                     int j = Math.abs(value.z - playerPosZ);
154                                     return i * i + j * j;
155                                 })).get();
156                                 POS.remove(pos);
157                                 EXECUTOR.submit(() -> {
158                                     try {
159                                         calculateChunk(world.getChunkManager().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), world, pos, entityContext);
160                                     } catch (Throwable throwable) {
161                                         LogManager.getLogger().throwing(throwable);
162                                     }
163                                 });
164                             }
165                         }
166                         if (ticks % 50 == 0) {
167                             CHUNK_MAP.entrySet().removeIf(pos -> MathHelper.abs(pos.getKey().x - playerPosX) > getChunkRange() * 2 || MathHelper.abs(pos.getKey().z - playerPosZ) > getChunkRange() * 2);
168                         }
169                     }
170                 }
171             } catch (Throwable throwable) {
172                 LogManager.getLogger().throwing(throwable);
173             }
174         });
175     }
176     
177     public static void queueChunkAndNear(ChunkPos pos) {
178         for (int xOffset = -1; xOffset <= 1; xOffset++) {
179             for (int zOffset = -1; zOffset <= 1; zOffset++) {
180                 queueChunk(new ChunkPos(pos.x + xOffset, pos.z + zOffset));
181             }
182         }
183     }
184     
185     public static void queueChunk(ChunkPos pos) {
186         if (caching)
187             if (!POS.contains(pos))
188                 POS.add(0, pos);
189     }
190     
191     public static int getChunkRange() {
192         return Math.max(MathHelper.ceil(reach / 16f), 1);
193     }
194     
195     private static void calculateChunk(WorldChunk chunk, World world, ChunkPos chunkPos, EntityContext entityContext) {
196         if (world != null && chunk != null) {
197             Long2ReferenceMap<Object> map = new Long2ReferenceOpenHashMap<>();
198             ChunkLightingView block = world.getLightingProvider().get(LightType.BLOCK);
199             ChunkLightingView sky = showNumber ? null : world.getLightingProvider().get(LightType.SKY);
200             for (BlockPos pos : BlockPos.iterate(chunkPos.getStartX(), 0, chunkPos.getStartZ(), chunkPos.getEndX(), 256, chunkPos.getEndZ())) {
201                 BlockPos down = pos.down();
202                 if (showNumber) {
203                     int level = LightOverlay.getCrossLevel(pos, down, chunk, block, entityContext);
204                     if (level >= 0) {
205                         map.put(pos.asLong(), Integer.valueOf(level));
206                     }
207                 } else {
208                     SpawnSettings spawnSettings = world.getBiomeAccess().getBiome(pos).getSpawnSettings();
209                     if (spawnSettings.getCreatureSpawnProbability() > 0 && !spawnSettings.getSpawnEntry(EntityCategory.MONSTER).isEmpty()) {
210                         CrossType type = LightOverlay.getCrossType(pos, down, chunk, block, sky, entityContext);
211                         if (type != CrossType.NONE) {
212                             map.put(pos.asLong(), type);
213                         }
214                     }
215                 }
216             }
217             CHUNK_MAP.put(chunkPos, map);
218         } else {
219             CHUNK_MAP.remove(chunkPos);
220         }
221     }
222     
223     public static CrossType getCrossType(BlockPos pos, BlockPos down, BlockView world, ChunkLightingView block, ChunkLightingView sky, EntityContext entityContext) {
224         BlockState blockBelowState = world.getBlockState(down);
225         BlockState blockUpperState = world.getBlockState(pos);
226         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
227         if (!underwater && !blockUpperState.getFluidState().isEmpty())
228             return CrossType.NONE;
229         // Check if the outline is full
230         if (Block.isFaceFullSquare(upperCollisionShape, Direction.UP))
231             return CrossType.NONE;
232         // TODO: Not to hard code no redstone
233         if (blockUpperState.emitsRedstonePower())
234             return CrossType.NONE;
235         // Check if the collision has a bump
236         if (upperCollisionShape.getMaximum(Direction.Axis.Y) > 0)
237             return CrossType.NONE;
238         if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
239             return CrossType.NONE;
240         // Check block state allow spawning (excludes bedrock and barriers automatically)
241         if (!blockBelowState.allowsSpawning(world, down, testingEntityType))
242             return CrossType.NONE;
243         int blockLightLevel = block.getLightLevel(pos);
244         int skyLightLevel = sky.getLightLevel(pos);
245         if (blockLightLevel > higherCrossLevel)
246             return CrossType.NONE;
247         if (skyLightLevel > higherCrossLevel)
248             return CrossType.YELLOW;
249         return lowerCrossLevel >= 0 && blockLightLevel > lowerCrossLevel ? CrossType.SECONDARY : CrossType.RED;
250     }
251     
252     public static int getCrossLevel(BlockPos pos, BlockPos down, BlockView world, ChunkLightingView view, EntityContext entityContext) {
253         BlockState blockBelowState = world.getBlockState(down);
254         BlockState blockUpperState = world.getBlockState(pos);
255         VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, entityContext);
256         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
257         if (!underwater && !blockUpperState.getFluidState().isEmpty())
258             return -1;
259         if (!blockBelowState.getFluidState().isEmpty())
260             return -1;
261         if (blockBelowState.isAir())
262             return -1;
263         if (Block.isFaceFullSquare(upperCollisionShape, Direction.DOWN))
264             return -1;
265         return view.getLightLevel(pos);
266     }
267     
268     public static void renderCross(Camera camera, World world, BlockPos pos, int color, EntityContext entityContext) {
269         double d0 = camera.getPos().x;
270         double d1 = camera.getPos().y - .005D;
271         VoxelShape upperOutlineShape = world.getBlockState(pos).getOutlineShape(world, pos, entityContext);
272         if (!upperOutlineShape.isEmpty())
273             d1 -= upperOutlineShape.getMaximum(Direction.Axis.Y);
274         double d2 = camera.getPos().z;
275         
276         int red = (color >> 16) & 255;
277         int green = (color >> 8) & 255;
278         int blue = color & 255;
279         int x = pos.getX();
280         int y = pos.getY();
281         int z = pos.getZ();
282         RenderSystem.color4f(red / 255f, green / 255f, blue / 255f, 1f);
283         GL11.glVertex3d(x + .01 - d0, y - d1, z + .01 - d2);
284         GL11.glVertex3d(x - .01 + 1 - d0, y - d1, z - .01 + 1 - d2);
285         GL11.glVertex3d(x - .01 + 1 - d0, y - d1, z + .01 - d2);
286         GL11.glVertex3d(x + .01 - d0, y - d1, z - .01 + 1 - d2);
287     }
288     
289     @SuppressWarnings("deprecation")
290     public static void renderLevel(MinecraftClient client, Camera camera, World world, BlockPos pos, BlockPos down, int level, EntityContext entityContext) {
291         String text = String.valueOf(level);
292         TextRenderer textRenderer_1 = client.textRenderer;
293         double double_4 = camera.getPos().x;
294         double double_5 = camera.getPos().y;
295         VoxelShape upperOutlineShape = world.getBlockState(down).getOutlineShape(world, down, entityContext);
296         if (!upperOutlineShape.isEmpty())
297             double_5 += 1 - upperOutlineShape.getMaximum(Direction.Axis.Y);
298         double double_6 = camera.getPos().z;
299         RenderSystem.pushMatrix();
300         RenderSystem.translatef((float) (pos.getX() + 0.5f - double_4), (float) (pos.getY() - double_5) + 0.005f, (float) (pos.getZ() + 0.5f - double_6));
301         RenderSystem.rotatef(90, 1, 0, 0);
302         RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
303         float size = 0.07F;
304         RenderSystem.scalef(-size, -size, size);
305         float float_3 = (float) (-textRenderer_1.getStringWidth(text)) / 2.0F + 0.4f;
306         RenderSystem.enableAlphaTest();
307         VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
308         textRenderer_1.draw(text, float_3, -3.5f, level > higherCrossLevel ? 0xff042404 : (lowerCrossLevel >= 0 && level > lowerCrossLevel ? 0xff0066ff : 0xff731111), false, Rotation3.identity().getMatrix(), immediate, false, 0, 15728880);
309         immediate.draw();
310         RenderSystem.popMatrix();
311     }
312     
313     static void loadConfig(File file) {
314         try {
315             redColor = 0xFF0000;
316             yellowColor = 0xFFFF00;
317             secondaryColor = 0x0000FF;
318             if (!file.exists() || !file.canRead())
319                 saveConfig(file);
320             FileInputStream fis = new FileInputStream(file);
321             Properties properties = new Properties();
322             properties.load(fis);
323             fis.close();
324             reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "12"));
325             crossLevel = Integer.parseInt((String) properties.computeIfAbsent("crossLevel", a -> "7"));
326             secondaryLevel = Integer.parseInt((String) properties.computeIfAbsent("secondaryLevel", a -> "-1"));
327             caching = ((String) properties.computeIfAbsent("caching", a -> "false")).equalsIgnoreCase("true");
328             showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
329             smoothLines = ((String) properties.computeIfAbsent("smoothLines", a -> "true")).equalsIgnoreCase("true");
330             underwater = ((String) properties.computeIfAbsent("underwater", a -> "false")).equalsIgnoreCase("true");
331             lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
332             {
333                 int r, g, b;
334                 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
335                 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
336                 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
337                 yellowColor = (r << 16) + (g << 8) + b;
338             }
339             {
340                 int r, g, b;
341                 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
342                 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
343                 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
344                 redColor = (r << 16) + (g << 8) + b;
345             }
346             {
347                 int r, g, b;
348                 r = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorRed", a -> "0"));
349                 g = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorGreen", a -> "0"));
350                 b = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorBlue", a -> "255"));
351                 secondaryColor = (r << 16) + (g << 8) + b;
352             }
353             saveConfig(file);
354         } catch (Exception e) {
355             e.printStackTrace();
356             reach = 12;
357             crossLevel = 7;
358             secondaryLevel = -1;
359             lineWidth = 1.0F;
360             redColor = 0xFF0000;
361             yellowColor = 0xFFFF00;
362             secondaryColor = 0x0000FF;
363             caching = false;
364             showNumber = false;
365             smoothLines = true;
366             underwater = false;
367             try {
368                 saveConfig(file);
369             } catch (IOException ex) {
370                 ex.printStackTrace();
371             }
372         }
373         if (secondaryLevel >= crossLevel) System.err.println("[Light Overlay] Secondary Level is higher than Cross Level");
374         lowerCrossLevel = Math.min(crossLevel, secondaryLevel);
375         higherCrossLevel = Math.max(crossLevel, secondaryLevel);
376         CHUNK_MAP.clear();
377         POS.clear();
378     }
379     
380     static void saveConfig(File file) throws IOException {
381         FileOutputStream fos = new FileOutputStream(file, false);
382         fos.write("# Light Overlay Config".getBytes());
383         fos.write("\n".getBytes());
384         fos.write(("reach=" + reach).getBytes());
385         fos.write("\n".getBytes());
386         fos.write(("crossLevel=" + crossLevel).getBytes());
387         fos.write("\n".getBytes());
388         fos.write(("secondaryLevel=" + secondaryLevel).getBytes());
389         fos.write("\n".getBytes());
390         fos.write(("caching=" + caching).getBytes());
391         fos.write("\n".getBytes());
392         fos.write(("showNumber=" + showNumber).getBytes());
393         fos.write("\n".getBytes());
394         fos.write(("smoothLines=" + smoothLines).getBytes());
395         fos.write("\n".getBytes());
396         fos.write(("underwater=" + underwater).getBytes());
397         fos.write("\n".getBytes());
398         fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
399         fos.write("\n".getBytes());
400         fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
401         fos.write("\n".getBytes());
402         fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
403         fos.write("\n".getBytes());
404         fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
405         fos.write("\n".getBytes());
406         fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
407         fos.write("\n".getBytes());
408         fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
409         fos.write("\n".getBytes());
410         fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
411         fos.write("\n".getBytes());
412         fos.write(("secondaryColorRed=" + ((secondaryColor >> 16) & 255)).getBytes());
413         fos.write("\n".getBytes());
414         fos.write(("secondaryColorGreen=" + ((secondaryColor >> 8) & 255)).getBytes());
415         fos.write("\n".getBytes());
416         fos.write(("secondaryColorBlue=" + (secondaryColor & 255)).getBytes());
417         fos.close();
418     }
419     
420     private static KeyBinding createKeyBinding(Identifier id, InputUtil.Type type, int code, String category) {
421         return KeyBindingHelper.registerKeyBinding(new KeyBinding("key." + id.getNamespace() + "." + id.getPath(), type, code, category));
422     }
423     
424     @Override
425     public void onInitializeClient() {
426         // Load Config
427         loadConfig(configFile);
428         
429         // Setup
430         testingEntityType = EntityType.Builder.create(EntityCategory.MONSTER).setDimensions(0f, 0f).disableSaving().build(null);
431         ClientTickEvents.END_CLIENT_TICK.register(minecraftClient -> {
432             while (ENABLE_OVERLAY.wasPressed())
433                 enabled = !enabled;
434         });
435         ClothClientHooks.DEBUG_RENDER_PRE.register(() -> {
436             if (LightOverlay.enabled) {
437                 PlayerEntity playerEntity = CLIENT.player;
438                 int playerPosX = ((int) playerEntity.getX()) >> 4;
439                 int playerPosZ = ((int) playerEntity.getZ()) >> 4;
440                 EntityContext entityContext = EntityContext.of(playerEntity);
441                 World world = CLIENT.world;
442                 BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
443                 Camera camera = CLIENT.gameRenderer.getCamera();
444                 if (showNumber) {
445                     RenderSystem.enableTexture();
446                     RenderSystem.depthMask(true);
447                     BlockPos.Mutable mutable = new BlockPos.Mutable();
448                     BlockPos.Mutable downMutable = new BlockPos.Mutable();
449                     for (Map.Entry<ChunkPos, Long2ReferenceMap<Object>> entry : CHUNK_MAP.entrySet()) {
450                         if (caching && (MathHelper.abs(entry.getKey().x - playerPosX) > getChunkRange() || MathHelper.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
451                             continue;
452                         }
453                         for (Long2ReferenceMap.Entry<Object> objectEntry : entry.getValue().long2ReferenceEntrySet()) {
454                             if (objectEntry.getValue() instanceof Integer) {
455                                 mutable.set(BlockPos.unpackLongX(objectEntry.getLongKey()), BlockPos.unpackLongY(objectEntry.getLongKey()), BlockPos.unpackLongZ(objectEntry.getLongKey()));
456                                 if (mutable.isWithinDistance(playerPos, reach)) {
457                                     if (frustum == null || FrustumHelper.isVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
458                                         downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
459                                         LightOverlay.renderLevel(CLIENT, camera, world, mutable, downMutable, (Integer) objectEntry.getValue(), entityContext);
460                                     }
461                                 }
462                             }
463                         }
464                     }
465                     RenderSystem.enableDepthTest();
466                 } else {
467                     RenderSystem.enableDepthTest();
468                     RenderSystem.disableTexture();
469                     RenderSystem.enableBlend();
470                     RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
471                     if (smoothLines) GL11.glEnable(GL11.GL_LINE_SMOOTH);
472                     GL11.glLineWidth(lineWidth);
473                     GL11.glBegin(GL11.GL_LINES);
474                     BlockPos.Mutable mutable = new BlockPos.Mutable();
475                     for (Map.Entry<ChunkPos, Long2ReferenceMap<Object>> entry : CHUNK_MAP.entrySet()) {
476                         if (caching && (MathHelper.abs(entry.getKey().x - playerPosX) > getChunkRange() || MathHelper.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
477                             continue;
478                         }
479                         for (Long2ReferenceMap.Entry<Object> objectEntry : entry.getValue().long2ReferenceEntrySet()) {
480                             if (objectEntry.getValue() instanceof CrossType) {
481                                 mutable.set(BlockPos.unpackLongX(objectEntry.getLongKey()), BlockPos.unpackLongY(objectEntry.getLongKey()), BlockPos.unpackLongZ(objectEntry.getLongKey()));
482                                 if (mutable.isWithinDistance(playerPos, reach)) {
483                                     if (frustum == null || FrustumHelper.isVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
484                                         int color = objectEntry.getValue() == CrossType.RED ? redColor : objectEntry.getValue() == CrossType.YELLOW ? yellowColor : secondaryColor;
485                                         LightOverlay.renderCross(camera, world, mutable, color, entityContext);
486                                     }
487                                 }
488                             }
489                         }
490                     }
491                     GL11.glEnd();
492                     RenderSystem.disableBlend();
493                     RenderSystem.enableTexture();
494                     if (smoothLines) GL11.glDisable(GL11.GL_LINE_SMOOTH);
495                 }
496             }
497         });
498     }
499     
500     private enum CrossType {
501         YELLOW,
502         RED,
503         SECONDARY,
504         NONE
505     }
506 }