]> git.lizzy.rs Git - LightOverlay.git/blob - src/main/java/me/shedaniel/lightoverlay/LightOverlayClient.java
Add Cloth Config Forge
[LightOverlay.git] / src / main / java / me / shedaniel / lightoverlay / LightOverlayClient.java
1 package me.shedaniel.lightoverlay;
2
3 import com.mojang.blaze3d.platform.GlStateManager;
4 import me.shedaniel.forge.clothconfig2.api.ConfigBuilder;
5 import me.shedaniel.forge.clothconfig2.api.ConfigCategory;
6 import me.shedaniel.forge.clothconfig2.impl.ConfigEntryBuilderImpl;
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.screen.Screen;
12 import net.minecraft.client.renderer.ActiveRenderInfo;
13 import net.minecraft.client.renderer.BufferBuilder;
14 import net.minecraft.client.renderer.Tessellator;
15 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
16 import net.minecraft.client.resources.I18n;
17 import net.minecraft.client.settings.KeyBinding;
18 import net.minecraft.client.util.InputMappings;
19 import net.minecraft.entity.Entity;
20 import net.minecraft.entity.EntityClassification;
21 import net.minecraft.entity.EntityType;
22 import net.minecraft.entity.player.PlayerEntity;
23 import net.minecraft.tags.BlockTags;
24 import net.minecraft.util.Direction;
25 import net.minecraft.util.ResourceLocation;
26 import net.minecraft.util.math.BlockPos;
27 import net.minecraft.util.math.MathHelper;
28 import net.minecraft.util.math.shapes.ISelectionContext;
29 import net.minecraft.util.math.shapes.VoxelShape;
30 import net.minecraft.util.text.TranslationTextComponent;
31 import net.minecraft.world.LightType;
32 import net.minecraft.world.World;
33 import net.minecraft.world.biome.Biome;
34 import net.minecraftforge.client.event.InputEvent;
35 import net.minecraftforge.client.event.RenderWorldLastEvent;
36 import net.minecraftforge.client.settings.KeyConflictContext;
37 import net.minecraftforge.client.settings.KeyModifier;
38 import net.minecraftforge.common.MinecraftForge;
39 import net.minecraftforge.eventbus.api.SubscribeEvent;
40 import net.minecraftforge.fml.ExtensionPoint;
41 import net.minecraftforge.fml.ModLoadingContext;
42 import net.minecraftforge.fml.client.registry.ClientRegistry;
43
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.text.DecimalFormat;
49 import java.util.Locale;
50 import java.util.Optional;
51 import java.util.Properties;
52 import java.util.function.BiFunction;
53
54 public class LightOverlayClient {
55     
56     private static final String KEYBIND_CATEGORY = "key.lightoverlay-forge.category";
57     private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay-forge", "enable_overlay");
58     private static final ResourceLocation INCREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_reach");
59     private static final ResourceLocation DECREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_reach");
60     private static final ResourceLocation INCREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_line_width");
61     private static final ResourceLocation DECREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_line_width");
62     private static final DecimalFormat FORMAT = new DecimalFormat("#.#");
63     private static KeyBinding enableOverlay, increaseReach, decreaseReach, increaseLineWidth, decreaseLineWidth;
64     private static boolean enabled = false;
65     private static int reach = 7;
66     private static EntityType<Entity> testingEntityType;
67     private static float lineWidth = 1.0F;
68     private static File configFile = new File(new File(Minecraft.getInstance().gameDir, "config"), "lightoverlay.properties");
69     private static int yellowColor = 0xFFFF00, redColor = 0xFF0000;
70     
71     public static void register() {
72         // Load Config
73         loadConfig(configFile);
74         
75         // Setup
76         testingEntityType = EntityType.Builder.create(EntityClassification.MONSTER).size(0f, 0f).disableSerialization().build(null);
77         enableOverlay = registerKeybind(ENABLE_OVERLAY_KEYBIND, InputMappings.Type.KEYSYM, 296, KEYBIND_CATEGORY);
78         increaseReach = registerKeybind(INCREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
79         decreaseReach = registerKeybind(DECREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
80         increaseLineWidth = registerKeybind(INCREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
81         decreaseLineWidth = registerKeybind(DECREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
82         MinecraftForge.EVENT_BUS.register(LightOverlayClient.class);
83     
84         ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (BiFunction<Minecraft, Screen, Screen>) (client, parent) -> {
85             ConfigBuilder builder = ConfigBuilder.create().setParentScreen(parent).setTitle("key.lightoverlay.category");
86     
87             ConfigEntryBuilderImpl eb = builder.getEntryBuilder();
88             ConfigCategory general = builder.getOrCreateCategory("config.lightoverlay-forge.general");
89             general.addEntry(eb.startIntSlider("config.lightoverlay-forge.reach", LightOverlayClient.reach, 1, 50)
90                     .setDefaultValue(7)
91                     .setTextGetter(integer -> "Reach: " + integer + " Blocks")
92                     .setSaveConsumer(integer -> LightOverlayClient.reach = integer)
93                     .build()
94             );
95             general.addEntry(eb.startIntSlider("config.lightoverlay-forge.lineWidth", MathHelper.floor(LightOverlayClient.lineWidth * 100), 100, 700)
96                     .setDefaultValue(100)
97                     .setTextGetter(integer -> "Light Width: " + LightOverlayClient.FORMAT.format(integer / 100d))
98                     .setSaveConsumer(integer -> LightOverlayClient.lineWidth = integer / 100f)
99                     .build()
100             );
101             general.addEntry(eb.startStrField("config.lightoverlay-forge.yellowColor", "#" + toStringColor(LightOverlayClient.yellowColor))
102                     .setDefaultValue("#FFFF00")
103                     .setSaveConsumer(str -> LightOverlayClient.yellowColor = toIntColor(str))
104                     .setErrorSupplier(s -> {
105                         if (!s.startsWith("#") || s.length() != 7 || !isInt(s.substring(1)))
106                             return Optional.of(I18n.format("config.lightoverlay-forge.invalidColor"));
107                         else return Optional.empty();
108                     })
109                     .build()
110             );
111             general.addEntry(eb.startStrField("config.lightoverlay-forge.redColor", "#" + toStringColor(LightOverlayClient.redColor))
112                     .setDefaultValue("#FF0000")
113                     .setSaveConsumer(str -> LightOverlayClient.redColor = toIntColor(str))
114                     .setErrorSupplier(s -> {
115                         if (!s.startsWith("#") || s.length() != 7 || !isInt(s.substring(1)))
116                             return Optional.of(I18n.format("config.lightoverlay-forge.invalidColor"));
117                         else return Optional.empty();
118                     })
119                     .build()
120             );
121     
122             return builder.setSavingRunnable(() -> {
123                 try {
124                     LightOverlayClient.saveConfig(LightOverlayClient.configFile);
125                 } catch (Exception e) {
126                     e.printStackTrace();
127                 }
128                 LightOverlayClient.loadConfig(LightOverlayClient.configFile);
129             }).build();
130         });
131     }
132     
133     private static boolean isInt(String s) {
134         try {
135             Integer.parseInt(s, 16);
136             return true;
137         } catch (NumberFormatException e) {
138             return false;
139         }
140     }
141     
142     private static int toIntColor(String str) {
143         String substring = str.substring(1);
144         int r = Integer.parseInt(substring.substring(0, 2), 16);
145         int g = Integer.parseInt(substring.substring(2, 4), 16);
146         int b = Integer.parseInt(substring.substring(4, 6), 16);
147         return (r << 16) + (g << 8) + b;
148     }
149     
150     private static String toStringColor(int toolColor) {
151         String r = Integer.toHexString((toolColor >> 16) & 0xFF);
152         String g = Integer.toHexString((toolColor >> 8) & 0xFF);
153         String b = Integer.toHexString((toolColor >> 0) & 0xFF);
154         if (r.length() == 1)
155             r = "0" + r;
156         if (g.length() == 1)
157             g = "0" + g;
158         if (b.length() == 1)
159             b = "0" + b;
160         return (r + g + b).toUpperCase(Locale.ROOT);
161     }
162     
163     public static CrossType getCrossType(BlockPos pos, World world, PlayerEntity playerEntity) {
164         BlockState blockBelowState = world.getBlockState(pos.down());
165         BlockState blockUpperState = world.getBlockState(pos);
166         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, ISelectionContext.forEntity(playerEntity));
167         if (!blockUpperState.getFluidState().isEmpty())
168             return CrossType.NONE;
169         /* WorldEntitySpawner.func_222266_a */
170         // Check if the outline is full
171         if (Block.doesSideFillSquare(upperCollisionShape, Direction.UP))
172             return CrossType.NONE;
173         // Check if there is power
174         if (blockUpperState.canProvidePower())
175             return CrossType.NONE;
176         // Check if the collision has a bump
177         if (upperCollisionShape.getEnd(Direction.Axis.Y) > 0)
178             return CrossType.NONE;
179         if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
180             return CrossType.NONE;
181         // Check block state allow spawning (excludes bedrock and barriers automatically)
182         if (!blockBelowState.canEntitySpawn(world, pos.down(), testingEntityType))
183             return CrossType.NONE;
184         if (world.getLightFor(LightType.BLOCK, pos) >= 8)
185             return CrossType.NONE;
186         if (world.getLightFor(LightType.SKY, pos) >= 8)
187             return CrossType.YELLOW;
188         return CrossType.RED;
189     }
190     
191     public static void renderCross(World world, BlockPos pos, int color, PlayerEntity entity) {
192         ActiveRenderInfo info = Minecraft.getInstance().gameRenderer.getActiveRenderInfo();
193         GlStateManager.lineWidth(lineWidth);
194         GlStateManager.depthMask(false);
195         GlStateManager.disableTexture();
196         Tessellator tessellator = Tessellator.getInstance();
197         BufferBuilder buffer = tessellator.getBuffer();
198         double d0 = info.getProjectedView().x;
199         double d1 = info.getProjectedView().y - .005D;
200         VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, ISelectionContext.forEntity(entity));
201         if (!upperOutlineShape.isEmpty())
202             d1 -= upperOutlineShape.getEnd(Direction.Axis.Y);
203         double d2 = info.getProjectedView().z;
204         
205         buffer.begin(1, DefaultVertexFormats.POSITION_COLOR);
206         int red = (color >> 16) & 255;
207         int green = (color >> 8) & 255;
208         int blue = color & 255;
209         buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
210         buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
211         buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
212         buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
213         tessellator.draw();
214         GlStateManager.depthMask(true);
215         GlStateManager.enableTexture();
216     }
217     
218     @SubscribeEvent(receiveCanceled = true)
219     public static void handleInput(InputEvent.KeyInputEvent event) {
220         if (enableOverlay.isPressed())
221             enabled = !enabled;
222         if (increaseReach.isPressed()) {
223             if (reach < 50)
224                 reach++;
225             try {
226                 saveConfig(configFile);
227             } catch (IOException e) {
228                 e.printStackTrace();
229             }
230             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
231         }
232         if (decreaseReach.isPressed()) {
233             if (reach > 1)
234                 reach--;
235             try {
236                 saveConfig(configFile);
237             } catch (IOException e) {
238                 e.printStackTrace();
239             }
240             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
241         }
242         if (increaseLineWidth.isPressed()) {
243             if (lineWidth < 7)
244                 lineWidth += 0.1f;
245             try {
246                 saveConfig(configFile);
247             } catch (IOException e) {
248                 e.printStackTrace();
249             }
250             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
251         }
252         if (decreaseLineWidth.isPressed()) {
253             if (lineWidth > 1)
254                 lineWidth -= 0.1F;
255             try {
256                 saveConfig(configFile);
257             } catch (IOException e) {
258                 e.printStackTrace();
259             }
260             Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
261         }
262     }
263     
264     @SubscribeEvent
265     public static void renderWorldLast(RenderWorldLastEvent event) {
266         if (LightOverlayClient.enabled) {
267             Minecraft client = Minecraft.getInstance();
268             ClientPlayerEntity playerEntity = client.player;
269             World world = client.world;
270             GlStateManager.disableTexture();
271             GlStateManager.disableBlend();
272             BlockPos playerPos = new BlockPos(playerEntity.posX, playerEntity.posY, playerEntity.posZ);
273             BlockPos.getAllInBox(playerPos.add(-reach, -reach, -reach), playerPos.add(reach, reach, reach)).forEach(pos -> {
274                 Biome biome = world.getBiome(pos);
275                 if (biome.getSpawningChance() > 0 && !biome.getSpawns(EntityClassification.MONSTER).isEmpty()) {
276                     CrossType type = LightOverlayClient.getCrossType(pos, world, playerEntity);
277                     if (type != CrossType.NONE) {
278                         VoxelShape shape = world.getBlockState(pos).getCollisionShape(world, pos);
279                         int color = type == CrossType.RED ? redColor : yellowColor;
280                         LightOverlayClient.renderCross(world, pos, color, playerEntity);
281                     }
282                 }
283             });
284             GlStateManager.enableBlend();
285             GlStateManager.enableTexture();
286         }
287     }
288     
289     private static KeyBinding registerKeybind(ResourceLocation resourceLocation, InputMappings.Type type, int keyCode, String category) {
290         KeyBinding keyBinding = new KeyBinding("key." + resourceLocation.getNamespace() + "." + resourceLocation.getPath(), KeyConflictContext.IN_GAME, KeyModifier.NONE, type, keyCode, category);
291         ClientRegistry.registerKeyBinding(keyBinding);
292         return keyBinding;
293     }
294     
295     private static void loadConfig(File file) {
296         try {
297             redColor = 0xFF0000;
298             yellowColor = 0xFFFF00;
299             if (!file.exists() || !file.canRead())
300                 saveConfig(file);
301             FileInputStream fis = new FileInputStream(file);
302             Properties properties = new Properties();
303             properties.load(fis);
304             fis.close();
305             reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "7"));
306             lineWidth = Float.valueOf((String) properties.computeIfAbsent("lineWidth", a -> "1"));
307             {
308                 int r, g, b;
309                 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
310                 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
311                 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
312                 yellowColor = (r << 16) + (g << 8) + b;
313             }
314             {
315                 int r, g, b;
316                 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
317                 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
318                 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
319                 redColor = (r << 16) + (g << 8) + b;
320             }
321             saveConfig(file);
322         } catch (Exception e) {
323             e.printStackTrace();
324             reach = 7;
325             lineWidth = 1.0F;
326             redColor = 0xFF0000;
327             yellowColor = 0xFFFF00;
328             try {
329                 saveConfig(file);
330             } catch (IOException ex) {
331                 ex.printStackTrace();
332             }
333         }
334     }
335     
336     private static void saveConfig(File file) throws IOException {
337         FileOutputStream fos = new FileOutputStream(file, false);
338         fos.write("# Light Overlay Config".getBytes());
339         fos.write("\n".getBytes());
340         fos.write(("reach=" + reach).getBytes());
341         fos.write("\n".getBytes());
342         fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
343         fos.write("\n".getBytes());
344         fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
345         fos.write("\n".getBytes());
346         fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
347         fos.write("\n".getBytes());
348         fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
349         fos.write("\n".getBytes());
350         fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
351         fos.write("\n".getBytes());
352         fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
353         fos.write("\n".getBytes());
354         fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
355         fos.close();
356     }
357     
358     private static enum CrossType {
359         YELLOW,
360         RED,
361         NONE
362     }
363     
364 }