Apply offsets to the logical plot grid

This commit is contained in:
2026-09-27 08:21:22 -07:00
parent 8d99c2e67f
commit 60c3d41e50
7 changed files with 234 additions and 40 deletions

View File

@@ -98,6 +98,9 @@ public class MetropolisPlugin extends JavaPlugin {
private int plotSizeX = 24;
private int plotSizeY = 256;
private int plotSizeZ = 24;
private int plotOffsetX;
private int plotOffsetY;
private int plotOffsetZ;
private int gridSizeX = 28;
private int gridSizeY = 256;
private int maxLevels = 1;
@@ -146,6 +149,7 @@ public class MetropolisPlugin extends JavaPlugin {
private Cuboid _cityCuboid = null;
private ProtectedRegion _spawnRegion = null;
private ProtectedRegion _cityRegion = null;
private PlotGridLayout plotGridLayout;
@Override
public void onDisable() {
@@ -201,6 +205,9 @@ public class MetropolisPlugin extends JavaPlugin {
plotSizeX = safeGetIntFromConfig(config, "plot.sizeX");
plotSizeY = optionalIntFromConfig(config, "plot.sizeY", 256);
plotSizeZ = safeGetIntFromConfig(config, "plot.sizeZ");
plotOffsetX = safeGetIntFromConfig(config, "plot.offsetX");
plotOffsetY = safeGetIntFromConfig(config, "plot.offsetY");
plotOffsetZ = safeGetIntFromConfig(config, "plot.offsetZ");
maxLevels = optionalIntFromConfig(config, "plot.maxLevels", "maxLevels", 1);
generateFloor = safeGetBooleanFromConfig(config, "plot.floor.generate");
floorMaterial = safeGetMaterialFromConfig(config, "plot.floor.material");
@@ -329,9 +336,20 @@ public class MetropolisPlugin extends JavaPlugin {
if (streetWidth < 0 || avenueWidth < 0) {
throw new IllegalArgumentException("Road widths must not be negative.");
}
gridSizeX = plotSizeX + avenueWidth;
gridSizeY = Math.max(plotSizeY + Math.max(streetWidth, avenueWidth), 1);
gridSizeZ = plotSizeZ + streetWidth;
plotGridLayout =
new PlotGridLayout(
plotSizeX,
plotSizeY,
plotSizeZ,
streetWidth,
avenueWidth,
plotOffsetX,
plotOffsetY,
plotOffsetZ,
roadLevel);
gridSizeX = plotGridLayout.gridSizeX();
gridSizeY = plotGridLayout.gridSizeY();
gridSizeZ = plotGridLayout.gridSizeZ();
regionManager =
WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(world));
@@ -622,7 +640,10 @@ public class MetropolisPlugin extends JavaPlugin {
plotSizeZ,
avenueWidth,
streetWidth,
roadLevel);
roadLevel,
plotOffsetX,
plotOffsetY,
plotOffsetZ);
if (!SpawnLayout.withinBuildHeight(spawnCuboid, world.getMinHeight(), world.getMaxHeight())) {
getLogger()
.severe(
@@ -1037,11 +1058,7 @@ public class MetropolisPlugin extends JavaPlugin {
}
public BlockVector3 getPlotMin(int row, int col, int plotMultiplier, int level) {
BlockVector3 gridMin = getGridMin(row, col, plotMultiplier, level);
BlockVector3 bv =
BlockVector3.at(
gridMin.x() + avenueWidth / 2, getLevelStartY(level), gridMin.z() + streetWidth / 2);
BlockVector3 bv = plotGridLayout.plotMin(row, col, level);
getLogger().info(String.format("getPlotMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
@@ -1051,13 +1068,7 @@ public class MetropolisPlugin extends JavaPlugin {
}
public BlockVector3 getPlotMax(int row, int col, int plotMultiplier, int level) {
BlockVector3 gridMax = getGridMax(row, col, plotMultiplier, level);
BlockVector3 bv =
BlockVector3.at(
gridMax.x() - (avenueWidth - avenueWidth / 2),
getLevelStartY(level) + plotSizeY - 1,
gridMax.z() - (streetWidth - streetWidth / 2));
BlockVector3 bv = plotGridLayout.plotMax(row, col, plotMultiplier, plotMultiplier, level);
getLogger().info(String.format("getPlotMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
@@ -1067,7 +1078,7 @@ public class MetropolisPlugin extends JavaPlugin {
}
public BlockVector3 getGridMin(int row, int col, int plotMultiplier, int level) {
BlockVector3 bv = BlockVector3.at(col * gridSizeX, getLevelStartY(level), row * gridSizeZ);
BlockVector3 bv = plotGridLayout.gridMin(row, col, level);
getLogger().info(String.format("getGridMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
@@ -1077,31 +1088,29 @@ public class MetropolisPlugin extends JavaPlugin {
}
public BlockVector3 getGridMax(int row, int col, int plotMultiplier, int level) {
BlockVector3 bv =
BlockVector3.at(
(col + plotMultiplier) * gridSizeX - 1,
getLevelStartY(level) + plotSizeY - 1,
(row + plotMultiplier) * gridSizeZ - 1);
BlockVector3 bv = plotGridLayout.gridMax(row, col, plotMultiplier, plotMultiplier, level);
getLogger().info(String.format("getGridMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
private int getPlotXFromMin(Cuboid cuboid) {
return (cuboid.minX - avenueWidth / 2) / gridSizeX;
return plotGridLayout.plotXIndexFromMin(cuboid);
}
private int getPlotZFromMin(Cuboid cuboid) {
return (cuboid.minZ - streetWidth / 2) / gridSizeZ;
return plotGridLayout.plotZIndexFromMin(cuboid);
}
private int getLevelPitchY() {
return plotSizeY + Math.max(streetWidth, avenueWidth);
return plotGridLayout.gridSizeY();
}
private int getEffectiveMaxLevels() {
int availableStreetLevels = getAvailableLevelsAt(roadLevel);
int streetBaseY = getLevelStartY(0);
int avenueBaseY = streetBaseY + avenueLevel - roadLevel;
int availableStreetLevels = getAvailableLevelsAt(streetBaseY);
int availableAvenueLevels =
avenueWidth > 0 ? getAvailableLevelsAt(avenueLevel) : availableStreetLevels;
avenueWidth > 0 ? getAvailableLevelsAt(avenueBaseY) : availableStreetLevels;
int maxWorldLevels = Math.min(availableStreetLevels, availableAvenueLevels);
return clampMaxLevels(maxLevels, maxWorldLevels);
}
@@ -1119,7 +1128,7 @@ public class MetropolisPlugin extends JavaPlugin {
}
private int getLevelStartY(int level) {
return roadLevel + level * getLevelPitchY();
return plotGridLayout.levelStartY(level);
}
private void setHomeOccupied(
@@ -1289,7 +1298,7 @@ public class MetropolisPlugin extends JavaPlugin {
private List<AvenueStairwayLayout.Step> getUpperRoadOpeningsForPlot(
Cuboid plotCuboid, int roadY, RoadLayout.RoadType roadType) {
int baseY = roadType == RoadLayout.RoadType.AVENUE ? avenueLevel : roadLevel;
int baseY = (roadType == RoadLayout.RoadType.AVENUE ? avenueLevel : roadLevel) + plotOffsetY;
if (!areStairsEnabled(roadType) || roadY <= baseY) {
return List.of();
}
@@ -1325,7 +1334,7 @@ public class MetropolisPlugin extends JavaPlugin {
avenueStairsEveryNBlocks,
avenueWidth,
avenueStairWidth,
avenueLevel + lowerLevel * getLevelPitchY(),
avenueLevel + plotOffsetY + lowerLevel * getLevelPitchY(),
getLevelPitchY(),
3);
}
@@ -1338,7 +1347,7 @@ public class MetropolisPlugin extends JavaPlugin {
streetStairsEveryNBlocks,
streetWidth,
streetStairWidth,
roadLevel + lowerLevel * getLevelPitchY(),
roadLevel + plotOffsetY + lowerLevel * getLevelPitchY(),
getLevelPitchY(),
3);
}

View File

@@ -0,0 +1,114 @@
/*
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 com.sk89q.worldedit.math.BlockVector3;
public final class PlotGridLayout {
private final int plotSizeX;
private final int plotSizeY;
private final int plotSizeZ;
private final int streetWidth;
private final int avenueWidth;
private final int offsetX;
private final int offsetY;
private final int offsetZ;
private final int roadLevel;
private final int gridSizeX;
private final int gridSizeY;
private final int gridSizeZ;
public PlotGridLayout(
int plotSizeX,
int plotSizeY,
int plotSizeZ,
int streetWidth,
int avenueWidth,
int offsetX,
int offsetY,
int offsetZ,
int roadLevel) {
if (plotSizeX < 1 || plotSizeY < 1 || plotSizeZ < 1 || streetWidth < 0 || avenueWidth < 0) {
throw new IllegalArgumentException(
"Plot dimensions must be positive and road widths nonnegative");
}
this.plotSizeX = plotSizeX;
this.plotSizeY = plotSizeY;
this.plotSizeZ = plotSizeZ;
this.streetWidth = streetWidth;
this.avenueWidth = avenueWidth;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
this.roadLevel = roadLevel;
gridSizeX = Math.addExact(plotSizeX, avenueWidth);
gridSizeY = Math.addExact(plotSizeY, Math.max(streetWidth, avenueWidth));
gridSizeZ = Math.addExact(plotSizeZ, streetWidth);
}
public BlockVector3 gridMin(int row, int col, int level) {
return BlockVector3.at(
offsetX + col * gridSizeX, levelStartY(level), offsetZ + row * gridSizeZ);
}
public BlockVector3 gridMax(int row, int col, int multiplierX, int multiplierZ, int level) {
return BlockVector3.at(
offsetX + (col + multiplierX) * gridSizeX - 1,
levelStartY(level) + plotSizeY - 1,
offsetZ + (row + multiplierZ) * gridSizeZ - 1);
}
public BlockVector3 plotMin(int row, int col, int level) {
BlockVector3 gridMin = gridMin(row, col, level);
return BlockVector3.at(
gridMin.x() + avenueWidth / 2, gridMin.y(), gridMin.z() + streetWidth / 2);
}
public BlockVector3 plotMax(int row, int col, int multiplierX, int multiplierZ, int level) {
BlockVector3 gridMax = gridMax(row, col, multiplierX, multiplierZ, level);
return BlockVector3.at(
gridMax.x() - (avenueWidth - avenueWidth / 2),
gridMax.y(),
gridMax.z() - (streetWidth - streetWidth / 2));
}
public int plotXIndexFromMin(Cuboid plot) {
return Math.floorDiv(plot.minX - offsetX - avenueWidth / 2, gridSizeX);
}
public int plotZIndexFromMin(Cuboid plot) {
return Math.floorDiv(plot.minZ - offsetZ - streetWidth / 2, gridSizeZ);
}
public int levelStartY(int level) {
return roadLevel + offsetY + level * gridSizeY;
}
public int gridSizeX() {
return gridSizeX;
}
public int gridSizeY() {
return gridSizeY;
}
public int gridSizeZ() {
return gridSizeZ;
}
}

View File

@@ -29,7 +29,10 @@ public final class SpawnLayout {
int plotSizeZ,
int avenueWidth,
int streetWidth,
int roadLevel) {
int roadLevel,
int offsetX,
int offsetY,
int offsetZ) {
if (sizeX < 1 || sizeY < 1 || sizeZ < 1 || avenueWidth < 0 || streetWidth < 0) {
throw new IllegalArgumentException(
"Spawn multipliers must be positive and road widths nonnegative");
@@ -42,10 +45,11 @@ public final class SpawnLayout {
int maxInsetX = avenueWidth - minX;
int maxInsetZ = streetWidth - minZ;
int maxX = Math.subtractExact(Math.multiplyExact(sizeX, gridSizeX) - 1, maxInsetX);
int maxY = Math.addExact(roadLevel, Math.multiplyExact(sizeY, plotSizeY) - 1);
int baseY = Math.addExact(roadLevel, offsetY);
int maxY = Math.addExact(baseY, Math.multiplyExact(sizeY, plotSizeY) - 1);
int maxZ = Math.subtractExact(Math.multiplyExact(sizeZ, gridSizeZ) - 1, maxInsetZ);
return new Cuboid(minX, roadLevel, minZ, maxX, maxY, maxZ);
return new Cuboid(offsetX + minX, baseY, offsetZ + minZ, offsetX + maxX, maxY, offsetZ + maxZ);
}
public static boolean withinBuildHeight(Cuboid cuboid, int minHeight, int maxHeight) {

View File

@@ -0,0 +1,55 @@
/*
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 com.sk89q.worldedit.math.BlockVector3;
import org.junit.jupiter.api.Test;
class PlotGridLayoutTest {
@Test
void zeroOffsetsPreserveTheExistingPlotGrid() {
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 4, 4, 0, 0, 0, 62);
assertEquals(BlockVector3.at(0, 62, 0), grid.gridMin(0, 0, 0));
assertEquals(BlockVector3.at(35, 71, 35), grid.gridMax(0, 0, 1, 1, 0));
assertEquals(BlockVector3.at(2, 62, 2), grid.plotMin(0, 0, 0));
assertEquals(BlockVector3.at(33, 71, 33), grid.plotMax(0, 0, 1, 1, 0));
}
@Test
void offsetsShiftGridAndPlotsOnAllAxesIncludingHigherLevels() {
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 2, 6, -7, 3, 11, 62);
assertEquals(BlockVector3.at(-45, 97, -23), grid.gridMin(-1, -1, 2));
assertEquals(BlockVector3.at(30, 106, 44), grid.gridMax(-1, -1, 2, 2, 2));
assertEquals(BlockVector3.at(-42, 97, -22), grid.plotMin(-1, -1, 2));
assertEquals(BlockVector3.at(27, 106, 43), grid.plotMax(-1, -1, 2, 2, 2));
}
@Test
void mapsNegativePlotBoundsBackToTheirLogicalIndices() {
PlotGridLayout grid = new PlotGridLayout(32, 10, 32, 2, 6, -7, 3, 11, 62);
Cuboid plot = new Cuboid(grid.plotMin(-1, -1, 0), grid.plotMax(-1, -1, 2, 2, 0));
assertEquals(-1, grid.plotXIndexFromMin(plot));
assertEquals(-1, grid.plotZIndexFromMin(plot));
}
}

View File

@@ -26,21 +26,21 @@ import org.junit.jupiter.api.Test;
class SpawnLayoutTest {
@Test
void defaultMultipliersPreserveTheOnePlotSpawnBounds() {
Cuboid spawn = SpawnLayout.bounds(1, 1, 1, 32, 10, 32, 4, 4, 62);
Cuboid spawn = SpawnLayout.bounds(1, 1, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0);
assertEqualsBounds(spawn, 2, 62, 2, 33, 71, 33);
}
@Test
void appliesEachMultiplierToItsOwnAlignedGridDimension() {
Cuboid spawn = SpawnLayout.bounds(2, 3, 4, 32, 10, 32, 4, 4, 62);
Cuboid spawn = SpawnLayout.bounds(2, 3, 4, 32, 10, 32, 4, 4, 62, 0, 0, 0);
assertEqualsBounds(spawn, 2, 62, 2, 69, 91, 141);
}
@Test
void validatesTheEntireSpawnHeightAgainstWorldBounds() {
Cuboid spawn = SpawnLayout.bounds(1, 3, 1, 32, 10, 32, 4, 4, 62);
Cuboid spawn = SpawnLayout.bounds(1, 3, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0);
assertTrue(SpawnLayout.withinBuildHeight(spawn, -64, 320));
assertFalse(SpawnLayout.withinBuildHeight(spawn, -64, 91));
@@ -48,15 +48,23 @@ class SpawnLayoutTest {
@Test
void alignsSpawnAxesToTheirIndependentRoadWidths() {
Cuboid spawn = SpawnLayout.bounds(2, 1, 3, 32, 10, 32, 6, 2, 62);
Cuboid spawn = SpawnLayout.bounds(2, 1, 3, 32, 10, 32, 6, 2, 62, 0, 0, 0);
assertEqualsBounds(spawn, 3, 62, 1, 72, 71, 100);
}
@Test
void alignsSpawnBoundsWithAllGridOffsets() {
Cuboid spawn = SpawnLayout.bounds(2, 2, 3, 32, 10, 32, 6, 2, 62, -7, 3, 11);
assertEqualsBounds(spawn, -4, 65, 12, 65, 84, 111);
}
@Test
void rejectsNonPositiveSpawnMultipliers() {
assertThrows(
IllegalArgumentException.class, () -> SpawnLayout.bounds(0, 1, 1, 32, 10, 32, 4, 4, 62));
IllegalArgumentException.class,
() -> SpawnLayout.bounds(0, 1, 1, 32, 10, 32, 4, 4, 62, 0, 0, 0));
}
private static void assertEqualsBounds(