]> git.lizzy.rs Git - LightOverlay.git/blob - src/main/java/me/shedaniel/lightoverlay/LightOverlayClient.java
acd825593e438ba7efb934b55096b884e4c6407d
[LightOverlay.git] / src / main / java / me / shedaniel / lightoverlay / LightOverlayClient.java
1 package me.shedaniel.lightoverlay;
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 net.minecraft.block.Block;
8 import net.minecraft.block.BlockState;
9 import net.minecraft.client.Minecraft;
10 import net.minecraft.client.entity.player.ClientPlayerEntity;
11 import net.minecraft.client.gui.FontRenderer;
12 import net.minecraft.client.renderer.*;
13 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
14 import net.minecraft.client.settings.KeyBinding;
15 import net.minecraft.client.util.InputMappings;
16 import net.minecraft.client.world.ClientWorld;
17 import net.minecraft.entity.Entity;
18 import net.minecraft.entity.EntityClassification;
19 import net.minecraft.entity.EntityType;
20 import net.minecraft.network.IPacket;
21 import net.minecraft.network.play.server.SChangeBlockPacket;
22 import net.minecraft.network.play.server.SChunkDataPacket;
23 import net.minecraft.network.play.server.SMultiBlockChangePacket;
24 import net.minecraft.tags.BlockTags;
25 import net.minecraft.util.Direction;
26 import net.minecraft.util.ResourceLocation;
27 import net.minecraft.util.math.BlockPos;
28 import net.minecraft.util.math.ChunkPos;
29 import net.minecraft.util.math.MathHelper;
30 import net.minecraft.util.math.Vec3d;
31 import net.minecraft.util.math.shapes.ISelectionContext;
32 import net.minecraft.util.math.shapes.VoxelShape;
33 import net.minecraft.util.text.TranslationTextComponent;
34 import net.minecraft.world.IBlockReader;
35 import net.minecraft.world.LightType;
36 import net.minecraft.world.World;
37 import net.minecraft.world.biome.Biome;
38 import net.minecraft.world.chunk.Chunk;
39 import net.minecraft.world.chunk.ChunkStatus;
40 import net.minecraft.world.lighting.IWorldLightListener;
41 import net.minecraftforge.api.distmarker.Dist;
42 import net.minecraftforge.client.event.InputEvent;
43 import net.minecraftforge.client.event.RenderWorldLastEvent;
44 import net.minecraftforge.client.settings.KeyConflictContext;
45 import net.minecraftforge.client.settings.KeyModifier;
46 import net.minecraftforge.common.MinecraftForge;
47 import net.minecraftforge.event.TickEvent;
48 import net.minecraftforge.eventbus.api.SubscribeEvent;
49 import net.minecraftforge.fml.DistExecutor;
50 import net.minecraftforge.fml.client.registry.ClientRegistry;
51 import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
52 import org.apache.logging.log4j.LogManager;
53 import org.lwjgl.opengl.GL11;
54
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.text.DecimalFormat;
60 import java.util.*;
61 import java.util.concurrent.ExecutorService;
62 import java.util.concurrent.Executors;
63
64 public class LightOverlayClient {
65     
66     static final DecimalFormat FORMAT = new DecimalFormat("#.#");
67     private static final String KEYBIND_CATEGORY = "key.lightoverlay-forge.category";
68     private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay-forge", "enable_overlay");
69     private static final ResourceLocation INCREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_reach");
70     private static final ResourceLocation DECREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_reach");
71     private static final ResourceLocation INCREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_line_width");
72     private static final ResourceLocation DECREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_line_width");
73     static int reach = 12;
74     static int crossLevel = 7;
75     static boolean showNumber = false;
76     static boolean smoothLines = true;
77     static boolean underwater = false;
78     static EntityType<Entity> testingEntityType;
79     static float lineWidth = 1.0F;
80     static int yellowColor = 0xFFFF00, redColor = 0xFF0000;
81     static File configFile = new File(new File(Minecraft.getInstance().gameDir, "config"), "lightoverlay.properties");
82     private static KeyBinding enableOverlay, increaseReach, decreaseReach, increaseLineWidth, decreaseLineWidth;
83     private static boolean enabled = false;
84     private static int threadNumber = 0;
85     private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
86         Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
87         thread.setDaemon(true);
88         return thread;
89     });
90     private static final List<ChunkPos> POS = Lists.newCopyOnWriteArrayList();
91     private static final Map<ChunkPos, Map<Long, Object>> CHUNK_MAP = Maps.newConcurrentMap();
92     private static long ticks = 0;
93     
94     public static void register() {
95         // Load Config
96         loadConfig(configFile);
97         
98         // Setup
99         testingEntityType = EntityType.Builder.create(EntityClassification.MONSTER).size(0f, 0f).disableSerialization().build(null);
100         enableOverlay = registerKeybind(ENABLE_OVERLAY_KEYBIND, InputMappings.Type.KEYSYM, 296, KEYBIND_CATEGORY);
101         increaseReach = registerKeybind(INCREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
102         decreaseReach = registerKeybind(DECREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
103         increaseLineWidth = registerKeybind(INCREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
104         decreaseLineWidth = registerKeybind(DECREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
105         MinecraftForge.EVENT_BUS.register(LightOverlayClient.class);
106         
107         try {
108             //noinspection Convert2MethodRef
109             DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> LightOverlayCloth.register());
110         } catch (Exception e) {
111             e.printStackTrace();
112         }
113     }
114     
115     public static CrossType getCrossType(BlockPos pos, BlockPos down, IBlockReader reader, IWorldLightListener block, IWorldLightListener sky, ISelectionContext selectionContext) {
116         BlockState blockBelowState = reader.getBlockState(down);
117         BlockState blockUpperState = reader.getBlockState(pos);
118         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(reader, pos, selectionContext);
119         if (!underwater && !blockUpperState.getFluidState().isEmpty())
120             return CrossType.NONE;
121         /* WorldEntitySpawner.func_222266_a */
122         // Check if the outline is full
123         if (Block.doesSideFillSquare(upperCollisionShape, Direction.UP))
124             return CrossType.NONE;
125         // Check if there is power
126         if (blockUpperState.canProvidePower())
127             return CrossType.NONE;
128         // Check if the collision has a bump
129         if (upperCollisionShape.getEnd(Direction.Axis.Y) > 0)
130             return CrossType.NONE;
131         if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
132             return CrossType.NONE;
133         // Check block state allow spawning (excludes bedrock and barriers automatically)
134         if (!blockBelowState.canEntitySpawn(reader, down, testingEntityType))
135             return CrossType.NONE;
136         if (block.getLightFor(pos) > crossLevel)
137             return CrossType.NONE;
138         if (sky.getLightFor(pos) > crossLevel)
139             return CrossType.YELLOW;
140         return CrossType.RED;
141     }
142     
143     public static int getCrossLevel(BlockPos pos, BlockPos down, IBlockReader reader, IWorldLightListener light, ISelectionContext context) {
144         BlockState blockBelowState = reader.getBlockState(down);
145         BlockState blockUpperState = reader.getBlockState(pos);
146         VoxelShape collisionShape = blockBelowState.getCollisionShape(reader, down, context);
147         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(reader, pos, context);
148         if (!underwater && !blockUpperState.getFluidState().isEmpty())
149             return -1;
150         if (!blockBelowState.getFluidState().isEmpty())
151             return -1;
152         if (blockBelowState.isAir(reader, down))
153             return -1;
154         if (Block.doesSideFillSquare(upperCollisionShape, Direction.DOWN))
155             return -1;
156         return light.getLightFor(pos);
157     }
158     
159     public static void renderCross(ActiveRenderInfo info, Tessellator tessellator, BufferBuilder buffer, World world, BlockPos pos, int color, ISelectionContext context) {
160         double d0 = info.getProjectedView().x;
161         double d1 = info.getProjectedView().y - .005D;
162         VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, context);
163         if (!upperOutlineShape.isEmpty())
164             d1 -= upperOutlineShape.getEnd(Direction.Axis.Y);
165         double d2 = info.getProjectedView().z;
166         buffer.begin(1, DefaultVertexFormats.POSITION_COLOR);
167         int red = (color >> 16) & 255;
168         int green = (color >> 8) & 255;
169         int blue = color & 255;
170         buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
171         buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
172         buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
173         buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
174         tessellator.draw();
175     }
176     
177     public static void renderLevel(Minecraft minecraft, ActiveRenderInfo info, World world, BlockPos pos, BlockPos down, int level, ISelectionContext context) {
178         String string_1 = String.valueOf(level);
179         FontRenderer fontRenderer = minecraft.fontRenderer;
180         double double_4 = info.getProjectedView().x;
181         double double_5 = info.getProjectedView().y;
182         VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, context);
183         if (!upperOutlineShape.isEmpty())
184             double_5 += 1 - upperOutlineShape.getEnd(Direction.Axis.Y);
185         double double_6 = info.getProjectedView().z;
186         RenderSystem.pushMatrix();
187         RenderSystem.translatef((float) (pos.getX() + 0.5f - double_4), (float) (pos.getY() - double_5) + 0.005f, (float) (pos.getZ() + 0.5f - double_6));
188         RenderSystem.rotatef(90, 1, 0, 0);
189         RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
190         float size = 0.07F;
191         RenderSystem.scalef(-size, -size, size);
192         float float_3 = (float) (-fontRenderer.getStringWidth(string_1)) / 2.0F + 0.4f;
193         RenderSystem.enableAlphaTest();
194         IRenderTypeBuffer.Impl vertexConsumerProvider$Immediate_1 = IRenderTypeBuffer.getImpl(Tessellator.getInstance().getBuffer());
195         fontRenderer.renderString(string_1, float_3, -3.5f, level > crossLevel ? 0xff042404 : 0xff731111, false, TransformationMatrix.identity().getMatrix(), vertexConsumerProvider$Immediate_1, false, 0, 15728880);
196         vertexConsumerProvider$Immediate_1.finish();
197         RenderSystem.popMatrix();
198     }
199     
200     @SubscribeEvent(receiveCanceled = true)
201     public static void handleInput(InputEvent.KeyInputEvent event) {
202         if (enableOverlay.isPressed())
203             enabled = !enabled;
204         if (increaseReach.isPressed()) {
205             if (reach < 64)
206                 reach++;
207             try {
208                 saveConfig(configFile);
209             } catch (IOException e) {
210                 e.printStackTrace();
211             }
212             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
213         }
214         if (decreaseReach.isPressed()) {
215             if (reach > 1)
216                 reach--;
217             try {
218                 saveConfig(configFile);
219             } catch (IOException e) {
220                 e.printStackTrace();
221             }
222             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
223         }
224         if (increaseLineWidth.isPressed()) {
225             if (lineWidth < 7)
226                 lineWidth += 0.1f;
227             try {
228                 saveConfig(configFile);
229             } catch (IOException e) {
230                 e.printStackTrace();
231             }
232             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
233         }
234         if (decreaseLineWidth.isPressed()) {
235             if (lineWidth > 1)
236                 lineWidth -= 0.1F;
237             try {
238                 saveConfig(configFile);
239             } catch (IOException e) {
240                 e.printStackTrace();
241             }
242             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
243         }
244     }
245     
246     public static void queueChunkAndNear(ChunkPos pos) {
247         for (int xOffset = -1; xOffset <= 1; xOffset++) {
248             for (int zOffset = -1; zOffset <= 1; zOffset++) {
249                 queueChunk(new ChunkPos(pos.x + xOffset, pos.z + zOffset));
250             }
251         }
252     }
253     
254     public static void queueChunk(ChunkPos pos) {
255         if (!POS.contains(pos))
256             POS.add(0, pos);
257     }
258     
259     public static int getChunkRange() {
260         return Math.max(MathHelper.ceil(reach / 16f), 1);
261     }
262     
263     @SubscribeEvent
264     public static void tick(TickEvent.ClientTickEvent event) {
265         if (event.phase == TickEvent.Phase.END) {
266             try {
267                 Minecraft minecraft = Minecraft.getInstance();
268                 ticks++;
269                 if (minecraft.player == null || !enabled) {
270                     POS.clear();
271                     CHUNK_MAP.clear();
272                 } else {
273                     ClientPlayerEntity player = minecraft.player;
274                     ClientWorld world = minecraft.world;
275                     ISelectionContext selectionContext = ISelectionContext.forEntity(player);
276                     Vec3d[] playerPos = {null};
277                     int playerPosX = ((int) player.getPosX()) >> 4;
278                     int playerPosZ = ((int) player.getPosZ()) >> 4;
279                     if (ticks % 20 == 0) {
280                         for (int chunkX = playerPosX - getChunkRange(); chunkX <= playerPosX + getChunkRange(); chunkX++) {
281                             for (int chunkZ = playerPosZ - getChunkRange(); chunkZ <= playerPosZ + getChunkRange(); chunkZ++) {
282                                 ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
283                                 if (!CHUNK_MAP.containsKey(chunkPos))
284                                     queueChunk(chunkPos);
285                             }
286                         }
287                     }
288                     if (!POS.isEmpty()) {
289                         if (playerPos[0] == null) {
290                             playerPos[0] = player.getPositionVec();
291                         }
292                         ChunkPos pos = POS.stream().min(Comparator.comparingDouble(value -> value.getBlock(8, 0, 8).distanceSq(playerPos[0].x, 0, playerPos[0].z, false))).get();
293                         EXECUTOR.submit(() -> {
294                             if (MathHelper.abs(pos.x - playerPosX) <= getChunkRange() && MathHelper.abs(pos.z - playerPosZ) <= getChunkRange()) {
295                                 calculateChunk(world.getChunkProvider().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), world, pos, selectionContext);
296                             } else {
297                                 CHUNK_MAP.remove(pos);
298                             }
299                         });
300                         POS.remove(pos);
301                     }
302                     Iterator<Map.Entry<ChunkPos, Map<Long, Object>>> chunkMapIterator = CHUNK_MAP.entrySet().iterator();
303                     while (chunkMapIterator.hasNext()) {
304                         Map.Entry<ChunkPos, Map<Long, Object>> pos = chunkMapIterator.next();
305                         if (MathHelper.abs(pos.getKey().x - playerPosX) > getChunkRange() * 2 || MathHelper.abs(pos.getKey().z - playerPosZ) > getChunkRange() * 2) {
306                             chunkMapIterator.remove();
307                         }
308                     }
309                 }
310             } catch (Exception e) {
311                 LogManager.getLogger().throwing(e);
312             }
313         }
314     }
315     
316     private static void calculateChunk(Chunk chunk, World world, ChunkPos chunkPos, ISelectionContext selectionContext) {
317         Map<Long, Object> map = Maps.newHashMap();
318         if (chunk != null) {
319             IWorldLightListener block = chunk.getWorldLightManager().getLightEngine(LightType.BLOCK);
320             IWorldLightListener sky = showNumber ? null : chunk.getWorldLightManager().getLightEngine(LightType.SKY);
321             for (BlockPos pos : BlockPos.getAllInBoxMutable(chunkPos.getXStart(), 0, chunkPos.getZStart(), chunkPos.getXEnd(), 256, chunkPos.getZEnd())) {
322                 BlockPos down = pos.down();
323                 if (showNumber) {
324                     int level = LightOverlayClient.getCrossLevel(pos, down, chunk, block, selectionContext);
325                     if (level >= 0) {
326                         map.put(pos.toLong(), level);
327                     }
328                 } else {
329                     Biome biome = world.getBiomeManager().getBiome(pos);
330                     if (biome.getSpawningChance() > 0 && !biome.getSpawns(EntityClassification.MONSTER).isEmpty()) {
331                         CrossType type = LightOverlayClient.getCrossType(pos, down, chunk, block, sky, selectionContext);
332                         if (type != CrossType.NONE) {
333                             map.put(pos.toLong(), type);
334                         }
335                     }
336                 }
337             }
338         }
339         CHUNK_MAP.put(chunkPos, map);
340     }
341     
342     public static void renderWorldLast() {
343         if (LightOverlayClient.enabled) {
344             RenderSystem.pushMatrix();
345             Minecraft client = Minecraft.getInstance();
346             ClientPlayerEntity playerEntity = client.player;
347             int playerPosX = ((int) playerEntity.getPosX()) >> 4;
348             int playerPosZ = ((int) playerEntity.getPosZ()) >> 4;
349             ISelectionContext selectionContext = ISelectionContext.forEntity(playerEntity);
350             World world = client.world;
351             BlockPos playerPos = playerEntity.getPosition();
352             ActiveRenderInfo info = client.gameRenderer.getActiveRenderInfo();
353             if (showNumber) {
354                 RenderSystem.enableTexture();
355                 RenderSystem.depthMask(true);
356                 BlockPos.Mutable mutable = new BlockPos.Mutable();
357                 for (Map.Entry<ChunkPos, Map<Long, Object>> entry : CHUNK_MAP.entrySet()) {
358                     if (MathHelper.abs(entry.getKey().x - playerPosX) > getChunkRange() || MathHelper.abs(entry.getKey().z - playerPosZ) > getChunkRange()) {
359                         continue;
360                     }
361                     for (Map.Entry<Long, Object> objectEntry : entry.getValue().entrySet()) {
362                         if (objectEntry.getValue() instanceof Integer) {
363                             mutable.setPos(BlockPos.unpackX(objectEntry.getKey()), BlockPos.unpackY(objectEntry.getKey()), BlockPos.unpackZ(objectEntry.getKey()));
364                             if (mutable.withinDistance(playerPos, reach)) {
365                                 BlockPos down = mutable.down();
366                                 LightOverlayClient.renderLevel(client, info, world, mutable, down, (Integer) objectEntry.getValue(), selectionContext);
367                             }
368                         }
369                     }
370                 }
371                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
372                 RenderSystem.enableDepthTest();
373             } else {
374                 RenderSystem.enableDepthTest();
375                 RenderSystem.shadeModel(7425);
376                 RenderSystem.enableAlphaTest();
377                 RenderSystem.defaultAlphaFunc();
378                 RenderSystem.disableTexture();
379                 RenderSystem.disableBlend();
380                 if (smoothLines) GL11.glEnable(GL11.GL_LINE_SMOOTH);
381                 RenderSystem.lineWidth(lineWidth);
382                 Tessellator tessellator = Tessellator.getInstance();
383                 BufferBuilder buffer = tessellator.getBuffer();
384                 BlockPos.Mutable mutable = new BlockPos.Mutable();
385                 for (Map.Entry<ChunkPos, Map<Long, Object>> entry : CHUNK_MAP.entrySet()) {
386                     if (MathHelper.abs(entry.getKey().x - playerPosX) > getChunkRange() || MathHelper.abs(entry.getKey().z - playerPosZ) > getChunkRange()) {
387                         continue;
388                     }
389                     for (Map.Entry<Long, Object> objectEntry : entry.getValue().entrySet()) {
390                         if (objectEntry.getValue() instanceof CrossType) {
391                             mutable.setPos(BlockPos.unpackX(objectEntry.getKey()), BlockPos.unpackY(objectEntry.getKey()), BlockPos.unpackZ(objectEntry.getKey()));
392                             if (mutable.withinDistance(playerPos, reach)) {
393                                 BlockPos down = mutable.down();
394                                 int color = objectEntry.getValue() == CrossType.RED ? redColor : yellowColor;
395                                 LightOverlayClient.renderCross(info, tessellator, buffer, world, mutable, color, selectionContext);
396                             }
397                         }
398                     }
399                 }
400                 RenderSystem.enableBlend();
401                 RenderSystem.enableTexture();
402                 RenderSystem.shadeModel(7424);
403                 if (smoothLines) GL11.glDisable(GL11.GL_LINE_SMOOTH);
404             }
405             RenderSystem.popMatrix();
406         }
407     }
408     
409     private static KeyBinding registerKeybind(ResourceLocation resourceLocation, InputMappings.Type type, int keyCode, String category) {
410         KeyBinding keyBinding = new KeyBinding("key." + resourceLocation.getNamespace() + "." + resourceLocation.getPath(), KeyConflictContext.IN_GAME, KeyModifier.NONE, type, keyCode, category);
411         ClientRegistry.registerKeyBinding(keyBinding);
412         return keyBinding;
413     }
414     
415     static void loadConfig(File file) {
416         try {
417             redColor = 0xFF0000;
418             yellowColor = 0xFFFF00;
419             if (!file.exists() || !file.canRead())
420                 saveConfig(file);
421             FileInputStream fis = new FileInputStream(file);
422             Properties properties = new Properties();
423             properties.load(fis);
424             fis.close();
425             reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "12"));
426             crossLevel = Integer.parseInt((String) properties.computeIfAbsent("crossLevel", a -> "7"));
427             showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
428             smoothLines = ((String) properties.computeIfAbsent("smoothLines", a -> "true")).equalsIgnoreCase("true");
429             underwater = ((String) properties.computeIfAbsent("underwater", a -> "false")).equalsIgnoreCase("true");
430             lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
431             {
432                 int r, g, b;
433                 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
434                 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
435                 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
436                 yellowColor = (r << 16) + (g << 8) + b;
437             }
438             {
439                 int r, g, b;
440                 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
441                 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
442                 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
443                 redColor = (r << 16) + (g << 8) + b;
444             }
445             saveConfig(file);
446         } catch (Exception e) {
447             e.printStackTrace();
448             reach = 12;
449             lineWidth = 1.0F;
450             redColor = 0xFF0000;
451             yellowColor = 0xFFFF00;
452             showNumber = false;
453             smoothLines = true;
454             underwater = false;
455             try {
456                 saveConfig(file);
457             } catch (IOException ex) {
458                 ex.printStackTrace();
459             }
460         }
461     }
462     
463     static void saveConfig(File file) throws IOException {
464         FileOutputStream fos = new FileOutputStream(file, false);
465         fos.write("# Light Overlay Config".getBytes());
466         fos.write("\n".getBytes());
467         fos.write(("reach=" + reach).getBytes());
468         fos.write("\n".getBytes());
469         fos.write(("crossLevel=" + crossLevel).getBytes());
470         fos.write("\n".getBytes());
471         fos.write(("showNumber=" + showNumber).getBytes());
472         fos.write("\n".getBytes());
473         fos.write(("smoothLines=" + smoothLines).getBytes());
474         fos.write("\n".getBytes());
475         fos.write(("underwater=" + underwater).getBytes());
476         fos.write("\n".getBytes());
477         fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
478         fos.write("\n".getBytes());
479         fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
480         fos.write("\n".getBytes());
481         fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
482         fos.write("\n".getBytes());
483         fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
484         fos.write("\n".getBytes());
485         fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
486         fos.write("\n".getBytes());
487         fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
488         fos.write("\n".getBytes());
489         fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
490         fos.close();
491     }
492     
493     public static void processPacket(IPacket<?> packet) {
494         if (packet instanceof SChangeBlockPacket) {
495             LightOverlayClient.queueChunkAndNear(new ChunkPos(((SChangeBlockPacket) packet).getPos()));
496         } else if (packet instanceof SChunkDataPacket) {
497             LightOverlayClient.queueChunkAndNear(new ChunkPos(((SChunkDataPacket) packet).getChunkX(), ((SChunkDataPacket) packet).getChunkZ()));
498         } else if (packet instanceof SMultiBlockChangePacket) {
499             ChunkPos chunkPos = ObfuscationReflectionHelper.getPrivateValue(SMultiBlockChangePacket.class, (SMultiBlockChangePacket) packet, "field_148925_b");
500             LightOverlayClient.queueChunkAndNear(new ChunkPos(chunkPos.x, chunkPos.z));
501         }
502     }
503     
504     private enum CrossType {
505         YELLOW,
506         RED,
507         NONE
508     }
509     
510 }