Implement base for RTP
This commit is contained in:
parent
139c51e9b8
commit
06a8aa0f65
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.alttd.essentia.api.model.randomteleport;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
public interface LocationValidator {
|
||||
|
||||
boolean validate(Location location);
|
||||
}
|
||||
|
|
@ -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<LocationValidator> 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<Class<?>> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CommandSourceStack> command() {
|
||||
final LiteralArgumentBuilder<CommandSourceStack> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Biome> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Material> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int[]> CHUNK_POS = new ArrayList<>();
|
||||
private final Multimap<Integer, Integer> 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<Location> 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<Location> 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<Location> 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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user