Avoid saved WorldGuard regions during allocation
This commit is contained in:
@@ -28,6 +28,7 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
||||
- Set `spawn.sizeX`, `spawn.sizeY`, and `spawn.sizeZ` to distinct positive values on a fresh disposable world. Confirm the Spawn region spans the corresponding plot-plus-road logical cells horizontally and the configured plot-height multiple vertically. Check blocks outside those bounds remain unchanged; configure a height exceeding the world's build limit and confirm startup rejects it clearly.
|
||||
- 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.
|
||||
- Create an unrelated saved WorldGuard region in the next candidate area, then allocate a home and confirm it is skipped. Repeat with a non-cuboid region and verify the full bounding cuboid is avoided; confirm `City` itself does not prevent allocation.
|
||||
- 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.
|
||||
|
||||
### Generated blocks and geometry
|
||||
@@ -77,11 +78,12 @@ Use this checklist for behavior that depends on a real Paper world, player inter
|
||||
|
||||
## Existing Automated Coverage
|
||||
|
||||
Run `./gradlew test` for the server-free JUnit suite. It has 57 tests covering:
|
||||
Run `./gradlew test` for the server-free JUnit suite. It has 58 tests covering:
|
||||
|
||||
- `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.
|
||||
- `PlotGridLayoutTest`: zero-offset compatibility, offsets on X/Y/Z, axis-specific spacing, and negative logical-index recovery.
|
||||
- `WorldGuardRegionOccupancyTest`: arbitrary saved-region bounds are excluded while City/global regions are treated as non-occupying envelopes.
|
||||
- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation.
|
||||
- `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`: independently sized street/avenue bands, correct road-type tagging, non-overlapping corners, and disabled roads at zero width.
|
||||
|
||||
@@ -62,6 +62,8 @@ Copy `.env.example` to `.env` and configure the Paper version/build and server p
|
||||
|
||||
`plot.offsetX`, `plot.offsetY`, and `plot.offsetZ` shift the logical plot grid in blocks. Positive or negative offsets apply consistently to plot/road placement, vertical levels, spawn alignment, and logical-cell indexing. Changing offsets does not move existing saved WorldGuard regions or automatically convert an existing world.
|
||||
|
||||
Before allocating, Metropolis treats every saved WorldGuard region except the enclosing `City` and special `__global__` region as occupied. It uses each region's bounding cuboid; for non-cuboid regions this can conservatively leave extra gaps to ensure new plot regions do not overlap them.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -90,6 +90,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
private HashMap<UUID, UserOverride> _userOverrides;
|
||||
private List<HomePermissionOverride> _permissionOverrides;
|
||||
private HashMap<UUID, Integer> _currentHomes;
|
||||
private List<Cuboid> _savedWorldGuardBounds = List.of();
|
||||
|
||||
private PlayerJoinListener _playerJoinListener = null;
|
||||
|
||||
@@ -891,7 +892,7 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return WorldGuardRegionOccupancy.overlapsAny(cuboid, _savedWorldGuardBounds);
|
||||
}
|
||||
|
||||
private boolean areBlocksOccupied(int row, int col, int i) {
|
||||
@@ -911,6 +912,8 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
}
|
||||
|
||||
private Cuboid findNextUnownedHomeRegion(int plotMultiplier) {
|
||||
refreshSavedWorldGuardBounds();
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
int ring = 0;
|
||||
@@ -995,6 +998,11 @@ public class MetropolisPlugin extends JavaPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void refreshSavedWorldGuardBounds() {
|
||||
_savedWorldGuardBounds =
|
||||
WorldGuardRegionOccupancy.boundsToAvoid(regionManager.getRegions().values());
|
||||
}
|
||||
|
||||
private boolean isAvailableSupportedPlot(int row, int col, int plotMultiplier, int level) {
|
||||
return !areBlocksOccupied(row, col, plotMultiplier, level)
|
||||
&& PlotLevelSupport.hasCompleteSupport(
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
This file is part of Metropolis.
|
||||
|
||||
Metropolis is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Metropolis is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||
*/
|
||||
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
public final class WorldGuardRegionOccupancy {
|
||||
private WorldGuardRegionOccupancy() {}
|
||||
|
||||
public static List<Cuboid> boundsToAvoid(Collection<ProtectedRegion> regions) {
|
||||
List<Cuboid> bounds = new ArrayList<>();
|
||||
for (ProtectedRegion region : regions) {
|
||||
String id = region.getId();
|
||||
if (id.equalsIgnoreCase("city") || id.equalsIgnoreCase("__global__")) {
|
||||
continue;
|
||||
}
|
||||
bounds.add(new Cuboid(region.getMinimumPoint(), region.getMaximumPoint()));
|
||||
}
|
||||
return List.copyOf(bounds);
|
||||
}
|
||||
|
||||
public static boolean overlapsAny(Cuboid candidate, Collection<Cuboid> occupiedBounds) {
|
||||
return occupiedBounds.stream().anyMatch(candidate::intersects);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
This file is part of Metropolis.
|
||||
|
||||
Metropolis is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Metropolis is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
||||
*/
|
||||
|
||||
package com.majinnaibu.bukkitplugins.metropolis;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class WorldGuardRegionOccupancyTest {
|
||||
@Test
|
||||
void treatsArbitrarySavedRegionBoundsAsOccupiedButIgnoresCityAndGlobalRegions() {
|
||||
ProtectedRegion market =
|
||||
region("market", BlockVector3.at(40, 62, 40), BlockVector3.at(70, 90, 70));
|
||||
ProtectedRegion city =
|
||||
region("City", BlockVector3.at(-100, 0, -100), BlockVector3.at(100, 320, 100));
|
||||
ProtectedRegion global =
|
||||
region(
|
||||
"__global__",
|
||||
BlockVector3.at(-30_000_000, -64, -30_000_000),
|
||||
BlockVector3.at(30_000_000, 320, 30_000_000));
|
||||
|
||||
List<Cuboid> occupied = WorldGuardRegionOccupancy.boundsToAvoid(List.of(market, city, global));
|
||||
|
||||
assertEquals(1, occupied.size());
|
||||
assertTrue(WorldGuardRegionOccupancy.overlapsAny(new Cuboid(60, 70, 60, 80, 75, 80), occupied));
|
||||
assertFalse(WorldGuardRegionOccupancy.overlapsAny(new Cuboid(0, 62, 0, 31, 71, 31), occupied));
|
||||
}
|
||||
|
||||
private static ProtectedRegion region(String id, BlockVector3 minimum, BlockVector3 maximum) {
|
||||
ProtectedRegion region = mock(ProtectedRegion.class);
|
||||
when(region.getId()).thenReturn(id);
|
||||
when(region.getMinimumPoint()).thenReturn(minimum);
|
||||
when(region.getMaximumPoint()).thenReturn(maximum);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user