Item API refactoring (#904)

This commit is contained in:
TheMode 2022-04-13 17:57:15 +02:00 committed by GitHub
parent 0875becdc7
commit 63b40deb34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1110 additions and 1818 deletions

View File

@ -49,9 +49,9 @@ public class CampfireHandler implements BlockHandler {
NBTType.TAG_Compound,
value.stream()
.map(item -> NBT.Compound(nbt -> {
nbt.setByte("Count", (byte) item.getAmount());
nbt.setByte("Count", (byte) item.amount());
nbt.setByte("Slot", (byte) 1);
nbt.setString("id", item.getMaterial().name());
nbt.setString("id", item.material().name());
}))
.toList()
));

View File

@ -211,7 +211,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
PickupItemEvent pickupItemEvent = new PickupItemEvent(this, itemEntity);
EventDispatcher.callCancellable(pickupItemEvent, () -> {
final ItemStack item = itemEntity.getItemStack();
sendPacketToViewersAndSelf(new CollectItemPacket(itemEntity.getEntityId(), getEntityId(), item.getAmount()));
sendPacketToViewersAndSelf(new CollectItemPacket(itemEntity.getEntityId(), getEntityId(), item.amount()));
itemEntity.remove();
});
}

View File

@ -264,7 +264,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
for (var player : MinecraftServer.getConnectionManager().getOnlinePlayers()) {
if (player != this) sendPacket(player.getAddPlayerToList());
}
//Teams
for (Team team : MinecraftServer.getTeamManager().getTeams()) {
sendPacket(team.createTeamsCreationPacket());
@ -355,7 +355,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
refreshActiveHand(false, isOffHand, false);
final ItemStack foodItem = itemUpdateStateEvent.getItemStack();
final boolean isFood = foodItem.getMaterial().isFood();
final boolean isFood = foodItem.material().isFood();
if (isFood) {
PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem, eatingHand);
@ -747,7 +747,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void openBook(@NotNull Book book) {
final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
.meta(WrittenBookMeta.fromAdventure(book, this))
.meta(WrittenBookMeta.class, builder -> builder.resolved(false)
.generation(WrittenBookMeta.WrittenBookGeneration.ORIGINAL)
.author(book.author())
.title(book.title())
.pages(book.pages()))
.build();
// Set book in offhand
playerConnection.sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook));
@ -1814,7 +1818,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
return null;
final ItemStack updatedItem = getItemInHand(hand);
final boolean isFood = updatedItem.getMaterial().isFood();
final boolean isFood = updatedItem.material().isFood();
if (isFood && !allowFood)
return null;
@ -2098,9 +2102,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
return mainHand;
}
public boolean enableTextFiltering() { return enableTextFiltering; }
public boolean enableTextFiltering() {
return enableTextFiltering;
}
public boolean allowServerListings() { return allowServerListings; }
public boolean allowServerListings() {
return allowServerListings;
}
/**
* Changes the player settings internally.

View File

@ -126,7 +126,7 @@ public final class InventoryClickProcessor {
// Handle armor equip
if (inventory instanceof PlayerInventory && targetInventory instanceof PlayerInventory) {
final Material material = clicked.getMaterial();
final Material material = clicked.material();
final EquipmentSlot equipmentSlot = material.registry().equipmentSlot();
if (equipmentSlot != null) {
// Shift-click equip

View File

@ -1,7 +1,7 @@
package net.minestom.server.item;
/**
* Represents a hide flag which can be applied to an {@link ItemStack} using {@link ItemMetaBuilder#hideFlag(int)}.
* Represents a hide flag which can be applied to an {@link ItemStack} using {@link ItemMeta.Builder#hideFlag(int)}.
*/
public enum ItemHideFlag {
HIDE_ENCHANTS,

View File

@ -5,7 +5,7 @@ import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.tag.Taggable;
import net.minestom.server.utils.binary.Writeable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -13,162 +13,161 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;
public class ItemMeta implements TagReadable, Writeable {
private final int damage;
private final boolean unbreakable;
private final int hideFlag;
private final Component displayName;
private final List<Component> lore;
private final Map<Enchantment, Short> enchantmentMap;
private final List<ItemAttribute> attributes;
private final int customModelData;
private final Set<Block> canDestroy;
private final Set<Block> canPlaceOn;
private final ItemMetaBuilder metaBuilder;
private final NBTCompound nbt;
private String cachedSNBT;
private ByteBuffer cachedBuffer;
protected ItemMeta(@NotNull ItemMetaBuilder metaBuilder) {
this.damage = metaBuilder.damage;
this.unbreakable = metaBuilder.unbreakable;
this.hideFlag = metaBuilder.hideFlag;
this.displayName = metaBuilder.displayName;
this.lore = List.copyOf(metaBuilder.lore);
this.enchantmentMap = Map.copyOf(metaBuilder.enchantmentMap);
this.attributes = List.copyOf(metaBuilder.attributes);
this.customModelData = metaBuilder.customModelData;
this.canDestroy = Set.copyOf(metaBuilder.canDestroy);
this.canPlaceOn = Set.copyOf(metaBuilder.canPlaceOn);
this.metaBuilder = metaBuilder;
this.nbt = metaBuilder.nbt.toCompound();
}
public sealed interface ItemMeta extends TagReadable, Writeable
permits ItemMetaImpl {
@Override
<T> @UnknownNullability T getTag(@NotNull Tag<T> tag);
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemMeta with(@NotNull Consumer<@NotNull ItemMetaBuilder> builderConsumer) {
var builder = builder();
builderConsumer.accept(builder);
return builder.build();
@NotNull ItemMeta with(@NotNull Consumer<@NotNull Builder> builderConsumer);
@NotNull NBTCompound toNBT();
@NotNull String toSNBT();
@Contract(pure = true)
default int getDamage() {
return getTag(ItemTags.DAMAGE);
}
@Contract(pure = true)
public int getDamage() {
return damage;
default boolean isUnbreakable() {
return getTag(ItemTags.UNBREAKABLE);
}
@Contract(pure = true)
public boolean isUnbreakable() {
return unbreakable;
default int getHideFlag() {
return getTag(ItemTags.HIDE_FLAGS);
}
@Contract(pure = true)
public int getHideFlag() {
return hideFlag;
default @Nullable Component getDisplayName() {
return getTag(ItemTags.NAME);
}
@Contract(pure = true)
public @Nullable Component getDisplayName() {
return displayName;
default @NotNull List<@NotNull Component> getLore() {
return getTag(ItemTags.LORE);
}
@Contract(pure = true)
public @NotNull List<@NotNull Component> getLore() {
return lore;
default @NotNull Map<Enchantment, Short> getEnchantmentMap() {
return getTag(ItemTags.ENCHANTMENTS);
}
@Contract(pure = true)
public @NotNull Map<Enchantment, Short> getEnchantmentMap() {
return enchantmentMap;
default @NotNull List<@NotNull ItemAttribute> getAttributes() {
return getTag(ItemTags.ATTRIBUTES);
}
@Contract(pure = true)
public @NotNull List<@NotNull ItemAttribute> getAttributes() {
return attributes;
default int getCustomModelData() {
return getTag(ItemTags.CUSTOM_MODEL_DATA);
}
@Contract(pure = true)
public int getCustomModelData() {
return customModelData;
default @NotNull Set<@NotNull Block> getCanDestroy() {
return Set.copyOf(getTag(ItemTags.CAN_DESTROY));
}
@Contract(pure = true)
public @NotNull Set<@NotNull Block> getCanDestroy() {
return canDestroy;
default @NotNull Set<@NotNull Block> getCanPlaceOn() {
return Set.copyOf(getTag(ItemTags.CAN_PLACE_ON));
}
@Contract(pure = true)
public @NotNull Set<@NotNull Block> getCanPlaceOn() {
return canPlaceOn;
}
sealed interface Builder extends Taggable permits ItemMetaImpl.Builder {
@NotNull ItemMeta build();
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbt);
}
public @NotNull NBTCompound toNBT() {
return nbt;
}
public @NotNull String toSNBT() {
if (cachedSNBT == null) {
this.cachedSNBT = nbt.toSNBT();
default <T> @NotNull Builder set(@NotNull Tag<T> tag, @Nullable T value) {
setTag(tag, value);
return this;
}
return cachedSNBT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemMeta itemMeta = (ItemMeta) o;
return nbt.equals(itemMeta.nbt);
}
@Override
public int hashCode() {
return nbt.hashCode();
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"nbt=" + nbt +
'}';
}
@Contract(value = "-> new", pure = true)
protected @NotNull ItemMetaBuilder builder() {
ItemMetaBuilder result = metaBuilder.createEmpty();
ItemMetaBuilder.resetMeta(result, nbt);
return result;
}
@Override
public synchronized void write(@NotNull BinaryWriter writer) {
if (nbt.isEmpty()) {
writer.writeByte((byte) 0);
return;
@Contract("_ -> this")
default @NotNull Builder damage(int damage) {
return set(ItemTags.DAMAGE, damage);
}
if (cachedBuffer == null) {
BinaryWriter w = new BinaryWriter();
w.writeNBT("", nbt);
this.cachedBuffer = w.getBuffer();
@Contract("_ -> this")
default @NotNull Builder unbreakable(boolean unbreakable) {
return set(ItemTags.UNBREAKABLE, unbreakable);
}
@Contract("_ -> this")
default @NotNull Builder hideFlag(int hideFlag) {
return set(ItemTags.HIDE_FLAGS, hideFlag);
}
@Contract("_ -> this")
default @NotNull Builder hideFlag(@NotNull ItemHideFlag... hideFlags) {
int result = 0;
for (ItemHideFlag hideFlag : hideFlags) result |= hideFlag.getBitFieldPart();
return hideFlag(result);
}
@Contract("_ -> this")
default @NotNull Builder displayName(@Nullable Component displayName) {
return set(ItemTags.NAME, displayName);
}
@Contract("_ -> this")
default @NotNull Builder lore(@NotNull List<? extends Component> lore) {
return set(ItemTags.LORE, lore.isEmpty() ? null : List.class.cast(lore));
}
@Contract("_ -> this")
default @NotNull Builder lore(Component... lore) {
return lore(Arrays.asList(lore));
}
@Contract("_ -> this")
default @NotNull Builder enchantments(@NotNull Map<Enchantment, Short> enchantments) {
return set(ItemTags.ENCHANTMENTS, Map.copyOf(enchantments));
}
@Contract("_, _ -> this")
default @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) {
var enchantments = new HashMap<>(getTag(ItemTags.ENCHANTMENTS));
enchantments.put(enchantment, level);
return enchantments(enchantments);
}
@Contract("-> this")
default @NotNull Builder clearEnchantment() {
return enchantments(Map.of());
}
@Contract("_ -> this")
default @NotNull Builder attributes(@NotNull List<@NotNull ItemAttribute> attributes) {
return set(ItemTags.ATTRIBUTES, attributes.isEmpty() ? null : attributes);
}
@Contract("_ -> this")
default @NotNull Builder customModelData(int customModelData) {
return set(ItemTags.CUSTOM_MODEL_DATA, customModelData);
}
@Contract("_ -> this")
default @NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) {
return set(ItemTags.CAN_PLACE_ON, List.copyOf(blocks));
}
@Contract("_ -> this")
default @NotNull Builder canPlaceOn(@NotNull Block... blocks) {
return canPlaceOn(Set.of(blocks));
}
@Contract("_ -> this")
default @NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) {
return set(ItemTags.CAN_DESTROY, List.copyOf(blocks));
}
@Contract("_ -> this")
default @NotNull Builder canDestroy(@NotNull Block... blocks) {
return canDestroy(Set.of(blocks));
}
writer.write(cachedBuffer.flip());
}
}

View File

@ -1,353 +0,0 @@
package net.minestom.server.item;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeOperation;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagWritable;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.Utils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public abstract class ItemMetaBuilder implements TagWritable {
MutableNBTCompound nbt = new MutableNBTCompound();
protected int damage;
protected boolean unbreakable;
protected int hideFlag;
protected Component displayName;
protected List<Component> lore = new ArrayList<>();
protected Map<Enchantment, Short> enchantmentMap = new HashMap<>();
protected List<ItemAttribute> attributes = new ArrayList<>();
protected int customModelData;
protected Set<Block> canDestroy = new HashSet<>();
protected Set<Block> canPlaceOn = new HashSet<>();
@Contract("_ -> this")
public @NotNull ItemMetaBuilder damage(int damage) {
this.damage = damage;
this.nbt.setInt("Damage", damage);
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder unbreakable(boolean unbreakable) {
this.unbreakable = unbreakable;
this.nbt.set("Unbreakable", NBT.Boolean(unbreakable));
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder hideFlag(int hideFlag) {
this.hideFlag = hideFlag;
this.nbt.setInt("HideFlags", hideFlag);
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder hideFlag(@NotNull ItemHideFlag... hideFlags) {
int result = 0;
for (ItemHideFlag hideFlag : hideFlags) result |= hideFlag.getBitFieldPart();
return hideFlag(result);
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder displayName(@Nullable Component displayName) {
this.displayName = displayName;
handleCompound("display", nbtCompound -> {
if (displayName != null) {
nbtCompound.setString("Name", GsonComponentSerializer.gson().serialize(displayName));
} else {
nbtCompound.remove("Name");
}
});
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder lore(@NotNull List<? extends Component> lore) {
this.lore = new ArrayList<>(lore);
handleCompound("display", nbtCompound -> {
final NBTList<NBTString> loreNBT = NBT.List(NBTType.TAG_String,
lore.stream()
.map(line -> new NBTString(GsonComponentSerializer.gson().serialize(line)))
.toList()
);
if (loreNBT.isEmpty()) {
nbtCompound.remove("Lore");
} else {
nbtCompound.set("Lore", loreNBT);
}
});
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder lore(Component... lore) {
lore(Arrays.asList(lore));
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder enchantments(@NotNull Map<Enchantment, Short> enchantments) {
this.enchantmentMap = new HashMap<>(enchantments);
handleMap(enchantmentMap, "Enchantments",
(nbt) -> NBTUtils.writeEnchant(nbt, "Enchantments", enchantmentMap));
return this;
}
@Contract("_, _ -> this")
public @NotNull ItemMetaBuilder enchantment(@NotNull Enchantment enchantment, short level) {
this.enchantmentMap.put(enchantment, level);
enchantments(enchantmentMap);
return this;
}
@Contract("-> this")
public @NotNull ItemMetaBuilder clearEnchantment() {
this.enchantmentMap = new HashMap<>();
this.nbt.remove("Enchantments");
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder attributes(@NotNull List<@NotNull ItemAttribute> attributes) {
this.attributes = new ArrayList<>(attributes);
handleCollection(attributes, "AttributeModifiers", () -> NBT.List(NBTType.TAG_Compound,
attributes.stream()
.map(itemAttribute -> NBT.Compound(Map.of(
"UUID", NBT.IntArray(Utils.uuidToIntArray(itemAttribute.uuid())),
"Amount", NBT.Double(itemAttribute.amount()),
"Slot", NBT.String(itemAttribute.slot().name().toLowerCase()),
"AttributeName", NBT.String(itemAttribute.attribute().key()),
"Operation", NBT.Int(itemAttribute.operation().getId()),
"Name", NBT.String(itemAttribute.name()))))
.toList()
));
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder customModelData(int customModelData) {
this.customModelData = customModelData;
this.nbt.setInt("CustomModelData", customModelData);
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder canPlaceOn(@NotNull Set<@NotNull Block> blocks) {
this.canPlaceOn = new HashSet<>(blocks);
handleCollection(canPlaceOn, "CanPlaceOn", () -> NBT.List(
NBTType.TAG_String,
canPlaceOn.stream()
.map(block -> new NBTString(block.name()))
.toList()
));
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder canPlaceOn(@NotNull Block... blocks) {
return canPlaceOn(Set.of(blocks));
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder canDestroy(@NotNull Set<@NotNull Block> blocks) {
this.canDestroy = new HashSet<>(blocks);
handleCollection(canDestroy, "CanDestroy", () -> NBT.List(
NBTType.TAG_String,
canDestroy.stream()
.map(block -> new NBTString(block.name()))
.toList()
));
return this;
}
@Contract("_ -> this")
public @NotNull ItemMetaBuilder canDestroy(@NotNull Block... blocks) {
return canDestroy(Set.of(blocks));
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbt, value);
}
public <T> @NotNull ItemMetaBuilder set(@NotNull Tag<T> tag, @Nullable T value) {
setTag(tag, value);
return this;
}
@Contract("-> new")
public abstract @NotNull ItemMeta build();
public abstract void read(@NotNull NBTCompound nbtCompound);
protected @NotNull ItemMetaBuilder createEmpty() {
try {
var constructor = getClass().getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected MutableNBTCompound mutableNbt() {
return nbt;
}
protected void mutateNbt(Consumer<MutableNBTCompound> consumer) {
consumer.accept(nbt);
}
protected void handleCompound(@NotNull String key,
@NotNull Consumer<@NotNull MutableNBTCompound> consumer) {
MutableNBTCompound compoundToModify = nbt.get(key) instanceof NBTCompound compound ?
compound.toMutableCompound() : new MutableNBTCompound();
consumer.accept(compoundToModify);
if (compoundToModify.isEmpty()) {
this.nbt.remove(key);
} else {
this.nbt.set(key, compoundToModify.toCompound());
}
}
protected void handleNullable(@Nullable Object value,
@NotNull String key,
@NotNull Supplier<@NotNull NBT> supplier) {
if (value != null) {
this.nbt.set(key, supplier.get());
} else {
this.nbt.remove(key);
}
}
protected void handleCollection(@NotNull Collection<?> objects,
@NotNull String key,
@NotNull Supplier<@NotNull NBT> supplier) {
if (!objects.isEmpty()) {
this.nbt.set(key, supplier.get());
} else {
this.nbt.remove(key);
}
}
protected void handleMap(@NotNull Map<?, ?> objects,
@NotNull String key,
@NotNull Consumer<MutableNBTCompound> consumer) {
if (!objects.isEmpty()) {
consumer.accept(nbt);
} else {
nbt.remove(key);
}
}
@ApiStatus.Internal
public static void resetMeta(@NotNull ItemMetaBuilder src, @NotNull NBTCompound nbtCompound) {
src.nbt.copyFrom(nbtCompound);
appendMeta(src, nbtCompound);
}
private static void appendMeta(@NotNull ItemMetaBuilder metaBuilder,
@NotNull NBTCompound nbt) {
if (nbt.get("Damage") instanceof NBTInt damage) metaBuilder.damage = damage.getValue();
if (nbt.get("Unbreakable") instanceof NBTByte unbreakable) metaBuilder.unbreakable = unbreakable.asBoolean();
if (nbt.get("HideFlags") instanceof NBTInt hideFlags) metaBuilder.hideFlag = hideFlags.getValue();
if (nbt.get("display") instanceof NBTCompound display) {
if (display.get("Name") instanceof NBTString rawName) {
metaBuilder.displayName = GsonComponentSerializer.gson().deserialize(rawName.getValue());
}
if (display.get("Lore") instanceof NBTList<?> loreList &&
loreList.getSubtagType() == NBTType.TAG_String) {
for (NBTString rawLore : loreList.<NBTString>asListOf()) {
metaBuilder.lore.add(GsonComponentSerializer.gson().deserialize(rawLore.getValue()));
}
}
}
// Enchantments
if (nbt.get("Enchantments") instanceof NBTList<?> nbtEnchants &&
nbtEnchants.getSubtagType() == NBTType.TAG_Compound) {
NBTUtils.loadEnchantments(nbtEnchants.asListOf(),
(enchantment, level) -> metaBuilder.enchantmentMap.put(enchantment, level));
}
// Attributes
if (nbt.get("AttributeModifiers") instanceof NBTList<?> nbtAttributes &&
nbtAttributes.getSubtagType() == NBTType.TAG_Compound) {
for (NBTCompound attributeNBT : nbtAttributes.<NBTCompound>asListOf()) {
final UUID uuid;
{
final int[] uuidArray = attributeNBT.getIntArray("UUID").copyArray();
uuid = Utils.intArrayToUuid(uuidArray);
}
final double value = attributeNBT.getAsDouble("Amount");
final String slot = attributeNBT.containsKey("Slot") ? attributeNBT.getString("Slot") : "MAINHAND";
final String attributeName = attributeNBT.getString("AttributeName");
final int operation = attributeNBT.getAsInt("Operation");
final String name = attributeNBT.getString("Name");
final Attribute attribute = Attribute.fromKey(attributeName);
// Wrong attribute name, stop here
if (attribute == null)
break;
final AttributeOperation attributeOperation = AttributeOperation.fromId(operation);
// Wrong attribute operation, stop here
if (attributeOperation == null) {
break;
}
// 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;
}
// Add attribute
final ItemAttribute itemAttribute =
new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot);
metaBuilder.attributes.add(itemAttribute);
}
}
// Custom model data
if (nbt.get("CustomModelData") instanceof NBTInt customModelData) {
metaBuilder.customModelData = customModelData.getValue();
}
// CanPlaceOn
if (nbt.get("CanPlaceOn") instanceof NBTList<?> canPlaceOn &&
canPlaceOn.getSubtagType() == NBTType.TAG_String) {
for (NBTString blockNamespace : canPlaceOn.<NBTString>asListOf()) {
Block block = Block.fromNamespaceId(blockNamespace.getValue());
metaBuilder.canPlaceOn.add(block);
}
}
// CanDestroy
if (nbt.get("CanDestroy") instanceof NBTList<?> canDestroy &&
canDestroy.getSubtagType() == NBTType.TAG_String) {
for (NBTString blockNamespace : canDestroy.<NBTString>asListOf()) {
Block block = Block.fromNamespaceId(blockNamespace.getValue());
metaBuilder.canDestroy.add(block);
}
}
// Meta specific fields
metaBuilder.read(nbt);
}
public interface Provider<T extends ItemMetaBuilder> {
}
}

View File

@ -0,0 +1,69 @@
package net.minestom.server.item;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Objects;
import java.util.function.Consumer;
record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
static final ItemMeta EMPTY = new ItemMetaImpl(TagHandler.newHandler());
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tagHandler.getTag(tag);
}
@Override
public @NotNull ItemMeta with(@NotNull Consumer<ItemMeta.@NotNull Builder> builderConsumer) {
Builder builder = new Builder(tagHandler.copy());
builderConsumer.accept(builder);
return builder.build();
}
@Override
public @NotNull NBTCompound toNBT() {
return tagHandler.asCompound();
}
@Override
public @NotNull String toSNBT() {
return toNBT().toSNBT();
}
@Override
public void write(@NotNull BinaryWriter writer) {
final NBTCompound nbt = toNBT();
if (nbt.isEmpty()) {
writer.writeByte((byte) 0);
return;
}
BinaryWriter w = new BinaryWriter();
w.writeNBT("", nbt);
var cachedBuffer = w.getBuffer();
writer.write(cachedBuffer.flip());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ItemMetaImpl itemMeta)) return false;
return toNBT().equals(itemMeta.toNBT());
}
@Override
public int hashCode() {
return Objects.hash(toNBT());
}
record Builder(TagHandler tagHandler) implements ItemMeta.Builder {
@Override
public @NotNull ItemMeta build() {
return new ItemMetaImpl(tagHandler.copy());
}
}
}

View File

@ -0,0 +1,13 @@
package net.minestom.server.item;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.Taggable;
import org.jetbrains.annotations.ApiStatus;
@SuppressWarnings("ALL")
@ApiStatus.Experimental
public interface ItemMetaView<T extends ItemMetaView.Builder> extends TagReadable {
@ApiStatus.Experimental
interface Builder extends Taggable {
}
}

View File

@ -0,0 +1,37 @@
package net.minestom.server.item;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
final class ItemMetaViewImpl {
static <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> Class<V> viewType(Class<T> metaClass) {
final Type type = metaClass.getGenericInterfaces()[0];
return (Class<V>) ((ParameterizedType) type).getActualTypeArguments()[0];
}
static <T extends ItemMetaView<?>> T construct(Class<T> metaClass, TagReadable tagReadable) {
try {
final Constructor<T> cons = metaClass.getDeclaredConstructor(TagReadable.class);
return cons.newInstance(tagReadable);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> V constructBuilder(Class<T> metaClass, TagHandler tagHandler) {
final Class<V> clazz = viewType(metaClass);
try {
final Constructor<V> cons = clazz.getDeclaredConstructor(TagHandler.class);
return cons.newInstance(tagHandler);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,86 @@
package net.minestom.server.item;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeOperation;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.TagSerializer;
import net.minestom.server.tag.TagWritable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@ApiStatus.Internal
public final class ItemSerializers {
public static final TagSerializer<EnchantmentEntry> ENCHANTMENT_SERIALIZER = new TagSerializer<>() {
static final Tag<Short> LEVEL = Tag.Short("Level");
static final Tag<String> ID = Tag.String("Id");
@Override
public @Nullable EnchantmentEntry read(@NotNull TagReadable reader) {
final String id = reader.getTag(ID);
final Short level = reader.getTag(LEVEL);
if (id == null || level == null) return null;
final Enchantment enchantment = Enchantment.fromNamespaceId(id);
return new EnchantmentEntry(enchantment, level);
}
@Override
public void write(@NotNull TagWritable writer, @NotNull EnchantmentEntry value) {
writer.setTag(ID, value.enchantment.name());
writer.setTag(LEVEL, value.level);
}
};
public record EnchantmentEntry(Enchantment enchantment, short level) {
}
static final TagSerializer<ItemAttribute> ATTRIBUTE_SERIALIZER = new TagSerializer<>() {
static final Tag<UUID> ID = Tag.UUID("UUID");
static final Tag<Double> AMOUNT = Tag.Double("Amount");
static final Tag<String> SLOT = Tag.String("Slot").defaultValue("MAINHAND");
static final Tag<String> ATTRIBUTE_NAME = Tag.String("AttributeName");
static final Tag<Integer> OPERATION = Tag.Integer("Operation");
static final Tag<String> NAME = Tag.String("Name");
@Override
public @Nullable ItemAttribute read(@NotNull TagReadable reader) {
final UUID uuid = reader.getTag(ID);
final double amount = reader.getTag(AMOUNT);
final String slot = reader.getTag(SLOT);
final String attributeName = reader.getTag(ATTRIBUTE_NAME);
final int operation = reader.getTag(OPERATION);
final String name = reader.getTag(NAME);
final Attribute attribute = Attribute.fromKey(attributeName);
// Wrong attribute name, stop here
if (attribute == null) return null;
final AttributeOperation attributeOperation = AttributeOperation.fromId(operation);
// Wrong attribute operation, stop here
if (attributeOperation == null) return null;
// 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;
}
return new ItemAttribute(uuid, name, attribute, attributeOperation, amount, attributeSlot);
}
@Override
public void write(@NotNull TagWritable writer, @NotNull ItemAttribute value) {
writer.setTag(ID, value.uuid());
writer.setTag(AMOUNT, value.amount());
writer.setTag(SLOT, value.slot().name());
writer.setTag(ATTRIBUTE_NAME, value.attribute().key());
writer.setTag(OPERATION, value.operation().getId());
writer.setTag(NAME, value.name());
}
};
}

View File

@ -3,20 +3,15 @@ package net.minestom.server.item;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.minestom.server.item.rule.VanillaStackingRule;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.*;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTByte;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTString;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.function.UnaryOperator;
@ -27,50 +22,36 @@ import java.util.function.UnaryOperator;
* <p>
* An item stack cannot be null, {@link ItemStack#AIR} should be used instead.
*/
public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent.ShowItem> {
static final @NotNull VanillaStackingRule DEFAULT_STACKING_RULE = new VanillaStackingRule();
public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEvent.ShowItem>
permits ItemStackImpl {
/**
* Constant AIR item. Should be used instead of 'null'.
*/
public static final @NotNull ItemStack AIR = ItemStack.of(Material.AIR);
private final Material material;
private final int amount;
private final ItemMeta meta;
ItemStack(@NotNull Material material, int amount,
@NotNull ItemMeta meta) {
this.material = material;
this.amount = amount;
this.meta = meta;
}
@NotNull ItemStack AIR = ItemStack.of(Material.AIR);
@Contract(value = "_ -> new", pure = true)
public static @NotNull ItemStackBuilder builder(@NotNull Material material) {
return new ItemStackBuilder(material);
static @NotNull Builder builder(@NotNull Material material) {
return new ItemStackImpl.Builder(material, 1);
}
@Contract(value = "_ ,_ -> new", pure = true)
public static @NotNull ItemStack of(@NotNull Material material, int amount) {
return builder(material).amount(amount).build();
static @NotNull ItemStack of(@NotNull Material material, int amount) {
return ItemStackImpl.create(material, amount);
}
@Contract(value = "_ -> new", pure = true)
public static @NotNull ItemStack of(@NotNull Material material) {
static @NotNull ItemStack of(@NotNull Material material) {
return of(material, 1);
}
@Contract(value = "_, _, _ -> new", pure = true)
public static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable NBTCompound nbtCompound, int amount) {
ItemMetaBuilder builder = ItemStackBuilder.getMetaBuilder(material);
if (nbtCompound != null) ItemMetaBuilder.resetMeta(builder, nbtCompound);
return new ItemStack(material, amount, builder.build());
static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable NBTCompound nbtCompound, int amount) {
if (nbtCompound == null) return of(material, amount);
return builder(material).amount(amount).meta(nbtCompound).build();
}
@Contract(value = "_, _ -> new", pure = true)
public static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable NBTCompound nbtCompound) {
static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable NBTCompound nbtCompound) {
return fromNBT(material, nbtCompound, 1);
}
@ -80,7 +61,7 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
* @param nbtCompound The nbt representation of the item
*/
@ApiStatus.Experimental
public static @NotNull ItemStack fromItemNBT(@NotNull NBTCompound nbtCompound) {
static @NotNull ItemStack fromItemNBT(@NotNull NBTCompound nbtCompound) {
String id = nbtCompound.getString("id");
Check.notNull(id, "Item NBT must contain an id field.");
Material material = Material.fromNamespaceId(id);
@ -93,137 +74,106 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
}
@Contract(pure = true)
public @NotNull Material getMaterial() {
return material;
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack with(@NotNull Consumer<@NotNull ItemStackBuilder> builderConsumer) {
var builder = builder();
builderConsumer.accept(builder);
return builder.build();
}
@NotNull Material material();
@Contract(pure = true)
public int getAmount() {
return amount;
}
int amount();
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withAmount(int amount) {
if (amount < 1) return AIR;
return new ItemStack(material, amount, meta);
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withAmount(@NotNull IntUnaryOperator intUnaryOperator) {
return withAmount(intUnaryOperator.applyAsInt(amount));
}
@Contract(pure = true)
@NotNull ItemMeta meta();
@Contract(pure = true)
@ApiStatus.Experimental
<T extends ItemMetaView<?>> @NotNull T meta(@NotNull Class<T> metaClass);
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack consume(int amount) {
return DEFAULT_STACKING_RULE.apply(this, currentAmount -> currentAmount - amount);
}
@NotNull ItemStack with(@NotNull Consumer<@NotNull Builder> builderConsumer);
@Contract(value = "_, _ -> new", pure = true)
public <T extends ItemMetaBuilder, U extends ItemMetaBuilder.Provider<T>> @NotNull ItemStack withMeta(Class<U> metaType, Consumer<T> metaConsumer) {
return builder().meta(metaType, metaConsumer).build();
}
@ApiStatus.Experimental
<V extends ItemMetaView.Builder, T extends ItemMetaView<V>> @NotNull ItemStack withMeta(@NotNull Class<T> metaType,
@NotNull Consumer<V> metaConsumer);
@Contract(value = "_ -> new", pure = true)
public <T extends ItemMetaBuilder> @NotNull ItemStack withMeta(@NotNull UnaryOperator<@NotNull T> metaOperator) {
return builder().meta(metaOperator).build();
@NotNull ItemStack withMeta(@NotNull UnaryOperator<ItemMeta.@NotNull Builder> metaOperator);
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withMaterial(@NotNull Material material) {
return ItemStackImpl.create(material, amount(), meta());
}
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withAmount(int amount) {
return ItemStackImpl.create(material(), amount, meta());
}
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withAmount(@NotNull IntUnaryOperator intUnaryOperator) {
return withAmount(intUnaryOperator.applyAsInt(amount()));
}
@ApiStatus.Experimental
@Contract(value = "_, -> new", pure = true)
@NotNull ItemStack consume(int amount);
@Contract(pure = true)
default @Nullable Component getDisplayName() {
return meta().getDisplayName();
}
@Contract(pure = true)
default @NotNull List<@NotNull Component> getLore() {
return meta().getLore();
}
@ApiStatus.Experimental
@Contract(value = "_ -> new", pure = true)
public @NotNull ItemStack withMeta(@NotNull ItemMeta meta) {
return new ItemStack(material, amount, meta);
}
@Contract(pure = true)
public @Nullable Component getDisplayName() {
return meta.getDisplayName();
default @NotNull ItemStack withMeta(@NotNull ItemMeta meta) {
return new ItemStackImpl(material(), amount(), meta);
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withDisplayName(@Nullable Component displayName) {
return builder().displayName(displayName).build();
default @NotNull ItemStack withDisplayName(@Nullable Component displayName) {
return withMeta(builder -> builder.displayName(displayName));
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) {
default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) {
return withDisplayName(componentUnaryOperator.apply(getDisplayName()));
}
@Contract(pure = true)
public @NotNull List<@NotNull Component> getLore() {
return meta.getLore();
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withLore(@NotNull List<? extends Component> lore) {
return withMeta(builder -> builder.lore(lore));
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withLore(@NotNull List<? extends Component> lore) {
return builder().lore(lore).build();
}
@Contract(value = "_, -> new", pure = true)
public @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) {
default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) {
return withLore(loreUnaryOperator.apply(getLore()));
}
@Contract(pure = true)
public @NotNull ItemMeta getMeta() {
return meta;
default boolean isAir() {
return material() == Material.AIR;
}
@Contract(pure = true)
public boolean isAir() {
return material == Material.AIR;
}
@Contract(pure = true)
public boolean isSimilar(@NotNull ItemStack itemStack) {
return material == itemStack.material &&
meta.equals(itemStack.meta);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ItemStack itemStack)) return false;
return amount == itemStack.amount && material.equals(itemStack.material) && meta.equals(itemStack.meta);
}
@Override
public int hashCode() {
return Objects.hash(material, amount, meta);
}
@Override
public String toString() {
return "ItemStack{" +
"material=" + material +
", amount=" + amount +
", meta=" + meta +
'}';
}
boolean isSimilar(@NotNull ItemStack itemStack);
@Contract(value = "_, _ -> new", pure = true)
public <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return builder().meta(metaBuilder -> metaBuilder.set(tag, value)).build();
default <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return withMeta(builder -> builder.set(tag, value));
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return meta.getTag(tag);
default <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return meta().getTag(tag);
}
@Override
public @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(this.material,
this.amount,
NBTUtils.asBinaryTagHolder(this.meta.toNBT()))));
default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(),
NBTUtils.asBinaryTagHolder(meta().toNBT()))));
}
/**
@ -232,16 +182,62 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
* @return The nbt representation of the item
*/
@ApiStatus.Experimental
public @NotNull NBTCompound toItemNBT() {
final NBTString material = NBT.String(getMaterial().name());
final NBTByte amount = NBT.Byte(getAmount());
final NBTCompound nbt = getMeta().toNBT();
if (nbt.isEmpty()) return NBT.Compound(Map.of("id", material, "Count", amount));
return NBT.Compound(Map.of("id", material, "Count", amount, "tag", nbt));
@NotNull NBTCompound toItemNBT();
@Deprecated
@Contract(pure = true)
default @NotNull Material getMaterial() {
return material();
}
@Contract(value = "-> new", pure = true)
private @NotNull ItemStackBuilder builder() {
return new ItemStackBuilder(material, meta.builder()).amount(amount);
@Deprecated
@Contract(pure = true)
default int getAmount() {
return amount();
}
@Deprecated
@Contract(pure = true)
default @NotNull ItemMeta getMeta() {
return meta();
}
sealed interface Builder permits ItemStackImpl.Builder {
@Contract(value = "_ -> this")
@NotNull Builder amount(int amount);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull TagHandler tagHandler);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull NBTCompound compound);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull ItemMeta itemMeta);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull UnaryOperator<ItemMeta.@NotNull Builder> consumer);
@Contract(value = "_, _ -> this")
<V extends ItemMetaView.Builder, T extends ItemMetaView<V>> @NotNull Builder meta(@NotNull Class<T> metaType, @NotNull Consumer<@NotNull V> itemMetaConsumer);
@Contract(value = "-> new", pure = true)
@NotNull ItemStack build();
@Contract(value = "_ -> this")
default @NotNull Builder displayName(@Nullable Component displayName) {
return meta(builder -> builder.displayName(displayName));
}
@Contract(value = "_ -> this")
default @NotNull Builder lore(@NotNull List<? extends Component> lore) {
return meta(builder -> builder.lore(lore));
}
@Contract(value = "_ -> this")
default @NotNull Builder lore(Component... lore) {
return meta(builder -> builder.lore(lore));
}
}
}

View File

@ -1,123 +0,0 @@
package net.minestom.server.item;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.metadata.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
public final class ItemStackBuilder {
private final Material material;
private int amount;
private ItemMetaBuilder metaBuilder;
ItemStackBuilder(@NotNull Material material, @NotNull ItemMetaBuilder metaBuilder) {
this.material = material;
this.amount = 1;
this.metaBuilder = metaBuilder;
}
private static final Map<Material, Supplier<ItemMetaBuilder>> MATERIAL_SUPPLIER_MAP = new ConcurrentHashMap<>();
static {
MATERIAL_SUPPLIER_MAP.put(Material.POTION, PotionMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LINGERING_POTION, PotionMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.SPLASH_POTION, PotionMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.TIPPED_ARROW, PotionMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.FILLED_MAP, MapMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.COMPASS, CompassMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.ENCHANTED_BOOK, EnchantedBookMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.CROSSBOW, CrossbowMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.WRITABLE_BOOK, WritableBookMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.WRITTEN_BOOK, WrittenBookMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_STAR, FireworkEffectMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_ROCKET, FireworkMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.PLAYER_HEAD, PlayerHeadMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.BUNDLE, BundleMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_HELMET, LeatherArmorMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_CHESTPLATE, LeatherArmorMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_LEGGINGS, LeatherArmorMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_BOOTS, LeatherArmorMeta.Builder::new);
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_HORSE_ARMOR, LeatherArmorMeta.Builder::new);
}
static ItemMetaBuilder getMetaBuilder(Material material) {
Supplier<ItemMetaBuilder> supplier = MATERIAL_SUPPLIER_MAP.get(material);
return supplier != null ? supplier.get() : new DefaultMeta();
}
ItemStackBuilder(@NotNull Material material) {
this(material, getMetaBuilder(material));
}
@Contract(value = "_ -> this")
public @NotNull ItemStackBuilder amount(int amount) {
this.amount = amount;
return this;
}
@Contract(value = "_ -> this")
public @NotNull ItemStackBuilder meta(@NotNull ItemMeta itemMeta) {
this.metaBuilder = itemMeta.builder();
return this;
}
@Contract(value = "_ -> this")
public <T extends ItemMetaBuilder> @NotNull ItemStackBuilder meta(@NotNull UnaryOperator<@NotNull T> itemMetaConsumer) {
//noinspection unchecked
this.metaBuilder = itemMetaConsumer.apply((T) metaBuilder);
return this;
}
@Contract(value = "_, _ -> this")
public <T extends ItemMetaBuilder, U extends ItemMetaBuilder.Provider<T>> @NotNull ItemStackBuilder meta(@NotNull Class<U> metaType, @NotNull Consumer<@NotNull T> itemMetaConsumer) {
itemMetaConsumer.accept((T) metaBuilder);
return this;
}
@Contract(value = "_ -> this")
public @NotNull ItemStackBuilder displayName(@Nullable Component displayName) {
this.metaBuilder.displayName(displayName);
return this;
}
@Contract(value = "_ -> this")
public @NotNull ItemStackBuilder lore(@NotNull List<? extends Component> lore) {
this.metaBuilder.lore(lore);
return this;
}
@Contract(value = "_ -> this")
public @NotNull ItemStackBuilder lore(Component... lore) {
this.metaBuilder.lore(lore);
return this;
}
@Contract(value = "-> new", pure = true)
public @NotNull ItemStack build() {
if (amount < 1) return ItemStack.AIR;
return new ItemStack(material, amount, metaBuilder.build());
}
private static final class DefaultMeta extends ItemMetaBuilder {
@Override
public @NotNull ItemMeta build() {
return new ItemMeta(this);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
// Empty
}
}
}

View File

@ -0,0 +1,136 @@
package net.minestom.server.item;
import net.minestom.server.item.rule.VanillaStackingRule;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTByte;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTString;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
record ItemStackImpl(Material material, int amount, ItemMeta meta) implements ItemStack {
static final @NotNull VanillaStackingRule DEFAULT_STACKING_RULE = new VanillaStackingRule();
static ItemStack create(Material material, int amount, ItemMeta meta) {
if (amount <= 0) return AIR;
return new ItemStackImpl(material, amount, meta);
}
static ItemStack create(Material material, int amount) {
return create(material, amount, ItemMetaImpl.EMPTY);
}
@Override
public <T extends ItemMetaView<?>> @NotNull T meta(@NotNull Class<T> metaClass) {
return ItemMetaViewImpl.construct(metaClass, meta);
}
@Override
public @NotNull ItemStack with(@NotNull Consumer<ItemStack.@NotNull Builder> builderConsumer) {
ItemStack.Builder builder = builder();
builderConsumer.accept(builder);
return builder.build();
}
@Override
public @NotNull <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> ItemStack withMeta(@NotNull Class<T> metaType,
@NotNull Consumer<V> metaConsumer) {
return builder().meta(metaType, metaConsumer).build();
}
@Override
public @NotNull ItemStack withMeta(@NotNull UnaryOperator<ItemMeta.@NotNull Builder> metaOperator) {
return builder().meta(metaOperator).build();
}
@Override
public @NotNull ItemStack consume(int amount) {
return DEFAULT_STACKING_RULE.apply(this, currentAmount -> currentAmount - amount);
}
@Override
public boolean isSimilar(@NotNull ItemStack itemStack) {
return material == itemStack.material() && meta.equals(itemStack.meta());
}
@Override
public @NotNull NBTCompound toItemNBT() {
final NBTString material = NBT.String(material().name());
final NBTByte amount = NBT.Byte(amount());
final NBTCompound nbt = meta().toNBT();
if (nbt.isEmpty()) return NBT.Compound(Map.of("id", material, "Count", amount));
return NBT.Compound(Map.of("id", material, "Count", amount, "tag", nbt));
}
@Contract(value = "-> new", pure = true)
private @NotNull ItemStack.Builder builder() {
return new Builder(material, amount, new ItemMetaImpl.Builder(TagHandler.fromCompound(meta.toNBT())));
}
static final class Builder implements ItemStack.Builder {
final Material material;
int amount;
ItemMeta.Builder metaBuilder;
Builder(Material material, int amount, ItemMeta.Builder metaBuilder) {
this.material = material;
this.amount = amount;
this.metaBuilder = metaBuilder;
}
Builder(Material material, int amount) {
this(material, amount, new ItemMetaImpl.Builder(TagHandler.newHandler()));
}
@Override
public ItemStack.@NotNull Builder amount(int amount) {
this.amount = amount;
return this;
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull TagHandler tagHandler) {
return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy()));
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull NBTCompound compound) {
return metaBuilder(new ItemMetaImpl.Builder(TagHandler.fromCompound(compound)));
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull ItemMeta itemMeta) {
final TagHandler tagHandler = ((ItemMetaImpl) itemMeta).tagHandler();
return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy()));
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull UnaryOperator<ItemMeta.Builder> consumer) {
this.metaBuilder = consumer.apply(metaBuilder);
return this;
}
@Override
public <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> ItemStack.@NotNull Builder meta(@NotNull Class<T> metaType,
@NotNull Consumer<@NotNull V> itemMetaConsumer) {
V view = ItemMetaViewImpl.constructBuilder(metaType, metaBuilder.tagHandler());
itemMetaConsumer.accept(view);
return this;
}
@Override
public @NotNull ItemStack build() {
return ItemStackImpl.create(material, amount, metaBuilder.build());
}
private ItemStack.@NotNull Builder metaBuilder(@NotNull ItemMeta.Builder builder) {
this.metaBuilder = builder;
return this;
}
}
}

View File

@ -0,0 +1,35 @@
package net.minestom.server.item;
import net.kyori.adventure.text.Component;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.tag.Tag;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static net.minestom.server.item.ItemSerializers.*;
final class ItemTags {
static final Tag<Integer> DAMAGE = Tag.Integer("Damage").defaultValue(0);
static final Tag<Boolean> UNBREAKABLE = Tag.Boolean("Unbreakable").defaultValue(false);
static final Tag<Integer> HIDE_FLAGS = Tag.Integer("HideFlags").defaultValue(0);
static final Tag<Integer> CUSTOM_MODEL_DATA = Tag.Integer("CustomModelData").defaultValue(0);
static final Tag<Component> NAME = Tag.Component("Name").path("display");
static final Tag<List<Component>> LORE = Tag.Component("Lore").path("display").list().defaultValue(List.of());
static final Tag<Map<Enchantment, Short>> ENCHANTMENTS = Tag.Structure("Enchantments", ENCHANTMENT_SERIALIZER).list().map(enchantmentEntry -> {
Map<Enchantment, Short> map = new HashMap<>();
for (var entry : enchantmentEntry) map.put(entry.enchantment(), entry.level());
return Map.copyOf(map);
}, o -> {
List<EnchantmentEntry> entries = new ArrayList<>();
for (var entry : o.entrySet()) entries.add(new EnchantmentEntry(entry.getKey(), entry.getValue()));
return List.copyOf(entries);
}).defaultValue(Map.of());
static final Tag<List<ItemAttribute>> ATTRIBUTES = Tag.Structure("AttributeModifiers", ATTRIBUTE_SERIALIZER).list().defaultValue(List.of());
static final Tag<List<Block>> CAN_PLACE_ON = Tag.String("CanPlaceOn").map(Block::fromNamespaceId, ProtocolObject::name).list().defaultValue(List.of());
static final Tag<List<Block>> CAN_DESTROY = Tag.String("CanDestroy").map(Block::fromNamespaceId, ProtocolObject::name).list().defaultValue(List.of());
}

View File

@ -13,7 +13,7 @@ import java.util.function.IntUnaryOperator;
public interface StackingRule {
static @NotNull StackingRule get() {
return ItemStack.DEFAULT_STACKING_RULE;
return ItemStackImpl.DEFAULT_STACKING_RULE;
}
/**

View File

@ -1,73 +1,48 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemStack;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTList;
import org.jglrxavpok.hephaistos.nbt.NBTType;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.List;
@ApiStatus.Experimental
public class BundleMeta extends ItemMeta implements ItemMetaBuilder.Provider<BundleMeta.Builder> {
private final List<ItemStack> items;
protected BundleMeta(ItemMetaBuilder metaBuilder,
@NotNull List<ItemStack> items) {
super(metaBuilder);
this.items = List.copyOf(items);
}
public record BundleMeta(TagReadable readable) implements ItemMetaView<BundleMeta.Builder> {
private static final Tag<List<ItemStack>> ITEMS = Tag.ItemStack("Items").list().defaultValue(List.of());
public @NotNull List<ItemStack> getItems() {
return items;
return getTag(ITEMS);
}
public static class Builder extends ItemMetaBuilder {
private List<ItemStack> items = new ArrayList<>();
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder items(@NotNull List<ItemStack> items) {
this.items = new ArrayList<>(items); // defensive copy
updateItems();
setTag(ITEMS, items);
return this;
}
@ApiStatus.Experimental
public Builder addItem(@NotNull ItemStack item) {
items.add(item);
updateItems();
return this;
var newList = new ArrayList<>(getTag(ITEMS));
newList.add(item);
return items(newList);
}
@ApiStatus.Experimental
public Builder removeItem(@NotNull ItemStack item) {
items.remove(item);
updateItems();
return this;
}
@Override
public @NotNull BundleMeta build() {
return new BundleMeta(this, items);
}
private void updateItems() {
mutableNbt().set("Items", NBT.List(NBTType.TAG_Compound, items.size(), i -> items.get(i).toItemNBT()));
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("Items") instanceof NBTList<?> list &&
list.getSubtagType() == NBTType.TAG_Compound) {
for (NBTCompound item : list.<NBTCompound>asListOf()) {
this.items.add(ItemStack.fromItemNBT(item));
}
}
var newList = new ArrayList<>(getTag(ITEMS));
newList.remove(item);
return items(newList);
}
}
}

View File

@ -2,99 +2,64 @@ package net.minestom.server.item.metadata;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTByte;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTString;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Map;
public record CompassMeta(TagReadable readable) implements ItemMetaView<CompassMeta.Builder> {
private static final Tag<Boolean> LODESTONE_TRACKED = Tag.Boolean("LodestoneTracked").defaultValue(false);
private static final Tag<String> LODESTONE_DIMENSION = Tag.String("LodestoneDimension");
private static final Tag<Point> LODESTONE_POSITION = Tag.Structure("LodestonePos", new TagSerializer<>() {
@Override
public @Nullable Point read(@NotNull TagReadable reader) {
final Integer x = reader.getTag(Tag.Integer("X"));
final Integer y = reader.getTag(Tag.Integer("Y"));
final Integer z = reader.getTag(Tag.Integer("Z"));
if (x == null || y == null || z == null) return null;
return new Vec(x, y, z);
}
public class CompassMeta extends ItemMeta implements ItemMetaBuilder.Provider<CompassMeta.Builder> {
private final boolean lodestoneTracked;
private final String lodestoneDimension;
private final Point lodestonePosition;
protected CompassMeta(ItemMetaBuilder metaBuilder,
boolean lodestoneTracked,
@Nullable String lodestoneDimension,
@Nullable Point lodestonePosition) {
super(metaBuilder);
this.lodestoneTracked = lodestoneTracked;
this.lodestoneDimension = lodestoneDimension;
this.lodestonePosition = lodestonePosition;
}
@Override
public void write(@NotNull TagWritable writer, @NotNull Point value) {
writer.setTag(Tag.Integer("X"), value.blockX());
writer.setTag(Tag.Integer("Y"), value.blockY());
writer.setTag(Tag.Integer("Z"), value.blockZ());
}
});
public boolean isLodestoneTracked() {
return lodestoneTracked;
return getTag(LODESTONE_TRACKED);
}
public @Nullable String getLodestoneDimension() {
return lodestoneDimension;
return getTag(LODESTONE_DIMENSION);
}
public @Nullable Point getLodestonePosition() {
return lodestonePosition;
return getTag(LODESTONE_POSITION);
}
public static class Builder extends ItemMetaBuilder {
private boolean lodestoneTracked;
private String lodestoneDimension;
private Point lodestonePosition;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder lodestoneTracked(boolean lodestoneTracked) {
this.lodestoneTracked = lodestoneTracked;
mutableNbt().setByte("LodestoneTracked", (byte) (lodestoneTracked ? 1 : 0));
setTag(LODESTONE_TRACKED, lodestoneTracked);
return this;
}
public Builder lodestoneDimension(@Nullable String lodestoneDimension) {
this.lodestoneDimension = lodestoneDimension;
if (lodestoneDimension != null) {
mutableNbt().setString("LodestoneDimension", lodestoneDimension);
} else {
mutableNbt().remove("LodestoneDimension");
}
setTag(LODESTONE_DIMENSION, lodestoneDimension);
return this;
}
public Builder lodestonePosition(@Nullable Point lodestonePosition) {
this.lodestonePosition = lodestonePosition;
if (lodestonePosition != null) {
mutableNbt().set("LodestonePos", NBT.Compound(Map.of(
"X", NBT.Int(lodestonePosition.blockX()),
"Y", NBT.Int(lodestonePosition.blockY()),
"Z", NBT.Int(lodestonePosition.blockZ()))));
} else {
mutableNbt().remove("LodestonePos");
}
setTag(LODESTONE_POSITION, lodestonePosition);
return this;
}
@Override
public @NotNull CompassMeta build() {
return new CompassMeta(this, lodestoneTracked, lodestoneDimension, lodestonePosition);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("LodestoneTracked") instanceof NBTByte tracked) {
this.lodestoneTracked = tracked.asBoolean();
}
if (nbtCompound.get("LodestoneDimension") instanceof NBTString dimension) {
this.lodestoneDimension = dimension.getValue();
}
if (nbtCompound.get("LodestonePos") instanceof NBTCompound posCompound) {
final int x = posCompound.getInt("X");
final int y = posCompound.getInt("Y");
final int z = posCompound.getInt("Z");
this.lodestonePosition = new Vec(x, y, z);
}
}
}
}

View File

@ -1,174 +1,46 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.List;
public class CrossbowMeta extends ItemMeta implements ItemMetaBuilder.Provider<SpawnEggMeta.Builder> {
public record CrossbowMeta(TagReadable readable) implements ItemMetaView<CrossbowMeta.Builder> {
private static final Tag<List<ItemStack>> PROJECTILES = Tag.ItemStack("ChargedProjectiles").list();
private static final Tag<Boolean> CHARGED = Tag.Boolean("Charged");
private final boolean triple;
private final ItemStack projectile1, projectile2, projectile3;
private final boolean charged;
protected CrossbowMeta(@NotNull ItemMetaBuilder metaBuilder,
boolean triple,
ItemStack projectile1, ItemStack projectile2, ItemStack projectile3,
boolean charged) {
super(metaBuilder);
this.triple = triple;
this.projectile1 = projectile1;
this.projectile2 = projectile2;
this.projectile3 = projectile3;
this.charged = charged;
public @NotNull List<ItemStack> getProjectiles() {
return getTag(PROJECTILES);
}
/**
* Gets if this crossbow is charged with 3 projectiles.
*
* @return true if this crossbow is charged with 3 projectiles, false otherwise
*/
public boolean isTriple() {
return triple;
}
/**
* Gets the first projectile.
*
* @return the first projectile
*/
public @NotNull ItemStack getProjectile1() {
return projectile1;
}
/**
* Gets the second projectile.
*
* @return the second projectile
*/
public @NotNull ItemStack getProjectile2() {
return projectile2;
}
/**
* Gets the third projectile.
*
* @return the third projectile
*/
public @NotNull ItemStack getProjectile3() {
return projectile3;
}
/**
* Gets if the crossbow is currently charged.
*
* @return true if the crossbow is charged, false otherwise
*/
public boolean isCharged() {
return charged;
return getTag(CHARGED);
}
public static class Builder extends ItemMetaBuilder {
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
private boolean triple;
private ItemStack projectile1 = ItemStack.AIR;
private ItemStack projectile2 = ItemStack.AIR;
private ItemStack projectile3 = ItemStack.AIR;
private boolean charged;
/**
* Sets the projectile of this crossbow.
*
* @param projectile the projectile of the crossbow, air to remove
*/
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder projectile(@NotNull ItemStack projectile) {
this.projectile1 = projectile;
this.triple = false;
mutableNbt().set("ChargedProjectiles", NBT.List(NBTType.TAG_Compound,
projectile.isAir() ? List.of() : List.of(getItemCompound(projectile))));
setTag(PROJECTILES, List.of(projectile));
return this;
}
/**
* Sets the triple projectiles of this crossbow.
*
* @param projectile1 the projectile 1
* @param projectile2 the projectile 2
* @param projectile3 the projectile 3
*/
public Builder projectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) {
Check.argCondition(projectile1.isAir(), "the projectile1 of your crossbow isn't visible");
Check.argCondition(projectile2.isAir(), "the projectile2 of your crossbow isn't visible");
Check.argCondition(projectile3.isAir(), "the projectile3 of your crossbow isn't visible");
this.projectile1 = projectile1;
this.projectile2 = projectile2;
this.projectile3 = projectile3;
this.triple = true;
List<NBTCompound> chargedProjectiles =
List.of(getItemCompound(projectile1), getItemCompound(projectile2), getItemCompound(projectile3));
mutableNbt().set("ChargedProjectiles", NBT.List(NBTType.TAG_Compound, chargedProjectiles));
setTag(PROJECTILES, List.of(projectile1, projectile2, projectile3));
return this;
}
/**
* Makes the bow charged or uncharged.
*
* @param charged true to make the crossbow charged, false otherwise
*/
public Builder charged(boolean charged) {
this.charged = charged;
mutableNbt().set("Charged", NBT.Boolean(charged));
setTag(CHARGED, charged);
return this;
}
@Override
public @NotNull CrossbowMeta build() {
return new CrossbowMeta(this, triple, projectile1, projectile2, projectile3, charged);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.containsKey("ChargedProjectiles")) {
final NBTList<NBTCompound> projectilesList = nbtCompound.getList("ChargedProjectiles");
List<ItemStack> projectiles = new ArrayList<>();
for (NBTCompound projectileCompound : projectilesList) {
final byte count = projectileCompound.getByte("Count");
final String id = projectileCompound.getString("id");
final Material material = Material.fromNamespaceId(id);
final NBTCompound tagsCompound = projectileCompound.getCompound("tag");
ItemStack itemStack = ItemStack.fromNBT(material, tagsCompound, count);
projectiles.add(itemStack);
}
if (projectiles.size() == 1) {
this.projectile1 = projectiles.get(0);
} else if (projectiles.size() == 3) {
this.projectile1 = projectiles.get(0);
this.projectile2 = projectiles.get(1);
this.projectile3 = projectiles.get(2);
}
}
if (nbtCompound.get("Charged") instanceof NBTByte charged) {
this.charged = charged.asBoolean();
}
}
private @NotNull NBTCompound getItemCompound(@NotNull ItemStack itemStack) {
NBTCompound compound = itemStack.getMeta().toNBT();
return compound.modify(n -> {
n.setByte("Count", (byte) itemStack.getAmount());
n.setString("id", itemStack.getMaterial().name());
});
}
}
}
}

View File

@ -1,63 +1,52 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemSerializers;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTList;
import org.jglrxavpok.hephaistos.nbt.NBTType;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EnchantedBookMeta extends ItemMeta implements ItemMetaBuilder.Provider<EnchantedBookMeta.Builder> {
import static net.minestom.server.item.ItemSerializers.ENCHANTMENT_SERIALIZER;
private final Map<Enchantment, Short> storedEnchantmentMap;
public record EnchantedBookMeta(TagReadable readable) implements ItemMetaView<EnchantedBookMeta.Builder> {
static final Tag<Map<Enchantment, Short>> ENCHANTMENTS = Tag.Structure("StoredEnchantments", ENCHANTMENT_SERIALIZER).list().map(enchantmentEntry -> {
Map<Enchantment, Short> map = new HashMap<>();
for (var entry : enchantmentEntry) map.put(entry.enchantment(), entry.level());
return Map.copyOf(map);
}, o -> {
List<ItemSerializers.EnchantmentEntry> entries = new ArrayList<>();
for (var entry : o.entrySet())
entries.add(new ItemSerializers.EnchantmentEntry(entry.getKey(), entry.getValue()));
return List.copyOf(entries);
}).defaultValue(Map.of());
protected EnchantedBookMeta(@NotNull ItemMetaBuilder metaBuilder, Map<Enchantment, Short> storedEnchantmentMap) {
super(metaBuilder);
this.storedEnchantmentMap = Map.copyOf(storedEnchantmentMap);
}
/**
* Gets the stored enchantment map.
* Stored enchantments are used on enchanted book.
*
* @return an unmodifiable map containing the item stored enchantments
*/
public @NotNull Map<Enchantment, Short> getStoredEnchantmentMap() {
return storedEnchantmentMap;
return getTag(ENCHANTMENTS);
}
public static class Builder extends ItemMetaBuilder {
private Map<Enchantment, Short> enchantments = new HashMap<>();
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public @NotNull Builder enchantments(@NotNull Map<Enchantment, Short> enchantments) {
this.enchantments = enchantments;
NBTUtils.writeEnchant(mutableNbt(), "StoredEnchantments", enchantments);
setTag(ENCHANTMENTS, Map.copyOf(enchantments));
return this;
}
public @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) {
this.enchantments.put(enchantment, level);
enchantments(enchantments);
return this;
}
@Override
public @NotNull EnchantedBookMeta build() {
return new EnchantedBookMeta(this, enchantments);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("StoredEnchantments") instanceof NBTList<?> list &&
list.getSubtagType() == NBTType.TAG_Compound) {
NBTUtils.loadEnchantments(list.asListOf(), this::enchantment);
}
var enchantments = new HashMap<>(getTag(ENCHANTMENTS));
enchantments.put(enchantment, level);
return enchantments(enchantments);
}
}
}

View File

@ -1,47 +1,32 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.firework.FireworkEffect;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Objects;
public record FireworkEffectMeta(TagReadable readable) implements ItemMetaView<FireworkEffectMeta.Builder> {
private static final Tag<FireworkEffect> FIREWORK_EFFECT = Tag.NBT("Explosion")
.map(nbt -> FireworkEffect.fromCompound((NBTCompound) nbt), FireworkEffect::asCompound);
public class FireworkEffectMeta extends ItemMeta implements ItemMetaBuilder.Provider<FireworkEffectMeta.Builder> {
private final FireworkEffect fireworkEffect;
protected FireworkEffectMeta(@NotNull ItemMetaBuilder metaBuilder, FireworkEffect fireworkEffect) {
super(metaBuilder);
this.fireworkEffect = fireworkEffect;
public @Nullable FireworkEffect getFireworkEffect() {
return getTag(FIREWORK_EFFECT);
}
public FireworkEffect getFireworkEffect() {
return fireworkEffect;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public static class Builder extends ItemMetaBuilder {
private FireworkEffect fireworkEffect;
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder effect(@Nullable FireworkEffect fireworkEffect) {
this.fireworkEffect = fireworkEffect;
handleNullable(fireworkEffect, "Explosion", () -> Objects.requireNonNull(fireworkEffect).asCompound());
setTag(FIREWORK_EFFECT, fireworkEffect);
return this;
}
@Override
public @NotNull FireworkEffectMeta build() {
return new FireworkEffectMeta(this, fireworkEffect);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("Explosion") instanceof NBTCompound explosionCompound) {
this.fireworkEffect = FireworkEffect.fromCompound(explosionCompound);
}
}
}
}
}

View File

@ -1,78 +1,44 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.firework.FireworkEffect;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class FireworkMeta extends ItemMeta implements ItemMetaBuilder.Provider<FireworkMeta.Builder> {
private final List<FireworkEffect> effects;
private final byte flightDuration;
protected FireworkMeta(@NotNull ItemMetaBuilder metaBuilder, List<FireworkEffect> effects,
byte flightDuration) {
super(metaBuilder);
this.effects = List.copyOf(effects);
this.flightDuration = flightDuration;
}
public record FireworkMeta(TagReadable readable) implements ItemMetaView<FireworkMeta.Builder> {
private static final Tag<List<FireworkEffect>> EFFECTS = Tag.NBT("Explosions").path("Fireworks")
.map(nbt -> FireworkEffect.fromCompound((NBTCompound) nbt), FireworkEffect::asCompound)
.list().defaultValue(List.of());
private static final Tag<Byte> FLIGHT_DURATION = Tag.Byte("Flight").path("Fireworks");
public List<FireworkEffect> getEffects() {
return effects;
return getTag(EFFECTS);
}
public byte getFlightDuration() {
return flightDuration;
return getTag(FLIGHT_DURATION);
}
public static class Builder extends ItemMetaBuilder {
private List<FireworkEffect> effects = new CopyOnWriteArrayList<>();
private byte flightDuration;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder effects(List<FireworkEffect> effects) {
this.effects = effects;
handleCompound("Fireworks", nbtCompound -> {
nbtCompound.set("Explosions", NBT.List(
NBTType.TAG_Compound,
effects.stream()
.map(FireworkEffect::asCompound)
.toList()
));
});
setTag(EFFECTS, effects);
return this;
}
public Builder flightDuration(byte flightDuration) {
this.flightDuration = flightDuration;
handleCompound("Fireworks", nbtCompound ->
nbtCompound.setByte("Flight", this.flightDuration));
setTag(FLIGHT_DURATION, flightDuration);
return this;
}
@Override
public @NotNull FireworkMeta build() {
return new FireworkMeta(this, effects, flightDuration);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("Fireworks") instanceof NBTCompound fireworksCompound) {
if (fireworksCompound.get("Flight") instanceof NBTByte flight) {
this.flightDuration = flight.getValue();
}
if (fireworksCompound.get("Explosions") instanceof NBTList<?> list &&
list.getSubtagType() == NBTType.TAG_Compound) {
for (NBTCompound explosion : list.<NBTCompound>asListOf()) {
this.effects.add(FireworkEffect.fromCompound(explosion));
}
}
}
}
}
}
}

View File

@ -1,54 +1,30 @@
package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTInt;
import org.jetbrains.annotations.UnknownNullability;
public class LeatherArmorMeta extends ItemMeta implements ItemMetaBuilder.Provider<LeatherArmorMeta.Builder> {
private final Color color;
protected LeatherArmorMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable Color color) {
super(metaBuilder);
this.color = color;
}
public record LeatherArmorMeta(TagReadable readable) implements ItemMetaView<LeatherArmorMeta.Builder> {
private static final Tag<Color> COLOR = Tag.Integer("color").path("display").map(Color::new, Color::asRGB);
public @Nullable Color getColor() {
return color;
return getTag(COLOR);
}
public static class Builder extends ItemMetaBuilder {
private Color color;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder color(@Nullable Color color) {
this.color = color;
handleCompound("display", nbtCompound -> {
if (color != null) {
nbtCompound.setInt("color", color.asRGB());
} else {
nbtCompound.remove("color");
}
});
setTag(COLOR, color);
return this;
}
@Override
public @NotNull LeatherArmorMeta build() {
return new LeatherArmorMeta(this, color);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("display") instanceof NBTCompound displayCompound) {
if (displayCompound.get("color") instanceof NBTInt colorInt) {
this.color = new Color(colorInt.getValue());
}
}
}
}
}

View File

@ -1,158 +1,81 @@
package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
public class MapMeta extends ItemMeta implements ItemMetaBuilder.Provider<MapMeta.Builder> {
public record MapMeta(TagReadable readable) implements ItemMetaView<MapMeta.Builder> {
private static final Tag<Integer> MAP_ID = Tag.Integer("map").defaultValue(0);
private static final Tag<Integer> MAP_SCALE_DIRECTION = Tag.Integer("map_scale_direction").defaultValue(0);
private static final Tag<List<Decoration>> DECORATIONS = Tag.Structure("Decorations", new TagSerializer<Decoration>() {
@Override
public @Nullable Decoration read(@NotNull TagReadable reader) {
final String id = reader.getTag(Tag.String("id"));
final Byte type = reader.getTag(Tag.Byte("type"));
final Byte x = reader.getTag(Tag.Byte("x"));
final Byte z = reader.getTag(Tag.Byte("z"));
final Double rot = reader.getTag(Tag.Double("rot"));
if (id == null || type == null || x == null || z == null || rot == null) return null;
return new Decoration(id, type, x, z, rot);
}
private final int mapId;
private final int mapScaleDirection;
private final List<Decoration> decorations;
private final Color mapColor;
@Override
public void write(@NotNull TagWritable writer, @NotNull Decoration value) {
writer.setTag(Tag.String("id"), value.id);
writer.setTag(Tag.Byte("type"), value.type);
writer.setTag(Tag.Byte("x"), value.x);
writer.setTag(Tag.Byte("z"), value.z);
writer.setTag(Tag.Double("rot"), value.rotation);
}
}).list().defaultValue(List.of());
private static final Tag<Color> MAP_COLOR = Tag.Integer("MapColor").path("display").map(Color::new, Color::asRGB);
protected MapMeta(ItemMetaBuilder metaBuilder,
int mapId,
int mapScaleDirection,
@NotNull List<Decoration> decorations,
@NotNull Color mapColor) {
super(metaBuilder);
this.mapId = mapId;
this.mapScaleDirection = mapScaleDirection;
this.decorations = List.copyOf(decorations);
this.mapColor = mapColor;
}
/**
* Gets the map id.
*
* @return the map id
*/
public int getMapId() {
return mapId;
return getTag(MAP_ID);
}
/**
* Gets the map scale direction.
*
* @return the map scale direction
*/
public int getMapScaleDirection() {
return mapScaleDirection;
return getTag(MAP_SCALE_DIRECTION);
}
/**
* Gets the map decorations.
*
* @return a modifiable list containing all the map decorations
*/
public List<Decoration> getDecorations() {
return decorations;
return getTag(DECORATIONS);
}
/**
* Gets the map color.
*
* @return the map color
*/
public @NotNull Color getMapColor() {
return this.mapColor;
return getTag(MAP_COLOR);
}
public static class Builder extends ItemMetaBuilder {
private int mapId;
private int mapScaleDirection = 1;
private List<Decoration> decorations = new CopyOnWriteArrayList<>();
private Color mapColor = new Color(0, 0, 0);
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder mapId(int value) {
this.mapId = value;
mutableNbt().setInt("map", mapId);
setTag(MAP_ID, value);
return this;
}
public Builder mapScaleDirection(int value) {
this.mapScaleDirection = value;
mutableNbt().setInt("map_scale_direction", value);
setTag(MAP_SCALE_DIRECTION, value);
return this;
}
public Builder decorations(List<Decoration> value) {
this.decorations = new ArrayList<>(value);
mutableNbt().set("Decorations", NBT.List(NBTType.TAG_Compound,
decorations.stream()
.map(decoration -> NBT.Compound(Map.of(
"id", NBT.String(decoration.id()),
"type", NBT.Byte(decoration.type()),
"x", NBT.Byte(decoration.x()),
"z", NBT.Byte(decoration.z()),
"rot", NBT.Double(decoration.rotation()))))
.toList()
));
setTag(DECORATIONS, value);
return this;
}
public Builder mapColor(Color value) {
this.mapColor = value;
handleCompound("display", displayCompound -> displayCompound.setInt("MapColor", mapColor.asRGB()));
setTag(MAP_COLOR, value);
return this;
}
@Override
public @NotNull ItemMeta build() {
return new MapMeta(this, mapId, mapScaleDirection, decorations, mapColor);
}
@Override
public void read(@NotNull NBTCompound compound) {
if (compound.get("map") instanceof NBTInt mapInt) {
this.mapId = mapInt.getValue();
}
if (compound.get("map_scale_direction") instanceof NBTInt mapScaleDirection) {
this.mapScaleDirection = mapScaleDirection.getValue();
}
if (compound.get("Decorations") instanceof NBTList<?> decorationsList &&
decorationsList.getSubtagType() == NBTType.TAG_Compound) {
List<Decoration> decorations = new ArrayList<>();
for (NBTCompound decorationCompound : decorationsList.<NBTCompound>asListOf()) {
final String id = decorationCompound.getString("id");
final byte type = decorationCompound.getAsByte("type");
byte x = 0;
if (decorationCompound.get("x") instanceof NBTByte xByte) {
x = xByte.getValue();
}
byte z = 0;
if (decorationCompound.get("z") instanceof NBTByte zByte) {
z = zByte.getValue();
}
double rotation = 0.0;
if (decorationCompound.get("rot") instanceof NBTDouble rotDouble) {
rotation = rotDouble.getValue();
}
decorations.add(new Decoration(id, type, x, z, rotation));
}
this.decorations = decorations;
}
if (compound.get("display") instanceof NBTCompound displayCompound) {
if (displayCompound.get("MapColor") instanceof NBTInt mapColor) {
this.mapColor = new Color(mapColor.getValue());
}
}
}
}
public record Decoration(String id, byte type, byte x, byte z, double rotation) {

View File

@ -1,89 +1,54 @@
package net.minestom.server.item.metadata;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.utils.Utils;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
public class PlayerHeadMeta extends ItemMeta implements ItemMetaBuilder.Provider<PlayerHeadMeta.Builder> {
public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView<PlayerHeadMeta.Builder> {
private static final Tag<UUID> SKULL_OWNER = Tag.UUID("Id").path("SkullOwner");
private static final Tag<PlayerSkin> SKIN = Tag.Structure("Properties", new TagSerializer<PlayerSkin>() {
@Override
public @Nullable PlayerSkin read(@NotNull TagReadable reader) {
final String value = reader.getTag(Tag.String("Value"));
final String signature = reader.getTag(Tag.String("Signature"));
if (value == null || signature == null) return null;
return new PlayerSkin(value, signature);
}
private final UUID skullOwner;
private final PlayerSkin playerSkin;
protected PlayerHeadMeta(@NotNull ItemMetaBuilder metaBuilder, UUID skullOwner,
@Nullable PlayerSkin playerSkin) {
super(metaBuilder);
this.skullOwner = skullOwner;
this.playerSkin = playerSkin;
}
@Override
public void write(@NotNull TagWritable writer, @NotNull PlayerSkin value) {
writer.setTag(Tag.String("Value"), value.textures());
writer.setTag(Tag.String("Signature"), value.signature());
}
}).path("SkullOwner");
public UUID getSkullOwner() {
return skullOwner;
return getTag(SKULL_OWNER);
}
public @Nullable PlayerSkin getPlayerSkin() {
return playerSkin;
return getTag(SKIN);
}
public static class Builder extends ItemMetaBuilder {
private UUID skullOwner;
private PlayerSkin playerSkin;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder skullOwner(@Nullable UUID skullOwner) {
this.skullOwner = skullOwner;
handleCompound("SkullOwner", nbtCompound ->
nbtCompound.setIntArray("Id", Utils.uuidToIntArray(this.skullOwner)));
setTag(SKULL_OWNER, skullOwner);
return this;
}
public Builder playerSkin(@Nullable PlayerSkin playerSkin) {
this.playerSkin = playerSkin;
handleCompound("SkullOwner", nbtCompound -> {
if (playerSkin == null) {
nbtCompound.remove("Properties");
return;
}
final String value = Objects.requireNonNullElse(this.playerSkin.textures(), "");
final String signature = Objects.requireNonNullElse(this.playerSkin.signature(), "");
NBTList<NBTCompound> textures = new NBTList<>(NBTType.TAG_Compound,
List.of(NBT.Compound(Map.of(
"Value", NBT.String(value),
"Signature", NBT.String(signature)))));
nbtCompound.set("Properties", NBT.Compound(Map.of("textures", textures)));
});
setTag(SKIN, playerSkin);
return this;
}
@Override
public @NotNull PlayerHeadMeta build() {
return new PlayerHeadMeta(this, skullOwner, playerSkin);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("SkullOwner") instanceof NBTCompound skullOwnerCompound) {
if (skullOwnerCompound.get("Id") instanceof NBTIntArray id) {
this.skullOwner = Utils.intArrayToUuid(id.getValue().copyArray());
}
if (skullOwnerCompound.get("Properties") instanceof NBTCompound propertyCompound) {
if (propertyCompound.get("textures") instanceof NBTList<?> textures &&
textures.getSubtagType() == NBTType.TAG_Compound) {
NBTCompound nbt = (NBTCompound) textures.get(0);
this.playerSkin = new PlayerSkin(nbt.getString("Value"), nbt.getString("Signature"));
}
}
}
}
}
}

View File

@ -1,108 +1,77 @@
package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.potion.CustomPotionEffect;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.tag.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PotionMeta extends ItemMeta implements ItemMetaBuilder.Provider<PotionMeta.Builder> {
public record PotionMeta(TagReadable readable) implements ItemMetaView<PotionMeta.Builder> {
private static final Tag<PotionType> POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, ProtocolObject::name);
private static final Tag<List<CustomPotionEffect>> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer<CustomPotionEffect>() {
@Override
public @Nullable CustomPotionEffect read(@NotNull TagReadable reader) {
final Byte id = reader.getTag(Tag.Byte("Id"));
final Byte amplifier = reader.getTag(Tag.Byte("Amplifier"));
final Integer duration = reader.getTag(Tag.Integer("Duration"));
final Boolean ambient = reader.getTag(Tag.Boolean("Ambient"));
final Boolean showParticles = reader.getTag(Tag.Boolean("ShowParticles"));
final Boolean showIcon = reader.getTag(Tag.Boolean("ShowIcon"));
if (id == null || amplifier == null || duration == null || ambient == null || showParticles == null || showIcon == null) {
return null;
}
return new CustomPotionEffect(id, amplifier, duration, ambient, showParticles, showIcon);
}
private final PotionType potionType;
private final List<CustomPotionEffect> customPotionEffects;
private final Color color;
protected PotionMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable PotionType potionType,
List<CustomPotionEffect> customPotionEffects,
Color color) {
super(metaBuilder);
this.potionType = potionType;
this.customPotionEffects = List.copyOf(customPotionEffects);
this.color = color;
}
@Override
public void write(@NotNull TagWritable writer, @NotNull CustomPotionEffect value) {
writer.setTag(Tag.Byte("Id"), value.id());
writer.setTag(Tag.Byte("Amplifier"), value.amplifier());
writer.setTag(Tag.Integer("Duration"), value.duration());
writer.setTag(Tag.Boolean("Ambient"), value.isAmbient());
writer.setTag(Tag.Boolean("ShowParticles"), value.showParticles());
writer.setTag(Tag.Boolean("ShowIcon"), value.showIcon());
}
}).list().defaultValue(List.of());
private static final Tag<Color> CUSTOM_POTION_COLOR = Tag.Integer("CustomPotionColor").path("display").map(Color::new, Color::asRGB);
public PotionType getPotionType() {
return potionType;
return getTag(POTION_TYPE);
}
public List<CustomPotionEffect> getCustomPotionEffects() {
return customPotionEffects;
return getTag(CUSTOM_POTION_EFFECTS);
}
public Color getColor() {
return color;
return getTag(CUSTOM_POTION_COLOR);
}
public static class Builder extends ItemMetaBuilder {
private PotionType potionType;
private List<CustomPotionEffect> customPotionEffects = new ArrayList<>();
private Color color;
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder potionType(@NotNull PotionType potionType) {
this.potionType = potionType;
mutableNbt().setString("Potion", potionType.name());
setTag(POTION_TYPE, potionType);
return this;
}
public Builder effects(@NotNull List<CustomPotionEffect> customPotionEffects) {
this.customPotionEffects = customPotionEffects;
mutableNbt().set("CustomPotionEffects", NBT.List(NBTType.TAG_Compound,
customPotionEffects.stream()
.map(customPotionEffect -> NBT.Compound(Map.of(
"Id", NBT.Byte(customPotionEffect.id()),
"Amplifier", NBT.Byte(customPotionEffect.amplifier()),
"Duration", NBT.Int(customPotionEffect.duration()),
"Ambient", NBT.Boolean(customPotionEffect.isAmbient()),
"ShowParticles", NBT.Boolean(customPotionEffect.showParticles()),
"ShowIcon", NBT.Boolean(customPotionEffect.showIcon()))))
.toList()
));
setTag(CUSTOM_POTION_EFFECTS, customPotionEffects);
return this;
}
public Builder color(@NotNull Color color) {
this.color = color;
mutableNbt().setInt("CustomPotionColor", color.asRGB());
setTag(CUSTOM_POTION_COLOR, color);
return this;
}
@Override
public @NotNull PotionMeta build() {
return new PotionMeta(this, potionType, customPotionEffects, color);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("Potion") instanceof NBTString potion) {
this.potionType = PotionType.fromNamespaceId(potion.getValue());
}
if (nbtCompound.get("CustomPotionEffects") instanceof NBTList<?> customEffectList &&
customEffectList.getSubtagType() == NBTType.TAG_Compound) {
for (NBTCompound potionCompound : customEffectList.<NBTCompound>asListOf()) {
final byte id = potionCompound.getAsByte("Id");
final byte amplifier = potionCompound.getAsByte("Amplifier");
final int duration = potionCompound.containsKey("Duration") ? potionCompound.getNumber("Duration").intValue() : (int) Duration.ofSeconds(30).toMillis();
final boolean ambient = potionCompound.containsKey("Ambient") ? potionCompound.getAsByte("Ambient") == 1 : false;
final boolean showParticles = potionCompound.containsKey("ShowParticles") ? potionCompound.getAsByte("ShowParticles") == 1 : true;
final boolean showIcon = potionCompound.containsKey("ShowIcon") ? potionCompound.getAsByte("ShowIcon") == 1 : true;
this.customPotionEffects.add(new CustomPotionEffect(id, amplifier, duration, ambient, showParticles, showIcon));
}
}
if (nbtCompound.get("CustomPotionColor") instanceof NBTInt color) {
this.color = new Color(color.getValue());
}
}
}
}
}

View File

@ -1,43 +0,0 @@
package net.minestom.server.item.metadata;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
public class SpawnEggMeta extends ItemMeta implements ItemMetaBuilder.Provider<SpawnEggMeta.Builder> {
private final EntityType entityType;
protected SpawnEggMeta(@NotNull ItemMetaBuilder metaBuilder, @Nullable EntityType entityType) {
super(metaBuilder);
this.entityType = entityType;
}
public @Nullable EntityType getEntityType() {
return entityType;
}
public static class Builder extends ItemMetaBuilder {
private EntityType entityType;
public Builder entityType(@Nullable EntityType entityType) {
this.entityType = entityType;
// TODO nbt
return this;
}
@Override
public @NotNull SpawnEggMeta build() {
return new SpawnEggMeta(this, entityType);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
// TODO
}
}
}

View File

@ -2,94 +2,34 @@ package net.minestom.server.item.metadata;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class WritableBookMeta extends ItemMeta implements ItemMetaBuilder.Provider<WritableBookMeta.Builder> {
private final String author;
private final String title;
private final List<Component> pages;
protected WritableBookMeta(@NotNull ItemMetaBuilder metaBuilder,
@Nullable String author, @Nullable String title,
@NotNull List<@NotNull Component> pages) {
super(metaBuilder);
this.author = author;
this.title = title;
this.pages = List.copyOf(pages);
}
public @Nullable String getAuthor() {
return author;
}
public @Nullable String getTitle() {
return title;
}
public record WritableBookMeta(TagReadable readable) implements ItemMetaView<WritableBookMeta.Builder> {
private static final Tag<List<Component>> PAGES = Tag.String("pages")
.<Component>map(s -> LegacyComponentSerializer.legacySection().deserialize(s),
textComponent -> LegacyComponentSerializer.legacySection().serialize(textComponent))
.list().defaultValue(List.of());
public @NotNull List<@NotNull Component> getPages() {
return pages;
return getTag(PAGES);
}
public static class Builder extends ItemMetaBuilder {
private String author;
private String title;
private List<Component> pages = new ArrayList<>();
public Builder author(@Nullable String author) {
this.author = author;
handleNullable(author, "author",
() -> new NBTString(Objects.requireNonNull(author)));
return this;
}
public Builder title(@Nullable String title) {
this.title = title;
handleNullable(title, "title", () -> NBT.String(Objects.requireNonNull(title)));
return this;
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder pages(@NotNull List<@NotNull Component> pages) {
this.pages = new ArrayList<>(pages);
handleCollection(pages, "pages", () -> NBT.List(
NBTType.TAG_String,
pages.stream()
.map(page -> new NBTString(LegacyComponentSerializer.legacySection().serialize(page)))
.toList()
));
setTag(PAGES, pages);
return this;
}
@Override
public @NotNull WritableBookMeta build() {
return new WritableBookMeta(this, author, title, pages);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("author") instanceof NBTString author) {
this.author = author.getValue();
}
if (nbtCompound.get("title") instanceof NBTString title) {
this.title = title.getValue();
}
if (nbtCompound.get("pages") instanceof NBTList<?> list &&
list.getSubtagType() == NBTType.TAG_String) {
for (NBTString page : list.<NBTString>asListOf()) {
this.pages.add(LegacyComponentSerializer.legacySection().deserialize(page.getValue()));
}
}
}
}
}

View File

@ -1,161 +1,93 @@
package net.minestom.server.item.metadata;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.minestom.server.adventure.Localizable;
import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class WrittenBookMeta extends ItemMeta implements ItemMetaBuilder.Provider<WrittenBookMeta.Builder> {
private final boolean resolved;
private final WrittenBookGeneration generation;
private final String author;
private final String title;
private final List<Component> pages;
protected WrittenBookMeta(@NotNull ItemMetaBuilder metaBuilder, boolean resolved,
@Nullable WrittenBookGeneration generation,
@Nullable String author, @Nullable String title,
@NotNull List<@NotNull Component> pages) {
super(metaBuilder);
this.resolved = resolved;
this.generation = generation;
this.author = author;
this.title = title;
this.pages = List.copyOf(pages);
}
public record WrittenBookMeta(TagReadable readable) implements ItemMetaView<WrittenBookMeta.Builder> {
private static final Tag<Boolean> RESOLVED = Tag.Boolean("resolved");
private static final Tag<WrittenBookGeneration> GENERATION = Tag.Integer("resolved").map(integer -> WrittenBookGeneration.values()[integer], Enum::ordinal);
private static final Tag<String> AUTHOR = Tag.String("author");
private static final Tag<String> TITLE = Tag.String("title");
private static final Tag<List<Component>> PAGES = Tag.String("pages")
.<Component>map(s -> LegacyComponentSerializer.legacySection().deserialize(s),
textComponent -> LegacyComponentSerializer.legacySection().serialize(textComponent))
.list().defaultValue(List.of());
public boolean isResolved() {
return resolved;
return getTag(RESOLVED);
}
public @Nullable WrittenBookGeneration getGeneration() {
return generation;
return getTag(GENERATION);
}
public @Nullable String getAuthor() {
return author;
return getTag(AUTHOR);
}
public @Nullable String getTitle() {
return title;
return getTag(TITLE);
}
public @NotNull List<@NotNull Component> getPages() {
return pages;
return getTag(PAGES);
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
}
public enum WrittenBookGeneration {
ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED
}
/**
* Creates a written book meta from an Adventure book. This meta will not be
* resolved and the generation will default to {@link WrittenBookGeneration#ORIGINAL}.
*
* @param book the book
* @param localizable who the book is for
* @return the meta
*/
public static @NotNull WrittenBookMeta fromAdventure(@NotNull Book book, @NotNull Localizable localizable) {
return new Builder()
.resolved(false)
.generation(WrittenBookGeneration.ORIGINAL)
.author(GsonComponentSerializer.gson().serialize(GlobalTranslator.render(book.author(), Objects.requireNonNullElse(localizable.getLocale(), MinestomAdventure.getDefaultLocale()))))
.title(GsonComponentSerializer.gson().serialize(GlobalTranslator.render(book.title(), Objects.requireNonNullElse(localizable.getLocale(), MinestomAdventure.getDefaultLocale()))))
.pages(book.pages())
.build();
}
public static class Builder extends ItemMetaBuilder {
private boolean resolved;
private WrittenBookGeneration generation;
private String author;
private String title;
private List<Component> pages = new ArrayList<>();
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder resolved(boolean resolved) {
this.resolved = resolved;
mutableNbt().set("resolved", NBT.Boolean(resolved));
setTag(RESOLVED, resolved);
return this;
}
public Builder generation(@Nullable WrittenBookGeneration generation) {
this.generation = generation;
handleNullable(generation, "generation",
() -> new NBTInt(Objects.requireNonNull(generation).ordinal()));
setTag(GENERATION, generation);
return this;
}
public Builder author(@Nullable String author) {
this.author = author;
handleNullable(author, "author",
() -> new NBTString(Objects.requireNonNull(author)));
setTag(AUTHOR, author);
return this;
}
public Builder author(@Nullable Component author) {
return author(author != null ? LegacyComponentSerializer.legacySection().serialize(author) : null);
}
public Builder title(@Nullable String title) {
this.title = title;
handleNullable(title, "title",
() -> new NBTString(Objects.requireNonNull(title)));
setTag(TITLE, title);
return this;
}
public Builder title(@Nullable Component title) {
return title(title != null ? LegacyComponentSerializer.legacySection().serialize(title) : null);
}
public Builder pages(@NotNull List<@NotNull Component> pages) {
this.pages = new ArrayList<>(pages);
handleCollection(pages, "pages", () -> NBT.List(
NBTType.TAG_String,
pages.stream()
.map(page -> new NBTString(GsonComponentSerializer.gson().serialize(page)))
.toList()
));
setTag(PAGES, pages);
return this;
}
public Builder pages(Component... pages) {
return pages(Arrays.asList(pages));
}
@Override
public @NotNull WrittenBookMeta build() {
return new WrittenBookMeta(this, resolved, generation, author, title, pages);
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
if (nbtCompound.get("resolved") instanceof NBTByte resolved) {
this.resolved = resolved.asBoolean();
}
if (nbtCompound.get("generation") instanceof NBTInt generation) {
this.generation = WrittenBookGeneration.values()[generation.getValue()];
}
if (nbtCompound.get("author") instanceof NBTString author) {
this.author = author.getValue();
}
if (nbtCompound.get("title") instanceof NBTString title) {
this.title = title.getValue();
}
if (nbtCompound.get("pages") instanceof NBTList<?> list &&
list.getSubtagType() == NBTType.TAG_String) {
for (NBTString page : list.<NBTString>asListOf()) {
this.pages.add(GsonComponentSerializer.gson().deserialize(page.getValue()));
}
}
}
}
}

View File

@ -24,12 +24,12 @@ public final class VanillaStackingRule implements StackingRule {
@Override
public int getAmount(@NotNull ItemStack itemStack) {
return itemStack.getAmount();
return itemStack.amount();
}
@Override
public int getMaxSize(@NotNull ItemStack itemStack) {
return itemStack.getMaterial().maxStackSize();
return itemStack.material().maxStackSize();
}
@Override

View File

@ -63,7 +63,7 @@ public class BlockPlacementListener {
return;
}
final Material useMaterial = usedItem.getMaterial();
final Material useMaterial = usedItem.material();
if (!useMaterial.isBlock()) {
// Player didn't try to place a block but interacted with one
PlayerUseItemOnBlockEvent event = new PlayerUseItemOnBlockEvent(player, hand, usedItem, blockPosition, blockFace);
@ -78,7 +78,7 @@ public class BlockPlacementListener {
canPlaceBlock = false; // Spectators can't place blocks
} else if (player.getGameMode() == GameMode.ADVENTURE) {
//Check if the block can be placed on the block
canPlaceBlock = usedItem.getMeta().getCanPlaceOn().contains(interactedBlock);
canPlaceBlock = usedItem.meta().getCanPlaceOn().contains(interactedBlock);
}
// Get the newly placed block position

View File

@ -60,7 +60,7 @@ public final class PlayerDiggingListener {
} else if (gameMode == GameMode.ADVENTURE) {
// Check if the item can break the block with the current item
final ItemStack itemInMainHand = player.getItemInMainHand();
if (!itemInMainHand.getMeta().getCanDestroy().contains(block)) {
if (!itemInMainHand.meta().getCanDestroy().contains(block)) {
return new DiggingResult(block, false);
}
} else if (gameMode == GameMode.CREATIVE) {

View File

@ -28,7 +28,7 @@ public class UseItemListener {
}
itemStack = useItemEvent.getItemStack();
final Material material = itemStack.getMaterial();
final Material material = itemStack.material();
// Equip armor with right click
final EquipmentSlot equipmentSlot = material.registry().equipmentSlot();

View File

@ -8,20 +8,19 @@ import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
// for lack of a better name
@ApiStatus.Internal
public final class NBTUtils {
private final static Logger LOGGER = LoggerFactory.getLogger(NBTUtils.class);
/**
* An Adventure codec to convert between NBT and SNBT.
@ -32,7 +31,6 @@ public final class NBTUtils {
public static final Codec<NBT, String, NBTException, RuntimeException> SNBT_CODEC = MinestomAdventure.NBT_CODEC;
private NBTUtils() {
}
/**
@ -76,15 +74,13 @@ public final class NBTUtils {
public static void saveAllItems(@NotNull List<NBTCompound> list, @NotNull Inventory inventory) {
for (int i = 0; i < inventory.getSize(); i++) {
final ItemStack stack = inventory.getItemStack(i);
NBTCompound tag = stack.getMeta().toNBT();
final NBTCompound tag = stack.meta().toNBT();
final int slotIndex = i;
list.add(NBT.Compound(nbt -> {
nbt.set("tag", tag);
nbt.setByte("Slot", (byte) slotIndex);
nbt.setByte("Count", (byte) stack.getAmount());
nbt.setString("id", stack.getMaterial().name());
nbt.setByte("Count", (byte) stack.amount());
nbt.setString("id", stack.material().name());
}));
}
}
@ -117,22 +113,4 @@ public final class NBTUtils {
compound : null;
return ItemStack.fromNBT(material, nbtCompound, count);
}
public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
for (NBTCompound enchantment : enchantments) {
final short level = enchantment.getAsShort("lvl");
final String id = enchantment.getString("id");
final Enchantment enchant = Enchantment.fromNamespaceId(id);
if (enchant != null) {
setter.applyEnchantment(enchant, level);
} else {
LOGGER.warn("Unknown enchantment type: {}", id);
}
}
}
@FunctionalInterface
public interface EnchantmentSetter {
void applyEnchantment(Enchantment name, short level);
}
}

View File

@ -307,9 +307,9 @@ public class BinaryWriter extends OutputStream {
writeBoolean(false);
} else {
writeBoolean(true);
writeVarInt(itemStack.getMaterial().id());
writeByte((byte) itemStack.getAmount());
write(itemStack.getMeta());
writeVarInt(itemStack.material().id());
writeByte((byte) itemStack.amount());
write(itemStack.meta());
}
}
@ -324,7 +324,7 @@ public class BinaryWriter extends OutputStream {
MinecraftServer.getExceptionManager().handleException(e);
}
}
/**
* Writes the given writeable object into this writer.
*

View File

@ -58,7 +58,7 @@ public record BiomeParticle(float probability, Option option) {
@Override
public NBTCompound toNbt() {
//todo test count might be wrong type
NBTCompound nbtCompound = item.getMeta().toNBT();
NBTCompound nbtCompound = item.meta().toNBT();
return nbtCompound.modify(n -> {
n.setString("type", type);
});

View File

@ -181,7 +181,7 @@ public class EventNodeTest {
AtomicBoolean childResult = new AtomicBoolean(false);
var node = EventNode.type("item_node", EventFilter.ITEM,
(event, item) -> item.getMaterial() == Material.DIAMOND);
(event, item) -> item.material() == Material.DIAMOND);
var child = EventNode.type("item_node2", EventFilter.ITEM)
.addListener(ItemTestEvent.class, event -> childResult.set(true));
node.addChild(child);
@ -204,7 +204,7 @@ public class EventNodeTest {
var node = EventNode.all("main");
AtomicBoolean result = new AtomicBoolean(false);
var binding = EventBinding.filtered(EventFilter.ITEM, itemStack -> itemStack.getMaterial() == Material.DIAMOND)
var binding = EventBinding.filtered(EventFilter.ITEM, itemStack -> itemStack.material() == Material.DIAMOND)
.map(ItemTestEvent.class, (itemStack, itemTestEvent) -> result.set(true))
.build();
node.register(binding);

View File

@ -0,0 +1,21 @@
package net.minestom.server.item;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ItemAirTest {
@Test
public void testAir() {
var item = ItemStack.of(Material.DIAMOND_SWORD);
assertFalse(item.isAir());
assertTrue(ItemStack.AIR.isAir());
var emptyItem = item.withAmount(0);
assertTrue(emptyItem.isAir());
assertEquals(emptyItem, ItemStack.AIR, "AIR item can be compared to empty item");
assertSame(emptyItem, ItemStack.AIR, "AIR item identity can be compared to empty item");
assertSame(ItemStack.AIR, ItemStack.fromNBT(Material.DIAMOND, null, 0));
assertSame(ItemStack.AIR, ItemStack.builder(Material.DIAMOND).amount(0).build());
}
}

View File

@ -0,0 +1,36 @@
package net.minestom.server.item;
import net.minestom.server.item.metadata.BundleMeta;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ItemMetaTest {
@Test
public void defaultMeta() {
var item = ItemStack.builder(Material.BUNDLE).build();
assertNotNull(item.meta());
}
@Test
public void fromNBT() {
var compound = NBT.Compound(Map.of("value", NBT.Int(5)));
var item = ItemStack.builder(Material.BUNDLE).meta(compound).build();
assertEquals(compound, item.meta().toNBT());
}
@Test
public void bundle() {
var item = ItemStack.builder(Material.BUNDLE)
.meta(BundleMeta.class, bundleMetaBuilder -> {
bundleMetaBuilder.addItem(ItemStack.of(Material.DIAMOND, 5));
bundleMetaBuilder.addItem(ItemStack.of(Material.RABBIT_FOOT, 5));
})
.build();
assertEquals(2, item.meta(BundleMeta.class).getItems().size());
}
}

View File

@ -0,0 +1,20 @@
package net.minestom.server.item;
import net.minestom.server.item.metadata.BundleMeta;
import net.minestom.server.tag.TagHandler;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
public class ItemMetaViewTest {
@Test
public void viewType() {
assertEquals(BundleMeta.Builder.class, ItemMetaViewImpl.viewType(BundleMeta.class));
}
@Test
public void construct() {
assertInstanceOf(BundleMeta.Builder.class, ItemMetaViewImpl.constructBuilder(BundleMeta.class, TagHandler.newHandler()));
}
}

View File

@ -13,27 +13,35 @@ import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class ItemTest {
private static final ItemStack ITEM = ItemStack.builder(Material.STONE)
.displayName(Component.text("Display name!", NamedTextColor.GREEN))
.lore(Component.text("Line 1"), Component.text("Line 2"))
.meta(metaBuilder ->
metaBuilder.enchantment(Enchantment.EFFICIENCY, (short) 10)
.hideFlag(ItemHideFlag.HIDE_ENCHANTS))
.build();
@Test
public void testFields() {
var item = ItemStack.of(Material.DIAMOND_SWORD);
assertEquals(item.getMaterial(), Material.DIAMOND_SWORD, "Material must be the same");
assertEquals(item.getAmount(), 1, "Default item amount must be 1");
assertEquals(item.material(), Material.DIAMOND_SWORD, "Material must be the same");
assertEquals(item.amount(), 1, "Default item amount must be 1");
assertNull(item.getDisplayName(), "Default item display name must be null");
assertTrue(item.getLore().isEmpty(), "Default item lore must be empty");
ItemStack finalItem = item;
assertThrows(Exception.class, () -> finalItem.getLore().add(Component.text("Hey!")), "Lore list cannot be modified directly");
item = item.withAmount(5);
assertEquals(item.getAmount(), 5, "Items with different amount should not be equals");
assertEquals(item.withAmount(amount -> amount * 2).getAmount(), 10, "Amount must be multiplied by 2");
assertEquals(item.amount(), 5, "Items with different amount should not be equals");
assertEquals(item.withAmount(amount -> amount * 2).amount(), 10, "Amount must be multiplied by 2");
}
@Test
public void defaultBuilder() {
var item = ItemStack.builder(Material.DIAMOND_SWORD).build();
assertEquals(item.material(), Material.DIAMOND_SWORD, "Material must be the same");
assertEquals(item.amount(), 1, "Default item amount must be 1");
assertNull(item.getDisplayName(), "Default item display name must be null");
assertTrue(item.getLore().isEmpty(), "Default item lore must be empty");
ItemStack finalItem = item;
assertThrows(Exception.class, () -> finalItem.getLore().add(Component.text("Hey!")), "Lore list cannot be modified directly");
item = item.withAmount(5);
assertEquals(item.amount(), 5, "Items with different amount should not be equals");
assertEquals(item.withAmount(amount -> amount * 2).amount(), 10, "Amount must be multiplied by 2");
}
@Test
@ -48,67 +56,56 @@ public class ItemTest {
assertFalse(item1.isSimilar(item2.withDisplayName(Component.text("Hey!"))));
}
@Test
public void testAir() {
var item = ItemStack.of(Material.DIAMOND_SWORD);
assertFalse(item.isAir());
assertTrue(ItemStack.AIR.isAir());
var emptyItem = item.withAmount(0);
assertTrue(emptyItem.isAir());
assertEquals(emptyItem, ItemStack.AIR, "AIR item can be compared to empty item");
assertSame(emptyItem, ItemStack.AIR, "AIR item identity can be compared to empty item");
}
@Test
public void testItemNbt() {
var itemNbt = ITEM.toItemNBT();
assertEquals(itemNbt.getString("id"), ITEM.getMaterial().name(), "id string should be the material name");
assertEquals(itemNbt.getByte("Count"), (byte) ITEM.getAmount(), "Count byte should be the item amount");
var itemNbt = createItem().toItemNBT();
assertEquals(itemNbt.getString("id"), createItem().material().name(), "id string should be the material name");
assertEquals(itemNbt.getByte("Count"), (byte) createItem().amount(), "Count byte should be the item amount");
var metaNbt = itemNbt.getCompound("tag");
var metaNbt2 = ITEM.getMeta().toNBT();
var metaNbt2 = createItem().meta().toNBT();
assertEquals(metaNbt, metaNbt2, "tag compound should be equal to the meta nbt");
}
@Test
public void testFromNbt() {
var itemNbt = ITEM.toItemNBT();
var itemNbt = createItem().toItemNBT();
var item = ItemStack.fromItemNBT(itemNbt);
assertEquals(ITEM, item, "Items must be equal if created from the same item nbt");
assertEquals(createItem(), item, "Items must be equal if created from the same item nbt");
assertEquals(itemNbt, item.toItemNBT(), "Item nbt must be equal back");
var metaNbt = ITEM.getMeta().toNBT();
item = ItemStack.fromNBT(ITEM.getMaterial(), metaNbt, ITEM.getAmount());
assertEquals(ITEM, item, "Items must be equal if created from the same meta nbt");
var metaNbt = createItem().meta().toNBT();
item = ItemStack.fromNBT(createItem().material(), metaNbt, createItem().amount());
assertEquals(createItem(), item, "Items must be equal if created from the same meta nbt");
}
@Test
public void testEnchant() {
var item = ItemStack.of(Material.DIAMOND_SWORD);
var enchantments = item.getMeta().getEnchantmentMap();
var enchantments = item.meta().getEnchantmentMap();
assertTrue(enchantments.isEmpty(), "items do not have enchantments by default");
item = item.withMeta(meta -> meta.enchantment(Enchantment.EFFICIENCY, (short) 10));
enchantments = item.getMeta().getEnchantmentMap();
enchantments = item.meta().getEnchantmentMap();
assertEquals(enchantments.size(), 1);
assertEquals(enchantments.get(Enchantment.EFFICIENCY), (short) 10);
item = item.withMeta(meta -> meta.enchantment(Enchantment.INFINITY, (short) 5));
enchantments = item.getMeta().getEnchantmentMap();
enchantments = item.meta().getEnchantmentMap();
assertEquals(enchantments.size(), 2);
assertEquals(enchantments.get(Enchantment.EFFICIENCY), (short) 10);
assertEquals(enchantments.get(Enchantment.INFINITY), (short) 5);
item = item.withMeta(meta -> meta.enchantments(Map.of()));
enchantments = item.getMeta().getEnchantmentMap();
enchantments = item.meta().getEnchantmentMap();
assertTrue(enchantments.isEmpty());
// Ensure that enchantments can still be modified after being emptied
item = item.withMeta(meta -> meta.enchantment(Enchantment.EFFICIENCY, (short) 10));
enchantments = item.getMeta().getEnchantmentMap();
enchantments = item.meta().getEnchantmentMap();
assertEquals(enchantments.get(Enchantment.EFFICIENCY), (short) 10);
item = item.withMeta(ItemMetaBuilder::clearEnchantment);
enchantments = item.getMeta().getEnchantmentMap();
item = item.withMeta(ItemMeta.Builder::clearEnchantment);
enchantments = item.meta().getEnchantmentMap();
assertTrue(enchantments.isEmpty());
}
@ -116,13 +113,13 @@ public class ItemTest {
public void testLore() {
var item = ItemStack.of(Material.DIAMOND_SWORD);
assertEquals(List.of(), item.getLore());
assertNull(item.getMeta().toNBT().get("display"));
assertNull(item.meta().toNBT().get("display"));
{
var lore = List.of(Component.text("Hello"));
item = item.withLore(lore);
assertEquals(lore, item.getLore());
var loreNbt = item.getMeta().toNBT().getCompound("display").<NBTString>getList("Lore");
var loreNbt = item.meta().toNBT().getCompound("display").<NBTString>getList("Lore");
assertNotNull(loreNbt);
assertEquals(loreNbt.getSize(), 1);
assertEquals(lore, loreNbt.asListView().stream().map(line -> GsonComponentSerializer.gson().deserialize(line.getValue())).toList());
@ -132,7 +129,7 @@ public class ItemTest {
var lore = List.of(Component.text("Hello"), Component.text("World"));
item = item.withLore(lore);
assertEquals(lore, item.getLore());
var loreNbt = item.getMeta().toNBT().getCompound("display").<NBTString>getList("Lore");
var loreNbt = item.meta().toNBT().getCompound("display").<NBTString>getList("Lore");
assertNotNull(loreNbt);
assertEquals(loreNbt.getSize(), 2);
assertEquals(lore, loreNbt.asListView().stream().map(line -> GsonComponentSerializer.gson().deserialize(line.getValue())).toList());
@ -142,7 +139,7 @@ public class ItemTest {
var lore = Stream.of("string test").map(Component::text).toList();
item = item.withLore(lore);
assertEquals(lore, item.getLore());
var loreNbt = item.getMeta().toNBT().getCompound("display").<NBTString>getList("Lore");
var loreNbt = item.meta().toNBT().getCompound("display").<NBTString>getList("Lore");
assertNotNull(loreNbt);
assertEquals(loreNbt.getSize(), 1);
assertEquals(lore, loreNbt.asListView().stream().map(line -> GsonComponentSerializer.gson().deserialize(line.getValue())).toList());
@ -150,7 +147,7 @@ public class ItemTest {
// Ensure that lore can be properly removed without residual (display compound)
item = item.withLore(List.of());
assertNull(item.getMeta().toNBT().get("display"));
assertNull(item.meta().toNBT().get("display"));
}
@Test
@ -162,4 +159,14 @@ public class ItemTest {
assertNotNull(item2.getDisplayName());
assertNotEquals(item1, item2, "Item builder should be reusable");
}
static ItemStack createItem() {
return ItemStack.builder(Material.STONE)
.displayName(Component.text("Display name!", NamedTextColor.GREEN))
.lore(Component.text("Line 1"), Component.text("Line 2"))
.meta(metaBuilder ->
metaBuilder.enchantment(Enchantment.EFFICIENCY, (short) 10)
.hideFlag(ItemHideFlag.HIDE_ENCHANTS))
.build();
}
}

View File

@ -1,87 +0,0 @@
package regressions;
import net.minestom.server.item.ItemMeta;
import net.minestom.server.item.ItemMetaBuilder;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTString;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import org.junit.jupiter.api.Test;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.*;
public class ItemMetaBuilderRegressions {
private static class BasicMetaBuilder extends ItemMetaBuilder {
@Override
public @NotNull ItemMeta build() {
return new ItemMeta(this) {};
}
@Override
public void read(@NotNull NBTCompound nbtCompound) {
// don't care
}
@Override
public void handleCompound(@NotNull String key, @NotNull Consumer<@NotNull MutableNBTCompound> consumer) {
super.handleCompound(key, consumer);
}
}
@Test
public void handleCompoundShouldUsePreviousValue() {
BasicMetaBuilder builder = new BasicMetaBuilder();
builder.handleCompound("test", nbt -> {
nbt.setString("my_key", "AAA");
});
NBTCompound nbt = builder.build().toNBT().getCompound("test");
assertTrue(nbt.contains("my_key"));
assertEquals(1, nbt.getSize());
assertTrue(nbt.get("my_key") instanceof NBTString);
assertEquals("AAA", nbt.getString("my_key"));
builder.handleCompound("test", n -> {
n.setString("my_other_key", "BBB");
});
nbt = builder.build().toNBT().getCompound("test");
assertTrue(nbt.contains("my_key"));
assertTrue(nbt.contains("my_other_key"));
assertEquals(2, nbt.getSize());
assertTrue(nbt.get("my_key") instanceof NBTString);
assertTrue(nbt.get("my_other_key") instanceof NBTString);
assertEquals("AAA", nbt.getString("my_key"));
assertEquals("BBB", nbt.getString("my_other_key"));
}
@Test
public void clearingShouldRemoveData() {
BasicMetaBuilder builder = new BasicMetaBuilder();
builder.handleCompound("test", nbt -> {
nbt.setString("my_key", "AAA");
});
NBTCompound nbt = builder.build().toNBT().getCompound("test");
assertTrue(nbt.contains("my_key"));
assertEquals(1, nbt.getSize());
assertTrue(nbt.get("my_key") instanceof NBTString);
assertEquals("AAA", nbt.getString("my_key"));
builder.handleCompound("test", n -> {
n.clear();
});
NBTCompound rootNBT = builder.build().toNBT();
assertFalse(rootNBT.contains("test"));
assertEquals(0, rootNBT.getSize());
}
}