1 package me.shedaniel.lightoverlay;
3 import com.mojang.blaze3d.platform.GlStateManager;
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.renderer.ActiveRenderInfo;
9 import net.minecraft.client.renderer.BufferBuilder;
10 import net.minecraft.client.renderer.Tessellator;
11 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
12 import net.minecraft.client.settings.KeyBinding;
13 import net.minecraft.client.util.InputMappings;
14 import net.minecraft.entity.Entity;
15 import net.minecraft.entity.EntityClassification;
16 import net.minecraft.entity.EntityType;
17 import net.minecraft.entity.player.PlayerEntity;
18 import net.minecraft.tags.BlockTags;
19 import net.minecraft.util.Direction;
20 import net.minecraft.util.ResourceLocation;
21 import net.minecraft.util.math.BlockPos;
22 import net.minecraft.util.math.shapes.ISelectionContext;
23 import net.minecraft.util.math.shapes.VoxelShape;
24 import net.minecraft.util.text.TranslationTextComponent;
25 import net.minecraft.world.LightType;
26 import net.minecraft.world.World;
27 import net.minecraft.world.biome.Biome;
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.client.registry.ClientRegistry;
37 import java.io.FileInputStream;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.text.DecimalFormat;
41 import java.util.Properties;
43 public class LightOverlayClient {
45 private static final String KEYBIND_CATEGORY = "key.lightoverlay-forge.category";
46 private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay-forge", "enable_overlay");
47 private static final ResourceLocation INCREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_reach");
48 private static final ResourceLocation DECREASE_REACH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_reach");
49 private static final ResourceLocation INCREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "increase_line_width");
50 private static final ResourceLocation DECREASE_LINE_WIDTH_KEYBIND = new ResourceLocation("lightoverlay-forge", "decrease_line_width");
51 private static final DecimalFormat FORMAT = new DecimalFormat("#.#");
52 private static KeyBinding enableOverlay, increaseReach, decreaseReach, increaseLineWidth, decreaseLineWidth;
53 private static boolean enabled = false;
54 private static int reach = 7;
55 private static EntityType<Entity> testingEntityType;
56 private static float lineWidth = 1.0F;
57 private static File configFile = new File(new File(Minecraft.getInstance().gameDir, "config"), "lightoverlay.properties");
58 private static int yellowColor = 0xFFFF00, redColor = 0xFF0000;
60 public static void register() {
62 loadConfig(configFile);
65 testingEntityType = EntityType.Builder.create(EntityClassification.MONSTER).size(0f, 0f).disableSerialization().build(null);
66 enableOverlay = registerKeybind(ENABLE_OVERLAY_KEYBIND, InputMappings.Type.KEYSYM, 296, KEYBIND_CATEGORY);
67 increaseReach = registerKeybind(INCREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
68 decreaseReach = registerKeybind(DECREASE_REACH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
69 increaseLineWidth = registerKeybind(INCREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
70 decreaseLineWidth = registerKeybind(DECREASE_LINE_WIDTH_KEYBIND, InputMappings.Type.KEYSYM, -1, KEYBIND_CATEGORY);
71 MinecraftForge.EVENT_BUS.register(LightOverlayClient.class);
74 public static CrossType getCrossType(BlockPos pos, World world, PlayerEntity playerEntity) {
75 BlockState blockBelowState = world.getBlockState(pos.down());
76 BlockState blockUpperState = world.getBlockState(pos);
77 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, ISelectionContext.forEntity(playerEntity));
78 if (!blockUpperState.getFluidState().isEmpty())
79 return CrossType.NONE;
80 /* WorldEntitySpawner.func_222266_a */
81 // Check if the outline is full
82 if (Block.doesSideFillSquare(upperCollisionShape, Direction.UP))
83 return CrossType.NONE;
84 // Check if there is power
85 if (blockUpperState.canProvidePower())
86 return CrossType.NONE;
87 // Check if the collision has a bump
88 if (upperCollisionShape.getEnd(Direction.Axis.Y) > 0)
89 return CrossType.NONE;
90 if (blockUpperState.getBlock().isIn(BlockTags.RAILS))
91 return CrossType.NONE;
92 // Check block state allow spawning (excludes bedrock and barriers automatically)
93 if (!blockBelowState.canEntitySpawn(world, pos.down(), testingEntityType))
94 return CrossType.NONE;
95 if (world.getLightFor(LightType.BLOCK, pos) >= 8)
96 return CrossType.NONE;
97 if (world.getLightFor(LightType.SKY, pos) >= 8)
98 return CrossType.YELLOW;
102 public static void renderCross(World world, BlockPos pos, int color, PlayerEntity entity) {
103 ActiveRenderInfo info = Minecraft.getInstance().gameRenderer.getActiveRenderInfo();
104 GlStateManager.lineWidth(lineWidth);
105 GlStateManager.depthMask(false);
106 GlStateManager.disableTexture();
107 Tessellator tessellator = Tessellator.getInstance();
108 BufferBuilder buffer = tessellator.getBuffer();
109 double d0 = info.getProjectedView().x;
110 double d1 = info.getProjectedView().y - .005D;
111 VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, ISelectionContext.forEntity(entity));
112 if (!upperOutlineShape.isEmpty())
113 d1 -= upperOutlineShape.getEnd(Direction.Axis.Y);
114 double d2 = info.getProjectedView().z;
116 buffer.begin(1, DefaultVertexFormats.POSITION_COLOR);
117 int red = (color >> 16) & 255;
118 int green = (color >> 8) & 255;
119 int blue = color & 255;
120 buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
121 buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
122 buffer.pos(pos.getX() - .01 + 1 - d0, pos.getY() - d1, pos.getZ() + .01 - d2).color(red, green, blue, 255).endVertex();
123 buffer.pos(pos.getX() + .01 - d0, pos.getY() - d1, pos.getZ() - .01 + 1 - d2).color(red, green, blue, 255).endVertex();
125 GlStateManager.depthMask(true);
126 GlStateManager.enableTexture();
129 @SubscribeEvent(receiveCanceled = true)
130 public static void handleInput(InputEvent.KeyInputEvent event) {
131 if (enableOverlay.isPressed())
133 if (increaseReach.isPressed()) {
137 saveConfig(configFile);
138 } catch (IOException e) {
141 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
143 if (decreaseReach.isPressed()) {
147 saveConfig(configFile);
148 } catch (IOException e) {
151 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_reach", reach), false);
153 if (increaseLineWidth.isPressed()) {
157 saveConfig(configFile);
158 } catch (IOException e) {
161 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
163 if (decreaseLineWidth.isPressed()) {
167 saveConfig(configFile);
168 } catch (IOException e) {
171 Minecraft.getInstance().player.sendStatusMessage(new TranslationTextComponent("text.lightoverlay-forge.current_line_width", FORMAT.format(lineWidth)), false);
176 public static void renderWorldLast(RenderWorldLastEvent event) {
177 if (LightOverlayClient.enabled) {
178 Minecraft client = Minecraft.getInstance();
179 ClientPlayerEntity playerEntity = client.player;
180 World world = client.world;
181 GlStateManager.disableTexture();
182 GlStateManager.disableBlend();
183 BlockPos playerPos = new BlockPos(playerEntity.posX, playerEntity.posY, playerEntity.posZ);
184 BlockPos.getAllInBox(playerPos.add(-reach, -reach, -reach), playerPos.add(reach, reach, reach)).forEach(pos -> {
185 Biome biome = world.getBiome(pos);
186 if (biome.getSpawningChance() > 0 && !biome.getSpawns(EntityClassification.MONSTER).isEmpty()) {
187 CrossType type = LightOverlayClient.getCrossType(pos, world, playerEntity);
188 if (type != CrossType.NONE) {
189 VoxelShape shape = world.getBlockState(pos).getCollisionShape(world, pos);
190 int color = type == CrossType.RED ? redColor : yellowColor;
191 LightOverlayClient.renderCross(world, pos, color, playerEntity);
195 GlStateManager.enableBlend();
196 GlStateManager.enableTexture();
200 private static KeyBinding registerKeybind(ResourceLocation resourceLocation, InputMappings.Type type, int keyCode, String category) {
201 KeyBinding keyBinding = new KeyBinding("key." + resourceLocation.getNamespace() + "." + resourceLocation.getPath(), KeyConflictContext.IN_GAME, KeyModifier.NONE, type, keyCode, category);
202 ClientRegistry.registerKeyBinding(keyBinding);
206 private static void loadConfig(File file) {
209 yellowColor = 0xFFFF00;
210 if (!file.exists() || !file.canRead())
212 FileInputStream fis = new FileInputStream(file);
213 Properties properties = new Properties();
214 properties.load(fis);
216 reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "7"));
217 lineWidth = Float.valueOf((String) properties.computeIfAbsent("lineWidth", a -> "1"));
220 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
221 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
222 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
223 yellowColor = (r << 16) + (g << 8) + b;
227 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
228 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
229 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
230 redColor = (r << 16) + (g << 8) + b;
233 } catch (Exception e) {
238 yellowColor = 0xFFFF00;
241 } catch (IOException ex) {
242 ex.printStackTrace();
247 private static void saveConfig(File file) throws IOException {
248 FileOutputStream fos = new FileOutputStream(file, false);
249 fos.write("# Light Overlay Config".getBytes());
250 fos.write("\n".getBytes());
251 fos.write(("reach=" + reach).getBytes());
252 fos.write("\n".getBytes());
253 fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
254 fos.write("\n".getBytes());
255 fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
256 fos.write("\n".getBytes());
257 fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
258 fos.write("\n".getBytes());
259 fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
260 fos.write("\n".getBytes());
261 fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
262 fos.write("\n".getBytes());
263 fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
264 fos.write("\n".getBytes());
265 fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
269 private static enum CrossType {