diff --git a/build.gradle.kts b/build.gradle.kts index 48f75d64..a3e074da 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") @@ -116,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 { 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/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)); } 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/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/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/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/KmlCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/kml/KmlCommand.java index 3e5ffa0e..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 @@ -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; @@ -46,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 @@ -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 @@ -279,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(); @@ -354,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) { @@ -563,15 +557,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)); @@ -631,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; } @@ -700,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/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/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(); 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/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/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 f703efa2..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 @@ -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); @@ -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/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 8b286927..c4cbafd9 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) { @@ -148,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 @@ -182,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 @@ -198,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/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/modules/navigation/components/warps/WarpsComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/navigation/components/warps/WarpsComponent.java index 89b795e9..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 @@ -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,15 @@ 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)); + String countryCode = result.countryCodeCCA2(); + if (countryCode == null || countryCode.isBlank()) { + return new RegionLookupResult( + result.regionName(), + NavUtils.getCCA2FromCountryName(result.regionName())); } return result; }); 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..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; @@ -34,15 +35,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; @@ -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/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/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 6d0021ae..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 @@ -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; @@ -24,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 */ @@ -243,34 +236,44 @@ 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) { 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/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/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..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 @@ -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; @@ -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 93c1cc8e..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 @@ -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,22 +86,30 @@ 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 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)); } } 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/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; } } - 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..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 @@ -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 { @@ -56,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(); } @@ -103,52 +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 (Object key : playerStats.keySet()) { - if (key instanceof UUID) continue; - - Object value = playerStats.get(key); - playerLore.add(Component.text(key + ": " + value)); + for (String key : playerStats.keySet()) { + if ("UUID".equals(key)) { + continue; + } + 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 (Object key : serverStats.keySet()) { - Object value = serverStats.get(key); - serverLore.add(Component.text(key + ": " + value)); + for (String key : serverStats.keySet()) { + 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 817b1f9f..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,7 +1,6 @@ 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; @@ -17,45 +16,20 @@ 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); } - @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..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,7 +1,6 @@ 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; @@ -14,46 +13,20 @@ 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); } - @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; } } - 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/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/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 94c9ef7e..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 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 +100,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++; 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)) {