3939 * <p>
4040 * Does not use any NMS code, and should work across all versions.
4141 *
42- * @author Dean B on 12/28/2016.
42+ * @author Dean B on 12/28/2016. Modified by Broken arrow.
4343 */
4444public class SkullCreator {
4545 private static final Logging LOG = new Logging (SkullCreator .class );
46- private static final CheckClassesExist checks = new CheckClassesExist ();
46+ private static final ServerSupport checks = new ServerSupport ();
4747
4848 private static final String PLAYER_HEAD = "PLAYER_HEAD" ;
4949 private static final String BLOCK = "block" ;
5050 private static final String NAME = "name" ;
51- public static final String PROFILE = "profile" ;
52- public static final String TEXTURES = "textures" ;
51+ private static final String PROFILE = "profile" ;
52+ private static final String TEXTURES = "textures" ;
5353 private static final String FAIL_CREATE_SKULL = "Failed to find the skull material." ;
54+ private static final String FAIL_TYPE_CREATE_SKULL = "[create skull]" ;
5455
5556 // some reflection stuff to be used when setting a skull's profile
5657 private static Field blockProfileField ;
@@ -70,14 +71,13 @@ private SkullCreator() {
7071 */
7172 @ Nullable
7273 public static ItemStack createSkull () {
73- try {
74- return new ItemStack (Material .valueOf (PLAYER_HEAD ));
75- } catch (IllegalArgumentException e ) {
76- final Material skullItem = getMaterial ("SKULL_ITEM" );
77- if (skullItem == null )
78- return null ;
79- return new ItemStack (skullItem , 1 , (byte ) 3 );
74+ org .bukkit .Material material = checks .getSkullMaterial ();
75+ if (material != null ) {
76+ if (!checks .isLegacySkull ())
77+ return new ItemStack (material );
78+ return new ItemStack (material , 1 , (byte ) 3 );
8079 }
80+ return null ;
8181 }
8282
8383 /**
@@ -90,7 +90,7 @@ public static ItemStack createSkull() {
9090 @ Deprecated
9191 public static ItemStack itemFromName (String name ) {
9292 final ItemStack skull = createSkull ();
93- checkNull (skull , "[create skull]" , FAIL_CREATE_SKULL );
93+ checkNull (skull , FAIL_TYPE_CREATE_SKULL , FAIL_CREATE_SKULL );
9494
9595 return itemWithName (skull , name );
9696 }
@@ -103,7 +103,7 @@ public static ItemStack itemFromName(String name) {
103103 */
104104 public static ItemStack itemFromUuid (UUID id ) {
105105 final ItemStack skull = createSkull ();
106- checkNull (skull , "[create skull]" , FAIL_CREATE_SKULL );
106+ checkNull (skull , FAIL_TYPE_CREATE_SKULL , FAIL_CREATE_SKULL );
107107 return itemWithUuid (skull , id );
108108 }
109109
@@ -115,7 +115,7 @@ public static ItemStack itemFromUuid(UUID id) {
115115 */
116116 public static ItemStack itemFromUrl (String url ) {
117117 final ItemStack skull = createSkull ();
118- checkNull (skull , "[create skull]" , FAIL_CREATE_SKULL );
118+ checkNull (skull , FAIL_TYPE_CREATE_SKULL , FAIL_CREATE_SKULL );
119119 return itemWithUrl (skull , url );
120120 }
121121
@@ -127,7 +127,7 @@ public static ItemStack itemFromUrl(String url) {
127127 */
128128 public static ItemStack itemFromBase64 (String base64 ) {
129129 final ItemStack skull = createSkull ();
130- checkNull (skull , "[create skull]" , FAIL_CREATE_SKULL );
130+ checkNull (skull , FAIL_TYPE_CREATE_SKULL , FAIL_CREATE_SKULL );
131131 return itemWithBase64 (skull , base64 );
132132 }
133133
@@ -137,17 +137,19 @@ public static ItemStack itemFromBase64(String base64) {
137137 * @param item The item to apply the name to. Must be a player skull.
138138 * @param name The Player's name.
139139 * @return The head of the Player.
140- * @deprecated names don't make for good identifiers.
140+ * @deprecated names don't make for good identifiers, also costly to look up .
141141 */
142142 @ Deprecated
143143 public static ItemStack itemWithName (@ Nonnull final ItemStack item , @ Nonnull final String name ) {
144144 notNull (item , "item" );
145145 notNull (name , NAME );
146146
147147 SkullMeta meta = (SkullMeta ) item .getItemMeta ();
148- meta .setOwner (name );
148+ if (meta != null && checks .isUsingLegacyApi ())
149+ meta .setOwner (name );
150+ else
151+ setOwningPlayer (meta , Bukkit .getOfflinePlayer (name ));
149152 item .setItemMeta (meta );
150-
151153 return item ;
152154 }
153155
@@ -282,7 +284,7 @@ public static void blockWithUrl(@Nonnull final Block block, @Nonnull final UUID
282284 final BlockState skullState = block .getState ();
283285 if (!(skullState instanceof Skull )) return ;
284286
285- if (checks .isDoesHaveOwnerProfile ()) {
287+ if (checks .isHasOwnerProfileSupport ()) {
286288 PlayerProfile profile = Bukkit .createPlayerProfile (id );
287289 if (url != null ) {
288290 try {
@@ -344,7 +346,7 @@ public static void blockWithBase64(@Nonnull final Block block, @Nonnull final St
344346 * @param url the skin URL to apply, or {@code null} to leave only the default player skin
345347 */
346348 public static void setSkullUrl (@ Nonnull final SkullMeta meta , @ Nonnull final UUID uuid , @ Nullable final String url ) {
347- if (checks .isDoesHaveOwnerProfile ()) {
349+ if (checks .isHasOwnerProfileSupport ()) {
348350 PlayerProfile profile = Bukkit .createPlayerProfile (uuid );
349351 if (url != null ) {
350352 try {
@@ -386,7 +388,7 @@ public static void setSkullUrl(@Nonnull final SkullMeta meta, @Nonnull final UUI
386388 */
387389 @ Nullable
388390 public static String getSkullUrl (SkullMeta meta ) {
389- if (checks .isDoesHaveOwnerProfile ()) {
391+ if (checks .isHasOwnerProfileSupport ()) {
390392 try {
391393 final PlayerProfile ownerProfile = meta .getOwnerProfile ();
392394 if (ownerProfile != null ) {
@@ -415,6 +417,16 @@ public static String getSkullUrl(SkullMeta meta) {
415417 return null ;
416418 }
417419
420+ /**
421+ * Returns the shared {@link ServerSupport} instance containing the
422+ * resolved support information for the current server.
423+ *
424+ * @return the {@link ServerSupport} instance with detected skull-related support.
425+ */
426+ public static ServerSupport getServerSupport () {
427+ return checks ;
428+ }
429+
418430 /**
419431 * Sets the block to a skull of type PLAYER_HEAD or legacy SKULL with player skull type.
420432 *
@@ -501,7 +513,7 @@ private static void mutateBlockState(Skull block, String b64) {
501513 * @param b64 The base64 texture string.
502514 */
503515 private static void mutateItemMeta (SkullMeta meta , String b64 ) {
504- if (checks .isDoesHaveOwnerProfile ()) {
516+ if (checks .isHasOwnerProfileSupport ()) {
505517 try {
506518 meta .setOwnerProfile (makePlayerProfile (b64 ));
507519 } catch (MalformedURLException ex2 ) {
@@ -590,9 +602,11 @@ private static Material getMaterial(final String name) {
590602 * @param meta The SkullMeta to modify.
591603 * @param player The OfflinePlayer to set as owner.
592604 */
593- private static void setOwningPlayer (SkullMeta meta , OfflinePlayer player ) {
605+ private static void setOwningPlayer (@ Nullable final SkullMeta meta ,final OfflinePlayer player ) {
606+ if (meta == null )
607+ return ;
594608 try {
595- if (checks .isLegacy ()) {
609+ if (checks .isUsingLegacyApi ()) {
596610 meta .setOwner (player .getName ());
597611 } else {
598612 meta .setOwningPlayer (player );
@@ -667,99 +681,160 @@ private static URL getUrlFromBase64(final String base64) {
667681 LOG .log (e , () -> "Failed to parse base64 texture to URL. With Base64 input: " + base64 );
668682 }
669683 return null ;
670- /*
671- try {
672- String decoded = new String(Base64.getDecoder().decode(base64));
673- return new URL(decoded.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), decoded.length() - "\"}}}".length()));
674- } catch (IllegalArgumentException exception) {
675- LOG.log(() -> "Failed to parse the Base64 string, does you provide a valid base64 string? The input string: " + base64);
676- }
677- return null;*/
678684 }
679685
680- public static class CheckClassesExist {
686+ /**
687+ * Resoles and exposes compatibility information for player skull handling
688+ * on the current Bukkit / Minecraft server.
689+ * <p>
690+ * This class performs a one-time detection of:
691+ * <ul>
692+ * <li>Whether the server is using a legacy (pre-1.13) Bukkit API</li>
693+ * <li>Which skull {@link Material} is available ({@code PLAYER_HEAD} or legacy)</li>
694+ * <li>Whether {@link SkullMeta} supports {@code setOwnerProfile(PlayerProfile)}</li>
695+ * <li>Whether legacy owner methods such as {@code setOwner(String)} should be used instead</li>
696+ * </ul>
697+ *
698+ * The resolved values can safely be reused to apply the correct metadata
699+ * and material without performing further version checks.
700+ *
701+ * <p><b>Typical usage:</b>
702+ * <pre>{@code
703+ * final ServerSupport support = SkullCreator.getServerSupport();
704+ * final Material material = support.getSkullMaterial();
705+ *
706+ * if (material == null) return;
707+ *
708+ * if (support.hasOwnerProfileSupport()) {
709+ * // use setOwnerProfile(PlayerProfile)
710+ * } else {
711+ * // fallback to legacy setOwner(String) or GameProfile
712+ * }
713+ * }</pre>
714+ */
715+ public static class ServerSupport {
716+ private final Material skullMaterial ;
681717 private boolean legacy ;
682- private boolean warningPosted = false ;
683- private boolean doesHaveOwnerProfile = true ;
718+ private boolean legacySkull = false ;
719+ private boolean legacyWarningLogged = false ;
720+ private boolean hasOwnerProfileSupport = true ;
684721
685722 /**
686723 * When instance is created it will check what classes exists for your minecraft version.
687724 */
688- public CheckClassesExist () {
689- checkLegacy ();
725+ public ServerSupport () {
726+ detectLegacyApiMismatch ();
690727 try {
691728 Class <?> skullMeta = Class .forName ("org.bukkit.inventory.meta.SkullMeta" );
692729 skullMeta .getMethod ("setOwningPlayer" , OfflinePlayer .class );
693730 } catch (ClassNotFoundException | NoSuchMethodException e ) {
694731 legacy = true ;
695732 }
696- final ItemStack skull = createSkull ();
733+ this .skullMaterial = createMaterial ();
734+
735+ final Material skull = this .skullMaterial ;
697736 if (skull != null ) {
698- final ItemMeta skullMeta = Bukkit .getItemFactory ().getItemMeta (skull . getType () );
737+ final ItemMeta skullMeta = Bukkit .getItemFactory ().getItemMeta (skull );
699738 if (skullMeta != null ) {
700739 checkIfHasOwnerMethod ((SkullMeta ) skullMeta );
701740 }
702741 }
703742 }
704743
705744 /**
706- * Check if the Minecraft version is below 1.13.
745+ * Returns the resolved skull {@link Material} for the current Minecraft version.
746+ * This will be either {@code PLAYER_HEAD} or a legacy equivalent.
747+ *
748+ * @return the resolved skull material, or {@code null} if none could be found
749+ */
750+ @ Nullable
751+ public Material getSkullMaterial () {
752+ return skullMaterial ;
753+ }
754+
755+ /**
756+ * Indicates whether the server is using a legacy (pre-1.13) Bukkit API.
707757 *
708- * @return true if its legacy.
758+ * @return {@code true} if the legacy API is in use
709759 */
710- public boolean isLegacy () {
760+ public boolean isUsingLegacyApi () {
711761 return legacy ;
712762 }
713763
714764 /**
715- * Check if you running legacy on modern minecraft version.
765+ * Indicates whether a legacy skull material name is being used
766+ * (e.g. {@code SKULL_ITEM} instead of {@code PLAYER_HEAD}).
716767 *
717- * @return Returns true if you did not set up correct API version.
768+ * @return {@code true} if the legacy skull material is in use
718769 */
719- public boolean isWarningPosted () {
720- return warningPosted ;
770+ public boolean isLegacySkull () {
771+ return legacySkull ;
721772 }
722773
723774 /**
724- * Checks if it has OwnerProfile setter in the metadata class and PlayerProfile class .
775+ * Indicates whether a legacy warning message has been logged .
725776 *
726- * @return Returns true if the profile exists.
777+ * @return {@code true} if a warning has already been logged
727778 */
728- public boolean isDoesHaveOwnerProfile () {
729- return doesHaveOwnerProfile ;
779+ public boolean isLegacyWarningLogged () {
780+ return legacyWarningLogged ;
730781 }
731782
732783 /**
733- * Checks if running on legacy Bukkit API and logs a warning if using legacy API on modern server.
784+ * Indicates whether the {@code setOwnerProfile(PlayerProfile)} method is
785+ * supported by {@link SkullMeta} on the current server version.
786+ *
787+ * @return {@code true} if owner profile support is available
734788 */
735- private void checkLegacy () {
789+ public boolean isHasOwnerProfileSupport () {
790+ return hasOwnerProfileSupport ;
791+ }
792+
793+ /**
794+ * Checks whether the plugin is running a legacy Bukkit API against
795+ * a modern server version and logs a warning if so.
796+ */
797+ private void detectLegacyApiMismatch () {
736798 try {
737799 // if both of these succeed, then we are running
738800 // in a legacy api, but on a modern (1.13+) server.
739801 Material .class .getDeclaredField (PLAYER_HEAD );
740802 Material .valueOf ("SKULL" );
741803
742- if (!warningPosted ) {
804+ if (!legacyWarningLogged ) {
743805 LOG .log (() -> "SKULLCREATOR API - Using the legacy bukkit API with 1.13+ bukkit versions is not supported!" );
744- warningPosted = true ;
806+ legacyWarningLogged = true ;
745807 }
746808 } catch (NoSuchFieldException | IllegalArgumentException ignored ) {
747- //We don't need to know a error is thrown. This only checks so you don't use wrong API version.
809+ //We don't need to know an error is thrown. This only checks so you don't use wrong API version.
748810 }
749811 }
750812
751813 /**
752- * Checks whether the SkullMeta class has the setOwnerProfile method
753- * and updates the flag accordingly.
814+ * Detects whether {@link SkullMeta} supports the
815+ * {@code setOwnerProfile(PlayerProfile)} method and updates the
816+ * internal support flag accordingly.
754817 *
755- * @param meta The SkullMeta instance to check.
818+ * @param meta the {@link SkullMeta} instance to inspect
756819 */
757820 private void checkIfHasOwnerMethod (final SkullMeta meta ) {
758821 try {
759822 meta .getClass ().getDeclaredMethod ("setOwnerProfile" , PlayerProfile .class );
760823 } catch (NoSuchMethodException | NoClassDefFoundError exception ) {
761- doesHaveOwnerProfile = false ;
824+ hasOwnerProfileSupport = false ;
762825 }
763826 }
827+
828+ private Material createMaterial () {
829+ Material skull ;
830+ try {
831+ skull = Material .valueOf (PLAYER_HEAD );
832+ } catch (IllegalArgumentException e ) {
833+ skull = getMaterial ("SKULL_ITEM" );
834+ legacySkull = true ;
835+ }
836+ return skull ;
837+ }
838+
764839 }
765840}
0 commit comments