Implement base for RTP
This commit is contained in:
parent
139c51e9b8
commit
06a8aa0f65
|
|
@ -1,5 +1,6 @@
|
||||||
package com.alttd.essentia;
|
package com.alttd.essentia;
|
||||||
|
|
||||||
|
import com.alttd.essentia.api.model.randomteleport.LocationValidator;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
public interface EssentiaAPI {
|
public interface EssentiaAPI {
|
||||||
|
|
@ -19,4 +20,6 @@ public interface EssentiaAPI {
|
||||||
Provider.instance = instance;
|
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.commands.EssentiaCommand;
|
||||||
import com.alttd.essentia.configuration.Config;
|
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.StorageManager;
|
||||||
import com.alttd.essentia.storage.StorageProvider;
|
import com.alttd.essentia.storage.StorageProvider;
|
||||||
import com.alttd.essentia.storage.StorageType;
|
import com.alttd.essentia.storage.StorageType;
|
||||||
|
|
@ -18,6 +19,8 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
import org.reflections.scanners.Scanners;
|
import org.reflections.scanners.Scanners;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI {
|
public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI {
|
||||||
|
|
@ -27,6 +30,8 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
@Getter
|
||||||
|
private List<LocationValidator> locationValidators;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private StorageProvider storageProvider;
|
private StorageProvider storageProvider;
|
||||||
|
|
@ -44,6 +49,7 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI {
|
||||||
loadEventListeners();
|
loadEventListeners();
|
||||||
loadManagers();
|
loadManagers();
|
||||||
loadStorageProvider();
|
loadStorageProvider();
|
||||||
|
loadLocationValidators();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -100,4 +106,26 @@ public class EssentiaPlugin extends JavaPlugin implements EssentiaAPI {
|
||||||
storageProvider.startAutoSaving();
|
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 {
|
public abstract class EssentiaRequest implements Request {
|
||||||
|
|
||||||
private final EssentiaPlugin plugin;
|
protected final EssentiaPlugin plugin;
|
||||||
@Getter private final Player requester;
|
@Getter private final Player requester;
|
||||||
@Getter private final Player target;
|
@Getter private final Player target;
|
||||||
private final RequestTimeout timeoutTask;
|
@Getter private final RequestTimeout timeoutTask;
|
||||||
|
|
||||||
TagResolver placeholders;
|
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