Updated Skull class handling custom heads. No more relying on MHF_* accounts

This commit is contained in:
Marc Baloup 2025-02-17 00:30:38 +01:00
parent e2b2ab466d
commit 3b4cf63c48
3 changed files with 137 additions and 230 deletions

View File

@ -4,6 +4,8 @@ import com.google.common.collect.Streams;
import fr.pandacube.lib.chat.Chat;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentType.Valued;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.Material;
@ -389,6 +391,15 @@ public class ItemStackBuilder {
return data(DataComponentTypes.CAN_PLACE_ON, ItemAdventurePredicate.itemAdventurePredicate(canPlaceOn)); */
}
/**
* Sets the {@code profile} data component to the provided profile.
* @param profile the profile to use as the component value.
* @return itself.
*/
public ItemStackBuilder profile(ResolvableProfile profile) {
return data(DataComponentTypes.PROFILE, profile);
}
/**
* Build the {@link ItemStack}.

View File

@ -0,0 +1,126 @@
package fr.pandacube.lib.paper.inventory;
import com.destroystokyo.paper.profile.ProfileProperty;
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.Base64;
import java.util.regex.Pattern;
/**
* Represents some special mob heads, also support creating player skulls and custom skulls.
*/
public enum Skull {
/** Jungle wood arrow left. */
ARROW_LEFT("http://textures.minecraft.net/texture/3625902b389ed6c147574e422da8f8f361c8eb57e7631676a72777e7b1d"),
/** Jungle wood arrow right. */
ARROW_RIGHT("http://textures.minecraft.net/texture/d4be8aeec11849697adc6fd1f189b16642dff19f2955c05deaba68c9dff1be"),
/** Jungle wood arrow up. */
ARROW_UP("http://textures.minecraft.net/texture/88c0f37dec764d6e26b57aa8212572fbace5ee8f27f7b61c1fdaa47dd4c893"),
/** Jungle wood arrow down. */
ARROW_DOWN("http://textures.minecraft.net/texture/751ced2e647366f8f3ad2dfe415cca85651bfaf9739a95cd57b6f21cba053"),
/** Jungle wood question mark. */
QUESTION("http://textures.minecraft.net/texture/b4d7cc4dca986a53f1d6b52aaf376dc6acc73b8b287f42dc8fef5808bb5d76"),
/** Jungle wood exclamation mark. */
EXCLAMATION("http://textures.minecraft.net/texture/e869dc405a3155f281c16a3e8d9ff54afc1599153b4d9385c9b7bab88680f0");
private final String skinUrl;
Skull(String skinUrl) {
this.skinUrl = skinUrl;
}
/**
* Return the item based on this Skull enum.
* @return the item stack.
*/
public ItemStack get() {
return getFromSkinURL(skinUrl);
}
/**
* Return an item stack builder already containing the skull.
* @return an item stack builder already containing the skull.
*/
public ItemStackBuilder builder() {
return ItemStackBuilder.wrap(get());
}
/**
* Return a skull of a player based on their name.
*
* @param name player's name
* @return item stack
*/
public static ItemStack getFromPlayerName(String name) {
return getFromProfile(ResolvableProfile.resolvableProfile().name(name).build());
}
/**
* Return a skull that has a custom texture specified by url.
* @param url skin url.
* @return item stack
*/
public static ItemStack getFromSkinURL(String url) {
return getFromProfile(ResolvableProfile.resolvableProfile().addProperty(getTexturesProperty(url)).build());
}
private static ItemStack getFromProfile(ResolvableProfile profile) {
return ItemStackBuilder.of(Material.PLAYER_HEAD).profile(profile).build();
}
/**
* The URL prefix for all the player related textures (skin, cape)
*/
public static final String TEXTURE_URL_PREFIX = "http://textures.minecraft.net/texture/";
private static final Pattern textureIdMatcher = Pattern.compile("^[0-9a-fA-F]+$");
/**
* Generate the base64 value of the "textures" profile property, based on the provided skin url!
* @param skinURL the URL of the skin. The "https" will be replaced by "http" because this is the protocol used in
* the profile property url. If only the texture id part is provided, {@link #TEXTURE_URL_PREFIX} is
* prepended.
* @return the base64 encoded texture data.
*/
private static String encodeTextureBase64String(String skinURL) {
if (skinURL.startsWith("https://")) // secure url is not the url found in texture data (even if it actually works in the browser)
skinURL = "http://" + skinURL.substring("https://".length());
if (!skinURL.startsWith(TEXTURE_URL_PREFIX)) { // accept taking only the texture id part ()
if (textureIdMatcher.matcher(skinURL).matches())
skinURL = TEXTURE_URL_PREFIX + skinURL;
else
throw new IllegalArgumentException("Invalid skin URL. Must be from " + TEXTURE_URL_PREFIX + ".");
}
return Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", skinURL).getBytes());
}
private static ProfileProperty getTexturesProperty(String skinURL) {
return new ProfileProperty("textures", encodeTextureBase64String(skinURL));
}
}

View File

