]> git.lizzy.rs Git - LightOverlay.git/commitdiff
Update to 1.17
authorshedaniel <daniel@shedaniel.me>
Tue, 15 Jun 2021 13:47:54 +0000 (21:47 +0800)
committershedaniel <daniel@shedaniel.me>
Tue, 15 Jun 2021 13:47:54 +0000 (21:47 +0800)
Signed-off-by: shedaniel <daniel@shedaniel.me>
16 files changed:
build.gradle
common/build.gradle
common/src/main/java/me/shedaniel/lightoverlay/common/ChunkData.java [deleted file]
common/src/main/java/me/shedaniel/lightoverlay/common/ClothScreen.java
common/src/main/java/me/shedaniel/lightoverlay/common/CubicChunkPos.java
common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlay.java
common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayRenderer.java [new file with mode: 0644]
common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayTicker.java [new file with mode: 0644]
fabric/build.gradle
fabric/src/main/java/me/shedaniel/lightoverlay/fabric/LightOverlayImpl.java
fabric/src/main/java/me/shedaniel/lightoverlay/fabric/mixin/MixinClientConnection.java
fabric/src/main/resources/fabric.mod.json
forge/build.gradle
gradle.properties
gradle/wrapper/gradle-wrapper.properties
settings.gradle

index 06d56861127348bb9358728f9d839a9e3e73bc02..59744a31718bc83dad41337c085771be37f80150 100644 (file)
@@ -1,25 +1,6 @@
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath("commons-io:commons-io:2.6")
-    }
-}
-
-import java.nio.file.FileVisitResult
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.SimpleFileVisitor
-import java.nio.file.attribute.BasicFileAttributes
-import java.util.stream.Stream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-import java.util.zip.ZipOutputStream
-
 plugins {
-    id "architectury-plugin" version "3.0.76"
-    id "forgified-fabric-loom" version "0.6.67" apply false
+    id "architectury-plugin" version "3.2-SNAPSHOT"
+    id "dev.architectury.loom" version "0.7.2-SNAPSHOT" apply false
 }
 
 architectury {
@@ -27,7 +8,7 @@ architectury {
 }
 
 subprojects {
-    apply plugin: "forgified-fabric-loom"
+    apply plugin: "dev.architectury.loom"
 
     loom {
         silentMojangMappingsLicense()
@@ -42,141 +23,12 @@ allprojects {
     archivesBaseName = rootProject.name
     version = rootProject.mod_version
 
-    tasks.withType(JavaCompile) {
-        options.encoding = "UTF-8"
-    }
-}
-
-task buildMerged {
-    allprojects {
-        dependsOn it.tasks.getByName("build")
-    }
-    doLast {
-        def folder = file(".gradle/.mergemods")
-        folder.mkdirs()
-        def fabricJar = file("fabric/build/libs/${rootProject.name}-${rootProject.mod_version}-fabric.jar")
-        def forgeJar = file("forge/build/libs/${rootProject.name}-${rootProject.mod_version}-forge.jar")
-        def fabricFolder = new File(folder, ".tempFabric")
-        def forgeFolder = new File(folder, ".tempForge")
-        def mergeFolder = new File(folder, ".tempMerge")
-        def policyMap = new HashMap<String, String>()
-        file("merging.policy").eachLine {
-            if (it.isBlank() || it.startsWith("#")) return
-            def env = it.substring(0, it.indexOf(' '))
-            if (env == "FABRIC")
-                policyMap.put(it.substring(env.length() + 1), "Fabric")
-            else if (env == "FORGE")
-                policyMap.put(it.substring(env.length() + 1), "Forge")
-            else throw new IllegalStateException("Illegal env $env at $it")
-        }
-        forgeFolder.deleteDir()
-        fabricFolder.deleteDir()
-        mergeFolder.deleteDir()
-        unzip(fabricJar, fabricFolder)
-        unzip(forgeJar, forgeFolder)
-        mergeFolder.mkdirs()
-        Stream.of(forgeFolder, fabricFolder).each { useFolder ->
-            try {
-                Files.walkFileTree(useFolder.toPath(), new SimpleFileVisitor<Path>() {
-                    @Override
-                    FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                        try {
-                            File ogFile = file.toFile()
-                            File outFile = new File(mergeFolder, ogFile.getAbsolutePath().replace(useFolder.getAbsolutePath(), ""))
-                            outFile.getParentFile().mkdirs()
-                            if (outFile.exists()) {
-                                def env = useFolder.getName().substring(5)
-                                def fileName = outFile.getAbsolutePath().replace(mergeFolder.getAbsolutePath(), "")
-                                if (!ogFile.isFile() || !outFile.isFile() || !Arrays.equals(ogFile.readBytes(), outFile.readBytes())) {
-                                    def policyEnv = policyMap.get(fileName)
-                                    if (policyEnv == null) {
-                                        throw new IllegalStateException("Unhandled duplicate file: $fileName")
-                                    }
-                                    println "Chose env ${policyEnv.toUpperCase(Locale.ROOT)} for duplicate file: $fileName"
-                                    if (policyEnv != env)
-                                        return FileVisitResult.CONTINUE
-                                }
-                            }
-                            if (!ogFile.isDirectory()) {
-                                org.apache.commons.io.FileUtils.copyFile(ogFile, outFile)
-                            } else {
-                                org.apache.commons.io.FileUtils.copyDirectory(ogFile, outFile)
-                            }
-                        } catch (IOException e) {
-                            e.printStackTrace()
-                            System.exit(0)
-                        }
-                        return FileVisitResult.CONTINUE
-                    }
-                })
-            } catch (IOException e) {
-                e.printStackTrace()
-                System.exit(0)
-            }
-        }
-        File finalMerge = file("build/libs/${rootProject.name}-${rootProject.mod_version}.jar")
-        finalMerge.parentFile.mkdirs()
-        finalMerge.delete()
-        compress(mergeFolder.toPath(), finalMerge)
-        folder.deleteDir()
+    repositories {
+        maven { url "https://maven.terraformersmc.com/releases" }
     }
-}
-
-rootProject.subprojects.forEach {
-    buildMerged.mustRunAfter it.tasks.getByName("build")
-}
 
-static def compress(Path sourceDir, File zipFile) {
-    try {
-        final ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(zipFile))
-        Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
-            @Override
-            FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
-                try {
-                    Path targetFile = sourceDir.relativize(file)
-                    outputStream.putNextEntry(new ZipEntry(targetFile.toString()))
-                    byte[] bytes = Files.readAllBytes(file)
-                    outputStream.write(bytes, 0, bytes.length)
-                    outputStream.closeEntry()
-                } catch (IOException e) {
-                    e.printStackTrace()
-                }
-                return FileVisitResult.CONTINUE
-            }
-        })
-        outputStream.close()
-    } catch (IOException e) {
-        e.printStackTrace()
+    tasks.withType(JavaCompile) {
+        options.encoding = "UTF-8"
+        options.release = 16
     }
 }
-
-static def unzip(File zipFile, File destDir) {
-    if (!destDir.exists())
-        destDir.mkdirs()
-    FileInputStream fis
-    byte[] buffer = new byte[1024]
-    try {
-        fis = new FileInputStream(zipFile)
-        ZipInputStream zis = new ZipInputStream(fis)
-        ZipEntry zipEntry = zis.getNextEntry()
-        while (zipEntry != null) {
-            if (!zipEntry.isDirectory()) {
-                File newFile = new File(destDir, zipEntry.getName())
-                new File(newFile.getParent()).mkdirs()
-                FileOutputStream fos = new FileOutputStream(newFile)
-                int len
-                while ((len = zis.read(buffer)) > 0) {
-                    fos.write(buffer, 0, len)
-                }
-                fos.close()
-            }
-            zis.closeEntry()
-            zipEntry = zis.getNextEntry()
-        }
-        zis.closeEntry()
-        zis.close()
-        fis.close()
-    } catch (IOException e) {
-        e.printStackTrace()
-    }
-}
\ No newline at end of file
index 377b4f2f118e255791168883f6b08ddf08bd4e81..f1f5bb37e1e4720ad284732cdd152d45335a0687 100644 (file)
@@ -1,13 +1,13 @@
 dependencies {
     minecraft "com.mojang:minecraft:${rootProject.architectury.minecraft}"
     mappings minecraft.officialMojangMappings()
-    modCompile "me.shedaniel:architectury:${rootProject.architectury_version}"
-    modCompile "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
+    modImplementation "dev.architectury:architectury:${rootProject.architectury_version}"
+    modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
     modCompileOnly("me.shedaniel.cloth:cloth-config:${rootProject.cloth_config_version}") {
         exclude(group: "net.fabricmc.fabric-api")
     }
 }
 
 architectury {
-    common()
+    common(false)
 }
