From 06a8aa0f6534352020be9631a81a5ace88c3b6cc Mon Sep 17 00:00:00 2001 From: Len <40720638+destro174@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:22:28 +0200 Subject: [PATCH] Implement base for RTP --- .../java/com/alttd/essentia/EssentiaAPI.java | 3 + .../randomteleport/LocationValidator.java | 8 + .../com/alttd/essentia/EssentiaPlugin.java | 28 +++ .../commands/admin/RandomTeleportCommand.java | 58 ++++++ .../randomteleport/BiomeValidator.java | 29 +++ .../randomteleport/BlockValidator.java | 28 +++ .../randomteleport/ProtectionValidator.java | 14 ++ .../randomteleport/WorldBorderValidator.java | 13 ++ .../essentia/request/EssentiaRequest.java | 4 +- .../request/RandomTeleportRequest.java | 168 ++++++++++++++++++ 10 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/com/alttd/essentia/api/model/randomteleport/LocationValidator.java create mode 100644 plugin/src/main/java/com/alttd/essentia/commands/admin/RandomTeleportCommand.java create mode 100644 plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BiomeValidator.java create mode 100644 plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BlockValidator.java create mode 100644 plugin/src/main/java/com/alttd/essentia/feature/randomteleport/ProtectionValidator.java create mode 100644 plugin/src/main/java/com/alttd/essentia/feature/randomteleport/WorldBorderValidator.java create mode 100644 plugin/src/main/java/com/alttd/essentia/request/RandomTeleportRequest.java diff --git a/api/src/main/java/com/alttd/essentia/EssentiaAPI.java b/api/src/main/java/com/alttd/essentia/EssentiaAPI.java index b9d0df2..8505d2c 100644 --- a/api/src/main/java/com/alttd/essentia/EssentiaAPI.java +++ b/api/src/main/java/com/alttd/essentia/EssentiaAPI.java @@ -1,5 +1,6 @@ package com.alttd.essentia; +import com.alttd.essentia.api.model.randomteleport.LocationValidator; import org.jetbrains.annotations.ApiStatus; public interface EssentiaAPI { @@ -19,4 +20,6 @@ public interface EssentiaAPI { Provider.instance = instance; } } + + public void addLocationValidator(LocationValidator locationValidator); } diff --git a/api/src/main/java/com/alttd/essentia/api/model/randomteleport/LocationValidator.java b/api/src/main/java/com/alttd/essentia/api/model/randomteleport/LocationValidator.java new file mode 100644 index 0000000..2b73d8f --- /dev/null +++ b/api/src/main/java/com/alttd/essentia/api/model/randomteleport/LocationValidator.java @@ -0,0 +1,8 @@ +package com.alttd.essentia.api.model.randomteleport; + +import org.bukkit.Location; + +public interface LocationValidator { + + boolean validate(Location location); +} diff --git a/plugin/src/main/java/com/alttd/essentia/EssentiaPlugin.java b/plugin/src/main/java/com/alttd/essentia/EssentiaPlugin.java index 8193334..2b68cc7 100644 --- a/plugin/src/main/java/com/alttd/essentia/EssentiaPlugin.java +++ b/plugin/src/main/java/com/alttd/essentia/EssentiaPlugin.java @@ -2,6 +2,7 @@ package com.alttd.essentia; import com.alttd.essentia.commands.EssentiaCommand; import com.alttd.essentia.configuration.Config; +import com.alttd.essentia.api.model.randomteleport.LocationValidator; import com.alttd.essentia.storage.StorageManager; import com.alttd.essentia.storage.StorageProvider; import com.alttd.essentia.storage.StorageType; @@ -18,6 +19,8 @@ import org.bukkit.plugin.java.JavaPlugin; import org.reflections.Reflections; import org.reflections.scanners.Scanners; +import java.util.ArrayList; +import java.util.List; import java.util.Set; public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI { @@ -27,6 +30,8 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI { @Getter private UserManager userManager; + @Getter + private List locationValidators; @Getter private StorageProvider storageProvider; @@ -44,6 +49,7 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI { loadEventListeners(); loadManagers(); loadStorageProvider(); + loadLocationValidators(); } @Override @@ -100,4 +106,26 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI { storageProvider.startAutoSaving(); } + void loadLocationValidators() { + this.locationValidators = new ArrayList<>(); + Reflections reflections = new Reflections("com.alttd.essentia.feature.randomteleport.validator"); + Set> subTypes = reflections.get(Scanners.SubTypes.of(LocationValidator.class).asClass()); + + subTypes.forEach(clazz -> { + try { + LocationValidator locationValidator = (LocationValidator) clazz.getDeclaredConstructor().newInstance(); + addLocationValidator(locationValidator); + } catch (Exception e) { + EssentiaPlugin.instance().getLogger().severe("Failed to add locationValidator " + clazz.getSimpleName()); + EssentiaPlugin.instance().getLogger().severe(e.getMessage()); + } + }); + } + + // TODO -- make this reload proof for API + @Override + public void addLocationValidator(LocationValidator locationValidator) { + locationValidators.add(locationValidator); + } + } diff --git a/plugin/src/main/java/com/alttd/essentia/commands/admin/RandomTeleportCommand.java b/plugin/src/main/java/com/alttd/essentia/commands/admin/RandomTeleportCommand.java new file mode 100644 index 0000000..e076dd4 --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/commands/admin/RandomTeleportCommand.java @@ -0,0 +1,58 @@ +package com.alttd.essentia.commands.admin; + +import com.alttd.essentia.EssentiaPlugin; +import com.alttd.essentia.commands.EssentiaCommand; +import com.alttd.essentia.request.RandomTeleportRequest; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class RandomTeleportCommand implements EssentiaCommand { + + @Override + public String commandName() { + return "randomteleport"; + } + + @Override + public @NotNull LiteralCommandNode command() { + final LiteralArgumentBuilder builder = + Commands.literal(commandName()) + .requires(commandSourceStack -> commandSourceStack.getSender().hasPermission(adminCommandPermission())) + .then( + Commands.argument("player", ArgumentTypes.player()) + .requires(commandSourceStack -> commandSourceStack.getSender().hasPermission(adminOtherCommandPermission())) + .executes((source) -> { + CommandSourceStack sourceStack = source.getSource(); + Player target = source.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(sourceStack).getFirst(); + execute(source.getSource().getSender(), target); + + return 1; + }) + ); + return builder.build(); + } + + @Override + public String description() { + return "Teleport a player to a random location!"; + } + + public void execute(CommandSender sender, Player target) { // TODO - messages + TagResolver placeholders = TagResolver.resolver( + Placeholder.component("requester", sender.name()), + Placeholder.component("target", target.displayName()) + ); + + new RandomTeleportRequest(EssentiaPlugin.instance(), target, target); + } + +} diff --git a/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BiomeValidator.java b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BiomeValidator.java new file mode 100644 index 0000000..a4e8415 --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BiomeValidator.java @@ -0,0 +1,29 @@ +package com.alttd.essentia.feature.randomteleport; + +import com.alttd.essentia.api.model.randomteleport.LocationValidator; +import org.bukkit.Location; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; + +import java.util.HashSet; +import java.util.Set; + +// TODO - load biomes from config +public class BiomeValidator implements LocationValidator { + + private final boolean whitelist; + private final Set biomes; + + public BiomeValidator() { + this.biomes = new HashSet<>(); + this.whitelist = false; + this.biomes.add(Biome.OCEAN); + } + + @Override + public boolean validate(Location location) { + Block block = location.getBlock(); + return biomes.contains(block.getBiome()) == whitelist; + } + +} diff --git a/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BlockValidator.java b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BlockValidator.java new file mode 100644 index 0000000..64feb0c --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/BlockValidator.java @@ -0,0 +1,28 @@ +package com.alttd.essentia.feature.randomteleport; + +import com.alttd.essentia.api.model.randomteleport.LocationValidator; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; + +import java.util.HashSet; +import java.util.Set; + +// TODO - load block list from config +public class BlockValidator implements LocationValidator { + + private final boolean whitelist; + private final Set materials; + + public BlockValidator() { + materials = new HashSet<>(); + this.whitelist = false; + } + + @Override + public boolean validate(Location location) { + Block block = location.getBlock(); + return materials.contains(block.getType()) == whitelist; + } + +} diff --git a/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/ProtectionValidator.java b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/ProtectionValidator.java new file mode 100644 index 0000000..aa37156 --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/ProtectionValidator.java @@ -0,0 +1,14 @@ +package com.alttd.essentia.feature.randomteleport; + +import com.alttd.essentia.api.model.randomteleport.LocationValidator; +import org.bukkit.Location; + +// TODO - claim hooks for GP. LandClaims should hook into the plugin and add the validator. +public class ProtectionValidator implements LocationValidator { + + @Override + public boolean validate(Location location) { + return false; + } + +} diff --git a/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/WorldBorderValidator.java b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/WorldBorderValidator.java new file mode 100644 index 0000000..3a06dad --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/feature/randomteleport/WorldBorderValidator.java @@ -0,0 +1,13 @@ +package com.alttd.essentia.feature.randomteleport; + +import com.alttd.essentia.api.model.randomteleport.LocationValidator; +import org.bukkit.Location; + +public class WorldBorderValidator implements LocationValidator { + + @Override + public boolean validate(Location location) { + return location.getWorld().getWorldBorder().isInside(location); + } + +} diff --git a/plugin/src/main/java/com/alttd/essentia/request/EssentiaRequest.java b/plugin/src/main/java/com/alttd/essentia/request/EssentiaRequest.java index 9d6dcf1..8626959 100644 --- a/plugin/src/main/java/com/alttd/essentia/request/EssentiaRequest.java +++ b/plugin/src/main/java/com/alttd/essentia/request/EssentiaRequest.java @@ -12,10 +12,10 @@ import org.bukkit.entity.Player; public abstract class EssentiaRequest implements Request { - private final EssentiaPlugin plugin; + protected final EssentiaPlugin plugin; @Getter private final Player requester; @Getter private final Player target; - private final RequestTimeout timeoutTask; + @Getter private final RequestTimeout timeoutTask; TagResolver placeholders; diff --git a/plugin/src/main/java/com/alttd/essentia/request/RandomTeleportRequest.java b/plugin/src/main/java/com/alttd/essentia/request/RandomTeleportRequest.java new file mode 100644 index 0000000..6f88712 --- /dev/null +++ b/plugin/src/main/java/com/alttd/essentia/request/RandomTeleportRequest.java @@ -0,0 +1,168 @@ +package com.alttd.essentia.request; + +import com.alttd.essentia.EssentiaPlugin; +import com.alttd.essentia.api.model.randomteleport.LocationValidator; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public class RandomTeleportRequest extends EssentiaRequest { + + private static final List CHUNK_POS = new ArrayList<>(); + private final Multimap checked = MultimapBuilder.hashKeys().hashSetValues().build(); + + private final Random random; + private final Location center; // todo - config + private final int minRadius; // todo - config + private final int maxRadius; // todo - config + private final int maxTries = 100; // TODO - config + + private int checks; + CompletableFuture future; + + static { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + CHUNK_POS.add(new int[]{x, z}); + } + } + } + + public RandomTeleportRequest(EssentiaPlugin plugin, Player requester, Player target) { + super(plugin, requester, target); + this.random = new Random(); + this.center = Bukkit.getWorlds().getFirst().getSpawnLocation(); // todo - config and sanity check for worlds + this.minRadius = 50; + this.maxRadius = 20000; + + this.timeoutTask().cancel(); + this.teleport(); + } + + /** + * Start searching for a safe random location within the world + * + * @return A CompletableFuture for when the searching is finished. + */ + public CompletableFuture startSearch() { + future = new CompletableFuture<>(); + target().sendRichMessage("Running search task"); + plugin.getServer().getScheduler().runTask(plugin, () -> searchRandomLocation(future)); + return future; + } + + // needs to be a task + private void searchRandomLocation(CompletableFuture future) { + if (future.isCancelled() || future.isDone() || future.isCompletedExceptionally()) { + return; + } + Location randomLoc = center.clone(); + int minChunk = minRadius >> 4; + int maxChunk = maxRadius >> 4; + int randChunkX; + int randChunkZ; + + do { + checks++; + if (checks >= maxTries) { + future.completeExceptionally(new Exception("Could not find random location within %s tries".formatted(maxTries + ""))); + return; + } + randChunkX = (random.nextBoolean() ? 1 : -1) * random.nextInt(maxChunk + 1); + randChunkZ = (random.nextBoolean() ? 1 : -1) * random.nextInt(maxChunk + 1); + target().sendRichMessage("randChunkX: " + randChunkX + ", randChunkZ: " + randChunkZ); + } while (!checked.put(randChunkX, randChunkZ) + || !inRadius(Math.abs(randChunkX), Math.abs(randChunkZ), minChunk, maxChunk) + /*|| randomLoc.getWorld().isChunkGenerated(randChunkX, randChunkZ)*/); + + + randomLoc.setX(((center.getBlockX() >> 4) + randChunkX) * 16); + randomLoc.setZ(((center.getBlockZ() >> 4) + randChunkZ) * 16); + randomLoc.getWorld().getChunkAtAsync(randomLoc).thenApply(chunk -> { + if (chunk == null) { + searchRandomLocation(future); + return false; + } + chunk.addPluginChunkTicket(plugin); + try { + int indexOffset = random.nextInt(CHUNK_POS.size()); + Location foundLoc = null; + for (int i = 0; i < CHUNK_POS.size(); i++) { + int index = (i + indexOffset) % CHUNK_POS.size(); + boolean validated = true; + Location loc = randomLoc.clone().add(CHUNK_POS.get(index)[0], 0, CHUNK_POS.get(index)[1]); + + if (!inRadius(loc)) { + continue; + } + + for (LocationValidator validator : plugin.locationValidators()) { + if (!validator.validate(loc)) { + validated = false; + break; + } + } + if (validated) { + foundLoc = loc; + break; + } + } + + if (foundLoc != null) { + // all checks are for the top block, put we want a location above that so add 1 to y + future.complete(foundLoc.add(0, 1, 0)); + return true; + } + plugin.getServer().getScheduler().runTaskLater(plugin, () -> searchRandomLocation(future), 1); + return false; + } finally { + chunk.removePluginChunkTicket(plugin); + } + }).exceptionally(future::completeExceptionally); + } + + private boolean inRadius(Location location) { + int diffX = Math.abs(location.getBlockX() - center.getBlockX()); + int diffZ = Math.abs(location.getBlockZ() - center.getBlockZ()); + return inRadius(diffX, diffZ, minRadius, maxRadius); + } + + private boolean inRadius(int diffX, int diffZ, int minRadius, int maxRadius) { + return diffX >= minRadius && diffX <= maxRadius && diffZ <= maxRadius + || diffZ >= minRadius && diffZ <= maxRadius && diffX <= maxRadius; + } + + @Override + protected void teleport() { + startSearch().thenApply(randomLocation -> { + randomLocation.setX(randomLocation.getBlockX() + 0.5); + randomLocation.setY(randomLocation.getY() + 0.5); + randomLocation.setZ(randomLocation.getBlockZ() + 0.5); + target().teleportAsync(randomLocation) + .whenComplete((success, exception) -> { + target().sendMessage("You have been teleported to a random location."); // todo -- config + }); + return true; + }); + if (!target().isOnline() || !requester().isOnline()) { + cancel(); + return; + } + } + + @Override + public void cancel() { + try { + timeoutTask().cancel(); + } catch (IllegalStateException ignore) { + } + } + +}