Spigot 1.18.1 added the new PlayerProfiles class, which finally allows us to use custom heads without needing any reflection! You can obtain them as normal items, or actually place them down into the world. I’ll show you how both works:
Creating a new PlayerProfile
First, we gotta create a new PlayerProfile object. To do so, we only need a UUID. We’re just putting this into a new method so we can reuse it later:
private static final UUID RANDOM_UUID = UUID.fromString("92864445-51c5-4c3b-9039-517c9927d1b4"); // We reuse the same "random" UUID all the time private static PlayerProfile getProfile(String url) { PlayerProfile profile = Bukkit.createPlayerProfile(RANDOM_UUID); // Get a new player profile PlayerTextures textures = profile.getTextures(); URL urlObject; try { urlObject = new URL(url); // The URL to the skin, for example: https://textures.minecraft.net/texture/18813764b2abc94ec3c3bc67b9147c21be850cdf996679703157f4555997ea63a } catch (MalformedURLException exception) { throw new RuntimeException("Invalid URL", exception); } textures.setSkin(urlObject); // Set the skin of the player profile to the URL profile.setTextures(textures); // Set the textures back to the profile return profile; }
Turning a PlayerProfile into an ItemStack
Let’s just write a tiny command, that gives the player a “gay zombie” head whenever they run the command. Please note that normally, you shouldn’t blindly cast the CommandSender to Player, but you already know that.
@Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Player player = (Player) sender; PlayerProfile profile = getProfile("https://textures.minecraft.net/texture/18813764b2abc94ec3c3bc67b9147c21be850cdf996679703157f4555997ea63"); System.out.println(profile.getTextures().getSkin().toString()); ItemStack head = new ItemStack(Material.PLAYER_HEAD); SkullMeta meta = (SkullMeta) head.getItemMeta(); meta.setOwnerProfile(profile); // Set the owning player of the head to the player profile head.setItemMeta(meta); player.getInventory().addItem(head); return true; }
Important: Please notice that were are NOT using base64 values for the skin, but the usual URl to the skin file. You can obtain these links for example here. (Just click on any Skin, then copy/paste the part at the bottom where it says Minecraft-URL. You have to copy the full URL, including the http://textures.minecraft.net part.)
If you only have access to the base64 string of your skin, then you’ll have to parse it manually. For example, you can just get your system’s Base64Decoder, then pass your string. It will now be something like this:
{"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/3453b43182ba4f470f42f237dbc081bc5523beec232989bc6f9d6557926ef480"}}}
You now need to extract the “url” part. You could for sample just parse this a json, or, even simpler – simply substring the stuff you don’t need:
public static URL getUrlFromBase64(String base64) throws MalformedURLException { String decoded = new String(Base64.getDecoder().decode(base64)); // We simply remove the "beginning" and "ending" part of the JSON, so we're left with only the URL. You could use a proper // JSON parser for this, but that's not worth it. The String will always start exactly with this stuff anyway return new URL(decoded.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), decoded.length() - "\"}}}".length())); }
Placing a custom head in the world
Placing your skull as actual block is equally easy. Just get the block’s “Skull” BlockState, then set the owning profile. Do not forget to update the BlockState afterwards!
@Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Player player = (Player) sender; PlayerProfile profile = getProfile("https://textures.minecraft.net/texture/b72afb31e8f49ff90d49127d0562c19ff07ddc0bba1f7d82cd150718df233f29"); System.out.println(profile.getTextures().getSkin().toString()); Block block = player.getTargetBlockExact(100, FluidCollisionMode.NEVER); block.setType(Material.PLAYER_HEAD); Skull skull = (Skull) block.getState(); skull.setOwnerProfile(profile); skull.update(false); return true; }
What to do on outdated versions like 1.18 and lower?
If you’re using outdated Spigot versions, then you indeed need reflection to create custom heads. I won’t give any support for ancient versions though, but you can just check out my SkullUtils class from my JeffLib library. It supports 1.16.1+, however it’s using the base64 Strings instead of directly using the proper URL:
Join my Discord Server for feedback or support. Just check out the channel #programming-help
🙂
Here’s a method combining the two without using deprecated method (Paper API)
public static ItemStack getHead(String headID) {
PlayerProfile profile = Bukkit.createProfile(UUID.fromString(“92864445-51c5-4c3b-9039-517c9927d1b4”));
PlayerTextures textures = profile.getTextures();
try {
textures.setSkin(URI.create(“https://textures.minecraft.net/texture/” + headID).toURL());
} catch (MalformedURLException exception) {
throw new RuntimeException(“Invalid URL”, exception);
}
profile.setTextures(textures);
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();
meta.setPlayerProfile(profile);
head.setItemMeta(meta);
return head;
}
Hi, I am unsure what you are talking about – my original code also does not use any deprecated methods, does it?
The setOwnerProfile is deprecated
No, it’s not: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/meta/SkullMeta.html#setOwnerProfile(org.bukkit.profile.PlayerProfile)
Just wondering, is it okay to run this code on the main thread or should I be running it async?
From what I see, it doesn’t make any HTTP requests (I have checked the source code of CraftPlayerTextures and CraftPlayerProfile), so it should be fine to run on the main thread.