diff --git a/src/main/java/de/t14d3/zones/PermissionManager.java b/src/main/java/de/t14d3/zones/PermissionManager.java index fe17583..43892a4 100644 --- a/src/main/java/de/t14d3/zones/PermissionManager.java +++ b/src/main/java/de/t14d3/zones/PermissionManager.java @@ -4,7 +4,6 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; -import org.bukkit.util.BoundingBox; import java.util.Map; import java.util.UUID; @@ -34,6 +33,12 @@ public void setRegionManager(RegionManager regionManager) { * @return True if the player can interact with the region, false otherwise. */ public boolean canInteract(Location location, UUID playerUUID, Actions action, String type) { + + // Skip checking if player has global bypass permission + Player player = Bukkit.getPlayer(playerUUID); + if (player != null && player.hasPermission("zones.bypass.claimed")) { + return true; + } if (interactionCache.containsKey(playerUUID)) { ConcurrentLinkedQueue entries = interactionCache.get(playerUUID); for (CacheEntry entry : entries) { @@ -42,19 +47,35 @@ public boolean canInteract(Location location, UUID playerUUID, Actions action, S } } } - for (Map.Entry entry : regionManager.regions().entrySet()) { - Region region = entry.getValue(); - BoundingBox box = BoundingBox.of(region.getMin(), region.getMax()); - - if (box.contains(location.toVector())) { - Result hasPermission = hasPermission(playerUUID.toString(), action.name(), type, region); - interactionCache.computeIfAbsent(playerUUID, k -> new ConcurrentLinkedQueue<>()).add(new CacheEntry(location, action.name(), type, hasPermission)); - - return hasPermission.equals(Result.TRUE); + if (!regionManager.getRegionsAt(location).isEmpty()) { + Result result = Result.UNDEFINED; + int priority = Integer.MIN_VALUE; + + for (Region region : regionManager.getRegionsAt(location)) { + + // Only check regions with a higher priority than the current value + if (region.getPriority() > priority) { + Result hasPermission = hasPermission(playerUUID.toString(), action.name(), type, region); + if (!hasPermission.equals(Result.UNDEFINED)) { + result = hasPermission; + priority = region.getPriority(); + continue; + } + } + // If same priority, both have to be true, otherwise will assume false + else if (region.getPriority() == priority) { + Result hasPermission = hasPermission(playerUUID.toString(), action.name(), type, region); + if (hasPermission.equals(Result.FALSE) || result.equals(Result.FALSE)) { + result = hasPermission; + priority = region.getPriority(); + continue; + } + } } - } - Player player = Bukkit.getPlayer(playerUUID); + return result.equals(Result.TRUE); + } + // If no region found, check for unclaimed bypass perm and return false if not found return player != null && player.hasPermission("zones.bypass.unclaimed"); } @@ -164,11 +185,6 @@ private Result calculatePermission(String who, String permission, String type, R } catch (IllegalArgumentException ignored) { } - // Check if player has a global bypass permission - if (player != null && player.hasPermission("zones.bypass.claimed")) { - return Result.TRUE; - } - Result result = Result.UNDEFINED; // Initialize result as null // Check if player is an admin, but only if not checking diff --git a/src/main/java/de/t14d3/zones/Region.java b/src/main/java/de/t14d3/zones/Region.java index 2995b41..6f4eb6f 100644 --- a/src/main/java/de/t14d3/zones/Region.java +++ b/src/main/java/de/t14d3/zones/Region.java @@ -22,41 +22,46 @@ public class Region { private Map> members; private String key; private String parent; + private int priority; // Constructor /** * Constructs a new region with the given name, minimum and maximum locations, members and parent. - * @param name The name of the region (not unique). - * @param min The minimum location of the region. - * @param max The maximum location of the region. - * @param members The members of the region. - * @param key The key of the region. - * @param parent The parent (if any) of the region. * - * @see #Region(String, Location, Location, Map, String) + * @param name The name of the region (not unique). + * @param min The minimum location of the region. + * @param max The maximum location of the region. + * @param members The members of the region. + * @param key The key of the region. + * @param parent The parent (if any) of the region. + * @param priority The priority of the region. + * @see #Region(String, Location, Location, Map, String, int) */ - Region(@NotNull String name, @NotNull Location min, @NotNull Location max, Map> members, @NotNull String key, @Nullable String parent) { + Region(@NotNull String name, @NotNull Location min, @NotNull Location max, Map> members, @NotNull String key, @Nullable String parent, int priority) { this.name = name; this.min = min; this.max = max; this.members = (members != null) ? members : new HashMap<>(); this.key = key; this.parent = parent; + this.priority = priority; } // Constructor overload for regions without parent /** * Constructs a new region with the given name, minimum and maximum locations, and members. - * @param name The name of the region (not unique). - * @param min The minimum location of the region. - * @param max The maximum location of the region. - * @param members The members of the region. - * @param key The key of the region. * - * @see #Region(String, Location, Location, Map, String) + * @param name The name of the region (not unique). + * @param min The minimum location of the region. + * @param max The maximum location of the region. + * @param members The members of the region. + * @param key The key of the region. + * @param priority The priority of the region. + * + * @see #Region(String, Location, Location, Map, String, int) */ - Region(String name, Location min, Location max, Map> members, String key) { - this(name, min, max, members, key, null); + Region(String name, Location min, Location max, Map> members, String key, int priority) { + this(name, min, max, members, key, null, priority); } // Getters and Setters @@ -240,6 +245,14 @@ void addMemberPermission(UUID uuid, String permission, String value, RegionManag return null; } + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + JsonObject getAsJson() { JsonObject json = new JsonObject(); diff --git a/src/main/java/de/t14d3/zones/RegionManager.java b/src/main/java/de/t14d3/zones/RegionManager.java index a9daad6..a9b9c0c 100644 --- a/src/main/java/de/t14d3/zones/RegionManager.java +++ b/src/main/java/de/t14d3/zones/RegionManager.java @@ -8,6 +8,7 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -80,12 +81,13 @@ public Future loadRegions() { loadedRegions.clear(); for (String key : Objects.requireNonNull(regionsConfig.getConfigurationSection("regions")).getKeys(false)) { String name = regionsConfig.getString("regions." + key + ".name"); + int priority = regionsConfig.getInt("regions." + key + ".priority"); Location min = loadLocation("regions." + key + ".min"); Location max = loadLocation("regions." + key + ".max"); String parent = regionsConfig.getString("regions." + key + ".parent"); Map> members = loadMembers(key); - Region region = new Region(name != null ? name : "Invalid Name", min, max, members, key, parent); + Region region = new Region(name != null ? name : "Invalid Name", min, max, members, key, parent, 0); loadedRegions.put(key, region); } regionCache.clear(); @@ -120,6 +122,7 @@ private Map> loadMembers(String key) { // Save a region to the YAML file public void saveRegion(String key, Region region) { regionsConfig.set("regions." + key + ".name", region.getName()); + regionsConfig.set("regions." + key + ".priority", region.getPriority()); saveLocation("regions." + key + ".min", region.getMin()); saveLocation("regions." + key + ".max", region.getMax()); if (region.getParent() != null) { @@ -186,7 +189,7 @@ public String createNewRegion(String name, Location min, Location max, UUID play } while (regions().containsKey(regionKey)); Map> members = new HashMap<>(); - Region newRegion = new Region(name, min, max, members, regionKey); + Region newRegion = new Region(name, min, max, members, regionKey, 0); ownerPermissions.forEach((permission, value) -> { newRegion.addMemberPermission(playerUUID, permission, value, this); @@ -223,8 +226,8 @@ public void createNewRegion(String name, Location min, Location max, UUID player * @param members The members of the new region. * @return The newly created region. */ - public Region createNewRegion(String key, String name, Location min, Location max, Map> members) { - Region region = new Region(name, min, max, members, key); + public Region createNewRegion(String key, String name, Location min, Location max, Map> members, int priority) { + Region region = new Region(name, min, max, members, key, priority); saveRegion(key, region); return region; } @@ -261,7 +264,7 @@ public void createSubRegion(String name, Location min, Location max, UUID player } while (regions().containsKey(regionKey)); Map> members = new HashMap<>(); - Region newRegion = new Region(name, min, max, members, regionKey, parentRegion.getKey()); + Region newRegion = new Region(name, min, max, members, regionKey, parentRegion.getKey(), 0); ownerPermissions.forEach((permission, value) -> { newRegion.addMemberPermission(playerUUID, permission, value, this); @@ -386,16 +389,35 @@ public List getRegionsAt(Location location) { return foundRegions; } for (Region region : regions().values()) { - BoundingBox regionBox = BoundingBox.of(region.getMin(), region.getMax()); - // Check if the location's bounding box overlaps with the region's bounding box - if (regionBox.contains(location.toVector())) { + if (region.contains(location)) { foundRegions.add(region); + regionCache.computeIfAbsent(location, k -> new ArrayList<>()).add(region.getKey()); } } + return foundRegions; } + /** + * Gets the region with the highest priority at the given location + * + * @param location Location to check + * @return Region at location, or null if no region found + */ + public @Nullable Region getEffectiveRegionAt(Location location) { + List regions = getRegionsAt(location); + int priority = Integer.MIN_VALUE; + Region effectiveRegion = null; + for (Region region : regions) { + if (region.getPriority() > priority) { + effectiveRegion = region; + priority = region.getPriority(); + } + } + return effectiveRegion; + } + /** * Gets Regions at location async * @@ -473,7 +495,7 @@ public Map getMemberPermissions(UUID uuid, Region region) { /** * Adds a region to the loaded regions map. - * Requires an existing region object, to create a new region use {@link #createNewRegion(String, String, Location, Location, Map)}. + * Requires an existing region object, to create a new region use {@link #createNewRegion(String, String, Location, Location, Map, int)}. * * @param region The region to add. * diff --git a/src/main/java/de/t14d3/zones/Zones.java b/src/main/java/de/t14d3/zones/Zones.java index de63540..202a5b6 100644 --- a/src/main/java/de/t14d3/zones/Zones.java +++ b/src/main/java/de/t14d3/zones/Zones.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public final class Zones extends JavaPlugin { @@ -31,7 +32,7 @@ public final class Zones extends JavaPlugin { private BeaconUtils beaconUtils; private ParticleHandler particleHandler; public Map> selection = new HashMap<>(); - public Map particles = new HashMap<>(); + public ConcurrentHashMap particles = new ConcurrentHashMap<>(); private Types types; private Messages messages; private static Zones instance; diff --git a/src/main/java/de/t14d3/zones/commands/CommandExecutor.java b/src/main/java/de/t14d3/zones/commands/CommandExecutor.java index 406e652..7f523d8 100644 --- a/src/main/java/de/t14d3/zones/commands/CommandExecutor.java +++ b/src/main/java/de/t14d3/zones/commands/CommandExecutor.java @@ -300,21 +300,21 @@ public void handleListCommand(CommandSender sender) { player = (Player) sender; } boolean perm = sender.hasPermission("zones.info.other"); - for (Map.Entry entry : regions.entrySet()) { - if (!perm && (player != null && !entry.getValue().isMember(player.getUniqueId()))) { + for (Region region : regions.values()) { + if (!perm && (player != null && !region.isMember(player.getUniqueId()))) { continue; } Component hoverText = regionInfo( - entry, + region, (perm || - (player != null && this.plugin.getPermissionManager().isAdmin(player.getUniqueId().toString(), regions.get(entry.getKey()))))) + (player != null && this.plugin.getPermissionManager().isAdmin(player.getUniqueId().toString(), region)))) .join(); var mm = MiniMessage.miniMessage(); - Component comp = mm.deserialize(messages.get("region.info.name"), parsed("name", entry.getValue().getName()), parsed("key", entry.getKey())); + Component comp = mm.deserialize(messages.get("region.info.name"), parsed("name", region.getName()), parsed("key", region.getKey())); HoverEvent hover = hoverText.asHoverEvent(); - ClickEvent click = ClickEvent.runCommand("/zone info " + entry.getKey()); + ClickEvent click = ClickEvent.runCommand("/zone info " + region.getKey()); comp = comp.hoverEvent(hover); comp = comp.clickEvent(click); sender.sendMessage(comp); @@ -347,7 +347,8 @@ public void handleInfoCommand(CommandSender sender, String[] args) { sender.sendMessage(miniMessage.deserialize(messages.get("commands.no-permission"))); return; } - regionInfo(regions.entrySet().stream().filter(entry -> entry.getKey().equals(regionKey)).findFirst().get(), regions.get(regionKey).isAdmin(player.getUniqueId())) + Region region = regions.get(regionKey); + regionInfo(region, region.isAdmin(player.getUniqueId())) .thenAccept(sender::sendMessage); } @@ -460,9 +461,8 @@ public void handleSelectCommand(CommandSender sender, String[] args) { if (sender instanceof Player player) { Region region; if (args.length == 1) { - try { - region = regionManager.getRegionsAt(player.getLocation()).get(0); - } catch (IndexOutOfBoundsException e) { + region = regionManager.getEffectiveRegionAt(player.getLocation()); + if (region == null) { plugin.particles.remove(player.getUniqueId()); player.sendMessage(miniMessage.deserialize(messages.get("commands.select.deselected"))); return; @@ -515,21 +515,21 @@ private void handleModeCommand(CommandSender sender, String[] args) { } } - private CompletableFuture regionInfo(Map.Entry entry, boolean showMembers) { + private CompletableFuture regionInfo(Region region, boolean showMembers) { var mm = MiniMessage.miniMessage(); Component comp = Component.text(""); - comp = comp.append(mm.deserialize(messages.get("region.info.name"), parsed("name", entry.getValue().getName()))); + comp = comp.append(mm.deserialize(messages.get("region.info.name"), parsed("name", region.getName()))); comp = comp.appendNewline(); - if (entry.getValue().getParent() != null) { - comp = comp.append(mm.deserialize(messages.get("region.info.parent"), parsed("parent", entry.getValue().getParent()))); + if (region.getParent() != null) { + comp = comp.append(mm.deserialize(messages.get("region.info.parent"), parsed("parent", region.getParent()))); } - comp = comp.append(mm.deserialize(messages.get("region.info.min"), parsed("min", entry.getValue().getMinString()))); + comp = comp.append(mm.deserialize(messages.get("region.info.min"), parsed("min", region.getMinString()))); comp = comp.appendNewline(); - comp = comp.append(mm.deserialize(messages.get("region.info.max"), parsed("max", entry.getValue().getMaxString()))); + comp = comp.append(mm.deserialize(messages.get("region.info.max"), parsed("max", region.getMaxString()))); if (showMembers) { // Iterate over members to format permissions - for (Map.Entry> member : entry.getValue().getMembers().entrySet()) { + for (Map.Entry> member : region.getMembers().entrySet()) { String playerName = null; try { playerName = Bukkit.getOfflinePlayer(UUID.fromString(member.getKey())).getName(); @@ -583,7 +583,7 @@ private CompletableFuture regionInfo(Map.Entry entry, .append(permissionsComponent); } } - comp = comp.append(mm.deserialize(messages.get("region.info.key"), parsed("key", entry.getKey()))); + comp = comp.append(mm.deserialize(messages.get("region.info.key"), parsed("key", region.getKey()))); return CompletableFuture.completedFuture(comp); } diff --git a/src/main/java/de/t14d3/zones/integrations/FAWEIntegration.java b/src/main/java/de/t14d3/zones/integrations/FAWEIntegration.java index 1731f94..1bc42c8 100644 --- a/src/main/java/de/t14d3/zones/integrations/FAWEIntegration.java +++ b/src/main/java/de/t14d3/zones/integrations/FAWEIntegration.java @@ -10,8 +10,6 @@ import org.bukkit.Location; import org.bukkit.entity.Player; -import java.util.List; - public class FAWEIntegration extends BukkitMaskManager { private final Zones plugin; @@ -37,19 +35,10 @@ public boolean isAllowed(Player player, Region region, MaskType type) { public FaweMask getMask(final com.sk89q.worldedit.entity.Player wePlayer, final MaskType type, boolean isWhitelist) { final Player player = BukkitAdapter.adapt(wePlayer); final Location location = player.getLocation(); - List regions = plugin.getRegionManager().getRegionsAt(location); - if (!regions.isEmpty()) { - boolean isAllowed = true; - for (Region region : regions) { - if (!isAllowed(player, region, type)) { - isAllowed = false; - break; - } - } - if (isAllowed) { - final Location pos1 = regions.get(0).getMin(); - final Location pos2 = regions.get(0).getMax(); - final Region region = regions.get(0); + Region region = plugin.getRegionManager().getEffectiveRegionAt(location); + if (isAllowed(player, region, type)) { + final Location pos1 = region.getMin(); + final Location pos2 = region.getMax(); return new FaweMask(new CuboidRegion(BukkitAdapter.asBlockVector(pos1), BukkitAdapter.asBlockVector(pos2))) { @Override public boolean isValid(com.sk89q.worldedit.entity.Player player, MaskType type) { @@ -57,10 +46,6 @@ public boolean isValid(com.sk89q.worldedit.entity.Player player, MaskType type) } }; } - } - - return null; } - } diff --git a/src/main/java/de/t14d3/zones/integrations/PlaceholderAPI.java b/src/main/java/de/t14d3/zones/integrations/PlaceholderAPI.java index 8ee77a9..f9142f9 100644 --- a/src/main/java/de/t14d3/zones/integrations/PlaceholderAPI.java +++ b/src/main/java/de/t14d3/zones/integrations/PlaceholderAPI.java @@ -55,6 +55,8 @@ public boolean persist() { "%zones_get_max_x%", "%zones_get_max_y%", "%zones_get_max_z%", + "%zones_get_priority%", + "%zones_get_parent%", "%zones_is_member%", "%zones_can_place_hand%", "%zones_can_break_target%", @@ -189,6 +191,18 @@ public String onPlaceholderRequest(Player player, @NotNull String params) { } return "false"; } + if (params.equalsIgnoreCase("get_priority")) { + if (!regions.isEmpty()) { + return String.valueOf(regions.get(0).getPriority()); + } + return "0"; + } + if (params.equalsIgnoreCase("get_parent")) { + if (!regions.isEmpty()) { + return regions.get(0).getParent(); + } + return ""; + } return null; } } diff --git a/src/main/java/de/t14d3/zones/integrations/WorldGuardImporter.java b/src/main/java/de/t14d3/zones/integrations/WorldGuardImporter.java index e4762a3..9f8ee40 100644 --- a/src/main/java/de/t14d3/zones/integrations/WorldGuardImporter.java +++ b/src/main/java/de/t14d3/zones/integrations/WorldGuardImporter.java @@ -47,7 +47,7 @@ public void importRegions() { region.getMembers().getUniqueIds().forEach(uuid -> members.put(uuid.toString(), Map.of("group", "member"))); region.getOwners().getUniqueIds().forEach(uuid -> members.put(uuid.toString(), Map.of("role", "owner"))); - Region newRegion = plugin.getRegionManager().createNewRegion(key, name, min, max, members); + Region newRegion = plugin.getRegionManager().createNewRegion(key, name, min, max, members, region.getPriority()); plugin.getRegionManager().addRegion(newRegion); count[0]++; }