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