diff --git a/build.gradle.kts b/build.gradle.kts index 082b3e3a..29482069 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,17 +10,10 @@ plugins { repositories { mavenCentral() - // Anvilgui + Alps Lib Geo (for geo it can be removed once https://github.com/AlpsBTE/Alps-Lib/pull/17 is merged & version is set to 1.0.0) - maven("https://mvn.alps-bte.com/repository/alps-bte-snapshots/") { - content { - includeGroup("com.alpsbte.alpslib") - includeGroup("net.wesjd") - } - } maven("https://maven.buildtheearth.net/releases") // BuildTheEarth Projection maven("https://mvn.alps-bte.com/repository/alps-bte/") // AlpsLib maven("https://maven.enginehub.org/repo/") // WorldEdit - //maven("https://mvn.wesjd.net/") // Anvilgui - includes 26.1 module which is build with Java 25, what breaks the Plugin Remapper on Paper 1.21.4 see https://github.com/WesJD/AnvilGUI/issues/408 + maven("https://mvn.wesjd.net/") // https://github.com/WesJD/AnvilGUI/issues/408 maven("https://repo.bluecolored.de/releases") // BlueMap maven("https://repo.essentialsx.net/releases/") maven("https://repo.lushplugins.org/releases") // PluginUpdater @@ -43,6 +36,7 @@ dependencies { exclude(group = "com.github.cryptomorin", module = "XSeries") } implementation(libs.alpslib.geo) + implementation(libs.alpslib.web.api) implementation(libs.alpsbte.canvas) implementation(libs.xseries) implementation(libs.anvilgui) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6309f4f..a12f8000 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,11 +9,13 @@ alpslib-io = "1.2.5" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-libpsterra/ alpslib-libpsterra = "1.1.4" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-utils/ -alpslib-utils = "1.5.2" +alpslib-utils = "1.6.0" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-geo/ -alpslib-geo = "1.0.0-SNAPSHOT" +alpslib-geo = "1.0.0" +# https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-web-api/ +alpslib-web-api = "1.0.0" # https://github.com/WesJD/AnvilGUI https://mvn.wesjd.net/ -anvilgui = "1.10.14-SNAPSHOT" +anvilgui = "1.10.13-SNAPSHOT" # @pin Ref: https://github.com/BlueMap-Minecraft/BlueMapAPI - Newer Versions require Java 25 bluemap-api = "2.7.8" # https://central.sonatype.com/artifact/org.bstats/bstats-bukkit @@ -60,6 +62,7 @@ alpsbte-canvas = { module = "com.alpsbte:canvas", version.ref = "alpsbte-canvas" alpslib-io = { module = "com.alpsbte.alpslib:alpslib-io", version.ref = "alpslib-io" } alpslib-utils = { module = "com.alpsbte.alpslib:alpslib-utils", version.ref = "alpslib-utils" } alpslib-geo = { module = "com.alpsbte.alpslib:alpslib-geo", version.ref = "alpslib-geo" } +alpslib-web-api = { module = "com.alpsbte.alpslib:web-api-client", version.ref = "alpslib-web-api" } anvilgui = { module = "net.wesjd:anvilgui", version.ref = "anvilgui" } bluemap-api = { module = "de.bluecolored:bluemap-api", version.ref = "bluemap-api" } bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } diff --git a/src/main/java/net/buildtheearth/buildteamtools/BuildTeamTools.java b/src/main/java/net/buildtheearth/buildteamtools/BuildTeamTools.java index 153f7435..848131e6 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/BuildTeamTools.java +++ b/src/main/java/net/buildtheearth/buildteamtools/BuildTeamTools.java @@ -6,6 +6,7 @@ import net.buildtheearth.buildteamtools.modules.common.CommonModule; import net.buildtheearth.buildteamtools.modules.common.commands.BttCommandManager; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.map.MapModule; import net.buildtheearth.buildteamtools.modules.miscellaneous.MiscModule; import net.buildtheearth.buildteamtools.modules.navigation.NavigationModule; import net.buildtheearth.buildteamtools.modules.network.NetworkModule; @@ -13,6 +14,7 @@ import net.buildtheearth.buildteamtools.modules.stats.StatsModule; import net.buildtheearth.buildteamtools.utils.io.ConfigurationLoader; import net.buildtheearth.buildteamtools.utils.io.GeneratorConfig; +import net.buildtheearth.buildteamtools.utils.io.MapConfig; import net.buildtheearth.buildteamtools.utils.io.MainConfig; import net.buildtheearth.buildteamtools.utils.io.NavigationConfig; import net.buildtheearth.buildteamtools.utils.io.PlotSystemConfig; @@ -38,6 +40,7 @@ public class BuildTeamTools extends JavaPlugin { private ConfigurationLoader mainConfigLoader; private ConfigurationLoader navigationConfigLoader; private ConfigurationLoader generatorConfigLoader; + private ConfigurationLoader mapConfigLoader; private ConfigurationLoader plotSystemConfigLoader; @Getter private BttCommandManager commandManager; @@ -50,11 +53,14 @@ public void onEnable() { getDataFolder().toPath().resolve("modules/navigation"), getSLF4JLogger()); generatorConfigLoader = new ConfigurationLoader<>(GeneratorConfig.class, getDataFolder().toPath().resolve("modules/generator"), getSLF4JLogger()); + mapConfigLoader = new ConfigurationLoader<>(MapConfig.class, + getDataFolder().toPath().resolve("modules/map"), getSLF4JLogger()); plotSystemConfigLoader = new ConfigurationLoader<>(PlotSystemConfig.class, getDataFolder().toPath().resolve("modules/plotsystem"), getSLF4JLogger()); MainConfig mainConfig = mainConfigLoader.load(); NavigationConfig navigationConfig = navigationConfigLoader.load(); GeneratorConfig generatorConfig = generatorConfigLoader.load(); + MapConfig mapConfig = mapConfigLoader.load(); plotSystemConfigLoader.load(); commandManager = new BttCommandManager(this); @@ -63,6 +69,7 @@ public void onEnable() { new CommonModule(mainConfig), new NetworkModule(mainConfig), new GeneratorModule(generatorConfig), + new MapModule(mapConfig), new NavigationModule(navigationConfig), MiscModule.getInstance(), StatsModule.getInstance(), @@ -83,6 +90,7 @@ public void reloadConfig() { mainConfigLoader.reload(); navigationConfigLoader.reload(); generatorConfigLoader.reload(); + mapConfigLoader.reload(); plotSystemConfigLoader.reload(); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManager.java b/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManager.java index ac91d2a3..72c11283 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManager.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManager.java @@ -38,7 +38,7 @@ public final class BttCommandManager { private static final Set COMMAND_NAMES = Set.of( "btt", "generate", "kml", "geopoints", "geopath", "georing", "geosurface", - "navigator", "buildteam", "btwarps", "address", "warp", "blockpalette", + "navigator", "buildteam", "btwarps", "address", "warp", "map", "blockpalette", "help", "communicators", "cache", "debug", "checkforupdates", "reload-config", "update" ); private static final ParserDescriptor ARGUMENT_PARSER = @@ -425,6 +425,7 @@ private List configuredAliases(String name) { case "btwarps" -> labels.btwarps(); case "address" -> labels.address(); case "warp" -> labels.warp(); + case "map" -> labels.map(); case "blockpalette" -> labels.blockpalette(); default -> List.of(); }; @@ -476,6 +477,7 @@ private static RichDescription descriptionFor(String name) { case "btwarps" -> "Open the warp menu for a build team."; case "address" -> "Look up the address at your current location."; case "warp" -> "Warp to a saved location."; + case "map" -> "Create map claims from polygonal WorldEdit selections."; case "blockpalette" -> "Open the block palette menu."; default -> "BuildTeamTools command."; }; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/map/MapModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/map/MapModule.java new file mode 100644 index 00000000..511e6b28 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/map/MapModule.java @@ -0,0 +1,27 @@ +package net.buildtheearth.buildteamtools.modules.map; + +import lombok.Getter; +import net.buildtheearth.buildteamtools.modules.Module; +import net.buildtheearth.buildteamtools.modules.map.commands.MapCreateCommand; +import net.buildtheearth.buildteamtools.utils.WikiLinks; +import net.buildtheearth.buildteamtools.utils.io.MapConfig; + +public class MapModule extends Module { + + @Getter + private final MapConfig config; + + public MapModule(MapConfig config) { + super("Map", WikiLinks.ENTRY); + this.config = config; + } + + @Override + public void registerCommands() { + new MapCreateCommand(this).register(this); + } + + @Override + public void registerListeners() { + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/map/commands/MapCreateCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/map/commands/MapCreateCommand.java new file mode 100644 index 00000000..388cc364 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/map/commands/MapCreateCommand.java @@ -0,0 +1,241 @@ +package net.buildtheearth.buildteamtools.modules.map.commands; + +import com.alpsbte.alpslib.utils.ChatHelper; +import com.alpsbte.alpslib.web.api.ClaimApi; +import com.alpsbte.alpslib.web.invoker.ApiClient; +import com.alpsbte.alpslib.web.invoker.ApiException; +import com.alpsbte.alpslib.web.model.Claim; +import com.alpsbte.alpslib.web.model.PublicBuildteamsTeamClaimsPostRequest; +import com.alpsbte.alpslib.web.model.PublicBuildteamsTeamClaimsPostRequestAreaInner; +import com.alpsbte.alpslib.web.model.PublicBuildteamsTeamClaimsPostRequestOwner; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import net.buildtheearth.OutOfProjectionBoundsException; +import net.buildtheearth.Projection; +import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.Module; +import net.buildtheearth.buildteamtools.modules.common.commands.BttCommandManager; +import net.buildtheearth.buildteamtools.modules.map.MapModule; +import net.buildtheearth.buildteamtools.modules.network.model.Permissions; +import net.buildtheearth.buildteamtools.utils.io.MapConfig; +import net.buildtheearth.model.GeographicalCoordinate; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.minecraft.extras.RichDescription; +import org.incendo.cloud.parser.standard.StringParser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapCreateCommand { + + private static final String COMMAND_NAME = "map"; + private static final int MIN_POLYGON_VERTICES = 3; + /** Claim creation may wait on Overpass (retries); OkHttp's default 10s read timeout is too short. */ + private static final int API_READ_TIMEOUT_MS = 120_000; + private static final String CLAIM_MAP_URL = "https://buildtheearth.net/map?claim="; + + private final MapModule module; + + public MapCreateCommand(MapModule module) { + this.module = module; + } + + private MapConfig config() { + // Config is assigned after Module.super() finishes; registerCommands() runs during super(). + return module.getConfig(); + } + + public void register(Module owner) { + var commandManager = BuildTeamTools.getInstance().getCommandManager(); + commandManager.register(owner, COMMAND_NAME, Permissions.MAP_CREATE, + (sender, args) -> sender.sendMessage(ChatHelper.getErrorComponent("Usage: /map create [name]")), + (sender, input) -> BttCommandManager.matchingSuggestions(List.of("create"), input)); + commandManager.registerSubcommand( + owner, + COMMAND_NAME, + "create", + "Create a map claim from your polygonal WorldEdit selection.", + builder -> builder + .permission(Permissions.MAP_CREATE) + .optional("name", StringParser.greedyStringParser(), + RichDescription.of(Component.text("Optional claim name"))), + context -> { + CommandSender sender = context.sender().getSender(); + if (!(sender instanceof Player player)) { + sender.sendMessage(ChatHelper.getErrorComponent("This command can only be used by a player!")); + return; + } + create(player, context.optional("name").orElse("").trim()); + } + ); + } + + private void create(Player player, String claimName) { + if (!hasApiConfig(player) || !hasWorldEdit(player)) return; + + Polygonal2DRegion selection = requirePolygonalSelection(player); + if (selection == null) return; + + List area = buildArea(player, selection); + if (area == null) return; + + PublicBuildteamsTeamClaimsPostRequest request = new PublicBuildteamsTeamClaimsPostRequest() + .owner(new PublicBuildteamsTeamClaimsPostRequestOwner().minecraft(player.getName())) + .area(area) + .active(true) + .finished(false); + if (!claimName.isEmpty()) { + request.name(claimName); + } + + player.sendMessage(ChatHelper.getStandardComponent(false, "Creating map claim from your selection...")); + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), + () -> submitClaim(player, request)); + } + + private boolean hasApiConfig(Player player) { + MapConfig config = config(); + if (config.webApiToken() == null || config.webApiToken().isBlank()) { + player.sendMessage(ChatHelper.getErrorComponent( + "Map claim creation is unavailable. Configure webApiToken in modules/map/config.yml.")); + return false; + } + if (config.buildTeamId() == null || config.buildTeamId().isBlank()) { + player.sendMessage(ChatHelper.getErrorComponent( + "Map claim creation is unavailable. Configure buildTeamId in modules/map/config.yml.")); + return false; + } + if (config.webApiBasePath() == null || config.webApiBasePath().isBlank()) { + player.sendMessage(ChatHelper.getErrorComponent( + "Map claim creation is unavailable. Configure webApiBasePath in modules/map/config.yml.")); + return false; + } + return true; + } + + private boolean hasWorldEdit(Player player) { + if (Bukkit.getPluginManager().isPluginEnabled("WorldEdit") + || Bukkit.getPluginManager().isPluginEnabled("FastAsyncWorldEdit")) { + return true; + } + player.sendMessage(ChatHelper.getErrorComponent("WorldEdit or FastAsyncWorldEdit must be enabled.")); + return false; + } + + private Polygonal2DRegion requirePolygonalSelection(Player player) { + World earthWorld = BuildTeamTools.getInstance().getEarthWorld(); + if (earthWorld == null) { + player.sendMessage(ChatHelper.getErrorComponent("The configured Earth world is not loaded.")); + return null; + } + if (!player.getWorld().equals(earthWorld)) { + player.sendMessage(ChatHelper.getErrorComponent("Select an area in the configured Earth world.")); + return null; + } + + Region selection; + try { + Actor actor = BukkitAdapter.adapt(player); + LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); + selection = session.getSelection(BukkitAdapter.adapt(earthWorld)); + } catch (IncompleteRegionException exception) { + player.sendMessage(ChatHelper.getErrorComponent( + "Make a polygonal WorldEdit selection (//sel poly) before creating a map claim.")); + return null; + } + if (!(selection instanceof Polygonal2DRegion polygonalSelection)) { + player.sendMessage(ChatHelper.getErrorComponent( + "Map claims require a polygonal WorldEdit selection. Use //sel poly.")); + return null; + } + if (polygonalSelection.getPoints().size() < MIN_POLYGON_VERTICES) { + player.sendMessage(ChatHelper.getErrorComponent( + "A map claim needs at least %s distinct polygon points.", MIN_POLYGON_VERTICES)); + return null; + } + return polygonalSelection; + } + + private List buildArea( + Player player, Polygonal2DRegion selection) { + try { + return createArea(selection); + } catch (OutOfProjectionBoundsException exception) { + player.sendMessage(ChatHelper.getErrorComponent( + "The selection is outside the supported map projection: %s", exception.getMessage())); + return null; + } + } + + private void submitClaim(Player player, PublicBuildteamsTeamClaimsPostRequest request) { + try { + MapConfig config = config(); + ApiClient apiClient = new ApiClient(); + apiClient.setBearerToken(config.webApiToken()); + apiClient.setBasePath(config.webApiBasePath()); + apiClient.setReadTimeout(API_READ_TIMEOUT_MS); + // null, not false: Express treats ?slug=false as truthy and looks up the UUID as a slug. + Claim claim = new ClaimApi(apiClient) + .publicBuildteamsTeamClaimsPost(config.buildTeamId(), request, null, null); + if (claim == null || claim.getId() == null) { + throw new IllegalStateException("The map API did not return the created claim ID."); + } + sendSuccess(player, claim); + } catch (ApiException | RuntimeException exception) { + BuildTeamTools.getInstance().getComponentLogger().error( + "Failed to create map claim for player {}.", player.getUniqueId(), exception); + sendFailure(player, exception); + } + } + + private void sendSuccess(Player player, Claim claim) { + String claimName = claim.getName() == null || claim.getName().isBlank() + ? "Unnamed Claim" + : claim.getName(); + String claimUrl = CLAIM_MAP_URL + claim.getId(); + Component idLink = Component.text(Objects.requireNonNull(claim.getId()).toString(), NamedTextColor.AQUA) + .clickEvent(ClickEvent.openUrl(claimUrl)) + .hoverEvent(HoverEvent.showText(Component.text("Open claim on the map", NamedTextColor.GRAY))); + player.sendMessage(ChatHelper.getSuccessComponent("Created map claim %s (ID: ", claimName) + .append(idLink) + .append(Component.text(")."))); + } + + private void sendFailure(Player player, Exception exception) { + if (exception instanceof ApiException apiException && apiException.getCode() == 404) { + player.sendMessage(ChatHelper.getErrorComponent( + "Your Minecraft account is not registered on buildtheearth.net. Link it on the website first.")); + return; + } + String reason = exception.getMessage() == null + ? exception.getClass().getSimpleName() + : exception.getMessage(); + player.sendMessage(ChatHelper.getErrorComponent("Failed to create map claim: %s", reason)); + } + + /** API default coordType expects {@code "lng, lat"} strings; the API closes the ring. */ + private List createArea(Polygonal2DRegion selection) + throws OutOfProjectionBoundsException { + List area = new ArrayList<>(); + for (BlockVector2 point : selection.getPoints()) { + GeographicalCoordinate coordinate = Projection.toGeo(point.x(), point.z()); + area.add(new PublicBuildteamsTeamClaimsPostRequestAreaInner( + coordinate.longitude() + ", " + coordinate.latitude())); + } + return area; + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java index 1a2fe96e..93c1cc8e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/OpenStreetMapAPI.java @@ -56,7 +56,7 @@ private static void completeRgcLookup(@NotNull GeographicalCoordinate coordinate var location = NavigationModule.getInstance().getRgcHandler() .locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); ChatHelper.logDebug("RGC lookup successful: %s", location); - future.complete(new RegionLookupResult(location.get(AdminLevel.COUNTRY), null)); + future.complete(new RegionLookupResult(location.get().get(AdminLevel.COUNTRY), null)); } catch (Exception ex) { future.completeExceptionally(ex); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Permissions.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Permissions.java index 5ec17f99..282370e3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Permissions.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Permissions.java @@ -43,6 +43,8 @@ public class Permissions { public static final String WARP_GROUP_EDIT = "btt.warp.group.edit"; public static final String WARP_GROUP_DELETE = "btt.warp.group.delete"; + public static final String MAP_CREATE = "btt.map.create"; + public static final String NOTIFY_UPDATE = "btt.notify.update"; public static final String AUTO_TPLL = "btt.global.autotpll"; diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/io/MainConfig.java b/src/main/java/net/buildtheearth/buildteamtools/utils/io/MainConfig.java index 88f798fc..d9b0617b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/io/MainConfig.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/io/MainConfig.java @@ -74,6 +74,7 @@ interface Labels { default List btwarps() { return List.of("btwarps", "btwarp", "wbt", "wpt", "buildteamwarps"); } default List address() { return List.of("address"); } default List warp() { return List.of("warp", "warps", "wp"); } + default List map() { return List.of("map"); } default List blockpalette() { return List.of("blockpalette", "bp", "blocks"); } } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/io/MapConfig.java b/src/main/java/net/buildtheearth/buildteamtools/utils/io/MapConfig.java new file mode 100644 index 00000000..acdf759d --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/io/MapConfig.java @@ -0,0 +1,26 @@ +package net.buildtheearth.buildteamtools.utils.io; + +import space.arim.dazzleconf.engine.Comments; + +@Comments("BuildTeamTools Map Module Configuration") +@Comments("[Github Repo] https://github.com/BuildTheEarth/BuildTeamTools") +@Comments("[Contacts - Discord] BTE Development Hub, @Zoriot") +public interface MapConfig { + + @Comments("Build Team ID used by the web API to create map claims.") + @Comments("This is independent of the Build Team ID configured for NWAPI.") + default String buildTeamId() { + return ""; + } + + @Comments("Authentication secret used by the web API client to create map claims.") + @Comments("Keep this private; claim creation is unavailable when this is empty.") + default String webApiToken() { + return ""; + } + + @Comments("Base URL of the BuildTheEarth web API (no trailing slash), e.g. https://api.buildtheearth.net/api/v1") + default String webApiBasePath() { + return "https://api.buildtheearth.net/api/v1"; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 05429c39..422e3a3a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -43,6 +43,7 @@ permissions: btt.warp.group.create: true btt.warp.group.edit: true btt.warp.group.delete: true + btt.map.create: true btt.notify.update: true btt.bp.edit: true btt.generator.rail.create: true @@ -75,4 +76,8 @@ permissions: description: Allows players to create and edit custom block palettes default: op + btt.map.create: + description: Allows players to create map claims from WorldEdit selections + default: op + github-repo: "BuildTheEarth/BuildTeamTools" diff --git a/src/test/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManagerTest.java b/src/test/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManagerTest.java index 9daedffa..e7055f3e 100644 --- a/src/test/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManagerTest.java +++ b/src/test/java/net/buildtheearth/buildteamtools/modules/common/commands/BttCommandManagerTest.java @@ -128,7 +128,7 @@ void commandsAreRegisteredWhenDisabledModuleIsEnabledLater() { @Test void allDefaultFeatureAliasesRegisterWithoutAmbiguity() { for (String name : List.of("generate", "kml", "geopoints", "geopath", "georing", "geosurface", - "warp", "address", "blockpalette")) { + "warp", "address", "map", "blockpalette")) { commands.register(null, name, (sender, args) -> {}); } commands.registerNoArguments(null, "navigator", Permissions.NAVIGATOR_USE, ignored -> {}); @@ -139,6 +139,9 @@ void allDefaultFeatureAliasesRegisterWithoutAmbiguity() { } assertSame(parse("navigator").command(), parse("nav").command()); + assertNotNull(parse("map").command()); + assertNotNull(parse("btt map").command()); + assertNotNull(parse("buildteamtools map").command()); assertSame(parse("btt navigator").command(), parse("buildteamtools navigate").command()); assertSame(parse("buildteam New York").command(), parse("bt New York").command()); assertEquals("New York", parse("buildteamtools bt New York").get("team")); @@ -150,12 +153,18 @@ void allDefaultFeatureAliasesRegisterWithoutAmbiguity() { @Test void subcommandsRetainArgumentsPermissionsAndHandlersForEveryAlias() throws Exception { AtomicInteger invocations = new AtomicInteger(); + AtomicInteger mapInvocations = new AtomicInteger(); Module owner = new EnabledModule(); commands.register(null, "warp", (sender, args) -> {}); + commands.register(null, "map", (sender, args) -> {}); commands.registerSubcommand(owner, "warp", "migrate", "Migrate warps", builder -> builder.permission(Permissions.WARP_MIGRATE) .required("source", EnumParser.enumParser(WarpMigrationSource.class)), ignored -> invocations.incrementAndGet()); + commands.registerSubcommand(owner, "map", "create", "Create a map claim", + builder -> builder.permission(Permissions.MAP_CREATE) + .optional("name", StringParser.greedyStringParser()), + context -> mapInvocations.incrementAndGet()); for (String input : List.of("warp migrate essentials", "wp migrate essentials", "btt warp migrate essentials", "buildteamtools wp migrate essentials")) { @@ -167,6 +176,15 @@ void subcommandsRetainArgumentsPermissionsAndHandlersForEveryAlias() throws Exce assertEquals(4, invocations.get()); assertParseFailure(InvalidSyntaxException.class, "wp migrate"); assertParseFailure(ArgumentParseException.class, "btt warp migrate invalid"); + for (String input : List.of("map create", "btt map create", "buildteamtools map create")) { + var context = parse(input); + assertEquals(Permissions.MAP_CREATE, context.command().commandPermission().permissionString()); + context.command().commandExecutionHandler().execute(context); + } + assertEquals(3, mapInvocations.get()); + assertEquals("Old Town", parse("map create Old Town").get("name")); + assertNotNull(parse("map").command()); + assertNotNull(parse("btt map").command()); manager.deniedPermissions = Set.of(Permissions.WARP_MIGRATE); assertParseFailure(NoPermissionException.class, "wp migrate essentials"); assertParseFailure(NoPermissionException.class, "buildteamtools wp migrate essentials");