diff --git a/src/main/java/com/moulberry/axiom/integration/Box.java b/src/main/java/com/moulberry/axiom/integration/Box.java new file mode 100644 index 0000000..3bf51b4 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/Box.java @@ -0,0 +1,68 @@ +package com.moulberry.axiom.integration; + +import net.minecraft.core.BlockPos; +import org.jetbrains.annotations.Nullable; + +public record Box(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + + @Nullable + public Box tryCombine(Box other) { + if (this.completelyOverlaps(other)) { + return this; + } + + if (other.completelyOverlaps(this)) { + return other; + } + + if (other.minX == this.minX && other.maxX == this.maxX) { + if (other.minY == this.minY && other.maxY == this.maxY) { + if (areLineSegmentsContinuous(other.minZ, other.maxZ, this.minZ, this.maxZ)) { + return new Box( + other.minX, other.minY, Math.min(other.minZ, this.minZ), + other.maxX, other.maxY, Math.max(other.maxZ, this.maxZ) + ); + } + } else if (other.minZ == this.minZ && other.maxZ == this.maxZ) { + if (areLineSegmentsContinuous(other.minY, other.maxY, this.minY, this.maxY)) { + return new Box( + other.minX, Math.min(other.minY, this.minY), other.minZ, + other.maxX, Math.max(other.maxY, this.maxY), other.maxZ + ); + } + } + } else if (other.minY == this.minY && other.maxY == this.maxY && + other.minZ == this.minZ && other.maxZ == this.maxZ) { + if (areLineSegmentsContinuous(other.minX, other.maxX, this.minX, this.maxX)) { + return new Box( + Math.min(other.minX, this.minX), other.minY, other.minZ, + Math.max(other.maxX, this.maxX), other.maxY, other.maxZ + ); + } + } + + return null; // Not able to combine + } + + public boolean completelyOverlaps(Box other) { + return this.minX() <= other.minX() && this.minY() <= other.minY() && this.minZ() <= other.minZ() && + this.maxX() >= other.maxX() && this.maxY() >= other.maxY() && this.maxZ() >= other.maxZ(); + } + + public boolean contains(int x, int y, int z) { + return this.minX() <= x && this.minY() <= y && this.minZ() <= z && + this.maxX() >= x && this.maxY() >= y && this.maxZ() >= z; + } + + private static boolean areLineSegmentsContinuous(int min1, int max1, int min2, int max2) { + int size1 = max1 - min1 + 1; + int size2 = max2 - min2 + 1; + + float mid1 = (min1 + max1); + float mid2 = (min2 + max2); + + float midDiff = Math.abs(mid1 - mid2); + return midDiff <= size1 + size2; + } + +} diff --git a/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java b/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java new file mode 100644 index 0000000..cf7d685 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/SectionPermissionChecker.java @@ -0,0 +1,137 @@ +package com.moulberry.axiom.integration; + +import java.util.List; + +public interface SectionPermissionChecker { + + boolean allAllowed(); + boolean noneAllowed(); + boolean allowed(int x, int y, int z); + Box bounds(); + + static SectionPermissionChecker fromAllowedBoxes(List allowed) { + if (allowed.isEmpty()) return NONE_ALLOWED; + + if (allowed.size() == 1) { + Box allowedBox = allowed.get(0); + if (allowedBox.completelyOverlaps(FULL_BOUNDS)) { + return ALL_ALLOWED; + } else { + return new AllAllowedInBox(allowedBox); + } + } + + int minBoundsX = 15; + int minBoundsY = 15; + int minBoundsZ = 15; + int maxBoundsX = 0; + int maxBoundsY = 0; + int maxBoundsZ = 0; + + for (Box box : allowed) { + minBoundsX = Math.min(box.minX(), minBoundsX); + minBoundsY = Math.min(box.minY(), minBoundsY); + minBoundsZ = Math.min(box.minZ(), minBoundsZ); + maxBoundsX = Math.max(box.maxX(), maxBoundsX); + maxBoundsY = Math.max(box.maxY(), maxBoundsY); + maxBoundsZ = Math.max(box.maxZ(), maxBoundsZ); + } + + return new AllAllowedBoxes(new Box(minBoundsX, minBoundsY, minBoundsZ, maxBoundsX, maxBoundsY, maxBoundsZ), allowed); + } + + record AllAllowedInBox(Box box) implements SectionPermissionChecker { + @Override + public boolean allAllowed() { + return true; + } + + @Override + public boolean noneAllowed() { + return false; + } + + @Override + public boolean allowed(int x, int y, int z) { + return true; + } + + @Override + public Box bounds() { + return box; + } + } + + record AllAllowedBoxes(Box bounds, List allowed) implements SectionPermissionChecker { + @Override + public boolean allAllowed() { + return false; + } + + @Override + public boolean noneAllowed() { + return false; + } + + @Override + public boolean allowed(int x, int y, int z) { + for (Box box : this.allowed) { + if (box.contains(x, y, z)) return true; + } + return false; + } + + @Override + public Box bounds() { + return this.bounds; + } + } + + Box FULL_BOUNDS = new Box(0, 0, 0, 15, 15, 15); + SectionPermissionChecker ALL_ALLOWED = new SectionPermissionChecker() { + @Override + public boolean allAllowed() { + return true; + } + + @Override + public boolean noneAllowed() { + return false; + } + + @Override + public boolean allowed(int x, int y, int z) { + return true; + } + + @Override + public Box bounds() { + return FULL_BOUNDS; + } + }; + + + Box EMPTY_BOUNDS = new Box(0, 0, 0, 0, 0, 0); + SectionPermissionChecker NONE_ALLOWED = new SectionPermissionChecker() { + @Override + public boolean allAllowed() { + return false; + } + + @Override + public boolean noneAllowed() { + return true; + } + + @Override + public boolean allowed(int x, int y, int z) { + return false; + } + + @Override + public Box bounds() { + return EMPTY_BOUNDS; + } + }; + +} diff --git a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java index 2ebc218..e2d51e7 100644 --- a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java +++ b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegration.java @@ -1,6 +1,7 @@ package com.moulberry.axiom.integration.plotsquared; +import com.moulberry.axiom.integration.SectionPermissionChecker; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; @@ -30,4 +31,11 @@ public class PlotSquaredIntegration { return PlotSquaredIntegrationImpl.isPlotWorld(world); } + public static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) { + if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) { + return SectionPermissionChecker.ALL_ALLOWED; + } + return PlotSquaredIntegrationImpl.checkSection(player, world, sectionX, sectionY, sectionZ); + } + } diff --git a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java index 7f2cfd7..c74f3b3 100644 --- a/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java +++ b/src/main/java/com/moulberry/axiom/integration/plotsquared/PlotSquaredIntegrationImpl.java @@ -1,5 +1,7 @@ package com.moulberry.axiom.integration.plotsquared; +import com.moulberry.axiom.integration.Box; +import com.moulberry.axiom.integration.SectionPermissionChecker; import com.plotsquared.bukkit.player.BukkitPlayer; import com.plotsquared.bukkit.util.BukkitUtil; import com.plotsquared.core.PlotSquared; @@ -8,17 +10,20 @@ import com.plotsquared.core.location.Location; import com.plotsquared.core.permissions.Permission; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.PlotArea; +import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.plot.flag.implementations.BreakFlag; import com.plotsquared.core.plot.flag.implementations.DoneFlag; import com.plotsquared.core.plot.flag.types.BlockTypeWrapper; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.block.BlockType; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.List; -import java.util.WeakHashMap; +import java.util.*; /* * PlotSquared, a land and world management plugin for Minecraft. @@ -117,6 +122,7 @@ public class PlotSquaredIntegrationImpl { } private static final WeakHashMap plotWorldCache = new WeakHashMap<>(); + static boolean isPlotWorld(World world) { if (plotWorldCache.containsKey(world)) { return plotWorldCache.get(world); @@ -134,7 +140,85 @@ public class PlotSquaredIntegrationImpl { plotWorldCache.put(world, isPlotWorld); return isPlotWorld; - } -} + static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) { + int minX = sectionX * 16; + int minY = sectionY * 16; + int minZ = sectionZ * 16; + int maxX = sectionX * 16 + 15; + int maxY = sectionY * 16 + 15; + int maxZ = sectionZ * 16 + 15; + + PlotArea[] plotAreas = PlotSquared.get().getPlotAreaManager().getPlotAreas(world.getName(), new CuboidRegion( + BlockVector3.at(minX, minY, minZ), + BlockVector3.at(maxX, maxY, maxZ) + )); + + if (plotAreas.length == 0) { + return SectionPermissionChecker.ALL_ALLOWED; + } + + Set checkedPlots = new HashSet<>(); + List allowed = new ArrayList<>(); + + for (PlotArea plotArea : plotAreas) { + for (int px = minX; px <= maxX; px += 15) { + for (int py = minY; py <= maxY; py += 15) { + for (int pz = minZ; pz <= maxZ; pz += 15) { + PlotId pid = plotArea.getPlotManager().getPlotId(px, py, pz); + if (pid == null) continue; + Plot plot = plotArea.getOwnedPlotAbs(pid); + if (plot == null) continue; + + if (!checkedPlots.add(plot)) continue; + + if (!plot.hasOwner()) continue; + if (!plot.isAdded(player.getUniqueId())) continue; + if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) continue; + + Location bottom = plot.getBottomAbs(); + Location top = plot.getTopAbs(); + + int minPlotX = Math.max(Math.min(bottom.getX(), top.getX()), minX); + int minPlotY = Math.max(Math.min(bottom.getY(), top.getY()), minY); + int minPlotZ = Math.max(Math.min(bottom.getZ(), top.getZ()), minZ); + int maxPlotX = Math.min(Math.max(bottom.getX(), top.getX()), maxX); + int maxPlotY = Math.min(Math.max(bottom.getY(), top.getY()), maxY); + int maxPlotZ = Math.min(Math.max(bottom.getZ(), top.getZ()), maxZ); + + if (minPlotX <= minX && minPlotY <= minY && minPlotZ <= minZ && + maxPlotX >= maxX && maxPlotY >= maxY && maxPlotZ >= maxZ) { + return SectionPermissionChecker.ALL_ALLOWED; + } + + allowed.add(new Box(minPlotX - minX, minPlotY - minY, minPlotZ - minZ, + maxPlotX - minX, maxPlotY - minY, maxPlotZ - minZ)); + } + } + } + } + + // Combine + main: + while (allowed.size() >= 2) { + for (int i = 0; i < allowed.size() - 1; i++) { + Box first = allowed.get(i); + for (int j = i + 1; j < allowed.size(); j++) { + Box second = allowed.get(j); + + Box combined = first.tryCombine(second); + if (combined != null) { + allowed.remove(j); + allowed.remove(i); + allowed.add(combined); + continue main; + } + } + } + break; + } + + return SectionPermissionChecker.fromAllowedBoxes(allowed); + } +} \ No newline at end of file diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 64f1911..2c2cacb 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -6,6 +6,8 @@ import com.moulberry.axiom.buffer.BiomeBuffer; import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.event.AxiomModifyWorldEvent; +import com.moulberry.axiom.integration.SectionPermissionChecker; +import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import net.minecraft.core.BlockPos; @@ -35,6 +37,7 @@ import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LightEngine; import org.bukkit.Bukkit; +import org.bukkit.Location; import xyz.jpenilla.reflectionremapper.ReflectionRemapper; import java.lang.reflect.InvocationTargetException; @@ -116,6 +119,11 @@ public class SetBlockBufferPacketListener { continue; } + SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz); + if (checker != null && checker.noneAllowed()) { + continue; + } + LevelChunk chunk = world.getChunk(cx, cz); LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy)); @@ -144,9 +152,28 @@ public class SetBlockBufferPacketListener { Short2ObjectMap blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey()); - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { + int minX = 0; + int minY = 0; + int minZ = 0; + int maxX = 15; + int maxY = 15; + int maxZ = 15; + + if (checker != null) { + minX = checker.bounds().minX(); + minY = checker.bounds().minY(); + minZ = checker.bounds().minZ(); + maxX = checker.bounds().maxX(); + maxY = checker.bounds().maxY(); + maxZ = checker.bounds().maxZ(); + if (checker.allAllowed()) { + checker = null; + } + } + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { BlockState blockState = container.get(x, y, z); if (blockState == emptyState) continue; @@ -158,6 +185,8 @@ public class SetBlockBufferPacketListener { continue; } + if (checker != null && !checker.allowed(x, y, z)) continue; + BlockState old = section.setBlockState(x, y, z, blockState, true); if (blockState != old) { sectionChanged = true; @@ -284,6 +313,9 @@ public class SetBlockBufferPacketListener { var holder = registry.getHolder(biome); if (holder.isPresent()) { + if (!PlotSquaredIntegration.canPlaceBlock(player.getBukkitEntity(), + new Location(player.getBukkitEntity().getWorld(), x+1, y+1, z+1))) return; + container.set(x & 3, y & 3, z & 3, holder.get()); changedChunks.add(chunk); }