From 2061608d8d0479ec45e72109e544e5beea6dae36 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sun, 24 May 2026 16:41:13 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8C=8D=20feat(navigation):=20add=20lo?= =?UTF-8?q?cal=20reverse-geocode=20database=20with=20auto-download=20and?= =?UTF-8?q?=20thread-safety=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add optional offline country/region lookup using alpslib-geo RgcHandler - Automatically download and cache RGC database on first run from configured URL - Fall back to online Photon/Komoot API if local database disabled or unavailable - Mark rgcHandler field as volatile to guarantee cross-thread visibility - Mark Module.enabled as volatile to ensure reliable isEnabled() checks across threads - Schedule RgcHandler creation and all method calls on main thread to prevent concurrent access - Create parent directories before FileOutputStream to handle first-run download - Use try-with-resources for download streams to prevent resource leaks - Schedule RGC lookups on main thread when called from worker threads in OpenStreetMapAPI - Add getCCA2FromCountryName() fallback helper for country code lookup - Add RGC configuration options: enabled, url, path under reverse-geocode.local-database - Handle download failures gracefully with logging and fallback to API Dependencies: - Add com.alpsbte.alpslib:alpslib-geo:1.0.0 Files: - build.gradle.kts: add alpslib-geo dependency - gradle/libs.versions.toml: define alpslib-geo version - settings.gradle.kts: enable mavenLocal() (TEMPORARY - disable before merge) - NavigationModule.java: add RGC initialization with auto-download - Module.java: mark enabled field volatile - OpenStreetMapAPI.java: schedule RGC lookups on main thread - NavUtils.java: add getCCA2FromCountryName() helper - ConfigPaths.java: add RGC_LOCAL_DB_* constants - WarpsComponent.java, WarpEditMenu.java: use fallback country code lookup - config.yml: document RGC local-database settings --- build.gradle.kts | 1 + gradle/libs.versions.toml | 3 ++ .../modules/navigation/NavUtils.java | 16 ++++++ .../modules/navigation/NavigationModule.java | 51 +++++++++++++++++-- .../components/warps/WarpsComponent.java | 2 + .../components/warps/menu/WarpEditMenu.java | 4 ++ .../modules/network/api/OpenStreetMapAPI.java | 39 +++++++++++++- .../buildteamtools/utils/io/ConfigPaths.java | 6 +++ .../buildteamtools/utils/io/ConfigUtil.java | 2 +- .../resources/modules/navigation/config.yml | 13 ++++- 10 files changed, 130 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d7b2034a..42980945 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(libs.alpslib.utils) { exclude(group = "com.github.cryptomorin", module = "XSeries") } + implementation(libs.alpslib.geo) implementation(libs.alpsbte.canvas) implementation(libs.xseries) implementation(libs.anvilgui) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 533a3fd5..f56c59c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,8 @@ alpslib-io = "1.2.5" alpslib-libpsterra = "1.1.4" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-utils/ alpslib-utils = "1.4.6" +# https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-geo/ +alpslib-geo = "1.0.0" # https://github.com/WesJD/AnvilGUI https://mvn.wesjd.net/ anvilgui = "1.10.13-SNAPSHOT" # Ref: https://github.com/BlueMap-Minecraft/BlueMapAPI @@ -49,6 +51,7 @@ shadow = "9.4.2" 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" } 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/modules/navigation/NavUtils.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java index d9f5d54b..85d0c0b5 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -24,6 +24,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Objects; + @UtilityClass public class NavUtils { public static void sendPlayerToConnectedServer(Player player, String server) { @@ -180,4 +182,18 @@ public static Location getLocationFromCoordinatesYawPitch(GeographicalCoordinate public static Location getLocationFromCoordinates(GeographicalCoordinate coordinate) { return getLocationFromCoordinatesYawPitch(coordinate, 0, 0); } + + /** + * Returns the CCA2 code of the country of the given country name. + */ + public static String getCCA2FromCountryName(String countryName, Player clickPlayer) { + var region = Objects.requireNonNull(NetworkModule.getInstance().getBuildTeam()).getRegions().stream().filter(regionF -> regionF.getName().equals(countryName)).findFirst(); + if (region.isPresent()) { + return region.get().getCountryCodeCca2(); + } else { + clickPlayer.sendMessage(ChatHelper.getErrorString("Could not find the country of the location! Please report that")); + return ""; + } + + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java index 1be1cf41..d0fbee14 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java @@ -1,5 +1,7 @@ package net.buildtheearth.buildteamtools.modules.navigation; +import com.alpsbte.alpslib.geo.rgc.RgcHandler; +import com.alpsbte.alpslib.utils.ChatHelper; import lombok.Getter; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.Module; @@ -22,6 +24,15 @@ import net.buildtheearth.buildteamtools.utils.io.ConfigPaths; import net.buildtheearth.buildteamtools.utils.io.ConfigUtil; import org.bukkit.Bukkit; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; /** * Manages all things related to universal tpll @@ -37,7 +48,9 @@ public class NavigationModule extends Module { private TpllComponent tpllComponent; @Getter private BluemapComponent bluemapComponent; - + @Getter + @Nullable + private RgcHandler rgcHandler = null; private static NavigationModule instance = null; @@ -61,9 +74,41 @@ public void enable() { navigatorComponent = new NavigatorComponent(); tpllComponent = new TpllComponent(); + var navConfig = BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION); + + if (navConfig.getBoolean(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false)) { + File rgcFile = BuildTeamTools.getInstance().getDataPath().resolve("modules/navigation").resolve(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_PATH, "bs.file")).toFile(); + ChatHelper.logDebug("Reverse Geocode local database support is enabled. Checking for local database file at: %s", rgcFile.getAbsolutePath()); + if (rgcFile.exists()) { + rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); + } else { + BuildTeamTools.getInstance().getComponentLogger().info("Reverse Geocode local database is enabled but the file does not exist at the specified path, installing it from the configured url."); + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { + try { + if (!rgcFile.getParentFile().mkdirs()) { + BuildTeamTools.getInstance().getComponentLogger().warn("Failed to create parent directories for Reverse Geocode local database file. Make sure the plugin has the necessary permissions to create directories and files in the plugin data folder."); + } + URL url = URI.create(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_UPDATE_URL, "")).toURL(); + try (ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream())) { + try (FileOutputStream fileOutputStream = new FileOutputStream(rgcFile)) { + FileChannel fileChannel = fileOutputStream.getChannel(); + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { + rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); + BuildTeamTools.getInstance().getComponentLogger().info("Successfully downloaded Reverse Geocode local database and enabled local database support for Reverse Geocoding."); + }); + } catch (Exception e) { + BuildTeamTools.getInstance().getComponentLogger().error("Failed to download Reverse Geocode local database from the configured url, disabling local database support for Reverse Geocoding.", e); + navConfig.set(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false); + } + }); + } + } + // Check if BlueMap plugin is enabled and config allows BlueMap integration - boolean bluemapConfigEnabled = BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION) - .getBoolean(ConfigPaths.Navigation.BLUEMAP_ENABLED, true); + boolean bluemapConfigEnabled = navConfig.getBoolean(ConfigPaths.Navigation.BLUEMAP_ENABLED, true); if (Bukkit.getPluginManager().isPluginEnabled("BlueMap") && bluemapConfigEnabled) { bluemapComponent = new BluemapComponent(); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java index d76a496d..fcd5464b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java @@ -177,6 +177,8 @@ public static void createWarp(@NonNull Player creator, WarpGroup group) { String regionName = result[0]; String countryCodeCCA2 = result[1].toUpperCase(); + if (countryCodeCCA2.isEmpty()) countryCodeCCA2 = NavUtils.getCCA2FromCountryName(regionName, creator); + //Check if the team owns this region/country boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java index 75c5daf1..707d1eeb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/WarpEditMenu.java @@ -6,6 +6,7 @@ import net.buildtheearth.OutOfProjectionBoundsException; import net.buildtheearth.Projection; import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.navigation.NavUtils; import net.buildtheearth.buildteamtools.modules.navigation.components.warps.model.Warp; import net.buildtheearth.buildteamtools.modules.network.NetworkModule; import net.buildtheearth.buildteamtools.modules.network.api.OpenStreetMapAPI; @@ -152,6 +153,9 @@ protected void setItemClickEventsAsync() { String regionName = result[0]; String countryCodeCCA2 = result[1].toUpperCase(); + if (countryCodeCCA2.isEmpty()) + countryCodeCCA2 = NavUtils.getCCA2FromCountryName(regionName, clickPlayer); + //Check if the team owns this region/country boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); 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 2754e40f..f883b332 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 @@ -1,12 +1,17 @@ package net.buildtheearth.buildteamtools.modules.network.api; +import com.alpsbte.alpslib.geo.AdminLevel; import com.alpsbte.alpslib.utils.ChatHelper; import net.buildtheearth.model.GeographicalCoordinate; +import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.navigation.NavigationModule; +import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.io.IOException; +import java.util.Objects; import java.util.concurrent.CompletableFuture; public class OpenStreetMapAPI extends API { @@ -17,8 +22,38 @@ public class OpenStreetMapAPI extends API { */ public static @NotNull CompletableFuture getCountryFromLocationAsync(@NotNull GeographicalCoordinate coordinates) { CompletableFuture future = new CompletableFuture<>(); - String url = "https://photon.komoot.io/reverse?lat=" + coordinates.latitude() + "&lon=" + coordinates.longitude() + - "&lang=en"; + + if (NavigationModule.getInstance().isEnabled() && NavigationModule.getInstance().getRgcHandler() != null) { + ChatHelper.logDebug("Using custom file API to get country from location: %s, %s", coordinates.latitude(), coordinates.longitude()); + + // If we are not on main thread, schedule lookup on main thread and complete the outer future there + if (!Bukkit.isPrimaryThread()) { + ChatHelper.logDebug("Not on main thread: scheduling RGC lookup on main thread..."); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { + try { + var rgcGeoLocation = NavigationModule.getInstance().getRgcHandler() + .locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); + ChatHelper.logDebug("RGC lookup successful: %s", rgcGeoLocation); + future.complete(new String[]{rgcGeoLocation.get(AdminLevel.COUNTRY), ""}); + } catch (Exception ex) { + future.completeExceptionally(ex); + } + }); + return future; + } + + // We're on the main thread already — run synchronously + try { + var location = Objects.requireNonNull(NavigationModule.getInstance().getRgcHandler()).locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); + ChatHelper.logDebug("RGC lookup successful: %s", location); + future.complete(new String[]{location.get(AdminLevel.COUNTRY), ""}); + } catch (Exception ex) { + future.completeExceptionally(ex); + } + return future; + } + + String url = "https://photon.komoot.io/reverse?lat=" + coordinates.latitude() + "&lon=" + coordinates.longitude() + "&lang=en"; ChatHelper.logDebug("Requesting country from location: %s", url); diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java index 671d0222..51dadc8d 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigPaths.java @@ -45,6 +45,12 @@ public static class Navigation { // BlueMap Integration private static final String BLUEMAP = "bluemap."; public static final String BLUEMAP_ENABLED = BLUEMAP + "enabled"; + + // Reverse Geocode + private static final String RGC_LOCAL_DB = "reverse-geocode.local-database."; + public static final String RGC_LOCAL_DB_ENABLED = RGC_LOCAL_DB + "enabled"; + public static final String RGC_LOCAL_DB_UPDATE_URL = RGC_LOCAL_DB + "url"; + public static final String RGC_LOCAL_DB_PATH = RGC_LOCAL_DB + "path"; } public static class PlotSystem { diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigUtil.java b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigUtil.java index e7b20228..472e9968 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigUtil.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/io/ConfigUtil.java @@ -42,7 +42,7 @@ public static void init() throws ConfigNotImplementedException { configUtilInstance = new ConfigurationUtil(new ConfigurationUtil.ConfigFile[]{ new ConfigurationUtil.ConfigFile(Paths.get("config.yml"), 1.4, false), new ConfigurationUtil.ConfigFile(Paths.get("modules", "plotsystem", "config.yml"), 1.6, false), - new ConfigurationUtil.ConfigFile(Paths.get("modules", "navigation", "config.yml"), 1.6, false), + new ConfigurationUtil.ConfigFile(Paths.get("modules", "navigation", "config.yml"), 1.7, false), }); } diff --git a/src/main/resources/modules/navigation/config.yml b/src/main/resources/modules/navigation/config.yml index 4b8d116c..f32043da 100644 --- a/src/main/resources/modules/navigation/config.yml +++ b/src/main/resources/modules/navigation/config.yml @@ -52,5 +52,16 @@ bluemap: # Enables or disables the BlueMap integration for displaying warps [true|false] enabled: true +reverse-geocode: + local-database: + # Whether to use the local database for reverse geocoding or not. If false, the online Photon/Komoot API will be used. + # Currently, we use only https://photon.komoot.io/ for online reverse geocoding. + enabled: true + # The URL to the local database. More infos: https://github.com/kno10/reversegeocode/blob/master/data/README.md + # The file is automatically downloaded once if it does not exist. + url: "https://data.ub.uni-muenchen.de/61/8/osm-20151130-0.001-2.bin" + # The relative path to the local database. + path: "reversegeocode/osm-20151130-0.001-2.bin" + # NOTE: Do not change -config-version: 1.6 \ No newline at end of file +config-version: 1.7 \ No newline at end of file From c4ab8f0a151d23f376af1ab70d7baaae55df29ae Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 9 Jun 2026 21:02:53 +0200 Subject: [PATCH 2/3] =?UTF-8?q?refactor(navigation):=20=E2=99=BB=EF=B8=8F?= =?UTF-8?q?=20extract=20helpers=20and=20flatten=20nested=20conditionals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review by splitting RGC setup, region lookup, and OpenStreetMap country resolution into focused methods and switch-based flows. --- .../modules/navigation/NavUtils.java | 68 ++++++----- .../modules/navigation/NavigationModule.java | 106 ++++++++++++------ .../modules/network/api/OpenStreetMapAPI.java | 98 ++++++++-------- 3 files changed, 167 insertions(+), 105 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java index 85d0c0b5..d0dcc3bb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -10,6 +10,7 @@ import net.buildtheearth.buildteamtools.modules.navigation.components.warps.model.WarpGroup; import net.buildtheearth.buildteamtools.modules.network.NetworkModule; import net.buildtheearth.buildteamtools.modules.network.model.BuildTeam; +import net.buildtheearth.buildteamtools.modules.network.model.Region; import net.buildtheearth.model.GeographicalCoordinate; import net.buildtheearth.model.MinecraftCoordinate; import net.md_5.bungee.api.chat.ClickEvent; @@ -21,10 +22,10 @@ import org.bukkit.UnsafeValues; import org.bukkit.World; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; - -import java.util.Objects; +import org.jspecify.annotations.NonNull; @UtilityClass public class NavUtils { @@ -97,17 +98,19 @@ public static void sendNoIpMessage(@NotNull Player player, String buildteam) { @NotNull BuildTeam targetBuildTeam) { if (targetBuildTeam.isConnected() && targetBuildTeam.getServerName() != null && NetworkModule.getInstance().getBuildTeam() != null && NetworkModule.getInstance().getBuildTeam().isConnected()) { return NavSwitchType.NETWORK; - } else if (targetBuildTeam.getIP() != null) { - if (isTransferCapable(player, targetBuildTeam)) { - return NavSwitchType.TRANSFER; - } else { - sendNotConnectedMessage(player, targetBuildTeam.getIP(), targetBuildTeam.getName()); - return null; - } - } else { + } + + if (targetBuildTeam.getIP() == null) { sendNoIpMessage(player, targetBuildTeam.getName()); return null; } + + if (isTransferCapable(player, targetBuildTeam)) { + return NavSwitchType.TRANSFER; + } + + sendNotConnectedMessage(player, targetBuildTeam.getIP(), targetBuildTeam.getName()); + return null; } public enum NavSwitchType { @@ -115,14 +118,14 @@ public enum NavSwitchType { } public static void switchToTeam(BuildTeam team, Player clickPlayer) { - var type = NavUtils.determineSwitchPossibilityOrMsgPlayerIfNone(clickPlayer, team); - - if (type != null) { - if (type == NavUtils.NavSwitchType.NETWORK) { - NavUtils.sendPlayerToConnectedServer(clickPlayer, team.getServerName()); - } else if (type == NavUtils.NavSwitchType.TRANSFER) { - NavUtils.transferPlayer(clickPlayer, team.getIP()); - } + NavSwitchType type = determineSwitchPossibilityOrMsgPlayerIfNone(clickPlayer, team); + if (type == null) { + return; + } + + switch (type) { + case NETWORK -> sendPlayerToConnectedServer(clickPlayer, team.getServerName()); + case TRANSFER -> transferPlayer(clickPlayer, team.getIP()); } } @@ -148,7 +151,8 @@ public static void switchToTeam(BuildTeam team, Player clickPlayer) { * @param pitch Player's pitch * @return A bukkit location matching the coordinates, yaw and pitch specified. Height is terrain elevation +2. */ - public static Location getLocationFromCoordinatesYawPitch(GeographicalCoordinate coordinate, float yaw, float pitch) { + @Contract("_, _, _ -> new") + public static @NonNull Location getLocationFromCoordinatesYawPitch(GeographicalCoordinate coordinate, float yaw, float pitch) { try { MinecraftCoordinate mcCoord = Projection.toMinecraft(coordinate); @@ -179,7 +183,8 @@ public static Location getLocationFromCoordinatesYawPitch(GeographicalCoordinate * @param coordinate Latitude and longitude of the location * @return A bukkit location matching the coordinates. Height is terrain elevation +2. */ - public static Location getLocationFromCoordinates(GeographicalCoordinate coordinate) { + @Contract("_ -> new") + public static @NonNull Location getLocationFromCoordinates(GeographicalCoordinate coordinate) { return getLocationFromCoordinatesYawPitch(coordinate, 0, 0); } @@ -187,13 +192,24 @@ public static Location getLocationFromCoordinates(GeographicalCoordinate coordin * Returns the CCA2 code of the country of the given country name. */ public static String getCCA2FromCountryName(String countryName, Player clickPlayer) { - var region = Objects.requireNonNull(NetworkModule.getInstance().getBuildTeam()).getRegions().stream().filter(regionF -> regionF.getName().equals(countryName)).findFirst(); - if (region.isPresent()) { - return region.get().getCountryCodeCca2(); - } else { - clickPlayer.sendMessage(ChatHelper.getErrorString("Could not find the country of the location! Please report that")); - return ""; + Region region = findRegionByName(countryName); + if (region != null) { + return region.getCountryCodeCca2(); + } + + clickPlayer.sendMessage(ChatHelper.getErrorString("Could not find the country of the location! Please report that")); + return ""; + } + + private static @Nullable Region findRegionByName(String countryName) { + BuildTeam buildTeam = NetworkModule.getInstance().getBuildTeam(); + if (buildTeam == null) { + return null; } + return buildTeam.getRegions().stream() + .filter(region -> region.getName().equals(countryName)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java index d0fbee14..308c57b7 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavigationModule.java @@ -24,10 +24,14 @@ import net.buildtheearth.buildteamtools.utils.io.ConfigPaths; import net.buildtheearth.buildteamtools.utils.io.ConfigUtil; import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.channels.Channels; @@ -75,50 +79,82 @@ public void enable() { tpllComponent = new TpllComponent(); var navConfig = BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION); + initializeRgcHandler(navConfig); + initializeBluemapComponent(navConfig); - if (navConfig.getBoolean(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false)) { - File rgcFile = BuildTeamTools.getInstance().getDataPath().resolve("modules/navigation").resolve(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_PATH, "bs.file")).toFile(); - ChatHelper.logDebug("Reverse Geocode local database support is enabled. Checking for local database file at: %s", rgcFile.getAbsolutePath()); - if (rgcFile.exists()) { - rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); - } else { - BuildTeamTools.getInstance().getComponentLogger().info("Reverse Geocode local database is enabled but the file does not exist at the specified path, installing it from the configured url."); - Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { - try { - if (!rgcFile.getParentFile().mkdirs()) { - BuildTeamTools.getInstance().getComponentLogger().warn("Failed to create parent directories for Reverse Geocode local database file. Make sure the plugin has the necessary permissions to create directories and files in the plugin data folder."); - } - URL url = URI.create(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_UPDATE_URL, "")).toURL(); - try (ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream())) { - try (FileOutputStream fileOutputStream = new FileOutputStream(rgcFile)) { - FileChannel fileChannel = fileOutputStream.getChannel(); - fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); - } - } - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { - rgcHandler = new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); - BuildTeamTools.getInstance().getComponentLogger().info("Successfully downloaded Reverse Geocode local database and enabled local database support for Reverse Geocoding."); - }); - } catch (Exception e) { - BuildTeamTools.getInstance().getComponentLogger().error("Failed to download Reverse Geocode local database from the configured url, disabling local database support for Reverse Geocoding.", e); - navConfig.set(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false); - } + if (navConfig.getBoolean(ConfigPaths.Navigation.NAVIGATOR_ITEM_ENABLED, false)) { + registerListeners(new NavigatorJoinListener(), new NavigatorOpenListener()); + } + + super.enable(); + } + + private void initializeRgcHandler(@NonNull FileConfiguration navConfig) { + if (!navConfig.getBoolean(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false)) { + return; + } + + File rgcFile = resolveRgcDatabaseFile(navConfig); + ChatHelper.logDebug("Reverse Geocode local database support is enabled. Checking for local database file at: %s", rgcFile.getAbsolutePath()); + + if (rgcFile.exists()) { + rgcHandler = createRgcHandler(rgcFile); + return; + } + + downloadRgcDatabaseAsync(rgcFile, navConfig); + } + + private @NonNull File resolveRgcDatabaseFile(@NonNull FileConfiguration navConfig) { + String path = navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_PATH, "bs.file"); + return BuildTeamTools.getInstance().getDataPath() + .resolve("modules/navigation") + .resolve(path) + .toFile(); + } + + @Contract("_ -> new") + private @NonNull RgcHandler createRgcHandler(File rgcFile) { + return new RgcHandler(rgcFile, BuildTeamTools.getInstance().getSLF4JLogger(), false); + } + + private void downloadRgcDatabaseAsync(File rgcFile, FileConfiguration navConfig) { + BuildTeamTools.getInstance().getComponentLogger().info( + "Reverse Geocode local database is enabled but the file does not exist at the specified path, installing it from the configured url."); + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { + try { + downloadRgcDatabase(rgcFile, navConfig); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { + rgcHandler = createRgcHandler(rgcFile); + BuildTeamTools.getInstance().getComponentLogger().info( + "Successfully downloaded Reverse Geocode local database and enabled local database support for Reverse Geocoding."); }); + } catch (Exception e) { + BuildTeamTools.getInstance().getComponentLogger().error( + "Failed to download Reverse Geocode local database from the configured url, disabling local database support for Reverse Geocoding.", e); + navConfig.set(ConfigPaths.Navigation.RGC_LOCAL_DB_ENABLED, false); } + }); + } + + private void downloadRgcDatabase(@NonNull File rgcFile, FileConfiguration navConfig) throws IOException { + if (!rgcFile.getParentFile().mkdirs()) { + BuildTeamTools.getInstance().getComponentLogger().warn( + "Failed to create parent directories for Reverse Geocode local database file. Make sure the plugin has the necessary permissions to create directories and files in the plugin data folder."); + } + URL url = URI.create(navConfig.getString(ConfigPaths.Navigation.RGC_LOCAL_DB_UPDATE_URL, "")).toURL(); + try (ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(rgcFile)) { + FileChannel fileChannel = fileOutputStream.getChannel(); + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); } + } - // Check if BlueMap plugin is enabled and config allows BlueMap integration + private void initializeBluemapComponent(@NonNull FileConfiguration navConfig) { boolean bluemapConfigEnabled = navConfig.getBoolean(ConfigPaths.Navigation.BLUEMAP_ENABLED, true); - if (Bukkit.getPluginManager().isPluginEnabled("BlueMap") && bluemapConfigEnabled) { bluemapComponent = new BluemapComponent(); } - - if (BuildTeamTools.getInstance().getConfig(ConfigUtil.NAVIGATION).getBoolean(ConfigPaths.Navigation.NAVIGATOR_ITEM_ENABLED, false)) { - registerListeners(new NavigatorJoinListener(), new NavigatorOpenListener()); - } - - super.enable(); } @Override 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 f883b332..5b09a8f2 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 @@ -2,57 +2,64 @@ import com.alpsbte.alpslib.geo.AdminLevel; import com.alpsbte.alpslib.utils.ChatHelper; -import net.buildtheearth.model.GeographicalCoordinate; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.navigation.NavigationModule; +import net.buildtheearth.model.GeographicalCoordinate; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import org.jspecify.annotations.NonNull; import java.io.IOException; -import java.util.Objects; import java.util.concurrent.CompletableFuture; public class OpenStreetMapAPI extends API { /** - * @param coordinates The latitude & longitude coordinates to get the country, region & city/town from + * @param coordinate The latitude & longitude coordinates to get the country, region & city/town from * @return The country name and country code belonging to this location */ - public static @NotNull CompletableFuture getCountryFromLocationAsync(@NotNull GeographicalCoordinate coordinates) { - CompletableFuture future = new CompletableFuture<>(); + public static @NotNull CompletableFuture getCountryFromLocationAsync(@NotNull GeographicalCoordinate coordinate) { + if (canUseRgcHandler()) { + return getCountryFromRgcAsync(coordinate); + } + return getCountryFromPhotonAsync(coordinate); + } - if (NavigationModule.getInstance().isEnabled() && NavigationModule.getInstance().getRgcHandler() != null) { - ChatHelper.logDebug("Using custom file API to get country from location: %s, %s", coordinates.latitude(), coordinates.longitude()); - - // If we are not on main thread, schedule lookup on main thread and complete the outer future there - if (!Bukkit.isPrimaryThread()) { - ChatHelper.logDebug("Not on main thread: scheduling RGC lookup on main thread..."); - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { - try { - var rgcGeoLocation = NavigationModule.getInstance().getRgcHandler() - .locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); - ChatHelper.logDebug("RGC lookup successful: %s", rgcGeoLocation); - future.complete(new String[]{rgcGeoLocation.get(AdminLevel.COUNTRY), ""}); - } catch (Exception ex) { - future.completeExceptionally(ex); - } - }); - return future; - } + private static boolean canUseRgcHandler() { + return NavigationModule.getInstance().isEnabled() + && NavigationModule.getInstance().getRgcHandler() != null; + } - // We're on the main thread already — run synchronously - try { - var location = Objects.requireNonNull(NavigationModule.getInstance().getRgcHandler()).locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); - ChatHelper.logDebug("RGC lookup successful: %s", location); - future.complete(new String[]{location.get(AdminLevel.COUNTRY), ""}); - } catch (Exception ex) { - future.completeExceptionally(ex); - } + private static @NotNull CompletableFuture getCountryFromRgcAsync(@NotNull GeographicalCoordinate coordinates) { + CompletableFuture future = new CompletableFuture<>(); + ChatHelper.logDebug("Using custom file API to get country from location: %s, %s", coordinates.latitude(), coordinates.longitude()); + + if (!Bukkit.isPrimaryThread()) { + ChatHelper.logDebug("Not on main thread: scheduling RGC lookup on main thread..."); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> completeRgcLookup(coordinates, future)); return future; } + completeRgcLookup(coordinates, future); + return future; + } + + private static void completeRgcLookup(@NotNull GeographicalCoordinate coordinates, CompletableFuture future) { + try { + if (NavigationModule.getInstance().getRgcHandler() == null) throw new AssertionError("RgcHandler have to be initialized first"); + var location = NavigationModule.getInstance().getRgcHandler() + .locationFromCoordinates((float) coordinates.latitude(), (float) coordinates.longitude()); + ChatHelper.logDebug("RGC lookup successful: %s", location); + future.complete(new String[]{location.get(AdminLevel.COUNTRY), ""}); + } catch (Exception ex) { + future.completeExceptionally(ex); + } + } + + private static @NotNull CompletableFuture getCountryFromPhotonAsync(@NotNull GeographicalCoordinate coordinates) { + CompletableFuture future = new CompletableFuture<>(); String url = "https://photon.komoot.io/reverse?lat=" + coordinates.latitude() + "&lon=" + coordinates.longitude() + "&lang=en"; ChatHelper.logDebug("Requesting country from location: %s", url); @@ -60,9 +67,20 @@ public class OpenStreetMapAPI extends API { API.getAsync(url, new API.ApiResponseCallback() { @Override public void onResponse(String response) { - JSONObject jsonObject = API.createJSONObject(response); + completePhotonLookup(response, future); + } + + @Override + public void onFailure(IOException e) { + future.completeExceptionally(e); + } + }); + return future; + } - ChatHelper.logDebug("Response from OpenStreetMap: %s", jsonObject); + private static void completePhotonLookup(String response, @NonNull CompletableFuture future) { + JSONObject jsonObject = API.createJSONObject(response); + ChatHelper.logDebug("Response from OpenStreetMap: %s", jsonObject); JSONArray featuresArray = (JSONArray) jsonObject.get("features"); @@ -76,17 +94,9 @@ public void onResponse(String response) { JSONObject propertiesObject = (JSONObject) featuresObject.get("properties"); - String countryCodeCca2 = (String) propertiesObject.get("countrycode"); - String countryName = (String) propertiesObject.get("country"); + String countryCodeCca2 = (String) propertiesObject.get("countrycode"); + String countryName = (String) propertiesObject.get("country"); - future.complete(new String[]{countryName, countryCodeCca2}); - } - - @Override - public void onFailure(IOException e) { - future.completeExceptionally(e); - } - }); - return future; + future.complete(new String[]{countryName, countryCodeCca2}); } } From 820eafa9d0fdf94aa2c8d0597bd0c1c3d5bb7347 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 20 Jun 2026 23:59:16 +0200 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20=F0=9F=91=B7=20Change=20alpslib-ge?= =?UTF-8?q?o=20to=20snapshot=20+=20reorganize=20repos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 62 +++++++-------------------------------- gradle/libs.versions.toml | 4 +-- 2 files changed, 13 insertions(+), 53 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 42980945..9217fbca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,65 +7,25 @@ plugins { repositories { mavenCentral() - //mavenLocal() // NEVER use in Production/Commits! - maven { - url = uri("https://repo.papermc.io/repository/maven-public/") - } - - maven { - url = uri("https://maven.buildtheearth.net/releases") - } - - maven { - url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - } - - maven { - url = uri("https://mvn.alps-bte.com/repository/alps-bte/") - } - - maven { - url = uri("https://repo.onarandombox.com/content/groups/public/") - } - - maven { - url = uri("https://repo.codemc.io/repository/maven-snapshots/") - } + // mavenLocal() // NEVER use in Production/Commits! + maven("https://repo.papermc.io/repository/maven-public/") - maven { - url = uri("https://repo.codemc.io/repository/maven-public/") - } + maven("https://maven.buildtheearth.net/releases") // BuildTheEarth Projection - maven { - url = uri("https://jitpack.io") - } - - maven { - url = uri("https://repo.dmulloy2.net/repository/public/") - } + maven("https://mvn.alps-bte.com/repository/alps-bte/") // AlpsLib - maven { - url = uri("https://maven.daporkchop.net/") - } + maven("https://maven.enginehub.org/repo/") // WorldEdit - maven { - url = uri("https://download.java.net/maven/2") - } + maven("https://mvn.wesjd.net/") // Anvilgui - maven { - url = uri("https://maven.enginehub.org/repo/") - } + maven("https://repo.bluecolored.de/releases") // BlueMap - maven { - url = uri("https://mvn.wesjd.net/") - } - maven { url = uri("https://jitpack.io") } + maven("https://repo.essentialsx.net/releases/") - maven("https://repo.bluecolored.de/releases") + // Alps Lib Geo - 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/") - maven { - url = uri("https://repo.essentialsx.net/releases/") - } + maven("https://jitpack.io") // Clipper2 } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f56c59c2..2dc086b3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ alpslib-libpsterra = "1.1.4" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-utils/ alpslib-utils = "1.4.6" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-geo/ -alpslib-geo = "1.0.0" +alpslib-geo = "1.0.0-SNAPSHOT" # https://github.com/WesJD/AnvilGUI https://mvn.wesjd.net/ anvilgui = "1.10.13-SNAPSHOT" # Ref: https://github.com/BlueMap-Minecraft/BlueMapAPI @@ -37,7 +37,7 @@ okhttp-jvm = "5.4.0" # @pin 26.1+ required Java 25 - # https://artifactory.papermc.io/ui/native/universe/io/papermc/paper/paper-api/ paper-api = "1.21.11-R0.1-SNAPSHOT" # https://github.com/CryptoMorin/XSeries/releases -xseries = "13.8.0" +xseries = "13.7.0" # # Plugins # https://github.com/palantir/gradle-git-version/releases