Configurable essentials: home tiers by permission, cooldowns, warp cap, RTP radii; rewards config

This commit is contained in:
Claude committed 2026-10-01 01:01:54 +00:00
1 parent 0dc948caa4
commit c859628e93
11 files changed
+276 -16

No files matched your search

@@ -133,7 +133,9 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
Map<String, Location> playerHomes = homes.get(player.getUniqueId());
if (playerHomes != null && playerHomes.containsKey(homeName)) {
player.closeInventory();
if (coolingDown(player, "home")) return;
player.teleport(playerHomes.get(homeName));
startCooldown(player, "home");
player.sendMessage(ChatColor.GREEN + "Teleported to home: " + ChatColor.YELLOW + homeName);
}
} else if (title.equals(GUI_WARPS_TITLE)) {
@@ -143,7 +145,9 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
String warpName = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace("Warp: ", "").trim().toLowerCase();
if (warps.containsKey(warpName)) {
player.closeInventory();
if (coolingDown(player, "warp")) return;
player.teleport(warps.get(warpName));
startCooldown(player, "warp");
player.sendMessage(ChatColor.GREEN + "Teleported to warp: " + ChatColor.YELLOW + warpName);
}
}
@@ -158,6 +162,9 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
if (!plugin.getConfig().getBoolean("essentials.enabled", true)) { player.sendMessage(ChatColor.RED + "Essentials are disabled."); return true; }
String cmd = command.getName().toLowerCase();
boolean cooled = cooldownSeconds(cmd) > 0 && !player.hasPermission("scopenet.cooldown.bypass");
if (cooled && coolingDown(player, cmd)) return true;
Location before = player.getLocation();
switch (cmd) {
case "spawn" -> handleSpawn(player);
case "home" -> handleHome(player, args);
@@ -178,6 +185,7 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
case "playtime" -> handlePlaytime(player, args);
default -> { return false; }
}
if (cooled && ((cmd.equals("tpa") && args.length > 0) || cmd.equals("rtp") || !before.equals(player.getLocation()))) startCooldown(player, cmd);
return true;
}
@@ -263,13 +271,36 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
player.openInventory(inv);
}
// ---- limits and cooldowns (the same rules as the Fabric mod; see config.yml) ----
private final net.scopenet.core.Limits.Cooldowns cooldowns = new net.scopenet.core.Limits.Cooldowns();
private int tpaTimeoutSeconds() { return Math.max(5, plugin.getConfig().getInt("essentials.tpa-timeout-seconds", 60)); }
private int cooldownSeconds(String cmd) { return Math.max(0, plugin.getConfig().getInt("essentials.cooldowns." + cmd, 0)); }
/** True (and tells the player) if they must still wait before using this command. */
private boolean coolingDown(Player player, String cmd) {
if (cooldownSeconds(cmd) <= 0 || player.hasPermission("scopenet.cooldown.bypass")) return false;
long wait = cooldowns.remainingMs(player.getUniqueId(), cmd, System.currentTimeMillis());
if (wait <= 0) return false;
player.sendMessage(ChatColor.RED + "Please wait " + net.scopenet.core.Limits.Cooldowns.waitText(wait) + " before using /" + cmd + " again.");
return true;
}
private void startCooldown(Player player, String cmd) {
if (!player.hasPermission("scopenet.cooldown.bypass")) cooldowns.start(player.getUniqueId(), cmd, System.currentTimeMillis(), cooldownSeconds(cmd));
}
private void handleSetHome(Player player, String[] args) {
String homeName = args.length > 0 ? args[0].toLowerCase() : "home";
int maxHomes = plugin.getConfig().getInt("essentials.max-homes", 5);
int nameMax = Math.max(1, plugin.getConfig().getInt("essentials.home-name-max-length", 32));
if (homeName.length() > nameMax) { player.sendMessage(ChatColor.RED + "Home names can be at most " + nameMax + " characters."); return; }
int maxHomes = net.scopenet.core.Limits.tier(player::hasPermission, "scopenet.homes.", plugin.getConfig().getInt("essentials.max-homes", 5), 500);
Map<String, Location> playerHomes = homes.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>());
if (!playerHomes.containsKey(homeName) && playerHomes.size() >= maxHomes) {
player.sendMessage(ChatColor.RED + "You have reached your limit of " + maxHomes + " homes.");
player.sendMessage(ChatColor.RED + "You have reached your limit of " + net.scopenet.core.Limits.show(maxHomes) + " homes.");
return;
}
@@ -324,13 +355,13 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
player.sendMessage(ChatColor.GREEN + "Teleport request sent to " + ChatColor.YELLOW + target.getName() + ChatColor.GREEN + ".");
target.sendMessage(ChatColor.YELLOW + player.getName() + ChatColor.GREEN + " has sent you a teleport request.");
target.sendMessage(ChatColor.GRAY + "Type " + ChatColor.YELLOW + "/tpaccept" + ChatColor.GRAY + " to accept or " + ChatColor.RED + "/tpdeny" + ChatColor.GRAY + " to deny (expires in 60s).");
target.sendMessage(ChatColor.GRAY + "Type " + ChatColor.YELLOW + "/tpaccept" + ChatColor.GRAY + " to accept or " + ChatColor.RED + "/tpdeny" + ChatColor.GRAY + " to deny (expires in " + tpaTimeoutSeconds() + "s).");
}
private void handleTpAccept(Player player) {
UUID requesterId = pendingTpa.get(player.getUniqueId());
Long time = tpaTimestamp.get(player.getUniqueId());
if (requesterId == null || time == null || System.currentTimeMillis() - time > 60_000) {
if (requesterId == null || time == null || System.currentTimeMillis() - time > tpaTimeoutSeconds() * 1000L) {
pendingTpa.remove(player.getUniqueId());
tpaTimestamp.remove(player.getUniqueId());
player.sendMessage(ChatColor.RED + "You have no active teleport requests.");
@@ -366,15 +397,18 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
}
private void handleRtp(Player player) {
int radius = plugin.getConfig().getInt("essentials.rtp-radius", 2500);
int radius = Math.max(10, plugin.getConfig().getInt("essentials.rtp-radius", 2500));
int minRadius = Math.min(plugin.getConfig().getInt("essentials.rtp-min-radius", 0), radius - 1);
int attempts = Math.max(1, plugin.getConfig().getInt("essentials.rtp-attempts", 15));
World world = player.getWorld();
Random rng = new Random();
player.sendMessage(ChatColor.GRAY + "Searching for a safe wilderness destination...");
Bukkit.getScheduler().runTask(plugin, () -> {
for (int i = 0; i < 15; i++) {
for (int i = 0; i < attempts; i++) {
int x = rng.nextInt(radius * 2) - radius;
int z = rng.nextInt(radius * 2) - radius;
if (Math.max(Math.abs(x), Math.abs(z)) < minRadius) continue;
int y = world.getHighestBlockYAt(x, z);
Block block = world.getBlockAt(x, y - 1, z);
if (block.getType().isSolid() && block.getType() != Material.LAVA && block.getType() != Material.WATER) {
@@ -471,6 +505,11 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
if (!warpAdmin(player)) return;
String name = warpName(player, args, "/setwarp <name>");
if (name == null) return;
int maxWarps = plugin.getConfig().getInt("essentials.max-warps", 0);
if (maxWarps > 0 && !warps.containsKey(name) && warps.size() >= maxWarps) {
player.sendMessage(ChatColor.RED + "The server already has " + maxWarps + " warps. Delete one first.");
return;
}
warps.put(name, player.getLocation());
saveWarps();
player.sendMessage(ChatColor.GREEN + "Warp set: " + name);
@@ -46,10 +46,30 @@ social:
essentials:
# Essentials commands (/spawn, /home, /sethome, /delhome, /back, /tpa, /tpaccept, /tpdeny, /rtp, /warp, /playtime)
enabled: true
# Maximum homes per player
# Homes every player can set. Raise it for a rank with a permission node: scopenet.homes.10 gives 10 homes,
# scopenet.homes.25 gives 25 (the highest one a player has wins), scopenet.homes.unlimited removes the limit.
# Operators and anyone with "*" get the top tier.
max-homes: 5
# RTP search radius
# Longest allowed home name.
home-name-max-length: 32
# Most warps the server may have (0 = no limit).
max-warps: 0
# How long a /tpa request stays open.
tpa-timeout-seconds: 60
# /rtp picks a random spot between rtp-min-radius and rtp-radius blocks from the centre (0,0).
rtp-radius: 2500
rtp-min-radius: 0
# How many random spots /rtp tries before giving up.
rtp-attempts: 15
# Seconds a player must wait between uses (0 = no cooldown). The wait starts when the command actually
# teleports, so a typo costs nothing. Players with scopenet.cooldown.bypass skip all of these.
cooldowns:
spawn: 0
home: 0
back: 0
tpa: 0
rtp: 0
warp: 0
map:
# The SCOPENET Map: this server renders its world to map tiles in the background and sends only what changed,
@@ -96,6 +116,23 @@ integrations:
enabled: true
report-interval-seconds: 30
# ==============================================================================
# Rewards
# Quests, achievements and rank milestones can give more than XP: money, items, permissions, groups, claim
# chunks, badges and messages (set them in the admin panel). The panel hands items, permissions and commands
# to this server when the player is online. Permissions and groups are applied with console commands, so any
# permissions plugin works; the defaults are LuckPerms'. Placeholders: {player} {uuid} {node} {value} {duration} {group}.
# ==============================================================================
rewards:
enabled: true
# Let the panel run its own console commands as rewards. Off by default: anyone who can edit rewards in the panel
# could otherwise run commands on this server. Only switch it on if you trust everyone with panel admin access.
allow-commands: false
permission-command: "lp user {player} permission set {node} true"
permission-deny-command: "lp user {player} permission set {node} false"
permission-temp-command: "lp user {player} permission settemp {node} {value} {duration}"
group-command: "lp user {player} parent add {group}"
# ==============================================================================
# Client mod link
# Sends balance, level, guild and claim info to players who have the optional SCOPENET