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.
This commit is contained in:
pop4959 2020-07-06 11:53:43 -07:00 committed by GitHub
parent 9681933ec2
commit 76e511a774
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 388 additions and 1 deletions

View file

@ -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<String> 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) {

View file

@ -66,6 +66,8 @@ public interface IEssentials extends Plugin {
Kits getKits();
RandomTeleport getRandomTeleport();
Methods getPaymentMethod();
BukkitTask runTaskAsynchronously(Runnable run);

View file

@ -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<Location> 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<Biome> getExcludedBiomes() {
List<String> biomeNames = config.getStringList("excluded-biomes");
Set<Biome> 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<Location> getCachedLocations() {
return cachedLocations;
}
// Get a random location; cached if possible. Otherwise on demand.
public CompletableFuture<Location> getRandomLocation(Location center, double minRange, double maxRange) {
int findAttempts = this.getFindAttempts();
Queue<Location> 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<Location> 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<Location> attemptRandomLocation(int attempts, Location center, double minRange, double maxRange) {
CompletableFuture<Location> 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<Location> calculateRandomLocation(Location center, double minRange, double maxRange) {
CompletableFuture<Location> 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());
}
}

View file

@ -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<String> getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) {
if (args.length == 1) {
return Arrays.asList("center", "minrange", "maxrange");
}
return Collections.emptyList();
}
}

View file

@ -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<Boolean> 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<String> getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) {
return Collections.emptyList();
}
}

View file

@ -694,6 +694,10 @@ sethomeCommandDescription=Set your home to your current location.
sethomeCommandUsage=/<command> [[player:]name]
setjailCommandDescription=Creates a jail where you specified named [jailname].
setjailCommandUsage=/<command> <jailname>
settprCommandDescription=Set the random teleport location and parameters.
settprCommandUsage=/<command> [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=/<command> <warp>
setworthCommandDescription=Set the sell value of an item.
@ -830,6 +834,9 @@ tpohereCommandDescription=Teleport here override for tptoggle.
tpohereCommandUsage=/<command> <player>
tpposCommandDescription=Teleport to coordinates.
tpposCommandUsage=/<command> <x> <y> <z> [yaw] [pitch] [world]
tprCommandDescription=Teleport randomly.
tprCommandUsage=/<command>
tprSuccess=\u00a76Teleporting to a random location...
tps=\u00a76Current TPS \= {0}
tptoggleCommandDescription=Blocks all forms of teleportation.
tptoggleCommandUsage=/<command> [player] [on|off]

View file

@ -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;
}
}

View file

@ -388,6 +388,10 @@ commands:
description: Creates a jail where you specified named [jailname].
usage: /<command> <jailname>
aliases: [esetjail,createjail,ecreatejail]
settpr:
description: Set the random teleport location and parameters.
usage: /<command> [center|minrange|maxrange] [value]
aliases: [esettpr, settprandom, esettprandom]
setwarp:
description: Creates a new warp.
usage: /<command> <warp>
@ -508,6 +512,10 @@ commands:
description: Teleport to coordinates.
usage: /<command> <x> <y> <z> [yaw] [pitch] [world]
aliases: [etppos]
tpr:
description: Teleport randomly.
usage: /<command>
aliases: [etpr, tprandom, etprandom]
tptoggle:
description: Blocks all forms of teleportation.
usage: /<command> [player] [on|off]

16
Essentials/src/tpr.yml Normal file
View file

@ -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