feat: full name injection - nicks replace real names in tab complete, /tpa, /msg, and all commands
This commit is contained in:
@@ -9,7 +9,9 @@ import com.nickcore.paper.command.NickCommand;
|
|||||||
import com.nickcore.paper.gui.GuiManager;
|
import com.nickcore.paper.gui.GuiManager;
|
||||||
import com.nickcore.paper.integration.NickCoreExpansion;
|
import com.nickcore.paper.integration.NickCoreExpansion;
|
||||||
import com.nickcore.paper.integration.VelocityMessaging;
|
import com.nickcore.paper.integration.VelocityMessaging;
|
||||||
|
import com.nickcore.paper.listener.CommandPreprocessListener;
|
||||||
import com.nickcore.paper.listener.PlayerConnectionListener;
|
import com.nickcore.paper.listener.PlayerConnectionListener;
|
||||||
|
import com.nickcore.paper.listener.TabCompleteListener;
|
||||||
import com.nickcore.paper.repository.DatabaseManager;
|
import com.nickcore.paper.repository.DatabaseManager;
|
||||||
import com.nickcore.paper.repository.SqlNickRepository;
|
import com.nickcore.paper.repository.SqlNickRepository;
|
||||||
import com.nickcore.paper.scheduler.SchedulerAdapter;
|
import com.nickcore.paper.scheduler.SchedulerAdapter;
|
||||||
@@ -40,6 +42,7 @@ public final class NickCorePlugin extends JavaPlugin {
|
|||||||
private PlaceholderService placeholderService;
|
private PlaceholderService placeholderService;
|
||||||
private NametagService nametagService;
|
private NametagService nametagService;
|
||||||
private ChatService chatService;
|
private ChatService chatService;
|
||||||
|
private NameOverrideService nameOverrideService;
|
||||||
private SchedulerAdapter scheduler;
|
private SchedulerAdapter scheduler;
|
||||||
private VelocityMessaging velocityMessaging;
|
private VelocityMessaging velocityMessaging;
|
||||||
private NickCoreExpansion placeholderExpansion;
|
private NickCoreExpansion placeholderExpansion;
|
||||||
@@ -87,6 +90,7 @@ public final class NickCorePlugin extends JavaPlugin {
|
|||||||
placeholderService = new PlaceholderService(profileCache, rankService);
|
placeholderService = new PlaceholderService(profileCache, rankService);
|
||||||
nametagService = new NametagService(profileCache, rankService, getLogger());
|
nametagService = new NametagService(profileCache, rankService, getLogger());
|
||||||
chatService = new ChatService(profileCache, rankService, getLogger());
|
chatService = new ChatService(profileCache, rankService, getLogger());
|
||||||
|
nameOverrideService = new NameOverrideService(profileCache);
|
||||||
|
|
||||||
// Load data files
|
// Load data files
|
||||||
loadNames();
|
loadNames();
|
||||||
@@ -104,8 +108,8 @@ public final class NickCorePlugin extends JavaPlugin {
|
|||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
NickCommand nickCommand = new NickCommand(nickService, rankService, skinService,
|
NickCommand nickCommand = new NickCommand(nickService, rankService, skinService,
|
||||||
placeholderService, nametagService, profileCache, guiManager,
|
placeholderService, nametagService, nameOverrideService, profileCache,
|
||||||
getLogger(), () -> { reloadPlugin(); return null; });
|
guiManager, getLogger(), () -> { reloadPlugin(); return null; });
|
||||||
|
|
||||||
var cmd = getCommand("nick");
|
var cmd = getCommand("nick");
|
||||||
if (cmd != null) {
|
if (cmd != null) {
|
||||||
@@ -116,9 +120,14 @@ public final class NickCorePlugin extends JavaPlugin {
|
|||||||
// Listeners
|
// Listeners
|
||||||
Bukkit.getPluginManager().registerEvents(
|
Bukkit.getPluginManager().registerEvents(
|
||||||
new PlayerConnectionListener(this, nickService, skinService,
|
new PlayerConnectionListener(this, nickService, skinService,
|
||||||
placeholderService, nametagService, scheduler, getLogger()), this);
|
placeholderService, nametagService, nameOverrideService,
|
||||||
|
scheduler, getLogger()), this);
|
||||||
Bukkit.getPluginManager().registerEvents(chatService, this);
|
Bukkit.getPluginManager().registerEvents(chatService, this);
|
||||||
Bukkit.getPluginManager().registerEvents(guiManager, this);
|
Bukkit.getPluginManager().registerEvents(guiManager, this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(
|
||||||
|
new CommandPreprocessListener(nameOverrideService, getLogger()), this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(
|
||||||
|
new TabCompleteListener(nameOverrideService), this);
|
||||||
|
|
||||||
// Integrations
|
// Integrations
|
||||||
registerPlaceholderAPI();
|
registerPlaceholderAPI();
|
||||||
@@ -146,6 +155,7 @@ public final class NickCorePlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear caches
|
// Clear caches
|
||||||
|
if (nameOverrideService != null) nameOverrideService.clear();
|
||||||
if (profileCache != null) profileCache.clear();
|
if (profileCache != null) profileCache.clear();
|
||||||
if (placeholderService != null) placeholderService.clear();
|
if (placeholderService != null) placeholderService.clear();
|
||||||
if (skinService != null) skinService.clearCache();
|
if (skinService != null) skinService.clearCache();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.nickcore.paper.gui.GuiManager;
|
|||||||
import com.nickcore.paper.service.NickService;
|
import com.nickcore.paper.service.NickService;
|
||||||
import com.nickcore.paper.service.PlaceholderService;
|
import com.nickcore.paper.service.PlaceholderService;
|
||||||
import com.nickcore.paper.service.NametagService;
|
import com.nickcore.paper.service.NametagService;
|
||||||
|
import com.nickcore.paper.service.NameOverrideService;
|
||||||
import com.nickcore.paper.service.RankService;
|
import com.nickcore.paper.service.RankService;
|
||||||
import com.nickcore.paper.service.SkinService;
|
import com.nickcore.paper.service.SkinService;
|
||||||
import com.nickcore.paper.cache.ProfileCache;
|
import com.nickcore.paper.cache.ProfileCache;
|
||||||
@@ -49,6 +50,7 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
private final SkinService skinService;
|
private final SkinService skinService;
|
||||||
private final PlaceholderService placeholderService;
|
private final PlaceholderService placeholderService;
|
||||||
private final NametagService nametagService;
|
private final NametagService nametagService;
|
||||||
|
private final NameOverrideService nameOverrideService;
|
||||||
private final ProfileCache profileCache;
|
private final ProfileCache profileCache;
|
||||||
private final GuiManager guiManager;
|
private final GuiManager guiManager;
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
@@ -57,13 +59,15 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
public NickCommand(NickService nickService, RankService rankService,
|
public NickCommand(NickService nickService, RankService rankService,
|
||||||
SkinService skinService, PlaceholderService placeholderService,
|
SkinService skinService, PlaceholderService placeholderService,
|
||||||
NametagService nametagService, ProfileCache profileCache,
|
NametagService nametagService, NameOverrideService nameOverrideService,
|
||||||
GuiManager guiManager, Logger logger, Supplier<Void> reloadAction) {
|
ProfileCache profileCache, GuiManager guiManager,
|
||||||
|
Logger logger, Supplier<Void> reloadAction) {
|
||||||
this.nickService = nickService;
|
this.nickService = nickService;
|
||||||
this.rankService = rankService;
|
this.rankService = rankService;
|
||||||
this.skinService = skinService;
|
this.skinService = skinService;
|
||||||
this.placeholderService = placeholderService;
|
this.placeholderService = placeholderService;
|
||||||
this.nametagService = nametagService;
|
this.nametagService = nametagService;
|
||||||
|
this.nameOverrideService = nameOverrideService;
|
||||||
this.profileCache = profileCache;
|
this.profileCache = profileCache;
|
||||||
this.guiManager = guiManager;
|
this.guiManager = guiManager;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -226,7 +230,8 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
sendMessage(player, "<red>Usage: /nick info <player>");
|
sendMessage(player, "<red>Usage: /nick info <player>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player target = Bukkit.getPlayer(args[1]);
|
// Resolve by nick name first, then real name
|
||||||
|
Player target = nameOverrideService.resolvePlayer(args[1]).orElse(null);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
sendMessage(player, "<red>Player not found.");
|
sendMessage(player, "<red>Player not found.");
|
||||||
return;
|
return;
|
||||||
@@ -253,7 +258,8 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
sendMessage(player, "<red>Usage: /nick force <player> <nickname>");
|
sendMessage(player, "<red>Usage: /nick force <player> <nickname>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player target = Bukkit.getPlayer(args[1]);
|
// Resolve by nick name first, then real name
|
||||||
|
Player target = nameOverrideService.resolvePlayer(args[1]).orElse(null);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
sendMessage(player, "<red>Player not found.");
|
sendMessage(player, "<red>Player not found.");
|
||||||
return;
|
return;
|
||||||
@@ -280,7 +286,8 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
sendMessage(player, "<red>Usage: /nick clear <player>");
|
sendMessage(player, "<red>Usage: /nick clear <player>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player target = Bukkit.getPlayer(args[1]);
|
// Resolve by nick name first, then real name
|
||||||
|
Player target = nameOverrideService.resolvePlayer(args[1]).orElse(null);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
sendMessage(player, "<red>Player not found.");
|
sendMessage(player, "<red>Player not found.");
|
||||||
return;
|
return;
|
||||||
@@ -301,13 +308,8 @@ public final class NickCommand implements CommandExecutor, TabCompleter {
|
|||||||
Bukkit.getScheduler().runTask(
|
Bukkit.getScheduler().runTask(
|
||||||
Bukkit.getPluginManager().getPlugin("NickCore"),
|
Bukkit.getPluginManager().getPlugin("NickCore"),
|
||||||
() -> {
|
() -> {
|
||||||
NickProfile profile = profileCache.get(player.getUniqueId()).orElse(null);
|
// Apply full name override (display, player list, custom name, lookup maps)
|
||||||
if (profile != null && profile.effectiveDisplayName() != null) {
|
nameOverrideService.applyFullOverride(player);
|
||||||
player.displayName(mm.deserialize(
|
|
||||||
InputSanitizer.escapeMiniMessage(profile.effectiveDisplayName())));
|
|
||||||
} else {
|
|
||||||
player.displayName(Component.text(player.getName()));
|
|
||||||
}
|
|
||||||
nametagService.updateNametag(player);
|
nametagService.updateNametag(player);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.nickcore.paper.listener;
|
||||||
|
|
||||||
|
import com.nickcore.paper.service.NameOverrideService;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts player commands and replaces nick names with real names before dispatch.
|
||||||
|
*
|
||||||
|
* <p>This ensures that commands like /tpa, /msg, /tp, /tell, /w, /pay, /trade,
|
||||||
|
* /party, /friend, etc. work correctly when players use nicked names as arguments.</p>
|
||||||
|
*
|
||||||
|
* <p>How it works: scans command arguments for known nick names and replaces them
|
||||||
|
* with the corresponding real player name so the target plugin can resolve them.</p>
|
||||||
|
*/
|
||||||
|
public final class CommandPreprocessListener implements Listener {
|
||||||
|
|
||||||
|
private final NameOverrideService nameOverrideService;
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
/** Pattern to split command into parts while preserving structure. */
|
||||||
|
private static final Pattern WORD_BOUNDARY = Pattern.compile("\\s+");
|
||||||
|
|
||||||
|
public CommandPreprocessListener(NameOverrideService nameOverrideService, Logger logger) {
|
||||||
|
this.nameOverrideService = Objects.requireNonNull(nameOverrideService);
|
||||||
|
this.logger = Objects.requireNonNull(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
public void onCommandPreprocess(PlayerCommandPreprocessEvent event) {
|
||||||
|
String message = event.getMessage();
|
||||||
|
if (message.length() < 2) return;
|
||||||
|
|
||||||
|
// Split: "/command arg1 arg2 ..."
|
||||||
|
String[] parts = message.split("\\s+");
|
||||||
|
if (parts.length < 2) return; // No arguments to process
|
||||||
|
|
||||||
|
boolean modified = false;
|
||||||
|
StringBuilder rebuilt = new StringBuilder(parts[0]); // Keep the command itself
|
||||||
|
|
||||||
|
for (int i = 1; i < parts.length; i++) {
|
||||||
|
rebuilt.append(' ');
|
||||||
|
String arg = parts[i];
|
||||||
|
|
||||||
|
// Check if this argument is a nick name
|
||||||
|
var resolved = nameOverrideService.resolvePlayer(arg);
|
||||||
|
if (resolved.isPresent() && nameOverrideService.isNickName(arg)) {
|
||||||
|
// Replace nick with real name so the command can find the player
|
||||||
|
rebuilt.append(resolved.get().getName());
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
rebuilt.append(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
event.setMessage(rebuilt.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,19 +26,21 @@ public final class PlayerConnectionListener implements Listener {
|
|||||||
private final SkinService skinService;
|
private final SkinService skinService;
|
||||||
private final PlaceholderService placeholderService;
|
private final PlaceholderService placeholderService;
|
||||||
private final NametagService nametagService;
|
private final NametagService nametagService;
|
||||||
|
private final NameOverrideService nameOverrideService;
|
||||||
private final SchedulerAdapter scheduler;
|
private final SchedulerAdapter scheduler;
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
private final MiniMessage mm = MiniMessage.miniMessage();
|
private final MiniMessage mm = MiniMessage.miniMessage();
|
||||||
|
|
||||||
public PlayerConnectionListener(Plugin plugin, NickService nickService,
|
public PlayerConnectionListener(Plugin plugin, NickService nickService,
|
||||||
SkinService skinService, PlaceholderService placeholderService,
|
SkinService skinService, PlaceholderService placeholderService,
|
||||||
NametagService nametagService, SchedulerAdapter scheduler,
|
NametagService nametagService, NameOverrideService nameOverrideService,
|
||||||
Logger logger) {
|
SchedulerAdapter scheduler, Logger logger) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.nickService = nickService;
|
this.nickService = nickService;
|
||||||
this.skinService = skinService;
|
this.skinService = skinService;
|
||||||
this.placeholderService = placeholderService;
|
this.placeholderService = placeholderService;
|
||||||
this.nametagService = nametagService;
|
this.nametagService = nametagService;
|
||||||
|
this.nameOverrideService = nameOverrideService;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
@@ -56,11 +58,8 @@ public final class PlayerConnectionListener implements Listener {
|
|||||||
scheduler.runAtEntity(plugin, player, () -> {
|
scheduler.runAtEntity(plugin, player, () -> {
|
||||||
if (!player.isOnline()) return;
|
if (!player.isOnline()) return;
|
||||||
|
|
||||||
// Apply display name
|
// Apply full name override (display name, player list, custom name, lookup maps)
|
||||||
if (profile.effectiveDisplayName() != null) {
|
nameOverrideService.applyFullOverride(player);
|
||||||
player.displayName(mm.deserialize(
|
|
||||||
InputSanitizer.escapeMiniMessage(profile.effectiveDisplayName())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply nametag
|
// Apply nametag
|
||||||
nametagService.updateNametag(player);
|
nametagService.updateNametag(player);
|
||||||
@@ -89,6 +88,7 @@ public final class PlayerConnectionListener implements Listener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
nameOverrideService.clearMapping(player);
|
||||||
nickService.unloadProfile(player.getUniqueId());
|
nickService.unloadProfile(player.getUniqueId());
|
||||||
placeholderService.remove(player.getUniqueId());
|
placeholderService.remove(player.getUniqueId());
|
||||||
nametagService.removeNametag(player);
|
nametagService.removeNametag(player);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.nickcore.paper.listener;
|
||||||
|
|
||||||
|
import com.nickcore.paper.service.NameOverrideService;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces real player names with nick names in tab completions.
|
||||||
|
*
|
||||||
|
* <p>When a player presses TAB to autocomplete a command argument, this listener
|
||||||
|
* intercepts the completion results and swaps real names for nicked names.
|
||||||
|
* This means if someone types "/tpa Sha" and a player is nicked as "ShadowWolf",
|
||||||
|
* "ShadowWolf" will appear in the completions instead of their real name.</p>
|
||||||
|
*/
|
||||||
|
public final class TabCompleteListener implements Listener {
|
||||||
|
|
||||||
|
private final NameOverrideService nameOverrideService;
|
||||||
|
|
||||||
|
public TabCompleteListener(NameOverrideService nameOverrideService) {
|
||||||
|
this.nameOverrideService = Objects.requireNonNull(nameOverrideService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onTabComplete(AsyncTabCompleteEvent event) {
|
||||||
|
if (event.getCompletions().isEmpty()) return;
|
||||||
|
|
||||||
|
List<String> original = event.getCompletions();
|
||||||
|
List<String> replaced = new ArrayList<>(original.size());
|
||||||
|
boolean modified = false;
|
||||||
|
|
||||||
|
for (String completion : original) {
|
||||||
|
// Check if this completion is a real player name that has a nick
|
||||||
|
Player player = Bukkit.getPlayerExact(completion);
|
||||||
|
if (player != null) {
|
||||||
|
String display = nameOverrideService.getNickOrReal(player.getName());
|
||||||
|
if (!display.equals(player.getName())) {
|
||||||
|
replaced.add(display);
|
||||||
|
modified = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replaced.add(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
event.setCompletions(replaced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package com.nickcore.paper.service;
|
||||||
|
|
||||||
|
import com.nickcore.common.model.NickProfile;
|
||||||
|
import com.nickcore.paper.cache.ProfileCache;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages full name override so that nicked names replace real names everywhere:
|
||||||
|
* tab completion, /tpa, /msg, player list, nametags, and any plugin that resolves
|
||||||
|
* players by display name.
|
||||||
|
*
|
||||||
|
* <p>Maintains a bidirectional mapping between nick names and real names so that
|
||||||
|
* commands referencing a nick name can be resolved back to the actual player.</p>
|
||||||
|
*
|
||||||
|
* <p>Thread-safe: all maps are ConcurrentHashMap.</p>
|
||||||
|
*/
|
||||||
|
public final class NameOverrideService {
|
||||||
|
|
||||||
|
private final ProfileCache profileCache;
|
||||||
|
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
/** nick (lowercase) -> real player UUID */
|
||||||
|
private final ConcurrentHashMap<String, UUID> nickToUuid = new ConcurrentHashMap<>(512);
|
||||||
|
|
||||||
|
/** real name (lowercase) -> nick name */
|
||||||
|
private final ConcurrentHashMap<String, String> realToNick = new ConcurrentHashMap<>(512);
|
||||||
|
|
||||||
|
public NameOverrideService(ProfileCache profileCache) {
|
||||||
|
this.profileCache = Objects.requireNonNull(profileCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a full name override to a player: display name, player list name,
|
||||||
|
* custom name, and registers the nick in lookup maps.
|
||||||
|
*/
|
||||||
|
public void applyFullOverride(Player player) {
|
||||||
|
NickProfile profile = profileCache.get(player.getUniqueId()).orElse(null);
|
||||||
|
|
||||||
|
// Clear old mapping for this player
|
||||||
|
clearMapping(player);
|
||||||
|
|
||||||
|
if (profile == null || !profile.nicked() || profile.nickname() == null) {
|
||||||
|
// Not nicked — restore real identity
|
||||||
|
player.displayName(Component.text(player.getName()));
|
||||||
|
player.playerListName(Component.text(player.getName()));
|
||||||
|
player.customName(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String nick = profile.nickname();
|
||||||
|
Component nickComponent = Component.text(nick);
|
||||||
|
|
||||||
|
// Set all name surfaces
|
||||||
|
player.displayName(nickComponent);
|
||||||
|
player.playerListName(nickComponent);
|
||||||
|
player.customName(nickComponent);
|
||||||
|
|
||||||
|
// Register bidirectional mapping
|
||||||
|
nickToUuid.put(nick.toLowerCase(), player.getUniqueId());
|
||||||
|
realToNick.put(player.getName().toLowerCase(), nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all name mappings for a player. Called on quit or reset.
|
||||||
|
*/
|
||||||
|
public void clearMapping(Player player) {
|
||||||
|
String realLower = player.getName().toLowerCase();
|
||||||
|
String oldNick = realToNick.remove(realLower);
|
||||||
|
if (oldNick != null) {
|
||||||
|
nickToUuid.remove(oldNick.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a player by either their nick name or real name.
|
||||||
|
* This is the key method that allows /tpa, /msg, etc. to work with nicks.
|
||||||
|
*
|
||||||
|
* @param name the name to look up (nick or real)
|
||||||
|
* @return the player if found and online
|
||||||
|
*/
|
||||||
|
public Optional<Player> resolvePlayer(String name) {
|
||||||
|
if (name == null) return Optional.empty();
|
||||||
|
String lower = name.toLowerCase();
|
||||||
|
|
||||||
|
// 1. Check if it's a nick name
|
||||||
|
UUID uuid = nickToUuid.get(lower);
|
||||||
|
if (uuid != null) {
|
||||||
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
|
if (player != null && player.isOnline()) return Optional.of(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fall back to real name
|
||||||
|
Player player = Bukkit.getPlayerExact(name);
|
||||||
|
return Optional.ofNullable(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the display name for a player (nick if active, real name otherwise).
|
||||||
|
*/
|
||||||
|
public String getDisplayName(Player player) {
|
||||||
|
String nick = realToNick.get(player.getName().toLowerCase());
|
||||||
|
return nick != null ? nick : player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the display name for a player by UUID.
|
||||||
|
*/
|
||||||
|
public String getDisplayName(UUID uuid) {
|
||||||
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
|
if (player == null) return null;
|
||||||
|
return getDisplayName(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the nick name for a real name, or the real name if not nicked.
|
||||||
|
* Used by tab completion to replace real names with nicks.
|
||||||
|
*/
|
||||||
|
public String getNickOrReal(String realName) {
|
||||||
|
String nick = realToNick.get(realName.toLowerCase());
|
||||||
|
return nick != null ? nick : realName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a name is a registered nick.
|
||||||
|
*/
|
||||||
|
public boolean isNickName(String name) {
|
||||||
|
return nickToUuid.containsKey(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a snapshot of all active nick-to-UUID mappings.
|
||||||
|
*/
|
||||||
|
public Map<String, UUID> getNickMappings() {
|
||||||
|
return Map.copyOf(nickToUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all mappings. Called on plugin shutdown.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
nickToUuid.clear();
|
||||||
|
realToNick.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user