Expand City and bound plot search

This commit is contained in:
2026-09-27 08:40:04 -07:00
parent 99f6195542
commit 4e459ec9fa
8 changed files with 244 additions and 17 deletions

View File

@@ -0,0 +1,43 @@
/*
This file is part of Metropolis.
Metropolis is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Metropolis 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
*/
package com.majinnaibu.bukkitplugins.metropolis;
public final class CityRegionBounds {
private CityRegionBounds() {}
public static Cuboid expandToContain(
Cuboid existing, Cuboid requested, Iterable<Cuboid> occupiedRegions) {
int minX = Math.min(existing.minX, requested.minX);
int minY = Math.min(existing.minY, requested.minY);
int minZ = Math.min(existing.minZ, requested.minZ);
int maxX = Math.max(existing.maxX, requested.maxX);
int maxY = Math.max(existing.maxY, requested.maxY);
int maxZ = Math.max(existing.maxZ, requested.maxZ);
for (Cuboid occupied : occupiedRegions) {
minX = Math.min(minX, occupied.minX);
minY = Math.min(minY, occupied.minY);
minZ = Math.min(minZ, occupied.minZ);
maxX = Math.max(maxX, occupied.maxX);
maxY = Math.max(maxY, occupied.maxY);
maxZ = Math.max(maxZ, occupied.maxZ);
}
return new Cuboid(minX, minY, minZ, maxX, maxY, maxZ);
}
}

View File

