1 package me.shedaniel.lightoverlay;
3 import com.mojang.blaze3d.systems.RenderSystem;
4 import net.minecraft.block.Block;
5 import net.minecraft.block.BlockState;
6 import net.minecraft.client.Minecraft;
7 import net.minecraft.client.entity.player.ClientPlayerEntity;
8 import net.minecraft.client.gui.FontRenderer;
9 import net.minecraft.client.renderer.*;
10 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
11 import net.minecraft.client.settings.KeyBinding;
12 import net.minecraft.client.util.InputMappings;
13 import net.minecraft.entity.Entity;
14 import net.minecraft.entity.EntityClassification;
15 import net.minecraft.entity.EntityType;
16 import net.minecraft.entity.player.PlayerEntity;
17 import net.minecraft.tags.BlockTags;
18 import net.minecraft.util.Direction;
19 import net.minecraft.util.ResourceLocation;
20 import net.minecraft.util.math.BlockPos;
21 import net.minecraft.util.math.shapes.ISelectionContext;
22 import net.minecraft.util.math.shapes.VoxelShape;
23 import net.minecraft.util.text.TranslationTextComponent;
24 import net.minecraft.world.LightType;
25 import net.minecraft.world.World;
26 import net.minecraft.world.biome.Biome;
27 import net.minecraftforge.api.distmarker.Dist;
28 import net.minecraftforge.client.event.InputEvent;
29 import net.minecraftforge.client.event.RenderWorldLastEvent;
30 import net.minecraftforge.client.settings.KeyConflictContext;
31 import net.minecraftforge.client.settings.KeyModifier;
32 import net.minecraftforge.common.MinecraftForge;
33 import net.minecraftforge.eventbus.api.SubscribeEvent;
34 import net.minecraftforge.fml.DistExecutor;
35 import net.minecraftforge.fml.client.registry.ClientRegistry;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.text.DecimalFormat;
42 import java.util.Properties;
44 public class LightOverlayClient {
46 static final DecimalFormat FORMAT = new DecimalFormat("#.#");
47 private static final String KEYBIND_CATEGORY = "key.lightoverlay-forge.category";
48 private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay-forge", "enable_overlay");
49 private static final ResourceLocation INCREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_reach");
50 private static final ResourceLocation DECREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_reach");
51 private static final ResourceLocation INCREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_line_width");
52 private static final ResourceLocation DECREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_line_width");
54 static int crossLevel = 7;
55 static boolean showNumber = false;
56 static EntityType<Entity> testingEntityType;
57 static float lineWidth = 1.0F;
58 static int yellowColor = 0xFFFF00, redColor = 0xFF0000;
59 static File configFile = new File(new File(Minecraft.getInstance().gameDir, "config"), "lightoverlay.properties");
60 private static KeyBinding enableOverlay, increaseReach, decreaseReach, increaseLineWidth, decreaseLineWidth;
61 private static boolean enabled = false;
63 public static void register() {
65 loadConfig(configFile);
68 testingEntityType = EntityType.Builder.create(EntityClassification.MONSTER).size(0f, 0f).disableSerialization().build(null);
69 enableOverlay = registerKeybind(ENABLE_OVERLAY_KEYBIND, InputMappings.Type.KEYSYM, 296, KEYBIND_CATEGORY);
70 increaseReach = registerKeybind(INCREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
71 decreaseReach = registerKeybind(DECREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
72 increaseLineWidth = registerKeybind(INCREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
73 decreaseLineWidth = registerKeybind(DECREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
74 MinecraftForge.EVENT_BUS.register(LightOverlayClient.class);
77 //noinspection Convert2MethodRef
78 DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> LightOverlayCloth.register());
79 } catch (Exception e) {
84 public static CrossType getCrossType(BlockPos pos, World world, PlayerEntity playerEntity) {
85 BlockState blockBelowState = world.getBlockState(pos.down());
86 BlockState blockUpperState = world.getBlockState(pos);
87 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, ISelectionContext.forEntity(playerEntity));
88 if (!blockUpperState.getFluidState().isEmpty())
89 return CrossType.NONE;
90 /* WorldEntitySpawner.func_222266_a */
91 // Check if the outline is full
92 if (Block.doesSideFillSquare(upperCollisionShape, Direction.UP))
93 return CrossType.NONE;
94 // Check if there is power
95 if (blockUpperState.canProvidePower())
96 return CrossType.NONE;
97 // Check if the collision has a bump
98 if (upperCollisionShape.getEnd(Direction.Axis.Y) > 0)
99 return CrossType.NONE;
100 if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
101 return CrossType.NONE;
102 // Check block state allow spawning (excludes bedrock and barriers automatically)
103 if (!blockBelowState.canEntitySpawn(world, pos.down(), testingEntityType))
104 return CrossType.NONE;
105 if (world.func_226658_a_(LightType.BLOCK, pos) > crossLevel)
106 return CrossType.NONE;
107 if (world.func_226658_a_(LightType.SKY, pos) > crossLevel)
108 return CrossType.YELLOW;
109 return CrossType.RED;
112 public static int getCrossLevel(BlockPos pos, World world, PlayerEntity playerEntity) {
113 BlockState blockBelowState = world.getBlockState(pos.down());
114 BlockState blockUpperState = world.getBlockState(pos);
115 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, ISelectionContext.forEntity(playerEntity));
116 if (!blockUpperState.getFluidState().isEmpty())
118 /* WorldEntitySpawner.func_222266_a */
119 // Check if the outline is full
120 if (Block.doesSideFillSquare(upperCollisionShape, Direction.UP))
122 // Check if there is power
123 if (blockUpperState.canProvidePower())
125 // Check if the collision has a bump
126 if (upperCollisionShape.getEnd(Direction.Axis.Y) > 0)
128 if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
130 // Check block state allow spawning (excludes bedrock and barriers automatically)
131 if (!blockBelowState.canEntitySpawn(world, pos.down(), testingEntityType))
133 return world.func_226658_a_(LightType.BLOCK, pos);
136 public static void renderCross(ActiveRenderInfo info, Tessellator tessellator, BufferBuilder buffer, World world, BlockPos pos, int color, PlayerEntity entity) {
137 double d0 = info.getProjectedView().x;
138 double d1 = info.getProjectedView().y - .005D;
139 VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, ISelectionContext.forEntity(entity));
140 if (!upperOutlineShape.isEmpty())
141 d1 -= upperOutlineShape.getEnd(Direction.Axis.Y);
142 double d2 = info.getProjectedView().z;
143 buffer.begin(1, DefaultVertexFormats.POSITION_COLOR);
144 int red = (color >> 16) & 255;
145 int green = (color >> 8) & 255;
146 int blue = color & 255;
147 buffer.func_225582_a_(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).func_225586_a_(red, green, blue, 255).endVertex();
148 buffer.func_225582_a_(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).func_225586_a_(red, green, blue, 255).endVertex();
149 buffer.func_225582_a_(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).func_225586_a_(red, green, blue, 255).endVertex();
150 buffer.func_225582_a_(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).func_225586_a_(red, green, blue, 255).endVertex();
154 public static void renderLevel(Minecraft minecraft, ActiveRenderInfo info, World world, BlockPos pos, int level, PlayerEntity entity) {
155 String string_1 = String.valueOf(level);
156 FontRenderer fontRenderer = minecraft.fontRenderer;
157 double double_4 = info.getProjectedView().x;
158 double double_5 = info.getProjectedView().y;
159 VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, ISelectionContext.forEntity(entity));
160 if (!upperOutlineShape.isEmpty())
161 double_5 -= upperOutlineShape.getEnd(Direction.Axis.Y);
162 double double_6 = info.getProjectedView().z;
163 RenderSystem.pushMatrix();
164 RenderSystem.translatef((float) (pos.getX() + 0.5f - double_4), (float) (pos.getY() - double_5) + 0.005f, (float) (pos.getZ() + 0.5f - double_6));
165 RenderSystem.rotatef(90, 1, 0, 0);
166 RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
168 RenderSystem.scalef(-size, -size, size);
169 float float_3 = (float) (-fontRenderer.getStringWidth(string_1)) / 2.0F + 0.4f;
170 RenderSystem.enableAlphaTest();
171 IRenderTypeBuffer.Impl vertexConsumerProvider$Immediate_1 = IRenderTypeBuffer.func_228455_a_(Tessellator.getInstance().getBuffer());
172 fontRenderer.func_228079_a_(string_1, float_3, -3.5f, level > crossLevel ? 0xff042404 : 0xff731111, false, TransformationMatrix.func_227983_a_().func_227988_c_(), vertexConsumerProvider$Immediate_1, false, 0, 15728880);
173 vertexConsumerProvider$Immediate_1.func_228461_a_();
174 RenderSystem.popMatrix();
177 @SubscribeEvent(receiveCanceled = true)
178 public static void handleInput(InputEvent.KeyInputEvent event) {
179 if (enableOverlay.isPressed())
181 if (increaseReach.isPressed()) {
185 saveConfig(configFile);
186 } catch (IOException e) {
189 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
191 if (decreaseReach.isPressed()) {
195 saveConfig(configFile);
196 } catch (IOException e) {
199 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
201 if (increaseLineWidth.isPressed()) {
205 saveConfig(configFile);
206 } catch (IOException e) {
209 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
211 if (decreaseLineWidth.isPressed()) {
215 saveConfig(configFile);
216 } catch (IOException e) {
219 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
224 public static void renderWorldLast(RenderWorldLastEvent event) {
225 if (LightOverlayClient.enabled) {
226 RenderSystem.pushMatrix();
227 RenderSystem.loadIdentity();
228 RenderSystem.multMatrix(event.getMatrixStack().func_227866_c_().func_227870_a_());
229 Minecraft client = Minecraft.getInstance();
230 ClientPlayerEntity playerEntity = client.player;
231 World world = client.world;
232 BlockPos playerPos = playerEntity.getPosition();
233 ActiveRenderInfo info = client.gameRenderer.getActiveRenderInfo();
235 RenderSystem.enableTexture();
236 RenderSystem.enableDepthTest();
238 RenderSystem.depthMask(true);
239 for (BlockPos pos : BlockPos.getAllInBoxMutable(playerPos.add(-reach, -reach, -reach), playerPos.add(reach, reach, reach))) {
240 Biome biome = world.func_226691_t_(pos);
241 if (biome.getSpawningChance() > 0 && !biome.getSpawns(EntityClassification.MONSTER).isEmpty()) {
242 int level = LightOverlayClient.getCrossLevel(pos, world, playerEntity);
244 VoxelShape shape = world.getBlockState(pos).getCollisionShape(world, pos);
245 LightOverlayClient.renderLevel(client, info, world, pos, level, playerEntity);
249 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
250 RenderSystem.enableDepthTest();
252 RenderSystem.disableTexture();
253 RenderSystem.disableBlend();
254 RenderSystem.lineWidth(lineWidth);
255 RenderSystem.depthMask(false);
256 Tessellator tessellator = Tessellator.getInstance();
257 BufferBuilder buffer = tessellator.getBuffer();
258 for (BlockPos pos : BlockPos.getAllInBoxMutable(playerPos.add(-reach, -reach, -reach), playerPos.add(reach, reach, reach))) {
259 Biome biome = world.func_226691_t_(pos);
260 if (biome.getSpawningChance() > 0 && !biome.getSpawns(EntityClassification.MONSTER).isEmpty()) {
261 CrossType type = LightOverlayClient.getCrossType(pos, world, playerEntity);
262 if (type != CrossType.NONE) {
263 VoxelShape shape = world.getBlockState(pos).getCollisionShape(world, pos);
264 int color = type == CrossType.RED ? redColor : yellowColor;
265 LightOverlayClient.renderCross(info, tessellator, buffer, world, pos, color, playerEntity);
269 RenderSystem.depthMask(true);
271 RenderSystem.popMatrix();
275 private static KeyBinding registerKeybind(ResourceLocation resourceLocation, InputMappings.Type type, int keyCode, String category) {
276 KeyBinding keyBinding = new KeyBinding("key." + resourceLocation.getNamespace() + "." + resourceLocation.getPath(), KeyConflictContext.IN_GAME, KeyModifier.NONE, type, keyCode, category);
277 ClientRegistry.registerKeyBinding(keyBinding);
281 static void loadConfig(File file) {
284 yellowColor = 0xFFFF00;
285 if (!file.exists() || !file.canRead())
287 FileInputStream fis = new FileInputStream(file);
288 Properties properties = new Properties();
289 properties.load(fis);
291 reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "7"));
292 crossLevel = Integer.parseInt((String) properties.computeIfAbsent("crossLevel", a -> "7"));
293 showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
294 lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
297 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
298 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
299 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
300 yellowColor = (r << 16) + (g << 8) + b;
304 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
305 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
306 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
307 redColor = (r << 16) + (g << 8) + b;
310 } catch (Exception e) {
315 yellowColor = 0xFFFF00;
318 } catch (IOException ex) {
319 ex.printStackTrace();
324 static void saveConfig(File file) throws IOException {
325 FileOutputStream fos = new FileOutputStream(file, false);
326 fos.write("# Light Overlay Config".getBytes());
327 fos.write("\n".getBytes());
328 fos.write(("reach=" + reach).getBytes());
329 fos.write("\n".getBytes());
330 fos.write(("crossLevel=" + crossLevel).getBytes());
331 fos.write("\n".getBytes());
332 fos.write(("showNumber=" + showNumber).getBytes());
333 fos.write("\n".getBytes());
334 fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
335 fos.write("\n".getBytes());
336 fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
337 fos.write("\n".getBytes());
338 fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
339 fos.write("\n".getBytes());
340 fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
341 fos.write("\n".getBytes());
342 fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
343 fos.write("\n".getBytes());
344 fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
345 fos.write("\n".getBytes());
346 fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
350 private enum CrossType {