diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java
index 5a6ae2b..9ff8cee 100644
--- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java
+++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java
@@ -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}.
diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/Skull.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/Skull.java
new file mode 100644
index 0000000..727dcd4
--- /dev/null
+++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/Skull.java
@@ -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));
+ }
+
+
+
+
+
+
+}
+
\ No newline at end of file
diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java
deleted file mode 100644
index 7ee32af..0000000
--- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java
+++ /dev/null
@@ -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 github.com/TigerHix/Hex-Utils/hex/util/Skull.java
- */
-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 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 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 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 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;
- }
-
-
-
-
-
-
-}
-
\ No newline at end of file