Skip to content

Commit 91c0501

Browse files
committed
add SkullModifier
add some notes about legacy modifiers
1 parent b9f6265 commit 91c0501

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@
137137
<id>placeholderapi</id>
138138
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
139139
</repository>
140+
<repository>
141+
<id>minecraft-repo</id>
142+
<url>https://libraries.minecraft.net/</url>
143+
</repository>
140144
</repositories>
141145

142146
<dependencies>
@@ -148,6 +152,14 @@
148152
<scope>provided</scope>
149153
</dependency>
150154

155+
<!-- AuthLib -->
156+
<dependency>
157+
<groupId>com.mojang</groupId>
158+
<artifactId>authlib</artifactId>
159+
<version>1.5.21</version>
160+
<scope>provided</scope>
161+
</dependency>
162+
151163
<!-- HSCore -->
152164
<dependency>
153165
<groupId>me.hsgamer</groupId>

src/main/java/me/hsgamer/bettergui/builder/ItemModifierBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package me.hsgamer.bettergui.builder;
22

33
import me.hsgamer.bettergui.modifier.NBTModifier;
4+
import me.hsgamer.bettergui.modifier.SkullModifier;
45
import me.hsgamer.hscore.builder.MassBuilder;
56
import me.hsgamer.hscore.bukkit.item.modifier.*;
67
import me.hsgamer.hscore.minecraft.item.ItemModifier;

src/main/java/me/hsgamer/bettergui/modifier/NBTModifier.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
import java.util.Objects;
1717
import java.util.UUID;
1818

