Rework BlueMap claiming and expand ranks guilds chat and warps
This commit is contained in:
1 parent
434ed5338a
commit
6a2944a88e
68 files changed
+1453
-2500
No files matched your search
@@ -0,0 +1,57 @@
|
||||
package net.scopenet.paper;
|
||||
|
||||
import net.scopenet.api.ScopenetPlayer;
|
||||
import net.scopenet.paper.compat.ScopenetApiImpl;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Formats player chat from the cached SCOPENET profile without a network call on the chat thread. */
|
||||
public final class ChatFormatter {
|
||||
private static final Pattern MARKDOWN = Pattern.compile("(\\*\\*(.+?)\\*\\*|__(.+?)__|~~(.+?)~~|`(.+?)`|\\*(.+?)\\*)");
|
||||
private static final Pattern HEX = Pattern.compile("&#([0-9a-fA-F]{6})");
|
||||
private final ScopenetApiImpl profiles;
|
||||
|
||||
public ChatFormatter(ScopenetApiImpl profiles) { this.profiles = profiles; }
|
||||
|
||||
private static String clean(String value) {
|
||||
return value == null ? "" : ChatColor.stripColor(value).replace('%', ' ').replace('[', ' ').replace(']', ' ').trim();
|
||||
}
|
||||
|
||||
public String format(Player player) {
|
||||
ScopenetPlayer p = profiles.peek(player.getUniqueId()).orElse(null);
|
||||
String guild = p == null || p.guild() == null ? "" : "§3[" + clean(p.guild().tag()) + "] ";
|
||||
String title = p == null || p.rankTitle() == null || p.rankTitle().isBlank() ? "Novice" : clean(p.rankTitle());
|
||||
String group = p == null || p.rankGroup() == null || p.rankGroup().isBlank() ? "Member"
|
||||
: p.rankDisplay() == null || p.rankDisplay().isBlank() ? clean(p.rankGroup()) : clean(p.rankDisplay());
|
||||
String prefix = p == null ? "" : p.rankPrefix();
|
||||
String suffix = p == null ? "" : p.rankSuffix();
|
||||
String decorated = prefix == null || prefix.isBlank() ? "" : ChatColor.translateAlternateColorCodes('&', prefix.replace("%", "%%")) + " ";
|
||||
String name = player.getDisplayName().replace("%", "%%");
|
||||
String tail = suffix == null || suffix.isBlank() ? "" : " " + ChatColor.translateAlternateColorCodes('&', suffix.replace("%", "%%"));
|
||||
return guild + "§6[" + title + "] §b[" + group + "] §r" + decorated + name + tail + "§7: §f%2$s";
|
||||
}
|
||||
|
||||
public String message(Player player, String raw) {
|
||||
String input = player.hasPermission("scopenet.chat.color") ? ChatColor.translateAlternateColorCodes('&', raw) : ChatColor.stripColor(raw);
|
||||
if (player.hasPermission("scopenet.chat.color")) {
|
||||
Matcher hex = HEX.matcher(input);
|
||||
StringBuffer colored = new StringBuffer();
|
||||
while (hex.find()) hex.appendReplacement(colored, Matcher.quoteReplacement(net.md_5.bungee.api.ChatColor.of('#' + hex.group(1)).toString()));
|
||||
hex.appendTail(colored);
|
||||
input = colored.toString();
|
||||
}
|
||||
Matcher matcher = MARKDOWN.matcher(input);
|
||||
StringBuffer out = new StringBuffer();
|
||||
while (matcher.find()) {
|
||||
String code = matcher.group(2) != null ? "§l" : matcher.group(3) != null ? "§n" : matcher.group(4) != null ? "§m" : matcher.group(5) != null ? "§7" : "§o";
|
||||
String text = matcher.group(2) != null ? matcher.group(2) : matcher.group(3) != null ? matcher.group(3)
|
||||
: matcher.group(4) != null ? matcher.group(4) : matcher.group(5) != null ? matcher.group(5) : matcher.group(6);
|
||||
matcher.appendReplacement(out, Matcher.quoteReplacement(code + text + "§r§f"));
|
||||
}
|
||||
matcher.appendTail(out);
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
private ClientLinkBridge clientLink;
|
||||
private EssentialsHandler essentialsHandler;
|
||||
private GuildHandler guildHandler;
|
||||
private boolean liveMapStarted;
|
||||
private ChatFormatter chatFormatter;
|
||||
|
||||
@Override public void onEnable() {
|
||||
// Install the deny-by-default listener even if configuration is invalid.
|
||||
@@ -51,17 +51,16 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
// Claim protection runs from a local copy of the panel's claims, refreshed in the background.
|
||||
integration.startClaimSync();
|
||||
|
||||
// Central Live Map: mirror chunks and player positions to the panel.
|
||||
// Live positions center the in-app BlueMap claim view on the player.
|
||||
if (getConfig().getBoolean("livemap.enabled", true)) {
|
||||
// STARTUP plugins enable before Paper loads any worlds. WorldLoadEvent starts
|
||||
// the map on a fresh boot; this also handles enabling after worlds exist.
|
||||
for (World world : Bukkit.getWorlds()) startLiveMap(world);
|
||||
integration.enableLiveMap();
|
||||
Bukkit.getScheduler().runTaskTimer(this, this::snapshotPlayers, 40, 20);
|
||||
}
|
||||
|
||||
// Developer API: other plugins use SCOPENET through ScopenetApiProvider / the services manager,
|
||||
// and receive level-ups, achievements and guild changes as ordinary Bukkit events.
|
||||
api = new ScopenetApiImpl(integration.client());
|
||||
chatFormatter = new ChatFormatter(api);
|
||||
ScopenetApiProvider.register(api);
|
||||
getServer().getServicesManager().register(ScopenetApi.class, api, this, org.bukkit.plugin.ServicePriority.Normal);
|
||||
// Optional client mod (HUD, claim overlay, shop and market windows).
|
||||
@@ -85,9 +84,16 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
EssentialsHandler essentials = new EssentialsHandler(this);
|
||||
essentialsHandler = essentials;
|
||||
getServer().getPluginManager().registerEvents(essentials, this);
|
||||
String[] essCmds = {"spawn", "home", "sethome", "delhome", "back", "tpa", "tpaccept", "tpdeny", "rtp", "warp", "playtime"};
|
||||
String[] essCmds = {"spawn", "home", "sethome", "delhome", "back", "tpa", "tpaccept", "tpdeny", "rtp", "warp", "reqwarp", "warprequests", "warpapprove", "warpdeny", "setwarp", "delwarp", "playtime"};
|
||||
for (String c : essCmds) bindCommand(c, essentials);
|
||||
}
|
||||
HandHandler hand = new HandHandler(this);
|
||||
getServer().getPluginManager().registerEvents(hand, this);
|
||||
bindCommand("hand", hand);
|
||||
bindCommand("handview", hand);
|
||||
NicknameHandler nick = new NicknameHandler(this);
|
||||
getServer().getPluginManager().registerEvents(nick, this);
|
||||
bindCommand("nick", nick);
|
||||
|
||||
// Register Economy commands & GUI
|
||||
{
|
||||
@@ -105,6 +111,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
bindCommand("guild", guilds);
|
||||
bindCommand("claim", guilds);
|
||||
bindCommand("unclaim", guilds);
|
||||
bindCommand("autoclaim", guilds);
|
||||
}
|
||||
|
||||
// Physical shops, actions from the map, and (when BlueMap is installed) claims, pins and the player panel on it.
|
||||
@@ -126,18 +133,6 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void worldLoaded(org.bukkit.event.world.WorldLoadEvent event) {
|
||||
startLiveMap(event.getWorld());
|
||||
}
|
||||
|
||||
private void startLiveMap(World world) {
|
||||
if (liveMapStarted || integration == null || world.getEnvironment() != World.Environment.NORMAL
|
||||
|| !getConfig().getBoolean("livemap.enabled", true)) return;
|
||||
integration.enableLiveMap(world.getWorldFolder().toPath());
|
||||
liveMapStarted = true;
|
||||
}
|
||||
|
||||
private Settings readSettings() {
|
||||
return Settings.of(
|
||||
getConfig().getString("panel-url", ""),
|
||||
@@ -181,11 +176,6 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
integration.liveMapPlayers(list);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void worldSaved(org.bukkit.event.world.WorldSaveEvent event) {
|
||||
if (integration != null) integration.liveMapSaved();
|
||||
}
|
||||
|
||||
public static String dimension(World world) {
|
||||
return world.getKey().toString();
|
||||
}
|
||||
@@ -372,8 +362,12 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener {
|
||||
if (claimed(event.getBlock())) event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void chat(AsyncPlayerChatEvent event) {
|
||||
if (integration != null && integration.settings().socialEnabled() && integration.settings().chatPrefixesEnabled() && chatFormatter != null) {
|
||||
event.setFormat(chatFormatter.format(event.getPlayer()));
|
||||
event.setMessage(chatFormatter.message(event.getPlayer(), event.getMessage()));
|
||||
}
|
||||
add(event.getPlayer(), "messages");
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
|
||||
private final ScopenetPlugin plugin;
|
||||
private final File homesFile;
|
||||
private final File warpsFile;
|
||||
private final File warpRequestsFile;
|
||||
private final YamlConfiguration homesConfig;
|
||||
private final YamlConfiguration warpsConfig;
|
||||
private final YamlConfiguration warpRequestsConfig;
|
||||
|
||||
private final Map<UUID, Map<String, Location>> homes = new ConcurrentHashMap<>();
|
||||
private final Map<String, Location> warps = new ConcurrentHashMap<>();
|
||||
@@ -43,8 +45,10 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
|
||||
this.plugin = plugin;
|
||||
this.homesFile = new File(plugin.getDataFolder(), "homes.yml");
|
||||
this.warpsFile = new File(plugin.getDataFolder(), "warps.yml");
|
||||
this.warpRequestsFile = new File(plugin.getDataFolder(), "warp_requests.yml");
|
||||
this.homesConfig = YamlConfiguration.loadConfiguration(homesFile);
|
||||
this.warpsConfig = YamlConfiguration.loadConfiguration(warpsFile);
|
||||
this.warpRequestsConfig = YamlConfiguration.loadConfiguration(warpRequestsFile);
|
||||
loadHomes();
|
||||
loadWarps();
|
||||
}
|
||||
@@ -100,6 +104,11 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private void saveWarpRequests() {
|
||||
try { warpRequestsConfig.save(warpRequestsFile); }
|
||||
catch (IOException e) { plugin.getLogger().warning("Failed to save warp requests: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onTeleport(PlayerTeleportEvent event) {
|
||||
if (event.getFrom() != null && event.getCause() != PlayerTeleportEvent.TeleportCause.UNKNOWN) {
|
||||
@@ -160,6 +169,12 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
|
||||
case "tpdeny" -> handleTpDeny(player);
|
||||
case "rtp" -> handleRtp(player);
|
||||
case "warp" -> handleWarp(player, args);
|
||||
case "reqwarp" -> requestWarp(player, args);
|
||||
case "warprequests" -> listWarpRequests(player);
|
||||
case "warpapprove" -> reviewWarp(player, args, true);
|
||||
case "warpdeny" -> reviewWarp(player, args, false);
|
||||
case "setwarp" -> setWarp(player, args);
|
||||
case "delwarp" -> deleteWarp(player, args);
|
||||
case "playtime" -> handlePlaytime(player, args);
|
||||
default -> { return false; }
|
||||
}
|
||||
@@ -389,6 +404,88 @@ public final class EssentialsHandler implements CommandExecutor, Listener {
|
||||
player.sendMessage(ChatColor.GREEN + "Teleported to warp " + ChatColor.YELLOW + warpName + ChatColor.GREEN + ".");
|
||||
}
|
||||
|
||||
private String warpName(Player player, String[] args, String usage) {
|
||||
if (args.length != 1 || !args[0].matches("[A-Za-z0-9_-]{2,24}")) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: " + usage + " (2–24 letters, numbers, _ or -)");
|
||||
return null;
|
||||
}
|
||||
return args[0].toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private void requestWarp(Player player, String[] args) {
|
||||
String name = warpName(player, args, "/reqwarp <name>");
|
||||
if (name == null) return;
|
||||
if (warps.containsKey(name)) { player.sendMessage(ChatColor.RED + "That warp already exists."); return; }
|
||||
if (warpRequestsConfig.contains(name) && !player.getUniqueId().toString().equals(warpRequestsConfig.getString(name + ".uuid"))) {
|
||||
player.sendMessage(ChatColor.RED + "That warp name has already been requested."); return;
|
||||
}
|
||||
warpRequestsConfig.set(name + ".location", player.getLocation());
|
||||
warpRequestsConfig.set(name + ".uuid", player.getUniqueId().toString());
|
||||
warpRequestsConfig.set(name + ".requester", player.getName());
|
||||
warpRequestsConfig.set(name + ".created_at", System.currentTimeMillis());
|
||||
saveWarpRequests();
|
||||
player.sendMessage(ChatColor.GREEN + "Warp request sent. A server admin can approve it with /warpapprove " + name + ".");
|
||||
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission("scopenet.command.warp.manage"))
|
||||
.forEach(p -> p.sendMessage(ChatColor.YELLOW + player.getName() + " requested warp '" + name + "'. Use /warprequests to review."));
|
||||
}
|
||||
|
||||
private boolean warpAdmin(Player player) {
|
||||
if (player.hasPermission("scopenet.command.warp.manage")) return true;
|
||||
player.sendMessage(ChatColor.RED + "Only server admins can manage warps.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void listWarpRequests(Player player) {
|
||||
if (!warpAdmin(player)) return;
|
||||
if (warpRequestsConfig.getKeys(false).isEmpty()) { player.sendMessage(ChatColor.YELLOW + "No pending warp requests."); return; }
|
||||
player.sendMessage(ChatColor.GOLD + "Pending warp requests:");
|
||||
for (String name : warpRequestsConfig.getKeys(false)) {
|
||||
Location at = warpRequestsConfig.getLocation(name + ".location");
|
||||
player.sendMessage(ChatColor.YELLOW + name + ChatColor.GRAY + " by " + warpRequestsConfig.getString(name + ".requester", "unknown")
|
||||
+ (at == null || at.getWorld() == null ? "" : " at " + at.getWorld().getName() + " " + at.getBlockX() + ", " + at.getBlockY() + ", " + at.getBlockZ()));
|
||||
}
|
||||
}
|
||||
|
||||
private void reviewWarp(Player player, String[] args, boolean approve) {
|
||||
if (!warpAdmin(player)) return;
|
||||
String name = warpName(player, args, approve ? "/warpapprove <name>" : "/warpdeny <name>");
|
||||
if (name == null) return;
|
||||
Location at = warpRequestsConfig.getLocation(name + ".location");
|
||||
if (at == null || at.getWorld() == null) { player.sendMessage(ChatColor.RED + "No pending request with a loaded world has that name."); return; }
|
||||
if (approve) {
|
||||
if (warps.containsKey(name)) { player.sendMessage(ChatColor.RED + "Warp already exists. Deny this request or choose another name."); return; }
|
||||
warps.put(name, at);
|
||||
saveWarps();
|
||||
}
|
||||
String uuid = warpRequestsConfig.getString(name + ".uuid", "");
|
||||
warpRequestsConfig.set(name, null);
|
||||
saveWarpRequests();
|
||||
player.sendMessage(ChatColor.GREEN + "Warp request " + (approve ? "approved" : "denied") + ": " + name);
|
||||
try {
|
||||
Player requester = Bukkit.getPlayer(UUID.fromString(uuid));
|
||||
if (requester != null) requester.sendMessage(ChatColor.YELLOW + "Your warp request '" + name + "' was " + (approve ? "approved" : "denied") + ".");
|
||||
} catch (IllegalArgumentException ignored) {}
|
||||
}
|
||||
|
||||
private void setWarp(Player player, String[] args) {
|
||||
if (!warpAdmin(player)) return;
|
||||
String name = warpName(player, args, "/setwarp <name>");
|
||||
if (name == null) return;
|
||||
warps.put(name, player.getLocation());
|
||||
saveWarps();
|
||||
player.sendMessage(ChatColor.GREEN + "Warp set: " + name);
|
||||
}
|
||||
|
||||
private void deleteWarp(Player player, String[] args) {
|
||||
if (!warpAdmin(player)) return;
|
||||
String name = warpName(player, args, "/delwarp <name>");
|
||||
if (name == null) return;
|
||||
if (warps.remove(name) == null) { player.sendMessage(ChatColor.RED + "Warp not found."); return; }
|
||||
warpsConfig.set(name, null);
|
||||
saveWarps();
|
||||
player.sendMessage(ChatColor.GREEN + "Warp deleted: " + name);
|
||||
}
|
||||
|
||||
private void openWarpPicker(Player player) {
|
||||
Inventory inv = Bukkit.createInventory(null, 27, GUI_WARPS_TITLE);
|
||||
int slot = 11;
|
||||
|
||||
@@ -20,6 +20,8 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
@@ -34,6 +36,8 @@ public final class GuildHandler implements CommandExecutor, Listener {
|
||||
private final File guildHomesFile;
|
||||
private final YamlConfiguration guildHomesConfig;
|
||||
private final Map<String, Location> guildHomes = new ConcurrentHashMap<>();
|
||||
private final Set<UUID> autoClaim = ConcurrentHashMap.newKeySet();
|
||||
private final Map<UUID, String> lastAutoClaim = new ConcurrentHashMap<>();
|
||||
|
||||
private static final String GUI_GUILD_TITLE = ChatColor.DARK_AQUA + "Guild Dashboard";
|
||||
|
||||
@@ -86,6 +90,23 @@ public final class GuildHandler implements CommandExecutor, Listener {
|
||||
handleUnclaim(player);
|
||||
return true;
|
||||
}
|
||||
if (cmd.equals("autoclaim")) {
|
||||
if (!player.hasPermission("scopenet.command.claim")) { player.sendMessage(ChatColor.RED + "You cannot claim land."); return true; }
|
||||
if (args.length != 1 || (!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off"))) {
|
||||
player.sendMessage(ChatColor.YELLOW + "Usage: /autoclaim on|off"); return true;
|
||||
}
|
||||
if (args[0].equalsIgnoreCase("on")) {
|
||||
autoClaim.add(player.getUniqueId());
|
||||
lastAutoClaim.remove(player.getUniqueId());
|
||||
player.sendMessage(ChatColor.GREEN + "Auto-claim is on. New chunks you walk into will be claimed for your guild.");
|
||||
claimOnMove(player, player.getLocation());
|
||||
} else {
|
||||
autoClaim.remove(player.getUniqueId());
|
||||
lastAutoClaim.remove(player.getUniqueId());
|
||||
player.sendMessage(ChatColor.YELLOW + "Auto-claim is off.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmd.equals("guild") || cmd.equals("g")) {
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("info") || args[0].equalsIgnoreCase("gui")) {
|
||||
@@ -211,6 +232,39 @@ public final class GuildHandler implements CommandExecutor, Listener {
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onMove(PlayerMoveEvent event) {
|
||||
if (event.getTo() == null || !autoClaim.contains(event.getPlayer().getUniqueId())) return;
|
||||
if (event.getFrom().getWorld() == event.getTo().getWorld()
|
||||
&& event.getFrom().getChunk().getX() == event.getTo().getChunk().getX()
|
||||
&& event.getFrom().getChunk().getZ() == event.getTo().getChunk().getZ()) return;
|
||||
claimOnMove(event.getPlayer(), event.getTo());
|
||||
}
|
||||
|
||||
@EventHandler public void onQuit(PlayerQuitEvent event) {
|
||||
autoClaim.remove(event.getPlayer().getUniqueId());
|
||||
lastAutoClaim.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
private void claimOnMove(Player player, Location location) {
|
||||
if (!integration.settings().landClaimingEnabled()) return;
|
||||
int x = location.getBlockX() >> 4, z = location.getBlockZ() >> 4;
|
||||
String dim = ScopenetPlugin.dimension(location.getWorld());
|
||||
String key = dim + ':' + x + ':' + z;
|
||||
if (key.equals(lastAutoClaim.put(player.getUniqueId(), key))) return;
|
||||
if (integration.isClaimed(dim, x, z)) return;
|
||||
UUID uuid = player.getUniqueId();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
integration.client().claimChunk(dim, x, z, uuid);
|
||||
player.sendMessage(ChatColor.GREEN + "Auto-claimed chunk [" + x + ", " + z + "].");
|
||||
} catch (Exception e) {
|
||||
autoClaim.remove(uuid);
|
||||
player.sendMessage(ChatColor.RED + "Auto-claim stopped: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleUnclaim(Player player) {
|
||||
Chunk chunk = player.getLocation().getChunk();
|
||||
String dim = ScopenetPlugin.dimension(player.getWorld());
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package net.scopenet.paper.commands;
|
||||
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** Share a read-only snapshot of the held item through a clickable chat message. */
|
||||
public final class HandHandler implements CommandExecutor, Listener {
|
||||
private record Shared(ItemStack item, long expires) {}
|
||||
private final Map<String, Shared> shares = new ConcurrentHashMap<>();
|
||||
private static final String TITLE = ChatColor.DARK_GRAY + "Shared item";
|
||||
|
||||
public HandHandler(Plugin plugin) {
|
||||
Bukkit.getScheduler().runTaskTimer(plugin, () -> shares.entrySet().removeIf(e -> e.getValue().expires < System.currentTimeMillis()), 1200, 1200);
|
||||
}
|
||||
|
||||
@Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Only players can use this command."); return true; }
|
||||
if (command.getName().equalsIgnoreCase("handview")) {
|
||||
if (args.length != 1) return false;
|
||||
Shared shared = shares.get(args[0]);
|
||||
if (shared == null || shared.expires < System.currentTimeMillis()) { player.sendMessage(ChatColor.RED + "That item share has expired."); return true; }
|
||||
Inventory view = Bukkit.createInventory(null, 9, TITLE);
|
||||
view.setItem(4, shared.item.clone());
|
||||
player.openInventory(view);
|
||||
return true;
|
||||
}
|
||||
ItemStack item = player.getInventory().getItemInMainHand();
|
||||
if (item.getType() == Material.AIR) { player.sendMessage(ChatColor.RED + "Hold an item first."); return true; }
|
||||
String token = UUID.randomUUID().toString().substring(0, 8);
|
||||
shares.put(token, new Shared(item.clone(), System.currentTimeMillis() + 300_000));
|
||||
String name = item.hasItemMeta() && item.getItemMeta().hasDisplayName()
|
||||
? item.getItemMeta().getDisplayName() : item.getType().name().toLowerCase().replace('_', ' ');
|
||||
TextComponent link = new TextComponent("[" + name + " ×" + item.getAmount() + "]");
|
||||
link.setColor(net.md_5.bungee.api.ChatColor.AQUA);
|
||||
link.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handview " + token));
|
||||
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to inspect " + name + " (5 minutes)").create()));
|
||||
TextComponent line = new TextComponent(player.getDisplayName() + ChatColor.WHITE + " is holding ");
|
||||
line.addExtra(link);
|
||||
for (Player target : Bukkit.getOnlinePlayers()) {
|
||||
target.spigot().sendMessage(line);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@EventHandler public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (TITLE.equals(event.getView().getTitle())) event.setCancelled(true);
|
||||
}
|
||||
@EventHandler public void onInventoryDrag(InventoryDragEvent event) {
|
||||
if (TITLE.equals(event.getView().getTitle())) event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.scopenet.paper.commands;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Player-owned chat nicknames, keyed by UUID and restored on every join. */
|
||||
public final class NicknameHandler implements CommandExecutor, Listener {
|
||||
private final Plugin plugin;
|
||||
private final File file;
|
||||
private final YamlConfiguration config;
|
||||
|
||||
public NicknameHandler(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.file = new File(plugin.getDataFolder(), "nicknames.yml");
|
||||
this.config = YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
@EventHandler public void join(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
String saved = config.getString(player.getUniqueId().toString());
|
||||
if (saved != null && !saved.isBlank()) player.setDisplayName(ChatColor.translateAlternateColorCodes('&', saved));
|
||||
}
|
||||
|
||||
@Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Only players can set a nickname."); return true; }
|
||||
if (args.length != 1) { player.sendMessage(ChatColor.YELLOW + "Usage: /nick <name|off>"); return true; }
|
||||
String value = args[0];
|
||||
if (value.equalsIgnoreCase("off")) {
|
||||
config.set(player.getUniqueId().toString(), null);
|
||||
player.setDisplayName(player.getName());
|
||||
save();
|
||||
player.sendMessage(ChatColor.GREEN + "Nickname removed.");
|
||||
return true;
|
||||
}
|
||||
String plain = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', value));
|
||||
if (plain.length() < 2 || plain.length() > 24 || !plain.matches("[A-Za-z0-9_ -]+")) {
|
||||
player.sendMessage(ChatColor.RED + "Nicknames must be 2–24 letters, numbers, spaces, _ or -.");
|
||||
return true;
|
||||
}
|
||||
if (!player.hasPermission("scopenet.chat.color")) value = plain;
|
||||
config.set(player.getUniqueId().toString(), value);
|
||||
player.setDisplayName(ChatColor.translateAlternateColorCodes('&', value));
|
||||
save();
|
||||
player.sendMessage(ChatColor.GREEN + "Nickname set to " + player.getDisplayName() + ChatColor.GREEN + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try { config.save(file); }
|
||||
catch (IOException e) { plugin.getLogger().warning("Failed to save nicknames: " + e.getMessage()); }
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ public final class LuckPermsModule implements IntegrationModule {
|
||||
JsonObject config = answer.has("config") && answer.get("config").isJsonObject() ? answer.getAsJsonObject("config") : new JsonObject();
|
||||
if (config.has("mode")) lastMode = config.get("mode").getAsString();
|
||||
if (context.getBoolean("luckperms", "apply-panel-groups", false) && config.has("assign")) apply(config.getAsJsonArray("assign"));
|
||||
if (config.has("level_assign")) apply(config.getAsJsonArray("level_assign"));
|
||||
} catch (Exception e) {
|
||||
context.log().fine("LuckPerms report failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public final class ScopenetApiImpl implements ScopenetApi, AutoCloseable {
|
||||
guild == null ? null : new GuildSummary(str(guild, "id"), str(guild, "name"), str(guild, "tag"), str(guild, "role"), (int) num(guild, "claims")),
|
||||
dbl(j, "balance"), num(j, "playtime_secs"), num(j, "server_playtime_secs"), (int) num(j, "kills"), (int) num(j, "deaths"),
|
||||
(int) num(j, "friends"), (int) num(j, "achievements"), quests(j, "daily"), quests(j, "weekly"),
|
||||
str(j, "joined"), str(rank, "group"), str(rank, "prefix"));
|
||||
str(j, "joined"), str(rank, "group"), str(rank, "display"), str(rank, "prefix"), str(rank, "suffix"));
|
||||
}
|
||||
|
||||
static ScopenetGuild guild(JsonObject j) {
|
||||
|
||||
@@ -52,9 +52,8 @@ essentials:
|
||||
rtp-radius: 2500
|
||||
|
||||
livemap:
|
||||
# Send world chunks and live player positions to the panel's central Live Map.
|
||||
# Players open it from the launcher; you don't run Vantage on this server.
|
||||
# It also needs "Live Map" switched on for this server in the panel.
|
||||
# Send live player positions to the panel for the launcher BlueMap claim view.
|
||||
# Configure BlueMap's public address and enable live positions in the panel.
|
||||
enabled: true
|
||||
|
||||
economy:
|
||||
|
||||
@@ -50,10 +50,37 @@ commands:
|
||||
permission: scopenet.command.warp
|
||||
description: Teleport to a server warp or open the warps GUI
|
||||
aliases: [warps]
|
||||
reqwarp:
|
||||
permission: scopenet.command.reqwarp
|
||||
description: Request a new public warp at your location
|
||||
warprequests:
|
||||
permission: scopenet.command.warp.manage
|
||||
description: List pending warp requests
|
||||
warpapprove:
|
||||
permission: scopenet.command.warp.manage
|
||||
description: Approve a pending warp request
|
||||
warpdeny:
|
||||
permission: scopenet.command.warp.manage
|
||||
description: Deny a pending warp request
|
||||
setwarp:
|
||||
permission: scopenet.command.warp.manage
|
||||
description: Set a public warp at your location
|
||||
delwarp:
|
||||
permission: scopenet.command.warp.manage
|
||||
description: Delete a public warp
|
||||
playtime:
|
||||
permission: scopenet.command.playtime
|
||||
description: Check total and session playtime
|
||||
aliases: [ontime]
|
||||
hand:
|
||||
permission: scopenet.command.hand
|
||||
description: Share your held item in chat
|
||||
handview:
|
||||
permission: scopenet.command.hand
|
||||
description: Inspect a shared item
|
||||
nick:
|
||||
permission: scopenet.command.nick
|
||||
description: Set or remove your chat nickname
|
||||
balance:
|
||||
permission: scopenet.command.balance
|
||||
description: Check your server economy balance
|
||||
@@ -94,6 +121,9 @@ commands:
|
||||
unclaim:
|
||||
permission: scopenet.command.unclaim
|
||||
description: Unclaim the current chunk
|
||||
autoclaim:
|
||||
permission: scopenet.command.claim
|
||||
description: Claim chunks as you walk into them
|
||||
|
||||
|
||||
permissions:
|
||||
@@ -137,9 +167,21 @@ permissions:
|
||||
scopenet.command.warp:
|
||||
description: Use /warp
|
||||
default: true
|
||||
scopenet.command.reqwarp:
|
||||
description: Request a public warp
|
||||
default: true
|
||||
scopenet.command.warp.manage:
|
||||
description: Review and manage public warps
|
||||
default: op
|
||||
scopenet.command.playtime:
|
||||
description: Use /playtime
|
||||
default: true
|
||||
scopenet.command.hand:
|
||||
description: Share and inspect held items in chat
|
||||
default: true
|
||||
scopenet.command.nick:
|
||||
description: Set or remove your chat nickname
|
||||
default: true
|
||||
scopenet.command.balance:
|
||||
description: Use /balance
|
||||
default: true
|
||||
@@ -176,6 +218,9 @@ permissions:
|
||||
scopenet.command.unclaim:
|
||||
description: Use /unclaim
|
||||
default: true
|
||||
scopenet.chat.color:
|
||||
description: Use Minecraft color codes in chat
|
||||
default: op
|
||||
scopenet.command.scopenet.help:
|
||||
description: Use /scopenet help
|
||||
default: true
|
||||
|
||||
Reference in new issue
Block a user