From 76e511a774bbb013b28bee9ad715e02ff762fb71 Mon Sep 17 00:00:00 2001 From: pop4959 Date: Mon, 6 Jul 2020 11:53:43 -0700 Subject: [PATCH] Implement random teleport command (#3418) Adds `/tpr` and `/settpr` commands, which respectively allow you to teleport randomly or set teleportation parameters. Server owners are expected to set the center with `/settpr` before players can use `/tpr`. They can also set the minimum and maximum range to be teleported from the center (default 0-1000). Also includes an event where plugins can adjust or cancel the teleport. Closes #3154. --- .../com/earth2me/essentials/Essentials.java | 15 +- .../com/earth2me/essentials/IEssentials.java | 2 + .../earth2me/essentials/RandomTeleport.java | 175 ++++++++++++++++++ .../essentials/commands/Commandsettpr.java | 43 +++++ .../essentials/commands/Commandtpr.java | 48 +++++ Essentials/src/messages.properties | 7 + .../api/events/UserRandomTeleportEvent.java | 75 ++++++++ Essentials/src/plugin.yml | 8 + Essentials/src/tpr.yml | 16 ++ 9 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 Essentials/src/com/earth2me/essentials/RandomTeleport.java create mode 100644 Essentials/src/com/earth2me/essentials/commands/Commandsettpr.java create mode 100644 Essentials/src/com/earth2me/essentials/commands/Commandtpr.java create mode 100644 Essentials/src/net/ess3/api/events/UserRandomTeleportEvent.java create mode 100644 Essentials/src/tpr.yml diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index 0b30a4ee3..47be9587a 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -113,6 +113,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient ServerStateProvider serverStateProvider; private transient ProviderListener recipeBookEventProvider; private transient Kits kits; + private transient RandomTeleport randomTeleport; public Essentials() { @@ -222,6 +223,13 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { confList.add(itemDb); execTimer.mark("Init(ItemDB)"); + randomTeleport = new RandomTeleport(this); + if (randomTeleport.getPreCache()) { + randomTeleport.cacheRandomLocations(randomTeleport.getCenter(), randomTeleport.getMinRange(), randomTeleport.getMaxRange()); + } + confList.add(randomTeleport); + execTimer.mark("Init(RandomTeleport)"); + customItemResolver = new CustomItemResolver(this); try { itemDb.registerResolver(this, "custom_items", customItemResolver); @@ -437,7 +445,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { @Override public List onTabComplete(CommandSender sender, Command command, String commandLabel, String[] args) { return onTabCompleteEssentials(sender, command, commandLabel, args, Essentials.class.getClassLoader(), - "com.earth2me.essentials.commands.Command", "essentials.", null); + "com.earth2me.essentials.commands.Command", "essentials.", null); } @Override @@ -682,6 +690,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { return kits; } + @Override + public RandomTeleport getRandomTeleport() { + return randomTeleport; + } + @Deprecated @Override public User getUser(final Object base) { diff --git a/Essentials/src/com/earth2me/essentials/IEssentials.java b/Essentials/src/com/earth2me/essentials/IEssentials.java index f7f0ba1e9..3f4d8a6bc 100644 --- a/Essentials/src/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/com/earth2me/essentials/IEssentials.java @@ -66,6 +66,8 @@ public interface IEssentials extends Plugin { Kits getKits(); + RandomTeleport getRandomTeleport(); + Methods getPaymentMethod(); BukkitTask runTaskAsynchronously(Runnable run); diff --git a/Essentials/src/com/earth2me/essentials/RandomTeleport.java b/Essentials/src/com/earth2me/essentials/RandomTeleport.java new file mode 100644 index 000000000..ea4a28252 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/RandomTeleport.java @@ -0,0 +1,175 @@ +package com.earth2me.essentials; + +import com.earth2me.essentials.utils.VersionUtil; +import io.papermc.lib.PaperLib; +import net.ess3.api.InvalidWorldException; +import org.bukkit.Location; +import org.bukkit.block.Biome; + +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class RandomTeleport implements IConf { + private final IEssentials essentials; + private final EssentialsConf config; + private final ConcurrentLinkedQueue cachedLocations = new ConcurrentLinkedQueue<>(); + private static final Random RANDOM = new Random(); + private static final int HIGHEST_BLOCK_Y_OFFSET = VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_R01) ? 1 : 0; + + public RandomTeleport(final IEssentials essentials) { + this.essentials = essentials; + File file = new File(essentials.getDataFolder(), "tpr.yml"); + config = new EssentialsConf(file); + config.setTemplateName("/tpr.yml"); + config.options().copyHeader(true); + reloadConfig(); + } + + @Override + public void reloadConfig() { + config.load(); + cachedLocations.clear(); + } + + public Location getCenter() { + try { + Location center = config.getLocation("center", essentials.getServer()); + if (center != null) { + return center; + } + } catch (InvalidWorldException ignored) { + } + Location center = essentials.getServer().getWorlds().get(0).getWorldBorder().getCenter(); + center.setY(center.getWorld().getHighestBlockYAt(center) + 1); + setCenter(center); + return center; + } + + public void setCenter(Location center) { + config.setProperty("center", center); + config.save(); + } + + public double getMinRange() { + return config.getDouble("min-range", 0d); + } + + public void setMinRange(double minRange) { + config.setProperty("min-range", minRange); + config.save(); + } + + public double getMaxRange() { + return config.getDouble("max-range", getCenter().getWorld().getWorldBorder().getSize() / 2); + } + + public void setMaxRange(double maxRange) { + config.setProperty("max-range", maxRange); + config.save(); + } + + public Set getExcludedBiomes() { + List biomeNames = config.getStringList("excluded-biomes"); + Set excludedBiomes = new HashSet<>(); + for (String biomeName : biomeNames) { + try { + excludedBiomes.add(Biome.valueOf(biomeName.toUpperCase())); + } catch (IllegalArgumentException ignored) { + } + } + return excludedBiomes; + } + + public int getFindAttempts() { + return config.getInt("find-attempts", 10); + } + + public int getCacheThreshold() { + return config.getInt("cache-threshold", 10); + } + + public boolean getPreCache() { + return config.getBoolean("pre-cache", true); + } + + public Queue getCachedLocations() { + return cachedLocations; + } + + // Get a random location; cached if possible. Otherwise on demand. + public CompletableFuture getRandomLocation(Location center, double minRange, double maxRange) { + int findAttempts = this.getFindAttempts(); + Queue cachedLocations = this.getCachedLocations(); + // Try to build up the cache if it is below the threshold + if (cachedLocations.size() < this.getCacheThreshold()) { + cacheRandomLocations(center, minRange, maxRange); + } + CompletableFuture future = new CompletableFuture<>(); + // Return a random location immediately if one is available, otherwise try to find one now + if (cachedLocations.isEmpty()) { + attemptRandomLocation(findAttempts, center, minRange, maxRange).thenAccept(future::complete); + } else { + future.complete(cachedLocations.poll()); + } + return future; + } + + // Prompts caching random valid locations, up to a maximum number of attempts + public void cacheRandomLocations(Location center, double minRange, double maxRange) { + essentials.getServer().getScheduler().scheduleSyncDelayedTask(essentials, () -> { + for (int i = 0; i < this.getFindAttempts(); ++i) { + calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> { + if (isValidRandomLocation(location)) { + this.getCachedLocations().add(location); + } + }); + } + }); + } + + // Recursively attempt to find a random location. After a maximum number of attempts, the center is returned. + private CompletableFuture attemptRandomLocation(int attempts, Location center, double minRange, double maxRange) { + CompletableFuture future = new CompletableFuture<>(); + if (attempts > 0) { + calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> { + if (isValidRandomLocation(location)) { + future.complete(location); + } else { + attemptRandomLocation(attempts - 1, center, minRange, maxRange).thenAccept(future::complete); + } + }); + } else { + future.complete(center); + } + return future; + } + + // Calculates a random location asynchronously. + private CompletableFuture calculateRandomLocation(Location center, double minRange, double maxRange) { + CompletableFuture future = new CompletableFuture<>(); + final int dx = RANDOM.nextBoolean() ? 1 : -1, dz = RANDOM.nextBoolean() ? 1 : -1; + Location location = new Location( + center.getWorld(), + center.getX() + dx * (minRange + RANDOM.nextDouble() * (maxRange - minRange)), + center.getWorld().getMaxHeight(), + center.getZ() + dz * (minRange + RANDOM.nextDouble() * (maxRange - minRange)), + 360 * RANDOM.nextFloat() - 180, + 0 + ); + PaperLib.getChunkAtAsync(location).thenAccept(chunk -> { + location.setY(center.getWorld().getHighestBlockYAt(location) + HIGHEST_BLOCK_Y_OFFSET); + future.complete(location); + }); + return future; + } + + private boolean isValidRandomLocation(Location location) { + return !this.getExcludedBiomes().contains(location.getBlock().getBiome()); + } +} diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandsettpr.java b/Essentials/src/com/earth2me/essentials/commands/Commandsettpr.java new file mode 100644 index 000000000..6baaa8ff5 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/commands/Commandsettpr.java @@ -0,0 +1,43 @@ +package com.earth2me.essentials.commands; + +import com.earth2me.essentials.RandomTeleport; +import com.earth2me.essentials.User; +import org.bukkit.Server; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.earth2me.essentials.I18n.tl; + + +public class Commandsettpr extends EssentialsCommand { + public Commandsettpr() { + super("settpr"); + } + + @Override + protected void run(Server server, User user, String commandLabel, String[] args) throws Exception { + RandomTeleport randomTeleport = ess.getRandomTeleport(); + randomTeleport.getCachedLocations().clear(); + if (args.length == 0 || "center".equalsIgnoreCase(args[0])) { + randomTeleport.setCenter(user.getLocation()); + user.sendMessage(tl("settpr")); + } else if (args.length > 1) { + if ("minrange".equalsIgnoreCase(args[0])) { + randomTeleport.setMinRange(Double.parseDouble(args[1])); + } else if ("maxrange".equalsIgnoreCase(args[0])) { + randomTeleport.setMaxRange(Double.parseDouble(args[1])); + } + user.sendMessage(tl("settprValue", args[0].toLowerCase(), args[1].toLowerCase())); + } + } + + @Override + protected List getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) { + if (args.length == 1) { + return Arrays.asList("center", "minrange", "maxrange"); + } + return Collections.emptyList(); + } +} diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpr.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpr.java new file mode 100644 index 000000000..c0090b8ee --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpr.java @@ -0,0 +1,48 @@ +package com.earth2me.essentials.commands; + +import com.earth2me.essentials.RandomTeleport; +import com.earth2me.essentials.Trade; +import com.earth2me.essentials.User; +import net.ess3.api.events.UserRandomTeleportEvent; +import org.bukkit.Server; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static com.earth2me.essentials.I18n.tl; + + +public class Commandtpr extends EssentialsCommand { + + public Commandtpr() { + super("tpr"); + } + + @Override + protected void run(Server server, User user, String commandLabel, String[] args) throws Exception { + final Trade charge = new Trade(this.getName(), ess); + charge.isAffordableFor(user); + RandomTeleport randomTeleport = ess.getRandomTeleport(); + UserRandomTeleportEvent event = new UserRandomTeleportEvent(user, randomTeleport.getCenter(), randomTeleport.getMinRange(), randomTeleport.getMaxRange()); + server.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + randomTeleport.getRandomLocation(event.getCenter(), event.getMinRange(), event.getMaxRange()).thenAccept(location -> { + CompletableFuture future = getNewExceptionFuture(user.getSource(), commandLabel); + user.getAsyncTeleport().teleport(location, charge, PlayerTeleportEvent.TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("tprSuccess")); + } + }); + }); + } + + @Override + protected List getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) { + return Collections.emptyList(); + } +} diff --git a/Essentials/src/messages.properties b/Essentials/src/messages.properties index 312078324..e9319724f 100644 --- a/Essentials/src/messages.properties +++ b/Essentials/src/messages.properties @@ -694,6 +694,10 @@ sethomeCommandDescription=Set your home to your current location. sethomeCommandUsage=/ [[player:]name] setjailCommandDescription=Creates a jail where you specified named [jailname]. setjailCommandUsage=/ +settprCommandDescription=Set the random teleport location and parameters. +settprCommandUsage=/ [center|minrange|maxrange] [value] +settpr=\u00a76Set random teleport center. +settprValue=\u00a76Set random teleport \u00a7c{0}\u00a76 to \u00a7c{1}\u00a76. setwarpCommandDescription=Creates a new warp. setwarpCommandUsage=/ setworthCommandDescription=Set the sell value of an item. @@ -830,6 +834,9 @@ tpohereCommandDescription=Teleport here override for tptoggle. tpohereCommandUsage=/ tpposCommandDescription=Teleport to coordinates. tpposCommandUsage=/ [yaw] [pitch] [world] +tprCommandDescription=Teleport randomly. +tprCommandUsage=/ +tprSuccess=\u00a76Teleporting to a random location... tps=\u00a76Current TPS \= {0} tptoggleCommandDescription=Blocks all forms of teleportation. tptoggleCommandUsage=/ [player] [on|off] diff --git a/Essentials/src/net/ess3/api/events/UserRandomTeleportEvent.java b/Essentials/src/net/ess3/api/events/UserRandomTeleportEvent.java new file mode 100644 index 000000000..a8727c813 --- /dev/null +++ b/Essentials/src/net/ess3/api/events/UserRandomTeleportEvent.java @@ -0,0 +1,75 @@ +package net.ess3.api.events; + +import net.ess3.api.IUser; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called when the player uses the command /tpr + */ +public class UserRandomTeleportEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private IUser user; + private Location center; + private double minRange, maxRange; + private boolean cancelled = false; + + public UserRandomTeleportEvent(IUser user, Location center, double minRange, double maxRange) { + super(!Bukkit.isPrimaryThread()); + this.user = user; + this.center = center; + this.minRange = minRange; + this.maxRange = maxRange; + } + + public IUser getUser() { + return user; + } + + public Location getCenter() { + return center; + } + + public void setCenter(Location center) { + this.center = center; + } + + public double getMinRange() { + return minRange; + } + + public void setMinRange(double minRange) { + this.minRange = minRange; + } + + public double getMaxRange() { + return maxRange; + } + + public void setMaxRange(double maxRange) { + this.maxRange = maxRange; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean b) { + cancelled = b; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Essentials/src/plugin.yml b/Essentials/src/plugin.yml index 3f2103cf7..3d800a85f 100644 --- a/Essentials/src/plugin.yml +++ b/Essentials/src/plugin.yml @@ -388,6 +388,10 @@ commands: description: Creates a jail where you specified named [jailname]. usage: / aliases: [esetjail,createjail,ecreatejail] + settpr: + description: Set the random teleport location and parameters. + usage: / [center|minrange|maxrange] [value] + aliases: [esettpr, settprandom, esettprandom] setwarp: description: Creates a new warp. usage: / @@ -508,6 +512,10 @@ commands: description: Teleport to coordinates. usage: / [yaw] [pitch] [world] aliases: [etppos] + tpr: + description: Teleport randomly. + usage: / + aliases: [etpr, tprandom, etprandom] tptoggle: description: Blocks all forms of teleportation. usage: / [player] [on|off] diff --git a/Essentials/src/tpr.yml b/Essentials/src/tpr.yml new file mode 100644 index 000000000..fcf721e7d --- /dev/null +++ b/Essentials/src/tpr.yml @@ -0,0 +1,16 @@ +# Configuration for the random teleport command. +# Some settings may be defaulted, and can be changed via the /settpr command in-game. +min-range: 0.0 +excluded-biomes: + - cold_ocean + - deep_cold_ocean + - deep_frozen_ocean + - deep_lukewarm_ocean + - deep_ocean + - deep_warm_ocean + - frozen_ocean + - frozen_river + - lukewarm_ocean + - ocean + - river + - warm_ocean