@@ -209,7 +209,7 @@ public class MetropolisPlugin extends JavaPlugin {
plotOffsetX = safeGetIntFromConfig(config, "plot.offsetX");
plotOffsetY = safeGetIntFromConfig(config, "plot.offsetY");
plotOffsetZ = safeGetIntFromConfig(config, "plot.offsetZ");
maxLevels = optionalIntFromConfig(config, "plot.maxLevels", "maxLevels", 1);
maxLevels = optionalIntFromConfig(config, "plot.maxLevels", 1);
generateFloor = safeGetBooleanFromConfig(config, "plot.floor.generate");
floorMaterial = safeGetMaterialFromConfig(config, "plot.floor.material");
spaceAboveFloor = safeGetIntFromConfig(config, "plot.floor.clearSpaceAbove");
@@ -919,14 +919,26 @@ public class MetropolisPlugin extends JavaPlugin {
int ring = 0;
int min = -ring;
int max = ring - (plotMultiplier - 1);
boolean done = false;
int levelCount = getEffectiveMaxLevels();
if (levelCount == 0) {
getLogger().severe("No configured plot level fits within the world's build height.");
return null;
}
while (!done) {
var worldBorder = world.getWorldBorder();
Location borderCenter = worldBorder.getCenter();
int maxSearchRing =
PlotWorldBorderBounds.maximumSearchRing(
worldBorder.getSize(),
borderCenter.getX(),
borderCenter.getZ(),
plotOffsetX,
plotOffsetZ,
gridSizeX,
gridSizeZ,
plotMultiplier);
while (ring <= maxSearchRing) {
row = min;
col = min;
@@ -988,13 +1000,9 @@ public class MetropolisPlugin extends JavaPlugin {
ring++;
min = -ring;
max = ring - (plotMultiplier - 1);
if (ring > 256) {
done = true;
}
}
getLogger()
.severe("Unable to find an available supported plot within the allocation search area.");
getLogger().severe("Unable to find an available supported plot before the world border.");
return null;
}
@@ -1004,7 +1012,17 @@ public class MetropolisPlugin extends JavaPlugin {
}
private boolean isAvailableSupportedPlot(int row, int col, int plotMultiplier, int level) {
return !areBlocksOccupied(row, col, plotMultiplier, level)
Location borderCenter = world.getWorldBorder().getCenter();
Cuboid candidateGrid =
new Cuboid(
getGridMin(row, col, plotMultiplier, level),
getGridMax(row, col, plotMultiplier, level));
return PlotWorldBorderBounds.contains(
candidateGrid,
world.getWorldBorder().getSize(),
borderCenter.getX(),
borderCenter.getZ())
&& !areBlocksOccupied(row, col, plotMultiplier, level)
&& PlotLevelSupport.hasCompleteSupport(
row, col, plotMultiplier, level, this::isGeneratedLogicalBlock);
}
@@ -1027,11 +1045,19 @@ public class MetropolisPlugin extends JavaPlugin {
if (cityRegion instanceof ProtectedCuboidRegion) {
ProtectedCuboidRegion region = (ProtectedCuboidRegion) cityRegion;
BlockVector3 min;
BlockVector3 max;
min = getPlotMin(-size / 2, -size / 2, 1);
max = getPlotMax(size / 2, size / 2, 1);
Cuboid requestedCityBounds =
new Cuboid(getPlotMin(-size / 2, -size / 2, 1), getPlotMax(size / 2, size / 2, 1));
List<Cuboid> occupiedBounds = new ArrayList<>();
for (Plot plot : _occupiedPlots) {
occupiedBounds.add(plot.getCuboid());
}
if (_spawnCuboid != null) {
occupiedBounds.add(_spawnCuboid);
}
Cuboid cityBounds =
CityRegionBounds.expandToContain(_cityCuboid, requestedCityBounds, occupiedBounds);
BlockVector3 min = cityBounds.getMin();
BlockVector3 max = cityBounds.getMax();
ProtectedCuboidRegion resizedRegion = new ProtectedCuboidRegion(region.getId(), min, max);
resizedRegion.copyFrom(region);
@@ -1233,6 +1259,7 @@ public class MetropolisPlugin extends JavaPlugin {
homeNumber,
newHomeRegion.getMinimumPoint(),
newHomeRegion.getMaximumPoint());
resizeCityRegion();
_currentHomes.put(playerId, homeNumber);
saveCurrentHomes();
@@ -1383,6 +1410,7 @@ public class MetropolisPlugin extends JavaPlugin {
regionManager.addRegion(reservedRegion);
_occupiedPlots.add(Plot.get(reservedRegion));
resizeCityRegion();
saveRegions();
}

View File

@@ -0,0 +1,59 @@
/*
This file is part of Metropolis.
Metropolis is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Metropolis 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
*/
package com.majinnaibu.bukkitplugins.metropolis;
public final class PlotWorldBorderBounds {
private PlotWorldBorderBounds() {}
public static int maximumSearchRing(
double borderSize,
double borderCenterX,
double borderCenterZ,
int offsetX,
int offsetZ,
int gridSizeX,
int gridSizeZ,
int plotMultiplier) {
if (borderSize <= 0 || gridSizeX <= 0 || gridSizeZ <= 0 || plotMultiplier < 1) {
return 0;
}
double halfBorder = borderSize / 2;
double farthestX = halfBorder + Math.abs(borderCenterX - offsetX);
double farthestZ = halfBorder + Math.abs(borderCenterZ - offsetZ);
double cellRadius = Math.max(farthestX / gridSizeX, farthestZ / gridSizeZ);
long bound = (long) Math.ceil(cellRadius) + plotMultiplier + 1L;
return (int) Math.min(bound, Integer.MAX_VALUE);
}
public static boolean contains(Cuboid bounds, double borderSize, double centerX, double centerZ) {
if (borderSize <= 0) {
return false;
}
double halfBorder = borderSize / 2;
double borderMinX = centerX - halfBorder;
double borderMaxX = centerX + halfBorder;
double borderMinZ = centerZ - halfBorder;
double borderMaxZ = centerZ + halfBorder;
return bounds.minX + 0.5 >= borderMinX
&& bounds.maxX + 0.5 <= borderMaxX
&& bounds.minZ + 0.5 >= borderMinZ
&& bounds.maxZ + 0.5 <= borderMaxZ;
}
}

View File

@@ -0,0 +1,44 @@
/*
This file is part of Metropolis.
Metropolis is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Metropolis 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
*/
package com.majinnaibu.bukkitplugins.metropolis;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
class CityRegionBoundsTest {
@Test
void expandsExistingCityToIncludeElevatedHomesAndSpawnWithoutShrinking() {
Cuboid existingCity = new Cuboid(-20, 62, -20, 20, 71, 20);
Cuboid requestedCity = new Cuboid(-40, 62, -40, 40, 71, 40);
Cuboid elevatedHome = new Cuboid(10, 104, 10, 41, 113, 41);
Cuboid spawn = new Cuboid(2, 62, 2, 69, 81, 69);
Cuboid expanded =
CityRegionBounds.expandToContain(existingCity, requestedCity, List.of(elevatedHome, spawn));
assertEquals(-40, expanded.minX);
assertEquals(62, expanded.minY);
assertEquals(-40, expanded.minZ);
assertEquals(69, expanded.maxX);
assertEquals(113, expanded.maxY);
assertEquals(69, expanded.maxZ);
}
}

View File

@@ -0,0 +1,45 @@
/*
This file is part of Metropolis.
Metropolis is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Metropolis 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
*/
package com.majinnaibu.bukkitplugins.metropolis;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class PlotWorldBorderBoundsTest {
@Test
void searchBoundFollowsTheWorldBorderInsteadOfAnArbitraryRingCount() {
int maximumRing = PlotWorldBorderBounds.maximumSearchRing(60_000_000, 0, 0, 0, 0, 36, 36, 1);
assertTrue(maximumRing > 256);
}
@Test
void accountsForAnOffsetBorderCenterAndMultiplier() {
int maximumRing = PlotWorldBorderBounds.maximumSearchRing(512, 100, -100, 64, -32, 36, 34, 2);
assertTrue(maximumRing > 10);
}
@Test
void requiresTheCompleteGridFootprintInsideTheBorder() {
assertTrue(PlotWorldBorderBounds.contains(new Cuboid(-9, 0, -9, 9, 10, 9), 20, 0, 0));
assertFalse(PlotWorldBorderBounds.contains(new Cuboid(-11, 0, -9, 9, 10, 9), 20, 0, 0));
}
}