2020-07-06 23:43:56 +02:00
|
|
|
package net.minestom.server.utils;
|
|
|
|
|
2021-03-11 18:07:04 +01:00
|
|
|
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
2021-03-04 14:20:52 +01:00
|
|
|
import net.kyori.adventure.text.Component;
|
|
|
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
2021-03-11 18:07:04 +01:00
|
|
|
import net.kyori.adventure.util.Codec;
|
2020-11-07 19:39:22 +01:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-06-11 16:11:21 +02:00
|
|
|
import net.minestom.server.adventure.MinestomAdventure;
|
2021-04-02 22:14:48 +02:00
|
|
|
import net.minestom.server.attribute.Attribute;
|
|
|
|
import net.minestom.server.attribute.AttributeOperation;
|
2021-04-11 00:42:09 +02:00
|
|
|
import net.minestom.server.instance.block.Block;
|
2020-07-06 23:43:56 +02:00
|
|
|
import net.minestom.server.inventory.Inventory;
|
2021-04-09 23:57:05 +02:00
|
|
|
import net.minestom.server.item.Enchantment;
|
|
|
|
import net.minestom.server.item.ItemMetaBuilder;
|
|
|
|
import net.minestom.server.item.ItemStack;
|
|
|
|
import net.minestom.server.item.Material;
|
2021-04-02 22:14:48 +02:00
|
|
|
import net.minestom.server.item.attribute.AttributeSlot;
|
2020-07-06 23:43:56 +02:00
|
|
|
import net.minestom.server.item.attribute.ItemAttribute;
|
|
|
|
import net.minestom.server.registry.Registries;
|
2020-08-19 20:34:21 +02:00
|
|
|
import net.minestom.server.utils.binary.BinaryReader;
|
2021-03-23 13:29:07 +01:00
|
|
|
import org.jetbrains.annotations.Contract;
|
2020-11-07 19:39:22 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2020-07-06 23:43:56 +02:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.*;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2021-04-11 00:42:09 +02:00
|
|
|
import java.util.*;
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
// for lack of a better name
|
2020-08-07 08:10:10 +02:00
|
|
|
public final class NBTUtils {
|
2020-07-13 14:36:39 +02:00
|
|
|
private final static Logger LOGGER = LoggerFactory.getLogger(NBTUtils.class);
|
2020-07-06 23:43:56 +02:00
|
|
|
|
2021-03-11 18:07:04 +01:00
|
|
|
/**
|
|
|
|
* An Adventure codec to convert between NBT and SNBT.
|
2021-06-11 16:11:21 +02:00
|
|
|
*
|
|
|
|
* @deprecated Use {@link MinestomAdventure#NBT_CODEC}
|
2021-03-11 18:07:04 +01:00
|
|
|
*/
|
2021-06-11 16:11:21 +02:00
|
|
|
@Deprecated(forRemoval = true)
|
|
|
|
public static final Codec<NBT, String, NBTException, RuntimeException> SNBT_CODEC = MinestomAdventure.NBT_CODEC;
|
2021-03-11 18:07:04 +01:00
|
|
|
|
2020-08-07 08:10:10 +02:00
|
|
|
private NBTUtils() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-03-11 18:07:04 +01:00
|
|
|
/**
|
|
|
|
* Turns an {@link NBTCompound} into an Adventure {@link BinaryTagHolder}.
|
2021-04-02 18:13:02 +02:00
|
|
|
*
|
2021-03-11 18:07:04 +01:00
|
|
|
* @param tag the tag, if any
|
|
|
|
* @return the binary tag holder, or {@code null} if the tag was null
|
|
|
|
*/
|
2021-03-23 13:29:07 +01:00
|
|
|
@Contract("null -> null; !null -> !null")
|
|
|
|
public static BinaryTagHolder asBinaryTagHolder(@Nullable NBTCompound tag) {
|
2021-03-11 18:07:04 +01:00
|
|
|
if (tag == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-06-11 16:11:21 +02:00
|
|
|
return BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC);
|
2021-03-11 18:07:04 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 23:43:56 +02:00
|
|
|
/**
|
|
|
|
* Loads all the items from the 'items' list into the given inventory
|
2020-07-09 15:51:39 +02:00
|
|
|
*
|
2020-08-07 09:14:50 +02:00
|
|
|
* @param items the items to save
|
|
|
|
* @param destination the inventory destination
|
2020-07-06 23:43:56 +02:00
|
|
|
*/
|
2020-11-07 19:39:22 +01:00
|
|
|
public static void loadAllItems(@NotNull NBTList<NBTCompound> items, @NotNull Inventory destination) {
|
2020-07-06 23:43:56 +02:00
|
|
|
destination.clear();
|
2020-07-09 15:51:39 +02:00
|
|
|
for (NBTCompound tag : items) {
|
2021-07-27 09:55:01 +02:00
|
|
|
Material material = Material.fromNamespaceId(tag.getString("id"));
|
2021-04-02 22:14:48 +02:00
|
|
|
if (material == Material.AIR) {
|
|
|
|
material = Material.STONE;
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
2021-04-02 22:14:48 +02:00
|
|
|
byte count = tag.getByte("Count");
|
|
|
|
NBTCompound nbtCompound = null;
|
2020-07-09 15:51:39 +02:00
|
|
|
if (tag.containsKey("tag")) {
|
2021-04-02 22:14:48 +02:00
|
|
|
nbtCompound = tag.getCompound("tag");
|
2020-07-06 23:50:32 +02:00
|
|
|
}
|
2021-04-18 04:44:16 +02:00
|
|
|
ItemStack itemStack = ItemStack.fromNBT(material, nbtCompound, count);
|
2021-04-02 22:14:48 +02:00
|
|
|
destination.setItemStack(tag.getByte("Slot"), itemStack);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-07 19:39:22 +01:00
|
|
|
public static void saveAllItems(@NotNull NBTList<NBTCompound> list, @NotNull Inventory inventory) {
|
2020-07-06 23:43:56 +02:00
|
|
|
for (int i = 0; i < inventory.getSize(); i++) {
|
2020-07-23 07:36:49 +02:00
|
|
|
final ItemStack stack = inventory.getItemStack(i);
|
2020-07-06 23:43:56 +02:00
|
|
|
NBTCompound nbt = new NBTCompound();
|
|
|
|
|
2021-04-10 00:24:29 +02:00
|
|
|
NBTCompound tag = stack.getMeta().toNBT();
|
2020-07-06 23:50:32 +02:00
|
|
|
|
|
|
|
nbt.set("tag", tag);
|
2020-07-06 23:43:56 +02:00
|
|
|
nbt.setByte("Slot", (byte) i);
|
2021-04-02 18:13:02 +02:00
|
|
|
nbt.setByte("Count", (byte) stack.getAmount());
|
2021-07-27 09:40:57 +02:00
|
|
|
nbt.setString("id", stack.getMaterial().name());
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
list.add(nbt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-07 19:39:22 +01:00
|
|
|
public static void writeEnchant(@NotNull NBTCompound nbt, @NotNull String listName,
|
|
|
|
@NotNull Map<Enchantment, Short> enchantmentMap) {
|
2020-07-06 23:43:56 +02:00
|
|
|
NBTList<NBTCompound> enchantList = new NBTList<>(NBTTypes.TAG_Compound);
|
|
|
|
for (Map.Entry<Enchantment, Short> entry : enchantmentMap.entrySet()) {
|
2020-07-23 07:36:49 +02:00
|
|
|
final Enchantment enchantment = entry.getKey();
|
|
|
|
final short level = entry.getValue();
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
enchantList.add(new NBTCompound()
|
|
|
|
.setShort("lvl", level)
|
|
|
|
.setString("id", "minecraft:" + enchantment.name().toLowerCase())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
nbt.set(listName, enchantList);
|
|
|
|
}
|
|
|
|
|
2021-04-02 18:13:02 +02:00
|
|
|
@NotNull
|
2020-11-07 19:39:22 +01:00
|
|
|
public static ItemStack readItemStack(@NotNull BinaryReader reader) {
|
2020-07-23 05:36:15 +02:00
|
|
|
final boolean present = reader.readBoolean();
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
if (!present) {
|
2021-04-02 18:13:02 +02:00
|
|
|
return ItemStack.AIR;
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-23 05:36:15 +02:00
|
|
|
final int id = reader.readVarInt();
|
2020-07-06 23:43:56 +02:00
|
|
|
if (id == -1) {
|
|
|
|
// Drop mode
|
2021-04-02 18:13:02 +02:00
|
|
|
return ItemStack.AIR;
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-23 05:36:15 +02:00
|
|
|
final Material material = Material.fromId((short) id);
|
|
|
|
final byte count = reader.readByte();
|
2021-04-02 22:14:48 +02:00
|
|
|
NBTCompound nbtCompound = null;
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
try {
|
2020-07-23 05:36:15 +02:00
|
|
|
final NBT itemNBT = reader.readTag();
|
2020-07-09 15:51:39 +02:00
|
|
|
if (itemNBT instanceof NBTCompound) { // can also be a TAG_End if no data
|
2021-04-02 22:14:48 +02:00
|
|
|
nbtCompound = (NBTCompound) itemNBT;
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
} catch (IOException | NBTException e) {
|
2021-01-19 18:25:54 +01:00
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
|
2021-04-18 04:44:16 +02:00
|
|
|
return ItemStack.fromNBT(material, nbtCompound, count);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
|
2021-02-28 19:37:31 +01:00
|
|
|
@SuppressWarnings("ConstantConditions")
|
2021-04-02 22:14:48 +02:00
|
|
|
public static void loadDataIntoMeta(@NotNull ItemMetaBuilder metaBuilder, @NotNull NBTCompound nbt) {
|
|
|
|
if (nbt.containsKey("Damage")) metaBuilder.damage(nbt.getInt("Damage"));
|
|
|
|
if (nbt.containsKey("Unbreakable")) metaBuilder.unbreakable(nbt.getAsByte("Unbreakable") == 1);
|
|
|
|
if (nbt.containsKey("HideFlags")) metaBuilder.hideFlag(nbt.getInt("HideFlags"));
|
2020-07-09 15:51:39 +02:00
|
|
|
if (nbt.containsKey("display")) {
|
2020-10-16 14:31:15 +02:00
|
|
|
final NBTCompound display = nbt.getCompound("display");
|
|
|
|
if (display.containsKey("Name")) {
|
2021-06-06 14:21:25 +02:00
|
|
|
final String rawName = StringUtils.unescapeJavaString(display.getString("Name"));
|
2021-03-04 14:20:52 +01:00
|
|
|
final Component displayName = GsonComponentSerializer.gson().deserialize(rawName);
|
2021-04-02 22:14:48 +02:00
|
|
|
metaBuilder.displayName(displayName);
|
2020-10-16 14:31:15 +02:00
|
|
|
}
|
2020-07-09 15:51:39 +02:00
|
|
|
if (display.containsKey("Lore")) {
|
2020-07-06 23:43:56 +02:00
|
|
|
NBTList<NBTString> loreList = display.getList("Lore");
|
2021-03-04 14:20:52 +01:00
|
|
|
List<Component> lore = new ArrayList<>();
|
2020-07-09 15:51:39 +02:00
|
|
|
for (NBTString s : loreList) {
|
2021-06-06 14:21:25 +02:00
|
|
|
final String rawLore = StringUtils.unescapeJavaString(s.getValue());
|
|
|
|
lore.add(GsonComponentSerializer.gson().deserialize(rawLore));
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
2021-04-02 22:14:48 +02:00
|
|
|
metaBuilder.lore(lore);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 20:26:17 +01:00
|
|
|
// Enchantments
|
2020-07-09 15:51:39 +02:00
|
|
|
if (nbt.containsKey("Enchantments")) {
|
2021-04-02 22:14:48 +02:00
|
|
|
loadEnchantments(nbt.getList("Enchantments"), metaBuilder::enchantment);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
2020-08-01 00:21:03 +02:00
|
|
|
|
2021-01-04 20:26:17 +01:00
|
|
|
// Attributes
|
2020-07-09 15:51:39 +02:00
|
|
|
if (nbt.containsKey("AttributeModifiers")) {
|
2021-04-02 22:14:48 +02:00
|
|
|
List<ItemAttribute> attributes = new ArrayList<>();
|
|
|
|
NBTList<NBTCompound> nbtAttributes = nbt.getList("AttributeModifiers");
|
|
|
|
for (NBTCompound attributeNBT : nbtAttributes) {
|
2020-10-04 03:04:51 +02:00
|
|
|
final UUID uuid;
|
|
|
|
{
|
|
|
|
final int[] uuidArray = attributeNBT.getIntArray("UUID");
|
|
|
|
uuid = Utils.intArrayToUuid(uuidArray);
|
|
|
|
}
|
2021-02-20 17:35:02 +01:00
|
|
|
|
2021-02-20 23:54:25 +01:00
|
|
|
final double value = attributeNBT.getAsDouble("Amount");
|
2021-02-20 17:35:02 +01:00
|
|
|
final String slot = attributeNBT.containsKey("Slot") ? attributeNBT.getString("Slot") : "MAINHAND";
|
2020-08-01 11:00:31 +02:00
|
|
|
final String attributeName = attributeNBT.getString("AttributeName");
|
2021-02-20 23:54:25 +01:00
|
|
|
final int operation = attributeNBT.getAsInt("Operation");
|
2020-08-01 11:00:31 +02:00
|
|
|
final String name = attributeNBT.getString("Name");
|
2020-07-06 23:43:56 +02:00
|
|
|
|
|
|
|
final Attribute attribute = Attribute.fromKey(attributeName);
|
|
|
|
// Wrong attribute name, stop here
|
|
|
|
if (attribute == null)
|
|
|
|
break;
|
2020-09-29 21:45:16 +02:00
|
|
|
final AttributeOperation attributeOperation = AttributeOperation.fromId(operation);
|
2020-07-06 23:43:56 +02:00
|
|
|
// Wrong attribute operation, stop here
|
2020-09-29 21:45:16 +02:00
|
|
|
if (attributeOperation == null) {
|
2020-07-06 23:43:56 +02:00
|
|
|
break;
|
2020-09-29 21:45:16 +02:00
|
|
|
}
|
2021-02-20 17:35:02 +01:00
|
|
|
|
2021-02-22 14:06:47 +01:00
|
|
|
// Find slot, default to the main hand if the nbt tag is invalid
|
|
|
|
AttributeSlot attributeSlot;
|
|
|
|
try {
|
|
|
|
attributeSlot = AttributeSlot.valueOf(slot.toUpperCase());
|
|
|
|
} catch (IllegalArgumentException e) {
|
2021-02-21 16:56:06 +01:00
|
|
|
attributeSlot = AttributeSlot.MAINHAND;
|
2021-02-22 14:06:47 +01:00
|
|
|
}
|
2021-02-21 16:56:06 +01:00
|
|
|
|
2020-07-06 23:43:56 +02:00
|
|
|
// Add attribute
|
|
|
|
final ItemAttribute itemAttribute =
|
|
|
|
new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot);
|
2021-04-02 22:14:48 +02:00
|
|
|
attributes.add(itemAttribute);
|
2021-01-04 20:26:17 +01:00
|
|
|
}
|
2021-04-02 22:14:48 +02:00
|
|
|
metaBuilder.attributes(attributes);
|
2021-01-04 20:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Custom model data
|
|
|
|
{
|
|
|
|
if (nbt.containsKey("CustomModelData")) {
|
2021-04-02 22:14:48 +02:00
|
|
|
metaBuilder.customModelData(nbt.getInt("CustomModelData"));
|
2021-01-04 20:26:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 22:14:48 +02:00
|
|
|
// Meta specific fields
|
|
|
|
metaBuilder.read(nbt);
|
2020-11-07 19:39:22 +01:00
|
|
|
|
2021-04-11 00:42:09 +02:00
|
|
|
// CanPlaceOn
|
2020-11-21 06:37:44 +01:00
|
|
|
{
|
2021-02-28 19:37:31 +01:00
|
|
|
if (nbt.containsKey("CanPlaceOn")) {
|
|
|
|
NBTList<NBTString> canPlaceOn = nbt.getList("CanPlaceOn");
|
2021-04-11 00:42:09 +02:00
|
|
|
Set<Block> blocks = new HashSet<>();
|
|
|
|
for (NBTString blockNamespace : canPlaceOn) {
|
2021-05-24 21:39:30 +02:00
|
|
|
Block block = Block.fromNamespaceId(blockNamespace.getValue());
|
2021-04-11 00:42:09 +02:00
|
|
|
blocks.add(block);
|
|
|
|
}
|
|
|
|
metaBuilder.canPlaceOn(blocks);
|
2021-02-28 19:37:31 +01:00
|
|
|
}
|
|
|
|
}
|
2021-04-11 00:42:09 +02:00
|
|
|
// CanDestroy
|
2021-02-28 19:37:31 +01:00
|
|
|
{
|
|
|
|
if (nbt.containsKey("CanDestroy")) {
|
2021-04-11 00:42:09 +02:00
|
|
|
NBTList<NBTString> canDestroy = nbt.getList("CanDestroy");
|
|
|
|
Set<Block> blocks = new HashSet<>();
|
|
|
|
for (NBTString blockNamespace : canDestroy) {
|
2021-05-24 21:39:30 +02:00
|
|
|
Block block = Block.fromNamespaceId(blockNamespace.getValue());
|
2021-04-11 00:42:09 +02:00
|
|
|
blocks.add(block);
|
|
|
|
}
|
|
|
|
metaBuilder.canDestroy(blocks);
|
2021-02-28 19:37:31 +01:00
|
|
|
}
|
2021-04-11 00:42:09 +02:00
|
|
|
}
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
|
2020-08-01 00:21:03 +02:00
|
|
|
public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
|
2020-07-09 15:51:39 +02:00
|
|
|
for (NBTCompound enchantment : enchantments) {
|
2020-10-16 14:31:15 +02:00
|
|
|
final short level = enchantment.getAsShort("lvl");
|
2020-07-23 07:36:49 +02:00
|
|
|
final String id = enchantment.getString("id");
|
|
|
|
final Enchantment enchant = Registries.getEnchantment(id);
|
2020-07-09 15:51:39 +02:00
|
|
|
if (enchant != null) {
|
2020-07-06 23:43:56 +02:00
|
|
|
setter.applyEnchantment(enchant, level);
|
|
|
|
} else {
|
2020-12-14 05:55:48 +01:00
|
|
|
LOGGER.warn("Unknown enchantment type: {}", id);
|
2020-07-06 23:43:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@FunctionalInterface
|
2020-08-01 00:21:03 +02:00
|
|
|
public interface EnchantmentSetter {
|
2020-07-06 23:43:56 +02:00
|
|
|
void applyEnchantment(Enchantment name, short level);
|
|
|
|
}
|
|
|
|
}
|