Server Implementation
Paper
Server Version
Paper 26.2 (build 121, 26.2-121-main@a2a42c5; also reproduced on build 112). WorldEdit 7.4.5 (7.4.5+7590-b8dc4c1).
Describe the bug
Since moving the server from Paper 1.21.11 to Paper 26.2, every /plot setbiome <biome> and every plot clear (/plot delete → /plot confirm) prints one ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24 per chunk of the plot. The world is a standard -64..319 world (24 sections).
The throw site is WorldEdit's adapter-26.2 PaperweightAdapter.setBiome, but the cause is PlotSquared asking it to set a biome at y = 320, one block above the world. Two independent code paths do this:
1. /plot setbiome — Plot.getRegions() uses the exclusive max build height as an inclusive region bound.
|
if (!this.isMerged()) { |
|
Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight()); |
|
Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight()); |
|
CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()); |
if (!this.isMerged()) {
Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight());
Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight()); // exclusive (default versionMaxHeight() + 1 == 320)
CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3());
PlotArea.getMaxBuildHeight() is documented "Exclusive" and defaults to versionMaxHeight() + 1 (320). The merged-plot branch of the same method already subtracts 1 (int maxHeight = getArea().getMaxBuildHeight() - 1;, line 2563); the unmerged branch does not. RegionManager.setBiome → WorldUtil.setBiomes then does region.forEach(bv -> world.setBiome(bv, biome)), and CuboidRegion iteration is inclusive, so the last layer visited is y = 320.
2. Plot clear — BukkitQueueCoordinator.enqueue rebuilds biome Y from the layer index without the negative-section offset.
|
int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j); |
|
int y = ChunkUtil.getY(layer, j); |
// blocks (line 180) — correct
int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j);
...
// biomes (line 201) — missing the offset
int y = ChunkUtil.getY(layer, j);
LocalChunk.setBiome stores biomes with getLayerIndex(y) = (y >> 4) - minSection, exactly like blocks. Reading them back without adding minSection back shifts every biome up by 64 blocks in a -64..319 world: a biome queued for y = -64 is written at y = 0, and a biome queued for y ≥ 256 is written at y ≥ 320. HybridPlotManager.clearPlot queues setBiomeCuboid(minGenHeight .. maxGenHeight), so layer 20 (queued y = 256) is the first to land at y = 320 → index 24.
Why it only shows up on 26.2. WorldEdit's adapter-1.21.11 implemented setBiome via vanilla ChunkAccess.setBiome, which clamps Y into the world. The adapter-26.1/adapter-26.2 implementation does chunk.getSection(chunk.getSectionIndex(y)).getBiomes() directly, with no clamp. So both PlotSquared bugs are older than 26.2 and were previously silently swallowed by the clamp (which also means path 2 has been writing biomes 64 blocks too high, and never writing y < 0, in every extended-height world).
Since BukkitChunkCoordinator catches the throwable per chunk and continues, the visible effect is: /plot setbiome applies the biome to every valid layer but skips the refreshChunk for the chunk that threw (players see the old biome until the chunk reloads); a plot clear sets the biome for y ≥ 0 only and skips the tile-entity / entity restoration for that chunk.
To Reproduce
- Paper 26.2 + WorldEdit 7.4.5 + PlotSquared, plot world with default heights (
world.max_height: 320, world.max_gen_height: 319).
- Stand in an unmerged plot, run
/plot setbiome flower_forest.
- Console prints one
ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24 per chunk.
/plot delete + /plot confirm on the same plot prints the same trace per chunk (via BukkitQueueCoordinator.lambda$enqueue$2).
Expected behaviour
No biome writes outside the world's Y range; the region top should be getMaxBuildHeight() - 1 like the merged branch, and the queue readback should be ChunkUtil.getY(layer + localChunk.getMinSection(), j) like the block loop directly above it.
Screenshots / Videos
No response
Error log (if applicable)
[WARN]: java.lang.ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24
[WARN]: at net.minecraft.world.level.chunk.ChunkAccess.getSection(ChunkAccess.java:231)
[WARN]: at WorldEdit.jar//com.sk89q.worldedit.bukkit.adapter.impl.v26_2.PaperweightAdapter.setBiome(PaperweightAdapter.java:445)
[WARN]: at WorldEdit.jar//com.sk89q.worldedit.bukkit.BukkitWorld.setBiome(BukkitWorld.java:589)
[WARN]: at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.lambda$setBiomes$0(WorldUtil.java:230)
[WARN]: at java.base/java.lang.Iterable.forEach(Iterable.java:75)
[WARN]: at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.setBiomes(WorldUtil.java:230)
[WARN]: at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.setBiome(WorldUtil.java:83)
[WARN]: at PlotSquared.jar//com.plotsquared.core.util.RegionManager.lambda$setBiome$2(RegionManager.java:409)
[WARN]: at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitChunkCoordinator.run(BukkitChunkCoordinator.java:187)
[WARN]: at PlotSquared.jar//com.plotsquared.bukkit.util.task.BukkitPlotSquaredTask.runTask(BukkitPlotSquaredTask.java:39)
[WARN]: at PlotSquared.jar//com.plotsquared.core.util.task.PlotSquaredTask.run(PlotSquaredTask.java:44)
[WARN]: at org.bukkit.craftbukkit.scheduler.CraftTask.run(CraftTask.java:78)
[WARN]: at org.bukkit.craftbukkit.scheduler.CraftScheduler.mainThreadHeartbeat(CraftScheduler.java:474)
[WARN]: at net.minecraft.server.MinecraftServer.tickChildren(MinecraftServer.java:1767)
Plot clear variant:
[WARN]: java.lang.ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24
[WARN]: at net.minecraft.world.level.chunk.ChunkAccess.getSection(ChunkAccess.java:231)
[WARN]: at WorldEdit.jar//com.sk89q.worldedit.bukkit.adapter.impl.v26_2.PaperweightAdapter.setBiome(PaperweightAdapter.java:445)
[WARN]: at WorldEdit.jar//com.sk89q.worldedit.bukkit.BukkitWorld.setBiome(BukkitWorld.java:589)
[WARN]: at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitQueueCoordinator.lambda$enqueue$2(BukkitQueueCoordinator.java:203)
[WARN]: at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitChunkCoordinator.run(BukkitChunkCoordinator.java:187)
Plot Debugpaste
Not attached — this is a code-level report; both lines are unchanged on main (b49bd002) and nothing in the paste would add to it. Happy to provide one if you still want it.
PlotSquared Version
Observed on a 7.5.11-SNAPSHOT build; the offending lines are identical in 7.5.11 (where the line numbers in the trace match exactly), 7.6.0 and current main @ b49bd00.
Checklist
Anything else?
Proposed fix (two one-line changes):
--- a/Core/src/main/java/com/plotsquared/core/plot/Plot.java
+++ b/Core/src/main/java/com/plotsquared/core/plot/Plot.java
@@ public @NonNull Set<CuboidRegion> getRegions() {
if (!this.isMerged()) {
Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight());
- Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight());
+ Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight() - 1);
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
@@ for (int layer = 0; layer < localChunk.getBiomes().length; layer++) {
int x = sx + ChunkUtil.getX(j);
- int y = ChunkUtil.getY(layer, j);
+ int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j);
int z = sz + ChunkUtil.getZ(j);
WorldEdit could additionally guard PaperweightAdapter.setBiome against out-of-range Y (as the 1.21.11 adapter effectively did), but the out-of-range coordinates originate here.
Server Implementation
Paper
Server Version
Paper 26.2 (build 121,
26.2-121-main@a2a42c5; also reproduced on build 112). WorldEdit 7.4.5 (7.4.5+7590-b8dc4c1).Describe the bug
Since moving the server from Paper 1.21.11 to Paper 26.2, every
/plot setbiome <biome>and every plot clear (/plot delete→/plot confirm) prints oneArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24per chunk of the plot. The world is a standard -64..319 world (24 sections).The throw site is WorldEdit's
adapter-26.2PaperweightAdapter.setBiome, but the cause is PlotSquared asking it to set a biome at y = 320, one block above the world. Two independent code paths do this:1.
/plot setbiome—Plot.getRegions()uses the exclusive max build height as an inclusive region bound.PlotSquared/Core/src/main/java/com/plotsquared/core/plot/Plot.java
Lines 2483 to 2486 in b49bd00
PlotArea.getMaxBuildHeight()is documented "Exclusive" and defaults toversionMaxHeight() + 1(320). The merged-plot branch of the same method already subtracts 1 (int maxHeight = getArea().getMaxBuildHeight() - 1;, line 2563); the unmerged branch does not.RegionManager.setBiome→WorldUtil.setBiomesthen doesregion.forEach(bv -> world.setBiome(bv, biome)), andCuboidRegioniteration is inclusive, so the last layer visited is y = 320.2. Plot clear —
BukkitQueueCoordinator.enqueuerebuilds biome Y from the layer index without the negative-section offset.PlotSquared/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
Line 180 in b49bd00
PlotSquared/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
Line 201 in b49bd00
LocalChunk.setBiomestores biomes withgetLayerIndex(y) = (y >> 4) - minSection, exactly like blocks. Reading them back without addingminSectionback shifts every biome up by 64 blocks in a -64..319 world: a biome queued for y = -64 is written at y = 0, and a biome queued for y ≥ 256 is written at y ≥ 320.HybridPlotManager.clearPlotqueuessetBiomeCuboid(minGenHeight .. maxGenHeight), so layer 20 (queued y = 256) is the first to land at y = 320 → index 24.Why it only shows up on 26.2. WorldEdit's
adapter-1.21.11implementedsetBiomevia vanillaChunkAccess.setBiome, which clamps Y into the world. Theadapter-26.1/adapter-26.2implementation doeschunk.getSection(chunk.getSectionIndex(y)).getBiomes()directly, with no clamp. So both PlotSquared bugs are older than 26.2 and were previously silently swallowed by the clamp (which also means path 2 has been writing biomes 64 blocks too high, and never writing y < 0, in every extended-height world).Since
BukkitChunkCoordinatorcatches the throwable per chunk and continues, the visible effect is:/plot setbiomeapplies the biome to every valid layer but skips therefreshChunkfor the chunk that threw (players see the old biome until the chunk reloads); a plot clear sets the biome for y ≥ 0 only and skips the tile-entity / entity restoration for that chunk.To Reproduce
world.max_height: 320,world.max_gen_height: 319)./plot setbiome flower_forest.ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24per chunk./plot delete+/plot confirmon the same plot prints the same trace per chunk (viaBukkitQueueCoordinator.lambda$enqueue$2).Expected behaviour
No biome writes outside the world's Y range; the region top should be
getMaxBuildHeight() - 1like the merged branch, and the queue readback should beChunkUtil.getY(layer + localChunk.getMinSection(), j)like the block loop directly above it.Screenshots / Videos
No response
Error log (if applicable)
Plot clear variant:
Plot Debugpaste
Not attached — this is a code-level report; both lines are unchanged on
main(b49bd002) and nothing in the paste would add to it. Happy to provide one if you still want it.PlotSquared Version
Observed on a
7.5.11-SNAPSHOTbuild; the offending lines are identical in 7.5.11 (where the line numbers in the trace match exactly), 7.6.0 and currentmain@ b49bd00.Checklist
main, see above)Anything else?
Proposed fix (two one-line changes):
WorldEdit could additionally guard
PaperweightAdapter.setBiomeagainst out-of-range Y (as the 1.21.11 adapter effectively did), but the out-of-range coordinates originate here.