38
38
import org .bukkit .inventory .ItemStack ;
39
39
import org .bukkit .inventory .meta .ItemMeta ;
40
40
41
+ import java .lang .reflect .InvocationTargetException ;
41
42
import java .util .ArrayList ;
42
43
import java .util .HashMap ;
43
44
import java .util .List ;
51
52
public abstract class NBT
52
53
{
53
54
private NBT () {}
54
-
55
+
55
56
/**
56
57
* Returns the NBT JSON representation of the given object.
57
58
* This method returns a non-strict JSON representation of the object,
58
59
* because minecraft (both client and server) can't deal with strict JSON
59
60
* for some item nbt tags.
61
+ *
60
62
* @param value The value to JSONify.
61
63
* @return the NBT JSON representation of the given object.
62
64
*/
@@ -66,13 +68,15 @@ static public String toNBTJSONString(Object value)
66
68
toNBTJSONString (sb , value );
67
69
return sb .toString ();
68
70
}
69
-
71
+
72
+
70
73
/* ========== Item utilities ========== */
71
74
72
75
/**
73
76
* Returns the NBT tag for the specified item.
74
77
* The tag is read-write, and any modification applied to it will also be
75
78
* applied to the item's NBT tag.
79
+ *
76
80
* @param item The item to get the tag from.
77
81
* @return the NBT tag for the specified item.
78
82
* @throws NMSException
@@ -94,17 +98,18 @@ static public NBTCompound fromItemStack(ItemStack item) throws NMSException
94
98
* Returns an NBT-like representation of the specified item meta.
95
99
* It is useful as a fallback if you need item data as an NBT format, but
96
100
* the actual NBT couldn't be retrieved for some reason.
101
+ *
97
102
* @param meta The item meta to get the data from.
98
103
* @return an NBT-like representation of the specified item meta.
99
104
*/
100
105
static public Map <String , Object > fromItemMeta (ItemMeta meta )
101
106
{
102
- Map <String , Object > itemData = new HashMap <String , Object >();
103
-
104
- if (meta .hasDisplayName ())
107
+ Map <String , Object > itemData = new HashMap <>();
108
+
109
+ if (meta .hasDisplayName ())
105
110
itemData .put ("Name" , meta .getDisplayName ());
106
-
107
- if (meta .hasLore ())
111
+
112
+ if (meta .hasLore ())
108
113
itemData .put ("Lore" , meta .getLore ());
109
114
110
115
return itemData ;
@@ -114,6 +119,7 @@ static public Map<String, Object> fromItemMeta(ItemMeta meta)
114
119
* Returns an NBT-like representation of the specified enchantments.
115
120
* It is useful as a fallback if you need item data as an NBT format, but
116
121
* the actual NBT couldn't be retrieved for some reason.
122
+ *
117
123
* @param enchants the enchantment list to get the data from.
118
124
* @return an NBT-like representation of the specified enchantments.
119
125
*/
@@ -123,7 +129,7 @@ static public List<Map<String, Object>> fromEnchantments(Map<Enchantment, Intege
123
129
124
130
for (Map .Entry <Enchantment , Integer > enchantment : enchants .entrySet ())
125
131
{
126
- Map <String , Object > enchantmentData = new HashMap <String , Object >();
132
+ Map <String , Object > enchantmentData = new HashMap <>();
127
133
enchantmentData .put ("id" , enchantment .getKey ().getId ());
128
134
enchantmentData .put ("lvl" , enchantment .getValue ());
129
135
enchantList .add (enchantmentData );
@@ -136,16 +142,17 @@ static public List<Map<String, Object>> fromEnchantments(Map<Enchantment, Intege
136
142
* Returns an NBT-like representation of the item flags (HideFlags).
137
143
* It is useful as a fallback if you need item data as an NBT format, but
138
144
* the actual NBT couldn't be retrieved for some reason.
145
+ *
139
146
* @param itemFlags item flag set to get the data from.
140
147
* @return an NBT-like representation of the item flags (HideFlags).
141
148
*/
142
149
static public byte fromItemFlags (Set <ItemFlag > itemFlags )
143
150
{
144
151
byte flags = 0 ;
145
-
146
- for (ItemFlag flag : itemFlags )
152
+
153
+ for (ItemFlag flag : itemFlags )
147
154
{
148
- switch (flag )
155
+ switch (flag )
149
156
{
150
157
case HIDE_ENCHANTS :
151
158
flags += 1 ; break ;
@@ -164,54 +171,203 @@ static public byte fromItemFlags(Set<ItemFlag> itemFlags)
164
171
165
172
return flags ;
166
173
}
167
-
174
+
175
+ /**
176
+ * Replaces the tags in the given ItemStack by the given tags.
177
+ *
178
+ * This operation is only possible on a CraftItemStack. As a consequence,
179
+ * this method <strong>returns</strong> an {@link ItemStack}. If the given
180
+ * ItemStack was a CraftItemStack, the same instance will be returned, but
181
+ * in the other cases, it will be a new one (a copy).
182
+ *
183
+ * @param item The ItemStack to change.
184
+ * @param tags The tags to place inside the stack.
185
+ *
186
+ * @return An item stack with the modification applied. It may (if you given
187
+ * a CraftItemStack) or may not (else) be the same instance as the given
188
+ * one.
189
+ * @throws NMSException if the operation cannot be executed.
190
+ * @see #addToItemStack(ItemStack, Map, boolean) This method is equivalent
191
+ * to this one with replace = true.
192
+ */
193
+ static public ItemStack addToItemStack (ItemStack item , Map <String , Object > tags ) throws NMSException
194
+ {
195
+ return addToItemStack (item , tags , true );
196
+ }
197
+
198
+ /**
199
+ * Adds or replaces the tags in the given ItemStack by the given tags.
200
+ *
201
+ * This operation is only possible on a CraftItemStack. As a consequence,
202
+ * this method <strong>returns</strong> an {@link ItemStack}. If the given
203
+ * ItemStack was a CraftItemStack, the same instance will be returned, but
204
+ * in the other cases, it will be a new one (a copy).
205
+ *
206
+ * @param item The ItemStack to change.
207
+ * @param tags The tags to place inside the stack.
208
+ * @param replace {@code true} to replace the whole set of tags. If {@code
209
+ * false}, tags will be added.
210
+ *
211
+ * @return An item stack with the modification applied. It may (if you given
212
+ * a CraftItemStack) or may not (else) be the same instance as the given
213
+ * one.
214
+ * @throws NMSException if the operation cannot be executed.
215
+ */
216
+ static public ItemStack addToItemStack (final ItemStack item , final Map <String , Object > tags , final boolean replace ) throws NMSException
217
+ {
218
+ init ();
219
+
220
+ try
221
+ {
222
+ final ItemStack craftItemStack = (ItemStack ) ItemUtils .getCraftItemStack (item );
223
+ final Object mcItemStack = ItemUtils .getNMSItemStack (item );
224
+ final NBTCompound compound = fromItemStack (craftItemStack );
225
+
226
+ if (replace ) compound .clear ();
227
+ compound .putAll (tags );
228
+ Object tag = compound .getNBTTagCompound ();
229
+
230
+ if (tag != null )
231
+ {
232
+ final ItemMeta craftItemMeta = (ItemMeta ) Reflection .call (craftItemStack .getClass (), null , "getItemMeta" , new Object [] {mcItemStack });
233
+
234
+ // There's an "applyToItem" method in CraftItemMeta but is doesn't handle well new NBT tags.
235
+ // We try to re-create a whole new instance from the same CraftItemMeta base class instead,
236
+ // using the constructor accepting a NBTTagCompound.
237
+ ItemMeta newCraftItemMeta ;
238
+ try
239
+ {
240
+ newCraftItemMeta = Reflection .instantiate (craftItemMeta .getClass (), tag );
241
+ }
242
+ catch (NoSuchMethodException e )
243
+ {
244
+ // The CraftMetaBlockState constructor is different (like some Portal's turrets):
245
+ // he takes the Material as his second argument.
246
+ newCraftItemMeta = Reflection .instantiate (craftItemMeta .getClass (), tag , craftItemStack .getType ());
247
+ }
248
+
249
+ craftItemStack .setItemMeta (newCraftItemMeta );
250
+ }
251
+
252
+ return craftItemStack ;
253
+ }
254
+ catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException | NMSException e )
255
+ {
256
+ throw new NMSException ("Cannot set item stack tags" , e );
257
+ }
258
+ }
259
+
260
+
168
261
/* ========== Internal utilities ========== */
169
262
170
- static Class MC_ITEM_STACK = null ;
171
-
263
+ static Class <?> MC_ITEM_STACK = null ;
264
+ static Class <?> MC_NBT_TAG_COMPOUND = null ;
265
+ static Class <?> CB_CRAFT_ITEM_META = null ;
266
+
267
+
172
268
static Class getMinecraftClass (String className ) throws NMSException
173
269
{
174
270
try
175
271
{
176
272
return Reflection .getMinecraftClassByName (className );
177
273
}
178
- catch (ClassNotFoundException ex )
274
+ catch (ClassNotFoundException ex )
179
275
{
180
- throw new NMSException ("Unable to find class : " + className , ex );
276
+ throw new NMSException ("Unable to find class: " + className , ex );
181
277
}
182
278
}
183
-
279
+
280
+ static Class getCraftBukkitClass (String className ) throws NMSException
281
+ {
282
+ try
283
+ {
284
+ return Reflection .getBukkitClassByName (className );
285
+ }
286
+ catch (ClassNotFoundException ex )
287
+ {
288
+ throw new NMSException ("Unable to find class: " + className , ex );
289
+ }
290
+ }
291
+
184
292
static private void init () throws NMSException
185
293
{
186
- if (MC_ITEM_STACK != null ) return ;// Already initialized
187
-
294
+ if (MC_ITEM_STACK != null ) return ; // Already initialized
295
+
188
296
MC_ITEM_STACK = getMinecraftClass ("ItemStack" );
297
+ MC_NBT_TAG_COMPOUND = getMinecraftClass ("NBTTagCompound" );
298
+ CB_CRAFT_ITEM_META = getCraftBukkitClass ("inventory.CraftMetaItem" );
189
299
}
190
-
300
+
301
+ /**
302
+ * Extracts the NBT compound in the given ItemStack.
303
+ *
304
+ * <p>If there isn't any NBT tag in the ItemStack, one is created, linked to
305
+ * the ItemStack, and returned, so this will always return a NBT compound tag
306
+ * linked to the ItemStack.</p>
307
+ *
308
+ * @param item The item to extract NBT tags from.
309
+ * @return The NMS NBT tag.
310
+ * @throws NMSException If something goes wrong while extracting the tag.
311
+ */
191
312
static private Object getMcNBTCompound (ItemStack item ) throws NMSException
192
313
{
193
314
Object mcItemStack = ItemUtils .getNMSItemStack (item );
194
- if (mcItemStack == null ) return null ;
315
+ if (mcItemStack == null ) return null ;
316
+
195
317
try
196
318
{
197
- return Reflection .getFieldValue (MC_ITEM_STACK , mcItemStack , "tag" );
319
+ Object tag = Reflection .getFieldValue (MC_ITEM_STACK , mcItemStack , "tag" );
320
+
321
+ if (tag == null )
322
+ {
323
+ tag = Reflection .instantiate (MC_NBT_TAG_COMPOUND );
324
+
325
+ try
326
+ {
327
+ Reflection .call (MC_ITEM_STACK , mcItemStack , "setTag" , tag );
328
+ }
329
+ catch (NoSuchMethodException e ) // If the set method change—more resilient, as the setTag will only update the field without any kind of callback.
330
+ {
331
+ Reflection .setFieldValue (MC_ITEM_STACK , mcItemStack , "tag" , tag );
332
+ }
333
+ }
334
+
335
+ return tag ;
198
336
}
199
337
catch (Exception ex )
200
338
{
201
339
throw new NMSException ("Unable to retrieve NBT tag from item" , ex );
202
340
}
203
341
}
204
-
342
+
343
+
344
+ /**
345
+ * Converts a native Java value to a NMS NBT instance.
346
+ *
347
+ * @param value The native Java value to convert.
348
+ * @return The NMS NBT tag instance converted from the native value.
349
+ */
205
350
static Object fromNativeValue (Object value )
206
351
{
207
- if (value == null ) return null ;
352
+ if (value == null ) return null ;
353
+
208
354
NBTType type = NBTType .fromClass (value .getClass ());
209
355
return type .newTag (value );
210
356
}
211
-
357
+
358
+ /**
359
+ * Converts a NMS NBT tag instance to a native value, effectively
360
+ * unwrapping the value inside to something Java alone can understand.
361
+ *
362
+ * Nested values are also converted, if any, to objects implementing
363
+ * the {@link Map} or {@link List} interface..
364
+ *
365
+ * @param nbtTag The NMS NBT tag instance.
366
+ * @return The corresponding native value.
367
+ */
212
368
static Object toNativeValue (Object nbtTag )
213
369
{
214
- if (nbtTag == null ) return null ;
370
+ if (nbtTag == null ) return null ;
215
371
NBTType type = NBTType .fromNmsNbtTag (nbtTag );
216
372
217
373
switch (type )
@@ -224,18 +380,19 @@ static Object toNativeValue(Object nbtTag)
224
380
return type .getData (nbtTag );
225
381
}
226
382
}
227
-
383
+
384
+
228
385
/* ========== NBT String Utilities ========== */
229
386
230
387
static private void toNBTJSONString (StringBuilder builder , Object value )
231
388
{
232
- if (value == null ) return ;
389
+ if (value == null ) return ;
233
390
234
- if (value instanceof List )
391
+ if (value instanceof List )
235
392
toNBTJSONString (builder , (List ) value );
236
- else if (value instanceof Map )
393
+ else if (value instanceof Map )
237
394
toNBTJSONString (builder , (Map ) value );
238
- else if (value instanceof String )
395
+ else if (value instanceof String )
239
396
toNBTJSONString (builder , (String ) value );
240
397
else
241
398
builder .append (value .toString ());
@@ -260,11 +417,11 @@ static private void toNBTJSONString(StringBuilder builder, List list)
260
417
static private void toNBTJSONString (StringBuilder builder , Map <Object , Object > map )
261
418
{
262
419
builder .append ("{" );
263
-
420
+
264
421
boolean isFirst = true ;
265
- for (Entry <Object , Object > entry : map .entrySet ())
422
+ for (Entry <Object , Object > entry : map .entrySet ())
266
423
{
267
- if (!isFirst )
424
+ if (!isFirst )
268
425
builder .append ("," );
269
426
builder .append (entry .getKey ().toString ());
270
427
builder .append (':' );
0 commit comments