From 2531c11e3c4e0a1f92052dcb87dee9da4f55a0cf Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:10:53 +0100 Subject: [PATCH 01/10] refactor: replace json-simple with org.json Drop the duplicate JSON library so network APIs and stats use typed org.json objects end-to-end. --- build.gradle.kts | 1 - gradle/libs.versions.toml | 3 - .../commands/BuildTeamToolsCommand.java | 2 +- .../modules/network/api/API.java | 41 +++-- .../modules/network/api/NetworkAPI.java | 157 +++++++++--------- .../modules/network/api/NominatimAPI.java | 2 +- .../modules/network/api/OpenStreetMapAPI.java | 28 ++-- .../modules/network/api/PhotonAPI.java | 32 ++-- .../modules/stats/StatsModule.java | 8 +- .../modules/stats/menu/StatsMenu.java | 11 +- .../modules/stats/model/StatsPlayer.java | 10 +- .../modules/stats/model/StatsServer.java | 9 +- 12 files changed, 149 insertions(+), 155 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 48f75d64..c82a2b99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,7 +54,6 @@ dependencies { implementation(libs.javaapiforkml) { exclude(group = "com.sun.xml.bind", module = "jaxb-xjc") // Else Remapping will yell of duplicated classes } - implementation(libs.googlecode.json.simple) implementation(libs.bstats.bukkit) implementation(libs.buildtheearth.projection) { exclude(group = "com.google.guava", module = "guava") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a82ce742..045bc8a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,8 +34,6 @@ dazzleconf = "2.0.0-M3" fawe-bom = "1.56" # https://central.sonatype.com/artifact/com.google.code.gson/gson googlecode-gson = "2.15.0" -# https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -googlecode-json-simple = "1.1.1" # https://github.com/urbancamo/javaapiforkml javaapiforkml = "3.1.2" # https://mvnrepository.com/artifact/org.json/json @@ -75,7 +73,6 @@ dazzleconf = { module = "space.arim.dazzleconf:dazzleconf-yaml", version.ref = " com-alpsbte-alpslib-alpslib-libpsterra = { module = "com.alpsbte.alpslib:alpslib-libpsterra", version.ref = "alpslib-libpsterra" } fawe-bom = { module = "com.intellectualsites.bom:bom-newest", version.ref = "fawe-bom" } googlecode-gson = { module = "com.google.code.gson:gson", version.ref = "googlecode-gson" } -googlecode-json-simple = { module = "com.googlecode.json-simple:json-simple", version.ref = "googlecode-json-simple" } javaapiforkml = { module = "uk.m0nom:javaapiforkml", version.ref = "javaapiforkml" } json = { module = "org.json:json", version.ref = "json" } okhttp-jvm = { module = "com.squareup.okhttp3:okhttp-jvm", version.ref = "okhttp-jvm" } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BuildTeamToolsCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BuildTeamToolsCommand.java index f898794e..209e5d30 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BuildTeamToolsCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/common/commands/BuildTeamToolsCommand.java @@ -120,7 +120,7 @@ private static void updateCacheCommand(@NonNull CommandSender sender) { private static void displayCache(CommandSender sender) { if (StatsModule.getInstance().isEnabled()) { MessageBoxes.send(sender, plainTitle(sender, MessageKeys.Common.CACHE_TITLE), () -> - sender.sendMessage(StatsModule.getInstance().getCurrentCache().toJSONString())); + sender.sendMessage(StatsModule.getInstance().getCurrentCache().toString())); } else { sender.sendMessage(Messages.component(MessageKeys.Common.CACHE_STATS_DISABLED)); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java index 6d0021ae..14c0baae 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java @@ -4,10 +4,9 @@ import okhttp3.*; import okio.Buffer; import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -243,29 +242,27 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { public static JSONArray createJSONArray(String jsonString) { - JSONArray jsonArray = new JSONArray(); - JSONParser jsonParser = new JSONParser(); - if ((jsonString != null) && !(jsonString.isEmpty())) { - try { - jsonArray = (JSONArray) jsonParser.parse(jsonString); - } catch (ParseException e) { - BuildTeamTools.getInstance().getComponentLogger().error("Failed to parse JSON Array.", e); - } + if (jsonString == null || jsonString.isEmpty()) { + return new JSONArray(); + } + try { + return new JSONArray(jsonString); + } catch (JSONException e) { + BuildTeamTools.getInstance().getComponentLogger().error("Failed to parse JSON Array.", e); + return new JSONArray(); } - return jsonArray; } public static JSONObject createJSONObject(String jsonString) { - JSONObject jsonObject = new JSONObject(); - JSONParser jsonParser = new JSONParser(); - if ((jsonString != null) && !(jsonString.isEmpty())) { - try { - jsonObject = (JSONObject) jsonParser.parse(jsonString); - } catch (ParseException e) { - BuildTeamTools.getInstance().getComponentLogger().error("Failed to parse JSON Object.", e); - } + if (jsonString == null || jsonString.isEmpty()) { + return new JSONObject(); + } + try { + return new JSONObject(jsonString); + } catch (JSONException e) { + BuildTeamTools.getInstance().getComponentLogger().error("Failed to parse JSON Object.", e); + return new JSONObject(); } - return jsonObject; } private static Request.Builder createRequestBuilder(String url) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NetworkAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NetworkAPI.java index 0833e7d7..1918b040 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NetworkAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NetworkAPI.java @@ -17,9 +17,8 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; +import org.json.JSONArray; +import org.json.JSONObject; import org.jspecify.annotations.NonNull; import java.io.IOException; @@ -37,13 +36,11 @@ public class NetworkAPI { public static void setBuildTeamToolsInstalled(boolean installed) { String apiKey = BuildTeamTools.getInstance().getMainConfig().apiKey(); - Map requestBodyJson = new HashMap<>(); + JSONObject requestBodyJson = new JSONObject(); requestBodyJson.put("hasBuildTeamToolsInstalled", installed); - String requestBodyString = JSONValue.toJSONString(requestBodyJson); - RequestBody requestBody = RequestBody.create( - requestBodyString, + requestBodyJson.toString(), MediaType.parse("application/json") ); @@ -86,28 +83,31 @@ public void onResponse(String response) { // Add all teams to the proxy manager - for (Object object : responseArray.toArray()) { + for (Object object : responseArray) { // Check if the object is a JSON object - if (!(object instanceof JSONObject teamObject) || - !(teamObject.get("Regions") instanceof JSONArray regions) || - !(teamObject.get("Warps") instanceof JSONArray warps) || - !(teamObject.get("WarpGroups") instanceof JSONArray warpGroups)) { + if (!(object instanceof JSONObject teamObject)) { + continue; + } + JSONArray regions = teamObject.optJSONArray("Regions"); + JSONArray warps = teamObject.optJSONArray("Warps"); + JSONArray warpGroups = teamObject.optJSONArray("WarpGroups"); + if (regions == null || warps == null || warpGroups == null) { continue; } // Get some values to add to the build team Continent continent; - boolean isConnected = (boolean) teamObject.get("isConnectedToNetwork"); - boolean hasBuildTeamToolsInstalled = (long) teamObject.get("hasBuildTeamToolsInstalled") == 1; - String mainServerIP = (String) teamObject.get("MainServerIP"); - String teamID = (String) teamObject.get("ID"); + boolean isConnected = teamObject.optBoolean("isConnectedToNetwork"); + boolean hasBuildTeamToolsInstalled = asLong(teamObject.opt("hasBuildTeamToolsInstalled")) == 1; + String mainServerIP = teamObject.optString("MainServerIP", null); + String teamID = teamObject.optString("ID", null); MainServerResolver.Resolution mainServerResolution = getMainServerResolution(teamObject); String serverName = mainServerResolution.serverName(); - String name = (String) teamObject.get("Name"); - String blankName = (String) teamObject.get("BlankName"); - boolean allowsTransfers = (long) teamObject.get("AllowsTransfers") == 1; - String tag = (String) teamObject.get("Tag"); + String name = teamObject.optString("Name", null); + String blankName = teamObject.optString("BlankName", null); + boolean allowsTransfers = asLong(teamObject.opt("AllowsTransfers")) == 1; + String tag = teamObject.optString("Tag", null); if (isConnected && mainServerIP != null && serverName == null) { BuildTeamTools.getInstance().getLogger().warning(String.format( @@ -122,14 +122,14 @@ public void onResponse(String response) { WarpGroup otherWarpGroup = NavUtils.createOtherWarpGroup(buildTeam); // Add all the warp groups of the team to their respective build teams - for (Object warpGroupJSON : warpGroups.toArray()) { + for (Object warpGroupJSON : warpGroups) { if (!(warpGroupJSON instanceof JSONObject warpGroupObject)) continue; - UUID warpGroupID = UUID.fromString((String) warpGroupObject.get("ID")); - String warpGroupName = (String) warpGroupObject.get("Name"); - String warpGroupDescription = (String) warpGroupObject.get("Description"); - int warpSlot = (int) (long) warpGroupObject.get("Slot"); - String warpMaterial = (String) warpGroupObject.get("Material"); + UUID warpGroupID = UUID.fromString(warpGroupObject.getString("ID")); + String warpGroupName = warpGroupObject.optString("Name", null); + String warpGroupDescription = warpGroupObject.optString("Description", null); + int warpSlot = (int) asLong(warpGroupObject.opt("Slot")); + String warpMaterial = warpGroupObject.optString("Material", null); WarpGroup warpGroup = new WarpGroup(warpGroupID, buildTeam, warpGroupName, warpGroupDescription, warpSlot, warpMaterial); @@ -137,25 +137,26 @@ public void onResponse(String response) { } // Add all the warps of the team to their respective warp groups - for (Object warpJSON : warps.toArray()) { + for (Object warpJSON : warps) { if (!(warpJSON instanceof JSONObject warpObject)) continue; - String warpIDString = (String) warpObject.get("ID"); - UUID warpID = warpIDString != null ? UUID.fromString(warpIDString) : null; - String warpGroupIDString = (String) warpObject.get("WarpGroup"); - UUID warpGroupID = warpGroupIDString != null ? UUID.fromString(warpGroupIDString) : null; - String warpName = (String) warpObject.get("Name"); - String countryCode = (String) warpObject.get("CountryCode"); - String address = (String) warpObject.get("Address"); - Warp.AddressType addressType = Warp.AddressType.fromValue((String) warpObject.get("AddressType")); - String material = (String) warpObject.get("Material"); - String warpWorldName = (String) warpObject.get("WorldName"); - double warpLat = (double) warpObject.get("Latitude"); - double warpLon = (double) warpObject.get("Longitude"); - int warpHeight = (int) (long) warpObject.get("Height"); - float warpYaw = Float.parseFloat(warpObject.get("Yaw") + ""); - float warpPitch = Float.parseFloat(warpObject.get("Pitch") + ""); - boolean isHighlight = warpObject.get("isHighlight") != null && (long) warpObject.get("isHighlight") == 1; + String warpIDString = warpObject.optString("ID", null); + UUID warpID = warpIDString != null && !warpIDString.isEmpty() ? UUID.fromString(warpIDString) : null; + String warpGroupIDString = warpObject.optString("WarpGroup", null); + UUID warpGroupID = warpGroupIDString != null && !warpGroupIDString.isEmpty() + ? UUID.fromString(warpGroupIDString) : null; + String warpName = warpObject.optString("Name", null); + String countryCode = warpObject.optString("CountryCode", null); + String address = warpObject.optString("Address", null); + Warp.AddressType addressType = Warp.AddressType.fromValue(warpObject.optString("AddressType", null)); + String material = warpObject.optString("Material", null); + String warpWorldName = warpObject.optString("WorldName", null); + double warpLat = warpObject.optDouble("Latitude"); + double warpLon = warpObject.optDouble("Longitude"); + int warpHeight = (int) asLong(warpObject.opt("Height")); + float warpYaw = Float.parseFloat(String.valueOf(warpObject.opt("Yaw"))); + float warpPitch = Float.parseFloat(String.valueOf(warpObject.opt("Pitch"))); + boolean isHighlight = warpObject.has("isHighlight") && asLong(warpObject.opt("isHighlight")) == 1; if (material != null && material.isEmpty()) material = null; @@ -192,27 +193,27 @@ material, warpWorldName, new GeographicalCoordinate(warpLat, warpLon), warpHeigh if (!otherWarpGroup.getWarps().isEmpty()) buildTeam.getWarpGroups().add(otherWarpGroup); // Add all the regions of the team to their respective continents - for (Object regionJSON : regions.toArray()) { + for (Object regionJSON : regions) { if (!(regionJSON instanceof JSONObject regionObject)) continue; - String regionName = (String) regionObject.get("RegionName"); - String headBase64 = (String) regionObject.get("Head"); - RegionType regionType = RegionType.getByLabel((String) regionObject.get("RegionType")); + String regionName = regionObject.optString("RegionName", null); + String headBase64 = regionObject.optString("Head", null); + RegionType regionType = RegionType.getByLabel(regionObject.optString("RegionType", null)); Region region; if (regionType.equals(RegionType.COUNTRY)) { int area = getArea(regionObject); - String regionCodeCca3 = (String) regionObject.get("RegionCode"); - String regionCodeCca2 = (String) regionObject.get("cca2"); - continent = Continent.getByLabel((String) regionObject.get("region")); + String regionCodeCca3 = regionObject.optString("RegionCode", null); + String regionCodeCca2 = regionObject.optString("cca2", null); + continent = Continent.getByLabel(regionObject.optString("region", null)); if (continent == Continent.OTHER) - continent = Continent.getByLabel((String) regionObject.get("subregion")); + continent = Continent.getByLabel(regionObject.optString("subregion", null)); region = new Region(regionName, continent, buildTeam, headBase64, area, regionCodeCca2, regionCodeCca3); } else { - continent = Continent.getByLabel((String) teamObject.get("Continent")); + continent = Continent.getByLabel(teamObject.optString("Continent", null)); region = new Region(regionName, regionType, continent, buildTeam, headBase64); } @@ -232,20 +233,20 @@ material, warpWorldName, new GeographicalCoordinate(warpLat, warpLon), warpHeigh private MainServerResolver.Resolution getMainServerResolution(@NonNull JSONObject teamObject) { - String mainServerIP = (String) teamObject.get("MainServerIP"); + String mainServerIP = teamObject.optString("MainServerIP", null); - Object serversObject = teamObject.get("Servers"); - if (!(serversObject instanceof JSONArray serversArray)) { + JSONArray serversArray = teamObject.optJSONArray("Servers"); + if (serversArray == null) { return MainServerResolver.resolve(mainServerIP, List.of()); } List servers = new ArrayList<>(); - for (Object object : serversArray.toArray()) { + for (Object object : serversArray) { if (!(object instanceof JSONObject serverObject)) continue; - Object serverIP = serverObject.get("IP"); - Object serverName = serverObject.get("Name"); + Object serverIP = serverObject.opt("IP"); + Object serverName = serverObject.opt("Name"); servers.add(new MainServerResolver.ServerEntry( serverName instanceof String ? (String) serverName : null, serverIP instanceof String ? (String) serverIP : null)); @@ -255,13 +256,15 @@ private MainServerResolver.Resolution getMainServerResolution(@NonNull JSONObjec } private int getArea(JSONObject regionObject) { - if (regionObject == null) return 0; - if (regionObject.get("area") == null) return 0; - - if (regionObject.get("area") instanceof Long area) - return Math.toIntExact(area); + if (regionObject == null || !regionObject.has("area")) { + return 0; + } - return ((Double) regionObject.get("area")).intValue(); + Object area = regionObject.opt("area"); + if (area instanceof Number number) { + return number.intValue(); + } + return 0; } @Override @@ -285,7 +288,7 @@ public void onResponse(String response) { try { JSONObject teamObject = API.createJSONObject(response); - String teamID = (String) teamObject.get("ID"); + String teamID = teamObject.getString("ID"); NetworkModule networkModule = NetworkModule.getInstance(); BuildTeam buildTeam = networkModule.getBuildTeamByID(teamID); @@ -354,27 +357,23 @@ public static void updateWarpGroup(WarpGroup warpGroup, API.ApiResponseCallback API.putAsync("https://nwapi.buildtheearth.net/api/teams/" + apiKey + "/warpgroups", requestBody, callback); } - @SuppressWarnings("unchecked") public static void deleteWarp(Warp warp, API.ApiResponseCallback callback) { String apiKey = BuildTeamTools.getInstance().getMainConfig().apiKey(); JSONObject requestBodyJson = new JSONObject(); requestBodyJson.put("key", warp.getId().toString()); - String requestBodyString = requestBodyJson.toString(); - RequestBody requestBody = RequestBody.create(requestBodyString, MediaType.parse("application/json")); + RequestBody requestBody = RequestBody.create(requestBodyJson.toString(), MediaType.parse("application/json")); API.deleteAsync("https://nwapi.buildtheearth.net/api/teams/" + apiKey + "/warps", requestBody, callback); } - @SuppressWarnings("unchecked") public static void deleteWarpGroup(WarpGroup warpGroup, API.ApiResponseCallback callback) { String apiKey = BuildTeamTools.getInstance().getMainConfig().apiKey(); JSONObject requestBodyJson = new JSONObject(); requestBodyJson.put("key", warpGroup.getId().toString()); - String requestBodyString = requestBodyJson.toString(); - RequestBody requestBody = RequestBody.create(requestBodyString, MediaType.parse("application/json")); + RequestBody requestBody = RequestBody.create(requestBodyJson.toString(), MediaType.parse("application/json")); API.deleteAsync("https://nwapi.buildtheearth.net/api/teams/" + apiKey + "/warpgroups", requestBody, callback); } @@ -386,17 +385,17 @@ public static void syncPlayerList() { String apiKey = BuildTeamTools.getInstance().getMainConfig().apiKey(); - List> players = new ArrayList<>(); + JSONArray players = new JSONArray(); for (Player player : Bukkit.getOnlinePlayers()) { - players.add(Arrays.asList( - player.getUniqueId().toString(), - player.getName() - )); + JSONArray playerEntry = new JSONArray(); + playerEntry.put(player.getUniqueId().toString()); + playerEntry.put(player.getName()); + players.put(playerEntry); } RequestBody requestBody = RequestBody.create( - JSONValue.toJSONString(players), + players.toString(), MediaType.parse("application/json") ); @@ -446,4 +445,8 @@ private static void getAsync(String url, API.ApiResponseCallback callback) { API.getAsync(url, callback); } + + private static long asLong(Object value) { + return value instanceof Number number ? number.longValue() : 0L; + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java index e2bb054e..5f51913f 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java @@ -3,7 +3,7 @@ import com.alpsbte.alpslib.utils.ChatHelper; import net.buildtheearth.model.GeographicalCoordinate; import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONObject; +import org.json.JSONObject; import java.io.IOException; import java.util.concurrent.CompletableFuture; 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 93c1cc8e..7db37805 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 @@ -7,8 +7,8 @@ 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.json.JSONArray; +import org.json.JSONObject; import org.jspecify.annotations.NonNull; import java.io.IOException; @@ -86,20 +86,22 @@ private static void completePhotonLookup(String response, @NonNull CompletableFu JSONObject jsonObject = API.createJSONObject(response); ChatHelper.logDebug("Response from OpenStreetMap: %s", jsonObject); - JSONArray featuresArray = (JSONArray) jsonObject.get("features"); + JSONArray featuresArray = jsonObject.optJSONArray("features"); - if (featuresArray == null || featuresArray.isEmpty()) { - future.completeExceptionally(new IllegalStateException("No location data found for these coordinates. The " + - "location may be in the ocean or outside mapped areas.")); - return; - } - - JSONObject featuresObject = (JSONObject) featuresArray.getFirst(); + if (featuresArray == null || featuresArray.isEmpty()) { + future.completeExceptionally(new IllegalStateException("No location data found for these coordinates. The " + + "location may be in the ocean or outside mapped areas.")); + return; + } - JSONObject propertiesObject = (JSONObject) featuresObject.get("properties"); + JSONObject propertiesObject = featuresArray.getJSONObject(0).optJSONObject("properties"); + if (propertiesObject == null) { + future.completeExceptionally(new IllegalStateException("No properties found in Photon response.")); + return; + } - String countryCodeCca2 = (String) propertiesObject.get("countrycode"); - String countryName = (String) propertiesObject.get("country"); + String countryCodeCca2 = propertiesObject.optString("countrycode", ""); + String countryName = propertiesObject.optString("country", null); future.complete(new RegionLookupResult(countryName, countryCodeCca2.toUpperCase(Locale.ROOT))); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/PhotonAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/PhotonAPI.java index 350e0b4e..c87c1c22 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/PhotonAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/PhotonAPI.java @@ -3,8 +3,8 @@ import com.alpsbte.alpslib.utils.ChatHelper; import net.buildtheearth.model.GeographicalCoordinate; import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; +import org.json.JSONArray; +import org.json.JSONObject; import java.io.IOException; import java.net.URLEncoder; @@ -42,20 +42,19 @@ public void onFailure(IOException e) { return future; } - private static String formatAddressFromResponse (String response) { + private static String formatAddressFromResponse(String response) { JSONObject jsonObject = API.createJSONObject(response); ChatHelper.logDebug("Response from Photon: %s", jsonObject); - JSONArray features = (JSONArray) jsonObject.get("features"); + JSONArray features = jsonObject.optJSONArray("features"); if (features == null || features.isEmpty()) { ChatHelper.logError("No address data found for these coordinates."); return ""; } - JSONObject feature = (JSONObject) features.getFirst(); - JSONObject properties = (JSONObject) feature.get("properties"); + JSONObject properties = features.getJSONObject(0).optJSONObject("properties"); if (properties == null) { ChatHelper.logError("No properties found in Photon response."); @@ -63,9 +62,9 @@ private static String formatAddressFromResponse (String response) { } return Stream.of( - properties.get("street"), - properties.get("city"), - properties.get("country") + properties.opt("street"), + properties.opt("city"), + properties.opt("country") ) .filter(Objects::nonNull) .map(Object::toString) @@ -113,18 +112,17 @@ public void onFailure(IOException e) { ChatHelper.logDebug("Response from Photon: %s", jsonObject); - JSONArray features = (JSONArray) jsonObject.get("features"); - + JSONArray features = jsonObject.optJSONArray("features"); JSONArray coordinates = getCoordinates(features); - if (coordinates == null || coordinates.size() < 2) { + if (coordinates == null || coordinates.length() < 2) { throw new IllegalArgumentException( "Invalid coordinates in Photon response." ); } - double longitude = ((Number) coordinates.get(0)).doubleValue(); - double latitude = ((Number) coordinates.get(1)).doubleValue(); + double longitude = coordinates.getDouble(0); + double latitude = coordinates.getDouble(1); return new GeographicalCoordinate(latitude, longitude); } @@ -136,9 +134,7 @@ private static JSONArray getCoordinates(JSONArray features) { ); } - JSONObject feature = (JSONObject) features.getFirst(); - - JSONObject geometry = (JSONObject) feature.get("geometry"); + JSONObject geometry = features.getJSONObject(0).optJSONObject("geometry"); if (geometry == null) { throw new IllegalArgumentException( @@ -146,6 +142,6 @@ private static JSONArray getCoordinates(JSONArray features) { ); } - return (JSONArray) geometry.get("coordinates"); + return geometry.optJSONArray("coordinates"); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/StatsModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/StatsModule.java index 5112ee5d..c38b7904 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/StatsModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/StatsModule.java @@ -15,7 +15,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.json.JSONArray; -import org.json.simple.JSONObject; +import org.json.JSONObject; import java.util.HashMap; import java.util.List; @@ -117,7 +117,7 @@ public boolean updateAndSave() { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("Stats"); out.writeUTF(p.getUniqueId().toString()); - out.writeUTF(StatsModule.getInstance().getCurrentCache().toJSONString()); + out.writeUTF(StatsModule.getInstance().getCurrentCache().toString()); p.sendPluginMessage(BuildTeamTools.getInstance(), "btt:buildteam", out.toByteArray()); FeatureUsage.record(UsageFeature.STATS); @@ -127,10 +127,10 @@ public boolean updateAndSave() { public JSONObject getCurrentCache() { JSONObject jsonObject = statsServer.toJSON(); - //Player Stats JSONArray jsonArray = new JSONArray(); - for (StatsPlayer statsPlayer : statsPlayerList.values()) + for (StatsPlayer statsPlayer : statsPlayerList.values()) { jsonArray.put(statsPlayer.toJSON()); + } jsonObject.put("PLAYERS", jsonArray); return jsonObject; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java index 82651d59..3b0e77b2 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java @@ -18,12 +18,11 @@ import org.bukkit.inventory.ItemStack; import org.ipvp.canvas.mask.BinaryMask; import org.ipvp.canvas.mask.Mask; -import org.json.simple.JSONObject; +import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.UUID; public class StatsMenu extends AbstractMenu { @@ -117,8 +116,10 @@ private void setupLores() { List playerLore = new ArrayList<>(); - for (Object key : playerStats.keySet()) { - if (key instanceof UUID) continue; + for (String key : playerStats.keySet()) { + if ("UUID".equals(key)) { + continue; + } Object value = playerStats.get(key); playerLore.add(Component.text(key + ": " + value)); @@ -132,7 +133,7 @@ private void setupLores() { List serverLore = new ArrayList<>(); - for (Object key : serverStats.keySet()) { + for (String key : serverStats.keySet()) { Object value = serverStats.get(key); serverLore.add(Component.text(key + ": " + value)); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java index 817b1f9f..82a02ab4 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java @@ -1,7 +1,7 @@ package net.buildtheearth.buildteamtools.modules.stats.model; import net.buildtheearth.buildteamtools.modules.stats.StatsModule; -import org.json.simple.JSONObject; +import org.json.JSONObject; import java.util.HashMap; import java.util.UUID; @@ -49,13 +49,13 @@ else if (object instanceof Integer && value instanceof Integer) { stats.put(statsPlayerType, object); } - @SuppressWarnings("unchecked") public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); - jsonObject.put("UUID", uuid); - for (StatsPlayerType statsPlayerType : stats.keySet()) - jsonObject.put(statsPlayerType, stats.get(statsPlayerType)); + jsonObject.put("UUID", uuid.toString()); + for (StatsPlayerType statsPlayerType : stats.keySet()) { + jsonObject.put(statsPlayerType.name(), stats.get(statsPlayerType)); + } return jsonObject; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java index 2693f553..0a8c3bcd 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java @@ -1,7 +1,7 @@ package net.buildtheearth.buildteamtools.modules.stats.model; import net.buildtheearth.buildteamtools.modules.stats.StatsModule; -import org.json.simple.JSONObject; +import org.json.JSONObject; import java.util.HashMap; @@ -46,14 +46,13 @@ else if (object instanceof Integer && value instanceof Integer) { stats.put(statsServerType, object); } - @SuppressWarnings("unchecked") public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); - for (StatsServerType statsServerType : stats.keySet()) - jsonObject.put(statsServerType, stats.get(statsServerType)); + for (StatsServerType statsServerType : stats.keySet()) { + jsonObject.put(statsServerType.name(), stats.get(statsServerType)); + } return jsonObject; } } - From 9d0c15567f647a090449e49fd5b3007f460f2860 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:11:03 +0100 Subject: [PATCH 02/10] fix: clear Paper and Canvas deprecation warnings Move KML command-block data to PersistentDataContainer, drop UnsafeValues protocol checks, and switch menus/items off deprecated display APIs. --- .../generator/components/kml/KmlCommand.java | 50 ++++++++++++------- .../blockpalettegui/BlockPaletteManager.java | 14 +++--- .../blockpalettegui/ChoosePaletteMenu.java | 2 +- .../modules/navigation/NavUtils.java | 12 ++--- .../components/tpll/TpllComponent.java | 2 +- .../utils/menus/NameListMenu.java | 38 ++++++++------ 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java index 3e5ffa0e..b8123eb5 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java @@ -19,6 +19,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; @@ -26,7 +27,8 @@ import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; import org.incendo.cloud.minecraft.extras.RichDescription; import org.incendo.cloud.parser.standard.StringParser; import org.jspecify.annotations.NonNull; @@ -59,6 +61,17 @@ public class KmlCommand { private static final String GEOSURFACE_COMMAND = "geosurface"; private static final String EXTEND_PREFIX = "-extend:"; + private static final NamespacedKey KEY_PLAYER_NAME = namespacedKey("kml_player_name"); + private static final NamespacedKey KEY_BLOCKTYPE = namespacedKey("kml_blocktype"); + private static final NamespacedKey KEY_PREVIOUS_BLOCKTYPE = namespacedKey("kml_previous_blocktype"); + private static final NamespacedKey KEY_BLOCK_CREATION_COMMAND = namespacedKey("kml_block_creation_command"); + private static final NamespacedKey KEY_EXTEND_TO_GROUND = namespacedKey("kml_extend_to_ground"); + private static final NamespacedKey KEY_EXTEND_TO_GROUND_BLOCKTYPE = namespacedKey("kml_extend_to_ground_blocktype"); + + private static NamespacedKey namespacedKey(String key) { + return new NamespacedKey("buildteamtools", key); + } + public void register(Module owner) { var aliases = BuildTeamTools.getInstance().getMainConfig().commands().labels(); var commandManager = BuildTeamTools.getInstance().getCommandManager(); @@ -245,17 +258,20 @@ void fillVerticalToTerrain(BlockLocation locationOverGround, @NonNull World worl */ public boolean processKml(@NonNull Block senderBlock, String[] args) { - //read metadata to get player name/ID BlockState blockState = senderBlock.getState(); - String playerName = blockState.getMetadata("kmlPlayerName").getFirst().asString(); - String blocktypeString = blockState.getMetadata("kmlBlocktype").getFirst().asString(); - String previousCommandBlockType = blockState.getMetadata("kmlPreviousBlocktype").getFirst().asString(); - String blockCreationCommand = blockState.getMetadata("kmlBlockCreationCommand").getFirst().asString(); - BlockCreationMode operationMode = commandToCreationMode(blockCreationCommand); + if (!(blockState instanceof CommandBlock commandBlock)) { + return false; + } - boolean extendToGround = blockState.getMetadata("kmlExtendToGround").getFirst().asBoolean(); - String extendToGroundBlockType = blockState.getMetadata("kmlExtendToGroundBlocktype").getFirst().asString(); + PersistentDataContainer data = commandBlock.getPersistentDataContainer(); + String playerName = data.getOrDefault(KEY_PLAYER_NAME, PersistentDataType.STRING, ""); + String blocktypeString = data.getOrDefault(KEY_BLOCKTYPE, PersistentDataType.STRING, ""); + String previousCommandBlockType = data.getOrDefault(KEY_PREVIOUS_BLOCKTYPE, PersistentDataType.STRING, ""); + String blockCreationCommand = data.getOrDefault(KEY_BLOCK_CREATION_COMMAND, PersistentDataType.STRING, ""); + BlockCreationMode operationMode = commandToCreationMode(blockCreationCommand); + boolean extendToGround = data.getOrDefault(KEY_EXTEND_TO_GROUND, PersistentDataType.BOOLEAN, false); + String extendToGroundBlockType = data.getOrDefault(KEY_EXTEND_TO_GROUND_BLOCKTYPE, PersistentDataType.STRING, ""); if (playerName.isEmpty() || blocktypeString.isEmpty()) { //invalid metadata, cancel @@ -563,15 +579,13 @@ public boolean createPasteUI(Player player, String alias, String @NonNull [] arg CommandBlock cmdBlock = (CommandBlock) block.getState(); cmdBlock.setCommand("/btt kml "); //ready to paste kml content - cmdBlock.setMetadata("kmlPlayerName", new FixedMetadataValue(BuildTeamTools.getInstance(), player.getName())); - //cmdBlock.setMetadata("kmlPlayerID", new FixedMetadataValue(BuildTeamTools.getInstance(), p.getUniqueId())); - cmdBlock.setMetadata("kmlBlocktype", new FixedMetadataValue(BuildTeamTools.getInstance(), blocktype)); - cmdBlock.setMetadata("kmlExtendToGround", new FixedMetadataValue(BuildTeamTools.getInstance(), extendToGround)); - cmdBlock.setMetadata("kmlExtendToGroundBlocktype", new FixedMetadataValue(BuildTeamTools.getInstance(), extendToGroundBlockType)); - - cmdBlock.setMetadata("kmlPreviousBlocktype", new FixedMetadataValue(BuildTeamTools.getInstance(), previousMaterial.toString())); - - cmdBlock.setMetadata("kmlBlockCreationCommand", new FixedMetadataValue(BuildTeamTools.getInstance(), alias)); + PersistentDataContainer data = cmdBlock.getPersistentDataContainer(); + data.set(KEY_PLAYER_NAME, PersistentDataType.STRING, player.getName()); + data.set(KEY_BLOCKTYPE, PersistentDataType.STRING, blocktype); + data.set(KEY_EXTEND_TO_GROUND, PersistentDataType.BOOLEAN, extendToGround); + data.set(KEY_EXTEND_TO_GROUND_BLOCKTYPE, PersistentDataType.STRING, extendToGroundBlockType); + data.set(KEY_PREVIOUS_BLOCKTYPE, PersistentDataType.STRING, previousMaterial.toString()); + data.set(KEY_BLOCK_CREATION_COMMAND, PersistentDataType.STRING, alias); cmdBlock.update(); player.sendMessage(Messages.component(MessageKeys.Generator.Kml.COMMANDBLOCK_CREATED)); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/BlockPaletteManager.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/BlockPaletteManager.java index 8e29ccf3..63f3cd42 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/BlockPaletteManager.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/BlockPaletteManager.java @@ -3,12 +3,13 @@ import com.cryptomorin.xseries.XMaterial; import lombok.Getter; import lombok.Setter; +import net.buildtheearth.buildteamtools.utils.ItemComponents; +import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -244,14 +245,11 @@ ItemStack[] getItemsForFilters(java.util.List filters) { ItemStack createMenuItem(XMaterial material, String name) { - ItemStack item = material.parseItem(); - if (item == null) item = new ItemStack(Material.BARRIER); - ItemMeta m = item.getItemMeta(); - if (m != null) { - m.setDisplayName(name); - item.setItemMeta(m); + Material bukkitMaterial = material.get(); + if (bukkitMaterial == null) { + bukkitMaterial = Material.BARRIER; } - return item; + return ItemComponents.of(bukkitMaterial, Component.text(name)); } /* ======================= diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java index f703efa2..bfc1bc88 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java @@ -174,7 +174,7 @@ protected void setItemClickEventsAsync() { manager.updatePlayerFilters(p, filters); // Update clicked slot title correctly (palette name if custom) - ItemStack cur = getMenu().getSlot(slot).getItem(); + ItemStack cur = getMenu().getSlot(slot).getItem(p); if (cur != null) { String baseTitle; BlockPaletteManager.Palette pal = manager.getPalette(key); 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 8b286927..45d386f9 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -18,9 +18,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; -import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.UnsafeValues; import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.Contract; @@ -42,16 +40,14 @@ public static void sendPlayerToConnectedServer(Player player, String server) { } public static boolean isTransferCapable(@NotNull Player player, @NotNull BuildTeam targetBuildTeam) { - // Must be 1.20.5 / 766 or higher for transfer capability + // Client must be 1.20.5 / 766+. Server is Paper 1.21+ for this plugin, so always capable. int playerVersion = player.getProtocolVersion(); - UnsafeValues unsafeValues = Bukkit.getUnsafe(); - int serverProtocolVersion = unsafeValues.getProtocolVersion(); - ChatHelper.logDebug("Transfer check - Player protocol: %d, Server protocol: %s, BuildTeam: %s, Allows transfers: %b, " + - "IP: %s", playerVersion, serverProtocolVersion, targetBuildTeam.getBlankName(), + ChatHelper.logDebug("Transfer check - Player protocol: %d, BuildTeam: %s, Allows transfers: %b, IP: %s", + playerVersion, targetBuildTeam.getBlankName(), targetBuildTeam.isAllowsTransfers(), targetBuildTeam.getIP()); - return targetBuildTeam.isAllowsTransfers() && playerVersion >= 766 && serverProtocolVersion >= 766; + return targetBuildTeam.isAllowsTransfers() && playerVersion >= 766; } public static void transferPlayer(@NotNull Player player, @NotNull String ip) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/TpllComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/TpllComponent.java index 7be744a2..c83622fd 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/TpllComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/tpll/TpllComponent.java @@ -99,7 +99,7 @@ public void processQueueForPlayer(@NonNull Player player) { */ public void tpllPlayer(@NotNull Player player, @NotNull GeographicalCoordinate coordinate, String targetServerName) { FeatureUsage.record(UsageFeature.TPLL); - ChatHelper.logDebug("Starting universal tpll teleportation for %s to %s.", player.getDisplayName(), targetServerName); + ChatHelper.logDebug("Starting universal tpll teleportation for %s to %s.", player.getName(), targetServerName); // Send a plugin message to the target server which adds the tpll to the queue ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("TPLL"); diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java index 94c9ef7e..eff0ef1a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java @@ -90,14 +90,19 @@ protected List getSource() { @Override protected void setPaginatedPreviewItems(List source) { - // Set pagignated items - List> pagItems = source.stream().map(l -> (MutablePair) l).toList(); int slot = 0; - for (MutablePair item : pagItems) { - if (selectedNames.contains(item.getRight())) - item.setLeft(new Item(item.getLeft()).setAmount(1).addEnchantment(Enchantment.LUCK_OF_THE_SEA, 1).hideEnchantments(true).build()); - - getMenu().getSlot(slot).setItem(item.getLeft()); + for (Object element : source) { + if (!(element instanceof MutablePair pair) + || !(pair.getLeft() instanceof ItemStack stack) + || !(pair.getRight() instanceof String name)) { + continue; + } + + ItemStack display = stack; + if (selectedNames.contains(name)) { + display = new Item(stack).setAmount(1).addEnchantment(Enchantment.LUCK_OF_THE_SEA, 1).hideEnchantments(true).build(); + } + getMenu().getSlot(slot).setItem(display); slot++; } } @@ -109,18 +114,19 @@ protected void setPaginatedMenuItemsAsync(List source) { @Override protected void setPaginatedItemClickEventsAsync(@NonNull List source) { - List> pagItems = source.stream().map(l -> (MutablePair) l).toList(); int slot = 0; - for (MutablePair item : pagItems) { - final int _slot = slot; - getMenu().getSlot(_slot).setClickHandler((clickPlayer, clickInformation) -> { - String type = (item.getRight().toLowerCase()); - - if (selectedNames.contains(type)) + for (Object element : source) { + if (!(element instanceof MutablePair pair) || !(pair.getRight() instanceof String name)) { + continue; + } + final int clickSlot = slot; + final String type = name.toLowerCase(); + getMenu().getSlot(clickSlot).setClickHandler((clickPlayer, clickInformation) -> { + if (selectedNames.contains(type)) { selectedNames.remove(type); - else + } else { selectedNames.add(type); - + } reloadMenuAsync(); }); slot++; From 301178c9ba7127941041af05873ba4d842d0ebfb Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:11:03 +0100 Subject: [PATCH 03/10] fix(i18n): allow null player when rendering messages Generator startup notifies console without a player; fall back to the default locale instead of NPEing in Messages.locale. --- .../buildteamtools/i18n/Messages.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/i18n/Messages.java b/src/main/java/net/buildtheearth/buildteamtools/i18n/Messages.java index 37cfb879..dc17ffb3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/i18n/Messages.java +++ b/src/main/java/net/buildtheearth/buildteamtools/i18n/Messages.java @@ -66,7 +66,10 @@ public static Locale locale(@Nullable Audience audience) { return audience.get(Identity.LOCALE).orElse(Translations.DEFAULT_LOCALE); } - public static Locale locale(Player player) { + public static Locale locale(@Nullable Player player) { + if (player == null) { + return Translations.DEFAULT_LOCALE; + } return player.locale(); } @@ -78,24 +81,24 @@ public static Component render(@Nullable Audience audience, Component component) return render(component, locale(audience)); } - public static Component render(Player player, Component component) { + public static Component render(@Nullable Player player, Component component) { return render(component, locale(player)); } - public static Component render(Player player, String key, ComponentLike... args) { + public static Component render(@Nullable Player player, String key, ComponentLike... args) { return render(player, component(key, args)); } /** Legacy section string for APIs that still require {@link String} item names/lore. */ - public static String legacy(Player player, Component component) { + public static String legacy(@Nullable Player player, Component component) { return LEGACY.serialize(render(player, component)); } - public static String legacy(Player player, String key, ComponentLike... args) { + public static String legacy(@Nullable Player player, String key, ComponentLike... args) { return legacy(player, component(key, args)); } - public static String plain(Player player, String key, ComponentLike... args) { + public static String plain(@Nullable Player player, String key, ComponentLike... args) { return PLAIN.serialize(render(player, key, args)); } From 5eea2974f48ddefee2318666c9f62a7592589de0 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:11:03 +0100 Subject: [PATCH 04/10] build: include kotlin_module duplicates for Shadow merge Let KotlinModuleMetadataTransformer see okhttp/okio/stdlib metadata instead of dropping duplicates under the default EXCLUDE strategy. --- build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index c82a2b99..a3e074da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -115,6 +115,12 @@ tasks.shadowJar { // Prevents the plugin itself from being relocated by autorelocate // This is needed because net.buildtheearth.projection shares the same package namespace relocate("net.buildtheearth.buildteamtools", "net.buildtheearth.buildteamtools") + + // Let KotlinModuleMetadataTransformer merge okhttp/okio/stdlib .kotlin_module files + // (default EXCLUDE drops duplicates before the transformer sees them). + filesMatching("META-INF/*.kotlin_module") { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } } tasks.assemble { From d8373fef24446d0dc9856e957ed5511a1052b7c2 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:43:36 +0100 Subject: [PATCH 05/10] refactor: extract shared helpers for stats, warps, palettes, menus, and KML Collapse duplicated accumulation, material-item, palette chat/text, selection chrome, and KML placement/material paths into shared helpers. --- .../generator/components/kml/KmlCommand.java | 71 +++++---- .../ChoosePaletteBlocksMenu.java | 22 +-- .../blockpalettegui/ChoosePaletteMenu.java | 20 +-- .../blockpalettegui/CreatePaletteMenu.java | 143 +++++++----------- .../blockpalettegui/EditPaletteMenu.java | 133 +++++++--------- .../blockpalettegui/PaletteChatInput.java | 138 +++++++++++++++++ .../blockpalettegui/PaletteTexts.java | 41 +++++ .../components/warps/model/Warp.java | 40 +---- .../components/warps/model/WarpGroup.java | 35 +---- .../warps/model/WarpMaterialItems.java | 50 ++++++ .../modules/stats/menu/StatsMenu.java | 59 ++------ .../modules/stats/model/StatsPlayer.java | 34 +---- .../modules/stats/model/StatsServer.java | 34 +---- .../modules/stats/model/StatsValues.java | 32 ++++ .../utils/menus/AbstractPaginatedMenu.java | 23 +++ .../utils/menus/BlockListMenu.java | 16 +- .../utils/menus/NameListMenu.java | 16 +- 17 files changed, 471 insertions(+), 436 deletions(-) create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteChatInput.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteTexts.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpMaterialItems.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsValues.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java index b8123eb5..d8277f5c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java @@ -48,7 +48,7 @@ * the player then pastes the kml contents and confirms the command
* 2) The CommandBlock then sends a kml command with the contents, which is parsed by the server

*

- * note: this command differentiates between aliases to determine wether to + * note: this command differentiates between aliases to determine whether to * generate intermediate points between the geo-coordinates from the kml linestrings. * Players should only use /geopoints or /geopath aliases, * direct use of the /kml command is restricted to CommandBlockSender @@ -295,17 +295,8 @@ public boolean processKml(@NonNull Block senderBlock, String[] args) { recordKml(blockCreationCommand); String kml_content = String.join(" ", args); - Material matchedBlockMaterial = Material.matchMaterial(blocktypeString); - final Material blockMaterial = (matchedBlockMaterial != null && matchedBlockMaterial.isBlock()) ? matchedBlockMaterial : Material.BRICKS; - if (matchedBlockMaterial == null || !matchedBlockMaterial.isBlock()) { - player.sendMessage(Messages.component(MessageKeys.Generator.Kml.INVALID_BLOCK_METADATA)); - } - - Material matchedExtendMaterial = Material.matchMaterial(extendToGroundBlockType); - final Material extendMaterial = (matchedExtendMaterial != null && matchedExtendMaterial.isBlock()) ? matchedExtendMaterial : Material.BRICKS; - if (matchedExtendMaterial == null || !matchedExtendMaterial.isBlock()) { - player.sendMessage(Messages.component(MessageKeys.Generator.Kml.INVALID_BLOCK_METADATA)); - } + final Material blockMaterial = resolvePlaceableMaterial(blocktypeString, player); + final Material extendMaterial = resolvePlaceableMaterial(extendToGroundBlockType, player); //parse kml long time_beforeKMLParse = System.currentTimeMillis(); @@ -370,23 +361,10 @@ public boolean processKml(@NonNull Block senderBlock, String[] args) { float maxDistanceToPlayer = 1000; //TODO server config - for (BlockLocation pt : blockPositions) { - Location loc = pt.getLocation(world); - if (!loc.getChunk().isLoaded()) preventedUnloadedChunkChanges[0] = true; - else if (loc.distance(player.getLocation()) > maxDistanceToPlayer) preventedFarChanges[0] = true; - else { - transaction.addBlockChange(pt, world, blockMaterial); - } - } - - for (BlockLocation pt : fillPositions) { - Location loc = pt.getLocation(world); - if (!loc.getChunk().isLoaded()) preventedUnloadedChunkChanges[0] = true; - else if (loc.distance(player.getLocation()) > maxDistanceToPlayer) preventedFarChanges[0] = true; - else { - transaction.addBlockChange(pt, world, extendMaterial); - } - } + addNearbyBlockChanges(transaction, blockPositions, world, player, blockMaterial, + maxDistanceToPlayer, preventedUnloadedChunkChanges, preventedFarChanges); + addNearbyBlockChanges(transaction, fillPositions, world, player, extendMaterial, + maxDistanceToPlayer, preventedUnloadedChunkChanges, preventedFarChanges); if (transaction.size() == 0) { @@ -645,9 +623,9 @@ public boolean undoCommand(Player player) { private @NonNull Location getLocationFromCoordinates(double[] coordinates, double altitudeFromKML) { Location mcLocation = NavUtils.getLocationFromCoordinates(new GeographicalCoordinate(coordinates[0], coordinates[1])); //add altitude from kml (altitude from Google Earth is always relative to ground) - //note: the "-2" is only neccesary because + //note: the "-2" is only necessary because // getLocationFromCoordinates returns terrain altitude + 2 - // (one from Bukkits getHighestBlockY and one from our geoutils) + // (one from Bukkit getHighestBlockY and one from our geoutils) mcLocation.add(0, altitudeFromKML, 0); return mcLocation; } @@ -714,6 +692,37 @@ public boolean undoCommand(Player player) { return mcLines; } + private static Material resolvePlaceableMaterial(String materialName, Player player) { + Material matched = Material.matchMaterial(materialName); + if (matched != null && matched.isBlock()) { + return matched; + } + player.sendMessage(Messages.component(MessageKeys.Generator.Kml.INVALID_BLOCK_METADATA)); + return Material.BRICKS; + } + + private static void addNearbyBlockChanges( + ChangeTransaction transaction, + Collection positions, + World world, + Player player, + Material material, + float maxDistanceToPlayer, + boolean[] preventedUnloadedChunkChanges, + boolean[] preventedFarChanges + ) { + for (BlockLocation pt : positions) { + Location loc = pt.getLocation(world); + if (!loc.getChunk().isLoaded()) { + preventedUnloadedChunkChanges[0] = true; + } else if (loc.distance(player.getLocation()) > maxDistanceToPlayer) { + preventedFarChanges[0] = true; + } else { + transaction.addBlockChange(pt, world, material); + } + } + } + private static void recordKml(@NonNull String command) { UsageDetail detail = switch (command) { case GEOPOINTS_COMMAND -> UsageDetail.KML_GEOPOINTS; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteBlocksMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteBlocksMenu.java index 712b6704..bb31b0f3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteBlocksMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteBlocksMenu.java @@ -84,7 +84,7 @@ public ChoosePaletteBlocksMenu(BlockPaletteManager manager, ========================= */ public void open() { - logger.info("[ChoosePalleteBlocksMenu] open for " + getMenuPlayer().getName() + logger.info("[ChoosePaletteBlocksMenu] open for " + getMenuPlayer().getName() + " (preselected: " + selectedBlocks.size() + ")"); reloadMenuAsync(true); getMenu().open(getMenuPlayer()); @@ -130,7 +130,7 @@ protected List getSource() { if (seen.add(m)) out.add(new ItemStack(m)); } } catch (Exception t) { - logger.log(Level.SEVERE, "[ChoosePalleteBlocksMenu] supplier failed for " + type.name() + ": " + t.getMessage(), t); + logger.log(Level.SEVERE, "[ChoosePaletteBlocksMenu] supplier failed for " + type.name() + ": " + t.getMessage(), t); } } @@ -350,24 +350,10 @@ private void applyGlow(ItemMeta meta) { } private String pretty(String constant) { - String lower = constant.toLowerCase(Locale.ROOT).replace('_', ' '); - String[] parts = lower.split("\\s+"); - StringBuilder sb = new StringBuilder(); - for (String p : parts) { - if (p.isEmpty()) continue; - sb.append(Character.toUpperCase(p.charAt(0))) - .append(p.length() > 1 ? p.substring(1) : "") - .append(' '); - } - return sb.toString().trim(); + return PaletteTexts.toTitleCase(constant); } private Material safeMat(XMaterial xmat, Material fallback) { - try { - Material m = xmat.get(); - return (m != null) ? m : fallback; - } catch (Exception t) { - return fallback; - } + return PaletteTexts.safeMat(xmat, fallback); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java index bfc1bc88..3523827b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/ChoosePaletteMenu.java @@ -270,22 +270,13 @@ private List buildCustomPaletteLore(Player player, BlockPaletteManage private void logError(String ctx, Throwable t) { try { - plugin.getLogger().log(Level.SEVERE, "[ChoosePalleteMenu] " + ctx + " failed: " + t.getMessage(), t); + plugin.getLogger().log(Level.SEVERE, "[ChoosePaletteMenu] " + ctx + " failed: " + t.getMessage(), t); } catch (Exception ignore) { } } private String toTitleCase(String s) { - if (s == null || s.isEmpty()) return ""; - String[] parts = s.trim().toLowerCase(Locale.ROOT).split("\\s+"); - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (part.isEmpty()) continue; - sb.append(Character.toUpperCase(part.charAt(0))) - .append(part.length() > 1 ? part.substring(1) : "") - .append(' '); - } - return sb.toString().trim(); + return PaletteTexts.toTitleCase(s); } private String normalizeKey(String input) { @@ -296,11 +287,6 @@ private String normalizeKey(String input) { } private Material safeMat(XMaterial xmat, Material fallback) { - try { - Material m = xmat.get(); - return (m != null) ? m : fallback; - } catch (Exception t) { - return fallback; - } + return PaletteTexts.safeMat(xmat, fallback); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/CreatePaletteMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/CreatePaletteMenu.java index 33e42eb6..065c2354 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/CreatePaletteMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/CreatePaletteMenu.java @@ -2,7 +2,6 @@ import com.alpsbte.alpslib.utils.item.Item; import com.cryptomorin.xseries.XMaterial; -import io.papermc.paper.event.player.AsyncChatEvent; import lombok.Getter; import net.buildtheearth.buildteamtools.i18n.MessageKeys; import net.buildtheearth.buildteamtools.i18n.Messages; @@ -11,11 +10,8 @@ import net.buildtheearth.buildteamtools.utils.menus.AbstractMenu; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -27,7 +23,7 @@ import java.util.*; -public class CreatePaletteMenu extends AbstractMenu { +public class CreatePaletteMenu extends AbstractMenu implements PaletteChatInput.Session { private static final int NAME_SLOT = 11; private static final int DESCRIPTION_SLOT = 13; @@ -60,15 +56,11 @@ public class CreatePaletteMenu extends AbstractMenu { private String name = UNNAMED_SENTINEL; private String description = ""; private final List blocks = new ArrayList<>(); - private InputMode inputMode = InputMode.NONE; - private ChatInputListener chatListener; + private PaletteChatInput.Mode inputMode = PaletteChatInput.Mode.NONE; - // --- NEW: singleton listener per speler + debounce --- private static final Map ACTIVE_CHAT = new HashMap<>(); private boolean awaitingInput = false; - private enum InputMode {NONE, NAME, DESCRIPTION} - public CreatePaletteMenu(BlockPaletteManager manager, Player player, @NonNull JavaPlugin plugin) { super(3, Messages.component(MessageKeys.BlockPalette.CREATE_TITLE), player); this.manager = manager; @@ -92,7 +84,7 @@ protected Mask getMask() { @Override protected void setMenuItemsAsync() { - logger.info("Setting menu items for CreatePalleteMenu for player {}. Blocks: {}", player.getName(), blocks); + logger.info("Setting menu items for CreatePaletteMenu for player {}. Blocks: {}", player.getName(), blocks); getMenu().getSlot(NAME_SLOT).setItem(ItemComponents.of( Objects.requireNonNull(XMaterial.NAME_TAG.get()), @@ -126,11 +118,11 @@ protected void setItemClickEventsAsync() { // Name getMenu().getSlot(NAME_SLOT).setClickHandler((p, i) -> { if (i.getClickType() != ClickType.LEFT) return; - if (awaitingInput || inputMode != InputMode.NONE) return; + if (awaitingInput || inputMode != PaletteChatInput.Mode.NONE) return; awaitingInput = true; p.closeInventory(); - inputMode = InputMode.NAME; + inputMode = PaletteChatInput.Mode.NAME; registerChatListener(); Bukkit.getScheduler().runTask(plugin, () -> p.sendMessage(Messages.component(MessageKeys.BlockPalette.PROMPT_NAME, Component.text(MAX_NAME_LENGTH))) @@ -140,11 +132,11 @@ protected void setItemClickEventsAsync() { // Description getMenu().getSlot(DESCRIPTION_SLOT).setClickHandler((p, i) -> { if (i.getClickType() != ClickType.LEFT) return; - if (awaitingInput || inputMode != InputMode.NONE) return; + if (awaitingInput || inputMode != PaletteChatInput.Mode.NONE) return; awaitingInput = true; p.closeInventory(); - inputMode = InputMode.DESCRIPTION; + inputMode = PaletteChatInput.Mode.DESCRIPTION; registerChatListener(); Bukkit.getScheduler().runTask(plugin, () -> p.sendMessage(Messages.component(MessageKeys.BlockPalette.PROMPT_DESCRIPTION, Component.text(MAX_DESCRIPTION_LENGTH))) @@ -154,7 +146,7 @@ protected void setItemClickEventsAsync() { // Blocks getMenu().getSlot(BLOCKS_SLOT).setClickHandler((p, i) -> { p.closeInventory(); - logger.info("Opening ChoosePalleteBlocksMenu from CreatePalleteMenu for player {}. Current blocks: {}", p.getName(), blocks); + logger.info("Opening ChoosePaletteBlocksMenu from CreatePaletteMenu for player {}. Current blocks: {}", p.getName(), blocks); new ChoosePaletteBlocksMenu(manager, p, plugin, blocks, updatedBlocks -> { logger.info("Received updated blocks for player {}: {}", p.getName(), updatedBlocks); blocks.clear(); @@ -177,7 +169,7 @@ protected void setItemClickEventsAsync() { return; } unregisterChatListener(); - logger.info("Apply clicked in CreatePalleteMenu by {}. Name: {}, Blocks: {}", p.getName(), name, blocks); + logger.info("Apply clicked in CreatePaletteMenu by {}. Name: {}, Blocks: {}", p.getName(), name, blocks); if (name.equals(UNNAMED_SENTINEL)) { p.sendMessage(Messages.component(MessageKeys.BlockPalette.INVALID_NAME)); return; @@ -192,85 +184,66 @@ protected void setItemClickEventsAsync() { }); } - // --- NEW/REPLACED --- private void registerChatListener() { - Listener prev = ACTIVE_CHAT.remove(player.getUniqueId()); - if (prev != null) { - HandlerList.unregisterAll(prev); - } - chatListener = new ChatInputListener(); - ACTIVE_CHAT.put(player.getUniqueId(), chatListener); - plugin.getServer().getPluginManager().registerEvents(chatListener, plugin); + PaletteChatInput.register(ACTIVE_CHAT, this, MAX_NAME_LENGTH, MAX_DESCRIPTION_LENGTH); } - // --- NEW/REPLACED --- private void unregisterChatListener() { - Listener prev = ACTIVE_CHAT.remove(player.getUniqueId()); - if (prev != null) { - HandlerList.unregisterAll(prev); - } - chatListener = null; - inputMode = InputMode.NONE; - awaitingInput = false; // reset debounce + PaletteChatInput.unregister(ACTIVE_CHAT, this); } - private class ChatInputListener implements Listener { - @EventHandler - public void onPlayerChat(@NonNull AsyncChatEvent event) { - if (!event.getPlayer().getUniqueId().equals(player.getUniqueId())) return; - - if (inputMode == InputMode.NONE) return; - - event.setCancelled(true); - String message = PlainTextComponentSerializer.plainText().serialize(event.message()); - - if (message.equalsIgnoreCase("cancel")) { - if (inputMode == InputMode.NAME) name = UNNAMED_SENTINEL; - else if (inputMode == InputMode.DESCRIPTION) description = ""; - player.sendMessage(Messages.component(MessageKeys.BlockPalette.INPUT_CANCELLED)); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - return; - } + @Override + public Player player() { + return player; + } - if (inputMode == InputMode.NAME) { - if (message.length() > MAX_NAME_LENGTH) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_TOO_LONG, Component.text(MAX_NAME_LENGTH))); - } else if (message.isEmpty()) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_EMPTY)); - } else { - name = message; - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_SET, Component.text(name))); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - } - } else if (inputMode == InputMode.DESCRIPTION) { - if (message.length() > MAX_DESCRIPTION_LENGTH) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.DESCRIPTION_TOO_LONG, Component.text(MAX_DESCRIPTION_LENGTH))); - } else { - description = message.isEmpty() ? "" : message; - String descDisplay = description.isEmpty() - ? Messages.plain(player, MessageKeys.BlockPalette.NONE) - : description; - player.sendMessage(Messages.component(MessageKeys.BlockPalette.DESCRIPTION_SET, Component.text(descDisplay))); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - } - } + @Override + public JavaPlugin plugin() { + return plugin; + } + + @Override + public PaletteChatInput.Mode mode() { + return inputMode; + } + + @Override + public void setMode(PaletteChatInput.Mode mode) { + this.inputMode = mode; + } + + @Override + public void setAwaitingInput(boolean awaiting) { + this.awaitingInput = awaiting; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public void onCancel() { + if (inputMode == PaletteChatInput.Mode.NAME) { + name = UNNAMED_SENTINEL; + } else if (inputMode == PaletteChatInput.Mode.DESCRIPTION) { + description = ""; } } + @Override + public void reopenMenu() { + setMenuItemsAsync(); + getMenu().open(player); + } + public void open() { - logger.info("Opening CreatePalleteMenu for player {}. Current blocks: {}", player.getName(), blocks); + logger.info("Opening CreatePaletteMenu for player {}. Current blocks: {}", player.getName(), blocks); setMenuItemsAsync(); getMenu().open(player); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/EditPaletteMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/EditPaletteMenu.java index 496edf43..b958a601 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/EditPaletteMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/EditPaletteMenu.java @@ -2,18 +2,14 @@ import com.alpsbte.alpslib.utils.item.Item; import com.cryptomorin.xseries.XMaterial; -import io.papermc.paper.event.player.AsyncChatEvent; import net.buildtheearth.buildteamtools.i18n.MessageKeys; import net.buildtheearth.buildteamtools.i18n.Messages; import net.buildtheearth.buildteamtools.modules.network.model.Permissions; import net.buildtheearth.buildteamtools.utils.ItemComponents; import net.buildtheearth.buildteamtools.utils.menus.AbstractMenu; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -24,7 +20,7 @@ import java.util.*; -public class EditPaletteMenu extends AbstractMenu { +public class EditPaletteMenu extends AbstractMenu implements PaletteChatInput.Session { private static final int NAME_SLOT = 10; private static final int DESCRIPTION_SLOT = 12; @@ -55,16 +51,12 @@ public class EditPaletteMenu extends AbstractMenu { private String name; private String description; private final List blocks; - private InputMode inputMode = InputMode.NONE; - private ChatInputListener chatListener; + private PaletteChatInput.Mode inputMode = PaletteChatInput.Mode.NONE; private final java.util.logging.Logger logger; - // --- NEW: singleton listener per speler + debounce --- private static final Map ACTIVE_CHAT = new HashMap<>(); private boolean awaitingInput = false; - private enum InputMode {NONE, NAME, DESCRIPTION} - public EditPaletteMenu(BlockPaletteManager manager, Player player, @NonNull JavaPlugin plugin, String paletteKey, String name, String description, List blocks) { super(3, Messages.component(MessageKeys.BlockPalette.EDIT_TITLE, Component.text(name)), player); @@ -93,7 +85,7 @@ protected Mask getMask() { @Override protected void setMenuItemsAsync() { - logger.info("Setting menu items for EditPalleteMenu for player " + player.getName() + ". Blocks: " + blocks); + logger.info("Setting menu items for EditPaletteMenu for player " + player.getName() + ". Blocks: " + blocks); getMenu().getSlot(NAME_SLOT).setItem(ItemComponents.of( Objects.requireNonNull(XMaterial.NAME_TAG.get()), @@ -139,11 +131,11 @@ protected void setItemClickEventsAsync() { return; } if (i.getClickType() != ClickType.LEFT) return; - if (awaitingInput || inputMode != InputMode.NONE) return; + if (awaitingInput || inputMode != PaletteChatInput.Mode.NONE) return; awaitingInput = true; p.closeInventory(); - inputMode = InputMode.NAME; + inputMode = PaletteChatInput.Mode.NAME; registerChatListener(); Bukkit.getScheduler().runTask(plugin, () -> p.sendMessage(Messages.component(MessageKeys.BlockPalette.PROMPT_NAME_EDIT, Component.text(MAX_NAME_LENGTH))) @@ -157,11 +149,11 @@ protected void setItemClickEventsAsync() { return; } if (i.getClickType() != ClickType.LEFT) return; - if (awaitingInput || inputMode != InputMode.NONE) return; + if (awaitingInput || inputMode != PaletteChatInput.Mode.NONE) return; awaitingInput = true; p.closeInventory(); - inputMode = InputMode.DESCRIPTION; + inputMode = PaletteChatInput.Mode.DESCRIPTION; registerChatListener(); Bukkit.getScheduler().runTask(plugin, () -> p.sendMessage(Messages.component(MessageKeys.BlockPalette.PROMPT_DESCRIPTION_EDIT, Component.text(MAX_DESCRIPTION_LENGTH))) @@ -175,7 +167,7 @@ protected void setItemClickEventsAsync() { return; } p.closeInventory(); - logger.info("Opening ChoosePalleteBlocksMenu from EditPalleteMenu for player " + p.getName() + ". Current blocks: " + blocks); + logger.info("Opening ChoosePaletteBlocksMenu from EditPaletteMenu for player " + p.getName() + ". Current blocks: " + blocks); new ChoosePaletteBlocksMenu(manager, p, plugin, blocks, updatedBlocks -> { logger.info("Received updated blocks for player " + p.getName() + ": " + updatedBlocks); blocks.clear(); @@ -218,7 +210,7 @@ protected void setItemClickEventsAsync() { return; } unregisterChatListener(); - logger.info("Apply clicked in EditPalleteMenu by " + p.getName() + ". Name: " + name + ", Blocks: " + blocks); + logger.info("Apply clicked in EditPaletteMenu by " + p.getName() + ". Name: " + name + ", Blocks: " + blocks); if (name.isEmpty()) { p.sendMessage(Messages.component(MessageKeys.BlockPalette.INVALID_NAME)); return; @@ -232,83 +224,62 @@ protected void setItemClickEventsAsync() { }); } - // --- NEW/REPLACED --- private void registerChatListener() { - Listener prev = ACTIVE_CHAT.remove(player.getUniqueId()); - if (prev != null) { - HandlerList.unregisterAll(prev); - } - chatListener = new ChatInputListener(); - ACTIVE_CHAT.put(player.getUniqueId(), chatListener); - plugin.getServer().getPluginManager().registerEvents(chatListener, plugin); + PaletteChatInput.register(ACTIVE_CHAT, this, MAX_NAME_LENGTH, MAX_DESCRIPTION_LENGTH); } - // --- NEW/REPLACED --- private void unregisterChatListener() { - Listener prev = ACTIVE_CHAT.remove(player.getUniqueId()); - if (prev != null) { - HandlerList.unregisterAll(prev); - } - chatListener = null; - inputMode = InputMode.NONE; - awaitingInput = false; // reset debounce + PaletteChatInput.unregister(ACTIVE_CHAT, this); } - private class ChatInputListener implements Listener { - @EventHandler - public void onPlayerChat(@NonNull AsyncChatEvent event) { - if (!event.getPlayer().getUniqueId().equals(player.getUniqueId())) return; + @Override + public Player player() { + return player; + } - if (inputMode == InputMode.NONE) return; + @Override + public JavaPlugin plugin() { + return plugin; + } - event.setCancelled(true); - String message = PlainTextComponentSerializer.plainText().serialize(event.message()); + @Override + public PaletteChatInput.Mode mode() { + return inputMode; + } - if (message.equalsIgnoreCase("cancel")) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.INPUT_CANCELLED)); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - return; - } + @Override + public void setMode(PaletteChatInput.Mode mode) { + this.inputMode = mode; + } - if (inputMode == InputMode.NAME) { - if (message.length() > MAX_NAME_LENGTH) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_TOO_LONG, Component.text(MAX_NAME_LENGTH))); - } else if (message.isEmpty()) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_EMPTY)); - } else { - name = message; - player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_SET, Component.text(name))); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - } - } else if (inputMode == InputMode.DESCRIPTION) { - if (message.length() > MAX_DESCRIPTION_LENGTH) { - player.sendMessage(Messages.component(MessageKeys.BlockPalette.DESCRIPTION_TOO_LONG, Component.text(MAX_DESCRIPTION_LENGTH))); - } else { - description = message.isEmpty() ? "" : message; - String descDisplay = description.isEmpty() - ? Messages.plain(player, MessageKeys.BlockPalette.NONE) - : description; - player.sendMessage(Messages.component(MessageKeys.BlockPalette.DESCRIPTION_SET, Component.text(descDisplay))); - unregisterChatListener(); - Bukkit.getScheduler().runTask(plugin, () -> { - setMenuItemsAsync(); - getMenu().open(player); - }); - } - } - } + @Override + public void setAwaitingInput(boolean awaiting) { + this.awaitingInput = awaiting; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public void onCancel() { + // Keep existing name/description when editing is cancelled. + } + + @Override + public void reopenMenu() { + setMenuItemsAsync(); + getMenu().open(player); } public void open() { - logger.info("Opening EditPalleteMenu for player " + player.getName() + ". Current blocks: " + blocks); + logger.info("Opening EditPaletteMenu for player " + player.getName() + ". Current blocks: " + blocks); setMenuItemsAsync(); getMenu().open(player); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteChatInput.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteChatInput.java new file mode 100644 index 00000000..6bc7ab0d --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteChatInput.java @@ -0,0 +1,138 @@ +package net.buildtheearth.buildteamtools.modules.miscellaneous.blockpalettegui; + +import io.papermc.paper.event.player.AsyncChatEvent; +import net.buildtheearth.buildteamtools.i18n.MessageKeys; +import net.buildtheearth.buildteamtools.i18n.Messages; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; +import org.jspecify.annotations.NonNull; + +import java.util.Map; +import java.util.UUID; + +/** + * Shared chat-prompt handling for create/edit palette name and description fields. + */ +final class PaletteChatInput { + + public enum Mode {NONE, NAME, DESCRIPTION} + + interface Session { + Player player(); + + JavaPlugin plugin(); + + Mode mode(); + + void setMode(Mode mode); + + void setAwaitingInput(boolean awaiting); + + void setName(String name); + + void setDescription(String description); + + void onCancel(); + + void reopenMenu(); + } + + private PaletteChatInput() { + } + + static void register( + Map activeChat, + Session session, + int maxNameLength, + int maxDescriptionLength + ) { + Listener prev = activeChat.remove(session.player().getUniqueId()); + if (prev != null) { + HandlerList.unregisterAll(prev); + } + Listener listener = new ChatListener(session, maxNameLength, maxDescriptionLength, activeChat); + activeChat.put(session.player().getUniqueId(), listener); + session.plugin().getServer().getPluginManager().registerEvents(listener, session.plugin()); + } + + static void unregister(Map activeChat, Session session) { + Listener prev = activeChat.remove(session.player().getUniqueId()); + if (prev != null) { + HandlerList.unregisterAll(prev); + } + session.setMode(Mode.NONE); + session.setAwaitingInput(false); + } + + private record ChatListener( + Session session, + int maxNameLength, + int maxDescriptionLength, + Map activeChat + ) implements Listener { + + @EventHandler + public void onPlayerChat(@NonNull AsyncChatEvent event) { + if (!event.getPlayer().getUniqueId().equals(session.player().getUniqueId())) { + return; + } + if (session.mode() == Mode.NONE) { + return; + } + + event.setCancelled(true); + String message = PlainTextComponentSerializer.plainText().serialize(event.message()); + Player player = session.player(); + + if (message.equalsIgnoreCase("cancel")) { + session.onCancel(); + player.sendMessage(Messages.component(MessageKeys.BlockPalette.INPUT_CANCELLED)); + finish(); + return; + } + + if (session.mode() == Mode.NAME) { + if (message.length() > maxNameLength) { + player.sendMessage(Messages.component( + MessageKeys.BlockPalette.NAME_TOO_LONG, Component.text(maxNameLength))); + } else if (message.isEmpty()) { + player.sendMessage(Messages.component(MessageKeys.BlockPalette.NAME_EMPTY)); + } else { + session.setName(message); + player.sendMessage(Messages.component( + MessageKeys.BlockPalette.NAME_SET, Component.text(message))); + finish(); + } + return; + } + + if (session.mode() == Mode.DESCRIPTION) { + if (message.length() > maxDescriptionLength) { + player.sendMessage(Messages.component( + MessageKeys.BlockPalette.DESCRIPTION_TOO_LONG, + Component.text(maxDescriptionLength))); + return; + } + String description = message.isEmpty() ? "" : message; + session.setDescription(description); + String descDisplay = description.isEmpty() + ? Messages.plain(player, MessageKeys.BlockPalette.NONE) + : description; + player.sendMessage(Messages.component( + MessageKeys.BlockPalette.DESCRIPTION_SET, Component.text(descDisplay))); + finish(); + } + } + + private void finish() { + unregister(activeChat, session); + Bukkit.getScheduler().runTask(session.plugin(), session::reopenMenu); + } + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteTexts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteTexts.java new file mode 100644 index 00000000..3f442e07 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/blockpalettegui/PaletteTexts.java @@ -0,0 +1,41 @@ +package net.buildtheearth.buildteamtools.modules.miscellaneous.blockpalettegui; + +import com.cryptomorin.xseries.XMaterial; +import org.bukkit.Material; + +import java.util.Locale; + +/** + * Shared string/material helpers for block-palette menus. + */ +final class PaletteTexts { + + private PaletteTexts() { + } + + static String toTitleCase(String input) { + if (input == null || input.isEmpty()) { + return ""; + } + String[] parts = input.trim().toLowerCase(Locale.ROOT).replace('_', ' ').split("\\s+"); + StringBuilder sb = new StringBuilder(); + for (String part : parts) { + if (part.isEmpty()) { + continue; + } + sb.append(Character.toUpperCase(part.charAt(0))) + .append(part.length() > 1 ? part.substring(1) : "") + .append(' '); + } + return sb.toString().trim(); + } + + static Material safeMat(XMaterial xmat, Material fallback) { + try { + Material material = xmat.get(); + return material != null ? material : fallback; + } catch (Exception ignored) { + return fallback; + } + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/Warp.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/Warp.java index b725c7ce..45e075c2 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/Warp.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/Warp.java @@ -1,20 +1,16 @@ package net.buildtheearth.buildteamtools.modules.navigation.components.warps.model; -import com.alpsbte.alpslib.utils.item.Item; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import net.buildtheearth.buildteamtools.i18n.MessageKeys; import net.buildtheearth.buildteamtools.i18n.Messages; -import net.buildtheearth.buildteamtools.utils.ItemComponents; import net.buildtheearth.buildteamtools.utils.ListUtil; import net.buildtheearth.buildteamtools.utils.Utils; -import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; import net.buildtheearth.buildteamtools.utils.heads.LetterType; import net.buildtheearth.model.GeographicalCoordinate; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.json.JSONObject; @@ -109,36 +105,7 @@ public ItemStack getMaterialItem(Player player) { lore.addAll(ListUtil.grayComponents(Utils.splitStringByLineLength(address, 30, ", "))); } - if (material == null) - return HeadFactory.letter( - LetterType.STONE, - name.charAt(0), - itemName, - lore - ); - else if (material.startsWith("http://textures.minecraft.net/texture/")) - return ItemComponents.named( - Item.createCustomHeadTextureURL(material, " ", null), - itemName, - lore); - - Material matchedMaterial = Material.matchMaterial(this.material.split(":")[0]); - - if (matchedMaterial == null) - return HeadFactory.letter( - LetterType.STONE, - name.charAt(0), - itemName, - lore - ); - else if (!this.material.contains(":")) - return ItemComponents.of(matchedMaterial, itemName, lore); - else - return ItemComponents.named( - Item.create(matchedMaterial, " ", Short.parseShort(this.material.split(":")[1]), null), - itemName, - lore); - + return WarpMaterialItems.create(material, name, itemName, lore, LetterType.STONE); } public JSONObject toJSON() { @@ -197,4 +164,9 @@ public static AddressType fromValue(String value) { return AddressType.CITY; } } + + public static boolean isMinecraftTextureUrl(String value) { + return value.startsWith("https://textures.minecraft.net/texture/") + || value.startsWith("http://textures.minecraft.net/texture/"); + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpGroup.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpGroup.java index 87dbd57b..ef8344c3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpGroup.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpGroup.java @@ -1,18 +1,14 @@ package net.buildtheearth.buildteamtools.modules.navigation.components.warps.model; -import com.alpsbte.alpslib.utils.item.Item; import lombok.Getter; import lombok.Setter; import net.buildtheearth.buildteamtools.i18n.MessageKeys; import net.buildtheearth.buildteamtools.i18n.Messages; import net.buildtheearth.buildteamtools.modules.network.model.BuildTeam; -import net.buildtheearth.buildteamtools.utils.ItemComponents; import net.buildtheearth.buildteamtools.utils.ListUtil; -import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; import net.buildtheearth.buildteamtools.utils.heads.LetterType; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.json.JSONObject; @@ -94,36 +90,7 @@ public ItemStack getMaterialItem(Player player) { : Messages.render(player, MessageKeys.Warp.Menu.ITEM_NAME, Component.text(getName())); List lore = getDescriptionLore(player); - if (material == null) - return HeadFactory.letter( - LetterType.WOODEN, - name.charAt(0), - itemName, - lore - ); - else if (material.startsWith("http://textures.minecraft.net/texture/")) - return ItemComponents.named( - Item.createCustomHeadTextureURL(material, " ", null), - itemName, - lore); - - Material matchedMaterial = Material.matchMaterial(this.material.split(":")[0]); - - if (matchedMaterial == null) - return HeadFactory.letter( - LetterType.STONE, - name.charAt(0), - itemName, - lore - ); - else if (!this.material.contains(":")) - return ItemComponents.of(matchedMaterial, itemName, lore); - else - return ItemComponents.named( - Item.create(matchedMaterial, " ", Short.parseShort(this.material.split(":")[1]), null), - itemName, - lore); - + return WarpMaterialItems.create(material, name, itemName, lore, LetterType.WOODEN); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpMaterialItems.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpMaterialItems.java new file mode 100644 index 00000000..cce1f0c9 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/model/WarpMaterialItems.java @@ -0,0 +1,50 @@ +package net.buildtheearth.buildteamtools.modules.navigation.components.warps.model; + +import com.alpsbte.alpslib.utils.item.Item; +import net.buildtheearth.buildteamtools.utils.ItemComponents; +import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; +import net.buildtheearth.buildteamtools.utils.heads.LetterType; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +/** + * Shared material/icon resolution for {@link Warp} and {@link WarpGroup} menu items. + */ +final class WarpMaterialItems { + + private WarpMaterialItems() { + } + + static ItemStack create( + String material, + String name, + Component itemName, + List lore, + LetterType fallbackLetterType + ) { + if (material == null) { + return HeadFactory.letter(fallbackLetterType, name.charAt(0), itemName, lore); + } + if (Warp.isMinecraftTextureUrl(material)) { + return ItemComponents.named( + Item.createCustomHeadTextureURL(material, " ", null), + itemName, + lore); + } + + Material matchedMaterial = Material.matchMaterial(material.split(":")[0]); + if (matchedMaterial == null) { + return HeadFactory.letter(LetterType.STONE, name.charAt(0), itemName, lore); + } + if (!material.contains(":")) { + return ItemComponents.of(matchedMaterial, itemName, lore); + } + return ItemComponents.named( + Item.create(matchedMaterial, " ", Short.parseShort(material.split(":")[1]), null), + itemName, + lore); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java index 3b0e77b2..96531354 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/menu/StatsMenu.java @@ -55,17 +55,10 @@ public StatsMenu(Player p) { */ @Override protected void setPreviewItems() { - setupLores(); - - byte playerHeadSlot = 4; - byte teamHeadSlot = 20; - byte globalHeadSlot = 22; - byte achievementHeadSlot = 24; - - getMenu().getSlot(playerHeadSlot).setItem(PLAYER_HEAD); - getMenu().getSlot(teamHeadSlot).setItem(TEAM_HEAD); - getMenu().getSlot(globalHeadSlot).setItem(GLOBAL_HEAD); - getMenu().getSlot(achievementHeadSlot).setItem(ACHIEVEMENTS_HEAD); + getMenu().getSlot(4).setItem(playerHeadWithLore()); + getMenu().getSlot(20).setItem(teamHeadWithLore()); + getMenu().getSlot(22).setItem(ItemComponents.lore(GLOBAL_HEAD, ListUtil.grayComponents("", "", ""))); + getMenu().getSlot(24).setItem(ItemComponents.lore(ACHIEVEMENTS_HEAD, ListUtil.grayComponents("", "", ""))); super.setPreviewItems(); } @@ -102,54 +95,28 @@ protected Mask getMask() { .build(); } - /** - * Sets up the correct lores for all items - */ - private void setupLores() { - StatsModule statsModule = StatsModule.getInstance(); - - - // Set Player Head Lore - - StatsPlayer statsPlayer = statsModule.getStatsPlayer(getMenuPlayer().getUniqueId()); + private ItemStack playerHeadWithLore() { + StatsPlayer statsPlayer = StatsModule.getInstance().getStatsPlayer(getMenuPlayer().getUniqueId()); JSONObject playerStats = statsPlayer.toJSON(); List playerLore = new ArrayList<>(); - for (String key : playerStats.keySet()) { if ("UUID".equals(key)) { continue; } - - Object value = playerStats.get(key); - playerLore.add(Component.text(key + ": " + value)); + playerLore.add(Component.text(key + ": " + playerStats.get(key))); } - ItemComponents.lore(PLAYER_HEAD, playerLore); - - // Set Team Head Lore + return ItemComponents.lore(PLAYER_HEAD, playerLore); + } - StatsServer statsServer = statsModule.getStatsServer(); + private ItemStack teamHeadWithLore() { + StatsServer statsServer = StatsModule.getInstance().getStatsServer(); JSONObject serverStats = statsServer.toJSON(); List serverLore = new ArrayList<>(); - for (String key : serverStats.keySet()) { - Object value = serverStats.get(key); - serverLore.add(Component.text(key + ": " + value)); + serverLore.add(Component.text(key + ": " + serverStats.get(key))); } - - ItemComponents.lore(TEAM_HEAD, serverLore); - - // Set Global Head Lore - - //TODO: HOW TO GET GLOBAL STATS? - - ItemComponents.lore(GLOBAL_HEAD, ListUtil.grayComponents("", "", "")); - - // Set Achievements Head Lore - - //TODO WAIT UNTIL ACHIEVEMENTS HAVE MORE PROGRESS - - ItemComponents.lore(ACHIEVEMENTS_HEAD, ListUtil.grayComponents("", "", "")); + return ItemComponents.lore(TEAM_HEAD, serverLore); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java index 82a02ab4..2b2608eb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsPlayer.java @@ -1,6 +1,5 @@ package net.buildtheearth.buildteamtools.modules.stats.model; -import net.buildtheearth.buildteamtools.modules.stats.StatsModule; import org.json.JSONObject; import java.util.HashMap; @@ -17,36 +16,11 @@ public StatsPlayer(UUID uuid) { } public void addValue(StatsPlayerType statsPlayerType, Object value) { - Object object = stats.get(statsPlayerType); - - if (object == null) - object = value; - else if (object instanceof Integer && value instanceof Integer) { - if ((Integer) object > StatsModule.RATE_LIMIT) - return; - - object = (Integer) object + (Integer) value; - } else if (object instanceof Double && value instanceof Double) { - if ((Double) object > StatsModule.RATE_LIMIT) - return; - - object = (Double) object + (Double) value; - } else if (object instanceof Float && value instanceof Float) { - if ((Float) object > StatsModule.RATE_LIMIT) - return; - - object = (Float) object + (Float) value; - } else if (object instanceof Long && value instanceof Long) { - if ((Long) object > StatsModule.RATE_LIMIT) - return; - - object = (Long) object + (Long) value; - } else { - object = value; + Object updated = StatsValues.accumulate(stats.get(statsPlayerType), value); + if (updated == null) { + return; } - - stats.remove(statsPlayerType); - stats.put(statsPlayerType, object); + stats.put(statsPlayerType, updated); } public JSONObject toJSON() { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java index 0a8c3bcd..e000b916 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsServer.java @@ -1,6 +1,5 @@ package net.buildtheearth.buildteamtools.modules.stats.model; -import net.buildtheearth.buildteamtools.modules.stats.StatsModule; import org.json.JSONObject; import java.util.HashMap; @@ -14,36 +13,11 @@ public StatsServer() { } public void addValue(StatsServerType statsServerType, Object value) { - Object object = stats.get(statsServerType); - - if (object == null) - object = value; - else if (object instanceof Integer && value instanceof Integer) { - if ((Integer) object > StatsModule.RATE_LIMIT) - return; - - object = (Integer) object + (Integer) value; - } else if (object instanceof Double && value instanceof Double) { - if ((Double) object > StatsModule.RATE_LIMIT) - return; - - object = (Double) object + (Double) value; - } else if (object instanceof Float && value instanceof Float) { - if ((Float) object > StatsModule.RATE_LIMIT) - return; - - object = (Float) object + (Float) value; - } else if (object instanceof Long && value instanceof Long) { - if ((Long) object > StatsModule.RATE_LIMIT) - return; - - object = (Long) object + (Long) value; - } else { - object = value; + Object updated = StatsValues.accumulate(stats.get(statsServerType), value); + if (updated == null) { + return; } - - stats.remove(statsServerType); - stats.put(statsServerType, object); + stats.put(statsServerType, updated); } public JSONObject toJSON() { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsValues.java b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsValues.java new file mode 100644 index 00000000..519c2270 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/stats/model/StatsValues.java @@ -0,0 +1,32 @@ +package net.buildtheearth.buildteamtools.modules.stats.model; + +import net.buildtheearth.buildteamtools.modules.stats.StatsModule; + +/** + * Shared numeric accumulation for player/server stats maps. + */ +final class StatsValues { + + private StatsValues() { + } + + /** + * @return updated value, or {@code null} when the existing value is already above the rate limit + */ + static Object accumulate(Object existing, Object value) { + if (existing == null) { + return value; + } + return switch (existing) { + case Integer current when value instanceof Integer added -> + current > StatsModule.RATE_LIMIT ? null : current + added; + case Double current when value instanceof Double added -> + current > StatsModule.RATE_LIMIT ? null : current + added; + case Float current when value instanceof Float added -> + current > StatsModule.RATE_LIMIT ? null : current + added; + case Long current when value instanceof Long added -> + current > StatsModule.RATE_LIMIT ? null : current + added; + default -> value; + }; + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/AbstractPaginatedMenu.java b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/AbstractPaginatedMenu.java index 7fa9e2b0..4f31e838 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/AbstractPaginatedMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/AbstractPaginatedMenu.java @@ -3,6 +3,7 @@ import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.i18n.MessageKeys; import net.buildtheearth.buildteamtools.i18n.Messages; +import net.buildtheearth.buildteamtools.utils.MenuItems; import net.buildtheearth.buildteamtools.utils.heads.HeadColor; import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; import net.buildtheearth.buildteamtools.utils.heads.HeadTexture; @@ -205,6 +206,28 @@ protected void setSwitchPageItems(int switchPageItemSlot) { getMenu().getSlot(switchPageItemSlot + 1).setItem(getNextPageItem()); } + /** + * Shared chrome for selection menus: optional back item, page switcher, and proceed/next affordance. + */ + protected void setSelectionPreviewItems( + AbstractMenu backMenu, + int backItemSlot, + int switchPageItemSlot, + int nextItemSlot, + boolean canProceed + ) { + if (backMenu != null) { + setBackItem(backItemSlot, backMenu); + } + setSwitchPageItems(switchPageItemSlot); + if (canProceed) { + getMenu().getSlot(nextItemSlot).setItem(HeadFactory.head(HeadTexture.CHECKMARK, + Messages.render(getMenuPlayer(), MessageKeys.Ui.NEXT))); + } else { + getMenu().getSlot(nextItemSlot).setItem(MenuItems.ITEM_BACKGROUND); + } + } + protected void setSwitchPageItemClickEvents(int switchPageItemSlot) { // Set click event for previous page item getMenu().getSlot(switchPageItemSlot - 1).setClickHandler((clickPlayer, clickInformation) -> { diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/BlockListMenu.java b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/BlockListMenu.java index 0e645ef9..b2911efb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/BlockListMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/BlockListMenu.java @@ -1,11 +1,7 @@ package net.buildtheearth.buildteamtools.utils.menus; import com.alpsbte.alpslib.utils.item.Item; -import net.buildtheearth.buildteamtools.i18n.MessageKeys; -import net.buildtheearth.buildteamtools.i18n.Messages; import net.buildtheearth.buildteamtools.utils.MenuItems; -import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; -import net.buildtheearth.buildteamtools.utils.heads.HeadTexture; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -41,17 +37,7 @@ public BlockListMenu(Player player, String invName, List items, Abstr @Override protected void setPreviewItems() { - if (backMenu != null) - setBackItem(BACK_ITEM_SLOT, backMenu); - - setSwitchPageItems(SWITCH_PAGE_ITEM_SLOT); - - if (canProceed()) - getMenu().getSlot(NEXT_ITEM_SLOT).setItem(HeadFactory.head(HeadTexture.CHECKMARK, - Messages.render(getMenuPlayer(), MessageKeys.Ui.NEXT))); - else - getMenu().getSlot(NEXT_ITEM_SLOT).setItem(MenuItems.ITEM_BACKGROUND); - + setSelectionPreviewItems(backMenu, BACK_ITEM_SLOT, SWITCH_PAGE_ITEM_SLOT, NEXT_ITEM_SLOT, canProceed()); super.setPreviewItems(); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java index eff0ef1a..3e4a3d29 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/menus/NameListMenu.java @@ -1,11 +1,7 @@ package net.buildtheearth.buildteamtools.utils.menus; import com.alpsbte.alpslib.utils.item.Item; -import net.buildtheearth.buildteamtools.i18n.MessageKeys; -import net.buildtheearth.buildteamtools.i18n.Messages; import net.buildtheearth.buildteamtools.utils.MenuItems; -import net.buildtheearth.buildteamtools.utils.heads.HeadFactory; -import net.buildtheearth.buildteamtools.utils.heads.HeadTexture; import org.apache.commons.lang3.tuple.MutablePair; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -46,17 +42,7 @@ public NameListMenu(Player player, String invName, List Date: Sat, 31 Oct 2026 22:44:03 +0100 Subject: [PATCH 06/10] refactor: convert Region and QueuedRequest to records; extract field direction helper Use records for immutable holders and pull the yaw-to-schematic direction choice out of FieldScripts. --- .../components/field/FieldScripts.java | 43 +++++---- .../network/api/AsyncRequestQueue.java | 28 ++---- .../modules/network/model/Region.java | 91 ++++++++++--------- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/field/FieldScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/field/FieldScripts.java index f7e03999..2128ef5a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/field/FieldScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/field/FieldScripts.java @@ -98,23 +98,7 @@ public void fieldScript_v_1_0() { for (int i = 0; i < schematicAmount; i++) availableDirections[i] = Short.parseShort(schematics[i].getName().replace(".schematic", "")); - // Calculate which direction should be used - short directionToUse = availableDirections[0]; // Assume the first element is the closest initially - short minDifference = Short.MAX_VALUE; // Initial difference - - for (short current : availableDirections) { - short difference1 = (short) Math.abs(yaw - current); - if (difference1 < minDifference) { - minDifference = difference1; - directionToUse = current; - } - - short difference2 = (short) Math.abs(yaw - (current + 180)); - if (difference2 < minDifference) { - minDifference = difference2; - directionToUse = current; - } - } + short directionToUse = closestDirection(availableDirections, yaw); setGmask("<0"); String path = @@ -320,4 +304,29 @@ public void fieldScript_v_1_0() { finish(blocks, points); */ } + + /** + * Picks the schematic line direction closest to the player's yaw + * (also considering the opposite 180° orientation). + */ + private static short closestDirection(short[] availableDirections, float yaw) { + short directionToUse = availableDirections[0]; + short minDifference = Short.MAX_VALUE; + + for (short current : availableDirections) { + short difference1 = (short) Math.abs(yaw - current); + if (difference1 < minDifference) { + minDifference = difference1; + directionToUse = current; + } + + short difference2 = (short) Math.abs(yaw - (current + 180)); + if (difference2 < minDifference) { + minDifference = difference2; + directionToUse = current; + } + } + + return directionToUse; + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueue.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueue.java index f55afa5e..18d52592 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueue.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueue.java @@ -146,7 +146,6 @@ public AsyncRequestQueue( /** * Schedules the next dispatch using the earliest allowed start time. - * * Must be called while holding this instance's monitor. */ private void scheduleDispatchLocked() { @@ -229,7 +228,7 @@ private void dispatchNext() { private void execute(QueuedRequest request) { try { CompletableFuture operationFuture = - request.operation.get(); + request.operation().get(); if (operationFuture == null) { throw new NullPointerException( @@ -239,19 +238,18 @@ private void execute(QueuedRequest request) { operationFuture.whenComplete((value, error) -> { if (error != null) { - request.result.completeExceptionally(error); + request.result().completeExceptionally(error); } else { - request.result.complete(value); + request.result().complete(value); } }); } catch (Exception e) { - request.result.completeExceptionally(e); + request.result().completeExceptionally(e); } } /** * Removes all waiting requests. - * * Must be called while holding this instance's monitor. * * @return the removed requests @@ -279,7 +277,7 @@ private void completeRequestsExceptionally( RejectedExecutionException exception ) { for (QueuedRequest request : requests) { - request.result.completeExceptionally(exception); + request.result().completeExceptionally(exception); } } @@ -322,17 +320,9 @@ public void close() { * * @param operation result type */ - private static final class QueuedRequest { - - private final Supplier> operation; - private final CompletableFuture result; - - private QueuedRequest( - Supplier> operation, - CompletableFuture result - ) { - this.operation = operation; - this.result = result; - } + private record QueuedRequest( + Supplier> operation, + CompletableFuture result + ) { } } 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 f489e1cf..20f64fff 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,27 +1,26 @@ package net.buildtheearth.buildteamtools.modules.network.model; -import lombok.Getter; import org.jspecify.annotations.Nullable; -public class Region { - - @Getter - private final Continent continent; - @Getter - private final BuildTeam buildTeam; - @Getter - private final String name; - @Getter - private final String countryCodeCca3; - @Getter - private final String countryCodeCca2; - @Getter - private final RegionType type; - @Getter - private final String headBase64; - @Getter - private final int area; +public record Region( + Continent continent, + BuildTeam buildTeam, + String name, + String countryCodeCca3, + String countryCodeCca2, + RegionType type, + String headBase64, + int area +) { + public Region { + if (type == RegionType.COUNTRY) { + if (countryCodeCca2 == null || countryCodeCca2.length() != 2) + throw new IllegalArgumentException("Country code must be in ISO 3166-1 alpha-2 format!"); + if (countryCodeCca3 == null || countryCodeCca3.length() != 3) + throw new IllegalArgumentException("Country code must be in ISO 3166-1 alpha-3 format!"); + } + } /** * Constructor for any region type @@ -33,15 +32,7 @@ public class Region { * @param headBase64 The base64 string of the head of the region */ public Region(String name, RegionType regionType, Continent continent, BuildTeam buildTeam, String headBase64) { - this.continent = continent; - this.name = name; - this.type = regionType; - this.buildTeam = buildTeam; - this.headBase64 = headBase64; - - this.area = 0; - this.countryCodeCca2 = null; - this.countryCodeCca3 = null; + this(continent, buildTeam, name, null, null, regionType, headBase64, 0); } /** @@ -57,20 +48,39 @@ public Region(String name, RegionType regionType, Continent continent, BuildTeam */ public Region(String name, Continent continent, BuildTeam buildTeam, String headBase64, int area, String countryCodeCca2, String countryCodeCca3) { - if (countryCodeCca2.length() != 2) - throw new IllegalArgumentException("Country code must be in ISO 3166-1 alpha-2 format!"); - if (countryCodeCca3.length() != 3) - throw new IllegalArgumentException("Country code must be in ISO 3166-1 alpha-3 format!"); + this(continent, buildTeam, name, countryCodeCca3, countryCodeCca2, RegionType.COUNTRY, headBase64, area); + } - this.type = RegionType.COUNTRY; + public Continent getContinent() { + return continent; + } + + public BuildTeam getBuildTeam() { + return buildTeam; + } + + public String getName() { + return name; + } - this.continent = continent; - this.name = name; - this.buildTeam = buildTeam; - this.headBase64 = headBase64; - this.area = area; - this.countryCodeCca2 = countryCodeCca2; - this.countryCodeCca3 = countryCodeCca3; + public String getCountryCodeCca3() { + return countryCodeCca3; + } + + public String getCountryCodeCca2() { + return countryCodeCca2; + } + + public RegionType getType() { + return type; + } + + public String getHeadBase64() { + return headBase64; + } + + public int getArea() { + return area; } public static @Nullable Region getByName(String name) { @@ -82,4 +92,3 @@ public Region(String name, Continent continent, BuildTeam buildTeam, String head return null; } } - From 92b52b5e2516722b8bf425c210182aee401bd0a4 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:44:05 +0100 Subject: [PATCH 07/10] fix: prevent zip-slip when extracting GeneratorCollections Normalize zip entry paths and reject extractions that escape the destination directory. --- .../modules/generator/model/GeneratorCollections.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorCollections.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorCollections.java index b75f96c1..398ad0e1 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorCollections.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorCollections.java @@ -185,11 +185,15 @@ private static boolean unzip(Path zipFilePath, @NonNull Path destDirectory) { return false; } + Path destRoot = destDirectory.toAbsolutePath().normalize(); try (ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(zipFilePath))) { ZipEntry entry = zipIn.getNextEntry(); while (entry != null) { - var filePath = destDirectory.resolve(entry.getName()); + Path filePath = destRoot.resolve(entry.getName()).normalize(); + if (!filePath.startsWith(destRoot)) { + throw new IOException("Zip entry is outside of the target directory: " + entry.getName()); + } if (!entry.isDirectory()) { File parentDir = filePath.getParent().toFile(); From d15f754ed73ccd182f3b3e4a46e0ca77099a18b2 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 22:44:14 +0100 Subject: [PATCH 08/10] chore: apply remaining inspection cleanups Fix typos, prefer positive boolean helpers, tighten finals, drop dead stubs, and modernize MenuItems switches from the inspection pass. --- .../common/listeners/ExceptionListener.java | 4 - .../components/house/HouseScripts.java | 4 +- .../components/kml/ChangeTransaction.java | 18 +- .../generator/components/kml/KmlParser.java | 34 +-- .../rail/configuration/RailType.java | 22 +- .../rail/generation/RailLanePathBuilder.java | 2 +- .../generation/RailPreparationProgress.java | 16 +- .../rail/generation/RailScripts.java | 34 +-- .../rail/generation/RailTerrainResolver.java | 8 +- .../components/road/RoadSettings.java | 2 +- .../modules/generator/model/Command.java | 6 +- .../modules/generator/model/Script.java | 1 - .../signtextgenerator/SignTextGenerator.java | 5 - .../modules/navigation/NavUtils.java | 4 +- .../warps/menu/MaterialSelectionMenu.java | 5 +- .../components/warps/menu/WarpEditMenu.java | 8 +- .../buildteamtools/utils/BlockLocation.java | 2 +- .../buildteamtools/utils/MenuItems.java | 218 ++++++------------ .../i18n/messages_en_US.properties | 2 +- .../commands/BttCommandManagerTest.java | 12 +- .../network/api/AsyncRequestQueueTest.java | 4 +- 21 files changed, 168 insertions(+), 243 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/common/listeners/ExceptionListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/common/listeners/ExceptionListener.java index a8d2f5c4..2b070f0a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/common/listeners/ExceptionListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/common/listeners/ExceptionListener.java @@ -4,8 +4,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import java.util.Arrays; - public class ExceptionListener implements Listener { public static boolean limiter = false; @@ -14,8 +12,6 @@ public class ExceptionListener implements Listener { public void onException(ServerExceptionEvent e) { if (limiter) return; - String string = Arrays.toString(e.getException().getStackTrace()); - //TODO implement Build Team Exception Handling limiter = true; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/HouseScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/HouseScripts.java index 0d937678..358e6c72 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/HouseScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/HouseScripts.java @@ -79,7 +79,7 @@ public void buildscript_v_1_2() { // Set pos1 and pos2 List outlinePoints = new ArrayList<>(selectionPoints); - outlinePoints.get(0).setY(highestBlock); + outlinePoints.getFirst().setY(highestBlock); for (Vector point : outlinePoints) point.setY(minY); createSelection(outlinePoints); @@ -175,7 +175,7 @@ public void buildscript_v_1_2() { // Set pos1 and pos2 outlinePoints = new ArrayList<>(selectionPoints); - outlinePoints.get(0).setY(maxY); + outlinePoints.getFirst().setY(maxY); for (Vector point : outlinePoints) point.setY(minY); createSelection(outlinePoints); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/ChangeTransaction.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/ChangeTransaction.java index 0bbdb420..9d2c8945 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/ChangeTransaction.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/ChangeTransaction.java @@ -29,9 +29,9 @@ public BlockModification(Block block, Material newType) { this.newType = newType; } - public Block block; - public Material previousType; - public Material newType; + public final Block block; + public final Material previousType; + public final Material newType; } //------------------------------------ @@ -39,7 +39,7 @@ public BlockModification(Block block, Material newType) { public ChangeTransaction(Player p) { this.player = p; changes = new ArrayList<>(); - blockPositions = new HashSet(); + blockPositions = new HashSet<>(); } @@ -49,7 +49,7 @@ public ChangeTransaction(Player p) { * * @param location the location to change * @param newType the new material / block type for the location - * @return boolean: indicate if the change was accepted or not (i.e. wether this already had a change for the location or not) + * @return boolean: indicate if the change was accepted or not (i.e. whether this already had a change for the location or not) */ public boolean addBlockChange(Location location, Material newType) { return addBlockChange(new BlockLocation(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getWorld(), newType); @@ -63,7 +63,7 @@ public boolean addBlockChange(Location location, Material newType) { * @param world the world for the change * @param location the location to change * @param newType the new material / block type for the location - * @return boolean: indicate if the change was accepted or not (i.e. wether this already had a change for the location or not) + * @return boolean: indicate if the change was accepted or not (i.e. whether this already had a change for the location or not) */ public boolean addBlockChange(BlockLocation location, World world, Material newType) { //check if we already have a change for this location. @@ -97,9 +97,9 @@ public int size() { return changes.size(); } - public Player player; - List changes; - Set blockPositions; //check to prevent duplicates + public final Player player; + final List changes; + final Set blockPositions; //check to prevent duplicates } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlParser.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlParser.java index 6449ff7d..b3540a36 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlParser.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlParser.java @@ -64,7 +64,7 @@ public List> extractCoordinates(String kmlString) { * Note: Each placemark can have multiple linestrings when using the type "MultiGeometry". * * @param kmlString KML content - * @return List: A list of LineString objects, which themselves are lists of geocoordiantes. + * @return List: A list of LineString objects, which themselves are lists of geocoordinates. */ public List extractLinestrings(String kmlString) { List linestrings = new ArrayList<>(); @@ -138,7 +138,7 @@ else if (feature instanceof Document) { * A placemark can have multiple linestrings when using the type "MultiGeometry". * * @param placemark the placemark to search - * @return List: A list of LineString objects, which themselves are lists of geocoordiantes. + * @return List: A list of LineString objects, which themselves are lists of geocoordinates. */ private List findLineStrings(Placemark placemark) { List lines = new ArrayList<>(); @@ -186,20 +186,20 @@ private List> getCoordinates(Geometry geom) { } private List getGeometryCoordinatesNoMulti(Geometry geom) { - if (geom instanceof LineString) { - return ((LineString) geom).getCoordinates(); - } else if (geom instanceof LinearRing) { - return ((LinearRing) geom).getCoordinates(); - } else if (geom instanceof Polygon) { - return ((Polygon) geom).getOuterBoundaryIs().getLinearRing().getCoordinates(); - } else if (geom instanceof MultiGeometry) { - this.player.sendMessage(Messages.component(MessageKeys.Generator.Kml.NESTED_MULTIGEOMETRY)); - } else { - this.player.sendMessage(Messages.component( - MessageKeys.Generator.Kml.UNSUPPORTED_GEOMETRY, - Component.text(geom.getClass().getName()))); - } - - return new ArrayList(); + return switch (geom) { + case LineString lineString -> lineString.getCoordinates(); + case LinearRing linearRing -> linearRing.getCoordinates(); + case Polygon polygon -> polygon.getOuterBoundaryIs().getLinearRing().getCoordinates(); + case MultiGeometry ignored -> { + this.player.sendMessage(Messages.component(MessageKeys.Generator.Kml.NESTED_MULTIGEOMETRY)); + yield new ArrayList<>(); + } + default -> { + this.player.sendMessage(Messages.component( + MessageKeys.Generator.Kml.UNSUPPORTED_GEOMETRY, + Component.text(geom.getClass().getName()))); + yield new ArrayList<>(); + } + }; } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/configuration/RailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/configuration/RailType.java index ed03ad0d..b68bb189 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/configuration/RailType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/configuration/RailType.java @@ -202,7 +202,7 @@ static RailType createStandard() { } private static @Nullable String validateBlocks(Configuration configuration) { - if (!isPlaceablePalette(configuration.railBlocks())) + if (isMissingPlaceablePalette(configuration.railBlocks())) return "Rail block must be a valid placeable Minecraft block."; List blocksBelow = configuration.blocksBelow(); @@ -211,7 +211,7 @@ static RailType createStandard() { return "Rail type needs at least one block below the rails."; for (XMaterial blockBelow : blocksBelow) - if (!isPlaceableBlock(blockBelow)) + if (isNonPlaceableBlock(blockBelow)) return "Blocks below the rails must be valid placeable Minecraft blocks."; return null; @@ -225,7 +225,7 @@ static RailType createStandard() { .formatted(MIN_SLEEPER_SPACING, MAX_SLEEPER_SPACING); if ((sleeperSpacing > 0 || !configuration.sleeperBlocks().isEmpty()) - && !isPlaceablePalette(configuration.sleeperBlocks())) + && isMissingPlaceablePalette(configuration.sleeperBlocks())) return "Sleeper block must be a valid placeable Minecraft block."; return null; @@ -274,7 +274,7 @@ private static List normalizeTrackSpacings(@Nullable List conf return poleError; if ((configuration.overheadWiresEnabled() || !configuration.overheadWireBlocks().isEmpty()) - && !isPlaceablePalette(configuration.overheadWireBlocks())) + && isMissingPlaceablePalette(configuration.overheadWireBlocks())) return "Overhead wire block must be a valid placeable Minecraft block."; return null; @@ -282,11 +282,11 @@ private static List normalizeTrackSpacings(@Nullable List conf private static @Nullable String validateOverheadPoles(Configuration configuration) { if ((configuration.overheadPolesEnabled() || !configuration.overheadPoleBlocks().isEmpty()) - && !isPlaceablePalette(configuration.overheadPoleBlocks())) + && isMissingPlaceablePalette(configuration.overheadPoleBlocks())) return "Overhead pole block must be a valid placeable Minecraft block."; if ((configuration.overheadPolesEnabled() || !configuration.overheadSupportBlocks().isEmpty()) - && !isPlaceablePalette(configuration.overheadSupportBlocks())) + && isMissingPlaceablePalette(configuration.overheadSupportBlocks())) return "Overhead support block must be a valid placeable Minecraft block."; return configuration.overheadPolesEnabled() ? validateOverheadPoleMeasurements(configuration) : null; @@ -318,16 +318,16 @@ private static boolean hasInvalidIdentifier(@Nullable String identifier) { || !identifier.toLowerCase().matches(IDENTIFIER_PATTERN); } - private static boolean isPlaceablePalette(List materials) { - return !materials.isEmpty() && materials.stream().allMatch(RailType::isPlaceableBlock); + private static boolean isMissingPlaceablePalette(List materials) { + return materials.isEmpty() || materials.stream().anyMatch(RailType::isNonPlaceableBlock); } - private static boolean isPlaceableBlock(@Nullable XMaterial material) { + private static boolean isNonPlaceableBlock(@Nullable XMaterial material) { if (material == null) - return false; + return true; Material bukkitMaterial = material.get(); - return bukkitMaterial != null && bukkitMaterial.isBlock(); + return bukkitMaterial == null || !bukkitMaterial.isBlock(); } public static @Nullable RailType byString(String value) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailLanePathBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailLanePathBuilder.java index 72c05da5..9ed2ec9c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailLanePathBuilder.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailLanePathBuilder.java @@ -152,7 +152,7 @@ private void addIfDifferentFromPrevious(List points, Vector point) { return; } - Vector previousPoint = points.get(points.size() - 1); + Vector previousPoint = points.getLast(); if (previousPoint.getBlockX() == point.getBlockX() && previousPoint.getBlockZ() == point.getBlockZ()) return; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailPreparationProgress.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailPreparationProgress.java index 39d431dd..49091aaf 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailPreparationProgress.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailPreparationProgress.java @@ -31,7 +31,7 @@ final class RailPreparationProgress implements Runnable { } void start() { - if (!canContinue()) + if (shouldStop()) return; if (task.get() != null) @@ -58,7 +58,7 @@ void stop() { } void startStage(long startPercentage, long endPercentage, long estimatedDurationMillis) { - if (!canContinue()) + if (shouldStop()) return; long clampedStartPercentage = clamp(startPercentage); @@ -74,7 +74,7 @@ void completeStage(long percentage) { } void update(long percentage) { - if (!canContinue()) + if (shouldStop()) return; long clampedPercentage = clamp(percentage); @@ -100,7 +100,7 @@ long scale(int completed, int total, long startPercentage, long endPercentage) { @Override public void run() { - if (!canContinue()) { + if (shouldStop()) { stop(); return; } @@ -125,9 +125,9 @@ private long clamp(long percentage) { return Math.clamp(percentage, 0L, maxPercentage); } - private boolean canContinue() { - return BuildTeamTools.getInstance().isEnabled() - && player != null - && player.isOnline(); + private boolean shouldStop() { + return !BuildTeamTools.getInstance().isEnabled() + || player == null + || !player.isOnline(); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailScripts.java index 2bdc0ea7..86ec69ed 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailScripts.java @@ -84,7 +84,7 @@ public RailScripts(Player player, GeneratorComponent generatorComponent, Runnabl boolean queuedGeneration = false; try { - if (!canContinue()) return; + if (shouldStop()) return; if (!prepareSession()) return; queuedGeneration = queueRailGeneration(); @@ -103,7 +103,7 @@ public RailScripts(Player player, GeneratorComponent generatorComponent, Runnabl } private boolean prepareSession() { - if (!canContinue()) return false; + if (shouldStop()) return false; railType = getRailType(); @@ -120,7 +120,7 @@ private boolean prepareSession() { centerPath = createCenterPath(controlPoints); preparationProgress.completeStage(PATH_PROGRESS); - if (!hasValidCenterPath()) return false; + if (isInvalidCenterPath()) return false; preparationProgress.startStage(PATH_PROGRESS, SAFETY_CHECK_PROGRESS, SAFETY_CHECK_ESTIMATED_MILLIS); @@ -159,7 +159,7 @@ private boolean prepareSession() { terrainResolver = new RailTerrainResolver(blocks); preparationProgress.completeStage(TERRAIN_PREPARE_PROGRESS); - if (!canContinue()) return false; + if (shouldStop()) return false; preparationProgress.startStage(TERRAIN_PREPARE_PROGRESS, TERRAIN_ADJUST_PROGRESS, TERRAIN_ADJUST_ESTIMATED_MILLIS); snapMissingControlPointHeightsToTerrain(controlPoints); @@ -171,9 +171,9 @@ private boolean prepareSession() { } private boolean queueRailGeneration() { - if (!canContinue()) return false; + if (shouldStop()) return false; - if (!hasValidCenterPath()) + if (isInvalidCenterPath()) return false; preparationProgress.startStage(TERRAIN_ADJUST_PROGRESS, RAIL_BLOCK_BUILD_PROGRESS, RAIL_BLOCK_BUILD_ESTIMATED_MILLIS); @@ -292,10 +292,10 @@ private boolean hasValidControlPoints() { return true; } - private boolean hasValidCenterPath() { + private boolean isInvalidCenterPath() { if (centerPath.size() < 2) { sendRailError(MessageKeys.Generator.Rail.INVALID_PATH); - return false; + return true; } if (centerPath.size() > limits.maxPathPoints()) { @@ -304,10 +304,10 @@ private boolean hasValidCenterPath() { Component.text(String.valueOf(centerPath.size())), Component.text(String.valueOf(limits.maxPathPoints())) ); - return false; + return true; } - return true; + return false; } private boolean hasSafePreparedSelection(List selectionPoints, int minY, int maxY) { @@ -351,13 +351,13 @@ private boolean hasSafePreparedSelection(List selectionPoints, int minY, } private void sendRailInfo(String key, ComponentLike... args) { - if (!canContinue()) return; + if (shouldStop()) return; getPlayer().sendMessage(Messages.component(key, args)); } private void sendRailError(String key, ComponentLike... args) { - if (!canContinue()) return; + if (shouldStop()) return; getPlayer().sendMessage(Messages.component(key, args)); } @@ -374,10 +374,10 @@ private void runOnMainThread(Runnable runnable) { Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), runnable); } - private boolean canContinue() { - return BuildTeamTools.getInstance().isEnabled() - && getPlayer() != null - && getPlayer().isOnline(); + private boolean shouldStop() { + return !BuildTeamTools.getInstance().isEnabled() + || getPlayer() == null + || !getPlayer().isOnline(); } private List getControlPoints() { @@ -499,7 +499,7 @@ private int getRailReferenceY(List points) { if (points.isEmpty()) return getPlayer().getWorld().getMinHeight(); - if (!RailTerrainResolver.hasAnyMissingHeights(points)) + if (RailTerrainResolver.hasAllHeights(points)) return points.getFirst().getBlockY(); int referenceY = getRegion() != null ? getRegion().getMinimumY() : GeneratorUtils.getMinHeight(points); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailTerrainResolver.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailTerrainResolver.java index 4b55ff9d..e4cf7d73 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailTerrainResolver.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/generation/RailTerrainResolver.java @@ -24,7 +24,7 @@ final class RailTerrainResolver { } void snapMissingHeightsToTerrain(List points, int referenceY) { - if (!hasAnyMissingHeights(points)) + if (hasAllHeights(points)) return; for (Vector point : points) @@ -42,11 +42,11 @@ int getNearestRailSurfaceY(int x, int z, int fallbackY) { return railYCache.computeIfAbsent(cacheKey, ignored -> findNearestRailSurfaceY(x, z, fallbackY)); } - static boolean hasAnyMissingHeights(List points) { + static boolean hasAllHeights(List points) { for (Vector point : points) - if (point.getBlockY() == 0) return true; + if (point.getBlockY() == 0) return false; - return false; + return true; } private void indexPreparedBlocks(Block[][][] blocks) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/road/RoadSettings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/road/RoadSettings.java index 37104ad6..ac732f10 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/road/RoadSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/road/RoadSettings.java @@ -11,7 +11,7 @@ public class RoadSettings extends Settings { - public static List streetLampTypes = new ArrayList<>(); + public static final List streetLampTypes = new ArrayList<>(); public RoadSettings(Player player) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index 663fcd63..06c19e59 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -122,7 +122,7 @@ public void tick() { break; } - Operation command = operations.get(0); + Operation command = operations.getFirst(); processOperation(command); if (breakPointActive || threadActive || command.getProgressWeight() > 1L) @@ -151,7 +151,7 @@ public void processOperation(Operation operation) { try { switch (operation.getOperationType()) { case COMMAND: - String command = (String) operation.getValues().get(0); + String command = (String) operation.getValues().getFirst(); if (command.contains("%%XYZ/")) command = convertXYZ(command); @@ -346,7 +346,7 @@ private long calculateProgressPercentage() { ); long maxVisibleProgress = Math.max(progressStartPercentage, progressEndPercentage - 1L); - return Math.max(progressStartPercentage, Math.min(scaledProgress, maxVisibleProgress)); + return Math.clamp(scaledProgress, progressStartPercentage, maxVisibleProgress); } private void completeOperation(Operation operation) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index 841f1c99..128681e0 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -123,7 +123,6 @@ public void createCommand(String command) { /** * Adds a command to the operation list and counts it as one undoable change. - * * Use this for generator commands that actually modify blocks, such as //line, * //set or //replace. Do not use this for pure selection commands. * diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/signtextgenerator/SignTextGenerator.java b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/signtextgenerator/SignTextGenerator.java index 65a30e27..7124d538 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/signtextgenerator/SignTextGenerator.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/miscellaneous/signtextgenerator/SignTextGenerator.java @@ -10,11 +10,6 @@ public SignTextGenerator() { super(COMPONENT_NAME); } - @Override - public void enable() { - super.enable(); - } - @Override public void disable() { if (!isEnabled()) 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 45d386f9..edcfbe05 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -144,7 +144,7 @@ public static void switchToTeam(BuildTeam team, Player clickPlayer) { * Height is extracted from the world. *

* Note: Height returned is actually terrain elevation +2. This is because this method internally uses - * Bukkits @see World::getHighestBlockYAt() already returns elevation+1, and this method deliberately + * Bukkit @see World::getHighestBlockYAt() already returns elevation+1, and this method deliberately * adds one to the location elevation on top. *

* The world is extracted from the server config's "earth world". If no earth world is specified then the height defaults @@ -178,7 +178,7 @@ public static void switchToTeam(BuildTeam team, Player clickPlayer) { * Height is extracted from the world. *

* Note: Height returned is actually terrain elevation +2. This is because this method internally uses - * Bukkits @see World::getHighestBlockYAt() already returns elevation+1, and this method deliberately + * Bukkit @see World::getHighestBlockYAt() already returns elevation+1, and this method deliberately * adds one to the location elevation on top. *

* The world is extracted from the server config's "earth world". If no earth world is specified then the height defaults diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/MaterialSelectionMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/MaterialSelectionMenu.java index eec342b5..7c75110a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/MaterialSelectionMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/menu/MaterialSelectionMenu.java @@ -117,7 +117,7 @@ else if (object instanceof WarpGroup) getMenu().getSlot(CUSTOM_HEAD_SLOT).setClickHandler((clickPlayer, clickInformation) -> { clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); - String[] url = {"http://textures.minecraft.net/texture/..."}; + String[] url = {"https://textures.minecraft.net/texture/..."}; List content = new ArrayList<>(); content.add(url); @@ -141,10 +141,11 @@ else if (object instanceof WarpGroup) for (int i = 1; i < text.getFirst().length; i++) finalText.append(text.getFirst()[i]); - if (!finalText.toString().startsWith("http://textures.minecraft.net/texture/")) { + if (!Warp.isMinecraftTextureUrl(finalText.toString())) { clickPlayer.playSound(clickPlayer.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F); clickPlayer.sendMessage(Messages.component(MessageKeys.Warp.Material.URL_INVALID)); openObjectMenu(); + return; } if (object instanceof Warp) 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 d5433bfd..5c849fda 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 @@ -34,15 +34,15 @@ public class WarpEditMenu extends AbstractMenu { - public static int WARP_SLOT = 4; - public static int LOCATION_SLOT = 19; + public static final int WARP_SLOT = 4; + public static final int LOCATION_SLOT = 19; public static int NAME_SLOT = 20; public static int GROUP_SLOT = 21; public static int ADDRESS_TYPE_SLOT = 22; public static int MATERIAL_SLOT = 23; public static int HIGHLIGHT_SLOT = 24; - public static int DELETE_SLOT = 25; - public static int CONFIRM_SLOT = 35; + public static final int DELETE_SLOT = 25; + public static final int CONFIRM_SLOT = 35; private final Warp warp; private final boolean alreadyExists; diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/BlockLocation.java b/src/main/java/net/buildtheearth/buildteamtools/utils/BlockLocation.java index f8289dd0..96f70f19 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/BlockLocation.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/BlockLocation.java @@ -8,7 +8,7 @@ * */ public class BlockLocation { - public int x, y, z; + public final int x, y, z; public BlockLocation(int x, int y, int z) { this.x = x; diff --git a/src/main/java/net/buildtheearth/buildteamtools/utils/MenuItems.java b/src/main/java/net/buildtheearth/buildteamtools/utils/MenuItems.java index ee219773..50d03582 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/utils/MenuItems.java +++ b/src/main/java/net/buildtheearth/buildteamtools/utils/MenuItems.java @@ -784,54 +784,34 @@ public static ItemStack[] getGatesArray() { * Filter selector (array form) mirroring the module's getItemsByFilter(String). */ public static ItemStack[] getItemsByFilterArray(String filter) { - switch (filter.toLowerCase(Locale.ROOT)) { - case "slabs": - return getSlabsArray(); - case "stairs": - return getStairsArray(); - case "walls": - return getWallsArray(); - case "logs": - return getLogsArray(); - case "leaves": - return getLeavesArray(); - case "fences": - return getFencesArray(); - case "carpet": - return getCarpetArray(); - case "wool": - return getWoolArray(); - case "terracotta": - return getTerracottaArray(); - case "concrete": - return getConcreteArray(); - case "concrete_powder": - return getConcretePowderArray(); - case "bed": - return getBedsArray(); - case "candle": - return getCandlesArray(); - case "banner": - return getBannersArray(); - case "glass_pane": - return getGlassPanesArray(); - case "signs": - return getSignsArray(); - case "shulker_boxes": - return getShulkerBoxesArray(); - case "gates": - return getGatesArray(); - case "glass": - return getGlassArray(); - default: - return getBlocksByColorArray(); - } + return switch (filter.toLowerCase(Locale.ROOT)) { + case "slabs" -> getSlabsArray(); + case "stairs" -> getStairsArray(); + case "walls" -> getWallsArray(); + case "logs" -> getLogsArray(); + case "leaves" -> getLeavesArray(); + case "fences" -> getFencesArray(); + case "carpet" -> getCarpetArray(); + case "wool" -> getWoolArray(); + case "terracotta" -> getTerracottaArray(); + case "concrete" -> getConcreteArray(); + case "concrete_powder" -> getConcretePowderArray(); + case "bed" -> getBedsArray(); + case "candle" -> getCandlesArray(); + case "banner" -> getBannersArray(); + case "glass_pane" -> getGlassPanesArray(); + case "signs" -> getSignsArray(); + case "shulker_boxes" -> getShulkerBoxesArray(); + case "gates" -> getGatesArray(); + case "glass" -> getGlassArray(); + default -> getBlocksByColorArray(); + }; } // ----------------- OTHER (from original utils) ----------------- // Background glass pane used in GUIs - public static ItemStack ITEM_BACKGROUND = new Item(GRAY_STAINED_GLASS_PANE.parseItem()) + public static final ItemStack ITEM_BACKGROUND = new Item(GRAY_STAINED_GLASS_PANE.parseItem()) .setDisplayName(" ").build(); /** @@ -852,106 +832,56 @@ public static Material[] getIgnoredMaterials() { * Map a stair XMaterial back to its "base" block XMaterial. */ public static XMaterial convertStairToBlock(XMaterial stair) { - switch (stair) { - case QUARTZ_STAIRS: - return QUARTZ_BLOCK; - case POLISHED_BLACKSTONE_BRICK_STAIRS: - return POLISHED_BLACKSTONE_BRICKS; - case POLISHED_BLACKSTONE_STAIRS: - return POLISHED_BLACKSTONE; - case BLACKSTONE_STAIRS: - return BLACKSTONE; - case DEEPSLATE_TILE_STAIRS: - return DEEPSLATE_TILES; - case DEEPSLATE_BRICK_STAIRS: - return DEEPSLATE_BRICKS; - case POLISHED_DEEPSLATE_STAIRS: - return POLISHED_DEEPSLATE; - case COBBLED_DEEPSLATE_STAIRS: - return COBBLED_DEEPSLATE; - case STONE_BRICK_STAIRS: - return STONE_BRICKS; - case POLISHED_ANDESITE_STAIRS: - return POLISHED_ANDESITE; - case STONE_STAIRS: - return STONE; - case ANDESITE_STAIRS: - return ANDESITE; - case POLISHED_DIORITE_STAIRS: - return POLISHED_DIORITE; - case DIORITE_STAIRS: - return DIORITE; - case SMOOTH_QUARTZ_STAIRS: - return SMOOTH_QUARTZ; - case CHERRY_STAIRS: - return CHERRY_PLANKS; - case PURPUR_STAIRS: - return PURPUR_BLOCK; - case WARPED_STAIRS: - return WARPED_PLANKS; - case DARK_PRISMARINE_STAIRS: - return DARK_PRISMARINE; - case OXIDIZED_CUT_COPPER_STAIRS: - return OXIDIZED_CUT_COPPER; - case PRISMARINE_BRICK_STAIRS: - return PRISMARINE_BRICKS; - case PRISMARINE_STAIRS: - return PRISMARINE; - case WEATHERED_CUT_COPPER_STAIRS: - return WEATHERED_CUT_COPPER; - case MOSSY_COBBLESTONE_STAIRS: - return MOSSY_COBBLESTONE; - case MOSSY_STONE_BRICK_STAIRS: - return MOSSY_STONE_BRICKS; - case EXPOSED_CUT_COPPER_STAIRS: - return EXPOSED_CUT_COPPER; - case POLISHED_GRANITE_STAIRS: - return POLISHED_GRANITE; - case GRANITE_STAIRS: - return GRANITE; - case JUNGLE_STAIRS: - return JUNGLE_PLANKS; - case MUD_BRICK_STAIRS: - return MUD_BRICKS; - case BIRCH_STAIRS: - return BIRCH_PLANKS; - case END_STONE_BRICK_STAIRS: - return END_STONE_BRICKS; - case SANDSTONE_STAIRS: - return SANDSTONE; - case SMOOTH_SANDSTONE_STAIRS: - return SMOOTH_SANDSTONE; - case BAMBOO_MOSAIC_STAIRS: - return BAMBOO_MOSAIC; - case BAMBOO_STAIRS: - return BAMBOO_PLANKS; - case RED_SANDSTONE_STAIRS: - return RED_SANDSTONE; - case SMOOTH_RED_SANDSTONE_STAIRS: - return SMOOTH_RED_SANDSTONE; - case ACACIA_STAIRS: - return ACACIA_PLANKS; - case BRICK_STAIRS: - return BRICKS; - case CUT_COPPER_STAIRS: - return CUT_COPPER; - case RED_NETHER_BRICK_STAIRS: - return RED_NETHER_BRICKS; - case MANGROVE_STAIRS: - return MANGROVE_PLANKS; - case CRIMSON_STAIRS: - return CRIMSON_PLANKS; - case NETHER_BRICK_STAIRS: - return NETHER_BRICKS; - case DARK_OAK_STAIRS: - return DARK_OAK_PLANKS; - case SPRUCE_STAIRS: - return SPRUCE_PLANKS; - case OAK_STAIRS: - return OAK_PLANKS; - case COBBLESTONE_STAIRS: - default: - return COBBLESTONE; - } + return switch (stair) { + case QUARTZ_STAIRS -> QUARTZ_BLOCK; + case POLISHED_BLACKSTONE_BRICK_STAIRS -> POLISHED_BLACKSTONE_BRICKS; + case POLISHED_BLACKSTONE_STAIRS -> POLISHED_BLACKSTONE; + case BLACKSTONE_STAIRS -> BLACKSTONE; + case DEEPSLATE_TILE_STAIRS -> DEEPSLATE_TILES; + case DEEPSLATE_BRICK_STAIRS -> DEEPSLATE_BRICKS; + case POLISHED_DEEPSLATE_STAIRS -> POLISHED_DEEPSLATE; + case COBBLED_DEEPSLATE_STAIRS -> COBBLED_DEEPSLATE; + case STONE_BRICK_STAIRS -> STONE_BRICKS; + case POLISHED_ANDESITE_STAIRS -> POLISHED_ANDESITE; + case STONE_STAIRS -> STONE; + case ANDESITE_STAIRS -> ANDESITE; + case POLISHED_DIORITE_STAIRS -> POLISHED_DIORITE; + case DIORITE_STAIRS -> DIORITE; + case SMOOTH_QUARTZ_STAIRS -> SMOOTH_QUARTZ; + case CHERRY_STAIRS -> CHERRY_PLANKS; + case PURPUR_STAIRS -> PURPUR_BLOCK; + case WARPED_STAIRS -> WARPED_PLANKS; + case DARK_PRISMARINE_STAIRS -> DARK_PRISMARINE; + case OXIDIZED_CUT_COPPER_STAIRS -> OXIDIZED_CUT_COPPER; + case PRISMARINE_BRICK_STAIRS -> PRISMARINE_BRICKS; + case PRISMARINE_STAIRS -> PRISMARINE; + case WEATHERED_CUT_COPPER_STAIRS -> WEATHERED_CUT_COPPER; + case MOSSY_COBBLESTONE_STAIRS -> MOSSY_COBBLESTONE; + case MOSSY_STONE_BRICK_STAIRS -> MOSSY_STONE_BRICKS; + case EXPOSED_CUT_COPPER_STAIRS -> EXPOSED_CUT_COPPER; + case POLISHED_GRANITE_STAIRS -> POLISHED_GRANITE; + case GRANITE_STAIRS -> GRANITE; + case JUNGLE_STAIRS -> JUNGLE_PLANKS; + case MUD_BRICK_STAIRS -> MUD_BRICKS; + case BIRCH_STAIRS -> BIRCH_PLANKS; + case END_STONE_BRICK_STAIRS -> END_STONE_BRICKS; + case SANDSTONE_STAIRS -> SANDSTONE; + case SMOOTH_SANDSTONE_STAIRS -> SMOOTH_SANDSTONE; + case BAMBOO_MOSAIC_STAIRS -> BAMBOO_MOSAIC; + case BAMBOO_STAIRS -> BAMBOO_PLANKS; + case RED_SANDSTONE_STAIRS -> RED_SANDSTONE; + case SMOOTH_RED_SANDSTONE_STAIRS -> SMOOTH_RED_SANDSTONE; + case ACACIA_STAIRS -> ACACIA_PLANKS; + case BRICK_STAIRS -> BRICKS; + case CUT_COPPER_STAIRS -> CUT_COPPER; + case RED_NETHER_BRICK_STAIRS -> RED_NETHER_BRICKS; + case MANGROVE_STAIRS -> MANGROVE_PLANKS; + case CRIMSON_STAIRS -> CRIMSON_PLANKS; + case NETHER_BRICK_STAIRS -> NETHER_BRICKS; + case DARK_OAK_STAIRS -> DARK_OAK_PLANKS; + case SPRUCE_STAIRS -> SPRUCE_PLANKS; + case OAK_STAIRS -> OAK_PLANKS; + default -> COBBLESTONE; + }; } } diff --git a/src/main/resources/net/buildtheearth/buildteamtools/i18n/messages_en_US.properties b/src/main/resources/net/buildtheearth/buildteamtools/i18n/messages_en_US.properties index b6221c66..f0793d34 100644 --- a/src/main/resources/net/buildtheearth/buildteamtools/i18n/messages_en_US.properties +++ b/src/main/resources/net/buildtheearth/buildteamtools/i18n/messages_en_US.properties @@ -306,7 +306,7 @@ btt.warp.material.anvil.item=Enter Material Name btt.warp.material.anvil.title=Enter Material Name btt.warp.material.book.title=Change Texture URL btt.warp.material.url-save-failed=Could not save the URL. Please try again. -btt.warp.material.url-invalid=The URL must start with http://textures.minecraft.net/texture/ +btt.warp.material.url-invalid=The URL must start with https://textures.minecraft.net/texture/ # ---- Tpll ---- btt.tpll.earth-world-unknown=The earth world for this server is not configured. 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 e7055f3e..d91fcee5 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 @@ -24,6 +24,7 @@ import java.lang.reflect.Proxy; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicInteger; @@ -151,7 +152,7 @@ void allDefaultFeatureAliasesRegisterWithoutAmbiguity() { } @Test - void subcommandsRetainArgumentsPermissionsAndHandlersForEveryAlias() throws Exception { + void subcommandsRetainArgumentsPermissionsAndHandlersForEveryAlias() { AtomicInteger invocations = new AtomicInteger(); AtomicInteger mapInvocations = new AtomicInteger(); Module owner = new EnabledModule(); @@ -313,8 +314,10 @@ public List warp() { private CommandContext parse(String input) { commands.registerEnabledCommands(); var context = new CommandContext<>(sender, manager); - Command command = manager.commandTree() - .parse(context, CommandInput.of(input), Runnable::run).join(); + Command command = Objects.requireNonNull( + manager.commandTree() + .parse(context, CommandInput.of(input), Runnable::run).join(), + "Expected command parse to succeed for: " + input); context.command(command); return context; } @@ -333,7 +336,8 @@ private TestManager() { } @Override - public boolean hasPermission(CommandSourceStack sender, String permission) { + public boolean hasPermission(@org.jspecify.annotations.NonNull CommandSourceStack sender, + @org.jspecify.annotations.NonNull String permission) { return !deniedPermissions.contains(permission); } diff --git a/src/test/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueueTest.java b/src/test/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueueTest.java index 72e78300..f57b7a65 100644 --- a/src/test/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueueTest.java +++ b/src/test/java/net/buildtheearth/buildteamtools/modules/network/api/AsyncRequestQueueTest.java @@ -125,7 +125,7 @@ void shouldFailPendingRequestsWhenClosed() throws Exception { } @Test - void shouldHandleSupplierException() throws Exception { + void shouldHandleSupplierException() { try (AsyncRequestQueue queue = new AsyncRequestQueue(0, "test-queue", 10)) { @@ -146,7 +146,7 @@ void shouldHandleSupplierException() throws Exception { } @Test - void shouldHandleFailedFuture() throws Exception { + void shouldHandleFailedFuture() { try (AsyncRequestQueue queue = new AsyncRequestQueue(0, "test-queue", 10)) { From 43e69676c418214b661c7ba67e4bf9043f66d06c Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 23:05:39 +0100 Subject: [PATCH 09/10] fix(warps): resolve country CCA2 globally without a false not-found message Look up CCA2 from the full region registry instead of only the team's owned regions, and stop messaging the player from that helper so ownership failures are reported once. --- .../modules/navigation/NavUtils.java | 27 +++++-------------- .../components/warps/WarpsComponent.java | 12 ++++----- 2 files changed, 12 insertions(+), 27 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 edcfbe05..c4cbafd9 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/NavUtils.java @@ -194,27 +194,12 @@ public static void switchToTeam(BuildTeam team, Player clickPlayer) { } /** - * Returns the CCA2 code of the country of the given country name. + * Returns the CCA2 code for a country name from the global region registry, + * or {@code null} when the name is unknown. Does not message the player — + * callers decide how to report ownership / lookup failures. */ - public static String getCCA2FromCountryName(String countryName, Player clickPlayer) { - Region region = findRegionByName(countryName); - if (region != null) { - return region.getCountryCodeCca2(); - } - - clickPlayer.sendMessage(Messages.component(MessageKeys.Navigation.COUNTRY_NOT_FOUND)); - 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); + public static @Nullable String getCCA2FromCountryName(String countryName) { + Region region = Region.getByName(countryName); + return region != null ? region.getCountryCodeCca2() : null; } } 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 89b795e9..276c5dd1 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 @@ -293,13 +293,13 @@ public static void openWarpMenu(@NotNull Player player, @NotNull BuildTeam build GeographicalCoordinate coordinate, Player player ) { - return getRegionLookupResult(coordinate, false, player) + return getRegionLookupResult(coordinate, false) .thenCompose(result -> { if (ownsRegion(result)) { return CompletableFuture.completedFuture(result); } - return getRegionLookupResult(coordinate, true, player) + return getRegionLookupResult(coordinate, true) .thenApply(fallbackResult -> { if (!ownsRegion(fallbackResult)) { throw new CompletionException(new IllegalStateException( @@ -315,14 +315,14 @@ public static void openWarpMenu(@NotNull Player player, @NotNull BuildTeam build private static @NotNull CompletableFuture getRegionLookupResult( GeographicalCoordinate coordinate, - boolean fallbackLookup, - Player player + boolean fallbackLookup ) { return OpenStreetMapAPI.getCountryFromLocationAsync(coordinate, fallbackLookup) .thenApply(result -> { if (result.countryCodeCCA2() == null) { - return new RegionLookupResult(result.regionName(), NavUtils.getCCA2FromCountryName(result.regionName(), - player)); + return new RegionLookupResult( + result.regionName(), + NavUtils.getCCA2FromCountryName(result.regionName())); } return result; }); From 359f31b1af87524e75c02aee9a4b95010d1a5784 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 31 Oct 2026 23:19:33 +0100 Subject: [PATCH 10/10] fix(network): harden region ownership, Photon CCA2, and async warp UI Avoid NPEs on null region country codes, treat blank Photon codes as missing so CCA2 enrichment runs, open warp edit menus on the main thread, rebuild the HTTP User-Agent per request, and parse Nominatim display names safely. --- .../components/warps/WarpsComponent.java | 3 ++- .../components/warps/menu/WarpEditMenu.java | 14 ++++++----- .../modules/network/NetworkModule.java | 25 ++++++++++++------- .../modules/network/api/API.java | 20 +++++++++------ .../modules/network/api/NominatimAPI.java | 6 ++--- .../modules/network/api/OpenStreetMapAPI.java | 10 ++++++-- 6 files changed, 50 insertions(+), 28 deletions(-) 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 276c5dd1..2df7dec7 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 @@ -319,7 +319,8 @@ public static void openWarpMenu(@NotNull Player player, @NotNull BuildTeam build ) { return OpenStreetMapAPI.getCountryFromLocationAsync(coordinate, fallbackLookup) .thenApply(result -> { - if (result.countryCodeCCA2() == null) { + String countryCode = result.countryCodeCCA2(); + if (countryCode == null || countryCode.isBlank()) { return new RegionLookupResult( result.regionName(), NavUtils.getCCA2FromCountryName(result.regionName())); 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 5c849fda..04c57a86 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 @@ -20,6 +20,7 @@ import net.buildtheearth.model.GeographicalCoordinate; import net.kyori.adventure.text.Component; import net.wesjd.anvilgui.AnvilGUI; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -182,7 +183,7 @@ protected void setItemClickEventsAsync() { GeographicalCoordinate coordinate = Projection.toGeo(location.getX(), location.getZ()); WarpsComponent.getOwnedRegionFromLocation(coordinate, clickPlayer) - .thenAccept(result -> { + .thenAccept(result -> Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> { warp.setCountryCode(result.countryCodeCCA2()); warp.setWorldName(location.getWorld().getName()); warp.setY(location.getY()); @@ -193,14 +194,15 @@ protected void setItemClickEventsAsync() { 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(Messages.component( - MessageKeys.Warp.Edit.LOCATION_CHANGE_FAILED, - Component.text(String.valueOf(cause.getMessage())) - )); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> + clickPlayer.sendMessage(Messages.component( + MessageKeys.Warp.Edit.LOCATION_CHANGE_FAILED, + Component.text(String.valueOf(cause.getMessage())) + ))); BuildTeamTools.getInstance().getComponentLogger().error( "An error occurred while changing the location of the warp!", diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/NetworkModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/NetworkModule.java index 87687965..c70c87ea 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/NetworkModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/NetworkModule.java @@ -27,7 +27,6 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.atomic.AtomicBoolean; @Getter public class NetworkModule extends Module { @@ -183,16 +182,24 @@ public void switchServer(Player player, String targetServer) { } public boolean ownsRegion(String regionName, String countryCodeCca2) { - AtomicBoolean ownsRegion = new AtomicBoolean(false); - if (buildTeam != null && buildTeam.getRegions() != null) { - buildTeam.getRegions().forEach(region -> { - ChatHelper.logDebug("Checking region: %s against %s, %s", region.toString(), regionName, countryCodeCca2); - if (region.getName().equals(regionName) || region.getCountryCodeCca2().equals(countryCodeCca2)) - ownsRegion.set(true); - }); + if (buildTeam == null || buildTeam.getRegions() == null) { + return false; + } + + for (Region region : buildTeam.getRegions()) { + ChatHelper.logDebug("Checking region: %s against %s, %s", region.toString(), regionName, countryCodeCca2); + if (region.getName().equals(regionName)) { + return true; + } + if (region.getType() == RegionType.COUNTRY + && countryCodeCca2 != null + && !countryCodeCca2.isBlank() + && countryCodeCca2.equalsIgnoreCase(region.getCountryCodeCca2())) { + return true; + } } - return ownsRegion.get(); + return false; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java index 14c0baae..4c2573d2 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/API.java @@ -23,12 +23,6 @@ public class API { .connectTimeout(30, TimeUnit.SECONDS) .build(); - private static final String USER_AGENT = "BuildTeamTools/" - + BuildTeamTools.getInstance().getPluginMeta().getVersion() - + " (contact: " - + BuildTeamTools.getInstance().getMainConfig().contactEmail() - + ")"; - /** * A callback function to be able to handle the result of an API request */ @@ -268,6 +262,18 @@ public static JSONObject createJSONObject(String jsonString) { private static Request.Builder createRequestBuilder(String url) { return new Request.Builder() .url(url) - .header("User-Agent", USER_AGENT); + .header("User-Agent", userAgent()); + } + + /** + * Builds the User-Agent on each request so config reloads of {@code contact-email} apply. + */ + private static String userAgent() { + BuildTeamTools plugin = BuildTeamTools.getInstance(); + return "BuildTeamTools/" + + plugin.getPluginMeta().getVersion() + + " (contact: " + + plugin.getMainConfig().contactEmail() + + ")"; } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java index 5f51913f..64e44e98 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/network/api/NominatimAPI.java @@ -63,16 +63,16 @@ private static String formatAddressFromResponse(String response) { jsonObject ); - Object displayName = jsonObject.get("display_name"); + String displayName = jsonObject.optString("display_name", "").trim(); - if (displayName == null || displayName.toString().isBlank()) { + if (displayName.isEmpty()) { ChatHelper.logError( "No address data found for these coordinates." ); return ""; } - return displayName.toString(); + return displayName; } public static void shutdownQueue() { 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 7db37805..e803a31e 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 @@ -100,10 +100,16 @@ private static void completePhotonLookup(String response, @NonNull CompletableFu return; } - String countryCodeCca2 = propertiesObject.optString("countrycode", ""); + String rawCountryCode = propertiesObject.optString("countrycode", "").trim(); + String countryCodeCca2 = rawCountryCode.isEmpty() + ? null + : rawCountryCode.toUpperCase(Locale.ROOT); String countryName = propertiesObject.optString("country", null); + if (countryName != null && countryName.isBlank()) { + countryName = null; + } - future.complete(new RegionLookupResult(countryName, countryCodeCca2.toUpperCase(Locale.ROOT))); + future.complete(new RegionLookupResult(countryName, countryCodeCca2)); } }