diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/listeners/TpllListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/listeners/TpllListener.java index 8df177e7..70ae06d3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/listeners/TpllListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/listeners/TpllListener.java @@ -98,23 +98,45 @@ private boolean isTpllCommand(@NotNull PlayerCommandPreprocessEvent event) { * @return A CompletableFuture representing whether teleportation interception is required. */ private @NotNull CompletableFuture shouldIntercept() { - return OpenStreetMapAPI.getCountryFromLocationAsync(coordinate) - .thenComposeAsync(address -> { - if (address == null) return CompletableFuture.completedFuture(false); - - String countryName = address[0]; - Region region = Region.getByName(countryName); + return getRegionFromLocation() + .thenApplyAsync(region -> { + if (region == null) return false; + BuildTeam regionBuildTeam = region.getBuildTeam(); BuildTeam currentTeam = networkModule.getBuildTeam(); - if (region != null && region.getBuildTeam() != null && currentTeam != null - && !Objects.equals(region.getBuildTeam().getID(), currentTeam.getID())) { - targetBuildTeam = region.getBuildTeam(); - return CompletableFuture.completedFuture(true); + + if (regionBuildTeam == null || currentTeam == null) return false; + + if (!Objects.equals(regionBuildTeam.getID(), currentTeam.getID())) { + targetBuildTeam = regionBuildTeam; + return true; } - return CompletableFuture.completedFuture(false); + return false; }); } + /** + * Resolves the region for the current coordinate, falling back to the alternative + * OpenStreetMap lookup if the first lookup does not produce a known region. + */ + private @NotNull CompletableFuture getRegionFromLocation() { + return getRegionFromLocation(false) + .thenCompose(region -> { + if (region != null) { + return CompletableFuture.completedFuture(region); + } + + return getRegionFromLocation(true); + }); + } + + /** + * Resolves the region for the current coordinate using the requested lookup mode. + */ + private @NotNull CompletableFuture getRegionFromLocation(boolean fallbackLookup) { + return OpenStreetMapAPI.getCountryFromLocationAsync(coordinate, fallbackLookup) + .thenApply(address -> Region.getByName(address.regionName())); + } } 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 a60677b4..98dedcc9 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 @@ -17,6 +17,7 @@ import net.buildtheearth.buildteamtools.modules.navigation.components.warps.model.WarpGroup; import net.buildtheearth.buildteamtools.modules.network.NetworkModule; import net.buildtheearth.buildteamtools.modules.network.api.OpenStreetMapAPI; +import net.buildtheearth.buildteamtools.modules.network.api.RegionLookupResult; import net.buildtheearth.buildteamtools.modules.network.model.BuildTeam; import net.buildtheearth.buildteamtools.utils.menus.AbstractMenu; import net.buildtheearth.model.GeographicalCoordinate; @@ -34,6 +35,7 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; public class WarpsComponent extends ModuleComponent { @@ -169,28 +171,14 @@ public static void createWarp(@NonNull Player creator, WarpGroup group) { try { GeographicalCoordinate coordinate = Projection.toGeo(location.getX(), location.getZ()); - //Get the country belonging to the coordinates - CompletableFuture future = OpenStreetMapAPI.getCountryFromLocationAsync(coordinate); - - future.thenAccept(result -> { - 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); - - if (!ownsRegion) { - creator.sendMessage(ChatHelper.getErrorString("This team does not own the country %s!", result[0])); - return; - } - + WarpsComponent.getOwnedRegionFromLocation(coordinate, creator) + .thenAccept(result -> { // Create a default name for the warp String name = creator.getName() + "'s Warp"; // Create an instance of the warp POJO - Warp warp = new Warp(group, name, countryCodeCCA2, "cca2", null, null, null, location.getWorld().getName(), + Warp warp = new Warp(group, name, result.countryCodeCCA2(), "cca2", null, null, null, + location.getWorld().getName(), coordinate, location.getY(), location.getYaw(), location.getPitch(), false); Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> @@ -211,29 +199,15 @@ public static void createWarp(@NonNull Player creator, WarpGroup group) { * Creates a warp at the given location. */ public static void createWarp(@NonNull Location location, String name, WarpGroup group, Player creator) throws OutOfProjectionBoundsException { - GeographicalCoordinate coordinates = Projection.toGeo(location.getX(), location.getZ()); - - //Get the country belonging to the coordinates - CompletableFuture future = OpenStreetMapAPI.getCountryFromLocationAsync(coordinates); - - future.thenAccept(result -> { - String regionName = result[0]; - String countryCodeCCA2 = result[1].toUpperCase(); - - BuildTeamTools.getInstance().getComponentLogger().debug("Creating warp at {}", location); + GeographicalCoordinate coordinate = Projection.toGeo(location.getX(), location.getZ()); - //Check if the team owns this region/country - boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); - - if (!ownsRegion) { - creator.sendMessage(ChatHelper.getErrorString("Warp %s cannot be created. This team does not own the country %s" + - " (Country code %s)!", name, regionName, countryCodeCCA2)); - return; - } + getOwnedRegionFromLocation(coordinate, creator) + .thenAccept(result -> { // Create an instance of the warp POJO - Warp warp = new Warp(group, name, countryCodeCCA2, "cca2", null, null, null, location.getWorld().getName(), - coordinates, location.getY(), location.getYaw(), location.getPitch(), false); + Warp warp = new Warp(group, name, result.countryCodeCCA2(), "cca2", null, null, null, + location.getWorld().getName(), + coordinate, location.getY(), location.getYaw(), location.getPitch(), false); Objects.requireNonNull(NetworkModule.getInstance().getBuildTeam()).createWarp(creator, warp, true); }).exceptionally(e -> { @@ -304,4 +278,49 @@ public static void openWarpMenu(@NotNull Player player, @NotNull BuildTeam build default -> new WarpGroupMenu(player, buildTeam, menu != null, true, menu); } } + + public static @NotNull CompletableFuture getOwnedRegionFromLocation( + GeographicalCoordinate coordinate, + Player player + ) { + return getRegionLookupResult(coordinate, false, player) + .thenCompose(result -> { + if (ownsRegion(result)) { + return CompletableFuture.completedFuture(result); + } + + return getRegionLookupResult(coordinate, true, player) + .thenApply(fallbackResult -> { + if (!ownsRegion(fallbackResult)) { + throw new CompletionException(new IllegalStateException( + "This team does not own the country " + fallbackResult.regionName() + "!" + )); + } + + return fallbackResult; + }); + }); + } + + private static @NotNull CompletableFuture getRegionLookupResult( + GeographicalCoordinate coordinate, + boolean fallbackLookup, + Player player + ) { + return OpenStreetMapAPI.getCountryFromLocationAsync(coordinate, fallbackLookup) + .thenApply(result -> { + if (result.countryCodeCCA2() == null) { + return new RegionLookupResult(result.regionName(), NavUtils.getCCA2FromCountryName(result.regionName(), + player)); + } + return result; + }); + } + + private static boolean ownsRegion(@NonNull RegionLookupResult result) { + return NetworkModule.getInstance().ownsRegion( + result.regionName(), + result.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 aba2949e..15c3eba5 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,10 +6,9 @@ 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.WarpsComponent; 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; import net.buildtheearth.buildteamtools.modules.network.model.BuildTeam; import net.buildtheearth.buildteamtools.modules.network.model.Permissions; import net.buildtheearth.buildteamtools.utils.ListUtil; @@ -30,7 +29,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Objects; -import java.util.concurrent.CompletableFuture; public class WarpEditMenu extends AbstractMenu { @@ -140,45 +138,40 @@ protected void setItemClickEventsAsync() { getMenu().getSlot(LOCATION_SLOT).setClickHandler((clickPlayer, clickInformation) -> { clickPlayer.playSound(clickPlayer.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); - // Get the geographic coordinates of the player's location. Location location = clickPlayer.getLocation(); + try { GeographicalCoordinate coordinate = Projection.toGeo(location.getX(), location.getZ()); - //Get the country belonging to the coordinates - CompletableFuture future = OpenStreetMapAPI.getCountryFromLocationAsync(coordinate); + WarpsComponent.getOwnedRegionFromLocation(coordinate, clickPlayer) + .thenAccept(result -> { + warp.setCountryCode(result.countryCodeCCA2()); + warp.setWorldName(location.getWorld().getName()); + warp.setY(location.getY()); + warp.setCoordinate(coordinate); + warp.setYaw(location.getYaw()); + warp.setPitch(location.getPitch()); - future.thenAccept(result -> { - String regionName = result[0]; - String countryCodeCCA2 = result[1].toUpperCase(); + clickPlayer.playSound(clickPlayer.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); - if (countryCodeCCA2.isEmpty()) - countryCodeCCA2 = NavUtils.getCCA2FromCountryName(regionName, clickPlayer); + new WarpEditMenu(clickPlayer, warp, alreadyExists, true); + }) + .exceptionally(e -> { + Throwable cause = e.getCause() != null ? e.getCause() : e; - //Check if the team owns this region/country - boolean ownsRegion = NetworkModule.getInstance().ownsRegion(regionName, countryCodeCCA2); + clickPlayer.sendMessage(ChatHelper.getErrorString( + "Failed to change location: %s", + cause.getMessage() + )); - if (!ownsRegion) { - clickPlayer.sendMessage(ChatHelper.getErrorString("This team does not own the country %s!", result[0])); - return; - } + BuildTeamTools.getInstance().getComponentLogger().error( + "An error occurred while changing the location of the warp!", + e + ); + + return null; + }); - warp.setCountryCode(countryCodeCCA2); - warp.setWorldName(location.getWorld().getName()); - warp.setY(location.getY()); - warp.setCoordinate(coordinate); - warp.setYaw(location.getYaw()); - warp.setPitch(location.getPitch()); - - clickPlayer.playSound(clickPlayer.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); - - new WarpEditMenu(clickPlayer, warp, alreadyExists, true); - }).exceptionally(e -> { - Throwable cause = e.getCause() != null ? e.getCause() : e; - clickPlayer.sendMessage(ChatHelper.getErrorString("Failed to change location: %s", cause.getMessage())); - BuildTeamTools.getInstance().getComponentLogger().error("An error occurred while changing the location of the warp!", e); - return null; - }); } catch (OutOfProjectionBoundsException e) { clickPlayer.sendMessage(ChatHelper.getErrorString("Cannot set location here: %s", e.getMessage())); } @@ -274,4 +267,5 @@ protected Mask getMask() { .pattern("111111110") .build(); } + } 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 5b09a8f2..1a2fe96e 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 @@ -12,16 +12,19 @@ import org.jspecify.annotations.NonNull; import java.io.IOException; +import java.util.Locale; import java.util.concurrent.CompletableFuture; public class OpenStreetMapAPI extends API { /** * @param coordinate The latitude & longitude coordinates to get the country, region & city/town from + * @param forcePhoton If true, the photon API will be used instead of the RGC API. This is useful when the RGC API is not + * working properly. * @return The country name and country code belonging to this location */ - public static @NotNull CompletableFuture getCountryFromLocationAsync(@NotNull GeographicalCoordinate coordinate) { - if (canUseRgcHandler()) { + public static @NotNull CompletableFuture getCountryFromLocationAsync(@NotNull GeographicalCoordinate coordinate, boolean forcePhoton) { + if (!forcePhoton && canUseRgcHandler()) { return getCountryFromRgcAsync(coordinate); } return getCountryFromPhotonAsync(coordinate); @@ -32,8 +35,8 @@ private static boolean canUseRgcHandler() { && NavigationModule.getInstance().getRgcHandler() != null; } - private static @NotNull CompletableFuture getCountryFromRgcAsync(@NotNull GeographicalCoordinate coordinates) { - CompletableFuture future = new CompletableFuture<>(); + 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()) { @@ -46,20 +49,21 @@ private static boolean canUseRgcHandler() { return future; } - private static void completeRgcLookup(@NotNull GeographicalCoordinate coordinates, CompletableFuture 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), ""}); + future.complete(new RegionLookupResult(location.get(AdminLevel.COUNTRY), null)); } catch (Exception ex) { future.completeExceptionally(ex); } } - private static @NotNull CompletableFuture getCountryFromPhotonAsync(@NotNull GeographicalCoordinate coordinates) { - CompletableFuture future = new CompletableFuture<>(); + 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); @@ -78,7 +82,7 @@ public void onFailure(IOException e) { return future; } - private static void completePhotonLookup(String response, @NonNull CompletableFuture future) { + private static void completePhotonLookup(String response, @NonNull CompletableFuture future) { JSONObject jsonObject = API.createJSONObject(response); ChatHelper.logDebug("Response from OpenStreetMap: %s", jsonObject); @@ -97,6 +101,7 @@ private static void completePhotonLookup(String response, @NonNull CompletableFu String countryCodeCca2 = (String) propertiesObject.get("countrycode"); String countryName = (String) propertiesObject.get("country"); - future.complete(new String[]{countryName, countryCodeCca2}); + future.complete(new RegionLookupResult(countryName, countryCodeCca2.toUpperCase(Locale.ROOT))); } + } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/RegionLookupResult.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/RegionLookupResult.java new file mode 100644 index 00000000..d42df8f2 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/RegionLookupResult.java @@ -0,0 +1,6 @@ +package net.buildtheearth.buildteamtools.modules.network.api; + +import org.jetbrains.annotations.Nullable; + +public record RegionLookupResult(String regionName, @Nullable String countryCodeCCA2) { +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Region.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Region.java index a0482c70..f489e1cf 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Region.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/model/Region.java @@ -1,6 +1,7 @@ package net.buildtheearth.buildteamtools.modules.network.model; import lombok.Getter; +import org.jspecify.annotations.Nullable; public class Region { @@ -72,20 +73,13 @@ public Region(String name, Continent continent, BuildTeam buildTeam, String head this.countryCodeCca3 = countryCodeCca3; } - public static Region getByName(String name) { - Region lastRegion = null; + public static @Nullable Region getByName(String name) { for (Continent continent : Continent.values()) { for (Region region : continent.getRegions()) { - lastRegion = region; if (region.getName().equals(name)) return region; } } - return lastRegion; - } - - // Getter - public boolean isConnected() { - return buildTeam != null && buildTeam.isConnected(); + return null; } }