19+
/**
20+
* This is a legacy modifier that was removed from HSCore.
21+
* I decide to keep it here for compatibility.
22+
*/
1923
public class NBTModifier implements ItemModifier<ItemStack> {
2024
private static final Gson GSON = new Gson();
2125
private static final boolean USE_ITEM_COMPONENT = VersionUtils.isAtLeast(20, 5);
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package me.hsgamer.bettergui.modifier;
2+
3+
import com.mojang.authlib.GameProfile;
4+
import com.mojang.authlib.properties.Property;
5+
import me.hsgamer.hscore.bukkit.item.modifier.ItemMetaComparator;
6+
import me.hsgamer.hscore.bukkit.item.modifier.ItemMetaModifier;
7+
import me.hsgamer.hscore.bukkit.utils.BukkitUtils;
8+
import me.hsgamer.hscore.bukkit.utils.VersionUtils;
9+
import me.hsgamer.hscore.common.StringReplacer;
10+
import me.hsgamer.hscore.common.Validate;
11+
import org.bukkit.Bukkit;
12+
import org.bukkit.Material;
13+
import org.bukkit.OfflinePlayer;
14+
import org.bukkit.inventory.ItemStack;
15+
import org.bukkit.inventory.meta.ItemMeta;
16+
import org.bukkit.inventory.meta.SkullMeta;
17+
import org.jetbrains.annotations.NotNull;
18+
import org.jetbrains.annotations.Nullable;
19+
20+
import java.lang.reflect.Field;
21+
import java.lang.reflect.Method;
22+
import java.net.URL;
23+
import java.util.Base64;
24+
import java.util.Collection;
25+
import java.util.Objects;
26+
import java.util.UUID;
27+
import java.util.regex.Pattern;
28+
29+
/**
30+
* This is a legacy modifier that was removed from HSCore.
31+
* I decide to keep it here for compatibility.
32+
*/
33+
@SuppressWarnings("deprecation")
34+
public class SkullModifier implements ItemMetaModifier, ItemMetaComparator {
35+
/**
36+
* <a href="https://github.com/CryptoMorin/XSeries/blob/b633d00608435701f1045a566b98a81edd5f923c/src/main/java/com/cryptomorin/xseries/profiles/objects/ProfileInputType.java#L29C35-L29C50">...</a>
37+
*/
38+
private static final Pattern MOJANG_SHA256_APPROX = Pattern.compile("[0-9a-z]{55,70}");
39+
private static final SkullMeta delegateSkullMeta;
40+
private static final SkullHandler skullHandler = new SkullHandler();
41+
42+
static {
43+
ItemStack itemStack;
44+
if (VersionUtils.isAtLeast(13)) {
45+
itemStack = new ItemStack(Material.valueOf("PLAYER_HEAD"));
46+
} else {
47+
itemStack = new ItemStack(Material.valueOf("SKULL_ITEM"));
48+
itemStack.setDurability((short) 3);
49+
}
50+
delegateSkullMeta = (SkullMeta) Objects.requireNonNull(itemStack.getItemMeta());
51+
}
52+
53+
private String skullString = "";
54+
55+
private static void setSkull(SkullMeta meta, String skull) {
56+
if (BukkitUtils.isUsername(skull)) {
57+
skullHandler.setSkullByName(meta, skull);
58+
} else if (Validate.isValidUUID(skull)) {
59+
skullHandler.setSkullByUUID(meta, UUID.fromString(skull));
60+
} else if (Validate.isValidURL(skull)) {
61+
skullHandler.setSkullByURL(meta, skull);
62+
} else if (MOJANG_SHA256_APPROX.matcher(skull).matches()) {
63+
skullHandler.setSkullByURL(meta, "https://textures.minecraft.net/texture/" + skull);
64+
}
65+
}
66+
67+
private static SkullMeta getSkullMeta(String skull) {
68+
SkullMeta meta = delegateSkullMeta.clone();
69+
setSkull(meta, skull);
70+
return meta;
71+
}
72+
73+
private String getFinalSkullString(UUID uuid, Collection<StringReplacer> replacers) {
74+
return StringReplacer.replace(skullString, uuid, replacers);
75+
}
76+
77+
@Override
78+
public @NotNull ItemMeta modifyMeta(@NotNull ItemMeta meta, @Nullable UUID uuid, @NotNull Collection<StringReplacer> stringReplacers) {
79+
if (!(meta instanceof SkullMeta)) {
80+
return meta;
81+
}
82+
setSkull((SkullMeta) meta, getFinalSkullString(uuid, stringReplacers));
83+
return meta;
84+
}
85+
86+
@Override
87+
public boolean loadFromItemMeta(ItemMeta meta) {
88+
if (meta instanceof SkullMeta) {
89+
skullString = skullHandler.getSkullValue((SkullMeta) meta);
90+
return true;
91+
}
92+
return false;
93+
}
94+
95+
@Override
96+
public boolean compare(@NotNull ItemMeta meta, @Nullable UUID uuid, @NotNull Collection<StringReplacer> stringReplacers) {
97+
if (!(meta instanceof SkullMeta)) {
98+
return false;
99+
}
100+
return skullHandler.compareSkull(
101+
getSkullMeta(getFinalSkullString(uuid, stringReplacers)),
102+
(SkullMeta) meta
103+
);
104+
}
105+
106+
@Override
107+
public Object toObject() {
108+
return skullString;
109+
}
110+
111+
@Override
112+
public void loadFromObject(Object object) {
113+
this.skullString = String.valueOf(object);
114+
}
115+
116+
private static class SkullHandler {
117+
private final Method getProfileMethod;
118+
119+
private SkullHandler() {
120+
Method method = null;
121+
try {
122+
//noinspection JavaReflectionMemberAccess
123+
method = Property.class.getDeclaredMethod("value");
124+
} catch (Exception e) {
125+
try {
126+
//noinspection JavaReflectionMemberAccess
127+
method = Property.class.getDeclaredMethod("getValue");
128+
} catch (NoSuchMethodException ex) {
129+
// IGNORE
130+
}
131+
}
132+
getProfileMethod = method;
133+
}
134+
135+
public void setSkullByName(SkullMeta meta, String name) {
136+
setSkullByPlayer(meta, Bukkit.getOfflinePlayer(name));
137+
}
138+
139+
public void setSkullByUUID(SkullMeta meta, UUID uuid) {
140+
setSkullByPlayer(meta, Bukkit.getOfflinePlayer(uuid));
141+
}
142+
143+
public void setSkullByURL(SkullMeta meta, String url) {
144+
try {
145+
setSkullByURL(meta, new URL(url));
146+
} catch (Exception e) {
147+
throw new RuntimeException(e);
148+
}
149+
}
150+
151+
@SuppressWarnings("deprecation")
152+
public void setSkullByPlayer(SkullMeta meta, OfflinePlayer player) {
153+
if (VersionUtils.isAtLeast(12)) {
154+
meta.setOwningPlayer(player);
155+
} else {
156+
meta.setOwner(player.getName());
157+
}
158+
}
159+
160+
public void setSkullByURL(SkullMeta meta, URL url) {
161+
GameProfile profile = new GameProfile(UUID.randomUUID(), null);
162+
profile.getProperties().put("textures", new Property("textures", Base64.getEncoder().encodeToString(String.format("{textures:{SKIN:{url:\"%s\"}}}", url).getBytes())));
163+
164+
try {
165+
Method setProfile = meta.getClass().getMethod("setProfile", GameProfile.class);
166+
setProfile.setAccessible(true);
167+
setProfile.invoke(meta, profile);
168+
} catch (Exception e) {
169+
try {
170+
Field profileField = meta.getClass().getDeclaredField("profile");
171+
profileField.setAccessible(true);
172+
profileField.set(meta, profile);
173+
} catch (Exception ignored) {
174+
// IGNORE
175+
}
176+
}
177+
}
178+
179+
public String getSkullValue(SkullMeta meta) {
180+
GameProfile profile;
181+
try {
182+
Field profileField = meta.getClass().getDeclaredField("profile");
183+
profileField.setAccessible(true);
184+
profile = (GameProfile) profileField.get(meta);
185+
} catch (Exception e) {
186+
return "";
187+
}
188+
189+
Collection<Property> properties = profile.getProperties().get("textures");
190+
if (properties == null || properties.isEmpty()) {
191+
return "";
192+
}
193+
194+
for (Property property : properties) {
195+
String value;
196+
try {
197+
value = (String) getProfileMethod.invoke(property);
198+
} catch (Exception e) {
199+
continue;
200+
}
201+
202+
if (!value.isEmpty()) {
203+
return value;
204+
}
205+
}
206+
return "";
207+
}
208+
209+
public boolean compareSkull(SkullMeta meta1, SkullMeta meta2) {
210+
return getSkullValue(meta1).equals(getSkullValue(meta2));
211+
}
212+
}
213+
}

0 commit comments

Comments
 (0)