Add multi-level plot allocation support
This commit is contained in:
@@ -23,6 +23,7 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
|||||||
- Join as a new player. Confirm one home is allocated and the welcome message reports its actual bounds. Check the corresponding `h_1_<UUID>` region has the player as owner and its bounds match the configured plot size.
|
- Join as a new player. Confirm one home is allocated and the welcome message reports its actual bounds. Check the corresponding `h_1_<UUID>` region has the player as owner and its bounds match the configured plot size.
|
||||||
- Set `plot.initial: 0` on a disposable server, restart, and join as a fresh player. Confirm no home is created until the player runs `/metropolis-home-acquire`; then verify the command creates the initial home and makes it active.
|
- Set `plot.initial: 0` on a disposable server, restart, and join as a fresh player. Confirm no home is created until the player runs `/metropolis-home-acquire`; then verify the command creates the initial home and makes it active.
|
||||||
- Join with a second new player. Confirm the new home is distinct, does not overlap the first home or spawn, and the City region grows to include it.
|
- Join with a second new player. Confirm the new home is distinct, does not overlap the first home or spawn, and the City region grows to include it.
|
||||||
|
- Start from a disposable world configured with `plot.sizeY` and `plot.maxLevels` from the multi-level example. Confirm the level count is honored, the city does not exceed the world's build height, and stacked plots use the same X/Z allocation pattern at each level.
|
||||||
- Join again as an existing player. Confirm the same home is selected and no duplicate region or plot is created.
|
- Join again as an existing player. Confirm the same home is selected and no duplicate region or plot is created.
|
||||||
- Reserve an area before allocating another home. Confirm future allocations skip it. Repeat with reservations near the city edge and near existing roads to look for overlaps or gaps.
|
- Reserve an area before allocating another home. Confirm future allocations skip it. Repeat with reservations near the city edge and near existing roads to look for overlaps or gaps.
|
||||||
- Test a user override that grants a different plot multiplier or home limit. Confirm the generated bounds and allowed number of homes match the override, and that the configured global defaults still apply to other players.
|
- Test a user override that grants a different plot multiplier or home limit. Confirm the generated bounds and allowed number of homes match the override, and that the configured global defaults still apply to other players.
|
||||||
@@ -71,7 +72,7 @@ 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 26 tests covering:
|
Run `./gradlew test` for the server-free JUnit suite. It has 28 tests covering:
|
||||||
|
|
||||||
- `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.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Metropolis expands a protected Minecraft city as players join. Each player can r
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Assigns a protected home plot when a player joins by default and supports acquiring additional homes up to the configured player limit; set `plot.initial` to `0` to require `/metropolis-home-acquire` for the first home.
|
- Assigns a protected home plot when a player joins by default and supports acquiring additional homes up to the configured player limit; set `plot.initial` to `0` to require `/metropolis-home-acquire` for the first home.
|
||||||
|
- Adds stacked plot levels with `plot.maxLevels`, while `plot.sizeY` continues to define the full height of each individual plot.
|
||||||
- Supports per-player plot sizes and home limits through `userOverrides` or configurable permission tiers; acquired homes can be selected as the active home.
|
- Supports per-player plot sizes and home limits through `userOverrides` or configurable permission tiers; acquired homes can be selected as the active home.
|
||||||
- Expands the City region as plots are occupied, with optional spawn generation, world-spawn placement, floors, support blocks, signs, and perimeter walls.
|
- Expands the City region as plots are occupied, with optional spawn generation, world-spawn placement, floors, support blocks, signs, and perimeter walls.
|
||||||
- Generates roads around plots with configurable width, level, material, clearance, and supports.
|
- Generates roads around plots with configurable width, level, material, clearance, and supports.
|
||||||
@@ -57,7 +58,9 @@ Copy `.env.example` to `.env` and configure the Paper version/build and server p
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`plugins/Metropolis/config.yml` controls plot dimensions and limits, floor and support generation, signs, road width/level/material/clearance, spawn behavior, wall generation, world name, and per-player overrides. `plot.multiplier` sets the default plot-size multiplier; `plot.maxPerPlayer` sets the default home limit; `plot.initial` sets how many homes are auto-generated on join, and `0` requires `/metropolis-home-acquire` for the player's first home.
|
`plugins/Metropolis/config.yml` controls plot dimensions and limits, floor and support generation, signs, road width/level/material/clearance, spawn behavior, wall generation, world name, and per-player overrides. The per-level stack is configured with `plot.sizeY` and `plot.maxLevels`: `sizeY` is the full height of one plot level, and `maxLevels` is the number of stacked levels that can be allocated without exceeding the world build height. `plot.multiplier` sets the default plot-size multiplier; `plot.maxPerPlayer` sets the default home limit; `plot.initial` sets how many homes are auto-generated on join, and `0` requires `/metropolis-home-acquire` for the player's first home.
|
||||||
|
|
||||||
|
The breaking multi-level schema is documented in the example config at `src/main/resources/examples/multi-level.yml`. It uses split road sections (`road.streets.*` and `road.avenues.*`), a level-aware `plot.maxLevels` field, and the valid stair material `COBBLESTONE_STAIRS`.
|
||||||
|
|
||||||
Use `permissionOverrides` to assign plot size and home limits through any permission manager. Each permission node maps to a `priority`, `plotMultiplier`, and `maxPlots`; the matching node with the highest priority wins, and the last matching entry in YAML order wins ties. An explicit username entry in `userOverrides` takes precedence over permission rules. If no override matches, the global `plot.*` defaults apply. Permission rules are checked for online players; offline home generation uses username overrides or global defaults. For example:
|
Use `permissionOverrides` to assign plot size and home limits through any permission manager. Each permission node maps to a `priority`, `plotMultiplier`, and `maxPlots`; the matching node with the highest priority wins, and the last matching entry in YAML order wins ties. An explicit username entry in `userOverrides` takes precedence over permission rules. If no override matches, the global `plot.*` defaults apply. Permission rules are checked for online players; offline home generation uses username overrides or global defaults. For example:
|
||||||
|
|
||||||
|
|||||||
@@ -93,10 +93,11 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
int size = 1;
|
int size = 1;
|
||||||
|
|
||||||
private int plotSizeX = 24;
|
private int plotSizeX = 24;
|
||||||
// private int plotSizeY = 256;
|
private int plotSizeY = 256;
|
||||||
private int plotSizeZ = 24;
|
private int plotSizeZ = 24;
|
||||||
private int gridSizeX = 28;
|
private int gridSizeX = 28;
|
||||||
private int gridSizeY = 256;
|
private int gridSizeY = 256;
|
||||||
|
private int maxLevels = 1;
|
||||||
private int gridSizeZ = 28;
|
private int gridSizeZ = 28;
|
||||||
private int roadWidth = 4;
|
private int roadWidth = 4;
|
||||||
private int roadLevel = 62;
|
private int roadLevel = 62;
|
||||||
@@ -178,19 +179,40 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
getLogger().info("Reading configuration from file.");
|
getLogger().info("Reading configuration from file.");
|
||||||
}
|
}
|
||||||
plotSizeX = safeGetIntFromConfig(config, "plot.sizeX");
|
plotSizeX = safeGetIntFromConfig(config, "plot.sizeX");
|
||||||
|
plotSizeY = optionalIntFromConfig(config, "plot.sizeY", 256);
|
||||||
plotSizeZ = safeGetIntFromConfig(config, "plot.sizeZ");
|
plotSizeZ = safeGetIntFromConfig(config, "plot.sizeZ");
|
||||||
|
maxLevels = optionalIntFromConfig(config, "plot.maxLevels", "maxLevels", 1);
|
||||||
generateFloor = safeGetBooleanFromConfig(config, "plot.floor.generate");
|
generateFloor = safeGetBooleanFromConfig(config, "plot.floor.generate");
|
||||||
floorMaterial = safeGetMaterialFromConfig(config, "plot.floor.material");
|
floorMaterial = safeGetMaterialFromConfig(config, "plot.floor.material");
|
||||||
spaceAboveFloor = safeGetIntFromConfig(config, "plot.floor.clearSpaceAbove");
|
spaceAboveFloor = safeGetIntFromConfig(config, "plot.floor.clearSpaceAbove");
|
||||||
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 = safeGetIntFromConfig(config, "road.width");
|
roadWidth =
|
||||||
spaceAboveRoad = safeGetIntFromConfig(config, "road.clearSpaceAbove");
|
safeGetIntFromConfig(config, "road.streets.width", "road.avenues.width", "road.width");
|
||||||
roadLevel = safeGetIntFromConfig(config, "road.level");
|
spaceAboveRoad =
|
||||||
roadMaterial = safeGetMaterialFromConfig(config, "road.material");
|
safeGetIntFromConfig(
|
||||||
generateRoadSupports = safeGetBooleanFromConfig(config, "road.supports.generate");
|
config,
|
||||||
roadSupportMaterial = safeGetMaterialFromConfig(config, "road.supports.material");
|
"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");
|
||||||
generateSpawn = safeGetBooleanFromConfig(config, "spawn.generate");
|
generateSpawn = safeGetBooleanFromConfig(config, "spawn.generate");
|
||||||
setWorldSpawn = safeGetBooleanFromConfig(config, "spawn.setAsWorldSpawn");
|
setWorldSpawn = safeGetBooleanFromConfig(config, "spawn.setAsWorldSpawn");
|
||||||
spawnFloorMaterial = safeGetMaterialFromConfig(config, "spawn.material");
|
spawnFloorMaterial = safeGetMaterialFromConfig(config, "spawn.material");
|
||||||
@@ -243,7 +265,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gridSizeX = plotSizeX + roadWidth;
|
gridSizeX = plotSizeX + roadWidth;
|
||||||
gridSizeY = world.getMaxHeight();
|
gridSizeY = Math.max(plotSizeY + roadWidth, 1);
|
||||||
gridSizeZ = plotSizeZ + roadWidth;
|
gridSizeZ = plotSizeZ + roadWidth;
|
||||||
|
|
||||||
regionManager =
|
regionManager =
|
||||||
@@ -415,43 +437,72 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean safeGetBooleanFromConfig(Configuration config, String name) {
|
private boolean safeGetBooleanFromConfig(Configuration config, String... names) {
|
||||||
if (config.isBoolean(name)) {
|
for (String name : names) {
|
||||||
return config.getBoolean(name);
|
if (config.isBoolean(name)) {
|
||||||
} else {
|
return config.getBoolean(name);
|
||||||
throwInvalidConfigException();
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
throwInvalidConfigException();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int safeGetIntFromConfig(Configuration config, String name) {
|
private int safeGetIntFromConfig(Configuration config, String... names) {
|
||||||
if (config.isInt(name)) {
|
for (String name : names) {
|
||||||
return config.getInt(name);
|
if (config.isInt(name)) {
|
||||||
} else {
|
return config.getInt(name);
|
||||||
throwInvalidConfigException();
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
throwInvalidConfigException();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Material safeGetMaterialFromConfig(Configuration config, String name) {
|
private int optionalIntFromConfig(Configuration config, String[] names, int defaultValue) {
|
||||||
Material material = null;
|
for (String name : names) {
|
||||||
if (config.isInt(name)) {
|
if (config.isInt(name)) {
|
||||||
material =
|
return config.getInt(name);
|
||||||
switch (config.getInt(name)) {
|
}
|
||||||
case 1 -> Material.STONE;
|
|
||||||
case 2 -> Material.GRASS_BLOCK;
|
|
||||||
case 4 -> Material.COBBLESTONE;
|
|
||||||
case 7 -> Material.BEDROCK;
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
} else if (config.isString(name)) {
|
|
||||||
material = Material.matchMaterial(config.getString(name));
|
|
||||||
}
|
}
|
||||||
if (material == null) {
|
return defaultValue;
|
||||||
getLogger().severe("Invalid material configured at " + name);
|
}
|
||||||
throwInvalidConfigException();
|
|
||||||
|
private int optionalIntFromConfig(Configuration config, String name, int defaultValue) {
|
||||||
|
return config.isInt(name) ? config.getInt(name) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int optionalIntFromConfig(
|
||||||
|
Configuration config, String firstName, String secondName, int defaultValue) {
|
||||||
|
if (config.isInt(firstName)) {
|
||||||
|
return config.getInt(firstName);
|
||||||
}
|
}
|
||||||
return material;
|
if (config.isInt(secondName)) {
|
||||||
|
return config.getInt(secondName);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Material safeGetMaterialFromConfig(Configuration config, String... names) {
|
||||||
|
for (String name : names) {
|
||||||
|
Material material = null;
|
||||||
|
if (config.isInt(name)) {
|
||||||
|
material =
|
||||||
|
switch (config.getInt(name)) {
|
||||||
|
case 1 -> Material.STONE;
|
||||||
|
case 2 -> Material.GRASS_BLOCK;
|
||||||
|
case 4 -> Material.COBBLESTONE;
|
||||||
|
case 7 -> Material.BEDROCK;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
} else if (config.isString(name)) {
|
||||||
|
material = Material.matchMaterial(config.getString(name));
|
||||||
|
}
|
||||||
|
if (material != null) {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getLogger().severe("Invalid material configured at " + String.join(" or ", names));
|
||||||
|
throwInvalidConfigException();
|
||||||
|
return Material.STONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void throwInvalidConfigException() {
|
private void throwInvalidConfigException() {
|
||||||
@@ -577,6 +628,16 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
return Math.max(0, count);
|
return Math.max(0, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int normalizeMaxLevels(int count) {
|
||||||
|
return Math.max(1, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int clampMaxLevels(int requestedLevels, int availableLevels) {
|
||||||
|
int normalizedRequested = normalizeMaxLevels(requestedLevels);
|
||||||
|
int normalizedAvailable = Math.max(1, availableLevels);
|
||||||
|
return Math.min(normalizedRequested, normalizedAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean shouldAutoGenerateInitialHome(int count) {
|
public static boolean shouldAutoGenerateInitialHome(int count) {
|
||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
@@ -595,7 +656,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
private void generateFloor(Cuboid plotCuboid) {
|
private void generateFloor(Cuboid plotCuboid) {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = roadLevel;
|
int y = plotCuboid.minY;
|
||||||
int z = 0;
|
int z = 0;
|
||||||
|
|
||||||
for (x = plotCuboid.minX; x <= plotCuboid.maxX; x++) {
|
for (x = plotCuboid.minX; x <= plotCuboid.maxX; x++) {
|
||||||
@@ -636,7 +697,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
private void createRoads(Cuboid plotCuboid, int roadMask) {
|
private void createRoads(Cuboid plotCuboid, int roadMask) {
|
||||||
if (roadWidth > 0) {
|
if (roadWidth > 0) {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = roadLevel;
|
int y = plotCuboid.minY;
|
||||||
int z = 0;
|
int z = 0;
|
||||||
|
|
||||||
if (plotCuboid == null) {
|
if (plotCuboid == null) {
|
||||||
@@ -763,7 +824,14 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBlockOccupied(int row, int col) {
|
public boolean isBlockOccupied(int row, int col) {
|
||||||
Cuboid cuboid = new Cuboid(getGridMin(row, col, 1), getGridMax(row, col, 1));
|
return isBlockOccupied(row, col, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockOccupied(int row, int col, int level, int plotMultiplier) {
|
||||||
|
Cuboid cuboid =
|
||||||
|
new Cuboid(
|
||||||
|
getGridMin(row, col, plotMultiplier, level),
|
||||||
|
getGridMax(row, col, plotMultiplier, level));
|
||||||
for (Plot plot : _occupiedPlots) {
|
for (Plot plot : _occupiedPlots) {
|
||||||
if (plot.getCuboid().intersects(cuboid)) {
|
if (plot.getCuboid().intersects(cuboid)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -778,9 +846,13 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean areBlocksOccupied(int row, int col, int i) {
|
private boolean areBlocksOccupied(int row, int col, int i) {
|
||||||
|
return areBlocksOccupied(row, col, i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean areBlocksOccupied(int row, int col, int i, int level) {
|
||||||
for (int ix = col; ix < col + i; ix++) {
|
for (int ix = col; ix < col + i; ix++) {
|
||||||
for (int iy = row; iy < row + i; iy++) {
|
for (int iy = row; iy < row + i; iy++) {
|
||||||
if (isBlockOccupied(iy, ix)) {
|
if (isBlockOccupied(iy, ix, level, 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,56 +868,63 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
int min = -ring;
|
int min = -ring;
|
||||||
int max = ring - (plotMultiplier - 1);
|
int max = ring - (plotMultiplier - 1);
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
|
int levelCount = getEffectiveMaxLevels();
|
||||||
|
|
||||||
while (!done) {
|
while (!done) {
|
||||||
row = min;
|
row = min;
|
||||||
col = min;
|
col = min;
|
||||||
|
|
||||||
// Top
|
for (int level = 0; level < levelCount; level++) {
|
||||||
for (col = min; col <= max; col++) {
|
// Top
|
||||||
if (!areBlocksOccupied(row, col, plotMultiplier)) {
|
for (col = min; col <= max; col++) {
|
||||||
if (DEBUG) {
|
if (!areBlocksOccupied(row, col, plotMultiplier, level)) {
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
|
||||||
}
|
|
||||||
return new Cuboid(
|
|
||||||
getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right side
|
|
||||||
col = max;
|
|
||||||
for (row = min + 1; row < max; row++) {
|
|
||||||
if (!areBlocksOccupied(row, col, plotMultiplier)) {
|
|
||||||
if (DEBUG) {
|
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
|
||||||
}
|
|
||||||
return new Cuboid(
|
|
||||||
getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom
|
|
||||||
row = max;
|
|
||||||
for (col = max; col >= min; col--) {
|
|
||||||
if (!areBlocksOccupied(row, col, plotMultiplier)) {
|
|
||||||
if (DEBUG) {
|
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
|
||||||
}
|
|
||||||
return new Cuboid(
|
|
||||||
getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left
|
|
||||||
col = min;
|
|
||||||
for (row = max; row > min; row--) {
|
|
||||||
if (!areBlocksOccupied(row, col, plotMultiplier)) {
|
|
||||||
if (row != 0 || col != 0) {
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
getLogger().info(String.format("row: %d, col: %d, level: %d", row, col, level));
|
||||||
}
|
}
|
||||||
return new Cuboid(
|
return new Cuboid(
|
||||||
getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
getPlotMin(row, col, plotMultiplier, level),
|
||||||
|
getPlotMax(row, col, plotMultiplier, level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
col = max;
|
||||||
|
for (row = min + 1; row < max; row++) {
|
||||||
|
if (!areBlocksOccupied(row, col, plotMultiplier, level)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
getLogger().info(String.format("row: %d, col: %d, level: %d", row, col, level));
|
||||||
|
}
|
||||||
|
return new Cuboid(
|
||||||
|
getPlotMin(row, col, plotMultiplier, level),
|
||||||
|
getPlotMax(row, col, plotMultiplier, level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
row = max;
|
||||||
|
for (col = max; col >= min; col--) {
|
||||||
|
if (!areBlocksOccupied(row, col, plotMultiplier, level)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
getLogger().info(String.format("row: %d, col: %d, level: %d", row, col, level));
|
||||||
|
}
|
||||||
|
return new Cuboid(
|
||||||
|
getPlotMin(row, col, plotMultiplier, level),
|
||||||
|
getPlotMax(row, col, plotMultiplier, level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left
|
||||||
|
col = min;
|
||||||
|
for (row = max; row > min; row--) {
|
||||||
|
if (!areBlocksOccupied(row, col, plotMultiplier, level)) {
|
||||||
|
if (row != 0 || col != 0) {
|
||||||
|
if (DEBUG) {
|
||||||
|
getLogger().info(String.format("row: %d, col: %d, level: %d", row, col, level));
|
||||||
|
}
|
||||||
|
return new Cuboid(
|
||||||
|
getPlotMin(row, col, plotMultiplier, level),
|
||||||
|
getPlotMax(row, col, plotMultiplier, level));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -853,12 +932,16 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
ring++;
|
ring++;
|
||||||
min = -ring;
|
min = -ring;
|
||||||
max = ring - (plotMultiplier - 1);
|
max = ring - (plotMultiplier - 1);
|
||||||
|
if (ring > 256) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
getLogger().info(String.format("row: %d, col: %d", row, col));
|
getLogger().info(String.format("row: %d, col: %d", row, col));
|
||||||
}
|
}
|
||||||
return new Cuboid(getPlotMin(row, col, plotMultiplier), getPlotMax(row, col, plotMultiplier));
|
return new Cuboid(
|
||||||
|
getPlotMin(row, col, plotMultiplier, 0), getPlotMax(row, col, plotMultiplier, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resizeCityRegion() {
|
private void resizeCityRegion() {
|
||||||
@@ -902,41 +985,54 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getPlotMin(int row, int col, int plotMultiplier) {
|
public BlockVector3 getPlotMin(int row, int col, int plotMultiplier) {
|
||||||
BlockVector3 gridMin = getGridMin(row, col, plotMultiplier);
|
return getPlotMin(row, col, plotMultiplier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 getPlotMin(int row, int col, int plotMultiplier, int level) {
|
||||||
|
BlockVector3 gridMin = getGridMin(row, col, plotMultiplier, level);
|
||||||
|
|
||||||
BlockVector3 bv =
|
BlockVector3 bv =
|
||||||
BlockVector3.at(gridMin.x() + roadWidth / 2, gridMin.y(), gridMin.z() + roadWidth / 2);
|
BlockVector3.at(
|
||||||
|
gridMin.x() + roadWidth / 2, getLevelStartY(level), gridMin.z() + roadWidth / 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getPlotMax(int row, int col, int plotMultiplier) {
|
public BlockVector3 getPlotMax(int row, int col, int plotMultiplier) {
|
||||||
BlockVector3 gridMax = getGridMax(row, col, plotMultiplier);
|
return getPlotMax(row, col, plotMultiplier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 getPlotMax(int row, int col, int plotMultiplier, int level) {
|
||||||
|
BlockVector3 gridMax = getGridMax(row, col, plotMultiplier, level);
|
||||||
|
|
||||||
BlockVector3 bv =
|
BlockVector3 bv =
|
||||||
BlockVector3.at(
|
BlockVector3.at(
|
||||||
gridMax.x() - (roadWidth - roadWidth / 2),
|
gridMax.x() - (roadWidth - roadWidth / 2),
|
||||||
gridMax.y(),
|
getLevelStartY(level) + plotSizeY - 1,
|
||||||
gridMax.z() - (roadWidth - roadWidth / 2));
|
gridMax.z() - (roadWidth - roadWidth / 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getGridMin(int row, int col, int plotMultiplier) {
|
public BlockVector3 getGridMin(int row, int col, int plotMultiplier) {
|
||||||
int level = 0;
|
return getGridMin(row, col, plotMultiplier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
BlockVector3 bv = BlockVector3.at(col * gridSizeX, level * gridSizeY, row * gridSizeZ);
|
public BlockVector3 getGridMin(int row, int col, int plotMultiplier, int level) {
|
||||||
|
BlockVector3 bv = BlockVector3.at(col * gridSizeX, getLevelStartY(level), row * gridSizeZ);
|
||||||
getLogger().info(String.format("getGridMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
getLogger().info(String.format("getGridMin (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||||
return bv;
|
return bv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockVector3 getGridMax(int row, int col, int plotMultiplier) {
|
public BlockVector3 getGridMax(int row, int col, int plotMultiplier) {
|
||||||
int level = 0;
|
return getGridMax(row, col, plotMultiplier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockVector3 getGridMax(int row, int col, int plotMultiplier, int level) {
|
||||||
BlockVector3 bv =
|
BlockVector3 bv =
|
||||||
BlockVector3.at(
|
BlockVector3.at(
|
||||||
(col + plotMultiplier) * gridSizeX - 1,
|
(col + plotMultiplier) * gridSizeX - 1,
|
||||||
(level + 1 /*plotMultiplier*/) * gridSizeY - 1,
|
getLevelStartY(level) + plotSizeY - 1,
|
||||||
(row + plotMultiplier) * gridSizeZ - 1);
|
(row + plotMultiplier) * gridSizeZ - 1);
|
||||||
getLogger().info(String.format("getGridMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
getLogger().info(String.format("getGridMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
|
||||||
return bv;
|
return bv;
|
||||||
@@ -950,6 +1046,19 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
return (cuboid.minZ - roadWidth / 2) / gridSizeZ;
|
return (cuboid.minZ - roadWidth / 2) / gridSizeZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getLevelPitchY() {
|
||||||
|
return plotSizeY + roadWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEffectiveMaxLevels() {
|
||||||
|
int maxWorldLevels = Math.max(1, (world.getMaxHeight() - roadLevel) / getLevelPitchY());
|
||||||
|
return clampMaxLevels(maxLevels, maxWorldLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLevelStartY(int level) {
|
||||||
|
return roadLevel + level * getLevelPitchY();
|
||||||
|
}
|
||||||
|
|
||||||
private void setHomeOccupied(
|
private void setHomeOccupied(
|
||||||
UUID ownerId,
|
UUID ownerId,
|
||||||
String ownerName,
|
String ownerName,
|
||||||
@@ -1068,7 +1177,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
private void generateSign(Cuboid plotCuboid, String playerName) {
|
private void generateSign(Cuboid plotCuboid, String playerName) {
|
||||||
Block signBlock =
|
Block signBlock =
|
||||||
world.getBlockAt(plotCuboid.getCenterX(), roadLevel + 1, plotCuboid.getCenterZ());
|
world.getBlockAt(plotCuboid.getCenterX(), plotCuboid.minY + 1, plotCuboid.getCenterZ());
|
||||||
signBlock.setType(Material.OAK_SIGN);
|
signBlock.setType(Material.OAK_SIGN);
|
||||||
Sign sign = (Sign) signBlock.getState();
|
Sign sign = (Sign) signBlock.getState();
|
||||||
sign.setLine(0, "Home of");
|
sign.setLine(0, "Home of");
|
||||||
|
|||||||
@@ -18,13 +18,22 @@ plot:
|
|||||||
sign:
|
sign:
|
||||||
generate: true
|
generate: true
|
||||||
road:
|
road:
|
||||||
width: 4
|
avenues:
|
||||||
clearSpaceAbove: 66
|
width: 4
|
||||||
level: 62
|
clearSpaceAbove: 66
|
||||||
material: COBBLESTONE
|
level: 62
|
||||||
supports:
|
material: COBBLESTONE
|
||||||
generate: true
|
supports:
|
||||||
material: STONE
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
streets:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 66
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
spawn:
|
spawn:
|
||||||
generate: true
|
generate: true
|
||||||
setAsWorldSpawn: true
|
setAsWorldSpawn: true
|
||||||
|
|||||||
59
src/main/resources/examples/multi-level.yml
Normal file
59
src/main/resources/examples/multi-level.yml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
plot:
|
||||||
|
sizeX: 32
|
||||||
|
sizeY: 10
|
||||||
|
sizeZ: 32
|
||||||
|
offsetX: 0
|
||||||
|
offsetY: 0
|
||||||
|
offsetZ: 0
|
||||||
|
multiplier: 1
|
||||||
|
maxLevels: 10
|
||||||
|
maxPerPlayer: 1
|
||||||
|
initial: 1
|
||||||
|
floor:
|
||||||
|
generate: true
|
||||||
|
clearSpaceAbove: 0
|
||||||
|
material: GRASS_BLOCK
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
sign:
|
||||||
|
generate: true
|
||||||
|
road:
|
||||||
|
avenues:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 9
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: true
|
||||||
|
material: COBBLESTONE_STAIRS
|
||||||
|
everyNBlocks: 4
|
||||||
|
streets:
|
||||||
|
width: 4
|
||||||
|
clearSpaceAbove: 9
|
||||||
|
level: 62
|
||||||
|
material: COBBLESTONE
|
||||||
|
supports:
|
||||||
|
generate: true
|
||||||
|
material: STONE
|
||||||
|
stairs:
|
||||||
|
generate: false
|
||||||
|
spawn:
|
||||||
|
generate: true
|
||||||
|
setAsWorldSpawn: true
|
||||||
|
material: GRASS_BLOCK
|
||||||
|
wall:
|
||||||
|
generate: true
|
||||||
|
material: BEDROCK
|
||||||
|
height: 10
|
||||||
|
worldname: world
|
||||||
|
userOverrides:
|
||||||
|
- username: majinnaibu
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
|
- username: hoggrim
|
||||||
|
plotMultiplier: 2
|
||||||
|
maxPlots: 2
|
||||||
@@ -75,4 +75,13 @@ class MetropolisHomeAcquireTest {
|
|||||||
assertEquals(0, zero);
|
assertEquals(0, zero);
|
||||||
assertFalse(MetropolisPlugin.shouldAutoGenerateInitialHome(zero));
|
assertFalse(MetropolisPlugin.shouldAutoGenerateInitialHome(zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsLevelCountsWithinTheConfiguredAndWorldBounds() {
|
||||||
|
assertEquals(1, MetropolisPlugin.normalizeMaxLevels(0));
|
||||||
|
assertEquals(4, MetropolisPlugin.normalizeMaxLevels(4));
|
||||||
|
assertEquals(3, MetropolisPlugin.clampMaxLevels(10, 3));
|
||||||
|
assertEquals(1, MetropolisPlugin.clampMaxLevels(2, 1));
|
||||||
|
assertEquals(1, MetropolisPlugin.clampMaxLevels(0, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user