Use independent street and avenue settings

This commit is contained in:
2026-09-27 08:11:52 -07:00
parent d68f7543ea
commit 8d99c2e67f
14 changed files with 425 additions and 161 deletions

View File

@@ -32,12 +32,14 @@ public final class AvenueStairwayBuilder {
private AvenueStairwayBuilder() {}
public static void build(World world, Flight flight, Material stairMaterial) {
BlockFace ascendingFacing = flight.side().runsAlongX() ? BlockFace.EAST : BlockFace.SOUTH;
BlockFace descendingFacing = ascendingFacing.getOppositeFace();
for (Step step : flight.stairBlocks()) {
Block stairBlock = world.getBlockAt(step.x(), step.y(), step.z());
stairBlock.setType(stairMaterial, false);
BlockData blockData = stairBlock.getBlockData();
if (blockData instanceof Stairs stairs) {
stairs.setFacing(BlockFace.SOUTH);
stairs.setFacing(ascendingFacing);
stairs.setHalf(Bisected.Half.BOTTOM);
stairs.setShape(Stairs.Shape.STRAIGHT);
stairBlock.setBlockData(stairs, false);
@@ -49,7 +51,7 @@ public final class AvenueStairwayBuilder {
supportBlock.setType(stairMaterial, false);
BlockData supportData = supportBlock.getBlockData();
if (supportData instanceof Stairs stairs) {
stairs.setFacing(BlockFace.NORTH);
stairs.setFacing(descendingFacing);
stairs.setHalf(Bisected.Half.TOP);
stairs.setShape(Stairs.Shape.STRAIGHT);
supportBlock.setBlockData(stairs, false);

View File

@@ -61,15 +61,64 @@ public final class AvenueStairwayLayout {
plot.maxX,
plot.maxY,
Math.min(segmentStart + plotSizeZ - 1, plot.maxZ));
for (Side side : Side.values()) {
for (Side side : List.of(Side.EAST, Side.WEST)) {
List<Step> stairs = stairBlocks(segment, avenueWidth, stairWidth, lowerY, rise, side);
if (stairs.isEmpty()) {
continue;
}
List<Step> backings = invertedBackings(stairs, lowerY);
List<Step> openings =
upperRoadOpening(stairs, avenueWidth, stairWidth, lowerY, rise, openingLength);
flights.add(new Flight(stairs, backings, openings));
upperRoadOpening(stairs, avenueWidth, stairWidth, lowerY, rise, openingLength, side);
flights.add(new Flight(side, stairs, backings, openings));
}
}
return List.copyOf(flights);
}
public static List<Flight> forUpperStreetSegments(
Cuboid plot,
int plotSizeX,
int gridSizeX,
int firstBlockIndex,
int everyNBlocks,
int streetWidth,
int stairWidth,
int lowerY,
int rise,
int openingLength) {
if (plotSizeX < 1 || gridSizeX < plotSizeX || everyNBlocks < 1) {
return List.of();
}
List<Flight> flights = new ArrayList<>();
int segmentIndex = 0;
for (int segmentStart = plot.minX;
segmentStart <= plot.maxX;
segmentStart += gridSizeX, segmentIndex++) {
if (Math.floorMod(firstBlockIndex + segmentIndex + 1, everyNBlocks) != 0) {
continue;
}
Cuboid segment =
new Cuboid(
segmentStart,
plot.minY,
plot.minZ,
Math.min(segmentStart + plotSizeX - 1, plot.maxX),
plot.maxY,
plot.maxZ);
for (Side side : List.of(Side.NORTH, Side.SOUTH)) {
List<Step> stairs = stairBlocks(segment, streetWidth, stairWidth, lowerY, rise, side);
if (stairs.isEmpty()) {
continue;
}
flights.add(
new Flight(
side,
stairs,
invertedBackings(stairs, lowerY),
upperRoadOpening(
stairs, streetWidth, stairWidth, lowerY, rise, openingLength, side)));
}
}
return List.copyOf(flights);
@@ -78,22 +127,31 @@ public final class AvenueStairwayLayout {
public static List<Step> upperRoadOpening(
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
return upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength);
return upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength, Side.EAST);
}
private static List<Step> upperRoadOpening(
List<Step> stairs, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
List<Step> stairs,
int roadWidth,
int stairWidth,
int lowerY,
int rise,
int openingLength,
Side side) {
if (stairs.isEmpty() || openingLength < 1) {
return List.of();
}
Step topStair = stairs.get(stairs.size() - 1);
int firstX = stairs.getFirst().x();
Step firstStair = stairs.getFirst();
int upperRoadY = lowerY + rise;
List<Step> opening = new ArrayList<>(stairWidth * openingLength);
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
for (int lengthIndex = openingLength; lengthIndex > 0; lengthIndex--) {
opening.add(new Step(firstX + widthIndex, upperRoadY, topStair.z() - lengthIndex));
opening.add(
side.runsAlongX()
? new Step(topStair.x() - lengthIndex, upperRoadY, firstStair.z() + widthIndex)
: new Step(firstStair.x() + widthIndex, upperRoadY, topStair.z() - lengthIndex));
}
}
return List.copyOf(opening);
@@ -103,13 +161,14 @@ public final class AvenueStairwayLayout {
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
if (stairs.isEmpty()) {
return new Flight(List.of(), List.of(), List.of());
return new Flight(Side.EAST, List.of(), List.of(), List.of());
}
return new Flight(
Side.EAST,
stairs,
invertedBackings(stairs, lowerY),
upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength));
upperRoadOpening(stairs, roadWidth, stairWidth, lowerY, rise, openingLength, Side.EAST));
}
private static List<Step> invertedBackings(List<Step> stairs, int lowerY) {
@@ -124,7 +183,7 @@ public final class AvenueStairwayLayout {
private static List<Step> stairBlocks(
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, Side side) {
int plotLength = plot.maxZ - plot.minZ + 1;
int plotLength = side.runsAlongX() ? plot.maxX - plot.minX + 1 : plot.maxZ - plot.minZ + 1;
if (roadWidth < 1
|| stairWidth < 1
|| stairWidth >= roadWidth
@@ -133,15 +192,21 @@ public final class AvenueStairwayLayout {
return List.of();
}
int firstX =
side == Side.EAST
? plot.maxX + 1 + (roadWidth - stairWidth) / 2
: plot.minX - roadWidth + (roadWidth - stairWidth) / 2;
int firstZ = plot.minZ + (plotLength - rise) / 2 + 1;
int firstRun = (side.runsAlongX() ? plot.minX : plot.minZ) + (plotLength - rise) / 2 + 1;
int firstCross =
switch (side) {
case EAST -> plot.maxX + 1 + (roadWidth - stairWidth) / 2;
case WEST -> plot.minX - roadWidth + (roadWidth - stairWidth) / 2;
case NORTH -> plot.minZ - roadWidth + (roadWidth - stairWidth) / 2;
case SOUTH -> plot.maxZ + 1 + (roadWidth - stairWidth) / 2;
};
List<Step> steps = new ArrayList<>(stairWidth * rise);
for (int riseIndex = 0; riseIndex < rise; riseIndex++) {
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
steps.add(new Step(firstX + widthIndex, lowerY + riseIndex + 1, firstZ + riseIndex));
steps.add(
side.runsAlongX()
? new Step(firstRun + riseIndex, lowerY + riseIndex + 1, firstCross + widthIndex)
: new Step(firstCross + widthIndex, lowerY + riseIndex + 1, firstRun + riseIndex));
}
}
return List.copyOf(steps);
@@ -149,13 +214,22 @@ public final class AvenueStairwayLayout {
public enum Side {
EAST,
WEST
WEST,
NORTH,
SOUTH;
public boolean runsAlongX() {
return this == NORTH || this == SOUTH;
}
}
public record Step(int x, int y, int z) {}
public record Flight(
List<Step> stairBlocks, List<Step> invertedBackingBlocks, List<Step> roadOpenings) {
Side side,
List<Step> stairBlocks,
List<Step> invertedBackingBlocks,
List<Step> roadOpenings) {
public Flight {
stairBlocks = List.copyOf(stairBlocks);
invertedBackingBlocks = List.copyOf(invertedBackingBlocks);

View File

@@ -101,14 +101,23 @@ public class MetropolisPlugin extends JavaPlugin {
private int gridSizeX = 28;
private int gridSizeY = 256;
private int maxLevels = 1;
private int gridSizeZ = 28;
private int roadWidth = 4;
private int streetWidth = 4;
private int avenueWidth = 4;
private int gridSizeZ = 28;
private int roadLevel = 62;
private int spaceAboveRoad = 2;
private Material roadMaterial = Material.COBBLESTONE;
private boolean generateRoadSupports = true;
private Material roadSupportMaterial = Material.STONE;
private int avenueLevel = 62;
private int streetSpaceAbove = 2;
private int avenueSpaceAbove = 2;
private Material streetMaterial = Material.COBBLESTONE;
private Material avenueMaterial = Material.COBBLESTONE;
private boolean generateStreetSupports = true;
private boolean generateAvenueSupports = true;
private Material streetSupportMaterial = Material.STONE;
private Material avenueSupportMaterial = Material.STONE;
private boolean generateStreetStairs;
private Material streetStairMaterial = Material.COBBLESTONE_STAIRS;
private int streetStairWidth;
private int streetStairsEveryNBlocks;
private boolean generateAvenueStairs;
private Material avenueStairMaterial = Material.COBBLESTONE_STAIRS;
private int avenueStairWidth;
@@ -199,32 +208,44 @@ public class MetropolisPlugin extends JavaPlugin {
generateFloorSupports = safeGetBooleanFromConfig(config, "plot.floor.supports.generate");
floorSupportMaterial = safeGetMaterialFromConfig(config, "plot.floor.supports.material");
generateSign = safeGetBooleanFromConfig(config, "plot.sign.generate");
roadWidth =
safeGetIntFromConfig(config, "road.streets.width", "road.avenues.width", "road.width");
streetWidth = safeGetIntFromConfig(config, "road.streets.width");
avenueWidth = safeGetIntFromConfig(config, "road.avenues.width");
spaceAboveRoad =
safeGetIntFromConfig(
config,
"road.streets.clearSpaceAbove",
"road.avenues.clearSpaceAbove",
"road.clearSpaceAbove");
roadLevel =
safeGetIntFromConfig(config, "road.streets.level", "road.avenues.level", "road.level");
roadMaterial =
safeGetMaterialFromConfig(
config, "road.streets.material", "road.avenues.material", "road.material");
generateRoadSupports =
safeGetBooleanFromConfig(
config,
"road.streets.supports.generate",
"road.avenues.supports.generate",
"road.supports.generate");
roadSupportMaterial =
safeGetMaterialFromConfig(
config,
"road.streets.supports.material",
"road.avenues.supports.material",
"road.supports.material");
streetSpaceAbove = safeGetIntFromConfig(config, "road.streets.clearSpaceAbove");
avenueSpaceAbove = safeGetIntFromConfig(config, "road.avenues.clearSpaceAbove");
roadLevel = safeGetIntFromConfig(config, "road.streets.level");
avenueLevel = safeGetIntFromConfig(config, "road.avenues.level");
streetMaterial = safeGetMaterialFromConfig(config, "road.streets.material");
avenueMaterial = safeGetMaterialFromConfig(config, "road.avenues.material");
generateStreetSupports = safeGetBooleanFromConfig(config, "road.streets.supports.generate");
generateAvenueSupports = safeGetBooleanFromConfig(config, "road.avenues.supports.generate");
streetSupportMaterial = safeGetMaterialFromConfig(config, "road.streets.supports.material");
avenueSupportMaterial = safeGetMaterialFromConfig(config, "road.avenues.supports.material");
generateStreetStairs = safeGetBooleanFromConfig(config, "road.streets.stairs.generate");
if (generateStreetStairs) {
streetStairMaterial = safeGetMaterialFromConfig(config, "road.streets.stairs.material");
streetStairWidth = safeGetIntFromConfig(config, "road.streets.stairs.width");
streetStairsEveryNBlocks = safeGetIntFromConfig(config, "road.streets.stairs.everyNBlocks");
if (streetStairWidth < 1 || streetStairWidth >= streetWidth) {
getLogger()
.severe(
"road.streets.stairs.width must be positive and narrower than road.streets.width;"
+ " street stairs are disabled.");
generateStreetStairs = false;
}
if (streetStairsEveryNBlocks < 1) {
getLogger()
.severe(
"road.streets.stairs.everyNBlocks must be positive; street stairs are disabled.");
generateStreetStairs = false;
}
if (!(streetStairMaterial.createBlockData() instanceof Stairs)) {
getLogger()
.severe(
"road.streets.stairs.material must be a stair block; street stairs are disabled.");
generateStreetStairs = false;
}
}
generateAvenueStairs = safeGetBooleanFromConfig(config, "road.avenues.stairs.generate");
if (generateAvenueStairs) {
avenueStairMaterial = safeGetMaterialFromConfig(config, "road.avenues.stairs.material");
@@ -305,9 +326,12 @@ public class MetropolisPlugin extends JavaPlugin {
throw new RuntimeException(String.format("The world %s does not exist", worldName));
}
gridSizeX = plotSizeX + roadWidth;
gridSizeY = Math.max(plotSizeY + roadWidth, 1);
gridSizeZ = plotSizeZ + roadWidth;
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;
regionManager =
WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(world));
@@ -372,7 +396,8 @@ public class MetropolisPlugin extends JavaPlugin {
}
*/
getLogger().info(String.format("roadWidth = %d", roadWidth));
getLogger()
.info(String.format("streetWidth = %d, avenueWidth = %d", streetWidth, avenueWidth));
}
_occupiedPlots = new ArrayList<Plot>();
@@ -595,7 +620,8 @@ public class MetropolisPlugin extends JavaPlugin {
plotSizeX,
plotSizeY,
plotSizeZ,
roadWidth,
avenueWidth,
streetWidth,
roadLevel);
if (!SpawnLayout.withinBuildHeight(spawnCuboid, world.getMinHeight(), world.getMaxHeight())) {
getLogger()
@@ -702,7 +728,7 @@ public class MetropolisPlugin extends JavaPlugin {
public static int clampMaxLevels(int requestedLevels, int availableLevels) {
int normalizedRequested = normalizeMaxLevels(requestedLevels);
int normalizedAvailable = Math.max(1, availableLevels);
int normalizedAvailable = Math.max(0, availableLevels);
return Math.min(normalizedRequested, normalizedAvailable);
}
@@ -770,28 +796,46 @@ public class MetropolisPlugin extends JavaPlugin {
return;
}
if (roadWidth > 0) {
int y = plotCuboid.minY;
Set<AvenueStairwayLayout.Step> stairOpenings = getUpperRoadOpenings(plotCuboid, y);
for (RoadLayout.BlockPosition roadBlock : RoadLayout.surroundingRoad(plotCuboid, roadWidth)) {
setRoad(roadBlock.x(), y, roadBlock.z(), stairOpenings);
if (streetWidth > 0 || avenueWidth > 0) {
for (RoadLayout.BlockPosition roadBlock :
RoadLayout.surroundingRoad(plotCuboid, streetWidth, avenueWidth)) {
int roadY =
roadBlock.roadType() == RoadLayout.RoadType.AVENUE
? plotCuboid.minY + avenueLevel - roadLevel
: plotCuboid.minY;
Set<AvenueStairwayLayout.Step> stairOpenings =
getUpperRoadOpenings(plotCuboid, roadY, roadBlock.roadType());
setRoad(roadBlock.x(), roadY, roadBlock.z(), roadBlock.roadType(), stairOpenings);
}
int upperLevel = Math.floorDiv(y - roadLevel, getLevelPitchY());
if (upperLevel > 0 && getLevelStartY(upperLevel) == y) {
generateAvenueStairs(plotCuboid, upperLevel - 1);
int upperLevel = Math.floorDiv(plotCuboid.minY - roadLevel, getLevelPitchY());
if (upperLevel > 0 && getLevelStartY(upperLevel) == plotCuboid.minY) {
generateRoadStairs(plotCuboid, upperLevel - 1, RoadLayout.RoadType.AVENUE);
generateRoadStairs(plotCuboid, upperLevel - 1, RoadLayout.RoadType.STREET);
}
}
}
private void setRoad(int x, int y, int z, Set<AvenueStairwayLayout.Step> stairOpenings) {
private void setRoad(
int x,
int y,
int z,
RoadLayout.RoadType roadType,
Set<AvenueStairwayLayout.Step> stairOpenings) {
if (stairOpenings.contains(new AvenueStairwayLayout.Step(x, y, z))) {
return;
}
boolean useAvenueSettings = roadType == RoadLayout.RoadType.AVENUE;
RoadBlockWriter.build(
world, x, y, z, roadMaterial, roadSupportMaterial, generateRoadSupports, spaceAboveRoad);
world,
x,
y,
z,
useAvenueSettings ? avenueMaterial : streetMaterial,
useAvenueSettings ? avenueSupportMaterial : streetSupportMaterial,
useAvenueSettings ? generateAvenueSupports : generateStreetSupports,
useAvenueSettings ? avenueSpaceAbove : streetSpaceAbove);
}
private boolean isSolidMaterial(Material material) {
@@ -853,6 +897,10 @@ public class MetropolisPlugin extends JavaPlugin {
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) {
row = min;
@@ -921,11 +969,9 @@ public class MetropolisPlugin extends JavaPlugin {
}
}
if (DEBUG) {
getLogger().info(String.format("row: %d, col: %d", row, col));
}
return new Cuboid(
getPlotMin(row, col, plotMultiplier, 0), getPlotMax(row, col, plotMultiplier, 0));
getLogger()
.severe("Unable to find an available supported plot within the allocation search area.");
return null;
}
private boolean isAvailableSupportedPlot(int row, int col, int plotMultiplier, int level) {
@@ -995,7 +1041,7 @@ public class MetropolisPlugin extends JavaPlugin {
BlockVector3 bv =
BlockVector3.at(
gridMin.x() + roadWidth / 2, getLevelStartY(level), gridMin.z() + roadWidth / 2);
gridMin.x() + avenueWidth / 2, getLevelStartY(level), gridMin.z() + streetWidth / 2);
getLogger().info(String.format("getPlotMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
@@ -1009,9 +1055,9 @@ public class MetropolisPlugin extends JavaPlugin {
BlockVector3 bv =
BlockVector3.at(
gridMax.x() - (roadWidth - roadWidth / 2),
gridMax.x() - (avenueWidth - avenueWidth / 2),
getLevelStartY(level) + plotSizeY - 1,
gridMax.z() - (roadWidth - roadWidth / 2));
gridMax.z() - (streetWidth - streetWidth / 2));
getLogger().info(String.format("getPlotMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
return bv;
}
@@ -1041,22 +1087,37 @@ public class MetropolisPlugin extends JavaPlugin {
}
private int getPlotXFromMin(Cuboid cuboid) {
return (cuboid.minX - roadWidth / 2) / gridSizeX;
return (cuboid.minX - avenueWidth / 2) / gridSizeX;
}
private int getPlotZFromMin(Cuboid cuboid) {
return (cuboid.minZ - roadWidth / 2) / gridSizeZ;
return (cuboid.minZ - streetWidth / 2) / gridSizeZ;
}
private int getLevelPitchY() {
return plotSizeY + roadWidth;
return plotSizeY + Math.max(streetWidth, avenueWidth);
}
private int getEffectiveMaxLevels() {
int maxWorldLevels = Math.max(1, (world.getMaxHeight() - roadLevel) / getLevelPitchY());
int availableStreetLevels = getAvailableLevelsAt(roadLevel);
int availableAvenueLevels =
avenueWidth > 0 ? getAvailableLevelsAt(avenueLevel) : availableStreetLevels;
int maxWorldLevels = Math.min(availableStreetLevels, availableAvenueLevels);
return clampMaxLevels(maxLevels, maxWorldLevels);
}
private int getAvailableLevelsAt(int baseY) {
if (world == null || baseY < world.getMinHeight() || plotSizeY < 1) {
return 0;
}
int highestPlotBase = world.getMaxHeight() - plotSizeY;
if (baseY > highestPlotBase) {
return 0;
}
return (highestPlotBase - baseY) / getLevelPitchY() + 1;
}
private int getLevelStartY(int level) {
return roadLevel + level * getLevelPitchY();
}
@@ -1104,6 +1165,9 @@ public class MetropolisPlugin extends JavaPlugin {
}
homeCuboid = findNextUnownedHomeRegion(multiplier);
if (homeCuboid == null) {
return null;
}
getLogger().info("Metropolis Generating home in " + homeCuboid.toString());
@@ -1195,37 +1259,28 @@ public class MetropolisPlugin extends JavaPlugin {
sign.update(true);
}
private void generateAvenueStairs(Cuboid plotCuboid, int lowerLevel) {
if (!generateAvenueStairs || lowerLevel < 0 || lowerLevel >= getEffectiveMaxLevels() - 1) {
return;
}
int lowerY = getLevelStartY(lowerLevel);
List<AvenueStairwayLayout.Flight> flights = getAvenueStairFlights(plotCuboid, lowerLevel);
private void generateRoadStairs(Cuboid plotCuboid, int lowerLevel, RoadLayout.RoadType roadType) {
List<AvenueStairwayLayout.Flight> flights =
getRoadStairFlights(plotCuboid, lowerLevel, roadType);
if (flights.isEmpty()) {
getLogger()
.warning(
"Avenue stair run does not fit beside plot at "
+ plotCuboid.minX
+ ","
+ plotCuboid.minY
+ ","
+ plotCuboid.minZ);
return;
}
Material stairMaterial =
roadType == RoadLayout.RoadType.AVENUE ? avenueStairMaterial : streetStairMaterial;
for (AvenueStairwayLayout.Flight flight : flights) {
AvenueStairwayBuilder.build(world, flight, avenueStairMaterial);
AvenueStairwayBuilder.build(world, flight, stairMaterial);
}
}
private Set<AvenueStairwayLayout.Step> getUpperRoadOpenings(Cuboid plotCuboid, int roadY) {
private Set<AvenueStairwayLayout.Step> getUpperRoadOpenings(
Cuboid plotCuboid, int roadY, RoadLayout.RoadType roadType) {
Set<AvenueStairwayLayout.Step> openings = new HashSet<>();
openings.addAll(getUpperRoadOpeningsForPlot(plotCuboid, roadY));
openings.addAll(getUpperRoadOpeningsForPlot(plotCuboid, roadY, roadType));
if (_occupiedPlots != null) {
for (Plot plot : _occupiedPlots) {
if (plot instanceof PlayerHome home && home.getCuboid().minY == roadY) {
openings.addAll(getUpperRoadOpeningsForPlot(home.getCuboid(), roadY));
if (plot instanceof PlayerHome home) {
openings.addAll(getUpperRoadOpeningsForPlot(home.getCuboid(), roadY, roadType));
}
}
}
@@ -1233,41 +1288,57 @@ public class MetropolisPlugin extends JavaPlugin {
}
private List<AvenueStairwayLayout.Step> getUpperRoadOpeningsForPlot(
Cuboid plotCuboid, int roadY) {
if (!generateAvenueStairs || roadY <= roadLevel) {
Cuboid plotCuboid, int roadY, RoadLayout.RoadType roadType) {
int baseY = roadType == RoadLayout.RoadType.AVENUE ? avenueLevel : roadLevel;
if (!areStairsEnabled(roadType) || roadY <= baseY) {
return List.of();
}
int upperLevel = Math.floorDiv(roadY - roadLevel, getLevelPitchY());
if (getLevelStartY(upperLevel) != roadY) {
int upperLevel = Math.floorDiv(roadY - baseY, getLevelPitchY());
if (baseY + upperLevel * getLevelPitchY() != roadY || upperLevel < 1) {
return List.of();
}
int lowerLevel = upperLevel - 1;
if (lowerLevel < 0) {
return List.of();
}
return getAvenueStairFlights(plotCuboid, lowerLevel).stream()
return getRoadStairFlights(plotCuboid, upperLevel - 1, roadType).stream()
.flatMap(flight -> flight.roadOpenings().stream())
.toList();
}
private List<AvenueStairwayLayout.Flight> getAvenueStairFlights(
Cuboid plotCuboid, int lowerLevel) {
if (!generateAvenueStairs || lowerLevel < 0 || lowerLevel >= getEffectiveMaxLevels() - 1) {
private boolean areStairsEnabled(RoadLayout.RoadType roadType) {
return roadType == RoadLayout.RoadType.AVENUE ? generateAvenueStairs : generateStreetStairs;
}
private List<AvenueStairwayLayout.Flight> getRoadStairFlights(
Cuboid plotCuboid, int lowerLevel, RoadLayout.RoadType roadType) {
if (!areStairsEnabled(roadType)
|| lowerLevel < 0
|| lowerLevel >= getEffectiveMaxLevels() - 1) {
return List.of();
}
return AvenueStairwayLayout.forUpperRoadSegments(
if (roadType == RoadLayout.RoadType.AVENUE) {
return AvenueStairwayLayout.forUpperRoadSegments(
plotCuboid,
plotSizeZ,
gridSizeZ,
getPlotZFromMin(plotCuboid),
avenueStairsEveryNBlocks,
avenueWidth,
avenueStairWidth,
avenueLevel + lowerLevel * getLevelPitchY(),
getLevelPitchY(),
3);
}
return AvenueStairwayLayout.forUpperStreetSegments(
plotCuboid,
plotSizeZ,
gridSizeZ,
getPlotZFromMin(plotCuboid),
avenueStairsEveryNBlocks,
avenueWidth,
avenueStairWidth,
getLevelStartY(lowerLevel),
plotSizeX,
gridSizeX,
getPlotXFromMin(plotCuboid),
streetStairsEveryNBlocks,
streetWidth,
streetStairWidth,
roadLevel + lowerLevel * getLevelPitchY(),
getLevelPitchY(),
3);
}

View File

@@ -23,35 +23,67 @@ import java.util.List;
public final class RoadLayout {
private RoadLayout() {}
public static List<BlockPosition> surroundingRoad(Cuboid plot, int roadWidth) {
if (roadWidth < 1) {
public static List<BlockPosition> surroundingRoad(Cuboid plot, int streetWidth, int avenueWidth) {
if (streetWidth < 0 || avenueWidth < 0 || streetWidth + avenueWidth == 0) {
return List.of();
}
List<BlockPosition> blocks = new ArrayList<>();
addRectangle(blocks, plot.minX, plot.maxX, plot.minZ - roadWidth, plot.minZ - 1);
addRectangle(blocks, plot.minX, plot.maxX, plot.maxZ + 1, plot.maxZ + roadWidth);
addRectangle(blocks, plot.minX - roadWidth, plot.minX - 1, plot.minZ, plot.maxZ);
addRectangle(blocks, plot.maxX + 1, plot.maxX + roadWidth, plot.minZ, plot.maxZ);
addRectangle(
blocks, plot.minX - roadWidth, plot.minX - 1, plot.minZ - roadWidth, plot.minZ - 1);
blocks, plot.minX, plot.maxX, plot.minZ - streetWidth, plot.minZ - 1, RoadType.STREET);
addRectangle(
blocks, plot.maxX + 1, plot.maxX + roadWidth, plot.minZ - roadWidth, plot.minZ - 1);
blocks, plot.minX, plot.maxX, plot.maxZ + 1, plot.maxZ + streetWidth, RoadType.STREET);
addRectangle(
blocks, plot.minX - roadWidth, plot.minX - 1, plot.maxZ + 1, plot.maxZ + roadWidth);
blocks, plot.minX - avenueWidth, plot.minX - 1, plot.minZ, plot.maxZ, RoadType.AVENUE);
addRectangle(
blocks, plot.maxX + 1, plot.maxX + roadWidth, plot.maxZ + 1, plot.maxZ + roadWidth);
blocks, plot.maxX + 1, plot.maxX + avenueWidth, plot.minZ, plot.maxZ, RoadType.AVENUE);
if (streetWidth > 0 && avenueWidth > 0) {
addRectangle(
blocks,
plot.minX - avenueWidth,
plot.minX - 1,
plot.minZ - streetWidth,
plot.minZ - 1,
RoadType.INTERSECTION);
addRectangle(
blocks,
plot.maxX + 1,
plot.maxX + avenueWidth,
plot.minZ - streetWidth,
plot.minZ - 1,
RoadType.INTERSECTION);
addRectangle(
blocks,
plot.minX - avenueWidth,
plot.minX - 1,
plot.maxZ + 1,
plot.maxZ + streetWidth,
RoadType.INTERSECTION);
addRectangle(
blocks,
plot.maxX + 1,
plot.maxX + avenueWidth,
plot.maxZ + 1,
plot.maxZ + streetWidth,
RoadType.INTERSECTION);
}
return List.copyOf(blocks);
}
private static void addRectangle(
List<BlockPosition> blocks, int minX, int maxX, int minZ, int maxZ) {
List<BlockPosition> blocks, int minX, int maxX, int minZ, int maxZ, RoadType roadType) {
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
blocks.add(new BlockPosition(x, z));
blocks.add(new BlockPosition(x, z, roadType));
}
}
}
public record BlockPosition(int x, int z) {}
public enum RoadType {
STREET,
AVENUE,
INTERSECTION
}
public record BlockPosition(int x, int z, RoadType roadType) {}
}

View File

@@ -27,22 +27,25 @@ public final class SpawnLayout {
int plotSizeX,
int plotSizeY,
int plotSizeZ,
int roadWidth,
int avenueWidth,
int streetWidth,
int roadLevel) {
if (sizeX < 1 || sizeY < 1 || sizeZ < 1 || roadWidth < 0) {
if (sizeX < 1 || sizeY < 1 || sizeZ < 1 || avenueWidth < 0 || streetWidth < 0) {
throw new IllegalArgumentException(
"Spawn multipliers must be positive and road width nonnegative");
"Spawn multipliers must be positive and road widths nonnegative");
}
int gridSizeX = Math.addExact(plotSizeX, roadWidth);
int gridSizeZ = Math.addExact(plotSizeZ, roadWidth);
int inset = roadWidth / 2;
int maxInset = roadWidth - inset;
int maxX = Math.subtractExact(Math.multiplyExact(sizeX, gridSizeX) - 1, maxInset);
int gridSizeX = Math.addExact(plotSizeX, avenueWidth);
int gridSizeZ = Math.addExact(plotSizeZ, streetWidth);
int minX = avenueWidth / 2;
int minZ = streetWidth / 2;
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 maxZ = Math.subtractExact(Math.multiplyExact(sizeZ, gridSizeZ) - 1, maxInset);
int maxZ = Math.subtractExact(Math.multiplyExact(sizeZ, gridSizeZ) - 1, maxInsetZ);
return new Cuboid(inset, roadLevel, inset, maxX, maxY, maxZ);
return new Cuboid(minX, roadLevel, minZ, maxX, maxY, maxZ);
}
public static boolean withinBuildHeight(Cuboid cuboid, int minHeight, int maxHeight) {

View File

@@ -28,6 +28,9 @@ road:
material: STONE
stairs:
generate: false
material: COBBLESTONE_STAIRS
everyNBlocks: 4
width: 2
streets:
width: 4
clearSpaceAbove: 66
@@ -36,6 +39,11 @@ road:
supports:
generate: true
material: STONE
stairs:
generate: false
material: COBBLESTONE_STAIRS
everyNBlocks: 4
width: 2
spawn:
generate: true
setAsWorldSpawn: true

View File

@@ -42,6 +42,9 @@ road:
material: STONE
stairs:
generate: false
material: COBBLESTONE_STAIRS
everyNBlocks: 4
width: 2
spawn:
generate: true
setAsWorldSpawn: true