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