FancyInnovations

Skin Manager

Loading and managing NPC skins

The SkinManager handles loading player skins from various sources including Mojang's API, direct URLs, and local files.

Accessing the skin manager

SkinManager skinManager = FancyNpcsPlugin.get().getSkinManager();

Loading skins

By player UUID

Load a skin using a player's UUID:

import de.oliver.fancynpcs.api.skins.SkinData;
import de.oliver.fancynpcs.api.skins.SkinData.SkinVariant;

UUID playerUUID = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"); // Notch
SkinData skin = skinManager.getByUUID(playerUUID, SkinVariant.AUTO);

if (skin != null) {
    npcData.setSkin(skin);
}

By player username

Load a skin using a player's username:

SkinData skin = skinManager.getByUsername("Notch", SkinVariant.AUTO);

if (skin != null) {
    npcData.setSkin(skin);
}

By URL

Load a skin from an image URL:

String skinUrl = "https://textures.minecraft.net/texture/...";
SkinData skin = skinManager.getByURL(skinUrl, SkinVariant.AUTO);

if (skin != null) {
    npcData.setSkin(skin);
}

By file

Load a skin from the plugins/FancyNPCs/skins/ directory:

// Place your skin PNG in plugins/FancyNPCs/skins/custom_skin.png
SkinData skin = skinManager.getByFile("custom_skin.png", SkinVariant.AUTO);

if (skin != null) {
    npcData.setSkin(skin);
}

Using a generic identifier

The skin manager can automatically detect the type of identifier:

// Will automatically detect UUID, username, URL, or file
SkinData skin = skinManager.getByIdentifier("Notch", SkinVariant.AUTO);

Skin variants

Skins come in two variants:

  • SLIM - Alex model (3px wide arms)
  • CLASSIC - Steve model (4px wide arms)
  • AUTO - Automatically detect the variant
// Force classic (Steve) variant
SkinData skin = skinManager.getByUsername("Notch", SkinVariant.CLASSIC);

// Force slim (Alex) variant
SkinData skin = skinManager.getByUsername("Notch", SkinVariant.SLIM);

// Auto-detect variant (recommended)
SkinData skin = skinManager.getByUsername("Notch", SkinVariant.AUTO);

Asynchronous loading

Skin loading can be slow, especially when fetching from Mojang's API. The skin manager handles this asynchronously:

// This may return null if the skin hasn't loaded yet
SkinData skin = skinManager.getByUsername("Notch", SkinVariant.AUTO);

if (skin == null) {
    // Skin is loading asynchronously
    // Listen for SkinGeneratedEvent (see below)
} else {
    // Skin was cached and returned immediately
    npcData.setSkin(skin);
}

Listening for skin loaded events

When a skin loads asynchronously, a SkinGeneratedEvent is fired:

import de.oliver.fancynpcs.api.events.SkinGeneratedEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class SkinListener implements Listener {

    @EventHandler
    public void onSkinGenerated(SkinGeneratedEvent event) {
        String identifier = event.getIdentifier();
        SkinData skin = event.getSkinData();

        // Apply the loaded skin to your NPC
        Npc npc = FancyNpcsPlugin.get().getNpcManager().getNpc("my_npc");
        if (npc != null) {
            npc.getData().setSkin(skin);
            npc.updateForAll();
        }
    }
}

Complete example: Loading skin with fallback

import de.oliver.fancynpcs.api.skins.SkinData;
import de.oliver.fancynpcs.api.skins.SkinData.SkinVariant;
import de.oliver.fancynpcs.api.skins.SkinManager;
import de.oliver.fancynpcs.api.events.SkinGeneratedEvent;

public class SkinLoader implements Listener {

    private final String npcName;
    private final String skinIdentifier;

    public SkinLoader(String npcName, String skinIdentifier) {
        this.npcName = npcName;
        this.skinIdentifier = skinIdentifier;
    }

