Adds multiple home support.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

This commit is contained in:
2026-09-27 04:15:26 -07:00
parent 013dc6749c
commit a13f512be6
12 changed files with 488 additions and 45 deletions

View File

@@ -0,0 +1,41 @@
/*
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.HashSet;
import java.util.List;
import java.util.Set;
public final class HomeNumberAllocator {
private HomeNumberAllocator() {}
public static int nextHomeNumber(List<Plot> plots) {
Set<Integer> allocatedNumbers = new HashSet<>();
for (Plot plot : plots) {
if (plot instanceof PlayerHome home) {
allocatedNumbers.add(home.getNumber());
}
}
int nextHomeNumber = 1;
while (allocatedNumbers.contains(nextHomeNumber)) {
nextHomeNumber++;
}
return nextHomeNumber;
}
}

View File

@@ -0,0 +1,154 @@
/*
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.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.bukkit.configuration.ConfigurationSection;
public final class HomePermissionOverride {
private final String _permission;
private final int _priority;
private final int _plotMultiplier;
private final int _maxPlots;
private HomePermissionOverride(
String permission, int priority, int plotMultiplier, int maxPlots) {
_permission = permission;
_priority = priority;
_plotMultiplier = plotMultiplier;
_maxPlots = maxPlots;
}
public String getPermission() {
return _permission;
}
public int getPriority() {
return _priority;
}
public int getPlotMultiplier() {
return _plotMultiplier;
}
public int getMaxPlots() {
return _maxPlots;
}
public static List<HomePermissionOverride> load(
ConfigurationSection section, Consumer<String> warningLogger) {
if (section == null) {
return List.of();
}
List<HomePermissionOverride> overrides = new ArrayList<>();
collect(section, "", overrides, warningLogger);
return List.copyOf(overrides);
}
private static void collect(
ConfigurationSection section,
String permissionPrefix,
List<HomePermissionOverride> overrides,
Consumer<String> warningLogger) {
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
String permission =
permissionPrefix.isEmpty() ? entry.getKey() : permissionPrefix + "." + entry.getKey();
Map<?, ?> values = getValues(entry.getValue());
if (values != null && !hasRuleValues(values)) {
collect((ConfigurationSection) entry.getValue(), permission, overrides, warningLogger);
continue;
}
if (permission.isBlank()
|| values == null
|| !(values.get("priority") instanceof Number priority)
|| !(values.get("plotMultiplier") instanceof Number plotMultiplier)
|| !(values.get("maxPlots") instanceof Number maxPlots)
|| priority.intValue() != priority.doubleValue()
|| plotMultiplier.intValue() != plotMultiplier.doubleValue()
|| maxPlots.intValue() != maxPlots.doubleValue()
|| plotMultiplier.intValue() < 1
|| maxPlots.intValue() < 0) {
warningLogger.accept("Ignoring invalid permissionOverrides entry '" + permission + "'.");
continue;
}
overrides.add(
new HomePermissionOverride(
permission, priority.intValue(), plotMultiplier.intValue(), maxPlots.intValue()));
}
}
private static boolean hasRuleValues(Map<?, ?> values) {
return values.containsKey("priority")
|| values.containsKey("plotMultiplier")
|| values.containsKey("maxPlots");
}
public static HomePermissionOverride findMatch(
List<HomePermissionOverride> overrides, Predicate<String> hasPermission) {
HomePermissionOverride match = null;
for (HomePermissionOverride override : overrides) {
if (hasPermission.test(override.getPermission())
&& (match == null || override.getPriority() >= match.getPriority())) {
match = override;
}
}
return match;
}
public static int resolveMaxPlots(
UserOverride userOverride,
List<HomePermissionOverride> overrides,
Predicate<String> hasPermission,
int fallback) {
if (userOverride != null) {
return userOverride.getMaxPlots();
}
HomePermissionOverride match = findMatch(overrides, hasPermission);
return match == null ? fallback : match.getMaxPlots();
}
public static int resolvePlotMultiplier(
UserOverride userOverride,
List<HomePermissionOverride> overrides,
Predicate<String> hasPermission,
int fallback) {
if (userOverride != null) {
return userOverride.getPlotMultiplier();
}
HomePermissionOverride match = findMatch(overrides, hasPermission);
return match == null ? fallback : match.getPlotMultiplier();
}
private static Map<?, ?> getValues(Object value) {
if (value instanceof ConfigurationSection section) {
return section.getValues(false);
}
if (value instanceof Map<?, ?> map) {
return map;
}
return null;
}
}

View File

@@ -85,6 +85,7 @@ public class MetropolisPlugin extends JavaPlugin {
private List<Plot> _occupiedPlots;
private HashMap<UUID, List<Plot>> _ownedPlots;
private HashMap<UUID, UserOverride> _userOverrides;
private List<HomePermissionOverride> _permissionOverrides;
private HashMap<UUID, Integer> _currentHomes;
private PlayerJoinListener _playerJoinListener = null;
@@ -196,10 +197,14 @@ public class MetropolisPlugin extends JavaPlugin {
wallMaterial = safeGetMaterialFromConfig(config, "wall.material");
wallHeight = safeGetIntFromConfig(config, "wall.height");
worldName = safeGetStringFromConfig(config, "worldname");
_maxPlots = safeGetIntFromConfig(config, "plot.multiplier");
_plotMultiplier = safeGetIntFromConfig(config, "plot.maxPerPlayer");
_plotMultiplier = safeGetIntFromConfig(config, "plot.multiplier");
_maxPlots = safeGetIntFromConfig(config, "plot.maxPerPlayer");
buildUserOverrides();
_permissionOverrides =
HomePermissionOverride.load(
config.getConfigurationSection("permissionOverrides"),
warning -> getLogger().warning(warning));
saveConfig();
if (DEBUG) {
@@ -494,9 +499,6 @@ public class MetropolisPlugin extends JavaPlugin {
ProtectedCuboidRegion cuboidRegion = (ProtectedCuboidRegion) region;
if (cuboidRegion.getId().startsWith("h_")) {
PlayerHome home = PlayerHome.get(region);
if (!_currentHomes.containsKey(home.getPlayerId())) {
_currentHomes.put(home.getPlayerId(), home.getNumber());
}
_occupiedPlots.add(home);
addOwnedPlot(home.getPlayerId(), home);
} else if (cuboidRegion.getId().startsWith("r_")) {
@@ -505,7 +507,17 @@ public class MetropolisPlugin extends JavaPlugin {
}
}
for (UUID playerId : _ownedPlots.keySet()) {
if (!_currentHomes.containsKey(playerId)) {
PlayerHome firstHome = getFirstOwnedHome(playerId);
if (firstHome != null) {
_currentHomes.put(playerId, firstHome.getNumber());
}
}
}
size = calculateCitySize();
saveCurrentHomes();
}
private void addOwnedPlot(UUID playerId, Plot plot) {
@@ -527,20 +539,27 @@ public class MetropolisPlugin extends JavaPlugin {
public PlayerHome getPlayerHome(Player player) {
PlayerHome home = null;
int homeNumber = _currentHomes.getOrDefault(player.getUniqueId(), 1);
UUID playerId = player.getUniqueId();
int homeNumber = _currentHomes.getOrDefault(playerId, 1);
String regionName = String.format("h_%d_%s", homeNumber, player.getUniqueId());
ProtectedRegion homeRegion = regionManager.getRegion(regionName);
if (homeRegion == null) {
PlayerHome existingHome = getOwnedHome(player.getUniqueId(), homeNumber);
PlayerHome existingHome = getOwnedHome(playerId, homeNumber);
if (existingHome != null) {
existingHome.setPlayerName(player.getName());
return existingHome;
}
existingHome = getFirstOwnedHome(playerId);
if (existingHome != null) {
existingHome.setPlayerName(player.getName());
setHome(playerId, existingHome.getNumber());
return existingHome;
}
if (DEBUG) {
getLogger().info(String.format("Creating home for player %s", player.getName()));
}
home = generateHome(player);
home = generateHome(player, 1);
} else {
home = new PlayerHome(homeRegion);
home.setPlayerName(player.getName());
@@ -891,7 +910,7 @@ public class MetropolisPlugin extends JavaPlugin {
BlockVector3 bv =
BlockVector3.at(
(col + plotMultiplier) * gridSizeX * plotMultiplier - 1,
(col + plotMultiplier) * gridSizeX - 1,
(level + 1 /*plotMultiplier*/) * gridSizeY - 1,
(row + plotMultiplier) * gridSizeZ - 1);
getLogger().info(String.format("getGridMax (%d, %d, %d)", bv.x(), bv.y(), bv.z()));
@@ -924,10 +943,14 @@ public class MetropolisPlugin extends JavaPlugin {
}
public PlayerHome generateHome(OfflinePlayer player) {
int homeNumber = _currentHomes.getOrDefault(player.getUniqueId(), 1);
return generateHome(player, homeNumber);
}
public PlayerHome generateHome(OfflinePlayer player, int homeNumber) {
UUID playerId = player.getUniqueId();
String playerName = player.getName() == null ? playerId.toString() : player.getName();
int homeNumber = _currentHomes.getOrDefault(playerId, 1);
int multiplier = getPlotMultiplier(playerId);
int multiplier = getPlotMultiplier(player);
if (DEBUG) {
getLogger().info(String.format("Generating home for %s", playerName));
@@ -993,7 +1016,7 @@ public class MetropolisPlugin extends JavaPlugin {
homeNumber,
newHomeRegion.getMinimumPoint(),
newHomeRegion.getMaximumPoint());
_currentHomes.putIfAbsent(playerId, homeNumber);
_currentHomes.put(playerId, homeNumber);
saveCurrentHomes();
createRoads(homeCuboid);
@@ -1130,10 +1153,28 @@ public class MetropolisPlugin extends JavaPlugin {
}
}
public int getMaxPlots(Player player) {
return HomePermissionOverride.resolveMaxPlots(
_userOverrides.get(player.getUniqueId()),
_permissionOverrides,
player::hasPermission,
_maxPlots);
}
public void assignPlot(OfflinePlayer player) {
generateHome(player);
}
public PlayerHome acquireHome(OfflinePlayer player) {
List<Plot> ownedPlots = _ownedPlots.getOrDefault(player.getUniqueId(), List.of());
int nextHomeNumber = HomeNumberAllocator.nextHomeNumber(ownedPlots);
PlayerHome home = generateHome(player, nextHomeNumber);
if (home != null) {
setHome(player.getUniqueId(), home.getNumber());
}
return home;
}
private int getPlotMultiplier(UUID playerId) {
if (_userOverrides.containsKey(playerId)) {
return _userOverrides.get(playerId).getPlotMultiplier();
@@ -1142,6 +1183,21 @@ public class MetropolisPlugin extends JavaPlugin {
}
}
private int getPlotMultiplier(OfflinePlayer player) {
if (player instanceof Player onlinePlayer) {
return HomePermissionOverride.resolvePlotMultiplier(
_userOverrides.get(player.getUniqueId()),
_permissionOverrides,
onlinePlayer::hasPermission,
_plotMultiplier);
}
return HomePermissionOverride.resolvePlotMultiplier(
_userOverrides.get(player.getUniqueId()),
_permissionOverrides,
ignored -> false,
_plotMultiplier);
}
public Plot getPlot(String string) {
/**
* string is the name of the region to get a plot for
@@ -1229,6 +1285,18 @@ public class MetropolisPlugin extends JavaPlugin {
return null;
}
private PlayerHome getFirstOwnedHome(UUID playerId) {
List<Plot> plots = _ownedPlots.get(playerId);
if (plots == null) {
return null;
}
return plots.stream()
.filter(PlayerHome.class::isInstance)
.map(PlayerHome.class::cast)
.min(java.util.Comparator.comparingInt(PlayerHome::getNumber))
.orElse(null);
}
private void saveCurrentHomes() {
File homesFile = new File(getDataFolder(), "currentHomes.yml");
try {

View File

@@ -40,12 +40,11 @@ public class MetropolisHomeAcquire implements CommandExecutor {
Player player = (Player) sender;
if (_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player.getUniqueId())) {
if (_plugin.getNumPlots(player.getUniqueId()) >= _plugin.getMaxPlots(player)) {
sender.sendMessage("You cannot have any more plots");
return false;
}
_plugin.assignPlot(player);
return true;
return _plugin.acquireHome(player) != null;
}
}