Add account connections, guild wallets and admin fixes [skip ci]

This commit is contained in:
SCOPEDD committed 2026-09-30 12:25:29 -04:00
1 parent 7076e847ea
commit 004862ea88
38 files changed
+1696 -458

No files matched your search

@@ -132,6 +132,10 @@ public final class Integration implements AutoCloseable {
return client.checkChunk(dimension, chunkX, chunkZ, uuid);
}
public void prefetchClaims(String dimension, int chunkX, int chunkZ, UUID uuid) {
client.prefetchClaims(dimension, chunkX, chunkZ, uuid);
}
@Override public void close() {
closed = true;
client.close();
@@ -12,6 +12,7 @@ public final class PanelClient {
private record Cached(ChunkCheckResult result, long expires) {}
private final java.util.Map<ChunkKey, Cached> claims = new java.util.concurrent.ConcurrentHashMap<>();
private final java.util.Set<ChunkKey> pendingClaims = java.util.concurrent.ConcurrentHashMap.newKeySet();
private final java.util.Set<ChunkKey> pendingSnapshots = java.util.concurrent.ConcurrentHashMap.newKeySet();
private final java.util.concurrent.ThreadPoolExecutor claimWorker = new java.util.concurrent.ThreadPoolExecutor(
2, 2, 0, java.util.concurrent.TimeUnit.SECONDS, new java.util.concurrent.ArrayBlockingQueue<>(64),
task -> { Thread t = new Thread(task, "scopenet-claims"); t.setDaemon(true); return t; });
@@ -86,6 +87,43 @@ public final class PanelClient {
return UNKNOWN;
}
/** Warm a 5x5 area when a player changes chunks, including environmental protection. */
public void prefetchClaims(String dimension, int chunkX, int chunkZ, java.util.UUID uuid) {
if (!settings.guildsEnabled() || !settings.landClaimingEnabled()) return;
ChunkKey center = new ChunkKey(dimension, chunkX, chunkZ, uuid);
Cached cached = claims.get(center);
if (cached != null && cached.expires() > System.nanoTime()) return;
if (!pendingSnapshots.add(center)) return;
long generation = claimGeneration.get();
try { claimWorker.execute(() -> {
try {
JsonObject req = new JsonObject();
req.addProperty("uuid", uuid.toString());
req.addProperty("dimension", dimension);
req.addProperty("chunk_x", chunkX);
req.addProperty("chunk_z", chunkZ);
JsonObject snapshot = post("guilds/claim-snapshot", req);
java.util.Map<String, ChunkCheckResult> found = new java.util.HashMap<>();
for (JsonElement el : snapshot.getAsJsonArray("claims")) {
JsonObject c = el.getAsJsonObject();
String coord = c.get("chunk_x").getAsInt() + ":" + c.get("chunk_z").getAsInt();
found.put(coord, new ChunkCheckResult(true, c.get("allowed").getAsBoolean(),
c.get("guild_name").getAsString(), c.get("guild_tag").getAsString()));
}
long expiry = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(15);
if (generation != claimGeneration.get()) return;
if (claims.size() > 4096) claims.clear();
for (int x = chunkX - 2; x <= chunkX + 2; x++) for (int z = chunkZ - 2; z <= chunkZ + 2; z++) {
ChunkCheckResult value = found.getOrDefault(x + ":" + z, new ChunkCheckResult(false, true, null, null));
claims.put(new ChunkKey(dimension, x, z, uuid), new Cached(value, expiry));
claims.put(new ChunkKey(dimension, x, z, new java.util.UUID(0, 0)),
new Cached(new ChunkCheckResult(value.claimed(), !value.claimed(), value.guildName(), value.guildTag()), expiry));
}
} catch (Exception ignored) { /* cold checks remain fail closed */ }
finally { pendingSnapshots.remove(center); }
}); } catch (java.util.concurrent.RejectedExecutionException e) { pendingSnapshots.remove(center); }
}
private ChunkCheckResult checkChunkRemote(String dimension, int chunkX, int chunkZ, java.util.UUID uuid) {
if (!settings.guildsEnabled() || !settings.landClaimingEnabled()) {
return new ChunkCheckResult(false, true, null, null);
@@ -226,4 +264,3 @@ public final class PanelClient {
return el.isJsonArray() ? el.getAsJsonArray() : new JsonArray();
}
}
@@ -140,7 +140,20 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void join(PlayerJoinEvent event) {
if (integration != null) integration.activity.join(event.getPlayer().getUniqueId(), event.getPlayer().getName());
if (integration != null) {
integration.activity.join(event.getPlayer().getUniqueId(), event.getPlayer().getName());
Chunk chunk = event.getPlayer().getLocation().getChunk();
integration.prefetchClaims(dimension(chunk.getWorld()), chunk.getX(), chunk.getZ(), event.getPlayer().getUniqueId());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void move(PlayerMoveEvent event) {
if (integration == null || event.getTo() == null) return;
org.bukkit.Location from = event.getFrom(), to = event.getTo();
if (from.getWorld() == to.getWorld() && (from.getBlockX() >> 4) == (to.getBlockX() >> 4)
&& (from.getBlockZ() >> 4) == (to.getBlockZ() >> 4)) return;
integration.prefetchClaims(dimension(to.getWorld()), to.getBlockX() >> 4, to.getBlockZ() >> 4, event.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -89,6 +89,12 @@ public final class GuildHandler implements CommandExecutor, Listener {
}
String sub = args[0].toLowerCase();
String permission = sub.equals("c") ? "chat" : sub;
if (java.util.Set.of("create", "leave", "claim", "unclaim", "map", "chat", "sethome", "home", "members").contains(permission)
&& !player.hasPermission("scopenet.command.guild." + permission)) {
player.sendMessage(ChatColor.RED + "You do not have permission to use /guild " + permission + ".");
return true;
}
switch (sub) {
case "create" -> handleCreate(player, args);
case "leave" -> handleLeave(player);
@@ -90,7 +90,7 @@ public final class ScopenetCommandHandler implements CommandExecutor {
}
private void sendStatus(CommandSender sender) {
if (!sender.hasPermission("scopenet.admin")) {
if (!sender.hasPermission("scopenet.command.scopenet.status")) {
sender.sendMessage(ChatColor.RED + "You do not have permission to view SCOPENET status.");
return;
}
@@ -119,7 +119,7 @@ public final class ScopenetCommandHandler implements CommandExecutor {
}
private void handleReload(CommandSender sender) {
if (!sender.hasPermission("scopenet.admin")) {
if (!sender.hasPermission("scopenet.command.scopenet.reload")) {
sender.sendMessage(ChatColor.RED + "You do not have permission to reload SCOPENET.");
return;
}
@@ -7,67 +7,200 @@ load: STARTUP
commands:
scopenet:
permission: scopenet.command.scopenet
description: SCOPENET server commands, status, and help
aliases: [sn]
permission: scopenet.use
spawn:
permission: scopenet.command.spawn
description: Teleport to the server spawn point
aliases: [hub, lobby]
home:
permission: scopenet.command.home
description: Teleport to one of your homes or open the homes GUI
aliases: [homes]
sethome:
permission: scopenet.command.sethome
description: Set a new home location
aliases: [createshome]
delhome:
permission: scopenet.command.delhome
description: Delete an existing home
aliases: [rmhome]
back:
permission: scopenet.command.back
description: Return to your previous location or death point
aliases: [return]
tpa:
permission: scopenet.command.tpa
description: Request to teleport to another player
tpaccept:
permission: scopenet.command.tpaccept
description: Accept an incoming teleport request
aliases: [tpyes]
tpdeny:
permission: scopenet.command.tpdeny
description: Deny an incoming teleport request
aliases: [tpno]
rtp:
permission: scopenet.command.rtp
description: Teleport to a random safe location in the wilderness
aliases: [wild, randomtp]
warp:
permission: scopenet.command.warp
description: Teleport to a server warp or open the warps GUI
aliases: [warps]
playtime:
permission: scopenet.command.playtime
description: Check total and session playtime
aliases: [ontime]
balance:
permission: scopenet.command.balance
description: Check your server economy balance
aliases: [bal, money]
pay:
permission: scopenet.command.pay
description: Send money to another player
baltop:
permission: scopenet.command.baltop
description: View the richest players on the server
aliases: [richest]
shop:
permission: scopenet.command.shop
description: Open the interactive server shop GUI
sell:
permission: scopenet.command.sell
description: Open the item selling GUI
market:
permission: scopenet.command.market
description: Open the player marketplace GUI
aliases: [ah, auction]
orders:
permission: scopenet.command.orders
description: View active marketplace orders
trade:
permission: scopenet.command.trade
description: Request a secure trade with another player
transactions:
permission: scopenet.command.transactions
description: View recent economy transactions
guild:
permission: scopenet.command.guild
description: Guild management, members, claims, and relations
aliases: [g, clan]
claim:
permission: scopenet.command.claim
description: Claim the current chunk for your guild
unclaim:
permission: scopenet.command.unclaim
description: Unclaim the current chunk
permissions:
scopenet.admin:
description: Legacy administrator permission
default: op
scopenet.command.scopenet:
description: Use /scopenet
default: true
scopenet.command.spawn:
description: Use /spawn
default: true
scopenet.command.home:
description: Use /home
default: true
scopenet.command.sethome:
description: Use /sethome
default: true
scopenet.command.delhome:
description: Use /delhome
default: true
scopenet.command.back:
description: Use /back
default: true
scopenet.command.tpa:
description: Use /tpa
default: true
scopenet.command.tpaccept:
description: Use /tpaccept
default: true
scopenet.command.tpdeny:
description: Use /tpdeny
default: true
scopenet.command.rtp:
description: Use /rtp
default: true
scopenet.command.warp:
description: Use /warp
default: true
scopenet.command.playtime:
description: Use /playtime
default: true
scopenet.command.balance:
description: Use /balance
default: true
scopenet.command.pay:
description: Use /pay
default: true
scopenet.command.baltop:
description: Use /baltop
default: true
scopenet.command.shop:
description: Use /shop
default: true
scopenet.command.sell:
description: Use /sell
default: true
scopenet.command.market:
description: Use /market
default: true
scopenet.command.orders:
description: Use /orders
default: true
scopenet.command.trade:
description: Use /trade
default: true
scopenet.command.transactions:
description: Use /transactions
default: true
scopenet.command.guild:
description: Use /guild
default: true
scopenet.command.claim:
description: Use /claim
default: true
scopenet.command.unclaim:
description: Use /unclaim
default: true
scopenet.command.scopenet.status:
description: Use /scopenet status
default: op
scopenet.command.scopenet.reload:
description: Use /scopenet reload
default: op
scopenet.command.guild.create:
description: Use /guild create
default: true
scopenet.command.guild.leave:
description: Use /guild leave
default: true
scopenet.command.guild.claim:
description: Use /guild claim
default: true
scopenet.command.guild.unclaim:
description: Use /guild unclaim
default: true
scopenet.command.guild.map:
description: Use /guild map
default: true
scopenet.command.guild.chat:
description: Use /guild chat
default: true
scopenet.command.guild.sethome:
description: Use /guild sethome
default: true
scopenet.command.guild.home:
description: Use /guild home
default: true
scopenet.command.guild.members:
description: Use /guild members
default: true