1 package me.shedaniel.lightoverlay;
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;
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;
54 public class LightOverlayClient {
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;
71 public static void register() {
73 loadConfig(configFile);
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);
84 ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (BiFunction<Minecraft, Screen, Screen>) (client, parent) -> {
85 ConfigBuilder builder = ConfigBuilder.create().setParentScreen(parent).setTitle("key.lightoverlay.category");
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)
91 .setTextGetter(integer -> "Reach: " + integer + " Blocks")
92 .setSaveConsumer(integer -> LightOverlayClient.reach = integer)
95 general.addEntry(eb.startIntSlider("config.lightoverlay-forge.lineWidth", MathHelper.floor(LightOverlayClient.lineWidth * 100), 100, 700)
97 .setTextGetter(integer -> "Light Width: " + LightOverlayClient.FORMAT.format(integer / 100d))
98 .setSaveConsumer(integer -> LightOverlayClient.lineWidth = integer / 100f)
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();
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();
122 return builder.setSavingRunnable(() -> {
124 LightOverlayClient.saveConfig(LightOverlayClient.configFile);
125 } catch (Exception e) {
128 LightOverlayClient.loadConfig(LightOverlayClient.configFile);
133 private static boolean isInt(String s) {
135 Integer.parseInt(s, 16);
137 } catch (NumberFormatException e) {
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;
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);
160 return (r + g + b).toUpperCase(Locale.ROOT);
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;
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;
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();
214 GlStateManager.depthMask(true);
215 GlStateManager.enableTexture();
218 @SubscribeEvent(receiveCanceled = true)
219 public static void handleInput(InputEvent.KeyInputEvent event) {
220 if (enableOverlay.isPressed())
222 if (increaseReach.isPressed()) {
226 saveConfig(configFile);
227 } catch (IOException e) {
230 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
232 if (decreaseReach.isPressed()) {
236 saveConfig(configFile);
237 } catch (IOException e) {
240 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
242 if (increaseLineWidth.isPressed()) {
246 saveConfig(configFile);
247 } catch (IOException e) {
250 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
252 if (decreaseLineWidth.isPressed()) {
256 saveConfig(configFile);
257 } catch (IOException e) {
260 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
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);
284 GlStateManager.enableBlend();
285 GlStateManager.enableTexture();
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);
295 private static void loadConfig(File file) {
298 yellowColor = 0xFFFF00;
299 if (!file.exists() || !file.canRead())
301 FileInputStream fis = new FileInputStream(file);
302 Properties properties = new Properties();
303 properties.load(fis);
305 reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "7"));
306 lineWidth = Float.valueOf((String) properties.computeIfAbsent("lineWidth", a -> "1"));
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;
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;
322 } catch (Exception e) {
327 yellowColor = 0xFFFF00;
330 } catch (IOException ex) {
331 ex.printStackTrace();
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());
358 private static enum CrossType {