From b7bc25e168564286a5f1776fc0bf890841ee3ef5 Mon Sep 17 00:00:00 2001 From: BaileyCodes Date: Mon, 1 Jun 2026 13:22:48 -0500 Subject: [PATCH] feat: integrate Atlantis Disguises API for packet-level nick/skin support - Added net.atlantismc.disguises:api:1.0 as compileOnly dependency - Created AtlantisDisguiseIntegration bridge class - NameOverrideService now delegates to Atlantis when available - Falls back to built-in Bukkit display name overrides if absent - Added AtlantisDisguises to plugin.yml softdepend --- build.gradle.kts | 1 + nickcore-paper/build.gradle.kts | 1 + .../com/nickcore/paper/NickCorePlugin.java | 16 +++ .../AtlantisDisguiseIntegration.java | 129 ++++++++++++++++++ .../paper/service/NameOverrideService.java | 58 +++++++- nickcore-paper/src/main/resources/plugin.yml | 1 + 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 nickcore-paper/src/main/java/com/nickcore/paper/integration/AtlantisDisguiseIntegration.java diff --git a/build.gradle.kts b/build.gradle.kts index cbb3127..778e518 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ allprojects { mavenCentral() maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.extendedclip.com/releases/") // PlaceholderAPI + maven("https://repo.selixe.com/repository/api/") // Atlantis Disguises } } diff --git a/nickcore-paper/build.gradle.kts b/nickcore-paper/build.gradle.kts index 1148675..79a91b7 100644 --- a/nickcore-paper/build.gradle.kts +++ b/nickcore-paper/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { compileOnly("net.luckperms:api:$luckpermsVersion") compileOnly("me.clip:placeholderapi:$placeholderApiVersion") + compileOnly("net.atlantismc.disguises:api:1.0") } tasks.jar { diff --git a/nickcore-paper/src/main/java/com/nickcore/paper/NickCorePlugin.java b/nickcore-paper/src/main/java/com/nickcore/paper/NickCorePlugin.java index 5e7a535..129c784 100644 --- a/nickcore-paper/src/main/java/com/nickcore/paper/NickCorePlugin.java +++ b/nickcore-paper/src/main/java/com/nickcore/paper/NickCorePlugin.java @@ -7,6 +7,7 @@ import com.nickcore.common.validation.NicknameValidator; import com.nickcore.paper.cache.ProfileCache; import com.nickcore.paper.command.NickCommand; import com.nickcore.paper.gui.GuiManager; +import com.nickcore.paper.integration.AtlantisDisguiseIntegration; import com.nickcore.paper.integration.NickCoreExpansion; import com.nickcore.paper.integration.VelocityMessaging; import com.nickcore.paper.listener.CommandPreprocessListener; @@ -102,6 +103,9 @@ public final class NickCorePlugin extends JavaPlugin { chatService = new ChatService(profileCache, rankService, getLogger()); nameOverrideService = new NameOverrideService(profileCache); + // Hook into Atlantis Disguises for packet-level nick/skin support + hookAtlantisDisguises(); + // Load data files loadNames(); loadRanks(); @@ -301,6 +305,18 @@ public final class NickCorePlugin extends JavaPlugin { skinService.loadConfiguredSkins(skins); } + private void hookAtlantisDisguises() { + if (Bukkit.getPluginManager().getPlugin("AtlantisDisguises") != null) { + AtlantisDisguiseIntegration atlantis = new AtlantisDisguiseIntegration(getLogger()); + if (atlantis.hook()) { + nameOverrideService.setAtlantis(atlantis); + getLogger().info("Atlantis Disguises integration enabled — using packet-level disguises"); + } + } else { + getLogger().info("Atlantis Disguises not found — using built-in name overrides"); + } + } + private void registerPlaceholderAPI() { if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { placeholderExpansion = new NickCoreExpansion(this, placeholderService); diff --git a/nickcore-paper/src/main/java/com/nickcore/paper/integration/AtlantisDisguiseIntegration.java b/nickcore-paper/src/main/java/com/nickcore/paper/integration/AtlantisDisguiseIntegration.java new file mode 100644 index 0000000..8cf0cb1 --- /dev/null +++ b/nickcore-paper/src/main/java/com/nickcore/paper/integration/AtlantisDisguiseIntegration.java @@ -0,0 +1,129 @@ +package com.nickcore.paper.integration; + +import net.atlantismc.disguises.api.API; +import net.atlantismc.disguises.api.DisguiseProvider; + +import org.bukkit.entity.Player; + +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Integration bridge for the Atlantis Disguises plugin. + * + *

When Atlantis Disguises is present on the server, NickCore delegates + * all name-change and skin operations to it. Atlantis handles packet-level + * name injection, skin application, and GameProfile rewriting — producing + * a much more seamless disguise than NickCore's built-in display-name approach.

+ * + *

All methods are safe to call even if Atlantis is not loaded; they will + * return false/null gracefully.

+ */ +public final class AtlantisDisguiseIntegration { + + private final Logger logger; + private volatile boolean available; + + public AtlantisDisguiseIntegration(Logger logger) { + this.logger = Objects.requireNonNull(logger); + this.available = false; + } + + /** + * Attempts to hook into the Atlantis Disguises API. + * Call this during plugin enable after soft-depend loads. + * + * @return true if the API was successfully hooked + */ + public boolean hook() { + try { + API api = DisguiseProvider.get(); + if (api != null) { + available = true; + logger.info("[NickCore] Hooked into Atlantis Disguises API"); + return true; + } + } catch (NoClassDefFoundError | Exception e) { + logger.log(Level.FINE, "Atlantis Disguises not available, using built-in name overrides", e); + } + available = false; + return false; + } + + /** + * Checks if Atlantis Disguises is available and hooked. + */ + public boolean isAvailable() { + return available; + } + + /** + * Disguises a player with the given name via Atlantis. + * This handles packet-level GameProfile rewriting, skin changes, and + * nametag updates — all at the protocol level. + * + * @param player the player to disguise + * @param name the disguise name + * @return true if the disguise was applied successfully + */ + public boolean disguise(Player player, String name) { + if (!available || player == null || name == null) return false; + try { + API api = DisguiseProvider.get(); + if (api != null) { + return api.disguise(player, name); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to apply Atlantis disguise for " + player.getName(), e); + } + return false; + } + + /** + * Removes a player's disguise via Atlantis. + * + * @param player the player to undisguise + * @return true if the disguise was removed + */ + public boolean undisguise(Player player) { + if (!available || player == null) return false; + try { + API api = DisguiseProvider.get(); + if (api != null) { + return api.undisguise(player); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to remove Atlantis disguise for " + player.getName(), e); + } + return false; + } + + /** + * Checks if a player is currently disguised via Atlantis. + */ + public boolean isDisguised(Player player) { + if (!available || player == null) return false; + try { + API api = DisguiseProvider.get(); + return api != null && api.isDisguised(player); + } catch (Exception e) { + return false; + } + } + + /** + * Gets the current disguise name for a player via Atlantis. + * + * @return the disguise name, or null if not disguised + */ + public String getDisguiseName(Player player) { + if (!available || player == null) return null; + try { + API api = DisguiseProvider.get(); + return api != null ? api.getDisguise(player) : null; + } catch (Exception e) { + return null; + } + } +} diff --git a/nickcore-paper/src/main/java/com/nickcore/paper/service/NameOverrideService.java b/nickcore-paper/src/main/java/com/nickcore/paper/service/NameOverrideService.java index d71850b..dd79ecb 100644 --- a/nickcore-paper/src/main/java/com/nickcore/paper/service/NameOverrideService.java +++ b/nickcore-paper/src/main/java/com/nickcore/paper/service/NameOverrideService.java @@ -2,6 +2,7 @@ package com.nickcore.paper.service; import com.nickcore.common.model.NickProfile; import com.nickcore.paper.cache.ProfileCache; +import com.nickcore.paper.integration.AtlantisDisguiseIntegration; import net.kyori.adventure.text.Component; @@ -19,6 +20,10 @@ import java.util.concurrent.ConcurrentHashMap; * tab completion, /tpa, /msg, player list, nametags, and any plugin that resolves * players by display name. * + *

When Atlantis Disguises is available, delegates to it for packet-level + * GameProfile rewriting (the most thorough approach). Otherwise, falls back to + * Bukkit display name + custom name overrides.

+ * *

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.

* @@ -27,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; public final class NameOverrideService { private final ProfileCache profileCache; + private volatile AtlantisDisguiseIntegration atlantis; /** nick (lowercase) -> real player UUID */ private final ConcurrentHashMap nickToUuid = new ConcurrentHashMap<>(512); @@ -38,9 +44,21 @@ public final class NameOverrideService { this.profileCache = Objects.requireNonNull(profileCache); } + /** + * Sets the Atlantis Disguises integration. When set and available, + * disguise/undisguise operations are delegated to Atlantis for + * packet-level name rewriting. + */ + public void setAtlantis(AtlantisDisguiseIntegration atlantis) { + this.atlantis = atlantis; + } + /** * Applies a full name override to a player: display name, player list name, * custom name, and registers the nick in lookup maps. + * + *

If Atlantis Disguises is available, uses its packet-level disguise system + * which rewrites the GameProfile and handles skin + nametag automatically.

*/ public void applyFullOverride(Player player) { NickProfile profile = profileCache.get(player.getUniqueId()).orElse(null); @@ -50,16 +68,21 @@ public final class NameOverrideService { 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); + undisguisePlayer(player); return; } String nick = profile.nickname(); - Component nickComponent = Component.text(nick); - // Set all name surfaces + // Try Atlantis Disguises first (packet-level, most thorough) + boolean atlantisApplied = false; + AtlantisDisguiseIntegration atl = this.atlantis; + if (atl != null && atl.isAvailable()) { + atlantisApplied = atl.disguise(player, nick); + } + + // Always set Bukkit-level names as fallback/supplement + Component nickComponent = Component.text(nick); player.displayName(nickComponent); player.playerListName(nickComponent); player.customName(nickComponent); @@ -69,6 +92,22 @@ public final class NameOverrideService { realToNick.put(player.getName().toLowerCase(), nick); } + /** + * Removes all name overrides from a player. + */ + private void undisguisePlayer(Player player) { + // Undo Atlantis disguise if active + AtlantisDisguiseIntegration atl = this.atlantis; + if (atl != null && atl.isAvailable() && atl.isDisguised(player)) { + atl.undisguise(player); + } + + // Reset Bukkit-level names + player.displayName(Component.text(player.getName())); + player.playerListName(Component.text(player.getName())); + player.customName(null); + } + /** * Clears all name mappings for a player. Called on quit or reset. */ @@ -80,6 +119,15 @@ public final class NameOverrideService { } } + /** + * Fully resets a player's identity — clears mappings and undisguises. + * Called on /nick reset or when a player quits. + */ + public void resetPlayer(Player player) { + clearMapping(player); + undisguisePlayer(player); + } + /** * 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. diff --git a/nickcore-paper/src/main/resources/plugin.yml b/nickcore-paper/src/main/resources/plugin.yml index 1907632..8d6a744 100644 --- a/nickcore-paper/src/main/resources/plugin.yml +++ b/nickcore-paper/src/main/resources/plugin.yml @@ -12,6 +12,7 @@ softdepend: - LuckPerms - TAB - Essentials + - AtlantisDisguises commands: nick: