diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java index 72ea700f..538b66bf 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java @@ -24,6 +24,7 @@ import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; import io.papermc.lib.PaperLib; import org.bukkit.BanList.Type; import org.bukkit.Bukkit; @@ -54,6 +55,16 @@ public class BukkitPlayer extends com.sk89q.worldedit.bukkit.BukkitPlayer implem return name; } + @Override + public String getDefaultNamespace() { + // TODO: Add a per player override + boolean useNamespaces = WorldGuard.getInstance().getPlatform().getGlobalStateManager().useNamespaces; + if (useNamespaces) { + return getUniqueId().toString(); + } + return null; + } + @Override public boolean hasGroup(String group) { return plugin.inGroup(getPlayer(), group); diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java b/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java index 7395bebf..b2b735ee 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java @@ -31,6 +31,29 @@ import java.util.List; public interface LocalPlayer extends Player, RegionAssociable { + /** + * Returns the default namespace for this player. + * + * @return the default namespace. + */ + String getDefaultNamespace(); + + default boolean isDefaultNamespace(String otherNamespace) { + String namespace = getDefaultNamespace(); + // If both are null, they're the same (the global) + if (namespace == null && otherNamespace == null) { + return true; + } + + // If only one is null, they're a mismatch + if (namespace == null || otherNamespace == null) { + return false; + } + + // Compare string equality + return namespace.equalsIgnoreCase(otherNamespace); + } + /** * Returns true if this player is inside a group. * diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java index e41eb1b9..809c89fc 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java @@ -32,6 +32,7 @@ import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.domains.DefaultDomain; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionIdentifier; import com.sk89q.worldguard.protection.util.DomainInputResolver; import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; @@ -50,13 +51,13 @@ public class MemberCommands extends RegionCommandsBase { flags = "nw:", desc = "Add a member to a region", min = 2) - public void addMember(CommandContext args, Actor sender) throws CommandException { + public void addMember(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world - String id = args.getString(0); RegionManager manager = checkRegionManager(world); - ProtectedRegion region = checkExistingRegion(manager, id, true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion region = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).mayAddMembers(region)) { @@ -69,10 +70,11 @@ public class MemberCommands extends RegionCommandsBase { resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_ONLY); - final String description = String.format("Adding members to the region '%s' on '%s'", region.getId(), world.getName()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Adding members to the region '%s' on '%s'", friendlyName, world.getName()); AsyncCommandBuilder.wrap(resolver, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) - .onSuccess(String.format("Region '%s' updated with new members.", region.getId()), region.getMembers()::addAll) + .onSuccess(String.format("Region '%s' updated with new members.", friendlyName), region.getMembers()::addAll) .onFailure("Failed to add new members", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -82,15 +84,14 @@ public class MemberCommands extends RegionCommandsBase { flags = "nw:", desc = "Add an owner to a region", min = 2) - public void addOwner(CommandContext args, Actor sender) throws CommandException { + public void addOwner(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world - String id = args.getString(0); - RegionManager manager = checkRegionManager(world); - ProtectedRegion region = checkExistingRegion(manager, id, true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion region = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).mayAddOwners(region)) { @@ -103,10 +104,11 @@ public class MemberCommands extends RegionCommandsBase { resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_ONLY); - final String description = String.format("Adding owners to the region '%s' on '%s'", region.getId(), world.getName()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Adding owners to the region '%s' on '%s'", friendlyName, world.getName()); AsyncCommandBuilder.wrap(checkedAddOwners(sender, manager, region, world, resolver), sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) - .onSuccess(String.format("Region '%s' updated with new owners.", region.getId()), region.getOwners()::addAll) + .onSuccess(String.format("Region '%s' updated with new owners.", friendlyName), region.getOwners()::addAll) .onFailure("Failed to add new owners", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -149,13 +151,13 @@ public class MemberCommands extends RegionCommandsBase { flags = "naw:", desc = "Remove an owner to a region", min = 1) - public void removeMember(CommandContext args, Actor sender) throws CommandException { + public void removeMember(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world - String id = args.getString(0); RegionManager manager = checkRegionManager(world); - ProtectedRegion region = checkExistingRegion(manager, id, true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion region = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).mayRemoveMembers(region)) { @@ -178,11 +180,12 @@ public class MemberCommands extends RegionCommandsBase { callable = resolver; } - final String description = String.format("Removing members from the region '%s' on '%s'", region.getId(), world.getName()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Removing members from the region '%s' on '%s'", friendlyName, world.getName()); AsyncCommandBuilder.wrap(callable, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .sendMessageAfterDelay("(Please wait... querying player names...)") - .onSuccess(String.format("Region '%s' updated with members removed.", region.getId()), region.getMembers()::removeAll) + .onSuccess(String.format("Region '%s' updated with members removed.", friendlyName), region.getMembers()::removeAll) .onFailure("Failed to remove members", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -192,13 +195,13 @@ public class MemberCommands extends RegionCommandsBase { flags = "naw:", desc = "Remove an owner to a region", min = 1) - public void removeOwner(CommandContext args, Actor sender) throws CommandException { + public void removeOwner(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world - String id = args.getString(0); RegionManager manager = checkRegionManager(world); - ProtectedRegion region = checkExistingRegion(manager, id, true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion region = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).mayRemoveOwners(region)) { @@ -221,11 +224,12 @@ public class MemberCommands extends RegionCommandsBase { callable = resolver; } - final String description = String.format("Removing owners from the region '%s' on '%s'", region.getId(), world.getName()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Removing owners from the region '%s' on '%s'", friendlyName, world.getName()); AsyncCommandBuilder.wrap(callable, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .sendMessageAfterDelay("(Please wait... querying player names...)") - .onSuccess(String.format("Region '%s' updated with owners removed.", region.getId()), region.getOwners()::removeAll) + .onSuccess(String.format("Region '%s' updated with owners removed.", friendlyName), region.getOwners()::removeAll) .onFailure("Failed to remove owners", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java index 426d1406..d14646b6 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java @@ -32,6 +32,7 @@ import com.sk89q.worldedit.command.util.AsyncCommandBuilder; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.util.formatting.component.ErrorFormat; import com.sk89q.worldedit.util.formatting.component.LabelFormat; import com.sk89q.worldedit.util.formatting.component.SubtleFormat; @@ -44,7 +45,6 @@ import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; import com.sk89q.worldedit.world.World; import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.commands.CommandUtils; import com.sk89q.worldguard.commands.task.RegionAdder; import com.sk89q.worldguard.commands.task.RegionLister; import com.sk89q.worldguard.commands.task.RegionManagerLoader; @@ -68,11 +68,8 @@ import com.sk89q.worldguard.protection.managers.migration.MigrationException; import com.sk89q.worldguard.protection.managers.migration.UUIDMigration; import com.sk89q.worldguard.protection.managers.storage.DriverType; import com.sk89q.worldguard.protection.managers.storage.RegionDriver; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.*; import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; -import com.sk89q.worldguard.protection.regions.RegionContainer; import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; import com.sk89q.worldguard.session.Session; import com.sk89q.worldguard.util.Enums; @@ -133,7 +130,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "ng", desc = "Defines a region", min = 1) - public void define(CommandContext args, Actor sender) throws CommandException { + public void define(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); LocalPlayer player = worldGuard.checkPlayer(sender); @@ -142,12 +139,12 @@ public final class RegionCommands extends RegionCommandsBase { throw new CommandPermissionsException(); } - String id = checkRegionId(args.getString(0), false); + RegionIdentifier id = processRegionId(sender, args.getString(0), false); World world = player.getWorld(); RegionManager manager = checkRegionManager(world); - checkRegionDoesNotExist(manager, id, true); + checkRegionDoesNotExist(sender, manager, id, true); ProtectedRegion region; @@ -160,17 +157,18 @@ public final class RegionCommands extends RegionCommandsBase { RegionAdder task = new RegionAdder(manager, region); task.addOwnersFromCommand(args, 2); - final String description = String.format("Adding region '%s'", region.getId()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Adding region '%s'", friendlyName); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .onSuccess((Component) null, t -> { - sender.print(String.format("A new region has been made named '%s'.", region.getId())); + sender.print(String.format("A new region has been made named '%s'.", friendlyName)); warnAboutDimensions(sender, region); informNewUser(sender, manager, region); checkSpawnOverlap(sender, world, region); }) - .onFailure(String.format("Failed to add the region '%s'", region.getId()), worldGuard.getExceptionConverter()) + .onFailure(String.format("Failed to add the region '%s'", friendlyName), worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -186,17 +184,17 @@ public final class RegionCommands extends RegionCommandsBase { desc = "Re-defines the shape of a region", flags = "g", min = 1, max = 1) - public void redefine(CommandContext args, Actor sender) throws CommandException { + public void redefine(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); LocalPlayer player = worldGuard.checkPlayer(sender); - String id = checkRegionId(args.getString(0), false); + RegionIdentifier id = processRegionId(sender, args.getString(0), false); World world = player.getWorld(); RegionManager manager = checkRegionManager(world); - ProtectedRegion existing = checkExistingRegion(manager, id, false); + ProtectedRegion existing = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(player).mayRedefine(existing)) { @@ -215,18 +213,19 @@ public final class RegionCommands extends RegionCommandsBase { RegionAdder task = new RegionAdder(manager, region); - final String description = String.format("Updating region '%s'", region.getId()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Updating region '%s'", friendlyName); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .sendMessageAfterDelay("(Please wait... " + description + ")") .onSuccess((Component) null, t -> { - player.print(String.format("Region '%s' has been updated with a new area.", region.getId())); + player.print(String.format("Region '%s' has been updated with a new area.", friendlyName)); warnAboutDimensions(player, region); informNewUser(player, manager, region); checkSpawnOverlap(sender, world, region); }) - .onFailure(String.format("Failed to update the region '%s'", region.getId()), worldGuard.getExceptionConverter()) + .onFailure(String.format("Failed to update the region '%s'", friendlyName), worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -244,7 +243,7 @@ public final class RegionCommands extends RegionCommandsBase { usage = "", desc = "Claim a region", min = 1, max = 1) - public void claim(CommandContext args, Actor sender) throws CommandException { + public void claim(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); LocalPlayer player = worldGuard.checkPlayer(sender); @@ -255,11 +254,11 @@ public final class RegionCommands extends RegionCommandsBase { throw new CommandPermissionsException(); } - String id = checkRegionId(args.getString(0), false); + RegionIdentifier id = processRegionId(sender, args.getString(0), false); RegionManager manager = checkRegionManager(player.getWorld()); - checkRegionDoesNotExist(manager, id, false); + checkRegionDoesNotExist(sender, manager, id, false); ProtectedRegion region = checkRegionFromSelection(player, id); WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(player.getWorld()); @@ -321,11 +320,12 @@ public final class RegionCommands extends RegionCommandsBase { task.setLocatorPolicy(UserLocatorPolicy.UUID_ONLY); task.setOwnersInput(new String[]{player.getName()}); - final String description = String.format("Claiming region '%s'", id); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Claiming region '%s'", friendlyName); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), description) .sendMessageAfterDelay("(Please wait... " + description + ")") - .onSuccess(TextComponent.of(String.format("A new region has been claimed named '%s'.", id)), null) + .onSuccess(TextComponent.of(String.format("A new region has been claimed named '%s'.", friendlyName)), null) .onFailure("Failed to claim region", WorldGuard.getInstance().getExceptionConverter()) .buildAndExec(WorldGuard.getInstance().getExecutorService()); } @@ -341,7 +341,7 @@ public final class RegionCommands extends RegionCommandsBase { usage = "[id]", desc = "Load a region as a WorldEdit selection", min = 0, max = 1) - public void select(CommandContext args, Actor sender) throws CommandException { + public void select(CommandContext args, Actor sender) throws CommandException, AuthorizationException { LocalPlayer player = worldGuard.checkPlayer(sender); RegionManager manager = checkRegionManager(player.getWorld()); ProtectedRegion existing; @@ -350,7 +350,8 @@ public final class RegionCommands extends RegionCommandsBase { if (args.argsLength() == 0) { existing = checkRegionStandingIn(manager, player, "/rg select %id%"); } else { - existing = checkExistingRegion(manager, args.getString(0), false); + RegionIdentifier id = processRegionId(sender, args.getString(0), false); + existing = checkExistingRegion(sender, manager, id); } // Check permissions @@ -374,7 +375,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "usw:", desc = "Get information about a region", min = 0, max = 1) - public void info(CommandContext args, Actor sender) throws CommandException { + public void info(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world @@ -392,7 +393,8 @@ public final class RegionCommands extends RegionCommandsBase { existing = checkRegionStandingIn(manager, (LocalPlayer) sender, true, "/rg info -w \"" + world.getName() + "\" %id%" + (args.hasFlag('u') ? " -u" : "") + (args.hasFlag('s') ? " -s" : "")); } else { // Get region from the ID - existing = checkExistingRegion(manager, args.getString(0), true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + existing = checkExistingRegion(sender, manager, id); } // Check permissions @@ -488,7 +490,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "g:w:eh:", desc = "Set flags", min = 2) - public void flag(CommandContext args, Actor sender) throws CommandException { + public void flag(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world @@ -508,13 +510,13 @@ public final class RegionCommands extends RegionCommandsBase { // Lookup the existing region RegionManager manager = checkRegionManager(world); - ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion existing = checkExistingRegion(sender, manager, id); // Check permissions if (!permModel.maySetFlag(existing)) { throw new CommandPermissionsException(); } - String regionId = existing.getId(); Flag foundFlag = Flags.fuzzyMatchFlag(flagRegistry, flagName); @@ -522,7 +524,7 @@ public final class RegionCommands extends RegionCommandsBase { // can use, and do nothing afterwards if (foundFlag == null) { AsyncCommandBuilder.wrap(new FlagListBuilder(flagRegistry, permModel, existing, world, - regionId, sender, flagName), sender) + id, sender, flagName), sender) .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), "Flag list for invalid flag command.") .onSuccess((Component) null, sender::print) .onFailure((Component) null, WorldGuard.getInstance().getExceptionConverter()) @@ -579,7 +581,8 @@ public final class RegionCommands extends RegionCommandsBase { } if (!args.hasFlag('h')) { - sender.print("Region flag " + foundFlag.getName() + " set on '" + regionId + "' to '" + value + "'."); + String friendlyName = id.getDisplayName(sender); + sender.print("Region flag " + foundFlag.getName() + " set on '" + friendlyName + "' to '" + value + "'."); } // No value? Clear the flag, if -g isn't specified @@ -594,7 +597,8 @@ public final class RegionCommands extends RegionCommandsBase { } if (!args.hasFlag('h')) { - sender.print("Region flag " + foundFlag.getName() + " removed from '" + regionId + "'. (Any -g(roups) were also removed.)"); + String friendlyName = id.getDisplayName(sender); + sender.print("Region flag " + foundFlag.getName() + " removed from '" + friendlyName + "'. (Any -g(roups) were also removed.)"); } } @@ -630,7 +634,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "p:w:", desc = "View region flags", min = 0, max = 2) - public void flagHelper(CommandContext args, Actor sender) throws CommandException { + public void flagHelper(CommandContext args, Actor sender) throws CommandException, AuthorizationException { World world = checkWorld(args, sender, 'w'); // Get the world // Lookup the existing region @@ -644,7 +648,8 @@ public final class RegionCommands extends RegionCommandsBase { region = checkRegionStandingIn(manager, (LocalPlayer) sender, true, "/rg flags -w \"" + world.getName() + "\" %id%"); } else { // Get region from the ID - region = checkExistingRegion(manager, args.getString(0), true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + region = checkExistingRegion(sender, manager, id); } final RegionPermissionModel perms = getPermissionModel(sender); @@ -680,7 +685,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "w:", desc = "Set the priority of a region", min = 2, max = 2) - public void setPriority(CommandContext args, Actor sender) throws CommandException { + public void setPriority(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world @@ -688,7 +693,8 @@ public final class RegionCommands extends RegionCommandsBase { // Lookup the existing region RegionManager manager = checkRegionManager(world); - ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), false); + RegionIdentifier id = processRegionId(sender, args.getString(0), false); + ProtectedRegion existing = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).maySetPriority(existing)) { @@ -697,7 +703,8 @@ public final class RegionCommands extends RegionCommandsBase { existing.setPriority(priority); - sender.print("Priority of '" + existing.getId() + "' set to " + priority + " (higher numbers override)."); + String friendlyName = id.getDisplayName(sender); + sender.print("Priority of '" + friendlyName + "' set to " + priority + " (higher numbers override)."); } /** @@ -712,7 +719,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "w:", desc = "Set the parent of a region", min = 1, max = 2) - public void setParent(CommandContext args, Actor sender) throws CommandException { + public void setParent(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world @@ -723,9 +730,11 @@ public final class RegionCommands extends RegionCommandsBase { RegionManager manager = checkRegionManager(world); // Get parent and child - child = checkExistingRegion(manager, args.getString(0), false); + RegionIdentifier childId = processRegionId(sender, args.getString(0), false); + child = checkExistingRegion(sender, manager, childId); if (args.argsLength() == 2) { - parent = checkExistingRegion(manager, args.getString(1), false); + RegionIdentifier parentId = processRegionId(sender, args.getString(1), false); + parent = checkExistingRegion(sender, manager, parentId); } else { parent = null; } @@ -739,11 +748,15 @@ public final class RegionCommands extends RegionCommandsBase { child.setParent(parent); } catch (CircularInheritanceException e) { // Tell the user what's wrong - RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), parent, null, sender); assert parent != null; - printout.append(ErrorFormat.wrap("Uh oh! Setting '", parent.getId(), "' to be the parent of '", child.getId(), + + String parentFriendlyName = parent.getIdentifier().getDisplayName(sender); + String childFriendlyName = childId.getDisplayName(sender); + + RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), parent, null, sender); + printout.append(ErrorFormat.wrap("Uh oh! Setting '", parentFriendlyName, "' to be the parent of '", childFriendlyName, "' would cause circular inheritance.")).newline(); - printout.append(SubtleFormat.wrap("(Current inheritance on '", parent.getId(), "':")).newline(); + printout.append(SubtleFormat.wrap("(Current inheritance on '", parentFriendlyName, "':")).newline(); printout.appendParentTree(true); printout.append(SubtleFormat.wrap(")")); printout.send(sender); @@ -751,8 +764,9 @@ public final class RegionCommands extends RegionCommandsBase { } // Tell the user the current inheritance + String childFriendlyName = childId.getDisplayName(sender); RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), child, null, sender); - printout.append(TextComponent.of("Inheritance set for region '" + child.getId() + "'.", TextColor.LIGHT_PURPLE)); + printout.append(TextComponent.of("Inheritance set for region '" + childFriendlyName + "'.", TextColor.LIGHT_PURPLE)); if (parent != null) { printout.newline(); printout.append(SubtleFormat.wrap("(Current inheritance:")).newline(); @@ -776,7 +790,7 @@ public final class RegionCommands extends RegionCommandsBase { flags = "fuw:", desc = "Remove a region", min = 1, max = 1) - public void remove(CommandContext args, Actor sender) throws CommandException { + public void remove(CommandContext args, Actor sender) throws CommandException, AuthorizationException { warnAboutSaveFailures(sender); World world = checkWorld(args, sender, 'w'); // Get the world @@ -785,7 +799,8 @@ public final class RegionCommands extends RegionCommandsBase { // Lookup the existing region RegionManager manager = checkRegionManager(world); - ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion existing = checkExistingRegion(sender, manager, id); // Check permissions if (!getPermissionModel(sender).mayDelete(existing)) { @@ -802,12 +817,15 @@ public final class RegionCommands extends RegionCommandsBase { task.setRemovalStrategy(RemovalStrategy.UNSET_PARENT_IN_CHILDREN); } - final String description = String.format("Removing region '%s' in '%s'", existing.getId(), world.getName()); + String friendlyName = id.getDisplayName(sender); + final String description = String.format("Removing region '%s' in '%s'", friendlyName, world.getName()); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), description) .sendMessageAfterDelay("Please wait... removing region.") .onSuccess((Component) null, removed -> sender.print(TextComponent.of( - "Successfully removed " + removed.stream().map(ProtectedRegion::getId).collect(Collectors.joining(", ")) + ".", + "Successfully removed " + removed.stream() + .map((r) -> r.getIdentifier().getDisplayName(sender)) + .collect(Collectors.joining(", ")) + ".", TextColor.LIGHT_PURPLE))) .onFailure("Failed to remove region", WorldGuard.getInstance().getExceptionConverter()) .buildAndExec(WorldGuard.getInstance().getExecutorService()); @@ -842,10 +860,6 @@ public final class RegionCommands extends RegionCommandsBase { if (world != null) { RegionManager manager = checkRegionManager(world); - if (manager == null) { - throw new CommandException("No region manager exists for world '" + world.getName() + "'."); - } - final String description = String.format("Loading region data for '%s'.", world.getName()); AsyncCommandBuilder.wrap(new RegionManagerLoader(manager), sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) @@ -902,10 +916,6 @@ public final class RegionCommands extends RegionCommandsBase { if (world != null) { RegionManager manager = checkRegionManager(world); - if (manager == null) { - throw new CommandException("No region manager exists for world '" + world.getName() + "'."); - } - final String description = String.format("Saving region data for '%s'.", world.getName()); AsyncCommandBuilder.wrap(new RegionManagerSaver(manager), sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) @@ -1068,14 +1078,15 @@ public final class RegionCommands extends RegionCommandsBase { flags = "sw:", desc = "Teleports you to the location associated with the region.", min = 1, max = 1) - public void teleport(CommandContext args, Actor sender) throws CommandException { + public void teleport(CommandContext args, Actor sender) throws CommandException, AuthorizationException { LocalPlayer player = worldGuard.checkPlayer(sender); Location teleportLocation; // Lookup the existing region World world = checkWorld(args, player, 'w'); RegionManager regionManager = checkRegionManager(world); - ProtectedRegion existing = checkExistingRegion(regionManager, args.getString(0), true); + RegionIdentifier id = processRegionId(sender, args.getString(0), true); + ProtectedRegion existing = checkExistingRegion(sender, regionManager, id); // Check permissions if (!getPermissionModel(player).mayTeleportTo(existing)) { @@ -1098,9 +1109,10 @@ public final class RegionCommands extends RegionCommandsBase { } } + String friendlyName = id.getDisplayName(sender); player.teleport(teleportLocation, - "Teleported you to the region '" + existing.getId() + "'.", - "Unable to teleport to region '" + existing.getId() + "'."); + "Teleported you to the region '" + friendlyName + "'.", + "Unable to teleport to region '" + friendlyName + "'."); } @Command(aliases = {"toggle-bypass", "bypass"}, @@ -1123,12 +1135,12 @@ public final class RegionCommands extends RegionCommandsBase { private final RegionPermissionModel permModel; private final ProtectedRegion existing; private final World world; - private final String regionId; + private final RegionIdentifier regionId; private final Actor sender; private final String flagName; FlagListBuilder(FlagRegistry flagRegistry, RegionPermissionModel permModel, ProtectedRegion existing, - World world, String regionId, Actor sender, String flagName) { + World world, RegionIdentifier regionId, Actor sender, String flagName) { this.flagRegistry = flagRegistry; this.permModel = permModel; this.existing = existing; @@ -1160,9 +1172,10 @@ public final class RegionCommands extends RegionCommandsBase { for (int i = 0; i < flagList.size(); i++) { String flag = flagList.get(i); + String qualifiedName = regionId.getQualifiedName(); builder.append(TextComponent.of(flag, i % 2 == 0 ? TextColor.GRAY : TextColor.WHITE) .hoverEvent(clickToSet).clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, - "/rg flag -w \"" + world.getName() + "\" " + regionId + " " + flag + " "))); + "/rg flag -w \"" + world.getName() + "\" " + qualifiedName + " " + flag + " "))); if (i < flagList.size() + 1) { builder.append(TextComponent.of(", ")); } @@ -1172,10 +1185,12 @@ public final class RegionCommands extends RegionCommandsBase { .append(TextComponent.newline()) .append(builder.build()); if (sender.isPlayer()) { + String friendlyName = regionId.getDisplayName(sender); + String qualifiedName = regionId.getQualifiedName(); return ret.append(TextComponent.of("Or use the command ", TextColor.LIGHT_PURPLE) - .append(TextComponent.of("/rg flags " + regionId, TextColor.AQUA) + .append(TextComponent.of("/rg flags " + friendlyName, TextColor.AQUA) .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, - "/rg flags -w \"" + world.getName() + "\" " + regionId)))); + "/rg flags -w \"" + world.getName() + "\" " + qualifiedName)))); } return ret; } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java index a3dee9f6..a83bb39e 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java @@ -33,13 +33,13 @@ import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.Polygonal2DRegionSelector; +import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.util.formatting.component.ErrorFormat; import com.sk89q.worldedit.util.formatting.component.SubtleFormat; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; -import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; import com.sk89q.worldedit.world.World; import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.WorldGuard; @@ -49,13 +49,13 @@ import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.flags.FlagContext; import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.*; +import com.sk89q.worldguard.util.profile.Profile; +import java.io.IOException; +import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; class RegionCommandsBase { @@ -77,9 +77,9 @@ class RegionCommandsBase { * Gets the world from the given flag, or falling back to the the current player * if the sender is a player, otherwise reporting an error. * - * @param args the arguments + * @param args the arguments * @param sender the sender - * @param flag the flag (such as 'w') + * @param flag the flag (such as 'w') * @return a world * @throws CommandException on error */ @@ -95,28 +95,190 @@ class RegionCommandsBase { } } + /** + * Validate a region name. + * + * @param name the name + * @param allowGlobal whether __global__ is allowed + * @throws CommandException thrown on an error + */ + protected static void checkName(String name, boolean allowGlobal) throws CommandException { + if (!RegionIdentifier.isValidName(name)) { + throw new CommandException( + "The region name of '" + name + "' contains characters that are not allowed."); + } + + if (!allowGlobal && name.equalsIgnoreCase("__global__")) { // Sorry, no global + throw new CommandException( + "Sorry, you can't use __global__ here."); + } + } + /** * Validate a region ID. * - * @param id the id + * @param id the id * @param allowGlobal whether __global__ is allowed * @return the id given * @throws CommandException thrown on an error */ + @Deprecated protected static String checkRegionId(String id, boolean allowGlobal) throws CommandException { - if (!ProtectedRegion.isValidId(id)) { - throw new CommandException( - "The region name of '" + id + "' contains characters that are not allowed."); - } - - if (!allowGlobal && id.equalsIgnoreCase("__global__")) { // Sorry, no global - throw new CommandException( - "Sorry, you can't use __global__ here."); - } - + checkName(id, allowGlobal); return id; } + /** + * Validate a region namespace. + * + * @param namespace the namespace name + * @throws CommandException thrown on an error + */ + protected static void checkNamespace(String namespace) throws CommandException { + if (!RegionIdentifier.isValidNamespace(namespace)) { + throw new CommandException( + "The region namespace of '" + namespace + "' contains characters that are not allowed."); + } + } + + /** + * Get the unqualified name from a possibly qualified region identifier. + * + * @param id the unprocessed region identifier + * @return the unqualified name, or an empty string if not present + */ + protected static String getUnqualifiedName(String id) { + int namespaceSeparatorIndex = id.lastIndexOf(':'); + if (namespaceSeparatorIndex == -1) { + return id; + } + + int unqualifiedNameStartIndex = namespaceSeparatorIndex + 1; + if (unqualifiedNameStartIndex == id.length()) { + return ""; + } + + return id.substring(unqualifiedNameStartIndex); + } + + /** + * Get the namespace name from a possibly qualified region identifier. + * + * @param id the unprocessed region identifier + * @return an optional containing the namespace name, or an empty string if qualified, + * if not qualified, an empty optional is returned + */ + protected static Optional getNamespace(String id) { + int namespaceSeparatorIndex = id.lastIndexOf(':'); + if (namespaceSeparatorIndex == -1) { + return Optional.empty(); + } + + return Optional.of(id.substring(0, namespaceSeparatorIndex)); + } + + private static class InvalidMacroException extends RuntimeException { + public InvalidMacroException(String message) { + super(message); + } + } + + /*** + * Expand macros in the namespace name. + * + * @param namespace the unexpanded namespace name + * @return the expanded namespace name + */ + protected static String expandNamespace(String namespace) { + if (namespace.startsWith("#")) { + String playerName = namespace.substring(1); + + try { + Profile profile = WorldGuard.getInstance().getProfileService().findByName(playerName); + if (profile != null) { + return profile.getUniqueId().toString(); + } + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + } + + throw new InvalidMacroException("Macro unresolvable: " + namespace); + } + + return namespace; + } + + /** + * Get the macro expanded namespace name from a possibly qualified region identifier. + * + * @param id the unprocessed region identifier + * @return an optional containing the expanded namespace name, or an empty string if qualified, + * if not qualified, an empty optional is returned + */ + protected static Optional getExpandedNamespace(String id) throws CommandException { + try { + return getNamespace(id).map(RegionCommandsBase::expandNamespace); + } catch (InvalidMacroException ex) { + throw new CommandException(ex.getMessage()); + } + } + + /** + * Get the default namespace for a given actor. + * + * @param sender the sender who's default namespace we intend to retrieve. + * @return the default namespace + */ + protected static String getDefaultNamespace(Actor sender) { + if (sender instanceof LocalPlayer) { + return ((LocalPlayer) sender).getDefaultNamespace(); + } + + return null; + } + + /** + * Process a possibly qualified region identifier into a RegionIdentifier. + * + * This method takes a possibly qualified region identifier, and processes it, expanding any namespace macros, + * running permissions checks, and validating the names for invalid character. + * + * @param sender the contextual sender to use for checks and macro expansions + * @param id the possibly unqualified id + * @param allowGlobal whether or not this method should allow use of the name __global__ name + * @return the processed region id + * @throws AuthorizationException if a permission check fails + * @throws CommandException if a name validation check fails + */ + protected static RegionIdentifier processRegionId(Actor sender, String id, boolean allowGlobal) throws AuthorizationException, CommandException { + String unqualifiedName = getUnqualifiedName(id); + checkName(unqualifiedName, allowGlobal); + + Optional optProvidedNamespace = getExpandedNamespace(id); + String namespace = optProvidedNamespace.orElse(getDefaultNamespace(sender)); + + // TODO use more informative permission checks + + // This needs checked as a special case, before namespace name validation. + if (namespace.equals("")) { + sender.checkPermission("worldguard.region.namespace.global"); + return new RegionIdentifier(null, unqualifiedName); + } + + checkNamespace(namespace); + + try { + UUID namespacePlayerId = UUID.fromString(namespace); + if (!namespacePlayerId.equals(sender.getUniqueId())) { + sender.checkPermission("worldguard.region.namespace.player"); + } + } catch (IllegalArgumentException ex) { + sender.checkPermission("worldguard.region.namespace.custom." + namespace); + } + + return new RegionIdentifier(namespace, unqualifiedName); + } + /** * Get a protected region by a given name, otherwise throw a * {@link CommandException}. @@ -128,6 +290,7 @@ class RegionCommandsBase { * @param allowGlobal true to allow selecting __global__ * @throws CommandException thrown if no region is found by the given name */ + @Deprecated protected static ProtectedRegion checkExistingRegion(RegionManager regionManager, String id, boolean allowGlobal) throws CommandException { // Validate the id checkRegionId(id, allowGlobal); @@ -150,6 +313,36 @@ class RegionCommandsBase { return region; } + /** + * Get a protected region by a given name, otherwise throw a + * {@link CommandException}. + * + *

This also validates the region ID.

+ * + * @param actor the associated player + * @param regionManager the region manager + * @param id the name to search + * @throws CommandException thrown if no region is found by the given name + */ + protected static ProtectedRegion checkExistingRegion(Actor actor, RegionManager regionManager, RegionIdentifier id) throws CommandException { + ProtectedRegion region = regionManager.getRegion(id); + + // No region found! + if (region == null) { + // But we want a __global__, so let's create one + if (id.getQualifiedName().equals(":__global__")) { + region = new GlobalProtectedRegion(id); + regionManager.addRegion(region); + return region; + } + + throw new CommandException( + "No region could be found with the name of '" + id.getDisplayName(actor) + "'."); + } + + return region; + } + /** * Get the region at the player's location, if possible. @@ -204,10 +397,12 @@ class RegionCommandsBase { builder.append(TextComponent.of(", ")); } first = false; - TextComponent regionComp = TextComponent.of(region.getId(), TextColor.AQUA); + + RegionIdentifier id = region.getIdentifier(); + TextComponent regionComp = TextComponent.of(id.getDisplayName(player), TextColor.AQUA); if (rgCmd != null && rgCmd.contains("%id%")) { regionComp = regionComp.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to pick this region"))) - .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, rgCmd.replace("%id%", region.getId()))); + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, rgCmd.replace("%id%", id.getQualifiedName()))); } builder.append(regionComp); } @@ -244,10 +439,23 @@ class RegionCommandsBase { * @param id the ID * @throws CommandException thrown if the ID already exists */ + @Deprecated protected static void checkRegionDoesNotExist(RegionManager manager, String id, boolean mayRedefine) throws CommandException { + checkRegionDoesNotExist(null, manager, new RegionIdentifier(id), mayRedefine); + } + + /** + * Check that a region with the given ID does not already exist. + * + * @param actor the associated player + * @param manager the manager + * @param id the identifier + * @throws CommandException thrown if the ID already exists + */ + protected static void checkRegionDoesNotExist(Actor actor, RegionManager manager, RegionIdentifier id, boolean mayRedefine) throws CommandException { if (manager.hasRegion(id)) { throw new CommandException("A region with that name already exists. Please choose another name." + - (mayRedefine ? " To change the shape, use /region redefine " + id + "." : "")); + (mayRedefine ? " To change the shape, use /region redefine " + id.getDisplayName(actor) + "." : "")); } } @@ -280,7 +488,20 @@ class RegionCommandsBase { * @return a new region * @throws CommandException thrown on an error */ + @Deprecated protected static ProtectedRegion checkRegionFromSelection(LocalPlayer player, String id) throws CommandException { + return checkRegionFromSelection(player, new RegionIdentifier(id)); + } + + /** + * Create a {@link ProtectedRegion} from the player's selection. + * + * @param player the player + * @param id the identifier of the new region + * @return a new region + * @throws CommandException thrown on an error + */ + protected static ProtectedRegion checkRegionFromSelection(LocalPlayer player, RegionIdentifier id) throws CommandException { Region selection = checkSelection(player); // Detect the type of region from WorldEdit diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java index d9bcc86b..c6946a84 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java @@ -19,6 +19,7 @@ package com.sk89q.worldguard.commands.region; +import com.sk89q.worldguard.protection.regions.RegionIdentifier; import com.sk89q.worldguard.util.profile.cache.ProfileCache; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.math.BlockVector3; @@ -58,6 +59,7 @@ public class RegionPrintoutBuilder implements Callable { @Nullable private final ProfileCache cache; private final TextComponentProducer builder = new TextComponentProducer(); + private final Actor actor; private final RegionPermissionModel perms; /** @@ -70,6 +72,7 @@ public class RegionPrintoutBuilder implements Callable { this.world = world; this.region = region; this.cache = cache; + this.actor = actor; this.perms = actor != null && actor.isPlayer() ? new RegionPermissionModel(actor) : null; } @@ -85,8 +88,10 @@ public class RegionPrintoutBuilder implements Callable { */ public void appendBasics() { builder.append(TextComponent.of("Region: ", TextColor.BLUE)); - builder.append(TextComponent.of(region.getId(), TextColor.YELLOW) - .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg info -w \"" + world + "\" " + region.getId()))); + + RegionIdentifier id = region.getIdentifier(); + builder.append(TextComponent.of(id.getDisplayName(actor), TextColor.YELLOW) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg info -w \"" + world + "\" " + id.getQualifiedName()))); builder.append(TextComponent.of(" (type=", TextColor.GRAY)); builder.append(TextComponent.of(region.getType().getName())); diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java b/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java index c9dd5e1d..c4e1c145 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java @@ -76,6 +76,7 @@ public abstract class ConfigurationManager { public boolean migrateRegionsToUuid; public boolean keepUnresolvedNames; public boolean particleEffects; + public boolean useNamespaces; @Unreported public Map hostKeys = new HashMap<>(); public boolean hostKeysAllowFMLClients; diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java b/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java index 56f45443..cc4492e1 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java @@ -63,6 +63,7 @@ public abstract class YamlConfigurationManager extends ConfigurationManager { usePlayerMove = config.getBoolean("use-player-move-event", true); usePlayerTeleports = config.getBoolean("use-player-teleports", true); particleEffects = config.getBoolean("use-particle-effects", true); + useNamespaces = config.getBoolean("use-namespaces", true); deopOnJoin = config.getBoolean("security.deop-everyone-on-join", false); blockInGameOp = config.getBoolean("security.block-in-game-op-command", false); diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java index 4fc45ecb..a139ae20 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java @@ -26,6 +26,7 @@ import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionIdentifier; import javax.annotation.Nullable; diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java index eb8a399e..21ef06fc 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java @@ -34,6 +34,7 @@ import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; import com.sk89q.worldguard.protection.managers.storage.StorageException; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionIdentifier; import com.sk89q.worldguard.protection.util.RegionCollectionConsumer; import com.sk89q.worldguard.util.Normal; @@ -242,10 +243,22 @@ public final class RegionManager { * @param id the name of the region * @return true if this index contains the region */ + @Deprecated public boolean hasRegion(String id) { return index.contains(id); } + /** + * Return whether the index contains a region with the given identifier, + * with quality determined by {@link Normal}. + * + * @param id the region identifier + * @return true if this index contains the region + */ + public boolean hasRegion(RegionIdentifier id) { + return index.contains(id.getLegacyQualifiedName()); + } + /** * Get the region named by the given name (equality determined using * {@link Normal}). @@ -254,11 +267,24 @@ public final class RegionManager { * @return a region or {@code null} */ @Nullable + @Deprecated public ProtectedRegion getRegion(String id) { checkNotNull(id); return index.get(id); } + /** + * Get the region named y the given id. + * + * @param id the region identifier + * @return a region or {@code null} + */ + @Nullable + public ProtectedRegion getRegion(RegionIdentifier id) { + checkNotNull(id); + return index.get(id.getLegacyQualifiedName()); + } + /** * @deprecated Use exact ids with {@link #getRegion} */ diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java index 61e0f1e2..36d28d91 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java @@ -70,12 +70,13 @@ public class HashMapIndex extends AbstractRegionIndex implements ConcurrentRegio region.setDirty(true); synchronized (lock) { - String normalId = normalize(region.getId()); + String newFullId = region.getIdentifier().getLegacyQualifiedName(); + String normalId = normalize(newFullId); ProtectedRegion existing = regions.get(normalId); // Casing / form of ID has changed - if (existing != null && !existing.getId().equals(region.getId())) { + if (existing != null && !existing.getIdentifier().getLegacyQualifiedName().equals(newFullId)) { removed.add(existing); } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DomainParser.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DomainParser.java new file mode 100644 index 00000000..160b79e6 --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DomainParser.java @@ -0,0 +1,60 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.worldguard.domains.DefaultDomain; + +import java.util.UUID; +import java.util.logging.Level; + +class DomainParser { + public static class One { + public static DefaultDomain parseDomain(YAMLNode node) { + if (node == null) { + return new DefaultDomain(); + } + + DefaultDomain domain = new DefaultDomain(); + + for (String name : node.getStringList("players", null)) { + if (!name.isEmpty()) { + domain.addPlayer(name); + } + } + + for (String stringId : node.getStringList("unique-ids", null)) { + try { + domain.addPlayer(UUID.fromString(stringId)); + } catch (IllegalArgumentException e) { + YamlCommon.log.log(Level.WARNING, "Failed to parse UUID '" + stringId + "'", e); + } + } + + for (String name : node.getStringList("groups", null)) { + if (!name.isEmpty()) { + domain.addGroup(name); + } + } + + return domain; + } + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/FlagParser.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/FlagParser.java new file mode 100644 index 00000000..be5d2940 --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/FlagParser.java @@ -0,0 +1,34 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +class FlagParser { + public static class One { + public static void setFlags(FlagRegistry flagRegistry, ProtectedRegion region, YAMLNode flagsData) { + if (flagsData != null) { + region.setFlags(flagRegistry.unmarshal(flagsData.getMap(), true)); + } + } + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlCommon.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlCommon.java new file mode 100644 index 00000000..0297297b --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlCommon.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.util.logging.Logger; + +class YamlCommon { + public static final Logger log = Logger.getLogger(YamlRegionFile.class.getCanonicalName()); + public static final Yaml ERROR_DUMP_YAML; + + static { + DumperOptions options = new DumperOptions(); + options.setIndent(4); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO); + + ERROR_DUMP_YAML = new Yaml(new SafeConstructor(), new Representer(), options); + } + + public static final String YAML_GLOBAL_NAMESPACE_NAME = "__global_ns__"; + + private YamlCommon() { + } + + /** + * Dump the given object as YAML for debugging purposes. + * + * @param object the object + * @return the YAML string or an error string if dumping fals + */ + public static String toYamlOutput(Object object) { + try { + return ERROR_DUMP_YAML.dump(object).replaceAll("(?m)^", "\t"); + } catch (Throwable t) { + return ""; + } + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReader.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReader.java new file mode 100644 index 00000000..ef928775 --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReader.java @@ -0,0 +1,34 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Set; + +interface YamlReader { + public int getVersion(); + default public boolean canLoad(YAMLProcessor config) { + return config.getInt("version", 1) == getVersion(); + } + public Set load(FlagRegistry flagRegistry, YAMLProcessor config); +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionOne.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionOne.java new file mode 100644 index 00000000..2541108d --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionOne.java @@ -0,0 +1,114 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabaseUtils; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.*; +import java.util.logging.Level; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldguard.protection.managers.storage.file.YamlCommon.log; +import static com.sk89q.worldguard.protection.managers.storage.file.YamlCommon.toYamlOutput; + +class YamlReaderVersionOne implements YamlReader { + @Override + public int getVersion() { + return 1; + } + + @Override + public Set load(FlagRegistry flagRegistry, YAMLProcessor config) { + Map loaded = new HashMap<>(); + Map regionData = config.getNodes("regions"); + + if (regionData == null) { + return Collections.emptySet(); // No regions are even configured + } + + Map parentSets = new LinkedHashMap<>(); + + for (Map.Entry entry : regionData.entrySet()) { + String id = entry.getKey(); + YAMLNode node = entry.getValue(); + + String type = node.getString("type"); + ProtectedRegion region; + + try { + if (type == null) { + log.warning("Undefined region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } else if (type.equals("cuboid")) { + Vector3 pt1 = checkNotNull(node.getVector("min")); + Vector3 pt2 = checkNotNull(node.getVector("max")); + BlockVector3 min = pt1.getMinimum(pt2).toBlockPoint(); + BlockVector3 max = pt1.getMaximum(pt2).toBlockPoint(); + region = new ProtectedCuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + Integer minY = checkNotNull(node.getInt("min-y")); + Integer maxY = checkNotNull(node.getInt("max-y")); + List points = node.getBlockVector2List("points", null); + region = new ProtectedPolygonalRegion(id, points, minY, maxY); + } else if (type.equals("global")) { + region = new GlobalProtectedRegion(id); + } else { + log.warning("Unknown region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } + + Integer priority = checkNotNull(node.getInt("priority")); + region.setPriority(priority); + FlagParser.One.setFlags(flagRegistry, region, node.getNode("flags")); + region.setOwners(DomainParser.One.parseDomain(node.getNode("owners"))); + region.setMembers(DomainParser.One.parseDomain(node.getNode("members"))); + + loaded.put(id, region); + + String parentId = node.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } catch (NullPointerException e) { + log.log(Level.WARNING, + "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + + "\n\nNote: This region will disappear as a result!", e); + } + } + + // Relink parents + RegionDatabaseUtils.relinkParents(loaded, parentSets); + + return new HashSet<>(loaded.values()); + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionTwo.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionTwo.java new file mode 100644 index 00000000..bd485bf7 --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlReaderVersionTwo.java @@ -0,0 +1,120 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabaseUtils; +import com.sk89q.worldguard.protection.regions.*; + +import java.util.*; +import java.util.logging.Level; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldguard.protection.managers.storage.file.YamlCommon.*; + +class YamlReaderVersionTwo implements YamlReader { + @Override + public int getVersion() { + return 2; + } + + @Override + public Set load(FlagRegistry flagRegistry, YAMLProcessor config) { + Map loaded = new HashMap<>(); + Map regionData = config.getNodes("regions"); + + if (regionData == null) { + return Collections.emptySet(); // No regions are even configured + } + + Map parentSets = new LinkedHashMap<>(); + + for (Map.Entry namespaceEntry : regionData.entrySet()) { + String namespaceKey = namespaceEntry.getKey(); + + String namespace = namespaceKey; + if (namespace.equals(YAML_GLOBAL_NAMESPACE_NAME)) { + namespace = null; + } + + for (Map.Entry entry : namespaceEntry.getValue().getMap().entrySet()) { + String name = entry.getKey(); + YAMLNode node = config.getNode("regions." + namespaceKey + "." + name); + + String type = node.getString("type"); + ProtectedRegion region; + + RegionIdentifier id = new RegionIdentifier(namespace, name); + try { + if (type == null) { + log.warning("Undefined region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(node.getMap()) + "\n"); + continue; + } else if (type.equals("cuboid")) { + Vector3 pt1 = checkNotNull(node.getVector("min")); + Vector3 pt2 = checkNotNull(node.getVector("max")); + BlockVector3 min = pt1.getMinimum(pt2).toBlockPoint(); + BlockVector3 max = pt1.getMaximum(pt2).toBlockPoint(); + region = new ProtectedCuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + Integer minY = checkNotNull(node.getInt("min-y")); + Integer maxY = checkNotNull(node.getInt("max-y")); + List points = node.getBlockVector2List("points", null); + region = new ProtectedPolygonalRegion(id, points, minY, maxY); + } else if (type.equals("global")) { + region = new GlobalProtectedRegion(id); + } else { + log.warning("Unknown region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(node.getMap()) + "\n"); + continue; + } + + Integer priority = checkNotNull(node.getInt("priority")); + region.setPriority(priority); + FlagParser.One.setFlags(flagRegistry, region, node.getNode("flags")); + region.setOwners(DomainParser.One.parseDomain(node.getNode("owners"))); + region.setMembers(DomainParser.One.parseDomain(node.getNode("members"))); + + loaded.put(id.getLegacyQualifiedName(), region); + + String parentQualifiedName = node.getString("parent"); + if (parentQualifiedName != null) { + parentSets.put(region, parentQualifiedName); + } + } catch (NullPointerException e) { + log.log(Level.WARNING, + "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(node.getMap()) + + "\n\nNote: This region will disappear as a result!", e); + } + } + } + + // Relink parents + RegionDatabaseUtils.relinkParents(loaded, parentSets); + + return new HashSet<>(loaded.values()); + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java index 9aebc5ac..be195d92 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java @@ -19,56 +19,32 @@ package com.sk89q.worldguard.protection.managers.storage.file; -import static com.google.common.base.Preconditions.checkNotNull; - import com.sk89q.util.yaml.YAMLFormat; import com.sk89q.util.yaml.YAMLNode; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.math.BlockVector2; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldguard.domains.DefaultDomain; import com.sk89q.worldguard.protection.flags.FlagUtil; import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; import com.sk89q.worldguard.protection.managers.RegionDifference; import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; -import com.sk89q.worldguard.protection.managers.storage.RegionDatabaseUtils; import com.sk89q.worldguard.protection.managers.storage.StorageException; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.DumperOptions.FlowStyle; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; +import com.sk89q.worldguard.protection.regions.*; import org.yaml.snakeyaml.parser.ParserException; -import org.yaml.snakeyaml.representer.Representer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.*; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldguard.protection.managers.storage.file.YamlCommon.YAML_GLOBAL_NAMESPACE_NAME; /** * A store that persists regions in a YAML-encoded file. */ public class YamlRegionFile implements RegionDatabase { - - private static final Logger log = Logger.getLogger(YamlRegionFile.class.getCanonicalName()); - private static final Yaml ERROR_DUMP_YAML; - private static final String FILE_HEADER = "#\r\n" + "# WorldGuard regions file\r\n" + "#\r\n" + @@ -81,17 +57,19 @@ public class YamlRegionFile implements RegionDatabase { "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + "#"; - private final String name; - private final File file; + private static final List YAML_READERS = new ArrayList<>(); static { - DumperOptions options = new DumperOptions(); - options.setIndent(4); - options.setDefaultFlowStyle(FlowStyle.AUTO); - - ERROR_DUMP_YAML = new Yaml(new SafeConstructor(), new Representer(), options); + // IMPORTANT: For best performance, add these in reverse order. + // We always want the latest version to be attempted first, so that once migrated, we are checking + // one version, not n. + YAML_READERS.add(new YamlReaderVersionTwo()); + YAML_READERS.add(new YamlReaderVersionOne()); } + private final String name; + private final File file; + /** * Create a new instance. * @@ -112,80 +90,22 @@ public class YamlRegionFile implements RegionDatabase { @Override public Set loadAll(FlagRegistry flagRegistry) throws StorageException { - Map loaded = new HashMap<>(); - YAMLProcessor config = createYamlProcessor(file); try { config.load(); } catch (FileNotFoundException e) { - return new HashSet<>(loaded.values()); + return new HashSet<>(); } catch (IOException | ParserException e) { throw new StorageException("Failed to load region data from '" + file + "'", e); } - Map regionData = config.getNodes("regions"); - - if (regionData == null) { - return Collections.emptySet(); // No regions are even configured - } - - Map parentSets = new LinkedHashMap<>(); - - for (Map.Entry entry : regionData.entrySet()) { - String id = entry.getKey(); - YAMLNode node = entry.getValue(); - - String type = node.getString("type"); - ProtectedRegion region; - - try { - if (type == null) { - log.warning("Undefined region type for region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); - continue; - } else if (type.equals("cuboid")) { - Vector3 pt1 = checkNotNull(node.getVector("min")); - Vector3 pt2 = checkNotNull(node.getVector("max")); - BlockVector3 min = pt1.getMinimum(pt2).toBlockPoint(); - BlockVector3 max = pt1.getMaximum(pt2).toBlockPoint(); - region = new ProtectedCuboidRegion(id, min, max); - } else if (type.equals("poly2d")) { - Integer minY = checkNotNull(node.getInt("min-y")); - Integer maxY = checkNotNull(node.getInt("max-y")); - List points = node.getBlockVector2List("points", null); - region = new ProtectedPolygonalRegion(id, points, minY, maxY); - } else if (type.equals("global")) { - region = new GlobalProtectedRegion(id); - } else { - log.warning("Unknown region type for region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); - continue; - } - - Integer priority = checkNotNull(node.getInt("priority")); - region.setPriority(priority); - setFlags(flagRegistry, region, node.getNode("flags")); - region.setOwners(parseDomain(node.getNode("owners"))); - region.setMembers(parseDomain(node.getNode("members"))); - - loaded.put(id, region); - - String parentId = node.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } catch (NullPointerException e) { - log.log(Level.WARNING, - "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + - "\n\nNote: This region will disappear as a result!", e); + for (YamlReader reader : YAML_READERS) { + if (reader.canLoad(config)) { + return reader.load(flagRegistry, config); } } - // Relink parents - RegionDatabaseUtils.relinkParents(loaded, parentSets); - - return new HashSet<>(loaded.values()); + throw new IllegalStateException("No YAML reader was registered that understands the saved region information"); } @Override @@ -197,13 +117,25 @@ public class YamlRegionFile implements RegionDatabase { config.clear(); + config.setProperty("version", YAML_READERS.get(0).getVersion()); YAMLNode regionsNode = config.addNode("regions"); - Map map = regionsNode.getMap(); + + // Creates a mapping of existing namespace node names, to their underlying YAMLNode. + Map namespaceMap = new HashMap<>(); for (ProtectedRegion region : regions) { - Map nodeMap = new HashMap<>(); - map.put(region.getId(), nodeMap); - YAMLNode node = new YAMLNode(nodeMap, false); + RegionIdentifier identifier = region.getIdentifier(); + + String internalNamespace = identifier.getNamespace().orElse(YAML_GLOBAL_NAMESPACE_NAME); + YAMLNode namespaceNode = namespaceMap.compute(internalNamespace, (ignored, existingNamespaceNode) -> { + if (existingNamespaceNode == null) { + existingNamespaceNode = regionsNode.addNode(internalNamespace); + } + + return existingNamespaceNode; + }); + + YAMLNode node = namespaceNode.addNode(identifier.getName()); if (region instanceof ProtectedCuboidRegion) { ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region; @@ -238,7 +170,7 @@ public class YamlRegionFile implements RegionDatabase { ProtectedRegion parent = region.getParent(); if (parent != null) { - node.setProperty("parent", parent.getId()); + node.setProperty("parent", parent.getIdentifier().getLegacyQualifiedName()); } } @@ -257,46 +189,10 @@ public class YamlRegionFile implements RegionDatabase { throw new DifferenceSaveException("Not supported"); } - private DefaultDomain parseDomain(YAMLNode node) { - if (node == null) { - return new DefaultDomain(); - } - - DefaultDomain domain = new DefaultDomain(); - - for (String name : node.getStringList("players", null)) { - if (!name.isEmpty()) { - domain.addPlayer(name); - } - } - - for (String stringId : node.getStringList("unique-ids", null)) { - try { - domain.addPlayer(UUID.fromString(stringId)); - } catch (IllegalArgumentException e) { - log.log(Level.WARNING, "Failed to parse UUID '" + stringId + "'", e); - } - } - - for (String name : node.getStringList("groups", null)) { - if (!name.isEmpty()) { - domain.addGroup(name); - } - } - - return domain; - } - private Map getFlagData(ProtectedRegion region) { return FlagUtil.marshal(region.getFlags()); } - private void setFlags(FlagRegistry flagRegistry, ProtectedRegion region, YAMLNode flagsData) { - if (flagsData != null) { - region.setFlags(flagRegistry.unmarshal(flagsData.getMap(), true)); - } - } - private Map getDomainData(DefaultDomain domain) { Map domainData = new HashMap<>(); @@ -331,19 +227,4 @@ public class YamlRegionFile implements RegionDatabase { checkNotNull(file); return new YAMLProcessor(file, false, YAMLFormat.COMPACT); } - - /** - * Dump the given object as YAML for debugging purposes. - * - * @param object the object - * @return the YAML string or an error string if dumping fals - */ - private static String toYamlOutput(Object object) { - try { - return ERROR_DUMP_YAML.dump(object).replaceAll("(?m)^", "\t"); - } catch (Throwable t) { - return ""; - } - } - } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java index bdbc54d0..6bdccda0 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java @@ -44,6 +44,7 @@ public class GlobalProtectedRegion extends ProtectedRegion { * * @param id the ID */ + @Deprecated public GlobalProtectedRegion(String id) { this(id, false); } @@ -54,7 +55,29 @@ public class GlobalProtectedRegion extends ProtectedRegion { * @param id the ID * @param transientRegion whether this region should only be kept in memory and not be saved */ + @Deprecated public GlobalProtectedRegion(String id, boolean transientRegion) { + this(new RegionIdentifier(id), transientRegion); + } + + /** + * Create a new instance.
+ * Equivalent to {@link #GlobalProtectedRegion(String, boolean) GlobalProtectedRegion(id, false)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the region identifier + */ + public GlobalProtectedRegion(RegionIdentifier id) { + this(id, false); + } + + /** + * Create a new instance. + * + * @param id the region identifier + * @param transientRegion whether this region should only be kept in memory and not be saved + */ + public GlobalProtectedRegion(RegionIdentifier id, boolean transientRegion) { super(id, transientRegion); min = BlockVector3.ZERO; max = BlockVector3.ZERO; diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java index 364b70d2..1eb734a3 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java @@ -47,6 +47,7 @@ public class ProtectedCuboidRegion extends ProtectedRegion { * @param pt1 the first point of this region * @param pt2 the second point of this region */ + @Deprecated public ProtectedCuboidRegion(String id, BlockVector3 pt1, BlockVector3 pt2) { this(id, false, pt1, pt2); } @@ -59,7 +60,34 @@ public class ProtectedCuboidRegion extends ProtectedRegion { * @param pt1 the first point of this region * @param pt2 the second point of this region */ + @Deprecated public ProtectedCuboidRegion(String id, boolean transientRegion, BlockVector3 pt1, BlockVector3 pt2) { + this(new RegionIdentifier(id), transientRegion, pt1, pt2); + } + + /** + * Construct a new instance of this cuboid region.
+ * Equivalent to {@link #ProtectedCuboidRegion(RegionIdentifier, boolean, BlockVector3, BlockVector3) + * ProtectedCuboidRegion(id, false, pt1, pt2)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the region identifier + * @param pt1 the first point of this region + * @param pt2 the second point of this region + */ + public ProtectedCuboidRegion(RegionIdentifier id, BlockVector3 pt1, BlockVector3 pt2) { + this(id, false, pt1, pt2); + } + + /** + * Construct a new instance of this cuboid region. + * + * @param id the region identifier + * @param transientRegion whether this region should only be kept in memory and not be saved + * @param pt1 the first point of this region + * @param pt2 the second point of this region + */ + public ProtectedCuboidRegion(RegionIdentifier id, boolean transientRegion, BlockVector3 pt1, BlockVector3 pt2) { super(id, transientRegion); setMinMaxPoints(pt1, pt2); } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java index 7e8defa0..d0b9da11 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java @@ -47,6 +47,7 @@ public class ProtectedPolygonalRegion extends ProtectedRegion { * @param minY the minimum y coordinate * @param maxY the maximum y coordinate */ + @Deprecated public ProtectedPolygonalRegion(String id, List points, int minY, int maxY) { this(id, false, points, minY, maxY); } @@ -60,7 +61,37 @@ public class ProtectedPolygonalRegion extends ProtectedRegion { * @param minY the minimum y coordinate * @param maxY the maximum y coordinate */ + @Deprecated public ProtectedPolygonalRegion(String id, boolean transientRegion, List points, int minY, int maxY) { + this(new RegionIdentifier(id), transientRegion, points, minY, maxY); + } + + /** + * Construct a new instance of this polygonal region.
+ * Equivalent to {@link #ProtectedPolygonalRegion(RegionIdentifier, boolean, List, int, int) + * ProtectedPolygonalRegion(id, false, points, minY, maxY)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the region identifier + * @param points a {@link List} of points that this region should contain + * @param minY the minimum y coordinate + * @param maxY the maximum y coordinate + */ + public ProtectedPolygonalRegion(RegionIdentifier id, List points, int minY, int maxY) { + this(id, false, points, minY, maxY); + } + + /** + * Construct a new instance of this polygonal region. + * + * @param id the region identifier + * @param transientRegion whether this region should only be kept in memory and not be saved + * @param points a {@link List} of points that this region should contain + * @param minY the minimum y coordinate + * @param maxY the maximum y coordinate + */ + public ProtectedPolygonalRegion(RegionIdentifier id, boolean transientRegion, List points, + int minY, int maxY) { super(id, transientRegion); ImmutableList immutablePoints = ImmutableList.copyOf(points); setMinMaxPoints(immutablePoints, minY, maxY); diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java index 18de660d..e711cc1a 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java @@ -28,7 +28,6 @@ import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.domains.DefaultDomain; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.util.ChangeTracked; -import com.sk89q.worldguard.util.Normal; import java.awt.geom.Area; import java.awt.geom.Line2D; @@ -37,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -50,12 +48,11 @@ import javax.annotation.Nullable; public abstract class ProtectedRegion implements ChangeTracked, Comparable { public static final String GLOBAL_REGION = "__global__"; - private static final Pattern VALID_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); protected BlockVector3 min; protected BlockVector3 max; - private final String id; + private final RegionIdentifier id; private final boolean transientRegion; private int priority = 0; private ProtectedRegion parent; @@ -67,18 +64,13 @@ public abstract class ProtectedRegion implements ChangeTracked, Comparable + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.util.Normal; +import com.sk89q.worldguard.util.profile.Profile; + +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class RegionIdentifier { + private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); + + private String namespace; + private String name; + + /** + * Construct a new RegionIdentifier for describing region name information, using the global namespace.
+ * Equivalent to {@link #RegionIdentifier(String, String) RegionIdentifier(null, name)}
+ * namespace will be set to null. + * + * @param name the name for this region + * @throws IllegalArgumentException thrown if the name is invalid (see {@link #isValidName(String)} + */ + public RegionIdentifier(String name) { + this(null, name); + } + + /** + * Construct a new RegionIdentifier for describing region name information. + * + * @param namespace the namespace name for this region, or null if there isn't one + * @param name the name for this region + * @throws IllegalArgumentException thrown if the namespace is invalid (see {@link #isValidNamespace(String)} + * @throws IllegalArgumentException thrown if the name is invalid (see {@link #isValidName(String)} + */ + public RegionIdentifier(@Nullable String namespace, String name) { + if (!isValidNamespace(name)) { + throw new IllegalArgumentException("Invalid region namespace: " + namespace); + } + + if (!isValidName(name)) { + throw new IllegalArgumentException("Invalid region name: " + name); + } + + this.namespace = namespace == null ? null : Normal.normalize(namespace); + this.name = Normal.normalize(name); + } + + /** + * Returns the qualified name. This is incompatible with the legacy naming scheme, and + * should be used when interacting with the command system. This will ensure that the correct + * region is referenced regardless of the user's default namespace. + * + * @return the qualified name + */ + public String getQualifiedName() { + return getNamespace().orElse("") + ":" + name; + } + + /** + * Returns the legacy qualified name. This is compatible with the legacy naming scheme, and + * should be used when interacting with the region manager via names. + * + * If you're using this outside of WorldGuard internals, you're doing something wrong. + * + * @return the legacy qualified name + */ + @Deprecated + public String getLegacyQualifiedName() { + return getNamespace().map((ns) -> ns + ':' + getName()).orElse(getName()); + } + + /** + * Compresses the name into a more user friendly macro based name. + * + * @param actor a player context + * @return the user friendly display name + */ + public String getDisplayName(Actor actor) { + if (actor instanceof LocalPlayer) { + LocalPlayer player = (LocalPlayer) actor; + if (player.isDefaultNamespace(namespace)) { + return name; + } + } + + if (namespace != null) { + try { + UUID playerID = UUID.fromString(namespace); + Profile profile = WorldGuard.getInstance().getProfileCache().getIfPresent(playerID); + if (profile != null) { + return "#" + profile.getName() + ":" + name; + } + } catch (IllegalArgumentException ignored) { + } + } + + // Fall back to the fully qualified name. + return getQualifiedName(); + } + + public Optional getNamespace() { + return Optional.ofNullable(namespace); + } + + public String getName() { + return name; + } + + public static boolean isValidNamespace(String namespace) { + if (namespace == null) { + return true; + } + + return VALID_NAME_PATTERN.matcher(namespace).matches(); + } + + public static boolean isValidName(String name) { + checkNotNull(name); + return VALID_NAME_PATTERN.matcher(name).matches(); + } +} diff --git a/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java b/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java index 87864ae7..a411fa83 100644 --- a/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java +++ b/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java @@ -64,6 +64,11 @@ public class TestPlayer extends AbstractPlayerActor implements LocalPlayer { return uuid; } + @Override + public String getDefaultNamespace() { + return null; + } + @Override public boolean hasGroup(String group) { return groups.contains(group.toLowerCase());