From 10a51271ffc92ca302862bc437a2f6c9bc331706 Mon Sep 17 00:00:00 2001 From: Smyler Date: Sun, 17 Sep 2023 19:55:48 +0200 Subject: [PATCH 01/11] TPLL: read generation settings from the generator --- .../terraplusminus/commands/TpllCommand.java | 44 ++++++++++++------- .../gen/RealWorldGenerator.java | 13 ++++-- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index e585019..bffb3c8 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -4,20 +4,24 @@ import com.google.common.io.ByteStreams; import de.btegermany.terraplusminus.Terraplusminus; import de.btegermany.terraplusminus.data.TerraConnector; +import de.btegermany.terraplusminus.gen.RealWorldGenerator; import de.btegermany.terraplusminus.utils.PluginMessageUtil; import io.papermc.lib.PaperLib; import net.buildtheearth.terraminusminus.generator.EarthGeneratorSettings; +import net.buildtheearth.terraminusminus.projection.GeographicProjection; import net.buildtheearth.terraminusminus.projection.OutOfProjectionBoundsException; import org.bukkit.Location; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; -public class TpllCommand implements CommandExecutor { +import static org.bukkit.ChatColor.RED; + - private final EarthGeneratorSettings bteGeneratorSettings = EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS); +public class TpllCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { @@ -101,28 +105,36 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command if (args.length >= 2) { if (player.hasPermission("t+-.tpll")) { - int xOffset = Terraplusminus.config.getInt("terrain_offset.x"); - int yOffset = Terraplusminus.config.getInt("terrain_offset.y"); - int zOffset = Terraplusminus.config.getInt("terrain_offset.z"); - Double minLat = Terraplusminus.config.getDouble("min_latitude"); - Double maxLat = Terraplusminus.config.getDouble("max_latitude"); - Double minLon = Terraplusminus.config.getDouble("min_longitude"); - Double maxLon = Terraplusminus.config.getDouble("max_longitude"); + double minLat = Terraplusminus.config.getDouble("min_latitude"); + double maxLat = Terraplusminus.config.getDouble("max_latitude"); + double minLon = Terraplusminus.config.getDouble("min_longitude"); + double maxLon = Terraplusminus.config.getDouble("max_longitude"); double[] coordinates = new double[2]; coordinates[1] = Double.parseDouble(args[0].replace(",", "").replace("°", "")); coordinates[0] = Double.parseDouble(args[1].replace("°", "")); - double[] mcCoordinates = new double[0]; + ChunkGenerator generator = player.getWorld().getGenerator(); + if (!(generator instanceof RealWorldGenerator)) { + commandSender.sendMessage(RED + "Must be in a Terra 1 to 1 world!"); + return true; + } + RealWorldGenerator terraGenerator = (RealWorldGenerator) generator; + EarthGeneratorSettings generatorSettings = terraGenerator.getSettings(); + GeographicProjection projection = generatorSettings.projection(); + int yOffset = terraGenerator.getYOffset(); + + double[] mcCoordinates; try { - mcCoordinates = bteGeneratorSettings.projection().fromGeo(coordinates[0], coordinates[1]); + mcCoordinates = projection.fromGeo(coordinates[0], coordinates[1]); } catch (OutOfProjectionBoundsException e) { - e.printStackTrace(); + commandSender.sendMessage(RED + "Location is not within projection bounds"); + return true; } if (minLat != 0 && maxLat != 0 && minLon != 0 && maxLon != 0 && !player.hasPermission("t+-.admin")) { if (coordinates[1] < minLat || coordinates[0] < minLon || coordinates[1] > maxLat || coordinates[0] > maxLon) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because this area is being worked on by another build team."); + player.sendMessage(Terraplusminus.config.getString("prefix") + RED + "You cannot tpll to these coordinates, because this area is being worked on by another build team."); return true; } } @@ -184,13 +196,13 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command return true; } } - Location location = new Location(player.getWorld(), mcCoordinates[0] + xOffset, height, mcCoordinates[1] + zOffset, player.getLocation().getYaw(), player.getLocation().getPitch()); + Location location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); if (PaperLib.isChunkGenerated(location)) { if (args.length >= 3) { - location = new Location(player.getWorld(), mcCoordinates[0] + xOffset, height, mcCoordinates[1] + zOffset, player.getLocation().getYaw(), player.getLocation().getPitch()); + location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); } else { - location = new Location(player.getWorld(), mcCoordinates[0] + xOffset, player.getWorld().getHighestBlockYAt((int) mcCoordinates[0] + xOffset, (int) mcCoordinates[1] + zOffset) + 1, mcCoordinates[1] + zOffset, player.getLocation().getYaw(), player.getLocation().getPitch()); + location = new Location(player.getWorld(), mcCoordinates[0], player.getWorld().getHighestBlockYAt((int) mcCoordinates[0], (int) mcCoordinates[1]) + 1, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); } } else { player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Location is generating. Please wait a moment..."); diff --git a/src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java b/src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java index 0ab19dd..dc37f6c 100644 --- a/src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java +++ b/src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java @@ -5,6 +5,7 @@ import de.btegermany.terraplusminus.Terraplusminus; import de.btegermany.terraplusminus.gen.tree.TreePopulator; import de.btegermany.terraplusminus.utils.ConfigurationHelper; +import lombok.Getter; import net.buildtheearth.terraminusminus.generator.CachedChunkData; import net.buildtheearth.terraminusminus.generator.ChunkDataLoader; import net.buildtheearth.terraminusminus.generator.EarthGeneratorSettings; @@ -37,12 +38,16 @@ public class RealWorldGenerator extends ChunkGenerator { + + @Getter + private final EarthGeneratorSettings settings; + @Getter + private final int yOffset; private Location spawnLocation = null; - public LoadingCache> cache; + private final LoadingCache> cache; private final CustomBiomeProvider customBiomeProvider; - private final int yOffset; private final Material surfaceMaterial; private final Map materialMapping; @@ -66,13 +71,13 @@ public RealWorldGenerator() { ); this.yOffset = Terraplusminus.config.getInt("terrain_offset.y"); - settings = settings.withProjection(projection); + this.settings = settings.withProjection(projection); this.customBiomeProvider = new CustomBiomeProvider(); this.cache = CacheBuilder.newBuilder() .expireAfterAccess(5L, TimeUnit.MINUTES) .softValues() - .build(new ChunkDataLoader(settings)); + .build(new ChunkDataLoader(this.settings)); this.surfaceMaterial = ConfigurationHelper.getMaterial(Terraplusminus.config, "surface_material", GRASS_BLOCK); this.materialMapping = Map.of( From 748f86b88b15a9586d93905865c14703f4ec86fc Mon Sep 17 00:00:00 2001 From: Smyler Date: Sun, 17 Sep 2023 20:01:04 +0200 Subject: [PATCH 02/11] Allow using aliases for tpll --- .../terraplusminus/commands/TpllCommand.java | 303 +++++++++--------- 1 file changed, 150 insertions(+), 153 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index bffb3c8..42d4e0a 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -25,208 +25,205 @@ public class TpllCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { - if (command.getName().equalsIgnoreCase("tpll")) { - //If sender is not a player cancel the command. - if (!(commandSender instanceof Player)) { - commandSender.sendMessage("This command can only be used by players!"); - return true; - } - Player player = (Player) commandSender; + //If sender is not a player cancel the command. + if (!(commandSender instanceof Player)) { + commandSender.sendMessage("This command can only be used by players!"); + return true; + } + Player player = (Player) commandSender; - // Entity selector + // Entity selector - // detect if command starts with @ or with a player name + // detect if command starts with @ or with a player name - if ((args[0].startsWith("@") || !isDouble(args[0].replace(",", ""))) && player.hasPermission("t+-.forcetpll")) { - if (args[0].equals("@a")) { - StringBuilder playerList = new StringBuilder(); - Terraplusminus.instance.getServer().getOnlinePlayers().forEach(p -> { - p.chat("/tpll " + String.join(" ", args).substring(2)); - if (Terraplusminus.instance.getServer().getOnlinePlayers().size() > 1) { - playerList.append(p.getName()).append(", "); - } else { - playerList.append(p.getName()).append(" "); - } - }); - // delete last comma if no player follows - if (playerList.length() > 0 && playerList.charAt(playerList.length() - 2) == ',') { - playerList.deleteCharAt(playerList.length() - 2); - } - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + playerList + "§7to" + String.join(" ", args).substring(2)); - return true; - } else if (args[0].equals("@p")) { - // find nearest player but not the player itself - Player nearestPlayer = null; - double nearestDistance = Double.MAX_VALUE; - for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { - if (p.getLocation().distanceSquared(player.getLocation()) < nearestDistance && (!p.equals(player) || Terraplusminus.instance.getServer().getOnlinePlayers().size() == 1)) { - nearestPlayer = p; - nearestDistance = p.getLocation().distanceSquared(player.getLocation()); - } - } - if (nearestPlayer != null) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + nearestPlayer.getName() + " §7to" + String.join(" ", args).substring(2)); - nearestPlayer.chat("/tpll " + String.join(" ", args).substring(2)); + if ((args[0].startsWith("@") || !isDouble(args[0].replace(",", ""))) && player.hasPermission("t+-.forcetpll")) { + if (args[0].equals("@a")) { + StringBuilder playerList = new StringBuilder(); + Terraplusminus.instance.getServer().getOnlinePlayers().forEach(p -> { + p.chat("/tpll " + String.join(" ", args).substring(2)); + if (Terraplusminus.instance.getServer().getOnlinePlayers().size() > 1) { + playerList.append(p.getName()).append(", "); + } else { + playerList.append(p.getName()).append(" "); } - return true; - } else { - Player target = null; - //check if target player is online - for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { - if (p.getName().equals(args[0])) { - target = p; - } + }); + // delete last comma if no player follows + if (playerList.length() > 0 && playerList.charAt(playerList.length() - 2) == ',') { + playerList.deleteCharAt(playerList.length() - 2); + } + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + playerList + "§7to" + String.join(" ", args).substring(2)); + return true; + } else if (args[0].equals("@p")) { + // find nearest player but not the player itself + Player nearestPlayer = null; + double nearestDistance = Double.MAX_VALUE; + for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { + if (p.getLocation().distanceSquared(player.getLocation()) < nearestDistance && (!p.equals(player) || Terraplusminus.instance.getServer().getOnlinePlayers().size() == 1)) { + nearestPlayer = p; + nearestDistance = p.getLocation().distanceSquared(player.getLocation()); } - - if (target == null) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cNo player found with name §9" + args[0]); - return true; + } + if (nearestPlayer != null) { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + nearestPlayer.getName() + " §7to" + String.join(" ", args).substring(2)); + nearestPlayer.chat("/tpll " + String.join(" ", args).substring(2)); + } + return true; + } else { + Player target = null; + //check if target player is online + for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { + if (p.getName().equals(args[0])) { + target = p; } + } - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + target.getName() + " §7to " + args[1] + " " + args[2]); - target.chat("/tpll " + String.join(" ", args).replace(target.getName(), "")); + if (target == null) { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cNo player found with name §9" + args[0]); return true; } - } - //Option to passthrough tpll to other bukkit plugins. - String passthroughTpll = Terraplusminus.config.getString("passthrough_tpll"); - if (!passthroughTpll.isEmpty()) { - //Check if any args are parsed. - if (args.length == 0) { - player.chat("/" + passthroughTpll + ":tpll"); - } else { - player.chat("/" + passthroughTpll + ":tpll " + String.join(" ", args)); - } + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + target.getName() + " §7to " + args[1] + " " + args[2]); + target.chat("/tpll " + String.join(" ", args).replace(target.getName(), "")); return true; } + } - // - - if (args.length >= 2) { - if (player.hasPermission("t+-.tpll")) { + //Option to passthrough tpll to other bukkit plugins. + String passthroughTpll = Terraplusminus.config.getString("passthrough_tpll"); + if (!passthroughTpll.isEmpty()) { + //Check if any args are parsed. + if (args.length == 0) { + player.chat("/" + passthroughTpll + ":tpll"); + } else { + player.chat("/" + passthroughTpll + ":tpll " + String.join(" ", args)); + } + return true; + } - double minLat = Terraplusminus.config.getDouble("min_latitude"); - double maxLat = Terraplusminus.config.getDouble("max_latitude"); - double minLon = Terraplusminus.config.getDouble("min_longitude"); - double maxLon = Terraplusminus.config.getDouble("max_longitude"); + // - + if (args.length >= 2) { + if (player.hasPermission("t+-.tpll")) { - double[] coordinates = new double[2]; - coordinates[1] = Double.parseDouble(args[0].replace(",", "").replace("°", "")); - coordinates[0] = Double.parseDouble(args[1].replace("°", "")); + double minLat = Terraplusminus.config.getDouble("min_latitude"); + double maxLat = Terraplusminus.config.getDouble("max_latitude"); + double minLon = Terraplusminus.config.getDouble("min_longitude"); + double maxLon = Terraplusminus.config.getDouble("max_longitude"); - ChunkGenerator generator = player.getWorld().getGenerator(); - if (!(generator instanceof RealWorldGenerator)) { - commandSender.sendMessage(RED + "Must be in a Terra 1 to 1 world!"); - return true; - } - RealWorldGenerator terraGenerator = (RealWorldGenerator) generator; - EarthGeneratorSettings generatorSettings = terraGenerator.getSettings(); - GeographicProjection projection = generatorSettings.projection(); - int yOffset = terraGenerator.getYOffset(); - - double[] mcCoordinates; - try { - mcCoordinates = projection.fromGeo(coordinates[0], coordinates[1]); - } catch (OutOfProjectionBoundsException e) { - commandSender.sendMessage(RED + "Location is not within projection bounds"); - return true; - } + double[] coordinates = new double[2]; + coordinates[1] = Double.parseDouble(args[0].replace(",", "").replace("°", "")); + coordinates[0] = Double.parseDouble(args[1].replace("°", "")); - if (minLat != 0 && maxLat != 0 && minLon != 0 && maxLon != 0 && !player.hasPermission("t+-.admin")) { - if (coordinates[1] < minLat || coordinates[0] < minLon || coordinates[1] > maxLat || coordinates[0] > maxLon) { - player.sendMessage(Terraplusminus.config.getString("prefix") + RED + "You cannot tpll to these coordinates, because this area is being worked on by another build team."); - return true; - } - } - - TerraConnector terraConnector = new TerraConnector(); + ChunkGenerator generator = player.getWorld().getGenerator(); + if (!(generator instanceof RealWorldGenerator)) { + commandSender.sendMessage(RED + "Must be in a Terra 1 to 1 world!"); + return true; + } + RealWorldGenerator terraGenerator = (RealWorldGenerator) generator; + EarthGeneratorSettings generatorSettings = terraGenerator.getSettings(); + GeographicProjection projection = generatorSettings.projection(); + int yOffset = terraGenerator.getYOffset(); + + double[] mcCoordinates; + try { + mcCoordinates = projection.fromGeo(coordinates[0], coordinates[1]); + } catch (OutOfProjectionBoundsException e) { + commandSender.sendMessage(RED + "Location is not within projection bounds"); + return true; + } - double height; - if (args.length >= 3) { - height = Double.parseDouble(args[2]) + yOffset; - } else { - height = terraConnector.getHeight((int) mcCoordinates[0], (int) mcCoordinates[1]).join() + yOffset; + if (minLat != 0 && maxLat != 0 && minLon != 0 && maxLon != 0 && !player.hasPermission("t+-.admin")) { + if (coordinates[1] < minLat || coordinates[0] < minLon || coordinates[1] > maxLat || coordinates[0] > maxLon) { + player.sendMessage(Terraplusminus.config.getString("prefix") + RED + "You cannot tpll to these coordinates, because this area is being worked on by another build team."); + return true; } - if (height > player.getWorld().getMaxHeight()) { - if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { + } - //send player uuid and coordinates to bungee + TerraConnector terraConnector = new TerraConnector(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF(player.getUniqueId().toString()); + double height; + if (args.length >= 3) { + height = Double.parseDouble(args[2]) + yOffset; + } else { + height = terraConnector.getHeight((int) mcCoordinates[0], (int) mcCoordinates[1]).join() + yOffset; + } + if (height > player.getWorld().getMaxHeight()) { + if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { - if (PluginMessageUtil.getNextServerName() != null) { - out.writeUTF(PluginMessageUtil.getNextServerName()); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); - return true; - } + //send player uuid and coordinates to bungee - out.writeUTF(coordinates[1] + ", " + coordinates[0]); - player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(player.getUniqueId().toString()); - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); - return true; + if (PluginMessageUtil.getNextServerName() != null) { + out.writeUTF(PluginMessageUtil.getNextServerName()); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not high enough at the moment."); + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); return true; } - } else if (height <= player.getWorld().getMinHeight()) { - if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { - //send player uuid and coordinates to bungee + out.writeUTF(coordinates[1] + ", " + coordinates[0]); + player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF(player.getUniqueId().toString()); + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); + return true; + } else { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not high enough at the moment."); + return true; + } + } else if (height <= player.getWorld().getMinHeight()) { + if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { - if (PluginMessageUtil.getLastServerName() != null) { - out.writeUTF(PluginMessageUtil.getLastServerName()); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); - return true; - } + //send player uuid and coordinates to bungee - out.writeUTF(coordinates[1] + ", " + coordinates[0]); - player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(player.getUniqueId().toString()); - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); - return true; + if (PluginMessageUtil.getLastServerName() != null) { + out.writeUTF(PluginMessageUtil.getLastServerName()); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not low enough at the moment."); + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); return true; } - } - Location location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - if (PaperLib.isChunkGenerated(location)) { - if (args.length >= 3) { - location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - } else { - location = new Location(player.getWorld(), mcCoordinates[0], player.getWorld().getHighestBlockYAt((int) mcCoordinates[0], (int) mcCoordinates[1]) + 1, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - } + out.writeUTF(coordinates[1] + ", " + coordinates[0]); + player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); + + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); + return true; } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Location is generating. Please wait a moment..."); + player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not low enough at the moment."); + return true; } - PaperLib.teleportAsync(player, location); - + } + Location location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); + if (PaperLib.isChunkGenerated(location)) { if (args.length >= 3) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + ", " + height + "."); + location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + "."); + location = new Location(player.getWorld(), mcCoordinates[0], player.getWorld().getHighestBlockYAt((int) mcCoordinates[0], (int) mcCoordinates[1]) + 1, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); } + } else { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Location is generating. Please wait a moment..."); + } + PaperLib.teleportAsync(player, location); - return true; + + if (args.length >= 3) { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + ", " + height + "."); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /tpll"); - return true; + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + "."); } + + return true; } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Usage: /tpll "); + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /tpll"); return true; } + } else { + player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Usage: /tpll "); + return true; } - return true; } public boolean isDouble(String str) { From 82b7456bd91987bafca26a23777de4adad05ad58 Mon Sep 17 00:00:00 2001 From: Smyler Date: Tue, 19 Sep 2023 10:16:12 +0200 Subject: [PATCH 03/11] Refactor offset command --- .../commands/OffsetCommand.java | 71 +++++++++++++++---- src/main/resources/plugin.yml | 2 +- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java index 64e7a4d..600ae6a 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java @@ -1,27 +1,74 @@ package de.btegermany.terraplusminus.commands; import de.btegermany.terraplusminus.Terraplusminus; +import de.btegermany.terraplusminus.gen.RealWorldGenerator; +import net.buildtheearth.terraminusminus.projection.GeographicProjection; +import net.buildtheearth.terraminusminus.projection.transform.OffsetProjectionTransform; +import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; +import static java.lang.Math.round; +import static org.bukkit.ChatColor.GRAY; +import static org.bukkit.ChatColor.RED; + public class OffsetCommand implements CommandExecutor { + @Override - public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { - if (command.getName().equalsIgnoreCase("offset")) { - Player player = (Player) commandSender; - if (player.hasPermission("t+-.offset")) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Offsets:"); - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7 | X: §8" + Terraplusminus.config.getInt("terrain_offset.x")); - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7 | Y: §8" + Terraplusminus.config.getInt("terrain_offset.y")); - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7 | Z: §8" + Terraplusminus.config.getInt("terrain_offset.z")); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /offset"); - return true; - } + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String... arguments) { + + String prefix = Terraplusminus.config.getString("prefix"); + + if (!sender.hasPermission("t+-.offset")) { + sender.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /offset"); + return true; } + + World world; + if (arguments.length == 1) { + String worldName = arguments[0]; + world = Bukkit.getWorld(worldName); + } else if (arguments.length == 0 && sender instanceof Player) { + Player player = (Player) sender; + world = player.getWorld(); + } else { + return false; + } + + if (world == null) { + sender.sendMessage(prefix + RED + "No such world"); + return true; + } + + ChunkGenerator generator = world.getGenerator(); + if (!(generator instanceof RealWorldGenerator)) { + sender.sendMessage(prefix + GRAY + world.getName() + RED + " is not a Terra+- world"); + return true; + } + RealWorldGenerator realWorldGenerator = (RealWorldGenerator) generator; + GeographicProjection projection = realWorldGenerator.getSettings().projection(); + + int offsetY = realWorldGenerator.getYOffset(); + int offsetX, offsetZ; + if (projection instanceof OffsetProjectionTransform) { + OffsetProjectionTransform transform = (OffsetProjectionTransform) projection; + // We assume there is ony one offset transform, or that it is the only one the user cares about + // We can safely round to int as we are only dealing with blocks at that scale + offsetX = (int) round(transform.dx()); + offsetZ = (int) round(transform.dy()); + } else { + offsetX = offsetZ = 0; + } + + sender.sendMessage(prefix + "§7Offsets for world" + world.getName() + ":"); + sender.sendMessage(prefix + "§7 | X: §8" + offsetX); + sender.sendMessage(prefix + "§7 | Y: §8" + offsetY); + sender.sendMessage(prefix + "§7 | Z: §8" + offsetZ); return true; } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4c496ed..447ad5b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,5 +18,5 @@ commands: usage: /where offset: description: Displays the x,y and z offset of your world - usage: /offset + usage: /offset [world] From 776dc5ce395107f26054b7964f8afaa40e6e7583 Mon Sep 17 00:00:00 2001 From: Smyler Date: Tue, 3 Oct 2023 23:41:35 +0200 Subject: [PATCH 04/11] Refactor tpll command Fix permission checks when sender is not the target. Prepare to make it work with any entity. Send messages asynchronously. Code cleanup. --- .../commands/CommandHelper.java | 80 +++ .../terraplusminus/commands/TpllCommand.java | 482 +++++++++++------- 2 files changed, 377 insertions(+), 185 deletions(-) create mode 100644 src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java diff --git a/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java b/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java new file mode 100644 index 0000000..a6231b2 --- /dev/null +++ b/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java @@ -0,0 +1,80 @@ +package de.btegermany.terraplusminus.commands; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Comparator.comparing; + +/** + * Utility class to help with commands. + * + * @author Smyler + */ +public final class CommandHelper { + + /** + * Parses a command target selector like '@a'. + * Currently supported selectors: + *
    + *
  • @a
  • + *
  • @p (doesn't select the sender)
  • + *
+ * Target selector arguments are not supported. + * It's a shame that Bukkit does not expose the vanilla command system... + * + * @param sender the {@link CommandSender command sender} + * @param selector the selector string + * @return the collection of {@link Entity entities} that match the selector + * + * @throws InvalidTargetSelectorException if the selector is invalid, either syntactically or in the specific context + */ + public static Collection parseTargetSelector(@NotNull CommandSender sender, String selector) throws InvalidTargetSelectorException { + if (selector.startsWith("@") && selector.length() >= 2) { + char selectorChar = selector.charAt(1); + if (selectorChar == 'a') { + return Bukkit.getOnlinePlayers().stream() + .map(p -> (Entity)p) + .collect(Collectors.toList()); + } else if (selectorChar == 'p' && sender instanceof Entity) { + Entity entitySender = (Entity) sender; + Location senderLocation = entitySender.getLocation(); + return Collections.singleton( + Bukkit.getOnlinePlayers().stream() + .filter(p -> p != sender) + .min(comparing(p -> p.getLocation().distanceSquared(senderLocation))) + .orElseThrow(InvalidTargetSelectorException::new) + ); + } + } else { + Player player = Bukkit.getPlayerExact(selector); + if (player == null || !player.isOnline()) { + throw new InvalidTargetSelectorException(); + } else { + return List.of(player); + } + } + + throw new InvalidTargetSelectorException(); + + } + + public static class InvalidTargetSelectorException extends Exception { + + } + + private CommandHelper() { + throw new IllegalStateException(); + } + +} diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index 42d4e0a..ea0c913 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -3,235 +3,347 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import de.btegermany.terraplusminus.Terraplusminus; -import de.btegermany.terraplusminus.data.TerraConnector; import de.btegermany.terraplusminus.gen.RealWorldGenerator; import de.btegermany.terraplusminus.utils.PluginMessageUtil; -import io.papermc.lib.PaperLib; -import net.buildtheearth.terraminusminus.generator.EarthGeneratorSettings; import net.buildtheearth.terraminusminus.projection.GeographicProjection; import net.buildtheearth.terraminusminus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraminusminus.util.geo.LatLng; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.command.Command; +import org.bukkit.command.CommandException; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; -import static org.bukkit.ChatColor.RED; +import java.text.DecimalFormat; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static de.btegermany.terraplusminus.commands.CommandHelper.InvalidTargetSelectorException; +import static de.btegermany.terraplusminus.commands.CommandHelper.parseTargetSelector; +import static io.papermc.lib.PaperLib.isChunkGenerated; +import static io.papermc.lib.PaperLib.teleportAsync; +import static java.lang.Double.isNaN; +import static java.lang.Double.parseDouble; +import static java.lang.String.join; +import static java.util.Arrays.copyOfRange; +import static java.util.Objects.requireNonNullElseGet; +import static java.util.stream.Collectors.toList; +import static net.buildtheearth.terraminusminus.util.geo.CoordinateParseUtils.parseVerbatimCoordinates; +import static org.bukkit.ChatColor.*; public class TpllCommand implements CommandExecutor { + private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("##.#####"); + @Override - public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { - //If sender is not a player cancel the command. - if (!(commandSender instanceof Player)) { - commandSender.sendMessage("This command can only be used by players!"); + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] arguments) { + + if (!sender.hasPermission("t+-.tpll")) { + sender.sendMessage(RED + "You do not have permission to use that command"); return true; } - Player player = (Player) commandSender; - - // Entity selector - - // detect if command starts with @ or with a player name - - if ((args[0].startsWith("@") || !isDouble(args[0].replace(",", ""))) && player.hasPermission("t+-.forcetpll")) { - if (args[0].equals("@a")) { - StringBuilder playerList = new StringBuilder(); - Terraplusminus.instance.getServer().getOnlinePlayers().forEach(p -> { - p.chat("/tpll " + String.join(" ", args).substring(2)); - if (Terraplusminus.instance.getServer().getOnlinePlayers().size() > 1) { - playerList.append(p.getName()).append(", "); - } else { - playerList.append(p.getName()).append(" "); - } - }); - // delete last comma if no player follows - if (playerList.length() > 0 && playerList.charAt(playerList.length() - 2) == ',') { - playerList.deleteCharAt(playerList.length() - 2); - } - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + playerList + "§7to" + String.join(" ", args).substring(2)); - return true; - } else if (args[0].equals("@p")) { - // find nearest player but not the player itself - Player nearestPlayer = null; - double nearestDistance = Double.MAX_VALUE; - for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { - if (p.getLocation().distanceSquared(player.getLocation()) < nearestDistance && (!p.equals(player) || Terraplusminus.instance.getServer().getOnlinePlayers().size() == 1)) { - nearestPlayer = p; - nearestDistance = p.getLocation().distanceSquared(player.getLocation()); - } - } - if (nearestPlayer != null) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + nearestPlayer.getName() + " §7to" + String.join(" ", args).substring(2)); - nearestPlayer.chat("/tpll " + String.join(" ", args).substring(2)); - } - return true; - } else { - Player target = null; - //check if target player is online - for (Player p : Terraplusminus.instance.getServer().getOnlinePlayers()) { - if (p.getName().equals(args[0])) { - target = p; - } - } - if (target == null) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cNo player found with name §9" + args[0]); - return true; - } + final String prefix = Terraplusminus.config.getString("prefix"); // Used in feedback messages - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported §9" + target.getName() + " §7to " + args[1] + " " + args[2]); - target.chat("/tpll " + String.join(" ", args).replace(target.getName(), "")); - return true; - } + // Option to pass through tpll to other bukkit plugins. + String passthroughTpll = Terraplusminus.config.getString("passthrough_tpll"); + if (!isNullOrEmpty(passthroughTpll)) { + Terraplusminus.instance.getServer().dispatchCommand( + sender, + passthroughTpll + ":tpll " + join(" ", arguments) + ); + return true; } - //Option to passthrough tpll to other bukkit plugins. - String passthroughTpll = Terraplusminus.config.getString("passthrough_tpll"); - if (!passthroughTpll.isEmpty()) { - //Check if any args are parsed. - if (args.length == 0) { - player.chat("/" + passthroughTpll + ":tpll"); + int consumedArguments = 0; // Number of consumed arguments, starting left + + // Parse targets first. + // They were either explicitly provided in the first argument, or it's just the sender + Collection targets = null; + if (arguments.length > 1) { + try { + targets = parseTargetSelector(sender, arguments[consumedArguments]); + consumedArguments++; + } catch (InvalidTargetSelectorException ignored) { + // Not a valid selector, probably coordinates + } + } + if (targets == null) { + if (sender instanceof Player) { + targets = Collections.singleton((Player) sender); } else { - player.chat("/" + passthroughTpll + ":tpll " + String.join(" ", args)); + return false; // Invalid command, non player senders must specify a valid target } + } else if ((targets.size() > 1 || !targets.contains(sender)) && !sender.hasPermission("t+-.forcetpll")){ + // Sender specified a target which is not themselves + sender.sendMessage( + prefix + + RED + "You do not have permission to use TPLL on others" + ); return true; } - // - - if (args.length >= 2) { - if (player.hasPermission("t+-.tpll")) { + // Now, parse destination latitude and longitude. + // This is trickier, as they may span multiple arguments as we delegate parsing to Terra--. + // We start left and arguments, stopping as soon as we found the first valid set of coordinates. + LatLng searchingLocation = null; + final LatLng geoLocation; // We are passing it to a lambda later, so it needs to be final + int i = consumedArguments; + for (; i < arguments.length && searchingLocation == null; i++) { + String rawArgumentString = join(" ", copyOfRange(arguments, consumedArguments, i + 1)); + searchingLocation = parseVerbatimCoordinates(rawArgumentString); + } + if (searchingLocation == null) { + // No valid position, command is invalid + return false; + } else { + geoLocation = searchingLocation; + } + consumedArguments = i; + + // Parse optional destination altitude + // If arguments remain after that, the command is invalid + double altitude; + if (consumedArguments == arguments.length - 1) { + // An altitude was passed in + try { + altitude = parseDouble(arguments[arguments.length - 1]); + } catch (NumberFormatException e) { + // Altitude is not valid + return false; + } + } else if (consumedArguments == arguments.length){ + // We will calculate the world height for each target latter (they may be in different worlds) + altitude = Double.NaN; + } else { + // Arguments would remain, syntax is wrong + return false; + } - double minLat = Terraplusminus.config.getDouble("min_latitude"); - double maxLat = Terraplusminus.config.getDouble("max_latitude"); - double minLon = Terraplusminus.config.getDouble("min_longitude"); - double maxLon = Terraplusminus.config.getDouble("max_longitude"); + Map worldMap = new HashMap<>(); + Map> targetMap = new HashMap<>(); - double[] coordinates = new double[2]; - coordinates[1] = Double.parseDouble(args[0].replace(",", "").replace("°", "")); - coordinates[0] = Double.parseDouble(args[1].replace("°", "")); + // Group targets by world + for (Entity target: targets) { + World world = target.getWorld(); + UUID worldId = world.getUID(); + worldMap.put(worldId, world); + targetMap.computeIfAbsent(worldId, u -> new ArrayList<>()).add(target); + } - ChunkGenerator generator = player.getWorld().getGenerator(); - if (!(generator instanceof RealWorldGenerator)) { - commandSender.sendMessage(RED + "Must be in a Terra 1 to 1 world!"); - return true; - } - RealWorldGenerator terraGenerator = (RealWorldGenerator) generator; - EarthGeneratorSettings generatorSettings = terraGenerator.getSettings(); - GeographicProjection projection = generatorSettings.projection(); - int yOffset = terraGenerator.getYOffset(); - - double[] mcCoordinates; - try { - mcCoordinates = projection.fromGeo(coordinates[0], coordinates[1]); - } catch (OutOfProjectionBoundsException e) { - commandSender.sendMessage(RED + "Location is not within projection bounds"); - return true; - } + // Dispatch targets + for (Map.Entry> entry: targetMap.entrySet()) { + UUID worldId = entry.getKey(); + World world = worldMap.get(worldId); + Collection worldTargets = entry.getValue(); + try { + this.dispatchTargetsInWorld(sender, worldTargets, world, geoLocation, altitude); + } catch (CommandException e) { + sender.sendMessage(prefix + RED + "Could not teleport " + this.formatTargetList(targets) + ", " + e.getMessage()); + } + } - if (minLat != 0 && maxLat != 0 && minLon != 0 && maxLon != 0 && !player.hasPermission("t+-.admin")) { - if (coordinates[1] < minLat || coordinates[0] < minLon || coordinates[1] > maxLat || coordinates[0] > maxLon) { - player.sendMessage(Terraplusminus.config.getString("prefix") + RED + "You cannot tpll to these coordinates, because this area is being worked on by another build team."); - return true; - } - } + return true; + } - TerraConnector terraConnector = new TerraConnector(); + private void dispatchTargetsInWorld(CommandSender sender, Collection targets, World world, final LatLng geolocation, double altitude) throws CommandException { - double height; - if (args.length >= 3) { - height = Double.parseDouble(args[2]) + yOffset; - } else { - height = terraConnector.getHeight((int) mcCoordinates[0], (int) mcCoordinates[1]).join() + yOffset; - } - if (height > player.getWorld().getMaxHeight()) { - if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { - - //send player uuid and coordinates to bungee - - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF(player.getUniqueId().toString()); - - if (PluginMessageUtil.getNextServerName() != null) { - out.writeUTF(PluginMessageUtil.getNextServerName()); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); - return true; - } - - out.writeUTF(coordinates[1] + ", " + coordinates[0]); - player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); - - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); - return true; - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not high enough at the moment."); - return true; - } - } else if (height <= player.getWorld().getMinHeight()) { - if (Terraplusminus.config.getBoolean("linked_servers.enabled")) { - - //send player uuid and coordinates to bungee - - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF(player.getUniqueId().toString()); - - if (PluginMessageUtil.getLastServerName() != null) { - out.writeUTF(PluginMessageUtil.getLastServerName()); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cPlease contact server administrator. Your config is not set up correctly."); - return true; - } - - out.writeUTF(coordinates[1] + ", " + coordinates[0]); - player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", out.toByteArray()); - - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cSending to another server..."); - return true; - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§cYou cannot tpll to these coordinates, because the world is not low enough at the moment."); - return true; - } - } - Location location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - - if (PaperLib.isChunkGenerated(location)) { - if (args.length >= 3) { - location = new Location(player.getWorld(), mcCoordinates[0], height, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - } else { - location = new Location(player.getWorld(), mcCoordinates[0], player.getWorld().getHighestBlockYAt((int) mcCoordinates[0], (int) mcCoordinates[1]) + 1, mcCoordinates[1], player.getLocation().getYaw(), player.getLocation().getPitch()); - } - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Location is generating. Please wait a moment..."); - } - PaperLib.teleportAsync(player, location); + // Get X and Z + Location destination = this.projectGeolocation(world, geolocation, altitude); + double y = destination.getY(); + boolean tooLow = y < world.getMinHeight(); + boolean tooHigh = y >= world.getMaxHeight(); - if (args.length >= 3) { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + ", " + height + "."); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Teleported to " + coordinates[1] + ", " + coordinates[0] + "."); - } + String prefix = Terraplusminus.config.getString("prefix"); - return true; + if (tooLow || tooHigh) { + // Not within world bounds, we either send to a different server or fail + if (!Terraplusminus.config.getBoolean("linked_servers.enabled")) { + throw new CommandException("the world is not " + (tooLow ? "low" : "high") + " enough at the moment"); + } + final String server; + if (tooLow) { + server = PluginMessageUtil.getLastServerName(); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /tpll"); - return true; + server = PluginMessageUtil.getNextServerName(); } + if (server == null) { + throw new CommandException("the server configuration is not set up properly, please contact the server administrator"); + } + targets.forEach(t -> this.dispatchToOtherServer(sender, t, server, geolocation)); + } else if (!sender.hasPermission("t+-.admin") && !this.isWithinTeleportationBounds(world, geolocation)) { + throw new CommandException("you cannot tpll to these coordinates, because this area is being worked on by another build team"); } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7Usage: /tpll "); - return true; + if (!isChunkGenerated(destination)) { + sender.sendMessage(prefix + "Generating destination in world " + world.getName() + "..."); + } + final Map> futures = new HashMap<>(); + targets.forEach( + target -> futures.put(target, teleportAsync(target, destination)) + ); + + // Wait for all teleportations to complete + CompletableFuture.allOf(futures.values().toArray(CompletableFuture[]::new)).thenAccept(unused -> { + List success = futures.entrySet().stream() + .filter(e -> { + try { + return e.getValue().get(); + } catch (InterruptedException | ExecutionException ex) { + throw new IllegalStateException(); + } + }) + .map(Map.Entry::getKey) + .collect(toList()); + List failures = futures.entrySet().stream() + .filter(e -> { + try { + return !e.getValue().get(); + } catch (InterruptedException | ExecutionException ex) { + throw new IllegalStateException(); + } + }) + .map(Map.Entry::getKey) + .collect(toList()); + if (!failures.isEmpty()) { + sender.sendMessage(prefix + RED + "Failed to teleport §9" + this.formatTargetList(success) + RED + " to " + this.formatDestination(geolocation)); + } + if (!success.isEmpty()) { + sender.sendMessage(prefix + "§7Teleported §9" + this.formatTargetList(success) + " §7to " + this.formatDestination(geolocation)); + } + }); } } - public boolean isDouble(String str) { + private Location projectGeolocation(World world, LatLng geolocation, double altitude) throws CommandException { + + ChunkGenerator chunkGenerator = world.getGenerator(); + if (!(chunkGenerator instanceof RealWorldGenerator)) { + throw new CommandException("world " + world.getName() + " is not a Terra+- world"); + } + + RealWorldGenerator generator = (RealWorldGenerator) chunkGenerator; + + // We update this later + final Location destination = new Location(world, 0, 0, 0); + + // Set destination X and Z + GeographicProjection projection = generator.getSettings().projection(); try { - Double.parseDouble(str); - return true; - } catch (NumberFormatException e) { - return false; + double[] xz = projection.fromGeo(geolocation.getLng(), geolocation.getLat()); + destination.setX(xz[0]); + destination.setZ(xz[1]); + } catch (OutOfProjectionBoundsException e) { + throw new CommandException("destination is out of projection bounds"); } + + // Set destination Y + if (isNaN(altitude)) { // Is set to NaN when not explicitly provided by the command sender + destination.setY(world.getHighestBlockYAt(destination)); + } else { + destination.setY(altitude + generator.getYOffset()); + } + + return destination; } + + @SuppressWarnings("unused") // Passing world as an argument because ideally we could set boundaries per-world + private boolean isWithinTeleportationBounds(World world, LatLng geolocation) { + + // Read the configuration + double minLat = Terraplusminus.config.getDouble("min_latitude"); + double maxLat = Terraplusminus.config.getDouble("max_latitude"); + double minLon = Terraplusminus.config.getDouble("min_longitude"); + double maxLon = Terraplusminus.config.getDouble("max_longitude"); + + // Keeping this for backward compatibility + // This is a bit abusive, there are legitimate use cases where the bounds could be 0. + // The best approach would probably be to simply make it optional in the config + if (minLat == 0d || maxLat == 0d || minLon == 0d || maxLon == 0d) { + return true; // No bounds configured + } + + // Actual boundary check (bounds inclusive) + double latitude = geolocation.getLat(); + double longitude = geolocation.getLng(); + return latitude >= minLat && longitude >= minLon & latitude >= maxLat && longitude >= maxLon; + } + + private void dispatchToOtherServer(CommandSender sender, Entity target, String server, LatLng geolocation) { + + // Make sure target is a player + String prefix = Terraplusminus.config.getString("prefix"); + if (!(target instanceof Player)) { + sender.sendMessage(prefix + + RED + "Cannot teleport " + GRAY + this.formatTargetName(target) + + RED + ": destination is outside of range and only players may be sent to linked servers." + ); + return; + } + Player player = (Player) target; + + + // Send the message to the proxy using the player's connexion + byte[] message = this.encodeTerraLinkDispatchMessage(player, server, geolocation); + player.sendPluginMessage(Terraplusminus.instance, "bungeecord:terraplusminus", message); + + // Send feedback to command sender and target + sender.sendMessage(prefix + + GRAY + "Sending " + DARK_GRAY + this.formatTargetName(target) + + GRAY + " to server " + DARK_GRAY + server + GRAY + "." + ); + player.sendMessage(prefix + + "§cSending to another server..." + ); + + } + + private String formatTargetName(Entity target) { + return requireNonNullElseGet(target.getCustomName(), target::getName); + } + + private String formatTargetList(Collection targets) { + if (targets.isEmpty()) { + return ""; + } + List names = new ArrayList<>(); + targets.stream() + .map(this::formatTargetName) + .sorted() + .forEach(names::add); + if (names.size() == 1) { + return names.get(0); + } + String last = names.remove(names.size() - 1); + return join(", ", names) + " and " + last; + } + + private String formatDestination(LatLng location) { + return + BLUE + DECIMAL_FORMATTER.format(location.getLat()) + + GRAY + ", " + + BLUE + DECIMAL_FORMATTER.format(location.getLng()); + } + + private byte[] encodeTerraLinkDispatchMessage(Player player, String serverName, LatLng destination) { + // Keeping it as is for backward compatibility, + // but we could cut down message length by more than half by sending doubles and longs directly + // That would also eliminate the cost of encoding numbers to UTF-8 and having to send string length information + // Also, relying on a beta functionality is not ideal when there are viable alternatives in the JDK + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(player.getUniqueId().toString()); + out.writeUTF(serverName); + String coordinateString = destination.getLat() + ", " + destination.getLng(); + out.writeUTF(coordinateString); + return out.toByteArray(); + } + } From 900dd15582a04f0ccbdb11965fa3b9c2b88e8fb3 Mon Sep 17 00:00:00 2001 From: Smyler Date: Wed, 4 Oct 2023 13:18:18 +0200 Subject: [PATCH 05/11] Refactor /where command Remove unnecessary command name check. Get the projection from the world generator. Send a proper error message when out of projection bounds. Do not use deprecated HoverEvent(Action, TextComponent) constructor. --- .../terraplusminus/commands/WhereCommand.java | 80 +++++++++++-------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java index 6b25cef..1531e58 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java @@ -1,54 +1,68 @@ package de.btegermany.terraplusminus.commands; import de.btegermany.terraplusminus.Terraplusminus; -import net.buildtheearth.terraminusminus.generator.EarthGeneratorSettings; +import de.btegermany.terraplusminus.gen.RealWorldGenerator; +import net.buildtheearth.terraminusminus.projection.GeographicProjection; import net.buildtheearth.terraminusminus.projection.OutOfProjectionBoundsException; import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; +import java.text.DecimalFormat; + +import static org.bukkit.ChatColor.RED; + public class WhereCommand implements CommandExecutor { - private final EarthGeneratorSettings bteGeneratorSettings = EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS); + private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("##.#####"); @Override - public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { - if (command.getName().equalsIgnoreCase("where")) { - if (!(commandSender instanceof Player)) { - commandSender.sendMessage("This command can only be used by players!"); - return true; - } - Player player = (Player) commandSender; - if (player.hasPermission("t+-.where")) { - int xOffset = Terraplusminus.config.getInt("terrain_offset.x"); - int zOffset = Terraplusminus.config.getInt("terrain_offset.z"); - - double[] mcCoordinates = new double[2]; - mcCoordinates[0] = player.getLocation().getX() - xOffset; - mcCoordinates[1] = player.getLocation().getZ() - zOffset; - System.out.println(mcCoordinates[0] + ", " + mcCoordinates[1]); - double[] coordinates = new double[0]; - try { - coordinates = bteGeneratorSettings.projection().toGeo(mcCoordinates[0], mcCoordinates[1]); - } catch (OutOfProjectionBoundsException e) { - e.printStackTrace(); - } - TextComponent message = new TextComponent(Terraplusminus.config.getString("prefix") + "§7Your coordinates are:"); - message.addExtra("\n§8" + coordinates[1] + ", " + coordinates[0] + "§7."); - message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://maps.google.com/maps?t=k&q=loc:" + coordinates[1] + "+" + coordinates[0])); - message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Click here to view in Google Maps.").create())); - player.spigot().sendMessage(message); - } else { - player.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /where"); - return true; - } + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { + String prefix = Terraplusminus.config.getString("prefix"); + if (!(sender instanceof Player)) { + sender.sendMessage("This command can only be used by players!"); + return true; + } + if (!sender.hasPermission("t+-.where")) { + sender.sendMessage(prefix + "§7No permission for /where"); + return true; + } + + Player player = (Player) sender; + World world = player.getWorld(); + ChunkGenerator chunkGenerator = world.getGenerator(); + + if (!(chunkGenerator instanceof RealWorldGenerator)) { + sender.sendMessage(prefix + RED + "Not a Terra+- world."); + return true; + } + + RealWorldGenerator generator = (RealWorldGenerator) chunkGenerator; + GeographicProjection projection = generator.getSettings().projection(); + + final TextComponent message = new TextComponent(prefix); + try { + Location location = player.getLocation(); + double[] coordinates = projection.toGeo(location.getX(), location.getZ()); + double longitude = coordinates[0]; + double latitude = coordinates[1]; + String googleMapsUrl = "https://maps.google.com/maps?t=k&q=loc:" + coordinates[1] + "+" + coordinates[0]; + message.addExtra("§7Your coordinates are:\n§8" + DECIMAL_FORMATTER.format(latitude) + ", " + DECIMAL_FORMATTER.format(longitude) + "§7."); + message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, googleMapsUrl)); + message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Click here to view in Google Maps."))); + } catch (OutOfProjectionBoundsException e) { + message.addExtra(RED + "Outside projection bounds."); } + player.spigot().sendMessage(message); return true; } } From a49141db1c9bc665657ecdcf73a74642f0cad032 Mon Sep 17 00:00:00 2001 From: Smyler Date: Wed, 4 Oct 2023 13:20:57 +0200 Subject: [PATCH 06/11] Use new Google Maps URL --- .../de/btegermany/terraplusminus/commands/WhereCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java index 1531e58..6362d93 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java @@ -55,7 +55,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command double[] coordinates = projection.toGeo(location.getX(), location.getZ()); double longitude = coordinates[0]; double latitude = coordinates[1]; - String googleMapsUrl = "https://maps.google.com/maps?t=k&q=loc:" + coordinates[1] + "+" + coordinates[0]; + String googleMapsUrl = "https://www.google.com/maps/@" + latitude + "," + longitude; message.addExtra("§7Your coordinates are:\n§8" + DECIMAL_FORMATTER.format(latitude) + ", " + DECIMAL_FORMATTER.format(longitude) + "§7."); message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, googleMapsUrl)); message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Click here to view in Google Maps."))); From a9adb2b9d297401a162df82be1dae3bc16b0800e Mon Sep 17 00:00:00 2001 From: Smyler Date: Thu, 5 Oct 2023 11:07:35 +0200 Subject: [PATCH 07/11] Make /where usable from the console --- .../commands/CommandHelper.java | 21 ++++- .../terraplusminus/commands/TpllCommand.java | 15 ++-- .../terraplusminus/commands/WhereCommand.java | 78 ++++++++++++------- src/main/resources/plugin.yml | 4 +- 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java b/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java index a6231b2..251540e 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/CommandHelper.java @@ -9,12 +9,11 @@ import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNullElseGet; /** * Utility class to help with commands. @@ -69,6 +68,24 @@ public static Collection parseTargetSelector(@NotNull CommandSender send } + /** + * Checks if a collection of entities is comprised solely of a single command sender (which may appear multiple times). + * This is useful for permission checks where one might allow a sender to execute a command on themselves, + * but not on other entities. + * + * @param sender the {@link CommandSender sender} executing the command + * @param targets the {@link Collection collection} of targets + * + * @return whether a {@link CommandSender sender} is the only entity in a {@link Collection collection} of targets + */ + public static boolean senderIsSoleTarget(CommandSender sender, @NotNull Collection targets) { + return !targets.stream().allMatch(target -> target == sender); + } + + public static String formatTargetName(Entity target) { + return requireNonNullElseGet(target.getCustomName(), target::getName); + } + public static class InvalidTargetSelectorException extends Exception { } diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index ea0c913..0d888fe 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -25,15 +25,13 @@ import java.util.concurrent.ExecutionException; import static com.google.common.base.Strings.isNullOrEmpty; -import static de.btegermany.terraplusminus.commands.CommandHelper.InvalidTargetSelectorException; -import static de.btegermany.terraplusminus.commands.CommandHelper.parseTargetSelector; +import static de.btegermany.terraplusminus.commands.CommandHelper.*; import static io.papermc.lib.PaperLib.isChunkGenerated; import static io.papermc.lib.PaperLib.teleportAsync; import static java.lang.Double.isNaN; import static java.lang.Double.parseDouble; import static java.lang.String.join; import static java.util.Arrays.copyOfRange; -import static java.util.Objects.requireNonNullElseGet; import static java.util.stream.Collectors.toList; import static net.buildtheearth.terraminusminus.util.geo.CoordinateParseUtils.parseVerbatimCoordinates; import static org.bukkit.ChatColor.*; @@ -82,7 +80,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command } else { return false; // Invalid command, non player senders must specify a valid target } - } else if ((targets.size() > 1 || !targets.contains(sender)) && !sender.hasPermission("t+-.forcetpll")){ + } else if (!sender.hasPermission("t+-.forcetpll") && !senderIsSoleTarget(sender, targets)){ // Sender specified a target which is not themselves sender.sendMessage( prefix @@ -283,7 +281,7 @@ private void dispatchToOtherServer(CommandSender sender, Entity target, String s String prefix = Terraplusminus.config.getString("prefix"); if (!(target instanceof Player)) { sender.sendMessage(prefix + - RED + "Cannot teleport " + GRAY + this.formatTargetName(target) + + RED + "Cannot teleport " + GRAY + formatTargetName(target) + RED + ": destination is outside of range and only players may be sent to linked servers." ); return; @@ -297,7 +295,7 @@ private void dispatchToOtherServer(CommandSender sender, Entity target, String s // Send feedback to command sender and target sender.sendMessage(prefix + - GRAY + "Sending " + DARK_GRAY + this.formatTargetName(target) + + GRAY + "Sending " + DARK_GRAY + formatTargetName(target) + GRAY + " to server " + DARK_GRAY + server + GRAY + "." ); player.sendMessage(prefix + @@ -306,9 +304,6 @@ private void dispatchToOtherServer(CommandSender sender, Entity target, String s } - private String formatTargetName(Entity target) { - return requireNonNullElseGet(target.getCustomName(), target::getName); - } private String formatTargetList(Collection targets) { if (targets.isEmpty()) { @@ -316,7 +311,7 @@ private String formatTargetList(Collection targets) { } List names = new ArrayList<>(); targets.stream() - .map(this::formatTargetName) + .map(CommandHelper::formatTargetName) .sorted() .forEach(names::add); if (names.size() == 1) { diff --git a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java index 6362d93..ffb292e 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java @@ -4,6 +4,7 @@ import de.btegermany.terraplusminus.gen.RealWorldGenerator; import net.buildtheearth.terraminusminus.projection.GeographicProjection; import net.buildtheearth.terraminusminus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraminusminus.util.geo.LatLng; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; @@ -13,12 +14,17 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; import java.text.DecimalFormat; +import java.util.Collection; +import static de.btegermany.terraplusminus.commands.CommandHelper.*; +import static java.util.Collections.singleton; +import static java.util.Objects.requireNonNullElse; import static org.bukkit.ChatColor.RED; public class WhereCommand implements CommandExecutor { @@ -28,41 +34,61 @@ public class WhereCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { String prefix = Terraplusminus.config.getString("prefix"); - if (!(sender instanceof Player)) { - sender.sendMessage("This command can only be used by players!"); - return true; - } + if (!sender.hasPermission("t+-.where")) { sender.sendMessage(prefix + "§7No permission for /where"); return true; } - Player player = (Player) sender; - World world = player.getWorld(); - ChunkGenerator chunkGenerator = world.getGenerator(); + // Parse targets + final Collection targets; + if (args.length == 0 && sender instanceof Player) { + targets = singleton((Player) sender); + } else if (args.length == 1) { + try { + targets = parseTargetSelector(sender, args[0]); + if (!sender.hasPermission("t+-.admin") && !senderIsSoleTarget(sender, targets)) { + String message = requireNonNullElse(command.getPermissionMessage(), RED + "Missing permission"); + sender.sendMessage(message); + return true; + } + } catch (CommandHelper.InvalidTargetSelectorException ignored) { + return false; + } + } else { + return false; + } - if (!(chunkGenerator instanceof RealWorldGenerator)) { - sender.sendMessage(prefix + RED + "Not a Terra+- world."); - return true; + // Do the magic + for (Entity target: targets) { + final TextComponent message = new TextComponent(prefix); + try { + LatLng geolocation = this.getGeolocation(target); + String googleMapsUrl = "https://www.google.com/maps/@" + geolocation.getLat() + "," + geolocation.getLng(); + String targetVerb = target == sender ? "Your": formatTargetName(target) + "'s"; + message.addExtra("§7" + targetVerb + " coordinates are:\n§8" + DECIMAL_FORMATTER.format(geolocation.getLat()) + ", " + DECIMAL_FORMATTER.format(geolocation.getLng()) + "§7."); + message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, googleMapsUrl)); + message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Click here to view in Google Maps."))); + } catch (OutOfProjectionBoundsException e) { + String targetVerb = target == sender? "You are": formatTargetName(target) + " is"; + message.addExtra(RED + targetVerb + " not in a Terra+- world or outside projection bounds."); + } + sender.spigot().sendMessage(message); } + return true; + } + private LatLng getGeolocation(Entity target) throws OutOfProjectionBoundsException { + World world = target.getWorld(); + ChunkGenerator chunkGenerator = world.getGenerator(); + if (!(chunkGenerator instanceof RealWorldGenerator)) { + throw OutOfProjectionBoundsException.get(); + } RealWorldGenerator generator = (RealWorldGenerator) chunkGenerator; GeographicProjection projection = generator.getSettings().projection(); - - final TextComponent message = new TextComponent(prefix); - try { - Location location = player.getLocation(); - double[] coordinates = projection.toGeo(location.getX(), location.getZ()); - double longitude = coordinates[0]; - double latitude = coordinates[1]; - String googleMapsUrl = "https://www.google.com/maps/@" + latitude + "," + longitude; - message.addExtra("§7Your coordinates are:\n§8" + DECIMAL_FORMATTER.format(latitude) + ", " + DECIMAL_FORMATTER.format(longitude) + "§7."); - message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, googleMapsUrl)); - message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Click here to view in Google Maps."))); - } catch (OutOfProjectionBoundsException e) { - message.addExtra(RED + "Outside projection bounds."); - } - player.spigot().sendMessage(message); - return true; + Location location = target.getLocation(); + double[] coordinates = projection.toGeo(location.getX(), location.getZ()); + return new LatLng(coordinates[1], coordinates[0]); } + } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 447ad5b..c87db84 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,11 +11,11 @@ load: STARTUP commands: tpll: description: Teleports you to longitude and latitude - usage: /tpll + usage: /tpll [target] aliases: [ tpc ] where: description: Gives you the longitude and latitude of your minecraft coordinates - usage: /where + usage: /where [target] offset: description: Displays the x,y and z offset of your world usage: /offset [world] From aae168086f0db2c938923b4d9afded526cb1b5e8 Mon Sep 17 00:00:00 2001 From: Smyler Date: Thu, 5 Oct 2023 11:31:57 +0200 Subject: [PATCH 08/11] Use ChatColor instead of literal color codes --- .../terraplusminus/commands/OffsetCommand.java | 13 ++++++------- .../terraplusminus/commands/TpllCommand.java | 6 +++--- .../terraplusminus/commands/WhereCommand.java | 8 ++++---- .../terraplusminus/events/PlayerMoveEvent.java | 6 +++++- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java index 600ae6a..f75c589 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java @@ -14,8 +14,7 @@ import org.jetbrains.annotations.NotNull; import static java.lang.Math.round; -import static org.bukkit.ChatColor.GRAY; -import static org.bukkit.ChatColor.RED; +import static org.bukkit.ChatColor.*; public class OffsetCommand implements CommandExecutor { @@ -25,7 +24,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command String prefix = Terraplusminus.config.getString("prefix"); if (!sender.hasPermission("t+-.offset")) { - sender.sendMessage(Terraplusminus.config.getString("prefix") + "§7No permission for /offset"); + sender.sendMessage(Terraplusminus.config.getString("prefix") + GRAY + "No permission for /offset"); return true; } @@ -65,10 +64,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command offsetX = offsetZ = 0; } - sender.sendMessage(prefix + "§7Offsets for world" + world.getName() + ":"); - sender.sendMessage(prefix + "§7 | X: §8" + offsetX); - sender.sendMessage(prefix + "§7 | Y: §8" + offsetY); - sender.sendMessage(prefix + "§7 | Z: §8" + offsetZ); + sender.sendMessage(prefix + GRAY + "Offsets for world" + world.getName() + ":"); + sender.sendMessage(prefix + GRAY + " | X: " + DARK_GRAY + offsetX); + sender.sendMessage(prefix + GRAY + " | Y: " + DARK_GRAY + offsetY); + sender.sendMessage(prefix + GRAY + " | Z: " + DARK_GRAY + offsetZ); return true; } } diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index 0d888fe..8e6cfdf 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -212,10 +212,10 @@ private void dispatchTargetsInWorld(CommandSender sender, Collection tar .map(Map.Entry::getKey) .collect(toList()); if (!failures.isEmpty()) { - sender.sendMessage(prefix + RED + "Failed to teleport §9" + this.formatTargetList(success) + RED + " to " + this.formatDestination(geolocation)); + sender.sendMessage(prefix + RED + "Failed to teleport " + BLUE + this.formatTargetList(success) + RED + " to " + this.formatDestination(geolocation)); } if (!success.isEmpty()) { - sender.sendMessage(prefix + "§7Teleported §9" + this.formatTargetList(success) + " §7to " + this.formatDestination(geolocation)); + sender.sendMessage(prefix + GRAY + "Teleported " + BLUE + this.formatTargetList(success) + GRAY + " to " + this.formatDestination(geolocation)); } }); } @@ -299,7 +299,7 @@ private void dispatchToOtherServer(CommandSender sender, Entity target, String s GRAY + " to server " + DARK_GRAY + server + GRAY + "." ); player.sendMessage(prefix + - "§cSending to another server..." + RED + "Sending to another server..." ); } diff --git a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java index ffb292e..1f09a56 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java @@ -25,7 +25,7 @@ import static de.btegermany.terraplusminus.commands.CommandHelper.*; import static java.util.Collections.singleton; import static java.util.Objects.requireNonNullElse; -import static org.bukkit.ChatColor.RED; +import static org.bukkit.ChatColor.*; public class WhereCommand implements CommandExecutor { @@ -36,7 +36,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command String prefix = Terraplusminus.config.getString("prefix"); if (!sender.hasPermission("t+-.where")) { - sender.sendMessage(prefix + "§7No permission for /where"); + sender.sendMessage(prefix + GRAY + "No permission for /where"); return true; } @@ -66,9 +66,9 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command LatLng geolocation = this.getGeolocation(target); String googleMapsUrl = "https://www.google.com/maps/@" + geolocation.getLat() + "," + geolocation.getLng(); String targetVerb = target == sender ? "Your": formatTargetName(target) + "'s"; - message.addExtra("§7" + targetVerb + " coordinates are:\n§8" + DECIMAL_FORMATTER.format(geolocation.getLat()) + ", " + DECIMAL_FORMATTER.format(geolocation.getLng()) + "§7."); + message.addExtra(GRAY + targetVerb + " coordinates are:\n" + DARK_GRAY + DECIMAL_FORMATTER.format(geolocation.getLat()) + ", " + DECIMAL_FORMATTER.format(geolocation.getLng()) + GRAY + "."); message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, googleMapsUrl)); - message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Click here to view in Google Maps."))); + message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(GRAY + "Click here to view in Google Maps."))); } catch (OutOfProjectionBoundsException e) { String targetVerb = target == sender? "You are": formatTargetName(target) + " is"; message.addExtra(RED + targetVerb + " not in a Terra+- world or outside projection bounds."); diff --git a/src/main/java/de/btegermany/terraplusminus/events/PlayerMoveEvent.java b/src/main/java/de/btegermany/terraplusminus/events/PlayerMoveEvent.java index d6f343a..5da9345 100644 --- a/src/main/java/de/btegermany/terraplusminus/events/PlayerMoveEvent.java +++ b/src/main/java/de/btegermany/terraplusminus/events/PlayerMoveEvent.java @@ -4,6 +4,7 @@ import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -12,6 +13,9 @@ import java.util.ArrayList; +import static java.lang.String.valueOf; +import static org.bukkit.ChatColor.BOLD; + public class PlayerMoveEvent implements Listener { @@ -33,7 +37,7 @@ void onPlayerMove(org.bukkit.event.player.PlayerMoveEvent event) { @Override public void run() { int height = p.getLocation().getBlockY() - yOffset; - p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent("§l" + height + "m")); + p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(BOLD + valueOf(height) + "m")); } }; runnable.runTaskTimer(plugin, 0, 20); From 2ac0530f9b9cc5ed163aecdeec04ca6e32c6449d Mon Sep 17 00:00:00 2001 From: Smyler Date: Thu, 5 Oct 2023 12:13:02 +0200 Subject: [PATCH 09/11] Add tab completion to /offset --- .../terraplusminus/Terraplusminus.java | 30 +++++++++++++++++-- .../commands/OffsetCommand.java | 25 ++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java b/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java index fa80c51..8819856 100644 --- a/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java +++ b/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java @@ -11,6 +11,8 @@ import de.btegermany.terraplusminus.utils.FileBuilder; import de.btegermany.terraplusminus.utils.PlayerHashMapManagement; import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.*; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -21,6 +23,9 @@ import java.io.*; import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.lang.String.format; public final class Terraplusminus extends JavaPlugin implements Listener { public static FileConfiguration config; @@ -76,9 +81,9 @@ public void onEnable() { // -------------------------- // Registering commands - getCommand("tpll").setExecutor(new TpllCommand()); - getCommand("where").setExecutor(new WhereCommand()); - getCommand("offset").setExecutor(new OffsetCommand()); + this.registerCommand("tpll", new TpllCommand()); + this.registerCommand("where", new WhereCommand()); + this.registerCommand("offset", new OffsetCommand()); // -------------------------- Bukkit.getLogger().log(Level.INFO, "[T+-] Terraplusminus successfully enabled"); @@ -190,4 +195,23 @@ private void updateConfig() { } } + private void registerCommand(String commandName, CommandExecutor executor) { + Logger logger = this.getLogger(); + PluginCommand command = this.getCommand(commandName); + if (command == null) { + logger.warning(format("Could not register command %s. Command is unknown to bukkit, has it been properly registered in plugin.yml?", commandName)); + return; + } + command.setExecutor(executor); + logger.fine(format("Registered command executor for /%s command", commandName)); + if (executor instanceof TabCompleter) { + command.setTabCompleter((TabCompleter) executor); + logger.fine(format("Registered tab completer for /%s command", commandName)); + } + } + + public static boolean isTerraWorld(World world) { + return world.getGenerator() instanceof RealWorldGenerator; + } + } \ No newline at end of file diff --git a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java index f75c589..3077421 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/OffsetCommand.java @@ -7,16 +7,21 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; import static java.lang.Math.round; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; import static org.bukkit.ChatColor.*; -public class OffsetCommand implements CommandExecutor { +public class OffsetCommand implements TabExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String... arguments) { @@ -64,10 +69,24 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command offsetX = offsetZ = 0; } - sender.sendMessage(prefix + GRAY + "Offsets for world" + world.getName() + ":"); + sender.sendMessage(prefix + GRAY + "Offsets for world \"" + DARK_GRAY + world.getName() + GRAY + "\":"); sender.sendMessage(prefix + GRAY + " | X: " + DARK_GRAY + offsetX); sender.sendMessage(prefix + GRAY + " | Y: " + DARK_GRAY + offsetY); sender.sendMessage(prefix + GRAY + " | Z: " + DARK_GRAY + offsetZ); return true; } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (args.length > 1 || !sender.hasPermission("t+-.offset")) { + return emptyList(); + } + + return Bukkit.getWorlds().stream() + .filter(Terraplusminus::isTerraWorld) + .map(World::getName) + .collect(toList()); + } + } From 04eabb159e07055569d0ad8b9217e423cff780b0 Mon Sep 17 00:00:00 2001 From: Smyler Date: Thu, 5 Oct 2023 12:30:27 +0200 Subject: [PATCH 10/11] Add tab completion to /where --- .../terraplusminus/commands/WhereCommand.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java index 1f09a56..b89d75a 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/WhereCommand.java @@ -9,25 +9,29 @@ import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.text.DecimalFormat; import java.util.Collection; +import java.util.List; import static de.btegermany.terraplusminus.commands.CommandHelper.*; -import static java.util.Collections.singleton; +import static java.util.Collections.*; import static java.util.Objects.requireNonNullElse; +import static java.util.stream.Collectors.toList; import static org.bukkit.ChatColor.*; -public class WhereCommand implements CommandExecutor { +public class WhereCommand implements TabExecutor { private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("##.#####"); @@ -91,4 +95,22 @@ private LatLng getGeolocation(Entity target) throws OutOfProjectionBoundsExcepti return new LatLng(coordinates[1], coordinates[0]); } + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + + if (args.length > 1 || !sender.hasPermission("t+-.where")) { + return emptyList(); + } + + if (sender.hasPermission("t+-.admin")) { + return Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .collect(toList()); + } else if (sender instanceof Player) { + return singletonList(sender.getName()); + } + + return emptyList(); + } } From 857edb576bde0475b7a21286fbf9776add8c859b Mon Sep 17 00:00:00 2001 From: Smyler Date: Thu, 5 Oct 2023 18:30:43 +0200 Subject: [PATCH 11/11] Add tab completion to /tpll --- .../terraplusminus/Terraplusminus.java | 5 +++ .../terraplusminus/commands/TpllCommand.java | 35 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java b/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java index 8819856..d9a4b86 100644 --- a/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java +++ b/src/main/java/de/btegermany/terraplusminus/Terraplusminus.java @@ -14,6 +14,7 @@ import org.bukkit.World; import org.bukkit.command.*; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.WorldInitEvent; @@ -214,4 +215,8 @@ public static boolean isTerraWorld(World world) { return world.getGenerator() instanceof RealWorldGenerator; } + public static boolean isInTerraWorld(Entity entity) { + return isTerraWorld(entity.getWorld()); + } + } \ No newline at end of file diff --git a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java index 8e6cfdf..e496301 100644 --- a/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java +++ b/src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java @@ -8,16 +8,15 @@ import net.buildtheearth.terraminusminus.projection.GeographicProjection; import net.buildtheearth.terraminusminus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraminusminus.util.geo.LatLng; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandException; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; +import org.bukkit.command.*; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.text.DecimalFormat; import java.util.*; @@ -25,6 +24,7 @@ import java.util.concurrent.ExecutionException; import static com.google.common.base.Strings.isNullOrEmpty; +import static de.btegermany.terraplusminus.Terraplusminus.isInTerraWorld; import static de.btegermany.terraplusminus.commands.CommandHelper.*; import static io.papermc.lib.PaperLib.isChunkGenerated; import static io.papermc.lib.PaperLib.teleportAsync; @@ -32,12 +32,14 @@ import static java.lang.Double.parseDouble; import static java.lang.String.join; import static java.util.Arrays.copyOfRange; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static net.buildtheearth.terraminusminus.util.geo.CoordinateParseUtils.parseVerbatimCoordinates; import static org.bukkit.ChatColor.*; -public class TpllCommand implements CommandExecutor { +public class TpllCommand implements TabExecutor { private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("##.#####"); @@ -341,4 +343,27 @@ private byte[] encodeTerraLinkDispatchMessage(Player player, String serverName, return out.toByteArray(); } + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + + if (!sender.hasPermission("t+-.tpll")) { + return emptyList(); + } + + // First argument: target + if (args.length == 1) { + if (sender.hasPermission("t+-.forcetpll")) { + return Bukkit.getOnlinePlayers().stream() + .filter(Terraplusminus::isInTerraWorld) + .map(Player::getName) + .collect(toList()); + } else if (sender instanceof Player && isInTerraWorld((Player) sender)) { + return singletonList(sender.getName()); + } + } + + // We can't tab-complete the location or altitude, so we are done here + return emptyList(); + } }