@ -1,230 +0,0 @@
package fr.pandacube.lib.paper.util;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import fr.pandacube.lib.chat.Chat;
/**
* Represents some special mob heads, also support creating player skulls and custom skulls.
*
* @author xigsag, SBPrime
*
* @see <a href="https://github.com/TigerHix/Hex-Utils/blob/9954159a323d12733b29c287a56980991cee2948/hex/util/Skull.java">github.com/TigerHix/Hex-Utils/hex/util/Skull.java</a>
*/
public enum Skull {
/** Standard skull of player MHF_ArrowLeft. */
ARROW_LEFT("MHF_ArrowLeft"),
/** Standard skull of player MHF_ArrowRight. */
ARROW_RIGHT("MHF_ArrowRight"),
/** Standard skull of player MHF_ArrowUp. */
ARROW_UP("MHF_ArrowUp"),
/** Standard skull of player MHF_ArrowDown. */
ARROW_DOWN("MHF_ArrowDown"),
/** Standard skull of player MHF_Question. */
QUESTION("MHF_Question"),
/** Standard skull of player MHF_Exclamation. */
EXCLAMATION("MHF_Exclamation"),
/** Standard skull of player FHG_Cam. */
CAMERA("FHG_Cam"),
/** Standard skull of player MHF_PigZombie. */
ZOMBIE_PIGMAN("MHF_PigZombie"),
/** Standard skull of player MHF_Pig. */
PIG("MHF_Pig"),
/** Standard skull of player MHF_Sheep. */
SHEEP("MHF_Sheep"),
/** Standard skull of player MHF_Blaze. */
BLAZE("MHF_Blaze"),
/** Standard skull of player MHF_Chicken. */
CHICKEN("MHF_Chicken"),
/** Standard skull of player MHF_Cow. */
COW("MHF_Cow"),
/** Standard skull of player MHF_Slime. */
SLIME("MHF_Slime"),
/** Standard skull of player MHF_Spider. */
SPIDER("MHF_Spider"),
/** Standard skull of player MHF_Squid. */
SQUID("MHF_Squid"),
/** Standard skull of player MHF_Villager. */
VILLAGER("MHF_Villager"),
/** Standard skull of player MHF_Ocelot. */
OCELOT("MHF_Ocelot"),
/** Standard skull of player MHF_Herobrine. */
HEROBRINE("MHF_Herobrine"),
/** Standard skull of player MHF_LavaSlime. */
LAVA_SLIME("MHF_LavaSlime"),
/** Standard skull of player MHF_MushroomCow. */
MOOSHROOM("MHF_MushroomCow"),
/** Standard skull of player MHF_Golem. */
GOLEM("MHF_Golem"),
/** Standard skull of player MHF_Ghast. */
GHAST("MHF_Ghast"),
/** Standard skull of player MHF_Enderman. */
ENDERMAN("MHF_Enderman"),
/** Standard skull of player MHF_CaveSpider. */
CAVE_SPIDER("MHF_CaveSpider"),
/** Standard skull of player MHF_Cactus. */
CACTUS("MHF_Cactus"),
/** Standard skull of player MHF_Cake. */
CAKE("MHF_Cake"),
/** Standard skull of player MHF_Chest. */
CHEST("MHF_Chest"),
/** Standard skull of player MHF_Melon. */
MELON("MHF_Melon"),
/** Standard skull of player MHF_OakLog. */
LOG("MHF_OakLog"),
/** Standard skull of player MHF_Pumpkin. */
PUMPKIN("MHF_Pumpkin"),
/** Standard skull of player MHF_TNT. */
TNT("MHF_TNT"),
/** Standard skull of player MHF_TNT2. */
DYNAMITE("MHF_TNT2");
private final String name;
Skull(String mcName) {
name = mcName;
}
/**
* Return the item based on this Skull enum.
*
* @return item stack
*/
public ItemStack get() {
return get(null, null);
}
/**
* Return the item based on this Skull enum, with the provided display name and lore.
* @param displayName the display name to add to the returned skull.
* @param lore the lore to add to the returned skull.
* @return item stack
*/
public ItemStack get(Chat displayName, List<Chat> lore) {
return getFromPlayerName(name, displayName, lore);
}
/**
* Return a skull of a player based on their name.
*
* @param name player's name
* @param displayName the display name to add to the returned skull.
* @param lore the lore to add to the returned skull.
* @return item stack
*/
public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) {
ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD, 1);
SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
@SuppressWarnings({ "deprecation", "unused" })
boolean b = meta.setOwner(name);
if (displayName != null)
meta.displayName(displayName.get());
if (lore != null)
meta.lore(lore.stream().map(Chat::get).collect(Collectors.toList()));
itemStack.setItemMeta(meta);
return itemStack;
}
/**
* Return a skull that has a custom texture specified by url.
* @param url skin url.
* @return item stack
*/
public static ItemStack getFromSkinURL(String url) {
return getFromSkinURL(url, null, null);
}
/**
* Return a skull that has a custom texture specified by url.
*
* @param url the skin full url.
* @param displayName the display name to add to the returned skull.
* @param lore the lore to add to the returned skull.
* @return item stack
*/
public static ItemStack getFromSkinURL(String url, Chat displayName, List<Chat> lore) {
return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), displayName, lore);
}
/**
* Return a skull that has a custom texture specified by a base64 String.
*
* @param str the base64 string from game profile information.
* @return item stack
*/
public static ItemStack getFromBase64String(String str) {
return getFromBase64String(str, null, null);
}
/**
* Return a skull that has a custom texture specified by a base64 String.
*
* @param str the base64 string from game profile information.
* @param displayName the display name to add to the returned skull.
* @param lore the lore to add to the returned skull.
* @return item stack
*/
public static ItemStack getFromBase64String(String str, Chat displayName, List<Chat> lore) {
ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1);
SkullMeta headMeta = (SkullMeta) head.getItemMeta();
PlayerProfile profile = Bukkit.createProfile(UUID.nameUUIDFromBytes(str.getBytes()));
profile.setProperty(new ProfileProperty("textures", str));
headMeta.setPlayerProfile(profile);
if (displayName != null)
headMeta.displayName(displayName.get());
if (lore != null)
headMeta.lore(lore.stream().map(Chat::get).collect(Collectors.toList()));
head.setItemMeta(headMeta);
return head;
}
}