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