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