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