Merge pull request #11 from Minestom/nbt-rewrite

Remove old NBT code, use Hephaistos
This commit is contained in:
Xavier Niochaut 2020-07-06 23:20:54 +02:00 committed by GitHub
commit 7bb8ff9773
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 306 additions and 567 deletions

View File

@ -1,6 +1,7 @@
plugins {
id 'java-library'
id 'net.ltgt.apt' version '0.10'
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
}
allprojects {
@ -71,4 +72,6 @@ dependencies {
// Pathfinding
implementation 'com.github.MadMartian:hydrazine-path-finding:1.1.0'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
api 'com.github.jglrxavpok:Hephaistos:v1.0.3'
}

@ -1 +1 @@
Subproject commit bf55e7257113471e1d6a8116e83edb6cf5011cca
Subproject commit 6e5241db3b4546242d2056b27691e9f7c11dc2d5

View File

@ -9,12 +9,18 @@ import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.command.CommandManager;
import net.minestom.server.data.DataManager;
import net.minestom.server.entity.EntityManager;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.fluids.Fluid;
import net.minestom.server.gamedata.loottables.LootTableManager;
import net.minestom.server.gamedata.tags.TagManager;
import net.minestom.server.instance.Biome;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.Material;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.PacketProcessor;
@ -22,10 +28,14 @@ import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.netty.NettyServer;
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.potion.PotionType;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.ResourceGatherer;
import net.minestom.server.scoreboard.TeamManager;
import net.minestom.server.sound.Sound;
import net.minestom.server.stat.StatisticType;
import net.minestom.server.storage.StorageFolder;
import net.minestom.server.storage.StorageManager;
import net.minestom.server.timer.SchedulerManager;
@ -115,6 +125,21 @@ public class MinecraftServer {
private static MinecraftSessionService sessionService = authService.createMinecraftSessionService();
public static MinecraftServer init() {
// warmup/force-init registries
// without this line, registry types that are not loaded explicitly will have an internal empty registry in Registries
// That can happen with PotionType for instance, if no code tries to access a PotionType field
// TODO: automate (probably with code generation)
Block.values();
Material.values();
PotionType.values();
Enchantment.values();
EntityType.values();
Sound.values();
Particle.values();
StatisticType.values();
Biome.values();
Fluid.values();
connectionManager = new ConnectionManager();
packetProcessor = new PacketProcessor();
packetListenerManager = new PacketListenerManager();

View File

@ -5,6 +5,7 @@ import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.network.packet.PacketReader;
import net.minestom.server.network.packet.PacketWriter;
import org.jglrxavpok.hephaistos.nbt.NBTException;
public class InventoryData extends DataType<Inventory> {

View File

@ -35,8 +35,7 @@ public class DamageType {
}
public RichMessage buildChatMessage(Player killed) {
RichMessage richMessage = RichMessage.of(ColoredText.ofFormat("{@death." + identifier + "}"))
.append(ColoredText.ofFormat(killed.getUsername()));
RichMessage richMessage = RichMessage.of(ColoredText.ofFormat("{@death." + identifier + ","+killed.getUsername()+"}"));
return richMessage;
}

View File

@ -7,8 +7,8 @@ import net.minestom.server.gamedata.loottables.LootTable;
import net.minestom.server.gamedata.loottables.LootTableManager;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.nbt.NbtWriter;
import net.minestom.server.utils.time.UpdateOption;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
/**
* TODO
@ -188,12 +188,14 @@ public abstract class CustomBlock {
}
/**
* Allows custom block to write block entity data to a given NBT compound
* Allows custom block to write block entity data to a given NBT compound.
* Used to send block entity data to the client over the network.
* Can also be used to save block entity data on disk for compatible chunk savers
*
* @param position position of the block
* @param blockData equivalent to <pre>instance.getBlockData(position)</pre>
*/
public void writeBlockEntity(BlockPosition position, Data blockData, NbtWriter nbt) {
public void writeBlockEntity(BlockPosition position, Data blockData, NBTCompound nbt) {
}
/**

View File

@ -6,13 +6,18 @@ import net.minestom.server.item.ItemStack;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.SerializerUtils;
import net.minestom.server.utils.Utils;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTException;
import org.jglrxavpok.hephaistos.nbt.NBTReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
// TODO delete
public class PacketReader {
public class PacketReader extends InputStream {
private ByteBuf buffer;
private NBTReader nbtReader = new NBTReader(this, false);
public PacketReader(ByteBuf buffer) {
this.buffer = buffer;
@ -106,4 +111,13 @@ public class PacketReader {
public ByteBuf getBuffer() {
return buffer;
}
@Override
public int read() {
return readByte();
}
public NBT readTag() throws IOException, NBTException {
return nbtReader.read();
}
}

View File

@ -7,15 +7,20 @@ import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.SerializerUtils;
import net.minestom.server.utils.Utils;
import net.minestom.server.utils.buffer.BufferWrapper;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
public class PacketWriter {
public class PacketWriter extends OutputStream {
private ByteBuf buffer = Unpooled.buffer();
private NBTWriter nbtWriter = new NBTWriter(this, false);
public void writeBoolean(boolean b) {
buffer.writeBoolean(b);
@ -128,6 +133,15 @@ public class PacketWriter {
Utils.writeItemStack(this, itemStack);
}
public void writeNBT(String name, NBT tag) {
try {
nbtWriter.writeNamed("", tag);
} catch (IOException e) {
// should not throw, as nbtWriter points to this PacketWriter
e.printStackTrace();
}
}
public byte[] toByteArray() {
byte[] bytes = new byte[buffer.readableBytes()];
int readerIndex = buffer.readerIndex();
@ -135,4 +149,8 @@ public class PacketWriter {
return bytes;
}
@Override
public void write(int b) {
writeByte((byte) b);
}
}

View File

@ -4,11 +4,11 @@ import net.minestom.server.entity.GameMode;
import net.minestom.server.network.packet.PacketWriter;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.nbt.NbtWriter;
import net.minestom.server.world.Dimension;
import net.minestom.server.world.LevelType;
import static net.minestom.server.utils.nbt.NBT.*;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTList;
import org.jglrxavpok.hephaistos.nbt.NBTTypes;
public class JoinGamePacket implements ServerPacket {
@ -27,7 +27,6 @@ public class JoinGamePacket implements ServerPacket {
@Override
public void write(PacketWriter writer) {
NbtWriter nbtWriter = new NbtWriter(writer);
int gameModeId = gameMode.getId();
if (gameMode.isHardcore())
gameModeId |= 8;
@ -40,23 +39,26 @@ public class JoinGamePacket implements ServerPacket {
//array of worlds
writer.writeVarInt(1);
writer.writeSizedString(identifier);
nbtWriter.writeCompound("", (writer1) -> {
writer1.writeList("dimension", NBT_COMPOUND, 1, () -> {
writer1.writeString("name", "test:normal");
writer1.writeFloat("ambient_light", 1F);
writer1.writeString("infiniburn", "");
writer1.writeByte("natural", (byte) 0x01);
writer1.writeByte("has_ceiling", (byte) 0x01);
writer1.writeByte("has_skylight", (byte) 0x01);
writer1.writeByte("shrunk", (byte) 0x00);
writer1.writeByte("ultrawarm", (byte) 0x00);
writer1.writeByte("has_raids", (byte) 0x00);
writer1.writeByte("respawn_anchor_works", (byte) 0x00);
writer1.writeByte("bed_works", (byte) 0x01);
writer1.writeByte("piglin_safe", (byte) 0x01);
writer1.writeInt("logical_height", 255);
});
});
// TODO: modifiable
NBTCompound dimension = new NBTCompound()
.setString("name", "test:normal")
.setFloat("ambient_light", 1F)
.setString("infiniburn", "")
.setByte("natural", (byte) 0x01)
.setByte("has_ceiling", (byte) 0x01)
.setByte("has_skylight", (byte) 0x01)
.setByte("shrunk", (byte) 0x00)
.setByte("ultrawarm", (byte) 0x00)
.setByte("has_raids", (byte) 0x00)
.setByte("respawn_anchor_works", (byte) 0x00)
.setByte("bed_works", (byte) 0x01)
.setByte("piglin_safe", (byte) 0x01)
.setInt("logical_height", 255)
;
NBTList<NBTCompound> dimensionList = new NBTList<>(NBTTypes.TAG_Compound);
dimensionList.add(dimension);
writer.writeNBT("", new NBTCompound().set("dimension", dimensionList));
//writer.writeInt(dimension.getId());
writer.writeSizedString("test:normal");

View File

@ -15,7 +15,7 @@ import net.minestom.server.utils.Utils;
import net.minestom.server.utils.buffer.BufferUtils;
import net.minestom.server.utils.buffer.BufferWrapper;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.nbt.NbtWriter;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Set;
@ -44,8 +44,6 @@ public class ChunkDataPacket implements ServerPacket {
@Override
public void write(PacketWriter writer) {
NbtWriter nbtWriter = new NbtWriter(writer);
writer.writeInt(chunkX);
writer.writeInt(chunkZ);
writer.writeBoolean(fullChunk);
@ -81,10 +79,11 @@ public class ChunkDataPacket implements ServerPacket {
}
{
nbtWriter.writeCompound("", compound -> {
compound.writeLongArray("MOTION_BLOCKING", Utils.encodeBlocks(motionBlocking, 9));
compound.writeLongArray("WORLD_SURFACE", Utils.encodeBlocks(worldSurface, 9));
});
writer.writeNBT("",
new NBTCompound()
.setLongArray("MOTION_BLOCKING", Utils.encodeBlocks(motionBlocking, 9))
.setLongArray("WORLD_SURFACE", Utils.encodeBlocks(worldSurface, 9))
);
}
// Biome data
@ -104,18 +103,18 @@ public class ChunkDataPacket implements ServerPacket {
for (int index : blockEntities) {
final BlockPosition blockPosition = ChunkUtils.getBlockPosition(index, chunkX, chunkZ);
nbtWriter.writeCompound("", compound -> {
compound.writeDouble("x", blockPosition.getX());
compound.writeDouble("y", blockPosition.getY());
compound.writeDouble("z", blockPosition.getZ());
NBTCompound nbt = new NBTCompound()
.setDouble("x", blockPosition.getX())
.setDouble("y", blockPosition.getY())
.setDouble("z", blockPosition.getZ());
final short customBlockId = customBlocksId[index];
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
if (customBlock != null) {
Data data = blocksData.get(index);
customBlock.writeBlockEntity(blockPosition, data, compound);
}
});
final short customBlockId = customBlocksId[index];
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
if (customBlock != null) {
Data data = blocksData.get(index);
customBlock.writeBlockEntity(blockPosition, data, nbt);
}
writer.writeNBT("", nbt);
}
}

View File

@ -1,23 +1,30 @@
package net.minestom.server.utils;
import io.netty.buffer.ByteBuf;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeOperation;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.instance.Chunk;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.network.packet.PacketReader;
import net.minestom.server.network.packet.PacketWriter;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.buffer.BufferWrapper;
import net.minestom.server.utils.item.NbtReaderUtils;
import net.minestom.server.utils.nbt.NBT;
import net.minestom.server.utils.nbt.NbtWriter;
import org.jglrxavpok.hephaistos.nbt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
public class Utils {
private final static Logger LOGGER = LoggerFactory.getLogger(Utils.class);
public static int getVarIntSize(int input) {
return (input & 0xFFFFFF80) == 0
? 1 : (input & 0xFFFFC000) == 0
@ -110,18 +117,18 @@ public class Utils {
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
0, 5};
private static void writeEnchant(NbtWriter writer, String listName, Map<Enchantment, Short> enchantmentMap) {
writer.writeList(listName, NBT.NBT_COMPOUND, enchantmentMap.size(), () -> {
for (Map.Entry<Enchantment, Short> entry : enchantmentMap.entrySet()) {
Enchantment enchantment = entry.getKey();
short level = entry.getValue();
private static void writeEnchant(NBTCompound nbt, String listName, Map<Enchantment, Short> enchantmentMap) {
NBTList<NBTCompound> enchantList = new NBTList<>(NBTTypes.TAG_Compound);
for (Map.Entry<Enchantment, Short> entry : enchantmentMap.entrySet()) {
Enchantment enchantment = entry.getKey();
short level = entry.getValue();
writer.writeShort("lvl", level);
writer.writeString("id", "minecraft:" + enchantment.name().toLowerCase());
}
});
enchantList.add(new NBTCompound()
.setShort("lvl", level)
.setString("id", "minecraft:" + enchantment.name().toLowerCase())
);
}
nbt.set(listName, enchantList);
}
public static ItemStack readItemStack(PacketReader reader) {
@ -141,18 +148,91 @@ public class Utils {
ItemStack item = new ItemStack((short) id, count);
byte nbt = reader.readByte(); // Should be compound start (0x0A) or 0 if there isn't NBT data
try {
NBT itemNBT = reader.readTag();
if(itemNBT instanceof NBTCompound) { // can also be a TAG_End if no data
NBTCompound nbt = (NBTCompound) itemNBT;
if(nbt.containsKey("Damage")) item.setDamage(nbt.getShort("Damage"));
if(nbt.containsKey("Unbreakable")) item.setUnbreakable(nbt.getInt("Unbreakable") == 1);
if(nbt.containsKey("HideFlags")) item.setHideFlag(nbt.getInt("HideFlags"));
if(nbt.containsKey("Potion")) item.addPotionType(Registries.getPotionType(nbt.getString("Potion")));
if(nbt.containsKey("display")) {
NBTCompound display = nbt.getCompound("display");
if(display.containsKey("Name")) item.setDisplayName(ColoredText.of(display.getString("Name")));
if(display.containsKey("Lore")) {
NBTList<NBTString> loreList = display.getList("Lore");
ArrayList<ColoredText> lore = new ArrayList<>();
for(NBTString s : loreList) {
lore.add(ColoredText.of(s.getValue()));
}
item.setLore(lore);
}
}
if (nbt == 0x00) {
return item;
} else if (nbt == 0x0A) {
reader.readShort(); // Ignored, should be empty (main compound name)
NbtReaderUtils.readItemStackNBT(reader, item);
if(nbt.containsKey("Enchantments")) {
loadEnchantments(nbt.getList("Enchantments"), item::setEnchantment);
}
if(nbt.containsKey("StoredEnchantments")) {
loadEnchantments(nbt.getList("StoredEnchantments"), item::setStoredEnchantment);
}
if(nbt.containsKey("AttributeModifiers")) {
NBTList<NBTCompound> attributes = nbt.getList("AttributeModifiers");
for (NBTCompound attributeNBT : attributes) {
// TODO: 1.16 changed how UUIDs are stored, is this part affected?
long uuidMost = attributeNBT.getLong("UUIDMost");
long uuidLeast = attributeNBT.getLong("UUIDLeast");
UUID uuid = new UUID(uuidMost, uuidLeast);
double value = attributeNBT.getDouble("Amount");
String slot = attributeNBT.getString("Slot");
String attributeName = attributeNBT.getString("AttributeName");
int operation = attributeNBT.getInt("Operation");
String name = attributeNBT.getString("Name");
final Attribute attribute = Attribute.fromKey(attributeName);
// Wrong attribute name, stop here
if (attribute == null)
break;
final AttributeOperation attributeOperation = AttributeOperation.byId(operation);
// Wrong attribute operation, stop here
if (attributeOperation == null)
break;
final AttributeSlot attributeSlot = AttributeSlot.valueOf(slot.toUpperCase());
// Wrong attribute slot, stop here
if (attributeSlot == null)
break;
// Add attribute
final ItemAttribute itemAttribute =
new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot);
item.addAttribute(itemAttribute);
}
}
}
} catch (IOException | NBTException e) {
e.printStackTrace();
}
return item;
}
@FunctionalInterface
private interface EnchantmentSetter {
void applyEnchantment(Enchantment name, short level);
}
private static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
for(NBTCompound enchantment : enchantments) {
short level = enchantment.getShort("lvl");
String id = enchantment.getString("id");
Enchantment enchant = Registries.getEnchantment(id);
if(enchant != null) {
setter.applyEnchantment(enchant, level);
} else {
LOGGER.warn("Unknown enchantment type: "+id);
}
}
}
public static void writeBlocks(BufferWrapper buffer, short[] blocksId, int bitsPerEntry) {
short count = 0;
for (short id : blocksId)
@ -178,28 +258,6 @@ public class Utils {
}
}
/*public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
long maxEntryValue = (1L << bitsPerEntry) - 1;
int length = (int) Math.ceil(blocks.length * bitsPerEntry / 64.0);
long[] data = new long[length];
for (int index = 0; index < blocks.length; index++) {
int value = blocks[index];
int bitIndex = index * bitsPerEntry;
int startIndex = bitIndex / 64;
int endIndex = ((index + 1) * bitsPerEntry - 1) / 64;
int startBitSubIndex = bitIndex % 64;
data[startIndex] = data[startIndex] & ~(maxEntryValue << startBitSubIndex) | ((long) value & maxEntryValue) << startBitSubIndex;
if (startIndex != endIndex) {
int endBitSubIndex = 64 - startBitSubIndex;
data[endIndex] = data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & maxEntryValue) >> endBitSubIndex;
}
}
return data;
}*/
public static void writeItemStack(PacketWriter packet, ItemStack itemStack) {
if (itemStack == null || itemStack.isAir()) {
packet.writeBoolean(false);
@ -209,126 +267,116 @@ public class Utils {
packet.writeByte(itemStack.getAmount());
if (!itemStack.hasNbtTag()) {
packet.writeByte((byte) 0x00); // No nbt
packet.writeByte((byte) NBTTypes.TAG_End); // No nbt
return;
}
NbtWriter mainWriter = new NbtWriter(packet);
NBTCompound itemNBT = new NBTCompound();
mainWriter.writeCompound("", writer -> {
// Unbreakable
if (itemStack.isUnbreakable()) {
writer.writeInt("Unbreakable", 1);
// Unbreakable
if (itemStack.isUnbreakable()) {
itemNBT.setInt("Unbreakable", 1);
}
// Start damage
{
itemNBT.setShort("Damage", itemStack.getDamage());
}
// End damage
// Display
boolean hasDisplayName = itemStack.hasDisplayName();
boolean hasLore = itemStack.hasLore();
if (hasDisplayName || hasLore) {
NBTCompound displayNBT = new NBTCompound();
if (hasDisplayName) {
final String name = itemStack.getDisplayName().toString();
displayNBT.setString("Name", name);
}
// Start damage
{
writer.writeShort("Damage", itemStack.getDamage());
}
// End damage
if (hasLore) {
final ArrayList<ColoredText> lore = itemStack.getLore();
// Display
boolean hasDisplayName = itemStack.hasDisplayName();
boolean hasLore = itemStack.hasLore();
if (hasDisplayName || hasLore) {
writer.writeCompound("display", displayWriter -> {
if (hasDisplayName) {
final String name = itemStack.getDisplayName().toString();
displayWriter.writeString("Name", name);
}
if (hasLore) {
final ArrayList<ColoredText> lore = itemStack.getLore();
displayWriter.writeList("Lore", NBT.NBT_STRING, lore.size(), () -> {
for (ColoredText line : lore) {
packet.writeShortSizedString(line.toString());
}
});
}
});
}
// End display
// Start enchantment
{
Map<Enchantment, Short> enchantmentMap = itemStack.getEnchantmentMap();
if (!enchantmentMap.isEmpty()) {
writeEnchant(writer, "Enchantments", enchantmentMap);
final NBTList<NBTString> loreNBT = new NBTList<>(NBTTypes.TAG_String);
for (ColoredText line : lore) {
loreNBT.add(new NBTString(line.toString()));
}
displayNBT.set("Lore", loreNBT);
}
Map<Enchantment, Short> storedEnchantmentMap = itemStack.getStoredEnchantmentMap();
if (!storedEnchantmentMap.isEmpty()) {
writeEnchant(writer, "StoredEnchantments", storedEnchantmentMap);
itemNBT.set("display", displayNBT);
}
// End display
// Start enchantment
{
Map<Enchantment, Short> enchantmentMap = itemStack.getEnchantmentMap();
if (!enchantmentMap.isEmpty()) {
writeEnchant(itemNBT, "Enchantments", enchantmentMap);
}
Map<Enchantment, Short> storedEnchantmentMap = itemStack.getStoredEnchantmentMap();
if (!storedEnchantmentMap.isEmpty()) {
writeEnchant(itemNBT, "StoredEnchantments", storedEnchantmentMap);
}
}
// End enchantment
// Start attribute
{
List<ItemAttribute> itemAttributes = itemStack.getAttributes();
if (!itemAttributes.isEmpty()) {
NBTList<NBTCompound> attributesNBT = new NBTList<>(NBTTypes.TAG_Compound);
for (ItemAttribute itemAttribute : itemAttributes) {
UUID uuid = itemAttribute.getUuid();
attributesNBT.add(
new NBTCompound()
.setLong("UUIDMost", uuid.getMostSignificantBits())
.setLong("UUIDLeast", uuid.getLeastSignificantBits())
.setDouble("Amount", itemAttribute.getValue())
.setString("Slot", itemAttribute.getSlot().name().toLowerCase())
.setString("itemAttribute", itemAttribute.getAttribute().getKey())
.setInt("Operation", itemAttribute.getOperation().getId())
.setString("Name", itemAttribute.getInternalName())
);
}
itemNBT.set("AttributeModifiers", attributesNBT);
}
}
// End attribute
// Start potion
{
Set<PotionType> potionTypes = itemStack.getPotionTypes();
if (!potionTypes.isEmpty()) {
for (PotionType potionType : potionTypes) {
itemNBT.setString("Potion", potionType.getNamespaceID());
}
}
// End enchantment
}
// End potion
// Start attribute
{
List<ItemAttribute> itemAttributes = itemStack.getAttributes();
if (!itemAttributes.isEmpty()) {
packet.writeByte((byte) 0x09); // Type id (list)
packet.writeShortSizedString("AttributeModifiers");
packet.writeByte((byte) 0x0A); // Compound
packet.writeInt(itemAttributes.size());
for (ItemAttribute itemAttribute : itemAttributes) {
UUID uuid = itemAttribute.getUuid();
writer.writeLong("UUIDMost", uuid.getMostSignificantBits());
writer.writeLong("UUIDLeast", uuid.getLeastSignificantBits());
writer.writeDouble("Amount", itemAttribute.getValue());
writer.writeString("Slot", itemAttribute.getSlot().name().toLowerCase());
writer.writeString("itemAttribute", itemAttribute.getAttribute().getKey());
writer.writeInt("Operation", itemAttribute.getOperation().getId());
writer.writeString("Name", itemAttribute.getInternalName());
}
packet.writeByte((byte) 0x00); // End compound
}
// Start hide flags
{
int hideFlag = itemStack.getHideFlag();
if (hideFlag != 0) {
itemNBT.setInt("HideFlags", hideFlag);
}
// End attribute
}
// End hide flags
// Start potion
{
Set<PotionType> potionTypes = itemStack.getPotionTypes();
if (!potionTypes.isEmpty()) {
for (PotionType potionType : potionTypes) {
packet.writeByte((byte) 0x08); // type id (string)
packet.writeShortSizedString("Potion");
packet.writeShortSizedString("minecraft:" + potionType.name().toLowerCase());
}
}
// Start custom model data
{
int customModelData = itemStack.getCustomModelData();
if (customModelData != 0) {
itemNBT.setInt("CustomModelData", customModelData);
}
// End potion
// Start hide flags
{
int hideFlag = itemStack.getHideFlag();
if (hideFlag != 0) {
writer.writeInt("HideFlags", hideFlag);
}
}
// End hide flags
// Start custom model data
{
int customModelData = itemStack.getCustomModelData();
if (customModelData != 0) {
writer.writeInt("CustomModelData", customModelData);
}
}
// End custom model data
});
}
// End custom model data
packet.writeNBT("", itemNBT);
}
}

View File

@ -1,246 +0,0 @@
package net.minestom.server.utils.item;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeOperation;
import net.minestom.server.chat.ChatParser;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.network.packet.PacketReader;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.Registries;
import java.util.ArrayList;
import java.util.UUID;
public class NbtReaderUtils {
public static void readItemStackNBT(PacketReader reader, ItemStack item) {
byte typeId = reader.readByte();
//System.out.println("DEBUG TYPE: " + typeId);
switch (typeId) {
case 0x00: // TAG_End
// End of item NBT
return;
case 0x01: // TAG_Byte
break;
case 0x02: // TAG_Short
String shortName = reader.readShortSizedString();
// Damage NBT
if (shortName.equals("Damage")) {
short damage = reader.readShort();
item.setDamage(damage);
readItemStackNBT(reader, item);
}
break;
case 0x03: // TAG_Int
String intName = reader.readShortSizedString();
// Damage
if (intName.equals("Damage")) {
int damage = reader.readInteger();
//item.setDamage(damage);
// TODO short vs int damage
readItemStackNBT(reader, item);
}
// Unbreakable
if (intName.equals("Unbreakable")) {
int value = reader.readInteger();
item.setUnbreakable(value == 1);
readItemStackNBT(reader, item);
}
if (intName.equals("HideFlags")) {
int flag = reader.readInteger();
item.setHideFlag(flag);
readItemStackNBT(reader, item);
}
break;
case 0x04: // TAG_Long
break;
case 0x05: // TAG_Float
break;
case 0x06: // TAG_Double
break;
case 0x07: // TAG_Byte_Array
break;
case 0x08: // TAG_String
String stringName = reader.readShortSizedString();
if (stringName.equals("Potion")) {
String potionId = reader.readShortSizedString();
PotionType potionType = Registries.getPotionType(potionId);
item.addPotionType(potionType);
readItemStackNBT(reader, item);
}
break;
case 0x09: // TAG_List
String listName = reader.readShortSizedString();
final boolean isEnchantment = listName.equals("Enchantments");
final boolean isStoredEnchantment = listName.equals("StoredEnchantments");
if (isEnchantment || isStoredEnchantment) {
reader.readByte(); // Should be a compound (0x0A)
int size = reader.readInteger(); // Enchantments count
for (int i = 0; i < size; i++) {
reader.readByte(); // Type id (short)
reader.readShortSizedString(); // Constant "lvl"
short lvl = reader.readShort();
reader.readByte(); // Type id (string)
reader.readShortSizedString(); // Constant "id"
String id = reader.readShortSizedString();
// Convert id
Enchantment enchantment = Registries.getEnchantment(id);
if (isEnchantment) {
item.setEnchantment(enchantment, lvl);
} else if (isStoredEnchantment) {
item.setStoredEnchantment(enchantment, lvl);
}
}
reader.readByte(); // Compound end
readItemStackNBT(reader, item);
}
if (listName.equals("AttributeModifiers")) {
reader.readByte(); // Should be a compound (0x0A);
int size = reader.readInteger(); // Attributes count
for (int i = 0; i < size; i++) {
reader.readByte(); // Type id (long)
reader.readShortSizedString(); // Constant "UUIDMost"
long uuidMost = reader.readLong();
reader.readByte(); // Type id (long)
reader.readShortSizedString(); // Constant "UUIDLeast"
long uuidLeast = reader.readLong();
final UUID uuid = new UUID(uuidMost, uuidLeast);
reader.readByte(); // Type id (double)
reader.readShortSizedString(); // Constant "Amount"
final double value = reader.readDouble();
reader.readByte(); // Type id (string)
reader.readShortSizedString(); // Constant "Slot"
final String slot = reader.readShortSizedString();
reader.readByte(); // Type id (string)
reader.readShortSizedString(); // Constant "AttributeName"
final String attributeName = reader.readShortSizedString();
reader.readByte(); // Type id (int)
reader.readShortSizedString(); // Constant "Operation"
final int operation = reader.readInteger();
reader.readByte(); // Type id (string)
reader.readShortSizedString(); // Constant "Name"
final String name = reader.readShortSizedString();
final Attribute attribute = Attribute.fromKey(attributeName);
// Wrong attribute name, stop here
if (attribute == null)
break;
final AttributeOperation attributeOperation = AttributeOperation.byId(operation);
// Wrong attribute operation, stop here
if (attributeOperation == null)
break;
final AttributeSlot attributeSlot = AttributeSlot.valueOf(slot.toUpperCase());
// Wrong attribute slot, stop here
if (attributeSlot == null)
break;
// Add attribute
final ItemAttribute itemAttribute =
new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot);
item.addAttribute(itemAttribute);
}
reader.readByte(); // Compound end
readItemStackNBT(reader, item);
}
break;
case 0x0A: // TAG_Compound
String compoundName = reader.readShortSizedString();
// Display Compound
if (compoundName.equals("display")) {
readItemStackDisplayNBT(reader, item);
}
break;
}
}
public static void readItemStackDisplayNBT(PacketReader reader, ItemStack item) {
byte typeId = reader.readByte();
switch (typeId) {
case 0x00: // TAG_End
// End of the display compound
readItemStackNBT(reader, item);
break;
case 0x08: // TAG_String
String stringName = reader.readShortSizedString();
if (stringName.equals("Name")) {
String jsonDisplayName = reader.readShortSizedString();
ColoredText displayName = ChatParser.toColoredText(jsonDisplayName);
item.setDisplayName(displayName);
readItemStackDisplayNBT(reader, item);
}
break;
case 0x09: // TAG_List
String listName = reader.readShortSizedString();
if (listName.equals("Lore")) {
reader.readByte(); // lore type, should always be 0x08 (TAG_String)
int size = reader.readInteger();
ArrayList<ColoredText> lore = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
String string = reader.readShortSizedString();
ColoredText text = ChatParser.toColoredText(string);
lore.add(text);
if (lore.size() == size) {
item.setLore(lore);
}
if (i == size - 1) { // Last iteration
readItemStackDisplayNBT(reader, item);
}
}
}
break;
}
}
}

View File

@ -1,18 +0,0 @@
package net.minestom.server.utils.nbt;
public class NBT {
public static final byte NBT_BYTE = 0x01;
public static final byte NBT_SHORT = 0x02;
public static final byte NBT_INT = 0x03;
public static final byte NBT_LONG = 0x04;
public static final byte NBT_FLOAT = 0x05;
public static final byte NBT_DOUBLE = 0x06;
public static final byte NBT_BYTE_ARRAY = 0x07;
public static final byte NBT_STRING = 0x08;
public static final byte NBT_LIST = 0x09;
public static final byte NBT_COMPOUND = 0x0A;
public static final byte NBT_INT_ARRAY = 0x0B;
public static final byte NBT_LONG_ARRAY = 0x0C;
}

View File

@ -1,6 +0,0 @@
package net.minestom.server.utils.nbt;
@FunctionalInterface
public interface NbtConsumer {
void accept(NbtWriter writer);
}

View File

@ -1,102 +0,0 @@
package net.minestom.server.utils.nbt;
import net.minestom.server.network.packet.PacketWriter;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.validate.Check;
import static net.minestom.server.utils.nbt.NBT.*;
public class NbtWriter {
private PacketWriter packet;
public NbtWriter(PacketWriter packet) {
this.packet = packet;
}
public void writeByte(String name, byte value) {
writeHeader(NBT_BYTE, name);
packet.writeByte(value);
}
public void writeShort(String name, short value) {
writeHeader(NBT_SHORT, name);
packet.writeShort(value);
}
public void writeInt(String name, int value) {
writeHeader(NBT_INT, name);
packet.writeInt(value);
}
public void writeLong(String name, long value) {
writeHeader(NBT_LONG, name);
packet.writeLong(value);
}
public void writeFloat(String name, float value) {
writeHeader(NBT_FLOAT, name);
packet.writeFloat(value);
}
public void writeDouble(String name, double value) {
writeHeader(NBT_DOUBLE, name);
packet.writeDouble(value);
}
public void writeByteArray(String name, byte[] value) {
writeHeader(NBT_BYTE_ARRAY, name);
packet.writeInt(value.length);
for (byte val : value) {
packet.writeByte(val);
}
}
public void writeString(String name, String value) {
writeHeader(NBT_STRING, name);
packet.writeShortSizedString(value);
}
public void writeList(String name, byte type, int size, Runnable callback) {
writeHeader(NBT_LIST, name);
packet.writeByte(type);
packet.writeInt(size);
callback.run();
if (type == NBT_COMPOUND)
packet.writeByte((byte) 0x00); // End compount
}
public void writeCompound(String name, NbtConsumer consumer) {
writeHeader(NBT_COMPOUND, name);
consumer.accept(this);
packet.writeByte((byte) 0x00); // End compound
}
public void writeIntArray(String name, int[] value) {
writeHeader(NBT_INT_ARRAY, name);
packet.writeInt(value.length);
for (int val : value) {
packet.writeInt(val);
}
}
public void writeLongArray(String name, long[] value) {
writeHeader(NBT_LONG_ARRAY, name);
packet.writeInt(value.length);
for (long val : value) {
packet.writeLong(val);
}
}
private void writeHeader(byte type, String name) {
Check.argCondition(!MathUtils.isBetween(type, NBT_BYTE, NBT_LONG_ARRAY),
"The NbtTag type " + type + " is not valid");
Check.notNull(name, "The NbtTag name cannot be null");
packet.writeByte(type);
packet.writeShortSizedString(name);
}
public PacketWriter getPacketWriter() {
return packet;
}
}