1 package me.shedaniel.lightoverlay.common;
3 import com.google.common.collect.Maps;
4 import com.mojang.blaze3d.platform.GlStateManager;
5 import com.mojang.blaze3d.platform.InputConstants;
6 import com.mojang.blaze3d.systems.RenderSystem;
7 import com.mojang.blaze3d.vertex.Tesselator;
8 import com.mojang.math.Transformation;
9 import it.unimi.dsi.fastutil.longs.Long2ByteMap;
10 import me.shedaniel.architectury.event.events.GuiEvent;
11 import me.shedaniel.architectury.event.events.client.ClientTickEvent;
12 import me.shedaniel.architectury.platform.Platform;
13 import me.shedaniel.architectury.registry.KeyBindings;
14 import net.minecraft.client.Camera;
15 import net.minecraft.client.KeyMapping;
16 import net.minecraft.client.Minecraft;
17 import net.minecraft.client.gui.Font;
18 import net.minecraft.client.multiplayer.ClientLevel;
19 import net.minecraft.client.player.LocalPlayer;
20 import net.minecraft.client.renderer.MultiBufferSource;
21 import net.minecraft.client.renderer.culling.Frustum;
22 import net.minecraft.core.BlockPos;
23 import net.minecraft.core.Direction;
24 import net.minecraft.resources.ResourceLocation;
25 import net.minecraft.tags.BlockTags;
26 import net.minecraft.util.LazyLoadedValue;
27 import net.minecraft.util.Mth;
28 import net.minecraft.world.entity.Entity;
29 import net.minecraft.world.entity.EntityType;
30 import net.minecraft.world.entity.MobCategory;
31 import net.minecraft.world.level.BlockGetter;
32 import net.minecraft.world.level.Level;
33 import net.minecraft.world.level.LightLayer;
34 import net.minecraft.world.level.biome.Biome;
35 import net.minecraft.world.level.block.Block;
36 import net.minecraft.world.level.block.state.BlockState;
37 import net.minecraft.world.level.chunk.ChunkStatus;
38 import net.minecraft.world.level.chunk.LevelChunk;
39 import net.minecraft.world.level.lighting.LayerLightEventListener;
40 import net.minecraft.world.phys.shapes.CollisionContext;
41 import net.minecraft.world.phys.shapes.VoxelShape;
42 import org.apache.logging.log4j.LogManager;
43 import org.lwjgl.opengl.GL11;
46 import java.io.FileInputStream;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.lang.invoke.MethodHandle;
50 import java.lang.invoke.MethodHandles;
51 import java.lang.invoke.MethodType;
52 import java.text.DecimalFormat;
54 import java.util.concurrent.Executors;
55 import java.util.concurrent.ThreadPoolExecutor;
57 public class LightOverlay {
58 public static final DecimalFormat FORMAT = new DecimalFormat("#.#");
59 private static final String KEYBIND_CATEGORY = "key.lightoverlay.category";
60 private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay", "enable_overlay");
61 public static int reach = 12;
62 public static int crossLevel = 7;
63 public static int secondaryLevel = -1;
64 public static int lowerCrossLevel = -1;
65 public static int higherCrossLevel = -1;
66 public static boolean caching = false;
67 public static boolean showNumber = false;
68 public static boolean smoothLines = true;
69 public static boolean underwater = false;
70 public static boolean mushroom = false;
71 public static boolean useListWhileCaching = true;
72 public static float lineWidth = 1.0F;
73 public static int yellowColor = 0xFFFF00, redColor = 0xFF0000, secondaryColor = 0x0000FF;
74 public static File configFile;
76 private static KeyMapping enableOverlay;
77 private static boolean enabled = false;
78 private static final LazyLoadedValue<EntityType<Entity>> TESTING_ENTITY_TYPE = new LazyLoadedValue<>(() ->
79 EntityType.Builder.createNothing(MobCategory.MONSTER).sized(0f, 0f).noSave().build(null));
80 private static int threadNumber = 0;
81 public static Frustum frustum;
82 private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
83 Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
84 thread.setDaemon(true);
87 private static final Set<CubicChunkPos> POS = Collections.synchronizedSet(new HashSet<>());
88 private static final Set<CubicChunkPos> CALCULATING_POS = Collections.synchronizedSet(new HashSet<>());
89 private static final Map<CubicChunkPos, ChunkData> CHUNK_MAP = Maps.newConcurrentMap();
90 private static final Minecraft CLIENT = Minecraft.getInstance();
91 private static long ticks = 0;
93 public static void register() {
95 configFile = new File(Platform.getConfigFolder().toFile(), "lightoverlay.properties");
96 loadConfig(configFile);
98 enableOverlay = createKeyBinding(ENABLE_OVERLAY_KEYBIND, InputConstants.Type.KEYSYM, 296, KEYBIND_CATEGORY);
99 KeyBindings.registerKeyBinding(enableOverlay);
101 registerDebugRenderer(() -> {
103 LocalPlayer playerEntity = CLIENT.player;
104 int playerPosX = ((int) playerEntity.getX()) >> 4;
105 int playerPosZ = ((int) playerEntity.getZ()) >> 4;
106 CollisionContext collisionContext = CollisionContext.of(playerEntity);
107 Level world = CLIENT.level;
108 BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
109 Camera camera = CLIENT.gameRenderer.getMainCamera();
112 RenderSystem.enableTexture();
113 RenderSystem.depthMask(true);
114 BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
115 BlockPos.MutableBlockPos downMutable = new BlockPos.MutableBlockPos();
116 for (Map.Entry<CubicChunkPos, ChunkData> entry : CHUNK_MAP.entrySet()) {
117 if (caching && (Mth.abs(entry.getKey().x - playerPosX) > getChunkRange() || Mth.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
120 for (Long2ByteMap.Entry objectEntry : entry.getValue().data().long2ByteEntrySet()) {
121 mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
122 if (mutable.closerThan(playerPos, reach)) {
123 if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
124 downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
125 renderLevel(CLIENT, camera, world, mutable, downMutable, objectEntry.getByteValue(), collisionContext);
130 RenderSystem.enableDepthTest();
132 boolean useList = useListWhileCaching && caching;
133 RenderSystem.enableDepthTest();
134 RenderSystem.disableTexture();
135 RenderSystem.enableBlend();
136 RenderSystem.enableCull();
137 RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
138 if (smoothLines) GL11.glEnable(GL11.GL_LINE_SMOOTH);
139 GL11.glLineWidth(lineWidth);
140 if (!useList) GL11.glBegin(GL11.GL_LINES);
142 BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
144 if (useList) GL11.glTranslated(-camera.getPosition().x, -camera.getPosition().y + .01D, -camera.getPosition().z);
145 for (Map.Entry<CubicChunkPos, ChunkData> entry : CHUNK_MAP.entrySet()) {
146 CubicChunkPos chunkPos = entry.getKey();
147 if (caching && (Mth.abs(chunkPos.x - playerPosX) > getChunkRange() || Mth.abs(chunkPos.z - playerPosZ) > getChunkRange())) {
152 if (frustum == null || isFrustumVisible(frustum, chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
153 entry.getValue().renderList(world, collisionContext);
156 for (Long2ByteMap.Entry objectEntry : entry.getValue().data().long2ByteEntrySet()) {
157 byte crossType = objectEntry.getByteValue();
158 mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
159 if (mutable.closerThan(playerPos, reach)) {
160 if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
161 int color = crossType == CROSS_RED ? redColor : crossType == CROSS_YELLOW ? yellowColor : secondaryColor;
162 renderCross(camera, world, mutable, color, collisionContext);
169 if (!useList) GL11.glEnd();
170 RenderSystem.disableBlend();
171 RenderSystem.enableTexture();
172 if (smoothLines) GL11.glDisable(GL11.GL_LINE_SMOOTH);
177 GuiEvent.DEBUG_TEXT_LEFT.register(list -> {
180 list.add(String.format("[Light Overlay] Chunks to queue: %02d", POS.size()));
182 list.add("[Light Overlay] Enabled");
185 list.add("[Light Overlay] Disabled");
188 ClientTickEvent.CLIENT_POST.register(LightOverlay::tick);
191 private static void processChunk(CubicChunkPos pos, int playerPosX, int playerPosY, int playerPosZ, CollisionContext context) {
192 CALCULATING_POS.remove(pos);
193 if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.y - playerPosY) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || POS.contains(pos)) {
197 calculateChunk(CLIENT.level.getChunkSource().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), CLIENT.level, pos, context);
198 } catch (Throwable throwable) {
199 LogManager.getLogger().throwing(throwable);
203 public static void queueChunkAndNear(CubicChunkPos pos) {
204 for (int xOffset = -1; xOffset <= 1; xOffset++) {
205 for (int yOffset = -1; yOffset <= 1; yOffset++) {
206 for (int zOffset = -1; zOffset <= 1; zOffset++) {
207 queueChunk(new CubicChunkPos(pos.x + xOffset, pos.y + yOffset, pos.z + zOffset));
213 public static void queueChunk(CubicChunkPos pos) {
214 if (enabled && caching && !CALCULATING_POS.contains(pos)) {
219 public static int getChunkRange() {
220 return Math.max(Mth.ceil(reach / 16f), 1);
223 private static void calculateChunk(LevelChunk chunk, Level world, CubicChunkPos chunkPos, CollisionContext collisionContext) {
224 if (world != null && chunk != null) {
225 ChunkData chunkData = new ChunkData();
226 LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
227 LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
228 for (BlockPos pos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
229 BlockPos down = pos.below();
231 int level = getCrossLevel(pos, down, chunk, block, collisionContext);
233 chunkData.data().put(pos.asLong(), (byte) level);
236 Biome biome = !mushroom ? world.getBiome(pos) : null;
237 byte type = getCrossType(pos, biome, down, chunk, block, sky, collisionContext);
238 if (type != CROSS_NONE) {
239 chunkData.data().put(pos.asLong(), type);
243 CHUNK_MAP.put(chunkPos, chunkData);
245 ChunkData data = CHUNK_MAP.remove(chunkPos);
253 public static byte getCrossType(BlockPos pos, Biome biome, BlockPos down, BlockGetter world, LayerLightEventListener block, LayerLightEventListener sky, CollisionContext entityContext) {
254 BlockState blockBelowState = world.getBlockState(down);
255 BlockState blockUpperState = world.getBlockState(pos);
256 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
257 if (!underwater && !blockUpperState.getFluidState().isEmpty())
259 // Check if the outline is full
260 if (Block.isFaceFull(upperCollisionShape, Direction.UP))
262 // TODO: Not to hard code no redstone
263 if (blockUpperState.isSignalSource())
265 // Check if the collision has a bump
266 if (upperCollisionShape.max(Direction.Axis.Y) > 0)
268 if (blockUpperState.getBlock().is(BlockTags.RAILS))
270 // Check block state allow spawning (excludes bedrock and barriers automatically)
271 if (!blockBelowState.isValidSpawn(world, down, TESTING_ENTITY_TYPE.get()))
273 if (!mushroom && Biome.BiomeCategory.MUSHROOM == biome.getBiomeCategory())
275 int blockLightLevel = block.getLightValue(pos);
276 int skyLightLevel = sky.getLightValue(pos);
277 if (blockLightLevel > higherCrossLevel)
279 if (skyLightLevel > higherCrossLevel)
281 return lowerCrossLevel >= 0 && blockLightLevel > lowerCrossLevel ? CROSS_SECONDARY : CROSS_RED;
284 public static int getCrossLevel(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener view, CollisionContext collisionContext) {
285 BlockState blockBelowState = world.getBlockState(down);
286 BlockState blockUpperState = world.getBlockState(pos);
287 VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, collisionContext);
288 VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, collisionContext);
289 if (!underwater && !blockUpperState.getFluidState().isEmpty())
291 if (!blockBelowState.getFluidState().isEmpty())
293 if (blockBelowState.isAir())
295 if (Block.isFaceFull(upperCollisionShape, Direction.DOWN))
297 return view.getLightValue(pos);
300 public static void renderCross(Camera camera, Level world, BlockPos pos, int color, CollisionContext collisionContext) {
301 double cameraX = camera.getPosition().x;
302 double cameraY = camera.getPosition().y - .005D;
303 double blockOffset = 0;
304 VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, collisionContext);
305 if (!upperOutlineShape.isEmpty())
306 blockOffset += upperOutlineShape.max(Direction.Axis.Y);
307 double cameraZ = camera.getPosition().z;
309 int red = (color >> 16) & 255;
310 int green = (color >> 8) & 255;
311 int blue = color & 255;
315 RenderSystem.color4f(red / 255f, green / 255f, blue / 255f, 1f);
316 GL11.glVertex3d(x + .01 - cameraX, y - cameraY + blockOffset, z + .01 - cameraZ);
317 GL11.glVertex3d(x - .01 + 1 - cameraX, y - cameraY + blockOffset, z - .01 + 1 - cameraZ);
318 GL11.glVertex3d(x - .01 + 1 - cameraX, y - cameraY + blockOffset, z + .01 - cameraZ);
319 GL11.glVertex3d(x + .01 - cameraX, y - cameraY + blockOffset, z - .01 + 1 - cameraZ);
322 @SuppressWarnings("deprecation")
323 public static void renderLevel(Minecraft client, Camera camera, Level world, BlockPos pos, BlockPos down, byte level, CollisionContext collisionContext) {
324 String text = String.valueOf(level);
325 Font font = client.font;
326 double cameraX = camera.getPosition().x;
327 double cameraY = camera.getPosition().y;
328 VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, collisionContext);
329 if (!upperOutlineShape.isEmpty())
330 cameraY += 1 - upperOutlineShape.max(Direction.Axis.Y);
331 double cameraZ = camera.getPosition().z;
332 RenderSystem.pushMatrix();
333 RenderSystem.translatef((float) (pos.getX() + 0.5f - cameraX), (float) (pos.getY() - cameraY) + 0.005f, (float) (pos.getZ() + 0.5f - cameraZ));
334 RenderSystem.rotatef(90, 1, 0, 0);
335 RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
337 RenderSystem.scalef(-size, -size, size);
338 float float_3 = (float) (-font.width(text)) / 2.0F + 0.4f;
339 RenderSystem.enableAlphaTest();
340 MultiBufferSource.BufferSource source = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
341 font.drawInBatch(text, float_3, -3.5f, level > higherCrossLevel ? 0xff042404 : (lowerCrossLevel >= 0 && level > lowerCrossLevel ? 0xff0066ff : 0xff731111), false, Transformation.identity().getMatrix(), source, false, 0, 15728880);
343 RenderSystem.popMatrix();
346 public static void loadConfig(File file) {
349 yellowColor = 0xFFFF00;
350 secondaryColor = 0x0000FF;
351 if (!file.exists() || !file.canRead())
353 FileInputStream fis = new FileInputStream(file);
354 Properties properties = new Properties();
355 properties.load(fis);
357 reach = Integer.parseInt((String) properties.computeIfAbsent("reach", a -> "12"));
358 crossLevel = Integer.parseInt((String) properties.computeIfAbsent("crossLevel", a -> "7"));
359 secondaryLevel = Integer.parseInt((String) properties.computeIfAbsent("secondaryLevel", a -> "-1"));
360 caching = ((String) properties.computeIfAbsent("caching", a -> "false")).equalsIgnoreCase("true");
361 showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
362 smoothLines = ((String) properties.computeIfAbsent("smoothLines", a -> "true")).equalsIgnoreCase("true");
363 underwater = ((String) properties.computeIfAbsent("underwater", a -> "false")).equalsIgnoreCase("true");
364 mushroom = ((String) properties.computeIfAbsent("mushroom", a -> "false")).equalsIgnoreCase("true");
365 useListWhileCaching = ((String) properties.computeIfAbsent("useListWhileCaching", a -> "true")).equalsIgnoreCase("true");
366 lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
369 r = Integer.parseInt((String) properties.computeIfAbsent("yellowColorRed", a -> "255"));
370 g = Integer.parseInt((String) properties.computeIfAbsent("yellowColorGreen", a -> "255"));
371 b = Integer.parseInt((String) properties.computeIfAbsent("yellowColorBlue", a -> "0"));
372 yellowColor = (r << 16) + (g << 8) + b;
376 r = Integer.parseInt((String) properties.computeIfAbsent("redColorRed", a -> "255"));
377 g = Integer.parseInt((String) properties.computeIfAbsent("redColorGreen", a -> "0"));
378 b = Integer.parseInt((String) properties.computeIfAbsent("redColorBlue", a -> "0"));
379 redColor = (r << 16) + (g << 8) + b;
383 r = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorRed", a -> "0"));
384 g = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorGreen", a -> "0"));
385 b = Integer.parseInt((String) properties.computeIfAbsent("secondaryColorBlue", a -> "255"));
386 secondaryColor = (r << 16) + (g << 8) + b;
389 } catch (Exception e) {
396 yellowColor = 0xFFFF00;
397 secondaryColor = 0x0000FF;
403 useListWhileCaching = true;
406 } catch (IOException ex) {
407 ex.printStackTrace();
410 if (secondaryLevel >= crossLevel) System.err.println("[Light Overlay] Secondary Level is higher than Cross Level");
411 lowerCrossLevel = Math.min(crossLevel, secondaryLevel);
412 higherCrossLevel = Math.max(crossLevel, secondaryLevel);
413 for (ChunkData data : CHUNK_MAP.values()) {
420 public static void saveConfig(File file) throws IOException {
421 FileOutputStream fos = new FileOutputStream(file, false);
422 fos.write("# Light Overlay Config".getBytes());
423 fos.write("\n".getBytes());
424 fos.write(("reach=" + reach).getBytes());
425 fos.write("\n".getBytes());
426 fos.write(("crossLevel=" + crossLevel).getBytes());
427 fos.write("\n".getBytes());
428 fos.write(("secondaryLevel=" + secondaryLevel).getBytes());
429 fos.write("\n".getBytes());
430 fos.write(("caching=" + caching).getBytes());
431 fos.write("\n".getBytes());
432 fos.write(("showNumber=" + showNumber).getBytes());
433 fos.write("\n".getBytes());
434 fos.write(("smoothLines=" + smoothLines).getBytes());
435 fos.write("\n".getBytes());
436 fos.write(("underwater=" + underwater).getBytes());
437 fos.write("\n".getBytes());
438 fos.write(("mushroom=" + mushroom).getBytes());
439 fos.write("\n".getBytes());
440 fos.write(("useListWhileCaching=" + useListWhileCaching).getBytes());
441 fos.write("\n".getBytes());
442 fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
443 fos.write("\n".getBytes());
444 fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
445 fos.write("\n".getBytes());
446 fos.write(("yellowColorGreen=" + ((yellowColor >> 8) & 255)).getBytes());
447 fos.write("\n".getBytes());
448 fos.write(("yellowColorBlue=" + (yellowColor & 255)).getBytes());
449 fos.write("\n".getBytes());
450 fos.write(("redColorRed=" + ((redColor >> 16) & 255)).getBytes());
451 fos.write("\n".getBytes());
452 fos.write(("redColorGreen=" + ((redColor >> 8) & 255)).getBytes());
453 fos.write("\n".getBytes());
454 fos.write(("redColorBlue=" + (redColor & 255)).getBytes());
455 fos.write("\n".getBytes());
456 fos.write(("secondaryColorRed=" + ((secondaryColor >> 16) & 255)).getBytes());
457 fos.write("\n".getBytes());
458 fos.write(("secondaryColorGreen=" + ((secondaryColor >> 8) & 255)).getBytes());
459 fos.write("\n".getBytes());
460 fos.write(("secondaryColorBlue=" + (secondaryColor & 255)).getBytes());
464 private static KeyMapping createKeyBinding(ResourceLocation id, InputConstants.Type type, int code, String category) {
465 return new KeyMapping("key." + id.getNamespace() + "." + id.getPath(), type, code, category);
468 private static final LazyLoadedValue<MethodHandle> IS_FRUSTUM_VISIBLE = new LazyLoadedValue<>(() -> {
470 return MethodHandles.lookup().findStatic(Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl"), "isFrustumVisible",
471 MethodType.methodType(boolean.class, Frustum.class, double.class, double.class, double.class, double.class, double.class, double.class));
472 } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
473 throw new RuntimeException(e);
477 private static boolean isFrustumVisible(Frustum frustum, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
479 return (boolean) IS_FRUSTUM_VISIBLE.get().invokeExact(frustum, minX, minY, minZ, maxX, maxY, maxZ);
480 } catch (Throwable throwable) {
481 throw new RuntimeException(throwable);
485 private static void registerDebugRenderer(Runnable runnable) {
487 Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl").getDeclaredField("debugRenderer").set(null, runnable);
488 } catch (Throwable throwable) {
489 throw new RuntimeException(throwable);
493 private static void tick(Minecraft minecraft) {
494 while (enableOverlay.consumeClick())
499 if (CLIENT.player == null || !enabled) {
501 CALCULATING_POS.clear();
502 EXECUTOR.getQueue().clear();
503 for (ChunkData data : CHUNK_MAP.values()) {
508 LocalPlayer player = CLIENT.player;
509 ClientLevel world = CLIENT.level;
510 CollisionContext collisionContext = CollisionContext.of(player);
513 CALCULATING_POS.clear();
515 for (ChunkData data : CHUNK_MAP.values()) {
519 BlockPos playerPos = player.blockPosition();
520 LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
521 LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
522 BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos();
523 Iterable<BlockPos> iterate = BlockPos.betweenClosed(playerPos.getX() - reach, playerPos.getY() - reach, playerPos.getZ() - reach,
524 playerPos.getX() + reach, playerPos.getY() + reach, playerPos.getZ() + reach);
525 ChunkData chunkData = new ChunkData();
526 CHUNK_MAP.put(new CubicChunkPos(0, 0, 0), chunkData);
527 for (BlockPos blockPos : iterate) {
528 downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
530 int level = getCrossLevel(blockPos, downPos, world, block, collisionContext);
532 chunkData.data().put(blockPos.asLong(), (byte) level);
535 Biome biome = !mushroom ? world.getBiome(blockPos) : null;
536 byte type = getCrossType(blockPos, biome, downPos, world, block, sky, collisionContext);
537 if (type != CROSS_NONE) {
538 chunkData.data().put(blockPos.asLong(), type);
543 int playerPosX = ((int) player.getX()) >> 4;
544 int playerPosY = ((int) player.getY()) >> 4;
545 int playerPosZ = ((int) player.getZ()) >> 4;
546 for (int chunkX = playerPosX - getChunkRange(); chunkX <= playerPosX + getChunkRange(); chunkX++) {
547 for (int chunkY = Math.max(playerPosY - getChunkRange(), 0); chunkY <= playerPosY + getChunkRange() && chunkY <= 15; chunkY++) {
548 for (int chunkZ = playerPosZ - getChunkRange(); chunkZ <= playerPosZ + getChunkRange(); chunkZ++) {
549 if (Mth.abs(chunkX - playerPosX) > getChunkRange() || Mth.abs(chunkY - playerPosY) > getChunkRange() || Mth.abs(chunkZ - playerPosZ) > getChunkRange())
551 CubicChunkPos chunkPos = new CubicChunkPos(chunkX, chunkY, chunkZ);
552 if (!CHUNK_MAP.containsKey(chunkPos))
553 queueChunk(chunkPos);
557 for (int p = 0; p < 3; p++) {
558 if (EXECUTOR.getQueue().size() >= Runtime.getRuntime().availableProcessors()) break;
559 double d1 = Double.MAX_VALUE, d2 = Double.MAX_VALUE, d3 = Double.MAX_VALUE;
560 CubicChunkPos c1 = null, c2 = null, c3 = null;
562 Iterator<CubicChunkPos> iterator = POS.iterator();
563 while (iterator.hasNext()) {
564 CubicChunkPos pos = iterator.next();
565 if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.y - playerPosY) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || CALCULATING_POS.contains(pos)) {
568 if (isFrustumVisible(frustum, pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ())) {
569 int i = Math.abs(pos.x - playerPosX);
570 int j = Math.abs(pos.y - playerPosY);
571 int k = Math.abs(pos.z - playerPosZ);
572 double distance = Math.sqrt(i * i + j * j + k * k);
581 } else if (distance < d2) {
587 } else if (distance < d3) {
596 CubicChunkPos finalC1 = c1;
597 CubicChunkPos finalC2 = c2;
598 CubicChunkPos finalC3 = c3;
599 if (finalC1 != null) {
600 CALCULATING_POS.add(finalC1);
601 if (finalC2 != null) {
602 CALCULATING_POS.add(finalC2);
603 if (finalC3 != null) {
604 CALCULATING_POS.add(finalC3);
607 EXECUTOR.submit(() -> {
608 int playerPosX1 = ((int) CLIENT.player.getX()) >> 4;
609 int playerPosY1 = ((int) CLIENT.player.getY()) >> 4;
610 int playerPosZ1 = ((int) CLIENT.player.getZ()) >> 4;
611 if (finalC1 != null) processChunk(finalC1, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
612 if (finalC2 != null) processChunk(finalC2, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
613 if (finalC3 != null) processChunk(finalC3, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
617 if (ticks % 50 == 0) {
618 Iterator<Map.Entry<CubicChunkPos, ChunkData>> iterator = CHUNK_MAP.entrySet().iterator();
619 while (iterator.hasNext()) {
620 Map.Entry<CubicChunkPos, ChunkData> entry = iterator.next();
621 if (Mth.abs(entry.getKey().x - playerPosX) > getChunkRange() * 2 || Mth.abs(entry.getKey().y - playerPosY) > getChunkRange() * 2 || Mth.abs(entry.getKey().z - playerPosZ) > getChunkRange() * 2) {
622 entry.getValue().close();
629 } catch (Throwable throwable) {
630 LogManager.getLogger().throwing(throwable);
634 public static final byte CROSS_YELLOW = 0;
635 public static final byte CROSS_RED = 1;
636 public static final byte CROSS_SECONDARY = 2;
637 public static final byte CROSS_NONE = 2;