Skip to content

Commit 284a605

Browse files
Merge pull request #15 from zDevelopers/feature-nbt-rw
Read and write support for any NBT data into items
2 parents 7c8e620 + 684a27d commit 284a605

File tree

9 files changed

+722
-270
lines changed

9 files changed

+722
-270
lines changed

src/main/java/fr/zcraft/zlib/components/configuration/ConfigurationItem.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131

3232
import fr.zcraft.zlib.tools.PluginLogger;
3333
import fr.zcraft.zlib.tools.reflection.Reflection;
34+
import org.bukkit.configuration.file.FileConfiguration;
35+
3436
import java.util.ArrayList;
3537
import java.util.Arrays;
3638
import java.util.HashMap;
37-
import org.bukkit.configuration.file.FileConfiguration;
3839

3940

4041
/**
@@ -147,12 +148,12 @@ public boolean isDefined()
147148
/**
148149
* Updates the value of this configuration item. Saves the change in the configuration file.
149150
*
150-
* If you don't want to save the update, use {@link #set(Object,Boolean) set(value, false)}.
151+
* If you don't want to save the update, use {@link #set(Object,boolean) set(value, false)}.
151152
*
152153
* @param value the new value.
153154
* @return The previously stored value.
154155
*
155-
* @see #set(Object, Boolean)
156+
* @see #set(Object, boolean)
156157
*/
157158
public T set(T value)
158159
{
@@ -299,7 +300,7 @@ static public <T extends ConfigurationSection> T section(String fieldName, Class
299300
}
300301
catch(Exception ex)
301302
{
302-
PluginLogger.warning("Unable to instanciate configuration field '{0}' of type '{1}'", ex, fieldName, sectionClass.getName());
303+
PluginLogger.warning("Unable to instantiate configuration field '{0}' of type '{1}'", ex, fieldName, sectionClass.getName());
303304
throw new RuntimeException(ex);
304305
}
305306
return section;

src/main/java/fr/zcraft/zlib/components/nbt/NBT.java

Lines changed: 191 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.bukkit.inventory.ItemStack;
3939
import org.bukkit.inventory.meta.ItemMeta;
4040

41+
import java.lang.reflect.InvocationTargetException;
4142
import java.util.ArrayList;
4243
import java.util.HashMap;
4344
import java.util.List;
@@ -51,12 +52,13 @@
5152
public abstract class NBT
5253
{
5354
private NBT() {}
54-
55+
5556
/**
5657
* Returns the NBT JSON representation of the given object.
5758
* This method returns a non-strict JSON representation of the object,
5859
* because minecraft (both client and server) can't deal with strict JSON
5960
* for some item nbt tags.
61+
*
6062
* @param value The value to JSONify.
6163
* @return the NBT JSON representation of the given object.
6264
*/
@@ -66,13 +68,15 @@ static public String toNBTJSONString(Object value)
6668
toNBTJSONString(sb, value);
6769
return sb.toString();
6870
}
69-
71+
72+
7073
/* ========== Item utilities ========== */
7174

7275
/**
7376
* Returns the NBT tag for the specified item.
7477
* The tag is read-write, and any modification applied to it will also be
7578
* applied to the item's NBT tag.
79+
*
7680
* @param item The item to get the tag from.
7781
* @return the NBT tag for the specified item.
7882
* @throws NMSException
@@ -94,17 +98,18 @@ static public NBTCompound fromItemStack(ItemStack item) throws NMSException
9498
* Returns an NBT-like representation of the specified item meta.
9599
* It is useful as a fallback if you need item data as an NBT format, but
96100
* the actual NBT couldn't be retrieved for some reason.
101+
*
97102
* @param meta The item meta to get the data from.
98103
* @return an NBT-like representation of the specified item meta.
99104
*/
100105
static public Map<String, Object> fromItemMeta(ItemMeta meta)
101106
{
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())
105110
itemData.put("Name", meta.getDisplayName());
106-
107-
if(meta.hasLore())
111+
112+
if (meta.hasLore())
108113
itemData.put("Lore", meta.getLore());
109114

110115
return itemData;
@@ -114,6 +119,7 @@ static public Map<String, Object> fromItemMeta(ItemMeta meta)
114119
* Returns an NBT-like representation of the specified enchantments.
115120
* It is useful as a fallback if you need item data as an NBT format, but
116121
* the actual NBT couldn't be retrieved for some reason.
122+
*
117123
* @param enchants the enchantment list to get the data from.
118124
* @return an NBT-like representation of the specified enchantments.
119125
*/
@@ -123,7 +129,7 @@ static public List<Map<String, Object>> fromEnchantments(Map<Enchantment, Intege
123129

124130
for (Map.Entry<Enchantment, Integer> enchantment : enchants.entrySet())
125131
{
126-
Map<String, Object> enchantmentData = new HashMap<String, Object>();
132+
Map<String, Object> enchantmentData = new HashMap<>();
127133
enchantmentData.put("id", enchantment.getKey().getId());
128134
enchantmentData.put("lvl", enchantment.getValue());
129135
enchantList.add(enchantmentData);
@@ -136,16 +142,17 @@ static public List<Map<String, Object>> fromEnchantments(Map<Enchantment, Intege
136142
* Returns an NBT-like representation of the item flags (HideFlags).
137143
* It is useful as a fallback if you need item data as an NBT format, but
138144
* the actual NBT couldn't be retrieved for some reason.
145+
*
139146
* @param itemFlags item flag set to get the data from.
140147
* @return an NBT-like representation of the item flags (HideFlags).
141148
*/
142149
static public byte fromItemFlags(Set<ItemFlag> itemFlags)
143150
{
144151
byte flags = 0;
145-
146-
for(ItemFlag flag : itemFlags)
152+
153+
for (ItemFlag flag : itemFlags)
147154
{
148-
switch(flag)
155+
switch (flag)
149156
{
150157
case HIDE_ENCHANTS:
151158
flags += 1; break;
@@ -164,54 +171,203 @@ static public byte fromItemFlags(Set<ItemFlag> itemFlags)
164171

165172
return flags;
166173
}
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+
168261
/* ========== Internal utilities ========== */
169262

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+
172268
static Class getMinecraftClass(String className) throws NMSException
173269
{
174270
try
175271
{
176272
return Reflection.getMinecraftClassByName(className);
177273
}
178-
catch(ClassNotFoundException ex)
274+
catch (ClassNotFoundException ex)
179275
{
180-
throw new NMSException("Unable to find class : " + className, ex);
276+
throw new NMSException("Unable to find class: " + className, ex);
181277
}
182278
}
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+
184292
static private void init() throws NMSException
185293
{
186-
if(MC_ITEM_STACK != null) return;//Already initialized
187-
294+
if (MC_ITEM_STACK != null) return; // Already initialized
295+
188296
MC_ITEM_STACK = getMinecraftClass("ItemStack");
297+
MC_NBT_TAG_COMPOUND = getMinecraftClass("NBTTagCompound");
298+
CB_CRAFT_ITEM_META = getCraftBukkitClass("inventory.CraftMetaItem");
189299
}
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+
*/
191312
static private Object getMcNBTCompound(ItemStack item) throws NMSException
192313
{
193314
Object mcItemStack = ItemUtils.getNMSItemStack(item);
194-
if(mcItemStack == null) return null;
315+
if (mcItemStack == null) return null;
316+
195317
try
196318
{
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;
198336
}
199337
catch (Exception ex)
200338
{
201339
throw new NMSException("Unable to retrieve NBT tag from item", ex);
202340
}
203341
}
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+
*/
205350
static Object fromNativeValue(Object value)
206351
{
207-
if(value == null) return null;
352+
if (value == null) return null;
353+
208354
NBTType type = NBTType.fromClass(value.getClass());
209355
return type.newTag(value);
210356
}
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+
*/
212368
static Object toNativeValue(Object nbtTag)
213369
{
214-
if(nbtTag == null) return null;
370+
if (nbtTag == null) return null;
215371
NBTType type = NBTType.fromNmsNbtTag(nbtTag);
216372

217373
switch(type)
@@ -224,18 +380,19 @@ static Object toNativeValue(Object nbtTag)
224380
return type.getData(nbtTag);
225381
}
226382
}
227-
383+
384+
228385
/* ========== NBT String Utilities ========== */
229386

230387
static private void toNBTJSONString(StringBuilder builder, Object value)
231388
{
232-
if(value == null) return;
389+
if (value == null) return;
233390

234-
if(value instanceof List)
391+
if (value instanceof List)
235392
toNBTJSONString(builder, (List) value);
236-
else if(value instanceof Map)
393+
else if (value instanceof Map)
237394
toNBTJSONString(builder, (Map) value);
238-
else if(value instanceof String)
395+
else if (value instanceof String)
239396
toNBTJSONString(builder, (String) value);
240397
else
241398
builder.append(value.toString());
@@ -260,11 +417,11 @@ static private void toNBTJSONString(StringBuilder builder, List list)
260417
static private void toNBTJSONString(StringBuilder builder, Map<Object, Object> map)
261418
{
262419
builder.append("{");
263-
420+
264421
boolean isFirst = true;
265-
for(Entry<Object, Object> entry : map.entrySet())
422+
for (Entry<Object, Object> entry : map.entrySet())
266423
{
267-
if(!isFirst)
424+
if (!isFirst)
268425
builder.append(",");
269426
builder.append(entry.getKey().toString());
270427
builder.append(':');

0 commit comments

Comments
 (0)