    public void loadSkin() {
        SkinManager skinManager = FancyNpcsPlugin.get().getSkinManager();
        SkinData skin = skinManager.getByIdentifier(skinIdentifier, SkinVariant.AUTO);

        if (skin != null) {
            // Skin was cached, apply immediately
            applySkin(skin);
        } else {
            // Skin is loading, will be applied in event handler
            plugin.getLogger().info("Loading skin asynchronously: " + skinIdentifier);
        }
    }

    @EventHandler
    public void onSkinGenerated(SkinGeneratedEvent event) {
        // Check if this is the skin we're waiting for
        if (!event.getIdentifier().equalsIgnoreCase(skinIdentifier)) {
            return;
        }

        // Apply the loaded skin
        applySkin(event.getSkinData());
    }

    private void applySkin(SkinData skin) {
        Npc npc = FancyNpcsPlugin.get().getNpcManager().getNpc(npcName);
        if (npc == null) {
            plugin.getLogger().warning("NPC not found: " + npcName);
            return;
        }

        npc.getData().setSkin(skin);
        npc.updateForAll();
        plugin.getLogger().info("Applied skin to NPC: " + npcName);
    }
}

SkinData class

The SkinData class contains the skin texture and signature:

public class SkinData {
    private final String texture;   // Base64 encoded texture data
    private final String signature; // Signature for validation

    // Get texture value
    String texture = skinData.getTexture();

    // Get signature
    String signature = skinData.getSignature();
}

Caching

The skin manager automatically caches loaded skins to improve performance:

  • Skins are cached in memory during runtime
  • Subsequent requests for the same skin return immediately
  • No need to manually manage the cache

Mirror skins

You can make an NPC mirror the skin of nearby players:

// Enable skin mirroring
npcData.setMirrorSkin(true);

// The NPC will automatically copy the skin of the nearest player

Mirror skin feature requires the NPC to have setMirrorSkin(true) in NpcData. The actual mirroring logic is handled internally by FancyNPCs.

Creating custom skins

From a texture and signature

If you have raw texture and signature data:

String texture = "eyJ0aW1lc3RhbX..."; // Base64 texture
String signature = "Kj3fL..."; // Signature

SkinData customSkin = new SkinData(texture, signature);
npcData.setSkin(customSkin);

From a file

  1. Place your skin PNG file in plugins/FancyNPCs/skins/
  2. Load it using the skin manager:
SkinData skin = skinManager.getByFile("my_custom_skin.png", SkinVariant.CLASSIC);

Example: Random skins

import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class RandomSkinNpc {

    private static final List<String> SKIN_NAMES = Arrays.asList(
        "Notch",
        "jeb_",
        "Dinnerbone",
        "Grumm"
    );

    private static final Random RANDOM = new Random();

    public void setRandomSkin(Npc npc) {
        String randomName = SKIN_NAMES.get(RANDOM.nextInt(SKIN_NAMES.size()));

        SkinManager skinManager = FancyNpcsPlugin.get().getSkinManager();
        SkinData skin = skinManager.getByUsername(randomName, SkinVariant.AUTO);

        if (skin != null) {
            npc.getData().setSkin(skin);
            npc.updateForAll();
        }
    }

    public void rotateSkins(Npc npc) {
        // Rotate through skins every 30 seconds
        Bukkit.getScheduler().runTaskTimer(plugin, () -> {
            setRandomSkin(npc);
        }, 0L, 600L); // 30 seconds = 600 ticks
    }
}

Error handling

Handle cases where skins fail to load:

public void loadSkinSafely(Npc npc, String identifier) {
    try {
        SkinData skin = skinManager.getByIdentifier(identifier, SkinVariant.AUTO);

        if (skin == null) {
            // Skin loading asynchronously or failed
            plugin.getLogger().warning("Skin not immediately available: " + identifier);
            return;
        }

        npc.getData().setSkin(skin);
        npc.updateForAll();

    } catch (Exception e) {
        plugin.getLogger().severe("Failed to load skin: " + e.getMessage());

        // Set a fallback skin
        SkinData fallback = skinManager.getByUsername("MHF_Villager", SkinVariant.AUTO);
        if (fallback != null) {
            npc.getData().setSkin(fallback);
            npc.updateForAll();
        }
    }
}

On this page