Minestom/src/main/java/net/minestom/server/utils/NBTUtils.java

271 lines
10 KiB
Java
Raw Normal View History

package net.minestom.server.utils;
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;
import net.kyori.adventure.util.Codec;
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;
import net.minestom.server.inventory.Inventory;
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;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.*;
// for lack of a better name
public final class NBTUtils {
private final static Logger LOGGER = LoggerFactory.getLogger(NBTUtils.class);
/**
* An Adventure codec to convert between NBT and SNBT.
2021-06-11 16:11:21 +02:00
*
* @deprecated Use {@link MinestomAdventure#NBT_CODEC}
*/
2021-06-11 16:11:21 +02:00
@Deprecated(forRemoval = true)
public static final Codec<NBT, String, NBTException, RuntimeException> SNBT_CODEC = MinestomAdventure.NBT_CODEC;
private NBTUtils() {
}
/**
* Turns an {@link NBTCompound} into an Adventure {@link BinaryTagHolder}.
2021-04-02 18:13:02 +02:00
*
* @param tag the tag, if any
* @return the binary tag holder, or {@code null} if the tag was null
*/
@Contract("null -> null; !null -> !null")
public static BinaryTagHolder asBinaryTagHolder(@Nullable NBTCompound tag) {
if (tag == null) {
return null;
}
2021-06-11 16:11:21 +02:00
return BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC);
}
/**
* 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
*/
public static void loadAllItems(@NotNull NBTList<NBTCompound> items, @NotNull Inventory destination) {
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;
}
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");
}
ItemStack itemStack = ItemStack.fromNBT(material, nbtCompound, count);
2021-04-02 22:14:48 +02:00
destination.setItemStack(tag.getByte("Slot"), itemStack);
}
}
public static void saveAllItems(@NotNull NBTList<NBTCompound> list, @NotNull Inventory inventory) {
for (int i = 0; i < inventory.getSize(); i++) {
2020-07-23 07:36:49 +02:00
final ItemStack stack = inventory.getItemStack(i);
NBTCompound nbt = new NBTCompound();
NBTCompound tag = stack.getMeta().toNBT();
nbt.set("tag", tag);
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());
list.add(nbt);
}
}
public static void writeEnchant(@NotNull NBTCompound nbt, @NotNull String listName,
@NotNull Map<Enchantment, Short> enchantmentMap) {
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();
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
public static ItemStack readItemStack(@NotNull BinaryReader reader) {
2020-07-23 05:36:15 +02:00
final boolean present = reader.readBoolean();
if (!present) {
2021-04-02 18:13:02 +02:00
return ItemStack.AIR;
}
2020-07-23 05:36:15 +02:00
final int id = reader.readVarInt();
if (id == -1) {
// Drop mode
2021-04-02 18:13:02 +02:00
return ItemStack.AIR;
}
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;
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;
}
} catch (IOException | NBTException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
return ItemStack.fromNBT(material, nbtCompound, count);
}
@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")) {
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));
}
2021-04-02 22:14:48 +02:00
metaBuilder.lore(lore);
}
}
// 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-08-01 00:21:03 +02: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) {
final UUID uuid;
{
final int[] uuidArray = attributeNBT.getIntArray("UUID");
uuid = Utils.intArrayToUuid(uuidArray);
}
2021-02-20 23:54:25 +01:00
final double value = attributeNBT.getAsDouble("Amount");
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");
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);
// Wrong attribute operation, stop here
2020-09-29 21:45:16 +02:00
if (attributeOperation == null) {
break;
2020-09-29 21:45:16 +02: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) {
attributeSlot = AttributeSlot.MAINHAND;
2021-02-22 14:06:47 +01: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-04-02 22:14:48 +02:00
metaBuilder.attributes(attributes);
}
// Custom model data
{
if (nbt.containsKey("CustomModelData")) {
2021-04-02 22:14:48 +02:00
metaBuilder.customModelData(nbt.getInt("CustomModelData"));
}
}
2021-04-02 22:14:48 +02:00
// Meta specific fields
metaBuilder.read(nbt);
2021-04-11 00:42:09 +02:00
// CanPlaceOn
{
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-04-11 00:42:09 +02:00
// CanDestroy
{
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-04-11 00:42:09 +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) {
setter.applyEnchantment(enchant, level);
} else {
2020-12-14 05:55:48 +01:00
LOGGER.warn("Unknown enchantment type: {}", id);
}
}
}
@FunctionalInterface
2020-08-01 00:21:03 +02:00
public interface EnchantmentSetter {
void applyEnchantment(Enchantment name, short level);
}
}