From 60a0129fe9ff04c104ff4553f99c5619d42a56c7 Mon Sep 17 00:00:00 2001
From: Jordan <dordsor21@gmail.com>
Date: Sun, 15 May 2022 10:42:19 +0100
Subject: [PATCH 1/3] Correctly use yIndex when regenerating plots in certain
 world configurations (#3601)

- Fixes #3597
---
 .../java/com/plotsquared/bukkit/util/BukkitRegionManager.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java
index 74b5c1372..76725c4df 100644
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java
@@ -278,7 +278,7 @@ public class BukkitRegionManager extends RegionManager {
                                         int minY = value.getMin().getY();
                                         for (int yIndex = 0; yIndex < ids.length; yIndex++) {
                                             int y = yIndex + minY;
-                                            BaseBlock id = ids[y];
+                                            BaseBlock id = ids[yIndex];
                                             if (id != null) {
                                                 value.setBlock(x1, y, z1, id);
                                             } else {

From 0ffa22b7a6bbc11085bd00fcac10f4b363839611 Mon Sep 17 00:00:00 2001
From: Jordan <dordsor21@gmail.com>
Date: Sun, 15 May 2022 10:57:26 +0100
Subject: [PATCH 2/3] Deprecations to Queues (#3613)

* Deprecations and niceties
 - Deprecate ScopedQueueCoordinator as it is poorly named
 - Deprecate ChunkQueueCoordinator for complete removal as it is poorly designed (though still used)

* Add since tags
---
 .../com/plotsquared/core/queue/ChunkQueueCoordinator.java   | 6 +++++-
 .../com/plotsquared/core/queue/ScopedQueueCoordinator.java  | 6 +++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/Core/src/main/java/com/plotsquared/core/queue/ChunkQueueCoordinator.java b/Core/src/main/java/com/plotsquared/core/queue/ChunkQueueCoordinator.java
index ad68de264..65b35e8da 100644
--- a/Core/src/main/java/com/plotsquared/core/queue/ChunkQueueCoordinator.java
+++ b/Core/src/main/java/com/plotsquared/core/queue/ChunkQueueCoordinator.java
@@ -36,8 +36,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- * Queue that is limited to a single chunk
+ * Queue that is limited to a single chunk. It does not allow a delegate queue and should be treated as a cache for changes to
+ * be set to. Does not support tile entities or entities.
+ *
+ * @deprecated This class is poorly designed and will no longer be used in P2
  */
+@Deprecated(forRemoval = true, since = "TODO")
 public class ChunkQueueCoordinator extends ScopedQueueCoordinator {
 
     public final BiomeType[][][] biomeResult;
diff --git a/Core/src/main/java/com/plotsquared/core/queue/ScopedQueueCoordinator.java b/Core/src/main/java/com/plotsquared/core/queue/ScopedQueueCoordinator.java
index cba1f8a2a..0eda75df2 100644
--- a/Core/src/main/java/com/plotsquared/core/queue/ScopedQueueCoordinator.java
+++ b/Core/src/main/java/com/plotsquared/core/queue/ScopedQueueCoordinator.java
@@ -35,8 +35,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- * Queue that only sets blocks with a designated area
+ * Queue that only sets blocks with a designated X-Z area, will accept any Y values. Requires all blocks be set normalized in
+ * the x and z directions, i.e. starting from 0,0. An offset of the minimum point of the region will then be applied to x and z.
+ *
+ * @deprecated This should be renamed to NormalizedScopedQueueCoordinator or something.
  */
+@Deprecated(forRemoval = true, since = "TODO")
 public class ScopedQueueCoordinator extends DelegateQueueCoordinator {
 
     private final Location min;

From 98a07dad1b3f23f00315ea5200202abfc35ab85f Mon Sep 17 00:00:00 2001
From: Jordan <dordsor21@gmail.com>
Date: Sun, 15 May 2022 10:58:04 +0100
Subject: [PATCH 3/3] Fix plot analysis (#3618)

* Fix plot analysis
 - Stop using deprecated ChunkQueueCoordinator and create a new purpose-built coordinator
 - Generation is chunk-by-chunk thus the old blocks cache needs to be filled accordingly
 - Remove the **four** System#gc calls
 - Fixes #3464
 - Fix really weird dumb... maths? in ExpiryTask
 - Fixes #3600

* Add since annotation

* Address comments and maxY should be inclusive

* Annotate new queue as internal use only
---
 .../core/generator/HybridUtils.java           |  62 +++----
 .../core/plot/expiration/ExpiryTask.java      |   4 +-
 ...BlockArrayCacheScopedQueueCoordinator.java | 175 ++++++++++++++++++
 3 files changed, 207 insertions(+), 34 deletions(-)
 create mode 100644 Core/src/main/java/com/plotsquared/core/queue/BlockArrayCacheScopedQueueCoordinator.java

diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java
index 0eab11c71..a1b688151 100644
--- a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java
+++ b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java
@@ -41,7 +41,7 @@ import com.plotsquared.core.plot.flag.GlobalFlagContainer;
 import com.plotsquared.core.plot.flag.PlotFlag;
 import com.plotsquared.core.plot.flag.implementations.AnalysisFlag;
 import com.plotsquared.core.plot.world.PlotAreaManager;
-import com.plotsquared.core.queue.ChunkQueueCoordinator;
+import com.plotsquared.core.queue.BlockArrayCacheScopedQueueCoordinator;
 import com.plotsquared.core.queue.GlobalBlockQueue;
 import com.plotsquared.core.queue.QueueCoordinator;
 import com.plotsquared.core.util.ChunkManager;
@@ -83,6 +83,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 public class HybridUtils {
 
     private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + HybridUtils.class.getSimpleName());
+    private static final BlockState AIR = BlockTypes.AIR.getDefaultState();
 
     public static HybridUtils manager;
     public static Set<BlockVector2> regions;
@@ -136,6 +137,11 @@ public class HybridUtils {
          *
          */
         TaskManager.runTaskAsync(() -> {
+            final PlotArea area = this.plotAreaManager.getPlotArea(world, null);
+            if (!(area instanceof HybridPlotWorld hpw)) {
+                return;
+            }
+
             final BlockVector3 bot = region.getMinimumPoint();
             final BlockVector3 top = region.getMaximumPoint();
 
@@ -152,37 +158,28 @@ public class HybridUtils {
             final int height = area.getMaxGenHeight() - area.getMinGenHeight() + 1;
             final int minHeight = area.getMinGenHeight();
 
-            final PlotArea area = this.plotAreaManager.getPlotArea(world, null);
-
-            if (!(area instanceof HybridPlotWorld hpw)) {
-                return;
-            }
-
-            ChunkQueueCoordinator chunk = new ChunkQueueCoordinator(worldUtil.getWeWorld(world), bot, top, false);
-            hpw.getGenerator().generateChunk(chunk, hpw);
-
-            final BlockState airBlock = BlockTypes.AIR.getDefaultState();
-            final BlockState[][][] oldBlocks = chunk.getBlocks();
             final BlockState[][][] newBlocks = new BlockState[height][width][length];
-            for (final BlockState[][] newBlock : newBlocks) {
-                for (final BlockState[] blockStates : newBlock) {
-                    Arrays.fill(blockStates, airBlock);
-                }
-            }
-            for (final BlockState[][] oldBlock : oldBlocks) {
-                for (final BlockState[] blockStates : oldBlock) {
-                    Arrays.fill(blockStates, airBlock);
-                }
-            }
 
-            System.gc();
-            System.gc();
+            BlockArrayCacheScopedQueueCoordinator oldBlockQueue = new BlockArrayCacheScopedQueueCoordinator(
+                    Location.at("", region.getMinimumPoint().withY(hpw.getMinGenHeight())),
+                    Location.at("", region.getMaximumPoint().withY(hpw.getMaxGenHeight()))
+            );
+
+            region.getChunks().forEach(chunkPos -> {
+                int relChunkX = chunkPos.getX() - cbx;
+                int relChunkZ = chunkPos.getZ() - cbz;
+                oldBlockQueue.setOffsetX(relChunkX << 4);
+                oldBlockQueue.setOffsetZ(relChunkZ << 4);
+                hpw.getGenerator().generateChunk(oldBlockQueue, hpw);
+            });
+
+            final BlockState[][][] oldBlocks = oldBlockQueue.getBlockStates();
 
             QueueCoordinator queue = area.getQueue();
             queue.addReadChunks(region.getChunks());
-            queue.setChunkConsumer(blockVector2 -> {
-                int X = blockVector2.getX();
-                int Z = blockVector2.getZ();
+            queue.setChunkConsumer(chunkPos -> {
+                int X = chunkPos.getX();
+                int Z = chunkPos.getZ();
                 int minX;
                 if (X == cbx) {
                     minX = bx & 15;
@@ -220,6 +217,9 @@ public class HybridUtils {
                         for (int yIndex = 0; yIndex < height; yIndex++) {
                             int y = yIndex + minHeight;
                             BlockState block = queue.getBlock(xx, y, zz);
+                            if (block == null) {
+                                block = AIR;
+                            }
                             int xr = xb + x;
                             int zr = zb + z;
                             newBlocks[yIndex][xr][zr] = block;
@@ -240,10 +240,10 @@ public class HybridUtils {
                     for (int z = 0; z < length; z++) {
                         Set<BlockType> types = new HashSet<>();
                         for (int yIndex = 0; yIndex < height; yIndex++) {
-                            BlockState old = oldBlocks[yIndex][x][z];
+                            BlockState old = oldBlocks[yIndex][x][z]; // Nullable
                             try {
-                                BlockState now = newBlocks[yIndex][x][z];
-                                if (!old.equals(now)) {
+                                BlockState now = newBlocks[yIndex][x][z]; // Not null
+                                if (!now.equals(old) && !(old == null && now.getBlockType().equals(BlockTypes.AIR))) {
                                     changes[i]++;
                                 }
                                 if (now.getBlockType().getMaterial().isAir()) {
@@ -301,8 +301,6 @@ public class HybridUtils {
                 analysis.data_sd = (int) (MathMan.getSD(data, analysis.data) * 100);
                 analysis.air_sd = (int) (MathMan.getSD(air, analysis.air) * 100);
                 analysis.variety_sd = (int) (MathMan.getSD(variety, analysis.variety) * 100);
-                System.gc();
-                System.gc();
                 whenDone.value = analysis;
                 whenDone.run();
             });
diff --git a/Core/src/main/java/com/plotsquared/core/plot/expiration/ExpiryTask.java b/Core/src/main/java/com/plotsquared/core/plot/expiration/ExpiryTask.java
index 03a9c010b..c0e2b8a1a 100644
--- a/Core/src/main/java/com/plotsquared/core/plot/expiration/ExpiryTask.java
+++ b/Core/src/main/java/com/plotsquared/core/plot/expiration/ExpiryTask.java
@@ -75,9 +75,9 @@ public class ExpiryTask {
                     plots = plots != null ? plots : getPlotsToCheck();
                     int diff = settings.REQUIRED_PLOTS;
                     boolean min = true;
-                    if (settings.REQUIRED_PLOTS - plots.size() < settings.REQUIRED_PLOTS) {
+                    if (plots.size() > settings.REQUIRED_PLOTS) {
                         min = false;
-                        diff = settings.REQUIRED_PLOTS - plots.size();
+                        diff = plots.size() - settings.REQUIRED_PLOTS;
                     }
                     List<Long> entireList =
                             plots.stream().map(plot -> ExpireManager.IMP.getAge(plot, settings.DELETE_IF_OWNER_IS_UNKNOWN))
diff --git a/Core/src/main/java/com/plotsquared/core/queue/BlockArrayCacheScopedQueueCoordinator.java b/Core/src/main/java/com/plotsquared/core/queue/BlockArrayCacheScopedQueueCoordinator.java
new file mode 100644
index 000000000..043e6e0ff
--- /dev/null
+++ b/Core/src/main/java/com/plotsquared/core/queue/BlockArrayCacheScopedQueueCoordinator.java
@@ -0,0 +1,175 @@
+/*
+ *       _____  _       _    _____                                _
+ *      |  __ \| |     | |  / ____|                              | |
+ *      | |__) | | ___ | |_| (___   __ _ _   _  __ _ _ __ ___  __| |
+ *      |  ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
+ *      | |    | | (_) | |_ ____) | (_| | |_| | (_| | | |  __/ (_| |
+ *      |_|    |_|\___/ \__|_____/ \__, |\__,_|\__,_|_|  \___|\__,_|
+ *                                    | |
+ *                                    |_|
+ *            PlotSquared plot management system for Minecraft
+ *               Copyright (C) 2014 - 2022 IntellectualSites
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation, either version 3 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.plotsquared.core.queue;
+
+import com.plotsquared.core.location.Location;
+import com.plotsquared.core.util.AnnotationHelper;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.worldedit.function.pattern.Pattern;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.world.biome.BiomeType;
+import com.sk89q.worldedit.world.block.BaseBlock;
+import com.sk89q.worldedit.world.block.BlockState;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * {@link QueueCoordinator} that caches all blocks set to it in a given array of form BlockState[][][]. An offset can be
+ * applied to blocks set to it, and the scope limited. This should have blocks set to it one chunk at a time, based on the
+ * result of {@link BlockArrayCacheScopedQueueCoordinator#getMin()} and {@link BlockArrayCacheScopedQueueCoordinator#getMax()}.
+ * The min and max points of this queue are offset according to the minimum point given in the constructor, and the offsets set
+ * in {@link BlockArrayCacheScopedQueueCoordinator#setOffsetX(int)} and
+ * {@link BlockArrayCacheScopedQueueCoordinator#setOffsetZ(int)}
+ */
+@AnnotationHelper.ApiDescription(info = "Internal use only. Subject to change at any time and created for specific use cases.")
+public class BlockArrayCacheScopedQueueCoordinator extends ScopedQueueCoordinator {
+
+    private final BlockState[][][] blockStates;
+    private final int height;
+    private final int width;
+    private final int length;
+    private final int minY;
+    private final int maxY;
+    private final int scopeMinX;
+    private final int scopeMinZ;
+    private final int scopeMaxX;
+    private final int scopeMaxZ;
+    private int offsetX = 0;
+    private int offsetZ = 0;
+    /**
+     * Construct a new instance
+     *
+     * @param min Inclusive location of the minimum point to limit the scope to.
+     * @param max Inclusive location of the maximum point to limit the scope to.
+     * @since TODO
+     */
+    public BlockArrayCacheScopedQueueCoordinator(Location min, Location max) {
+        super(null, min, max);
+        this.width = max.getX() - min.getX() + 1;
+        this.length = max.getZ() - min.getZ() + 1;
+        this.minY = min.getY();
+        this.maxY = max.getY();
+        this.height = maxY - minY + 1;
+
+        this.scopeMinX = min.getX() & 15;
+        this.scopeMinZ = min.getZ() & 15;
+        this.scopeMaxX = scopeMinX + width;
+        this.scopeMaxZ = scopeMinZ + length;
+        this.blockStates = new BlockState[height][width][length];
+    }
+
+    public BlockState[][][] getBlockStates() {
+        return blockStates;
+    }
+
+    @Override
+    public boolean setBlock(int x, final int y, int z, final @NonNull BlockState id) {
+        x += offsetX;
+        z += offsetZ;
+        if (x >= scopeMinX && x < scopeMaxX && y >= minY && y <= maxY && z >= scopeMinZ && z < scopeMaxZ) {
+            blockStates[y - minY][x - scopeMinX][z - scopeMinZ] = id;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean setBlock(final int x, final int y, final int z, @NonNull final Pattern pattern) {
+        int rx = x + offsetX;
+        int rz = z + offsetZ;
+        if (rx >= scopeMinX && rx < scopeMaxX && y >= minY && y <= maxY && rz >= scopeMinZ && rz < scopeMaxZ) {
+            BlockState state = pattern
+                    .applyBlock(super.getMin().getBlockVector3().add(BlockVector3.at(x, y, z)))
+                    .toImmutableState();
+            blockStates[y - minY][rx - scopeMinX][rz - scopeMinZ] = state;
+        }
+        return false;
+    }
+
+    @Override
+    public @NonNull Location getMin() {
+        return super.getMin().add(offsetX - scopeMinX, 0, offsetZ - scopeMinZ);
+    }
+
+    @Override
+    public @NonNull Location getMax() {
+        return getMin().add(15, 0, 15).withY(maxY);
+    }
+
+    @Override
+    public boolean setBlock(int x, int y, int z, final @NonNull BaseBlock id) {
+        x += offsetX;
+        z += offsetZ;
+        if (x >= scopeMinX && x < scopeMaxX && y >= minY && y <= maxY && z >= scopeMinZ && z < scopeMaxZ) {
+            blockStates[y - minY][x][z] = id.toImmutableState();
+        }
+        return false;
+    }
+
+    @Override
+    public @Nullable BlockState getBlock(final int x, final int y, final int z) {
+        if (x >= 0 && x < width && y >= minY && y <= maxY && z >= 0 && z < length) {
+            return blockStates[y - minY][x][z];
+        }
+        return null;
+    }
+
+    public void setOffsetX(final int offsetX) {
+        this.offsetX = offsetX;
+    }
+
+    public void setOffsetZ(final int offsetZ) {
+        this.offsetZ = offsetZ;
+    }
+
+    @Override
+    public int size() {
+        return height * width * length;
+    }
+
+    @Override
+    public boolean setBiome(final int x, final int z, @NonNull final BiomeType biome) {
+        //do nothing
+        return false;
+    }
+
+    @Override
+    public boolean setBiome(final int x, final int y, final int z, @NonNull final BiomeType biome) {
+        //do nothing
+        return false;
+    }
+
+    @Override
+    public void fillBiome(final BiomeType biome) {
+        //do nothing
+    }
+
+    @Override
+    public boolean setTile(final int x, final int y, final int z, @NonNull final CompoundTag tag) {
+        //do nothing
+        return false;
+    }
+
+}