\ No newline at end of file
diff --git a/common/src/main/java/me/shedaniel/lightoverlay/common/ChunkData.java b/common/src/main/java/me/shedaniel/lightoverlay/common/ChunkData.java
deleted file mode 100644 (file)
index e4278a1..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-package me.shedaniel.lightoverlay.common;
-
-import it.unimi.dsi.fastutil.longs.Long2ByteMap;
-import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.phys.shapes.CollisionContext;
-import net.minecraft.world.phys.shapes.VoxelShape;
-import org.lwjgl.opengl.GL11;
-
-import java.io.Closeable;
-
-import static me.shedaniel.lightoverlay.common.LightOverlay.*;
-
-public class ChunkData implements Closeable {
-    private static final IllegalStateException WRONG_TYPE = new IllegalStateException("Wrong type accessed!");
-    private Long2ByteMap data;
-    private int glListIndex = 0;
-    private boolean generatedList = false;
-    
-    public ChunkData() {
-        this(new Long2ByteOpenHashMap());
-    }
-    
-    public ChunkData(Long2ByteMap data) {
-        this.data = data;
-    }
-    
-    public Long2ByteMap data() {
-        return data;
-    }
-    
-    private void compileList(Level level, CollisionContext collisionContext) {
-        generatedList = true;
-        
-        if (data().isEmpty()) {
-            glListIndex = 0;
-            return;
-        }
-        
-        glListIndex = GL11.glGenLists(3);
-        GL11.glNewList(glListIndex, GL11.GL_COMPILE);
-        GL11.glBegin(GL11.GL_LINES);
-        color(redColor);
-        
-        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
-        for (Long2ByteMap.Entry objectEntry : data().long2ByteEntrySet()) {
-            byte crossType = objectEntry.getByteValue();
-            mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
-            if (crossType == CROSS_RED) {
-                renderCross(level, mutable, collisionContext);
-            }
-        }
-        
-        GL11.glEnd();
-        GL11.glEndList();
-        
-        GL11.glNewList(glListIndex + 1, GL11.GL_COMPILE);
-        GL11.glBegin(GL11.GL_LINES);
-        color(yellowColor);
-        
-        for (Long2ByteMap.Entry objectEntry : data().long2ByteEntrySet()) {
-            byte crossType = objectEntry.getByteValue();
-            mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
-            if (crossType == CROSS_YELLOW) {
-                renderCross(level, mutable, collisionContext);
-            }
-        }
-        
-        GL11.glEnd();
-        GL11.glEndList();
-        
-        GL11.glNewList(glListIndex + 2, GL11.GL_COMPILE);
-        GL11.glBegin(GL11.GL_LINES);
-        color(secondaryColor);
-        
-        for (Long2ByteMap.Entry objectEntry : data().long2ByteEntrySet()) {
-            byte crossType = objectEntry.getByteValue();
-            mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
-            if (crossType == CROSS_SECONDARY) {
-                renderCross(level, mutable, collisionContext);
-            }
-        }
-        
-        GL11.glEnd();
-        GL11.glEndList();
-    }
-    
-    public void renderList(Level level, CollisionContext collisionContext) {
-        if (!generatedList) {
-            compileList(level, collisionContext);
-        }
-        
-        if (glListIndex != 0) {
-            GL11.glCallList(glListIndex);
-            GL11.glCallList(glListIndex + 1);
-            GL11.glCallList(glListIndex + 2);
-        }
-    }
-    
-    private static void color(int color) {
-        int red = (color >> 16) & 255;
-        int green = (color >> 8) & 255;
-        int blue = color & 255;
-        GL11.glColor4f(red / 255f, green / 255f, blue / 255f, 1f);
-    }
-    
-    public static void renderCross(Level level, BlockPos pos, CollisionContext collisionContext) {
-        double blockOffset = 0;
-        VoxelShape upperOutlineShape = level.getBlockState(pos).getShape(level, pos, collisionContext);
-        if (!upperOutlineShape.isEmpty())
-            blockOffset += upperOutlineShape.max(Direction.Axis.Y);
-        
-        
-        int x = pos.getX();
-        int y = pos.getY();
-        int z = pos.getZ();
-        GL11.glVertex3d(x + .01, y + blockOffset, z + .01);
-        GL11.glVertex3d(x - .01 + 1, y + blockOffset, z - .01 + 1);
-        GL11.glVertex3d(x - .01 + 1, y + blockOffset, z + .01);
-        GL11.glVertex3d(x + .01, y + blockOffset, z - .01 + 1);
-    }
-    
-    @Override
-    public void close() {
-        if (glListIndex != 0) {
-            GL11.glDeleteLists(glListIndex, 3);
-        }
-    }
-}
index 1a71dca31bdcf67eec44d139897a18d5aa0d7400..5812d2f75c633bbb5a0ac30c1a3a6397d7f91927 100644 (file)
@@ -31,10 +31,8 @@ public class ClothScreen {
                     return Optional.empty();
                 }).setDefaultValue(-1).setTextGetter(integer -> new TextComponent(integer < 0 ? "Off" : "Level: " + integer)).setSaveConsumer(integer -> LightOverlay.secondaryLevel = integer).build());
         general.addEntry(eb.startBooleanToggle(new TranslatableComponent("config.lightoverlay.showNumber"), LightOverlay.showNumber).setDefaultValue(false).setSaveConsumer(bool -> LightOverlay.showNumber = bool).build());
-        general.addEntry(eb.startBooleanToggle(new TranslatableComponent("config.lightoverlay.smoothLines"), LightOverlay.smoothLines).setDefaultValue(true).setSaveConsumer(bool -> LightOverlay.smoothLines = bool).build());
         general.addEntry(eb.startBooleanToggle(new TranslatableComponent("config.lightoverlay.underwater"), LightOverlay.underwater).setDefaultValue(false).setSaveConsumer(bool -> LightOverlay.underwater = bool).build());
         general.addEntry(eb.startBooleanToggle(new TranslatableComponent("config.lightoverlay.mushroom"), LightOverlay.mushroom).setDefaultValue(false).setSaveConsumer(bool -> LightOverlay.mushroom = bool).build());
-        general.addEntry(eb.startBooleanToggle(new TranslatableComponent("config.lightoverlay.useListWhileCaching"), LightOverlay.useListWhileCaching).setDefaultValue(true).setSaveConsumer(bool -> LightOverlay.useListWhileCaching = bool).build());
         general.addEntry(eb.startIntSlider(new TranslatableComponent("config.lightoverlay.lineWidth"), Mth.floor(LightOverlay.lineWidth * 100), 100, 700).setDefaultValue(100).setTextGetter(integer -> new TextComponent("Light Width: " + LightOverlay.FORMAT.format(integer / 100d))).setSaveConsumer(integer -> LightOverlay.lineWidth = integer / 100f).build());
         general.addEntry(eb.startColorField(new TranslatableComponent("config.lightoverlay.yellowColor"), LightOverlay.yellowColor).setDefaultValue(0xFFFF00).setSaveConsumer(color -> LightOverlay.yellowColor = color).build());
         general.addEntry(eb.startColorField(new TranslatableComponent("config.lightoverlay.redColor"), LightOverlay.redColor).setDefaultValue(0xFF0000).setSaveConsumer(color -> LightOverlay.redColor = color).build());
index 576724e210615beca35ed593c5466b1293fa706e..5c3a18897e7adb0e9a75b75d17eb9366f5c8f60e 100644 (file)
@@ -2,8 +2,6 @@ package me.shedaniel.lightoverlay.common;
 
 import net.minecraft.core.BlockPos;
 
