Use independent street and avenue settings
This commit is contained in:
@@ -33,7 +33,8 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
|||||||
|
|
||||||
- Inspect a newly allocated home and adjoining roads. Check road width, level, material, clearance, plot floor, support blocks, and optional sign against the active config.
|
- Inspect a newly allocated home and adjoining roads. Check road width, level, material, clearance, plot floor, support blocks, and optional sign against the active config.
|
||||||
- Generate one plot in a disposable world and confirm each adjoining road is fully paved to the configured width. Generate a plot directly beside it: confirm already-correct shared road blocks are untouched, any missing half-width legacy section is filled, and stair treads/openings remain intact.
|
- Generate one plot in a disposable world and confirm each adjoining road is fully paved to the configured width. Generate a plot directly beside it: confirm already-correct shared road blocks are untouched, any missing half-width legacy section is filled, and stair treads/openings remain intact.
|
||||||
- Enable avenue stairs with a stair width narrower than the avenue. Generate a plot on a level below `plot.maxLevels`; verify stairs are added while the upper road is built on both bordering avenues and the lower road stays unchanged. Confirm the first tread starts one block above and forward from the road-level landing, connected following treads use inverted stairs underneath, and the top tread reaches the upper road Y. Check that exactly three road blocks before each top tread remain open only across the stair width, with regular road beside each opening. For a 2-plot-long home with `everyNBlocks: 1`, confirm there are two centered runs on each bordering avenue. Repeat with a higher-level plot to confirm lower stair flights remain open.
|
- Configure streets and avenues with different widths, materials, clearances, and support settings. Verify streets along X use only the street options and avenues along Z use only avenue options.
|
||||||
|
- Enable stairs for both sections with different widths/cadences, each narrower than its road. Generate a plot on a level below `plot.maxLevels`; verify street stairs ascend along positive X at north/south borders and avenue stairs ascend along positive Z at east/west borders. Confirm lower roads remain unchanged, the first treads start one block above and forward from the road-level landing, connected treads use inverted backing, and top treads reach the upper road Y. Check each upper road leaves exactly three blocks open before its top tread only across that stair width. For a 2-plot-wide/long home with cadence 1, confirm two centered runs along each eligible edge. Repeat with higher levels to confirm existing flights remain open.
|
||||||
- Test generation over varied terrain, including water, caves, uneven ground, and existing structures. Confirm generated floors and roads do not leave unsafe gaps or erase blocks outside their intended area.
|
- Test generation over varied terrain, including water, caves, uneven ground, and existing structures. Confirm generated floors and roads do not leave unsafe gaps or erase blocks outside their intended area.
|
||||||
- Enable and disable optional floor, support, sign, spawn, world-spawn, and perimeter-wall settings one at a time on a disposable world. Confirm each setting changes only its intended behavior.
|
- Enable and disable optional floor, support, sign, spawn, world-spawn, and perimeter-wall settings one at a time on a disposable world. Confirm each setting changes only its intended behavior.
|
||||||
- Allocate plots in several directions and near the world origin. Check road joins, plot dimensions, City region expansion, and boundary coordinates for symmetry and off-by-one errors.
|
- Allocate plots in several directions and near the world origin. Check road joins, plot dimensions, City region expansion, and boundary coordinates for symmetry and off-by-one errors.
|
||||||
@@ -75,13 +76,13 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
|||||||
|
|
||||||
## Existing Automated Coverage
|
## Existing Automated Coverage
|
||||||
|
|
||||||
Run `./gradlew test` for the server-free JUnit suite. It has 49 tests covering:
|
Run `./gradlew test` for the server-free JUnit suite. It has 53 tests covering:
|
||||||
|
|
||||||
- `AvenueStairwayLayoutTest`: centered per-segment flights on both avenue sides, per-segment cadence, top-tread alignment, inverted backing plan, exact-width upper-road openings, and invalid-fit rejection.
|
- `AvenueStairwayLayoutTest`: centered per-segment street and avenue flights, both border directions, independent cadence, top-tread alignment, inverted backing plan, exact-width openings, and invalid-fit rejection.
|
||||||
- `PlotLevelSupportTest`: rejection of upper plots with missing logical support cells and complete coverage requirements across all lower levels.
|
- `PlotLevelSupportTest`: rejection of upper plots with missing logical support cells and complete coverage requirements across all lower levels.
|
||||||
- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation.
|
- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation.
|
||||||
- `AvenueStairwayBuilderTest`: configured stair material and facing/half data, upside-down backing stairs, exact upper-road AIR placements, and no block writes at lower-road Y.
|
- `AvenueStairwayBuilderTest`: configured stair material and facing/half data for avenue and street directions, upside-down backing stairs, exact upper-road AIR placements, and no block writes at lower-road Y.
|
||||||
- `RoadLayoutTest`: full-width surrounding road bands, non-overlapping corners, and disabled roads at zero width.
|
- `RoadLayoutTest`: independently sized street/avenue bands, correct road-type tagging, non-overlapping corners, and disabled roads at zero width.
|
||||||
- `RoadBlockWriterTest`: no-op writes for existing roads, filling missing road blocks, and preserving stair treads.
|
- `RoadBlockWriterTest`: no-op writes for existing roads, filling missing road blocks, and preserving stair treads.
|
||||||
- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment.
|
- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment.
|
||||||
- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key.
|
- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Copy `.env.example` to `.env` and configure the Paper version/build and server p
|
|||||||
|
|
||||||
Set `spawn.sizeX`, `spawn.sizeY`, and `spawn.sizeZ` to positive logical-grid multipliers. X and Z include the matching plot dimension plus road spacing; Y sets the spawn volume's height upward from `road.level` in multiples of `plot.sizeY`. All default to `1`. A newly created Spawn region must fit within the world's build height, and generation clears only within that region's configured vertical bounds. Existing saved Spawn regions retain their saved bounds.
|
Set `spawn.sizeX`, `spawn.sizeY`, and `spawn.sizeZ` to positive logical-grid multipliers. X and Z include the matching plot dimension plus road spacing; Y sets the spawn volume's height upward from `road.level` in multiples of `plot.sizeY`. All default to `1`. A newly created Spawn region must fit within the world's build height, and generation clears only within that region's configured vertical bounds. Existing saved Spawn regions retain their saved bounds.
|
||||||
|
|
||||||
Enable `road.avenues.stairs.generate` to add centered stair runs as part of upper-level roads. Set `material`, `width`, and `everyNBlocks`; the stair width must be narrower than `road.avenues.width`. Runs are centered within each eligible logical plot-sized avenue segment on both bordering avenues. For a 2-plot-long home, `everyNBlocks: 1` creates two runs on each border avenue. The lower road remains the normal-material landing; stairs begin one block above and forward from it. Connected following treads use upside-down stairs facing back down the run as backing, and the final tread reaches the upper road Y. The implementation ascends along positive Z and leaves the three blocks before each top tread open only across the stair width.
|
The `road.streets.*` and `road.avenues.*` sections independently configure width, level, material, clearance, supports, and stairs. Streets run along X; avenues run along Z. Each section's stair settings (`generate`, `material`, `width`, and `everyNBlocks`) use that section's road width and cadence. Street stair runs ascend along positive X at the north/south borders; avenue runs ascend along positive Z at the east/west borders. Runs are centered within each eligible logical plot-sized segment. The lower road remains the normal-material landing; stairs begin one block above and forward from it. Connected following treads use upside-down stairs facing back down the run as backing, and the final tread reaches the upper road Y. The three blocks before each top tread remain open only across the stair width.
|
||||||
|
|
||||||
Upper-level plots require complete generated support below: every logical plot cell in their footprint must be occupied by a generated home or spawn at every lower level. If a footprint has gaps below, allocation skips that upper candidate and keeps searching for a supported lower-level location.
|
Upper-level plots require complete generated support below: every logical plot cell in their footprint must be occupied by a generated home or spawn at every lower level. If a footprint has gaps below, allocation skips that upper candidate and keeps searching for a supported lower-level location.
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,14 @@ public final class AvenueStairwayBuilder {
|
|||||||
private AvenueStairwayBuilder() {}
|
private AvenueStairwayBuilder() {}
|
||||||
|
|
||||||
public static void build(World world, Flight flight, Material stairMaterial) {
|
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()) {
|
for (Step step : flight.stairBlocks()) {
|
||||||
Block stairBlock = world.getBlockAt(step.x(), step.y(), step.z());
|
Block stairBlock = world.getBlockAt(step.x(), step.y(), step.z());
|
||||||
stairBlock.setType(stairMaterial, false);
|
stairBlock.setType(stairMaterial, false);
|
||||||
BlockData blockData = stairBlock.getBlockData();
|
BlockData blockData = stairBlock.getBlockData();
|
||||||
if (blockData instanceof Stairs stairs) {
|
if (blockData instanceof Stairs stairs) {
|
||||||
stairs.setFacing(BlockFace.SOUTH);
|
stairs.setFacing(ascendingFacing);
|
||||||
stairs.setHalf(Bisected.Half.BOTTOM);
|
stairs.setHalf(Bisected.Half.BOTTOM);
|
||||||
stairs.setShape(Stairs.Shape.STRAIGHT);
|
stairs.setShape(Stairs.Shape.STRAIGHT);
|
||||||
stairBlock.setBlockData(stairs, false);
|
stairBlock.setBlockData(stairs, false);
|
||||||
@@ -49,7 +51,7 @@ public final class AvenueStairwayBuilder {
|
|||||||
supportBlock.setType(stairMaterial, false);
|
supportBlock.setType(stairMaterial, false);
|
||||||
BlockData supportData = supportBlock.getBlockData();
|
BlockData supportData = supportBlock.getBlockData();
|
||||||
if (supportData instanceof Stairs stairs) {
|
if (supportData instanceof Stairs stairs) {
|
||||||
stairs.setFacing(BlockFace.NORTH);
|
stairs.setFacing(descendingFacing);
|
||||||
stairs.setHalf(Bisected.Half.TOP);
|
stairs.setHalf(Bisected.Half.TOP);
|
||||||
stairs.setShape(Stairs.Shape.STRAIGHT);
|
stairs.setShape(Stairs.Shape.STRAIGHT);
|
||||||
supportBlock.setBlockData(stairs, false);
|
supportBlock.setBlockData(stairs, false);
|
||||||
|
|||||||
@@ -61,15 +61,64 @@ public final class AvenueStairwayLayout {
|
|||||||
plot.maxX,
|
plot.maxX,
|
||||||
plot.maxY,
|
plot.maxY,
|
||||||
Math.min(segmentStart + plotSizeZ - 1, plot.maxZ));
|
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);
|
List<Step> stairs = stairBlocks(segment, avenueWidth, stairWidth, lowerY, rise, side);
|
||||||
if (stairs.isEmpty()) {
|
if (stairs.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<Step> backings = invertedBackings(stairs, lowerY);
|
List<Step> backings = invertedBackings(stairs, lowerY);
|
||||||
List<Step> openings =
|
List<Step> openings =
|
||||||
upperRoadOpening(stairs, avenueWidth, stairWidth, lowerY, rise, openingLength);
|
upperRoadOpening(stairs, avenueWidth, stairWidth, lowerY, rise, openingLength, side);
|
||||||
flights.add(new Flight(stairs, backings, openings));
|
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);
|
return List.copyOf(flights);
|
||||||
@@ -78,22 +127,31 @@ public final class AvenueStairwayLayout {
|
|||||||
public static List<Step> upperRoadOpening(
|
public static List<Step> upperRoadOpening(
|
||||||
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
||||||
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
|
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(
|
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) {
|
if (stairs.isEmpty() || openingLength < 1) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
Step topStair = stairs.get(stairs.size() - 1);
|
Step topStair = stairs.get(stairs.size() - 1);
|
||||||
int firstX = stairs.getFirst().x();
|
Step firstStair = stairs.getFirst();
|
||||||
int upperRoadY = lowerY + rise;
|
int upperRoadY = lowerY + rise;
|
||||||
List<Step> opening = new ArrayList<>(stairWidth * openingLength);
|
List<Step> opening = new ArrayList<>(stairWidth * openingLength);
|
||||||
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
|
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
|
||||||
for (int lengthIndex = openingLength; lengthIndex > 0; lengthIndex--) {
|
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);
|
return List.copyOf(opening);
|
||||||
@@ -103,13 +161,14 @@ public final class AvenueStairwayLayout {
|
|||||||
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, int openingLength) {
|
||||||
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
|
List<Step> stairs = centeredEastAvenue(plot, roadWidth, stairWidth, lowerY, rise);
|
||||||
if (stairs.isEmpty()) {
|
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(
|
return new Flight(
|
||||||
|
Side.EAST,
|
||||||
stairs,
|
stairs,
|
||||||
invertedBackings(stairs, lowerY),
|
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) {
|
private static List<Step> invertedBackings(List<Step> stairs, int lowerY) {
|
||||||
@@ -124,7 +183,7 @@ public final class AvenueStairwayLayout {
|
|||||||
|
|
||||||
private static List<Step> stairBlocks(
|
private static List<Step> stairBlocks(
|
||||||
Cuboid plot, int roadWidth, int stairWidth, int lowerY, int rise, Side side) {
|
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
|
if (roadWidth < 1
|
||||||
|| stairWidth < 1
|
|| stairWidth < 1
|
||||||
|| stairWidth >= roadWidth
|
|| stairWidth >= roadWidth
|
||||||
@@ -133,15 +192,21 @@ public final class AvenueStairwayLayout {
|
|||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstX =
|
int firstRun = (side.runsAlongX() ? plot.minX : plot.minZ) + (plotLength - rise) / 2 + 1;
|
||||||
side == Side.EAST
|
int firstCross =
|
||||||
? plot.maxX + 1 + (roadWidth - stairWidth) / 2
|
switch (side) {
|
||||||
: plot.minX - roadWidth + (roadWidth - stairWidth) / 2;
|
case EAST -> plot.maxX + 1 + (roadWidth - stairWidth) / 2;
|
||||||
int firstZ = plot.minZ + (plotLength - rise) / 2 + 1;
|
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);
|
List<Step> steps = new ArrayList<>(stairWidth * rise);
|
||||||
for (int riseIndex = 0; riseIndex < rise; riseIndex++) {
|
for (int riseIndex = 0; riseIndex < rise; riseIndex++) {
|
||||||
for (int widthIndex = 0; widthIndex < stairWidth; widthIndex++) {
|
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);
|
return List.copyOf(steps);
|
||||||
@@ -149,13 +214,22 @@ public final class AvenueStairwayLayout {
|
|||||||
|
|
||||||
public enum Side {
|
public enum Side {
|
||||||
EAST,
|
EAST,
|
||||||
WEST
|
WEST,
|
||||||
|
NORTH,
|
||||||
|
SOUTH;
|
||||||
|
|
||||||
|
public boolean runsAlongX() {
|
||||||
|
return this == NORTH || this == SOUTH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Step(int x, int y, int z) {}
|
public record Step(int x, int y, int z) {}
|
||||||
|
|
||||||
public record Flight(
|
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 {
|
public Flight {
|
||||||
stairBlocks = List.copyOf(stairBlocks);
|
stairBlocks = List.copyOf(stairBlocks);
|
||||||
invertedBackingBlocks = List.copyOf(invertedBackingBlocks);
|
invertedBackingBlocks = List.copyOf(invertedBackingBlocks);
|
||||||
|
|||||||
@@ -101,14 +101,23 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
private int gridSizeX = 28;
|
private int gridSizeX = 28;
|
||||||
private int gridSizeY = 256;
|
private int gridSizeY = 256;
|
||||||
private int maxLevels = 1;
|
private int maxLevels = 1;
|
||||||
private int gridSizeZ = 28;
|
private int streetWidth = 4;
|
||||||
private int roadWidth = 4;
|
|
||||||
private int avenueWidth = 4;
|
private int avenueWidth = 4;
|
||||||
|
private int gridSizeZ = 28;
|
||||||
private int roadLevel = 62;
|
private int roadLevel = 62;
|
||||||
private int spaceAboveRoad = 2;
|
private int avenueLevel = 62;
|
||||||
private Material roadMaterial = Material.COBBLESTONE;
|
private int streetSpaceAbove = 2;
|
||||||
private boolean generateRoadSupports = true;
|
private int avenueSpaceAbove = 2;
|
||||||
private Material roadSupportMaterial = Material.STONE;
|
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 boolean generateAvenueStairs;
|
||||||
private Material avenueStairMaterial = Material.COBBLESTONE_STAIRS;
|
private Material avenueStairMaterial = Material.COBBLESTONE_STAIRS;
|
||||||
private int avenueStairWidth;
|
private int avenueStairWidth;
|
||||||
@@ -199,32 +208,44 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
generateFloorSupports = safeGetBooleanFromConfig(config, "plot.floor.supports.generate");
|
generateFloorSupports = safeGetBooleanFromConfig(config, "plot.floor.supports.generate");
|
||||||
floorSupportMaterial = safeGetMaterialFromConfig(config, "plot.floor.supports.material");
|
floorSupportMaterial = safeGetMaterialFromConfig(config, "plot.floor.supports.material");
|
||||||
generateSign = safeGetBooleanFromConfig(config, "plot.sign.generate");
|
generateSign = safeGetBooleanFromConfig(config, "plot.sign.generate");
|
||||||
roadWidth =
|
streetWidth = safeGetIntFromConfig(config, "road.streets.width");
|
||||||
safeGetIntFromConfig(config, "road.streets.width", "road.avenues.width", "road.width");
|
|
||||||
avenueWidth = safeGetIntFromConfig(config, "road.avenues.width");
|
avenueWidth = safeGetIntFromConfig(config, "road.avenues.width");
|
||||||
spaceAboveRoad =
|
streetSpaceAbove = safeGetIntFromConfig(config, "road.streets.clearSpaceAbove");
|
||||||
safeGetIntFromConfig(
|
avenueSpaceAbove = safeGetIntFromConfig(config, "road.avenues.clearSpaceAbove");
|
||||||
config,
|
roadLevel = safeGetIntFromConfig(config, "road.streets.level");
|
||||||
"road.streets.clearSpaceAbove",
|
avenueLevel = safeGetIntFromConfig(config, "road.avenues.level");
|
||||||
"road.avenues.clearSpaceAbove",
|
streetMaterial = safeGetMaterialFromConfig(config, "road.streets.material");
|
||||||
"road.clearSpaceAbove");
|
avenueMaterial = safeGetMaterialFromConfig(config, "road.avenues.material");
|
||||||
roadLevel =
|
generateStreetSupports = safeGetBooleanFromConfig(config, "road.streets.supports.generate");
|
||||||
safeGetIntFromConfig(config, "road.streets.level", "road.avenues.level", "road.level");
|
generateAvenueSupports = safeGetBooleanFromConfig(config, "road.avenues.supports.generate");
|
||||||
roadMaterial =
|
streetSupportMaterial = safeGetMaterialFromConfig(config, "road.streets.supports.material");
|
||||||
safeGetMaterialFromConfig(
|
avenueSupportMaterial = safeGetMaterialFromConfig(config, "road.avenues.supports.material");
|
||||||
config, "road.streets.material", "road.avenues.material", "road.material");
|
generateStreetStairs = safeGetBooleanFromConfig(config, "road.streets.stairs.generate");
|
||||||
generateRoadSupports =
|
if (generateStreetStairs) {
|
||||||
safeGetBooleanFromConfig(
|
streetStairMaterial = safeGetMaterialFromConfig(config, "road.streets.stairs.material");
|
||||||
config,
|
streetStairWidth = safeGetIntFromConfig(config, "road.streets.stairs.width");
|
||||||
"road.streets.supports.generate",
|
streetStairsEveryNBlocks = safeGetIntFromConfig(config, "road.streets.stairs.everyNBlocks");
|
||||||
"road.avenues.supports.generate",
|
|
||||||
"road.supports.generate");
|
if (streetStairWidth < 1 || streetStairWidth >= streetWidth) {
|
||||||
roadSupportMaterial =
|
getLogger()
|
||||||
safeGetMaterialFromConfig(
|
.severe(
|
||||||
config,
|
"road.streets.stairs.width must be positive and narrower than road.streets.width;"
|
||||||
"road.streets.supports.material",
|
+ " street stairs are disabled.");
|
||||||
"road.avenues.supports.material",
|
generateStreetStairs = false;
|
||||||
"road.supports.material");
|
}
|
||||||
|
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");
|
generateAvenueStairs = safeGetBooleanFromConfig(config, "road.avenues.stairs.generate");
|
||||||
if (generateAvenueStairs) {
|
if (generateAvenueStairs) {
|
||||||
avenueStairMaterial = safeGetMaterialFromConfig(config, "road.avenues.stairs.material");
|
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));
|
throw new RuntimeException(String.format("The world %s does not exist", worldName));
|
||||||
}
|
}
|
||||||
|
|
||||||
gridSizeX = plotSizeX + roadWidth;
|
if (streetWidth < 0 || avenueWidth < 0) {
|
||||||
gridSizeY = Math.max(plotSizeY + roadWidth, 1);
|
throw new IllegalArgumentException("Road widths must not be negative.");
|
||||||
gridSizeZ = plotSizeZ + roadWidth;
|
}
|
||||||
|
gridSizeX = plotSizeX + avenueWidth;
|
||||||
|
gridSizeY = Math.max(plotSizeY + Math.max(streetWidth, avenueWidth), 1);
|
||||||
|
gridSizeZ = plotSizeZ + streetWidth;
|
||||||
|
|
||||||
regionManager =
|
regionManager =
|
||||||
WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(world));
|
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>();
|
_occupiedPlots = new ArrayList<Plot>();
|
||||||
@@ -595,7 +620,8 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
plotSizeX,
|
plotSizeX,
|
||||||
plotSizeY,
|
plotSizeY,
|
||||||
plotSizeZ,
|
plotSizeZ,
|
||||||
roadWidth,
|
avenueWidth,
|
||||||
|
streetWidth,
|
||||||
roadLevel);
|
roadLevel);
|
||||||
if (!SpawnLayout.withinBuildHeight(spawnCuboid, world.getMinHeight(), world.getMaxHeight())) {
|
if (!SpawnLayout.withinBuildHeight(spawnCuboid, world.getMinHeight(), world.getMaxHeight())) {
|
||||||
getLogger()
|
getLogger()
|
||||||
@@ -702,7 +728,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
public static int clampMaxLevels(int requestedLevels, int availableLevels) {
|
public static int clampMaxLevels(int requestedLevels, int availableLevels) {
|
||||||
int normalizedRequested = normalizeMaxLevels(requestedLevels);
|
int normalizedRequested = normalizeMaxLevels(requestedLevels);
|
||||||
int normalizedAvailable = Math.max(1, availableLevels);
|
int normalizedAvailable = Math.max(0, availableLevels);
|
||||||
return Math.min(normalizedRequested, normalizedAvailable);
|
return Math.min(normalizedRequested, normalizedAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,28 +796,46 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roadWidth > 0) {
|
if (streetWidth > 0 || avenueWidth > 0) {
|
||||||
int y = plotCuboid.minY;
|
for (RoadLayout.BlockPosition roadBlock :
|
||||||
Set<AvenueStairwayLayout.Step> stairOpenings = getUpperRoadOpenings(plotCuboid, y);
|
RoadLayout.surroundingRoad(plotCuboid, streetWidth, avenueWidth)) {
|
||||||
|
int roadY =
|
||||||
for (RoadLayout.BlockPosition roadBlock : RoadLayout.surroundingRoad(plotCuboid, roadWidth)) {
|
roadBlock.roadType() == RoadLayout.RoadType.AVENUE
|
||||||
setRoad(roadBlock.x(), y, roadBlock.z(), stairOpenings);
|
? 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());
|
int upperLevel = Math.floorDiv(plotCuboid.minY - roadLevel, getLevelPitchY());
|
||||||
if (upperLevel > 0 && getLevelStartY(upperLevel) == y) {
|
if (upperLevel > 0 && getLevelStartY(upperLevel) == plotCuboid.minY) {
|
||||||
generateAvenueStairs(plotCuboid, upperLevel - 1);
|
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))) {
|
if (stairOpenings.contains(new AvenueStairwayLayout.Step(x, y, z))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean useAvenueSettings = roadType == RoadLayout.RoadType.AVENUE;
|
||||||
RoadBlockWriter.build(
|
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) {
|
private boolean isSolidMaterial(Material material) {
|
||||||
@@ -853,6 +897,10 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
int max = ring - (plotMultiplier - 1);
|
int max = ring - (plotMultiplier - 1);
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
int levelCount = getEffectiveMaxLevels();
|
int levelCount = getEffectiveMaxLevels();
|
||||||
|
if (levelCount == 0) {
|
||||||
|
getLogger().severe("No configured plot level fits within the world's build height.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
while (!done) {
|
while (!done) {
|
||||||
row = min;
|
row = min;
|
||||||
@@ -921,11 +969,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
getLogger()
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
.severe("Unable to find an available supported plot within the allocation search area.");
|
||||||
}
|
return null;
|
||||||
return new Cuboid(
|
|
||||||
getPlotMin(row, col, plotMultiplier, 0), getPlotMax(row, col, plotMultiplier, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAvailableSupportedPlot(int row, int col, int plotMultiplier, int level) {
|
private boolean isAvailableSupportedPlot(int row, int col, int plotMultiplier, int level) {
|
||||||
@@ -995,7 +1041,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
BlockVector3 bv =
|
BlockVector3 bv =
|
||||||
BlockVector3.at(
|
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()));
|
getLogger().info(String.format("getPlotMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||||
return bv;
|
return bv;
|
||||||
}
|
}
|
||||||
@@ -1009,9 +1055,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
BlockVector3 bv =
|
BlockVector3 bv =
|
||||||
BlockVector3.at(
|
BlockVector3.at(
|
||||||
gridMax.x() - (roadWidth - roadWidth / 2),
|
gridMax.x() - (avenueWidth - avenueWidth / 2),
|
||||||
getLevelStartY(level) + plotSizeY - 1,
|
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()));
|
getLogger().info(String.format("getPlotMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||||
return bv;
|
return bv;
|
||||||
}
|
}
|
||||||
@@ -1041,22 +1087,37 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int getPlotXFromMin(Cuboid cuboid) {
|
private int getPlotXFromMin(Cuboid cuboid) {
|
||||||
return (cuboid.minX - roadWidth / 2) / gridSizeX;
|
return (cuboid.minX - avenueWidth / 2) / gridSizeX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPlotZFromMin(Cuboid cuboid) {
|
private int getPlotZFromMin(Cuboid cuboid) {
|
||||||
return (cuboid.minZ - roadWidth / 2) / gridSizeZ;
|
return (cuboid.minZ - streetWidth / 2) / gridSizeZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getLevelPitchY() {
|
private int getLevelPitchY() {
|
||||||
return plotSizeY + roadWidth;
|
return plotSizeY + Math.max(streetWidth, avenueWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getEffectiveMaxLevels() {
|
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);
|
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) {
|
private int getLevelStartY(int level) {
|
||||||
return roadLevel + level * getLevelPitchY();
|
return roadLevel + level * getLevelPitchY();
|
||||||
}
|
}
|
||||||
@@ -1104,6 +1165,9 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
homeCuboid = findNextUnownedHomeRegion(multiplier);
|
homeCuboid = findNextUnownedHomeRegion(multiplier);
|
||||||
|
if (homeCuboid == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getLogger().info("Metropolis Generating home in " + homeCuboid.toString());
|
getLogger().info("Metropolis Generating home in " + homeCuboid.toString());
|
||||||
|
|
||||||
@@ -1195,37 +1259,28 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
sign.update(true);
|
sign.update(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateAvenueStairs(Cuboid plotCuboid, int lowerLevel) {
|
private void generateRoadStairs(Cuboid plotCuboid, int lowerLevel, RoadLayout.RoadType roadType) {
|
||||||
if (!generateAvenueStairs || lowerLevel < 0 || lowerLevel >= getEffectiveMaxLevels() - 1) {
|
List<AvenueStairwayLayout.Flight> flights =
|
||||||
return;
|
getRoadStairFlights(plotCuboid, lowerLevel, roadType);
|
||||||
}
|
|
||||||
|
|
||||||
int lowerY = getLevelStartY(lowerLevel);
|
|
||||||
List<AvenueStairwayLayout.Flight> flights = getAvenueStairFlights(plotCuboid, lowerLevel);
|
|
||||||
if (flights.isEmpty()) {
|
if (flights.isEmpty()) {
|
||||||
getLogger()
|
|
||||||
.warning(
|
|
||||||
"Avenue stair run does not fit beside plot at "
|
|
||||||
+ plotCuboid.minX
|
|
||||||
+ ","
|
|
||||||
+ plotCuboid.minY
|
|
||||||
+ ","
|
|
||||||
+ plotCuboid.minZ);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Material stairMaterial =
|
||||||
|
roadType == RoadLayout.RoadType.AVENUE ? avenueStairMaterial : streetStairMaterial;
|
||||||
for (AvenueStairwayLayout.Flight flight : flights) {
|
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<>();
|
Set<AvenueStairwayLayout.Step> openings = new HashSet<>();
|
||||||
openings.addAll(getUpperRoadOpeningsForPlot(plotCuboid, roadY));
|
openings.addAll(getUpperRoadOpeningsForPlot(plotCuboid, roadY, roadType));
|
||||||
if (_occupiedPlots != null) {
|
if (_occupiedPlots != null) {
|
||||||
for (Plot plot : _occupiedPlots) {
|
for (Plot plot : _occupiedPlots) {
|
||||||
if (plot instanceof PlayerHome home && home.getCuboid().minY == roadY) {
|
if (plot instanceof PlayerHome home) {
|
||||||
openings.addAll(getUpperRoadOpeningsForPlot(home.getCuboid(), roadY));
|
openings.addAll(getUpperRoadOpeningsForPlot(home.getCuboid(), roadY, roadType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1233,32 +1288,35 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<AvenueStairwayLayout.Step> getUpperRoadOpeningsForPlot(
|
private List<AvenueStairwayLayout.Step> getUpperRoadOpeningsForPlot(
|
||||||
Cuboid plotCuboid, int roadY) {
|
Cuboid plotCuboid, int roadY, RoadLayout.RoadType roadType) {
|
||||||
if (!generateAvenueStairs || roadY <= roadLevel) {
|
int baseY = roadType == RoadLayout.RoadType.AVENUE ? avenueLevel : roadLevel;
|
||||||
|
if (!areStairsEnabled(roadType) || roadY <= baseY) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
int upperLevel = Math.floorDiv(roadY - roadLevel, getLevelPitchY());
|
int upperLevel = Math.floorDiv(roadY - baseY, getLevelPitchY());
|
||||||
if (getLevelStartY(upperLevel) != roadY) {
|
if (baseY + upperLevel * getLevelPitchY() != roadY || upperLevel < 1) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
int lowerLevel = upperLevel - 1;
|
return getRoadStairFlights(plotCuboid, upperLevel - 1, roadType).stream()
|
||||||
if (lowerLevel < 0) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
return getAvenueStairFlights(plotCuboid, lowerLevel).stream()
|
|
||||||
.flatMap(flight -> flight.roadOpenings().stream())
|
.flatMap(flight -> flight.roadOpenings().stream())
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AvenueStairwayLayout.Flight> getAvenueStairFlights(
|
private boolean areStairsEnabled(RoadLayout.RoadType roadType) {
|
||||||
Cuboid plotCuboid, int lowerLevel) {
|
return roadType == RoadLayout.RoadType.AVENUE ? generateAvenueStairs : generateStreetStairs;
|
||||||
if (!generateAvenueStairs || lowerLevel < 0 || lowerLevel >= getEffectiveMaxLevels() - 1) {
|
}
|
||||||
|
|
||||||
|
private List<AvenueStairwayLayout.Flight> getRoadStairFlights(
|
||||||
|
Cuboid plotCuboid, int lowerLevel, RoadLayout.RoadType roadType) {
|
||||||
|
if (!areStairsEnabled(roadType)
|
||||||
|
|| lowerLevel < 0
|
||||||
|
|| lowerLevel >= getEffectiveMaxLevels() - 1) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (roadType == RoadLayout.RoadType.AVENUE) {
|
||||||
return AvenueStairwayLayout.forUpperRoadSegments(
|
return AvenueStairwayLayout.forUpperRoadSegments(
|
||||||
plotCuboid,
|
plotCuboid,
|
||||||
plotSizeZ,
|
plotSizeZ,
|
||||||
@@ -1267,7 +1325,20 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
avenueStairsEveryNBlocks,
|
avenueStairsEveryNBlocks,
|
||||||
avenueWidth,
|
avenueWidth,
|
||||||
avenueStairWidth,
|
avenueStairWidth,
|
||||||
getLevelStartY(lowerLevel),
|
avenueLevel + lowerLevel * getLevelPitchY(),
|
||||||
|
getLevelPitchY(),
|
||||||
|
3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvenueStairwayLayout.forUpperStreetSegments(
|
||||||
|
plotCuboid,
|
||||||
|
plotSizeX,
|
||||||
|
gridSizeX,
|
||||||
|
getPlotXFromMin(plotCuboid),
|
||||||
|
streetStairsEveryNBlocks,
|
||||||
|
streetWidth,
|
||||||
|
streetStairWidth,
|
||||||
|
roadLevel + lowerLevel * getLevelPitchY(),
|
||||||
getLevelPitchY(),
|
getLevelPitchY(),
|
||||||
3);
|
3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,35 +23,67 @@ import java.util.List;
|
|||||||
public final class RoadLayout {
|
public final class RoadLayout {
|
||||||
private RoadLayout() {}
|
private RoadLayout() {}
|
||||||
|
|
||||||
public static List<BlockPosition> surroundingRoad(Cuboid plot, int roadWidth) {
|
public static List<BlockPosition> surroundingRoad(Cuboid plot, int streetWidth, int avenueWidth) {
|
||||||
if (roadWidth < 1) {
|
if (streetWidth < 0 || avenueWidth < 0 || streetWidth + avenueWidth == 0) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BlockPosition> blocks = new ArrayList<>();
|
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(
|
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(
|
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(
|
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(
|
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);
|
return List.copyOf(blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addRectangle(
|
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 x = minX; x <= maxX; x++) {
|
||||||
for (int z = minZ; z <= maxZ; z++) {
|
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) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,22 +27,25 @@ public final class SpawnLayout {
|
|||||||
int plotSizeX,
|
int plotSizeX,
|
||||||
int plotSizeY,
|
int plotSizeY,
|
||||||
int plotSizeZ,
|
int plotSizeZ,
|
||||||
int roadWidth,
|
int avenueWidth,
|
||||||
|
int streetWidth,
|
||||||
int roadLevel) {
|
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(
|
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 gridSizeX = Math.addExact(plotSizeX, avenueWidth);
|
||||||
int gridSizeZ = Math.addExact(plotSizeZ, roadWidth);
|
int gridSizeZ = Math.addExact(plotSizeZ, streetWidth);
|
||||||
int inset = roadWidth / 2;
|
int minX = avenueWidth / 2;
|
||||||
int maxInset = roadWidth - inset;
|
int minZ = streetWidth / 2;
|
||||||
int maxX = Math.subtractExact(Math.multiplyExact(sizeX, gridSizeX) - 1, maxInset);
|
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 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) {
|
public static boolean withinBuildHeight(Cuboid cuboid, int minHeight, int maxHeight) {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ road:
|
|||||||
material: STONE
|
material: STONE
|
||||||
stairs:
|
stairs:
|
||||||
generate: false
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
streets:
|
streets:
|
||||||
width: 4
|
width: 4
|
||||||
clearSpaceAbove: 66
|
clearSpaceAbove: 66
|
||||||
@@ -36,6 +39,11 @@ road:
|
|||||||
supports:
|
supports:
|
||||||
generate: true
|
generate: true
|
||||||
material: STONE
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
spawn:
|
spawn:
|
||||||
generate: true
|
generate: true
|
||||||
setAsWorldSpawn: true
|
setAsWorldSpawn: true
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ road:
|
|||||||
material: STONE
|
material: STONE
|
||||||
stairs:
|
stairs:
|
||||||
generate: false
|
generate: false
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
width: 2
|
||||||
spawn:
|
spawn:
|
||||||
generate: true
|
generate: true
|
||||||
setAsWorldSpawn: true
|
setAsWorldSpawn: true
|
||||||
|
|||||||
@@ -40,6 +40,39 @@ import org.bukkit.block.data.type.Stairs;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class AvenueStairwayBuilderTest {
|
class AvenueStairwayBuilderTest {
|
||||||
|
@Test
|
||||||
|
void buildsStreetStairsFacingEastWithWestFacingInvertedBacking() {
|
||||||
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
Flight flight =
|
||||||
|
AvenueStairwayLayout.forUpperStreetSegments(plot, 32, 36, 0, 1, 4, 2, 62, 14, 3).getFirst();
|
||||||
|
World world = mock(World.class);
|
||||||
|
Map<Step, Block> blocks = new HashMap<>();
|
||||||
|
Map<Step, Stairs> blockData = new HashMap<>();
|
||||||
|
when(world.getBlockAt(anyInt(), anyInt(), anyInt()))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
Step position =
|
||||||
|
new Step(
|
||||||
|
invocation.getArgument(0),
|
||||||
|
invocation.getArgument(1),
|
||||||
|
invocation.getArgument(2));
|
||||||
|
return blocks.computeIfAbsent(
|
||||||
|
position,
|
||||||
|
ignored -> {
|
||||||
|
Block block = mock(Block.class);
|
||||||
|
Stairs stairs = mock(Stairs.class);
|
||||||
|
when(block.getBlockData()).thenReturn(stairs);
|
||||||
|
blockData.put(position, stairs);
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AvenueStairwayBuilder.build(world, flight, Material.COBBLESTONE_STAIRS);
|
||||||
|
|
||||||
|
verify(blockData.get(flight.stairBlocks().getFirst())).setFacing(BlockFace.EAST);
|
||||||
|
verify(blockData.get(flight.invertedBackingBlocks().getFirst())).setFacing(BlockFace.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildsAscendingAndInvertedStairsAndClearsOnlyThePlannedOpening() {
|
void buildsAscendingAndInvertedStairsAndClearsOnlyThePlannedOpening() {
|
||||||
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 33);
|
||||||
|
|||||||
@@ -104,6 +104,25 @@ class AvenueStairwayLayoutTest {
|
|||||||
new AvenueStairwayLayout.Step(-1, 63, 48), flights.getLast().stairBlocks().getFirst());
|
new AvenueStairwayLayout.Step(-1, 63, 48), flights.getLast().stairBlocks().getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsStreetFlightsAlongXOnBothNorthAndSouthBorders() {
|
||||||
|
Cuboid twoByTwoPlot = new Cuboid(2, 62, 2, 69, 71, 69);
|
||||||
|
|
||||||
|
List<AvenueStairwayLayout.Flight> flights =
|
||||||
|
AvenueStairwayLayout.forUpperStreetSegments(twoByTwoPlot, 32, 36, 0, 1, 4, 2, 62, 14, 3);
|
||||||
|
|
||||||
|
assertEquals(4, flights.size());
|
||||||
|
assertEquals(AvenueStairwayLayout.Side.NORTH, flights.getFirst().side());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(12, 63, -1), flights.getFirst().stairBlocks().getFirst());
|
||||||
|
assertEquals(AvenueStairwayLayout.Side.SOUTH, flights.get(1).side());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(12, 63, 71), flights.get(1).stairBlocks().getFirst());
|
||||||
|
assertEquals(
|
||||||
|
new AvenueStairwayLayout.Step(48, 63, -1), flights.get(2).stairBlocks().getFirst());
|
||||||
|
assertTrue(flights.stream().allMatch(flight -> flight.stairBlocks().getLast().y() == 76));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsAStairRunThatCannotFitWithinThePlotLength() {
|
void rejectsAStairRunThatCannotFitWithinThePlotLength() {
|
||||||
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 13);
|
Cuboid plot = new Cuboid(2, 62, 2, 33, 71, 13);
|
||||||
|
|||||||
@@ -30,19 +30,30 @@ class RoadLayoutTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildsFullRoadWidthAndNonOverlappingCornersAtOnce() {
|
void buildsFullRoadWidthAndNonOverlappingCornersAtOnce() {
|
||||||
List<RoadLayout.BlockPosition> road = RoadLayout.surroundingRoad(PLOT, 4);
|
List<RoadLayout.BlockPosition> road = RoadLayout.surroundingRoad(PLOT, 4, 4);
|
||||||
HashSet<RoadLayout.BlockPosition> uniqueBlocks = new HashSet<>(road);
|
HashSet<RoadLayout.BlockPosition> uniqueBlocks = new HashSet<>(road);
|
||||||
|
|
||||||
assertEquals(576, road.size());
|
assertEquals(576, road.size());
|
||||||
assertEquals(road.size(), uniqueBlocks.size());
|
assertEquals(road.size(), uniqueBlocks.size());
|
||||||
assertTrue(road.contains(new RoadLayout.BlockPosition(34, 2)));
|
assertTrue(road.contains(new RoadLayout.BlockPosition(34, 2, RoadLayout.RoadType.AVENUE)));
|
||||||
assertTrue(road.contains(new RoadLayout.BlockPosition(37, 33)));
|
assertTrue(road.contains(new RoadLayout.BlockPosition(2, 34, RoadLayout.RoadType.STREET)));
|
||||||
assertTrue(road.contains(new RoadLayout.BlockPosition(2, 34)));
|
assertTrue(
|
||||||
assertTrue(road.contains(new RoadLayout.BlockPosition(33, 37)));
|
road.contains(new RoadLayout.BlockPosition(34, 34, RoadLayout.RoadType.INTERSECTION)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsNoRoadForZeroWidth() {
|
void returnsNoRoadForZeroWidth() {
|
||||||
assertTrue(RoadLayout.surroundingRoad(PLOT, 0).isEmpty());
|
assertTrue(RoadLayout.surroundingRoad(PLOT, 0, 0).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesIndependentWidthsForStreetAndAvenueBands() {
|
||||||
|
List<RoadLayout.BlockPosition> road = RoadLayout.surroundingRoad(PLOT, 2, 4);
|
||||||
|
|
||||||
|
assertEquals(416, road.size());
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(2, 0, RoadLayout.RoadType.STREET)));
|
||||||
|
assertTrue(road.contains(new RoadLayout.BlockPosition(-2, 2, RoadLayout.RoadType.AVENUE)));
|
||||||
|
assertTrue(
|
||||||
|
road.contains(new RoadLayout.BlockPosition(-2, 0, RoadLayout.RoadType.INTERSECTION)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,30 +26,37 @@ import org.junit.jupiter.api.Test;
|
|||||||
class SpawnLayoutTest {
|
class SpawnLayoutTest {
|
||||||
@Test
|
@Test
|
||||||
void defaultMultipliersPreserveTheOnePlotSpawnBounds() {
|
void defaultMultipliersPreserveTheOnePlotSpawnBounds() {
|
||||||
Cuboid spawn = SpawnLayout.bounds(1, 1, 1, 32, 10, 32, 4, 62);
|
Cuboid spawn = SpawnLayout.bounds(1, 1, 1, 32, 10, 32, 4, 4, 62);
|
||||||
|
|
||||||
assertEqualsBounds(spawn, 2, 62, 2, 33, 71, 33);
|
assertEqualsBounds(spawn, 2, 62, 2, 33, 71, 33);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void appliesEachMultiplierToItsOwnAlignedGridDimension() {
|
void appliesEachMultiplierToItsOwnAlignedGridDimension() {
|
||||||
Cuboid spawn = SpawnLayout.bounds(2, 3, 4, 32, 10, 32, 4, 62);
|
Cuboid spawn = SpawnLayout.bounds(2, 3, 4, 32, 10, 32, 4, 4, 62);
|
||||||
|
|
||||||
assertEqualsBounds(spawn, 2, 62, 2, 69, 91, 141);
|
assertEqualsBounds(spawn, 2, 62, 2, 69, 91, 141);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validatesTheEntireSpawnHeightAgainstWorldBounds() {
|
void validatesTheEntireSpawnHeightAgainstWorldBounds() {
|
||||||
Cuboid spawn = SpawnLayout.bounds(1, 3, 1, 32, 10, 32, 4, 62);
|
Cuboid spawn = SpawnLayout.bounds(1, 3, 1, 32, 10, 32, 4, 4, 62);
|
||||||
|
|
||||||
assertTrue(SpawnLayout.withinBuildHeight(spawn, -64, 320));
|
assertTrue(SpawnLayout.withinBuildHeight(spawn, -64, 320));
|
||||||
assertFalse(SpawnLayout.withinBuildHeight(spawn, -64, 91));
|
assertFalse(SpawnLayout.withinBuildHeight(spawn, -64, 91));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void alignsSpawnAxesToTheirIndependentRoadWidths() {
|
||||||
|
Cuboid spawn = SpawnLayout.bounds(2, 1, 3, 32, 10, 32, 6, 2, 62);
|
||||||
|
|
||||||
|
assertEqualsBounds(spawn, 3, 62, 1, 72, 71, 100);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsNonPositiveSpawnMultipliers() {
|
void rejectsNonPositiveSpawnMultipliers() {
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalArgumentException.class, () -> SpawnLayout.bounds(0, 1, 1, 32, 10, 32, 4, 62));
|
IllegalArgumentException.class, () -> SpawnLayout.bounds(0, 1, 1, 32, 10, 32, 4, 4, 62));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertEqualsBounds(
|
private static void assertEqualsBounds(
|
||||||
|
|||||||
@@ -82,6 +82,6 @@ class MetropolisHomeAcquireTest {
|
|||||||
assertEquals(4, MetropolisPlugin.normalizeMaxLevels(4));
|
assertEquals(4, MetropolisPlugin.normalizeMaxLevels(4));
|
||||||
assertEquals(3, MetropolisPlugin.clampMaxLevels(10, 3));
|
assertEquals(3, MetropolisPlugin.clampMaxLevels(10, 3));
|
||||||
assertEquals(1, MetropolisPlugin.clampMaxLevels(2, 1));
|
assertEquals(1, MetropolisPlugin.clampMaxLevels(2, 1));
|
||||||
assertEquals(1, MetropolisPlugin.clampMaxLevels(0, 0));
|
assertEquals(0, MetropolisPlugin.clampMaxLevels(0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user