From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 7 May 2020 19:17:36 -0400
Subject: [PATCH] Fix Light Command

This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
fix all light data in the chunks.

diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -0,0 +0,0 @@ import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
 import org.bukkit.entity.Player;
 
 import java.io.File;
@@ -0,0 +0,0 @@ public class PaperCommand extends Command {
     public PaperCommand(String name) {
         super(name);
         this.description = "Paper related commands";
-        this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo | syncloadinfo]";
+        this.usageMessage = "/paper [heap | entity | reload | version | debug | dumpwaiting | chunkinfo | syncloadinfo | fixlight]";
         this.setPermission("bukkit.command.paper");
     }
 
     @Override
     public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
         if (args.length <= 1)
-            return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo", "syncloadinfo");
+            return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug", "dumpwaiting", "chunkinfo", "syncloadinfo", "fixlight");
 
         switch (args[0].toLowerCase(Locale.ENGLISH))
         {
@@ -0,0 +0,0 @@ public class PaperCommand extends Command {
             case "syncloadinfo":
                 this.doSyncLoadInfo(sender, args);
                 break;
+            case "fixlight":
+                this.doFixLight(sender, args);
+                break;
             case "ver":
             case "version":
                 Command ver = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version");
@@ -0,0 +0,0 @@ public class PaperCommand extends Command {
         return true;
     }
 
+    private void doFixLight(CommandSender sender, String[] args) {
+        if (!(sender instanceof Player)) {
+            sender.sendMessage("Only players can use this command");
+            return;
+        }
+        int radius = 2;
+        if (args.length > 1) {
+            try {
+                radius = Math.min(5, Integer.parseInt(args[1]));
+            } catch (Exception e) {
+                sender.sendMessage("Not a number");
+                return;
+            }
+
+        }
+
+        CraftPlayer player = (CraftPlayer) sender;
+        EntityPlayer handle = player.getHandle();
+        net.minecraft.server.WorldServer world = (WorldServer) handle.world;
+        LightEngineThreaded lightengine = world.getChunkProvider().getLightEngine();
+
+        BlockPosition center = MCUtil.toBlockPosition(player.getLocation());
+        Deque<ChunkCoordIntPair> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
+        updateLight(sender, world, lightengine, queue);
+    }
+
+    private void updateLight(CommandSender sender, WorldServer world, LightEngineThreaded lightengine, Deque<ChunkCoordIntPair> queue) {
+        ChunkCoordIntPair coord = queue.poll();
+        if (coord == null) {
+            sender.sendMessage("All Chunks Light updated");
+            return;
+        }
+        world.getChunkProvider().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
+            if (ex != null) {
+                sender.sendMessage("Error loading chunk " + coord);
+                updateLight(sender, world, lightengine, queue);
+                return;
+            }
+            Chunk chunk = (Chunk) either.left().orElse(null);
+            if (chunk == null) {
+                updateLight(sender, world, lightengine, queue);
+                return;
+            }
+            lightengine.a(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
+            sender.sendMessage("Updating Light " + coord);
+            int cx = chunk.getPos().x << 4;
+            int cz = chunk.getPos().z << 4;
+            for (int y = 0; y < world.getHeight(); y++) {
+                for (int x = 0; x < 16; x++) {
+                    for (int z = 0; z < 16; z++) {
+                        BlockPosition pos = new BlockPosition(cx + x, y, cz + z);
+                        lightengine.a(pos);
+                    }
+                }
+            }
+            lightengine.queueUpdate();
+            PlayerChunk visibleChunk = world.getChunkProvider().playerChunkMap.getVisibleChunk(chunk.coordinateKey);
+            if (visibleChunk != null) {
+                world.getChunkProvider().playerChunkMap.addLightTask(visibleChunk, () -> {
+                    MinecraftServer.getServer().processQueue.add(() -> {
+                        visibleChunk.sendPacketToTrackedPlayers(new PacketPlayOutLightUpdate(chunk.getPos(), lightengine, true), false);
+                        updateLight(sender, world, lightengine, queue);
+                    });
+                });
+            } else {
+                updateLight(sender, world, lightengine, queue);
+            }
+            lightengine.a(world.paperConfig.lightQueueSize);
+        }, MinecraftServer.getServer());
+    }
+
     private void doSyncLoadInfo(CommandSender sender, String[] args) {
         if (!SyncLoadFinder.ENABLED) {
             sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.");
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -0,0 +0,0 @@ public class PlayerChunk {
 
     }
 
+    public void sendPacketToTrackedPlayers(Packet<?> packet, boolean flag) { a(packet, flag); } // Paper - OBFHELPER
     private void a(Packet<?> packet, boolean flag) {
         // Paper start - per player view distance
         // there can be potential desync with player's last mapped section and the view distance map, so use the
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     private final ChunkTaskQueueSorter p;
     private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxWorldGen;
     final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxMain; // Paper - private -> package private
+    // Paper start
+    final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxLight;
+    public void addLightTask(PlayerChunk playerchunk, Runnable run) {
+        this.mailboxLight.a(ChunkTaskQueueSorter.a(playerchunk, run));
+    }
+    // Paper end
     public final WorldLoadListener worldLoadListener;
     public final PlayerChunkMap.a chunkDistanceManager; public final PlayerChunkMap.a getChunkMapDistanceManager() { return this.chunkDistanceManager; } // Paper - OBFHELPER
     private final AtomicInteger u;
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         Mailbox<Runnable> mailbox = Mailbox.a("main", iasynctaskhandler::a);
 
         this.worldLoadListener = worldloadlistener;
-        ThreadedMailbox<Runnable> threadedmailbox1 = ThreadedMailbox.a(executor, "light");
+        ThreadedMailbox<Runnable> lightthreaded; ThreadedMailbox<Runnable> threadedmailbox1 = lightthreaded = ThreadedMailbox.a(executor, "light"); // Paper
 
         this.p = new ChunkTaskQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE);
         this.mailboxWorldGen = this.p.a(threadedmailbox, false);
         this.mailboxMain = this.p.a(mailbox, false);
+        this.mailboxLight = this.p.a(lightthreaded, false);// Paper
         this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false));
         this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); this.chunkDistanceManager.chunkMap = this; // Paper
         this.l = supplier;
