Skip to content

Commit 8f74bf2

Browse files
committed
Made the set of NBT more flexible
Made so could be expanded in the future, but should still just be an light wight option to NBTAPI.
1 parent 120b70e commit 8f74bf2

File tree

3 files changed

+352
-170
lines changed

3 files changed

+352
-170
lines changed

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

Lines changed: 25 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.broken.arrow.library.itemcreator.utility;
22

3+
import org.broken.arrow.library.itemcreator.utility.compound.CompoundTag;
4+
import org.broken.arrow.library.itemcreator.utility.compound.NbtData;
35
import org.broken.arrow.library.logging.Logging;
46
import org.bukkit.Bukkit;
57
import org.bukkit.Material;
@@ -8,9 +10,7 @@
810

911
import javax.annotation.Nonnull;
1012
import javax.annotation.Nullable;
11-
import java.lang.reflect.Constructor;
1213
import java.lang.reflect.InvocationTargetException;
13-
import java.lang.reflect.Method;
1414

1515
public final class UnbreakableUtil {
1616
private static final Logging logger = new Logging(UnbreakableUtil.class);
@@ -40,7 +40,6 @@ public static ItemMeta applyToMeta(@Nullable final ItemMeta meta, final boolean
4040
meta.setUnbreakable(unbreakable);
4141
return meta;
4242
}
43-
if (!NMS_NBT_BRIDGE.isReflectionReady()) return meta;
4443

4544
Material original = getMetaType(meta);
4645
if (original == null || original == Material.AIR) original = Material.STONE;
@@ -72,8 +71,6 @@ public static ItemStack applyToItem(@Nullable final ItemStack item, final boolea
7271
return item;
7372
}
7473

75-
if (!NMS_NBT_BRIDGE.isReflectionReady()) return item;
76-
7774
try {
7875
return NMS_NBT_BRIDGE.applyUnbreakableTag(item, "Unbreakable", unbreakable);
7976
} catch (InvocationTargetException | IllegalAccessException | InstantiationException t) {
@@ -98,7 +95,6 @@ public static boolean isUnbreakable(@Nullable final ItemStack item) {
9895
final ItemMeta itemMeta = item.getItemMeta();
9996
return itemMeta != null && itemMeta.isUnbreakable();
10097
} else {
101-
if (!NMS_NBT_BRIDGE.isReflectionReady()) return false;
10298
try {
10399
return NMS_NBT_BRIDGE.hasBooleanTag(item, "Unbreakable");
104100
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
@@ -136,17 +132,7 @@ private static Material getMetaType(@Nonnull final ItemMeta meta) {
136132
* <p>Otherwise it dynamically resolves required CraftBukkit and NMS classes
137133
* and methods.</p>
138134
*/
139-
private static class NmsNbtBridge {
140-
private Method asNMSCopyMethod;
141-
private Method asBukkitCopyMethod;
142-
private Method hasTagMethod;
143-
private Method getTagMethod;
144-
private Method setTagMethod;
145-
private Method setBooleanMethod;
146-
private Method getBooleanMethod;
147-
private Constructor<?> nbtTagConstructor;
148-
149-
private boolean reflectionReady;
135+
public static class NmsNbtBridge {
150136
private boolean modernSupported;
151137

152138
/**
@@ -162,42 +148,17 @@ private NmsNbtBridge() {
162148
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
163149
this.modernSupported = false;
164150
}
165-
if (this.modernSupported) return;
166-
loadLegacyReflection();
167-
}
168-
169-
public Method getAsBukkitCopyMethod() {
170-
return asBukkitCopyMethod;
171-
}
172-
173-
public Method getHasTagMethod() {
174-
return hasTagMethod;
175-
}
176-
177-
public Method getTagMethod() {
178-
return getTagMethod;
179-
}
180-
181-
public Method getSetTagMethod() {
182-
return setTagMethod;
183-
}
184-
185-
public Method getSetBooleanMethod() {
186-
return setBooleanMethod;
187-
}
188-
189-
public Method getBooleanMethod() {
190-
return getBooleanMethod;
191-
}
192-
193-
public Constructor<?> getNbtTagConstructor() {
194-
return nbtTagConstructor;
195-
}
196-
197-
public boolean isReflectionReady() {
198-
return reflectionReady;
199151
}
200152

153+
/**
154+
* Indicates whether the current server version supports the modern
155+
* Bukkit API method {@code ItemMeta#setUnbreakable(boolean)}.
156+
*
157+
* <p>If this returns {@code true}, no reflection or NMS handling is required
158+
* and this bridge will remain inactive.</p>
159+
*
160+
* @return {@code true} if the modern API is available, otherwise {@code false}
161+
*/
201162
public boolean isModernSupported() {
202163
return modernSupported;
203164
}
@@ -212,137 +173,31 @@ public boolean isModernSupported() {
212173
* @return a new Bukkit ItemStack instance with updated NBT
213174
*/
214175
public ItemStack applyUnbreakableTag(@Nonnull final ItemStack item, @Nonnull final String key, final boolean unbreakable) throws InvocationTargetException, IllegalAccessException, InstantiationException {
215-
Object nmsItem = this.toNmsItemStack(item);
216-
this.applyBooleanTag(nmsItem, key, unbreakable);
217-
return (ItemStack) this.getAsBukkitCopyMethod().invoke(null, nmsItem);
176+
NbtData nms = new NbtData(item);
177+
if(!nms.isReflectionReady()) return item;
178+
179+
CompoundTag compound = nms.getOrCreateCompound();
180+
compound.setBoolean(key,unbreakable);
181+
return nms.apply(compound);
218182
}
219183

220184
/**
221185
* Checks whether the given Bukkit item contains the specified
222-
* boolean NBT key.
186+
* NBT key.
223187
*
224188
* @param item Bukkit ItemStack
225189
* @param key NBT key
226190
* @return {@code true} if the key exists and is set to {@code true}
227191
*/
228192
public boolean hasBooleanTag(@Nonnull final ItemStack item, @Nonnull String key) throws InvocationTargetException, IllegalAccessException, InstantiationException {
229-
Object nmsItem = this.toNmsItemStack(item);
230-
if (!(Boolean) this.getHasTagMethod().invoke(nmsItem)) {
231-
return false;
232-
}
233-
Object tag = this.getOrCreateNbtTag(nmsItem);
234-
return (Boolean) this.getBooleanMethod().invoke(tag, key);
235-
}
236-
237-
/**
238-
* Applies a boolean tag to an NMS ItemStack.
239-
*
240-
* @param nmsItem the nms stack.
241-
* @param key the key to set
242-
* @param unbreakable if it set to {@code true} it will be unbreakable, otherwise the tool can break.
243-
*/
244-
public void applyBooleanTag(@Nonnull final Object nmsItem, @Nonnull final String key, final boolean unbreakable) throws InvocationTargetException, IllegalAccessException, InstantiationException {
245-
final Object tag = this.getOrCreateNbtTag(nmsItem);
246-
this.setBooleanTag(tag, key, unbreakable);
247-
this.applyTagCompound(nmsItem, tag);
248-
}
249-
250-
/**
251-
* Applies an NBT compound to an NMS ItemStack.
252-
*
253-
* @param nmsItem the nms stack.
254-
* @param tag the NBTTagCompound to be set.
255-
*/
256-
public void applyTagCompound(@Nonnull final Object nmsItem, @Nonnull final Object tag) throws InvocationTargetException, IllegalAccessException {
257-
getSetTagMethod().invoke(nmsItem, tag);
258-
}
193+
NbtData nms = new NbtData(item);
194+
if(!nms.isReflectionReady()) return false;
259195

260-
/**
261-
* Sets a boolean inside an NBTTagCompound.
262-
*
263-
* @param tag the NBTTagCompound to be set.
264-
* @param key the key to set
265-
* @param unbreakable if it shall be true or false.
266-
*/
267-
public void setBooleanTag(@Nonnull final Object tag, @Nonnull final String key, final boolean unbreakable) throws InvocationTargetException, IllegalAccessException {
268-
getSetBooleanMethod().invoke(tag, key, unbreakable);
269-
}
270-
271-
/**
272-
* Returns the existing NBTTagCompound if one is present,
273-
* otherwise creates a new one.
274-
*
275-
* @param nmsItem the nms stack.
276-
* @return the NBTTagCompound if exist or create new one.
277-
*/
278-
public Object getOrCreateNbtTag(@Nonnull final Object nmsItem) throws InvocationTargetException, IllegalAccessException, InstantiationException {
279-
boolean hasTag = (boolean) hasTagMethod.invoke(nmsItem);
280-
return hasTag
281-
? getTagMethod.invoke(nmsItem)
282-
: nbtTagConstructor.newInstance();
283-
}
284-
285-
/**
286-
* Resolves all CraftBukkit and NMS classes and methods using reflection.
287-
* This is only required for legacy versions.
288-
*/
289-
private void loadLegacyReflection() {
290-
try {
291-
292-
String craftPath = getCraftBukkitPath();
293-
294-
final Class<?> craftItemStackClass = Class.forName(craftPath + ".inventory.CraftItemStack");
295-
asNMSCopyMethod = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class);
296-
asBukkitCopyMethod = craftItemStackClass.getMethod("asBukkitCopy", Class.forName(getMinecraftPath() + ".ItemStack"));
297-
298-
final Class<?> nbtTagCompoundClass = Class.forName(getMinecraftPath() + ".NBTTagCompound");
299-
300-
nbtTagConstructor = nbtTagCompoundClass.getConstructor();
301-
302-
Class<?> nmsStackClass = Class.forName(getMinecraftPath() + ".ItemStack");
303-
304-
hasTagMethod = nmsStackClass.getMethod("hasTag");
305-
getTagMethod = nmsStackClass.getMethod("getTag");
306-
setTagMethod = nmsStackClass.getMethod("setTag", nbtTagCompoundClass);
307-
setBooleanMethod = nbtTagCompoundClass.getMethod("setBoolean", String.class, boolean.class);
308-
getBooleanMethod = nbtTagCompoundClass.getMethod("getBoolean", String.class);
309-
310-
reflectionReady = true;
311-
} catch (ClassNotFoundException | NoSuchMethodException t) {
312-
logger.logError(t, () -> "Could not resolve the unbreakable tag for this minecraft version");
313-
reflectionReady = false;
196+
final CompoundTag compound = nms.getCompound();
197+
if (compound == null) {
198+
return false;
314199
}
315-
}
316-
317-
private String getMinecraftPath() {
318-
return "net.minecraft.server."
319-
+ getPackageVersion();
320-
}
321-
322-
private String getCraftBukkitPath() {
323-
return "org.bukkit.craftbukkit." + getPackageVersion();
324-
}
325-
326-
/**
327-
* Extracts the version identifier from the Bukkit server package.
328-
* This version will only work on legacy, as the path changed in newer
329-
* minecraft versions.
330-
* Example: v1_8_R3
331-
*
332-
* @return it returns for example v1_8_R3
333-
*/
334-
private String getPackageVersion() {
335-
return Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
336-
}
337-
338-
/**
339-
* Converts a Bukkit ItemStack into its NMS counterpart.
340-
*
341-
* @param item Bukkit ItemStack to convert.
342-
* @return the underlying Nms itemStack.
343-
*/
344-
private Object toNmsItemStack(@Nonnull final ItemStack item) throws IllegalAccessException, InvocationTargetException {
345-
return this.asNMSCopyMethod.invoke(null, item);
200+
return compound.hasKey(key);
346201
}
347202

348203
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.broken.arrow.library.itemcreator.utility.compound;
2+
3+
import org.broken.arrow.library.logging.Logging;
4+
import org.broken.arrow.library.logging.Validate;
5+
6+
import javax.annotation.Nonnull;
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Method;
9+
10+
/**
11+
* Wraps an underlying NBTTagCompound belonging to an NMS ItemStack.
12+
*
13+
* <p>This is <b>not</b> intended for normal plugin-specific NBT storage.
14+
* It is used to modify low-level, vanilla-controlled item properties
15+
* which are otherwise inaccessible through the Bukkit API in legacy
16+
* Minecraft versions.</p>
17+
*
18+
* <p>Examples include keys such as {@code "Unbreakable"} that affect the
19+
* behaviour of the actual item, not just custom plugin data.</p>
20+
*
21+
* <p>This class is backed by reflection. If the required NMS classes or
22+
* methods cannot be resolved, operations will fail silently (with logging)
23+
* and no changes will be applied to the compound.</p>
24+
*
25+
*/
26+
public final class CompoundTag {
27+
private static final Logging logger = new Logging(CompoundTag.class);
28+
private static final Method hasKey;
29+
private static final Method setBoolean;
30+
private static final Method getBoolean;
31+
private final Object handle;
32+
33+
static {
34+
Method hasTagKey = null;
35+
Method setBooleanM = null;
36+
Method getBooleanM = null;
37+
try {
38+
final Class<?> nbtTag = Class.forName(NbtData.getNbtTagPath());
39+
hasTagKey = nbtTag.getMethod("hasKey", String.class);
40+
setBooleanM = nbtTag.getMethod("setBoolean", String.class, boolean.class);
41+
getBooleanM = nbtTag.getMethod("getBoolean", String.class);
42+
} catch (ClassNotFoundException | NoSuchMethodException e) {
43+
logger.logError(e, () -> "Failed to bind NBT methods");
44+
}
45+
hasKey = hasTagKey;
46+
setBoolean = setBooleanM;
47+
getBoolean = getBooleanM;
48+
49+
}
50+
51+
CompoundTag(@Nonnull final Object handle) {
52+
Validate.checkNotNull(handle, "CompoundTag handle cannot be null");
53+
this.handle = handle;
54+
}
55+
56+
/**
57+
* This handle is the NBTTagCompound object.
58+
*
59+
* @return returns the NBTTagCompound object.
60+
*/
61+
@Nonnull
62+
Object getHandle() {
63+
return handle;
64+
}
65+
66+
/**
67+
* Checks whether this {@link CompoundTag} contains the given key.
68+
*
69+
* @param key the NBT key to check
70+
* @return {@code true} if the key exists, otherwise {@code false}
71+
*/
72+
public boolean hasKey(@Nonnull String key) {
73+
if (hasKey == null) return false;
74+
75+
try {
76+
return (boolean) hasKey.invoke(handle, key);
77+
} catch (IllegalAccessException | InvocationTargetException e) {
78+
logger.logError(e, () -> "Failed to check if the compound have the key.");
79+
}
80+
return false;
81+
}
82+
83+
/**
84+
* Sets a boolean value in the underlying NBTTagCompound.
85+
*
86+
* @param key the key to set
87+
* @param value the boolean value to assign
88+
*/
89+
public void setBoolean(@Nonnull String key, boolean value) {
90+
if (setBoolean == null) return;
91+
92+
try {
93+
setBoolean.invoke(handle, key, value);
94+
} catch (IllegalAccessException | InvocationTargetException e) {
95+
logger.logError(e, () -> "Failed to set boolean value from reflection");
96+
}
97+
}
98+
99+
/**
100+
* Gets a boolean value from the underlying NBTTagCompound.
101+
*
102+
* @param key the key of the boolean value
103+
* @return the stored boolean value, or {@code false} if unavailable
104+
*/
105+
public boolean getBoolean(@Nonnull String key) {
106+
if (getBoolean == null) return false;
107+
108+
try {
109+
return (boolean) getBoolean.invoke(handle, key);
110+
} catch (IllegalAccessException | InvocationTargetException e) {
111+
logger.logError(e, () -> "Failed to retrieve boolean value from reflection");
112+
}
113+
return false;
114+
}
115+
}

0 commit comments

Comments
 (0)