Skip to content

Commit acb590b

Browse files
committed
Improve the clarity or the SkullCreator.
Also added option to access the instance of the inner check class without creating new instance.
1 parent 148d7c7 commit acb590b

File tree

1 file changed

+134
-59
lines changed

1 file changed

+134
-59
lines changed

Item Creator/src/main/java/org/broken/arrow/library/itemcreator/SkullCreator.java

Lines changed: 134 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,19 @@
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
*/
4444
public 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

Comments
 (0)