-import java.util.Objects;
-
 public class CubicChunkPos {
     public final int x;
     public final int y;
@@ -23,7 +21,7 @@ public class CubicChunkPos {
     
     public CubicChunkPos(BlockPos blockPos) {
         this.x = blockPos.getX() >> 4;
-        this.y = blockPos.getY() >> 4;
+        this.y = blockPos.getY() >> 5;
         this.z = blockPos.getZ() >> 4;
     }
     
@@ -53,7 +51,7 @@ public class CubicChunkPos {
     }
     
     public int getMinBlockY() {
-        return this.y << 4;
+        return this.y << 5;
     }
     
     public int getMinBlockZ() {
@@ -65,7 +63,7 @@ public class CubicChunkPos {
     }
     
     public int getMaxBlockY() {
-        return (this.y << 4) + 15;
+        return (this.y << 5) + 31;
     }
     
     public int getMaxBlockZ() {
@@ -80,8 +78,17 @@ public class CubicChunkPos {
         return x == that.x && y == that.y && z == that.z;
     }
     
+    @Override
+    public String toString() {
+        return "CubicChunkPos{" +
+               "x=" + x +
+               ", y=" + y +
+               ", z=" + z +
+               '}';
+    }
+    
     @Override
     public int hashCode() {
-        return Objects.hash(x, y, z);
+        return Long.hashCode(toLong());
     }
 }
index d3eb13195f6b5f13022ea6b840d480482383eef8..9d93dc477bf588f78344feb7f55d7fcc624e2e21 100644 (file)
@@ -1,63 +1,26 @@
 package me.shedaniel.lightoverlay.common;
 
-import com.google.common.collect.Maps;
-import com.mojang.blaze3d.platform.GlStateManager;
 import com.mojang.blaze3d.platform.InputConstants;
-import com.mojang.blaze3d.systems.RenderSystem;
-import com.mojang.blaze3d.vertex.Tesselator;
-import com.mojang.math.Transformation;
-import it.unimi.dsi.fastutil.longs.Long2ByteMap;
-import me.shedaniel.architectury.event.events.GuiEvent;
-import me.shedaniel.architectury.event.events.client.ClientTickEvent;
-import me.shedaniel.architectury.platform.Platform;
-import me.shedaniel.architectury.registry.KeyBindings;
-import net.minecraft.client.Camera;
+import com.mojang.blaze3d.vertex.PoseStack;
+import dev.architectury.event.events.client.ClientGuiEvent;
+import dev.architectury.event.events.client.ClientTickEvent;
+import dev.architectury.injectables.targets.ArchitecturyTarget;
+import dev.architectury.platform.Platform;
+import dev.architectury.registry.client.keymappings.KeyMappingRegistry;
 import net.minecraft.client.KeyMapping;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.Font;
-import net.minecraft.client.multiplayer.ClientLevel;
-import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.client.renderer.MultiBufferSource;
-import net.minecraft.client.renderer.culling.Frustum;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
 import net.minecraft.resources.ResourceLocation;
-import net.minecraft.tags.BlockTags;
-import net.minecraft.util.LazyLoadedValue;
 import net.minecraft.util.Mth;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.entity.EntityType;
-import net.minecraft.world.entity.MobCategory;
-import net.minecraft.world.level.BlockGetter;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.LightLayer;
-import net.minecraft.world.level.biome.Biome;
-import net.minecraft.world.level.block.Block;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.chunk.ChunkStatus;
-import net.minecraft.world.level.chunk.LevelChunk;
-import net.minecraft.world.level.lighting.LayerLightEventListener;
-import net.minecraft.world.phys.shapes.CollisionContext;
-import net.minecraft.world.phys.shapes.VoxelShape;
-import org.apache.logging.log4j.LogManager;
-import org.lwjgl.opengl.GL11;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.text.DecimalFormat;
-import java.util.*;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadPoolExecutor;
+import java.util.Properties;
+import java.util.function.Consumer;
 
 public class LightOverlay {
     public static final DecimalFormat FORMAT = new DecimalFormat("#.#");
-    private static final String KEYBIND_CATEGORY = "key.lightoverlay.category";
-    private static final ResourceLocation ENABLE_OVERLAY_KEYBIND = new ResourceLocation("lightoverlay", "enable_overlay");
     public static int reach = 12;
     public static int crossLevel = 7;
     public static int secondaryLevel = -1;
@@ -65,119 +28,32 @@ public class LightOverlay {
     public static int higherCrossLevel = -1;
     public static boolean caching = false;
     public static boolean showNumber = false;
-    public static boolean smoothLines = true;
     public static boolean underwater = false;
     public static boolean mushroom = false;
-    public static boolean useListWhileCaching = true;
     public static float lineWidth = 1.0F;
     public static int yellowColor = 0xFFFF00, redColor = 0xFF0000, secondaryColor = 0x0000FF;
     public static File configFile;
     
-    private static KeyMapping enableOverlay;
-    private static boolean enabled = false;
-    private static final LazyLoadedValue<EntityType<Entity>> TESTING_ENTITY_TYPE = new LazyLoadedValue<>(() ->
-            EntityType.Builder.createNothing(MobCategory.MONSTER).sized(0f, 0f).noSave().build(null));
-    private static int threadNumber = 0;
-    public static Frustum frustum;
-    private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
-        Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
-        thread.setDaemon(true);
-        return thread;
-    });
-    private static final Set<CubicChunkPos> POS = Collections.synchronizedSet(new HashSet<>());
-    private static final Set<CubicChunkPos> CALCULATING_POS = Collections.synchronizedSet(new HashSet<>());
-    private static final Map<CubicChunkPos, ChunkData> CHUNK_MAP = Maps.newConcurrentMap();
-    private static final Minecraft CLIENT = Minecraft.getInstance();
-    private static long ticks = 0;
+    public static KeyMapping enableOverlay;
+    public static boolean enabled = false;
+    
+    public static LightOverlayTicker ticker = new LightOverlayTicker();
+    public static LightOverlayRenderer renderer = new LightOverlayRenderer(ticker);
     
     public static void register() {
         // Load Config
         configFile = new File(Platform.getConfigFolder().toFile(), "lightoverlay.properties");
         loadConfig(configFile);
         
-        enableOverlay = createKeyBinding(ENABLE_OVERLAY_KEYBIND, InputConstants.Type.KEYSYM, 296, KEYBIND_CATEGORY);
-        KeyBindings.registerKeyBinding(enableOverlay);
+        enableOverlay = createKeyBinding(new ResourceLocation("lightoverlay", "enable_overlay"), InputConstants.Type.KEYSYM, 296, "key.lightoverlay.category");
+        KeyMappingRegistry.register(enableOverlay);
         
-        registerDebugRenderer(() -> {
-            if (enabled) {
-                LocalPlayer playerEntity = CLIENT.player;
-                int playerPosX = ((int) playerEntity.getX()) >> 4;
-                int playerPosZ = ((int) playerEntity.getZ()) >> 4;
-                CollisionContext collisionContext = CollisionContext.of(playerEntity);
-                Level world = CLIENT.level;
-                BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
-                Camera camera = CLIENT.gameRenderer.getMainCamera();
-                
-                if (showNumber) {
-                    RenderSystem.enableTexture();
-                    RenderSystem.depthMask(true);
-                    BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
-                    BlockPos.MutableBlockPos downMutable = new BlockPos.MutableBlockPos();
-                    for (Map.Entry<CubicChunkPos, ChunkData> entry : CHUNK_MAP.entrySet()) {
-                        if (caching && (Mth.abs(entry.getKey().x - playerPosX) > getChunkRange() || Mth.abs(entry.getKey().z - playerPosZ) > getChunkRange())) {
-                            continue;
-                        }
-                        for (Long2ByteMap.Entry objectEntry : entry.getValue().data().long2ByteEntrySet()) {
-                            mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
-                            if (mutable.closerThan(playerPos, reach)) {
-                                if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
-                                    downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
-                                    renderLevel(CLIENT, camera, world, mutable, downMutable, objectEntry.getByteValue(), collisionContext);
-                                }
-                            }
-                        }
-                    }
-                    RenderSystem.enableDepthTest();
-                } else {
-                    boolean useList = useListWhileCaching && caching;
-                    RenderSystem.enableDepthTest();
-                    RenderSystem.disableTexture();
-                    RenderSystem.enableBlend();
-                    RenderSystem.enableCull();
-                    RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
-                    if (smoothLines) GL11.glEnable(GL11.GL_LINE_SMOOTH);
-                    GL11.glLineWidth(lineWidth);
-                    if (!useList) GL11.glBegin(GL11.GL_LINES);
-                    
-                    BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
-                    
-                    if (useList) GL11.glTranslated(-camera.getPosition().x, -camera.getPosition().y + .01D, -camera.getPosition().z);
-                    for (Map.Entry<CubicChunkPos, ChunkData> entry : CHUNK_MAP.entrySet()) {
-                        CubicChunkPos chunkPos = entry.getKey();
-                        if (caching && (Mth.abs(chunkPos.x - playerPosX) > getChunkRange() || Mth.abs(chunkPos.z - playerPosZ) > getChunkRange())) {
-                            continue;
-                        }
-                        
-                        if (useList) {
-                            if (frustum == null || isFrustumVisible(frustum, chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
-                                entry.getValue().renderList(world, collisionContext);
-                            }
-                        } else {
-                            for (Long2ByteMap.Entry objectEntry : entry.getValue().data().long2ByteEntrySet()) {
-                                byte crossType = objectEntry.getByteValue();
-                                mutable.set(BlockPos.getX(objectEntry.getLongKey()), BlockPos.getY(objectEntry.getLongKey()), BlockPos.getZ(objectEntry.getLongKey()));
-                                if (mutable.closerThan(playerPos, reach)) {
-                                    if (frustum == null || isFrustumVisible(frustum, mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
-                                        int color = crossType == CROSS_RED ? redColor : crossType == CROSS_YELLOW ? yellowColor : secondaryColor;
-                                        renderCross(camera, world, mutable, color, collisionContext);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    
-                    if (!useList) GL11.glEnd();
-                    RenderSystem.disableBlend();
-                    RenderSystem.enableTexture();
-                    if (smoothLines) GL11.glDisable(GL11.GL_LINE_SMOOTH);
-                }
-            }
-        });
+        registerDebugRenderer(renderer);
         
-        GuiEvent.DEBUG_TEXT_LEFT.register(list -> {
+        ClientGuiEvent.DEBUG_TEXT_LEFT.register(list -> {
             if (enabled) {
                 if (caching) {
-                    list.add(String.format("[Light Overlay] Chunks to queue: %02d", POS.size()));
+                    list.add(String.format("[Light Overlay] Chunks to queue: %02d", ticker.POS.size()));
                 } else {
                     list.add("[Light Overlay] Enabled");
                 }
@@ -185,20 +61,9 @@ public class LightOverlay {
                 list.add("[Light Overlay] Disabled");
             }
         });
-        ClientTickEvent.CLIENT_POST.register(LightOverlay::tick);
+        ClientTickEvent.CLIENT_POST.register(ticker::tick);
     }
     
-    private static void processChunk(CubicChunkPos pos, int playerPosX, int playerPosY, int playerPosZ, CollisionContext context) {
-        CALCULATING_POS.remove(pos);
-        if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.y - playerPosY) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || POS.contains(pos)) {
-            return;
-        }
-        try {
-            calculateChunk(CLIENT.level.getChunkSource().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), CLIENT.level, pos, context);
-        } catch (Throwable throwable) {
-            LogManager.getLogger().throwing(throwable);
-        }
-    }
     
     public static void queueChunkAndNear(CubicChunkPos pos) {
         for (int xOffset = -1; xOffset <= 1; xOffset++) {
@@ -211,137 +76,13 @@ public class LightOverlay {
     }
     
     public static void queueChunk(CubicChunkPos pos) {
-        if (enabled && caching && !CALCULATING_POS.contains(pos)) {
-            POS.add(pos);
-        }
+        ticker.queueChunk(pos);
     }
     
     public static int getChunkRange() {
         return Math.max(Mth.ceil(reach / 16f), 1);
     }
     
-    private static void calculateChunk(LevelChunk chunk, Level world, CubicChunkPos chunkPos, CollisionContext collisionContext) {
-        if (world != null && chunk != null) {
-            ChunkData chunkData = new ChunkData();
-            LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
-            LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
-            for (BlockPos pos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
-                BlockPos down = pos.below();
-                if (showNumber) {
-                    int level = getCrossLevel(pos, down, chunk, block, collisionContext);
-                    if (level >= 0) {
-                        chunkData.data().put(pos.asLong(), (byte) level);
-                    }
-                } else {
-                    Biome biome = !mushroom ? world.getBiome(pos) : null;
-                    byte type = getCrossType(pos, biome, down, chunk, block, sky, collisionContext);
-                    if (type != CROSS_NONE) {
-                        chunkData.data().put(pos.asLong(), type);
-                    }
-                }
-            }
-            CHUNK_MAP.put(chunkPos, chunkData);
-        } else {
-            ChunkData data = CHUNK_MAP.remove(chunkPos);
-            
-            if (data != null) {
-                data.close();
-            }
-        }
-    }
-    
-    public static byte getCrossType(BlockPos pos, Biome biome, BlockPos down, BlockGetter world, LayerLightEventListener block, LayerLightEventListener sky, CollisionContext entityContext) {
-        BlockState blockBelowState = world.getBlockState(down);
-        BlockState blockUpperState = world.getBlockState(pos);
-        VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
-        if (!underwater && !blockUpperState.getFluidState().isEmpty())
-            return CROSS_NONE;
-        // Check if the outline is full
-        if (Block.isFaceFull(upperCollisionShape, Direction.UP))
-            return CROSS_NONE;
-        // TODO: Not to hard code no redstone
-        if (blockUpperState.isSignalSource())
-            return CROSS_NONE;
-        // Check if the collision has a bump
-        if (upperCollisionShape.max(Direction.Axis.Y) > 0)
-            return CROSS_NONE;
-        if (blockUpperState.getBlock().is(BlockTags.RAILS))
-            return CROSS_NONE;
-        // Check block state allow spawning (excludes bedrock and barriers automatically)
-        if (!blockBelowState.isValidSpawn(world, down, TESTING_ENTITY_TYPE.get()))
-            return CROSS_NONE;
-        if (!mushroom && Biome.BiomeCategory.MUSHROOM == biome.getBiomeCategory())
-            return CROSS_NONE;
-        int blockLightLevel = block.getLightValue(pos);
-        int skyLightLevel = sky.getLightValue(pos);
-        if (blockLightLevel > higherCrossLevel)
-            return CROSS_NONE;
-        if (skyLightLevel > higherCrossLevel)
-            return CROSS_YELLOW;
-        return lowerCrossLevel >= 0 && blockLightLevel > lowerCrossLevel ? CROSS_SECONDARY : CROSS_RED;
-    }
-    
-    public static int getCrossLevel(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener view, CollisionContext collisionContext) {
-        BlockState blockBelowState = world.getBlockState(down);
-        BlockState blockUpperState = world.getBlockState(pos);
-        VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, collisionContext);
-        VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, collisionContext);
-        if (!underwater && !blockUpperState.getFluidState().isEmpty())
-            return -1;
-        if (!blockBelowState.getFluidState().isEmpty())
-            return -1;
-        if (blockBelowState.isAir())
-            return -1;
-        if (Block.isFaceFull(upperCollisionShape, Direction.DOWN))
-            return -1;
-        return view.getLightValue(pos);
-    }
-    
-    public static void renderCross(Camera camera, Level world, BlockPos pos, int color, CollisionContext collisionContext) {
-        double cameraX = camera.getPosition().x;
-        double cameraY = camera.getPosition().y - .005D;
-        double blockOffset = 0;
-        VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, collisionContext);
-        if (!upperOutlineShape.isEmpty())
-            blockOffset += upperOutlineShape.max(Direction.Axis.Y);
-        double cameraZ = camera.getPosition().z;
-        
-        int red = (color >> 16) & 255;
-        int green = (color >> 8) & 255;
-        int blue = color & 255;
-        int x = pos.getX();
-        int y = pos.getY();
-        int z = pos.getZ();
-        RenderSystem.color4f(red / 255f, green / 255f, blue / 255f, 1f);
-        GL11.glVertex3d(x + .01 - cameraX, y - cameraY + blockOffset, z + .01 - cameraZ);
-        GL11.glVertex3d(x - .01 + 1 - cameraX, y - cameraY + blockOffset, z - .01 + 1 - cameraZ);
-        GL11.glVertex3d(x - .01 + 1 - cameraX, y - cameraY + blockOffset, z + .01 - cameraZ);
-        GL11.glVertex3d(x + .01 - cameraX, y - cameraY + blockOffset, z - .01 + 1 - cameraZ);
-    }
-    
-    @SuppressWarnings("deprecation")
-    public static void renderLevel(Minecraft client, Camera camera, Level world, BlockPos pos, BlockPos down, byte level, CollisionContext collisionContext) {
-        String text = String.valueOf(level);
-        Font font = client.font;
-        double cameraX = camera.getPosition().x;
-        double cameraY = camera.getPosition().y;
-        VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, collisionContext);
-        if (!upperOutlineShape.isEmpty())
-            cameraY += 1 - upperOutlineShape.max(Direction.Axis.Y);
-        double cameraZ = camera.getPosition().z;
-        RenderSystem.pushMatrix();
-        RenderSystem.translatef((float) (pos.getX() + 0.5f - cameraX), (float) (pos.getY() - cameraY) + 0.005f, (float) (pos.getZ() + 0.5f - cameraZ));
-        RenderSystem.rotatef(90, 1, 0, 0);
-        RenderSystem.normal3f(0.0F, 1.0F, 0.0F);
-        float size = 0.07F;
-        RenderSystem.scalef(-size, -size, size);
-        float float_3 = (float) (-font.width(text)) / 2.0F + 0.4f;
-        RenderSystem.enableAlphaTest();
-        MultiBufferSource.BufferSource source = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
-        font.drawInBatch(text, float_3, -3.5f, level > higherCrossLevel ? 0xff042404 : (lowerCrossLevel >= 0 && level > lowerCrossLevel ? 0xff0066ff : 0xff731111), false, Transformation.identity().getMatrix(), source, false, 0, 15728880);
-        source.endBatch();
-        RenderSystem.popMatrix();
-    }
     
     public static void loadConfig(File file) {
         try {
@@ -359,10 +100,8 @@ public class LightOverlay {
             secondaryLevel = Integer.parseInt((String) properties.computeIfAbsent("secondaryLevel", a -> "-1"));
             caching = ((String) properties.computeIfAbsent("caching", a -> "false")).equalsIgnoreCase("true");
             showNumber = ((String) properties.computeIfAbsent("showNumber", a -> "false")).equalsIgnoreCase("true");
-            smoothLines = ((String) properties.computeIfAbsent("smoothLines", a -> "true")).equalsIgnoreCase("true");
             underwater = ((String) properties.computeIfAbsent("underwater", a -> "false")).equalsIgnoreCase("true");
             mushroom = ((String) properties.computeIfAbsent("mushroom", a -> "false")).equalsIgnoreCase("true");
-            useListWhileCaching = ((String) properties.computeIfAbsent("useListWhileCaching", a -> "true")).equalsIgnoreCase("true");
             lineWidth = Float.parseFloat((String) properties.computeIfAbsent("lineWidth", a -> "1"));
             {
                 int r, g, b;
@@ -397,10 +136,8 @@ public class LightOverlay {
             secondaryColor = 0x0000FF;
             caching = false;
             showNumber = false;
-            smoothLines = true;
             underwater = false;
             mushroom = false;
-            useListWhileCaching = true;
             try {
                 saveConfig(file);
             } catch (IOException ex) {
@@ -410,11 +147,8 @@ public class LightOverlay {
         if (secondaryLevel >= crossLevel) System.err.println("[Light Overlay] Secondary Level is higher than Cross Level");
         lowerCrossLevel = Math.min(crossLevel, secondaryLevel);
         higherCrossLevel = Math.max(crossLevel, secondaryLevel);
-        for (ChunkData data : CHUNK_MAP.values()) {
-            data.close();
-        }
-        CHUNK_MAP.clear();
-        POS.clear();
+        ticker.CHUNK_MAP.clear();
+        ticker.POS.clear();
     }
     
     public static void saveConfig(File file) throws IOException {
@@ -431,14 +165,10 @@ public class LightOverlay {
         fos.write("\n".getBytes());
         fos.write(("showNumber=" + showNumber).getBytes());
         fos.write("\n".getBytes());
-        fos.write(("smoothLines=" + smoothLines).getBytes());
-        fos.write("\n".getBytes());
         fos.write(("underwater=" + underwater).getBytes());
         fos.write("\n".getBytes());
         fos.write(("mushroom=" + mushroom).getBytes());
         fos.write("\n".getBytes());
-        fos.write(("useListWhileCaching=" + useListWhileCaching).getBytes());
-        fos.write("\n".getBytes());
         fos.write(("lineWidth=" + FORMAT.format(lineWidth)).getBytes());
         fos.write("\n".getBytes());
         fos.write(("yellowColorRed=" + ((yellowColor >> 16) & 255)).getBytes());
@@ -465,172 +195,15 @@ public class LightOverlay {
         return new KeyMapping("key." + id.getNamespace() + "." + id.getPath(), type, code, category);
     }
     
-    private static final LazyLoadedValue<MethodHandle> IS_FRUSTUM_VISIBLE = new LazyLoadedValue<>(() -> {
-        try {
-            return MethodHandles.lookup().findStatic(Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl"), "isFrustumVisible",
-                    MethodType.methodType(boolean.class, Frustum.class, double.class, double.class, double.class, double.class, double.class, double.class));
-        } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    });
-    
-    private static boolean isFrustumVisible(Frustum frustum, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
-        try {
-            return (boolean) IS_FRUSTUM_VISIBLE.get().invokeExact(frustum, minX, minY, minZ, maxX, maxY, maxZ);
-        } catch (Throwable throwable) {
-            throw new RuntimeException(throwable);
-        }
-    }
     
-    private static void registerDebugRenderer(Runnable runnable) {
+    private static void registerDebugRenderer(Consumer<PoseStack> runnable) {
         try {
-            Class.forName("me.shedaniel.lightoverlay." + Platform.getModLoader() + ".LightOverlayImpl").getDeclaredField("debugRenderer").set(null, runnable);
+            Class.forName("me.shedaniel.lightoverlay." + ArchitecturyTarget.getCurrentTarget() + ".LightOverlayImpl").getDeclaredField("debugRenderer").set(null, runnable);
         } catch (Throwable throwable) {
             throw new RuntimeException(throwable);
         }
     }
     
-    private static void tick(Minecraft minecraft) {
-        while (enableOverlay.consumeClick())
-            enabled = !enabled;
-        
-        try {
-            ticks++;
-            if (CLIENT.player == null || !enabled) {
-                POS.clear();
-                CALCULATING_POS.clear();
-                EXECUTOR.getQueue().clear();
-                for (ChunkData data : CHUNK_MAP.values()) {
-                    data.close();
-                }
-                CHUNK_MAP.clear();
-            } else {
-                LocalPlayer player = CLIENT.player;
-                ClientLevel world = CLIENT.level;
-                CollisionContext collisionContext = CollisionContext.of(player);
-                
-                if (!caching) {
-                    CALCULATING_POS.clear();
-                    POS.clear();
-                    for (ChunkData data : CHUNK_MAP.values()) {
-                        data.close();
-                    }
-                    CHUNK_MAP.clear();
-                    BlockPos playerPos = player.blockPosition();
-                    LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
-                    LayerLightEventListener sky = showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
-                    BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos();
-                    Iterable<BlockPos> iterate = BlockPos.betweenClosed(playerPos.getX() - reach, playerPos.getY() - reach, playerPos.getZ() - reach,
-                            playerPos.getX() + reach, playerPos.getY() + reach, playerPos.getZ() + reach);
-                    ChunkData chunkData = new ChunkData();
-                    CHUNK_MAP.put(new CubicChunkPos(0, 0, 0), chunkData);
-                    for (BlockPos blockPos : iterate) {
-                        downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
-                        if (showNumber) {
-                            int level = getCrossLevel(blockPos, downPos, world, block, collisionContext);
-                            if (level >= 0) {
-                                chunkData.data().put(blockPos.asLong(), (byte) level);
-                            }
-                        } else {
-                            Biome biome = !mushroom ? world.getBiome(blockPos) : null;
-                            byte type = getCrossType(blockPos, biome, downPos, world, block, sky, collisionContext);
-                            if (type != CROSS_NONE) {
-                                chunkData.data().put(blockPos.asLong(), type);
-                            }
-                        }
-                    }
-                } else {
-                    int playerPosX = ((int) player.getX()) >> 4;
-                    int playerPosY = ((int) player.getY()) >> 4;
-                    int playerPosZ = ((int) player.getZ()) >> 4;
-                    for (int chunkX = playerPosX - getChunkRange(); chunkX <= playerPosX + getChunkRange(); chunkX++) {
-                        for (int chunkY = Math.max(playerPosY - getChunkRange(), 0); chunkY <= playerPosY + getChunkRange() && chunkY <= 15; chunkY++) {
-                            for (int chunkZ = playerPosZ - getChunkRange(); chunkZ <= playerPosZ + getChunkRange(); chunkZ++) {
-                                if (Mth.abs(chunkX - playerPosX) > getChunkRange() || Mth.abs(chunkY - playerPosY) > getChunkRange() || Mth.abs(chunkZ - playerPosZ) > getChunkRange())
-                                    continue;
-                                CubicChunkPos chunkPos = new CubicChunkPos(chunkX, chunkY, chunkZ);
-                                if (!CHUNK_MAP.containsKey(chunkPos))
-                                    queueChunk(chunkPos);
-                            }
-                        }
-                    }
-                    for (int p = 0; p < 3; p++) {
-                        if (EXECUTOR.getQueue().size() >= Runtime.getRuntime().availableProcessors()) break;
-                        double d1 = Double.MAX_VALUE, d2 = Double.MAX_VALUE, d3 = Double.MAX_VALUE;
-                        CubicChunkPos c1 = null, c2 = null, c3 = null;
-                        synchronized (POS) {
-                            Iterator<CubicChunkPos> iterator = POS.iterator();
-                            while (iterator.hasNext()) {
-                                CubicChunkPos pos = iterator.next();
-                                if (Mth.abs(pos.x - playerPosX) > getChunkRange() || Mth.abs(pos.y - playerPosY) > getChunkRange() || Mth.abs(pos.z - playerPosZ) > getChunkRange() || CALCULATING_POS.contains(pos)) {
-                                    iterator.remove();
-                                } else {
-                                    if (isFrustumVisible(frustum, pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ())) {
-                                        int i = Math.abs(pos.x - playerPosX);
-                                        int j = Math.abs(pos.y - playerPosY);
-                                        int k = Math.abs(pos.z - playerPosZ);
-                                        double distance = Math.sqrt(i * i + j * j + k * k);
-                                        if (distance < d1) {
-                                            d3 = d2;
-                                            d2 = d1;
-                                            d1 = distance;
-                                            c3 = c2;
-                                            c2 = c1;
-                                            c1 = pos;
-                                            iterator.remove();
-                                        } else if (distance < d2) {
-                                            d3 = d2;
-                                            d2 = distance;
-                                            c3 = c2;
-                                            c2 = pos;
-                                            iterator.remove();
-                                        } else if (distance < d3) {
-                                            d3 = distance;
-                                            c3 = pos;
-                                            iterator.remove();
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                        CubicChunkPos finalC1 = c1;
-                        CubicChunkPos finalC2 = c2;
-                        CubicChunkPos finalC3 = c3;
-                        if (finalC1 != null) {
-                            CALCULATING_POS.add(finalC1);
-                            if (finalC2 != null) {
-                                CALCULATING_POS.add(finalC2);
-                                if (finalC3 != null) {
-                                    CALCULATING_POS.add(finalC3);
-                                }
-                            }
-                            EXECUTOR.submit(() -> {
-                                int playerPosX1 = ((int) CLIENT.player.getX()) >> 4;
-                                int playerPosY1 = ((int) CLIENT.player.getY()) >> 4;
-                                int playerPosZ1 = ((int) CLIENT.player.getZ()) >> 4;
-                                if (finalC1 != null) processChunk(finalC1, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
-                                if (finalC2 != null) processChunk(finalC2, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
-                                if (finalC3 != null) processChunk(finalC3, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
-                            });
-                        }
-                    }
-                    if (ticks % 50 == 0) {
-                        Iterator<Map.Entry<CubicChunkPos, ChunkData>> iterator = CHUNK_MAP.entrySet().iterator();
-                        while (iterator.hasNext()) {
-                            Map.Entry<CubicChunkPos, ChunkData> entry = iterator.next();
-                            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) {
-                                entry.getValue().close();
-                                iterator.remove();
-                            }
-                        }
-                    }
-                }
-            }
-        } catch (Throwable throwable) {
-            LogManager.getLogger().throwing(throwable);
-        }
-    }
-    
     public static final byte CROSS_YELLOW = 0;
     public static final byte CROSS_RED = 1;
     public static final byte CROSS_SECONDARY = 2;
diff --git a/common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayRenderer.java b/common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayRenderer.java
new file mode 100644 (file)
index 0000000..7204963
--- /dev/null
@@ -0,0 +1,183 @@
+package me.shedaniel.lightoverlay.common;
+
+import com.google.common.base.Suppliers;
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.*;
+import com.mojang.math.Matrix4f;
+import com.mojang.math.Vector3f;
+import dev.architectury.injectables.targets.ArchitecturyTarget;
+import it.unimi.dsi.fastutil.longs.Long2ByteMap;
+import net.minecraft.client.Camera;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.client.renderer.GameRenderer;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.culling.Frustum;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.util.Mth;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.VoxelShape;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class LightOverlayRenderer implements Consumer<PoseStack> {
+    private final Minecraft minecraft = Minecraft.getInstance();
+    public Frustum frustum;
+    public LightOverlayTicker ticker;
+    
+    public LightOverlayRenderer(LightOverlayTicker ticker) {
+        this.ticker = ticker;
+    }
+    
+    @Override
+    public void accept(PoseStack poses) {
+        if (LightOverlay.enabled) {
+            LocalPlayer playerEntity = minecraft.player;
+            BlockPos playerPos = new BlockPos(playerEntity.getX(), playerEntity.getY(), playerEntity.getZ());
+            int playerPosX = playerPos.getX() >> 4;
+            int playerPosY = playerPos.getY() >> 5;
+            int playerPosZ = playerPos.getZ() >> 4;
+            CollisionContext collisionContext = CollisionContext.of(playerEntity);
+            Camera camera = minecraft.gameRenderer.getMainCamera();
+            int chunkRange = LightOverlay.getChunkRange();
+            
+            if (LightOverlay.showNumber) {
+                renderLevels(new PoseStack(), camera, playerPos, playerPosX, playerPosY, playerPosZ, chunkRange, collisionContext);
+            } else {
+                renderCrosses(poses, camera, playerPos, playerPosX, playerPosY, playerPosZ, chunkRange, collisionContext);
+            }
+        }
+    }
+    
+    private void renderLevels(PoseStack poses, Camera camera, BlockPos playerPos, int playerPosX, int playerPosY, int playerPosZ, int chunkRange, CollisionContext collisionContext) {
+        RenderSystem.enableTexture();
+        RenderSystem.depthMask(true);
+        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
+        BlockPos.MutableBlockPos downMutable = new BlockPos.MutableBlockPos();
+        MultiBufferSource.BufferSource source = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
+        for (Map.Entry<CubicChunkPos, Long2ByteMap> entry : ticker.CHUNK_MAP.entrySet()) {
+            CubicChunkPos chunkPos = entry.getKey();
+            if (LightOverlay.caching && (Mth.abs(chunkPos.x - playerPosX) > chunkRange || Mth.abs(chunkPos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(chunkPos.z - playerPosZ) > chunkRange)) {
+                continue;
+            }
+            for (Long2ByteMap.Entry objectEntry : entry.getValue().long2ByteEntrySet()) {
+                mutable.set(objectEntry.getLongKey());
+                if (mutable.closerThan(playerPos, LightOverlay.reach)) {
+                    if (isFrustumVisible(mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
+                        downMutable.set(mutable.getX(), mutable.getY() - 1, mutable.getZ());
+                        renderLevel(poses, source, camera, minecraft.level, mutable, downMutable, objectEntry.getByteValue(), collisionContext);
+                    }
+                }
+            }
+        }
+        RenderSystem.enableDepthTest();
+        source.endBatch();
+    }
+    
+    public void renderLevel(PoseStack poses, MultiBufferSource.BufferSource source, Camera camera, Level world, BlockPos pos, BlockPos down, byte level, CollisionContext collisionContext) {
+        String text = String.valueOf(level);
+        Font font = minecraft.font;
+        double cameraX = camera.getPosition().x;
+        double cameraY = camera.getPosition().y;
+        VoxelShape upperOutlineShape = world.getBlockState(down).getShape(world, down, collisionContext);
+        if (!upperOutlineShape.isEmpty())
+            cameraY += 1 - upperOutlineShape.max(Direction.Axis.Y);
+        double cameraZ = camera.getPosition().z;
+        poses.pushPose();
+        poses.translate(pos.getX() + 0.5 - cameraX, pos.getY() - cameraY + 0.005, pos.getZ() + 0.5 - cameraZ);
+        poses.mulPose(Vector3f.XP.rotationDegrees(90));
+//        poses.glNormal3f(0.0F, 1.0F, 0.0F);
+        float size = 0.07F;
+        poses.scale(-size, -size, size);
+        float float_3 = (float) (-font.width(text)) / 2.0F + 0.4f;
+        font.drawInBatch(text, float_3, -3.5f, level > LightOverlay.higherCrossLevel ? 0xff042404 : (LightOverlay.lowerCrossLevel >= 0 && level > LightOverlay.lowerCrossLevel ? 0xff0066ff : 0xff731111), false, poses.last().pose(), source, false, 0, 15728880);
+        poses.popPose();
+    }
+    
+    private void renderCrosses(PoseStack poses, Camera camera, BlockPos playerPos, int playerPosX, int playerPosY, int playerPosZ, int chunkRange, CollisionContext collisionContext) {
+        RenderSystem.enableDepthTest();
+        RenderSystem.disableTexture();
+        RenderSystem.disableBlend();
+        RenderSystem.setShader(GameRenderer::getPositionColorShader);
+        RenderSystem.lineWidth(LightOverlay.lineWidth);
+        Tesselator tesselator = Tesselator.getInstance();
+        BufferBuilder builder = tesselator.getBuilder();
+        builder.begin(VertexFormat.Mode.DEBUG_LINES, DefaultVertexFormat.POSITION_COLOR);
+        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
+    
+        System.out.println(ticker.CHUNK_MAP.size());
+        for (Map.Entry<CubicChunkPos, Long2ByteMap> entry : ticker.CHUNK_MAP.entrySet()) {
+            CubicChunkPos chunkPos = entry.getKey();
+            if (LightOverlay.caching && (Mth.abs(chunkPos.x - playerPosX) > chunkRange || Mth.abs(chunkPos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(chunkPos.z - playerPosZ) > chunkRange)) {
+                continue;
+            }
+            
+            for (Long2ByteMap.Entry objectEntry : entry.getValue().long2ByteEntrySet()) {
+                byte crossType = objectEntry.getByteValue();
+                mutable.set(objectEntry.getLongKey());
+                if (mutable.closerThan(playerPos, LightOverlay.reach)) {
+                    if (isFrustumVisible(mutable.getX(), mutable.getY(), mutable.getZ(), mutable.getX() + 1, mutable.getX() + 1, mutable.getX() + 1)) {
+                        int color = switch (crossType) {
+                            case LightOverlay.CROSS_RED -> LightOverlay.redColor;
+                            case LightOverlay.CROSS_YELLOW -> LightOverlay.yellowColor;
+                            default -> LightOverlay.secondaryColor;
+                        };
+                        renderCross(poses.last().pose(), builder, camera, minecraft.level, mutable, color, collisionContext);
+                    }
+                }
+            }
+        }
+        
+        tesselator.end();
+        RenderSystem.lineWidth(1.0F);
+        RenderSystem.enableBlend();
+        RenderSystem.enableTexture();
+    }
+    
+    public void renderCross(Matrix4f pose, BufferBuilder builder, Camera camera, Level world, BlockPos pos, int color, CollisionContext collisionContext) {
+        double cameraX = camera.getPosition().x;
+        double cameraY = camera.getPosition().y - .005D;
+        double blockOffset = 0;
+        VoxelShape upperOutlineShape = world.getBlockState(pos).getShape(world, pos, collisionContext);
+        if (!upperOutlineShape.isEmpty()) {
+            blockOffset += upperOutlineShape.max(Direction.Axis.Y);
+        }
+        double cameraZ = camera.getPosition().z;
+        
+        int red = (color >> 16) & 255;
+        int green = (color >> 8) & 255;
+        int blue = color & 255;
+        double x = pos.getX() - cameraX;
+        double y = pos.getY() - cameraY + blockOffset;
+        double z = pos.getZ() - cameraZ;
+        builder.vertex(x + .01, y, z + .01).color(red, green, blue, 255).endVertex();
+        builder.vertex(x + .99, y, z + .99).color(red, green, blue, 255).endVertex();
+        builder.vertex(x + .99, y, z + .01).color(red, green, blue, 255).endVertex();
+        builder.vertex(x + .01, y, z + .99).color(red, green, blue, 255).endVertex();
+    }
+    
+    private static final Supplier<MethodHandle> IS_FRUSTUM_VISIBLE = Suppliers.memoize(() -> {
+        try {
+            return MethodHandles.lookup().findStatic(Class.forName("me.shedaniel.lightoverlay." + ArchitecturyTarget.getCurrentTarget() + ".LightOverlayImpl"), "isFrustumVisible",
+                    MethodType.methodType(boolean.class, Frustum.class, double.class, double.class, double.class, double.class, double.class, double.class));
+        } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    });
+    
+    public boolean isFrustumVisible(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+        try {
+            return frustum == null || (boolean) IS_FRUSTUM_VISIBLE.get().invokeExact(frustum, minX, minY, minZ, maxX, maxY, maxZ);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+}
diff --git a/common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayTicker.java b/common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayTicker.java
new file mode 100644 (file)
index 0000000..fd0fe17
--- /dev/null
@@ -0,0 +1,272 @@
+package me.shedaniel.lightoverlay.common;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Maps;
+import it.unimi.dsi.fastutil.longs.Long2ByteMap;
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.tags.BlockTags;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.MobCategory;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LightLayer;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.function.Supplier;
+
+public class LightOverlayTicker {
+    private final Minecraft minecraft = Minecraft.getInstance();
+    private long ticks = 0;
+    private static int threadNumber = 0;
+    private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
+        Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
+        thread.setDaemon(true);
+        return thread;
+    });
+    public final Set<CubicChunkPos> POS = Collections.synchronizedSet(new HashSet<>());
+    public final Set<CubicChunkPos> CALCULATING_POS = Collections.synchronizedSet(new HashSet<>());
+    public final Map<CubicChunkPos, Long2ByteMap> CHUNK_MAP = Maps.newConcurrentMap();
+    private static final Supplier<EntityType<Entity>> TESTING_ENTITY_TYPE = Suppliers.memoize(() ->
+            EntityType.Builder.createNothing(MobCategory.MONSTER).sized(0f, 0f).noSave().build(null));
+    
+    public void queueChunk(CubicChunkPos pos) {
+        if (LightOverlay.enabled && LightOverlay.caching && !CALCULATING_POS.contains(pos)) {
+            POS.add(pos);
+        }
+    }
+    
+    public void tick(Minecraft minecraft) {
+        while (LightOverlay.enableOverlay.consumeClick())
+            LightOverlay.enabled = !LightOverlay.enabled;
+        
+        try {
+            ticks++;
+            if (minecraft.player == null || !LightOverlay.enabled) {
+                POS.clear();
+                CALCULATING_POS.clear();
+                EXECUTOR.getQueue().clear();
+                CHUNK_MAP.clear();
+            } else {
+                LocalPlayer player = minecraft.player;
+                ClientLevel world = minecraft.level;
+                CollisionContext collisionContext = CollisionContext.of(player);
+                
+                if (!LightOverlay.caching) {
+                    CALCULATING_POS.clear();
+                    POS.clear();
+                    CHUNK_MAP.clear();
+                    BlockPos playerPos = player.blockPosition();
+                    LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
+                    LayerLightEventListener sky = LightOverlay.showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
+                    BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos();
+                    Iterable<BlockPos> iterate = BlockPos.betweenClosed(playerPos.getX() - LightOverlay.reach, playerPos.getY() - LightOverlay.reach, playerPos.getZ() - LightOverlay.reach,
+                            playerPos.getX() + LightOverlay.reach, playerPos.getY() + LightOverlay.reach, playerPos.getZ() + LightOverlay.reach);
+                    Long2ByteMap chunkData = new Long2ByteOpenHashMap();
+                    CHUNK_MAP.put(new CubicChunkPos(0, 0, 0), chunkData);
+                    for (BlockPos blockPos : iterate) {
+                        downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
+                        if (LightOverlay.showNumber) {
+                            int level = getCrossLevel(blockPos, downPos, world, block, collisionContext);
+                            if (level >= 0) {
+                                chunkData.put(blockPos.asLong(), (byte) level);
+                            }
+                        } else {
+                            Biome biome = !LightOverlay.mushroom ? world.getBiome(blockPos) : null;
+                            byte type = getCrossType(blockPos, biome, downPos, world, block, sky, collisionContext);
+                            if (type != LightOverlay.CROSS_NONE) {
+                                chunkData.put(blockPos.asLong(), type);
+                            }
+                        }
+                    }
+                } else {
+                    var height = Mth.ceil(Minecraft.getInstance().level.getHeight() / 32.0);
+                    var start = Math.floorDiv(Minecraft.getInstance().level.getMinBuildHeight(), 32);
+                    int playerPosX = ((int) player.getX()) >> 4;
+                    int playerPosY = ((int) player.getY()) >> 5;
+                    int playerPosZ = ((int) player.getZ()) >> 4;
+                    var chunkRange = LightOverlay.getChunkRange();
+                    for (int chunkX = playerPosX - chunkRange; chunkX <= playerPosX + chunkRange; chunkX++) {
+                        for (int chunkY = Math.max(playerPosY - Math.max(1, chunkRange >> 1), start); chunkY <= playerPosY + Math.max(1, chunkRange >> 1) && chunkY <= start + height; chunkY++) {
+                            for (int chunkZ = playerPosZ - chunkRange; chunkZ <= playerPosZ + chunkRange; chunkZ++) {
+                                if (Mth.abs(chunkX - playerPosX) > chunkRange || Mth.abs(chunkY - playerPosY) > chunkRange || Mth.abs(chunkZ - playerPosZ) > chunkRange)
+                                    continue;
+                                CubicChunkPos chunkPos = new CubicChunkPos(chunkX, chunkY, chunkZ);
+                                if (!CHUNK_MAP.containsKey(chunkPos))
+                                    queueChunk(chunkPos);
+                            }
+                        }
+                    }
+                    for (int p = 0; p < 3; p++) {
+                        if (EXECUTOR.getQueue().size() >= Runtime.getRuntime().availableProcessors()) break;
+                        double d1 = Double.MAX_VALUE, d2 = Double.MAX_VALUE, d3 = Double.MAX_VALUE;
+                        CubicChunkPos c1 = null, c2 = null, c3 = null;
+                        synchronized (POS) {
+                            Iterator<CubicChunkPos> iterator = POS.iterator();
+                            while (iterator.hasNext()) {
+                                CubicChunkPos pos = iterator.next();
+                                if (Mth.abs(pos.x - playerPosX) > chunkRange || Mth.abs(pos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(pos.z - playerPosZ) > chunkRange || CALCULATING_POS.contains(pos)) {
+                                    iterator.remove();
+                                } else {
+                                    if (LightOverlay.renderer.isFrustumVisible(pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ())) {
+                                        int dx = Math.abs(pos.x - playerPosX);
+                                        int dy = Math.abs(pos.y - playerPosY) << 1;
+                                        int dz = Math.abs(pos.z - playerPosZ);
+                                        double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
+                                        if (distance < d1) {
+                                            d3 = d2;
+                                            d2 = d1;
+                                            d1 = distance;
+                                            c3 = c2;
+                                            c2 = c1;
+                                            c1 = pos;
+                                        } else if (distance < d2) {
+                                            d3 = d2;
+                                            d2 = distance;
+                                            c3 = c2;
+                                            c2 = pos;
+                                        } else if (distance < d3) {
+                                            d3 = distance;
+                                            c3 = pos;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        CubicChunkPos finalC1 = c1;
+                        CubicChunkPos finalC2 = c2;
+                        CubicChunkPos finalC3 = c3;
+                        if (finalC1 != null) {
+                            CALCULATING_POS.add(finalC1);
+                            POS.remove(finalC1);
+                            if (finalC2 != null) {
+                                CALCULATING_POS.add(finalC2);
+                                POS.remove(finalC2);
+                                if (finalC3 != null) {
+                                    CALCULATING_POS.add(finalC3);
+                                    POS.remove(finalC3);
+                                }
+                            }
+                            EXECUTOR.submit(() -> {
+                                int playerPosX1 = ((int) minecraft.player.getX()) >> 4;
+                                int playerPosY1 = ((int) minecraft.player.getY()) >> 5;
+                                int playerPosZ1 = ((int) minecraft.player.getZ()) >> 4;
+                                if (finalC1 != null) processChunk(finalC1, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
+                                if (finalC2 != null) processChunk(finalC2, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
+                                if (finalC3 != null) processChunk(finalC3, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
+                            });
+                        }
+                    }
+                    if (ticks % 50 == 0) {
+                        CHUNK_MAP.entrySet().removeIf(entry -> Mth.abs(entry.getKey().x - playerPosX) > chunkRange * 2 || Mth.abs(entry.getKey().y - playerPosY) > chunkRange * 2 || Mth.abs(entry.getKey().z - playerPosZ) > chunkRange * 2);
+                    }
+                }
+            }
+        } catch (Throwable throwable) {
+            LogManager.getLogger().throwing(throwable);
+        }
+    }
+    
+    private void processChunk(CubicChunkPos pos, int playerPosX, int playerPosY, int playerPosZ, CollisionContext context) {
+        CALCULATING_POS.remove(pos);
+        int chunkRange = LightOverlay.getChunkRange();
+        if (Mth.abs(pos.x - playerPosX) > chunkRange || Mth.abs(pos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(pos.z - playerPosZ) > chunkRange || POS.contains(pos)) {
+            return;
+        }
+        try {
+            calculateChunk(minecraft.level.getChunkSource().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), minecraft.level, pos, context);
+        } catch (Throwable throwable) {
+            LogManager.getLogger().throwing(throwable);
+        }
+    }
+    
+    private void calculateChunk(LevelChunk chunk, Level world, CubicChunkPos chunkPos, CollisionContext collisionContext) {
+        if (world != null && chunk != null) {
+            Long2ByteMap chunkData = new Long2ByteOpenHashMap();
+            LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
+            LayerLightEventListener sky = LightOverlay.showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
+            for (BlockPos pos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
+                BlockPos down = pos.below();
+                if (LightOverlay.showNumber) {
+                    int level = getCrossLevel(pos, down, chunk, block, collisionContext);
+                    if (level >= 0) {
+                        chunkData.put(pos.asLong(), (byte) level);
+                    }
+                } else {
+                    Biome biome = !LightOverlay.mushroom ? world.getBiome(pos) : null;
+                    byte type = getCrossType(pos, biome, down, chunk, block, sky, collisionContext);
+                    if (type != LightOverlay.CROSS_NONE) {
+                        chunkData.put(pos.asLong(), type);
+                    }
+                }
+            }
+            CHUNK_MAP.put(chunkPos, chunkData);
+        } else {
+            CHUNK_MAP.remove(chunkPos);
+        }
+    }
+    
+    public byte getCrossType(BlockPos pos, Biome biome, BlockPos down, BlockGetter world, LayerLightEventListener block, LayerLightEventListener sky, CollisionContext entityContext) {
+        BlockState blockBelowState = world.getBlockState(down);
+        BlockState blockUpperState = world.getBlockState(pos);
+        VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
+        if (!LightOverlay.underwater && !blockUpperState.getFluidState().isEmpty())
+            return LightOverlay.CROSS_NONE;
+        // Check if the outline is full
+        if (Block.isFaceFull(upperCollisionShape, Direction.UP))
+            return LightOverlay.CROSS_NONE;
+        // TODO: Not to hard code no redstone
+        if (blockUpperState.isSignalSource())
+            return LightOverlay.CROSS_NONE;
+        // Check if the collision has a bump
+        if (upperCollisionShape.max(Direction.Axis.Y) > 0)
+            return LightOverlay.CROSS_NONE;
+        if (blockUpperState.is(BlockTags.RAILS))
+            return LightOverlay.CROSS_NONE;
+        // Check block state allow spawning (excludes bedrock and barriers automatically)
+        if (!blockBelowState.isValidSpawn(world, down, TESTING_ENTITY_TYPE.get()))
+            return LightOverlay.CROSS_NONE;
+        if (!LightOverlay.mushroom && Biome.BiomeCategory.MUSHROOM == biome.getBiomeCategory())
+            return LightOverlay.CROSS_NONE;
+        int blockLightLevel = block.getLightValue(pos);
+        int skyLightLevel = sky.getLightValue(pos);
+        if (blockLightLevel > LightOverlay.higherCrossLevel)
+            return LightOverlay.CROSS_NONE;
+        if (skyLightLevel > LightOverlay.higherCrossLevel)
+            return LightOverlay.CROSS_YELLOW;
+        return LightOverlay.lowerCrossLevel >= 0 && blockLightLevel > LightOverlay.lowerCrossLevel ? LightOverlay.CROSS_SECONDARY : LightOverlay.CROSS_RED;
+    }
+    
+    public static int getCrossLevel(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener view, CollisionContext collisionContext) {
+        BlockState blockBelowState = world.getBlockState(down);
+        BlockState blockUpperState = world.getBlockState(pos);
+        VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, collisionContext);
+        VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, collisionContext);
+        if (!LightOverlay.underwater && !blockUpperState.getFluidState().isEmpty())
+            return -1;
+        if (!blockBelowState.getFluidState().isEmpty())
+            return -1;
+        if (blockBelowState.isAir())
+            return -1;
+        if (Block.isFaceFull(upperCollisionShape, Direction.DOWN))
+            return -1;
+        return view.getLightValue(pos);
+    }
+}
index b8a902a25d5821ba4d06014b871fe02791db85c0..72e531bca9e479a023b81e8dd9ffbdc7b9681f57 100755 (executable)
@@ -1,5 +1,5 @@
 plugins {
-    id "com.github.johnrengelman.shadow" version "5.0.0"
+    id "com.github.johnrengelman.shadow" version "7.0.0"
 }
 
 minecraft {
@@ -7,7 +7,6 @@ minecraft {
 }
 
 architectury {
-    transformerVersion = "2.0.9999"
     platformSetupLoomIde()
     fabric()
 }
@@ -20,26 +19,22 @@ processResources {
 }
 
 configurations {
-    shadow
-}
-
-repositories {
-    mavenLocal()
+    shadowCommon
 }
 
 dependencies {
     minecraft "com.mojang:minecraft:${rootProject.architectury.minecraft}"
     mappings minecraft.officialMojangMappings()
-    modCompile "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
+    modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
 
-    modCompile "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
+    modImplementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
     modImplementation("me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_config_version}") {
         exclude(group: "net.fabricmc.fabric-api")
     }
-    modImplementation("io.github.prospector:modmenu:${rootProject.modmenu_version}") {
+    modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") {
         transitive = false
     }
-    modCompile("me.shedaniel:architectury:${rootProject.architectury_version}:fabric")
+    modImplementation("dev.architectury:architectury-fabric:${rootProject.architectury_version}")
 
     implementation(project(path: ":common")) {
         transitive = false
@@ -47,15 +42,13 @@ dependencies {
     developmentFabric(project(path: ":common")) {
         transitive = false
     }
-    shadow(project(path: ":common", configuration: "transformProductionFabric")) {
+    shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) {
         transitive = false
     }
 }
 
 shadowJar {
-    relocate "me.shedaniel.lightoverlay.common", "me.shedaniel.lightoverlay.common.fabric"
-
-    configurations = [project.configurations.shadow]
+    configurations = [project.configurations.shadowCommon]
     classifier "shadow"
 }
 
index 56ba7060843159d79746937d20f66dcd2d8c62e0..58cd0f7934bc5d2144274d2d398a980f7dfb0f19 100644 (file)
@@ -1,16 +1,19 @@
 package me.shedaniel.lightoverlay.fabric;
 
+import com.mojang.blaze3d.vertex.PoseStack;
 import me.shedaniel.lightoverlay.common.LightOverlay;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
 import net.minecraft.client.renderer.culling.Frustum;
 
+import java.util.function.Consumer;
+
 public class LightOverlayImpl {
-    public static Runnable debugRenderer = () -> {};
+    public static Consumer<PoseStack> debugRenderer = poses -> {};
     
     public static void init() {
         LightOverlay.register();
-        WorldRenderEvents.AFTER_SETUP.register(context -> LightOverlay.frustum = context.frustum());
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> debugRenderer.run());
+        WorldRenderEvents.AFTER_SETUP.register(context -> LightOverlay.renderer.frustum = context.frustum());
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> debugRenderer.accept(context.matrixStack()));
     }
     
     public static boolean isFrustumVisible(Frustum frustum, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
index ac553e6e78157b1917fbd353d243864bf4dac8d4..446f8aff74bfdd2ee5373ca248a99b81f1ecb819 100644 (file)
@@ -2,6 +2,7 @@ package me.shedaniel.lightoverlay.fabric.mixin;
 
 import me.shedaniel.lightoverlay.common.CubicChunkPos;
 import me.shedaniel.lightoverlay.common.LightOverlay;
+import net.minecraft.client.multiplayer.ClientPacketListener;
 import net.minecraft.network.Connection;
 import net.minecraft.network.PacketListener;
 import net.minecraft.network.protocol.Packet;
@@ -9,6 +10,7 @@ import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
 import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
 import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
 import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
+import net.minecraft.util.Mth;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -17,21 +19,30 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 @Mixin(Connection.class)
 public class MixinClientConnection {
     @Inject(method = "genericsFtw", at = @At("HEAD"))
-    private static void handlePacket(Packet packet, PacketListener listener, CallbackInfo ci) {
-        if (packet instanceof ClientboundBlockUpdatePacket) {
-            LightOverlay.queueChunkAndNear(new CubicChunkPos(((ClientboundBlockUpdatePacket) packet).getPos()));
-        } else if (packet instanceof ClientboundSetChunkCacheCenterPacket) {
-            for (int y = 0; y <= 15; y++) {
-                LightOverlay.queueChunkAndNear(new CubicChunkPos(((ClientboundSetChunkCacheCenterPacket) packet).getX(), y, ((ClientboundSetChunkCacheCenterPacket) packet).getZ()));
-            }
-        } else if (packet instanceof ClientboundSectionBlocksUpdatePacket) {
-            for (int y = 0; y <= 15; y++) {
-                LightOverlay.queueChunkAndNear(new CubicChunkPos(((ClientboundSectionBlocksUpdatePacket) packet).sectionPos.getX(), y, ((ClientboundSectionBlocksUpdatePacket) packet).sectionPos.getZ()));
-            }
-        } else if (packet instanceof ClientboundLightUpdatePacket) {
-            for (int y = 0; y <= 15; y++) {
-                LightOverlay.queueChunk(new CubicChunkPos(((ClientboundLightUpdatePacket) packet).getX(), y, ((ClientboundLightUpdatePacket) packet).getZ()));
+    private static void handlePacket(Packet<?> packet, PacketListener listener, CallbackInfo ci) {
+        try {
+            if (!(listener instanceof ClientPacketListener packetListener)) return;
+            var level = packetListener.getLevel();
+            if (level == null) return;
+            if (packet instanceof ClientboundBlockUpdatePacket p) {
+                LightOverlay.queueChunkAndNear(new CubicChunkPos(p.getPos()));
+            } else if (packet instanceof ClientboundSetChunkCacheCenterPacket p) {
+                var height = Mth.ceil(level.getHeight() / 32.0);
+                var start = Math.floorDiv(level.getMinBuildHeight(), 32);
+                for (int y = start; y < start + height; y++) {
+                    LightOverlay.queueChunkAndNear(new CubicChunkPos(p.getX(), y, p.getZ()));
+                }
+            } else if (packet instanceof ClientboundSectionBlocksUpdatePacket p) {
+                LightOverlay.queueChunkAndNear(new CubicChunkPos(p.sectionPos.getX(), p.sectionPos.getY() >> 1, p.sectionPos.getZ()));
+            } else if (packet instanceof ClientboundLightUpdatePacket p) {
+                var height = Mth.ceil(level.getHeight() / 32.0);
+                var start = Math.floorDiv(level.getMinBuildHeight(), 32);
+                for (int y = start; y < start + height; y++) {
+                    LightOverlay.queueChunk(new CubicChunkPos(p.getX(), y, p.getZ()));
+                }
             }
+        } catch (Throwable throwable) {
+            new RuntimeException("Light Overlay failed to process packet", throwable).printStackTrace();
         }
     }
 }
index 93a18f6b462e4a0125b2dae1aae9df5e410d3f36..504209aad634db9cbc02f9cc957565c4b499f295 100755 (executable)
@@ -32,7 +32,7 @@
   },
   "depends": {
     "fabric": ">=0.29.1",
-    "architectury": ">=1.5.112",
-    "cloth-config2": ">=4.10.13"
+    "architectury": ">=2-",
+    "cloth-config2": ">=5-"
   }
 }
index 95ece2e25052c79de9a263f62ac8dbeed1cb46c6..fe2097e4cfcf3950f50741cdbb8cdb5b5ac78ffc 100644 (file)
@@ -7,7 +7,6 @@ configurations {
 }
 
 architectury {
-    transformerVersion = "2.0.9999"
     platformSetupLoomIde()
     forge()
 }
@@ -23,10 +22,6 @@ processResources {
     inputs.property "META-INF/mods.toml", project.version
 }
 
-repositories {
-    mavenLocal()
-}
-
 dependencies {
     minecraft("com.mojang:minecraft:${rootProject.architectury.minecraft}")
     mappings(minecraft.officialMojangMappings())
index 7e2573de3a822a03a1aafb57b4eb298811297e0d..b18dc0338b9b49731bfb512ac031b240744dca62 100755 (executable)
@@ -1,16 +1,16 @@
 org.gradle.jvmargs=-Xmx3G
 org.gradle.daemon=false
 
-mod_version=5.8.1
-minecraft_version=1.16.4
+mod_version=6.0.0
+minecraft_version=1.17
 
-architectury_version=1.5.112
+architectury_version=2.0.9
 
 # fabric
-fabric_loader_version=0.10.8
-fabric_api_version=0.29.1+1.16
-cloth_config_version=4.10.13
-modmenu_version=1.14.6+build.31
+fabric_loader_version=0.11.6
+fabric_api_version=0.35.1+1.17
+cloth_config_version=5.0.34
+modmenu_version=2.0.2
 
 # forge
 forge_version=35.1.7
\ No newline at end of file
index c4bb5b22e57436f80c99e0382ba237e617146765..d16afa09f3919e1aa7a0b478a558db5711eea407 100755 (executable)
@@ -1,4 +1,4 @@
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
index a232ac9e2b4c4f3bec988c1203a274ef8113264f..323a92b2c0469908ea2a4ed3fc34881b527abe6a 100644 (file)
@@ -11,5 +11,5 @@ rootProject.name = "light-overlay"
 
 include("common")
 include("fabric")
-include("forge")
+//include("forge")