feat: first version of a bunch of components

This commit is contained in:
mworzala 2024-04-10 21:42:15 -04:00
parent 8de5370849
commit b40ae720fc
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
55 changed files with 1817 additions and 710 deletions

View File

@ -42,7 +42,6 @@ public final class ServerFlag {
public static final @NotNull String AUTH_URL = System.getProperty("minestom.auth.url", "https://sessionserver.mojang.com/session/minecraft/hasJoined");
// World
public static final @Nullable String STACKING_RULE = System.getProperty("minestom.stacking-rule");
public static final int WORLD_BORDER_SIZE = Integer.getInteger("minestom.world-border-size", 29999984);
// Maps

View File

@ -1,5 +1,6 @@
package net.minestom.server.entity.attribute;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@ -7,12 +8,12 @@ import java.util.UUID;
/**
* Represent an attribute modifier.
*/
public class AttributeModifier {
private final double amount;
private final String name;
private final AttributeOperation operation;
private final UUID id;
public record AttributeModifier(
@NotNull UUID id,
@NotNull String name,
double amount,
@NotNull AttributeOperation operation
) implements NetworkBuffer.Writer {
/**
* Creates a new modifier with a random id.
@ -25,19 +26,17 @@ public class AttributeModifier {
this(UUID.randomUUID(), name, amount, operation);
}
/**
* Creates a new modifier.
*
* @param id the id of this modifier
* @param name the name of this modifier
* @param amount the value of this modifier
* @param operation the operation to apply this modifier with
*/
public AttributeModifier(@NotNull UUID id, @NotNull String name, double amount, @NotNull AttributeOperation operation) {
this.id = id;
this.name = name;
this.amount = amount;
this.operation = operation;
public AttributeModifier(@NotNull NetworkBuffer reader) {
this(reader.read(NetworkBuffer.UUID), reader.read(NetworkBuffer.STRING),
reader.read(NetworkBuffer.DOUBLE), reader.readEnum(AttributeOperation.class));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.UUID, id);
writer.write(NetworkBuffer.STRING, name);
writer.write(NetworkBuffer.DOUBLE, amount);
writer.writeEnum(AttributeOperation.class, operation);
}
/**
@ -45,8 +44,8 @@ public class AttributeModifier {
*
* @return the id of this modifier
*/
@NotNull
public UUID getId() {
@Deprecated
public @NotNull UUID getId() {
return id;
}
@ -55,8 +54,8 @@ public class AttributeModifier {
*
* @return the name of this modifier
*/
@NotNull
public String getName() {
@Deprecated
public @NotNull String getName() {
return name;
}
@ -65,6 +64,7 @@ public class AttributeModifier {
*
* @return the value of this modifier
*/
@Deprecated
public double getAmount() {
return amount;
}
@ -74,8 +74,8 @@ public class AttributeModifier {
*
* @return the operation of this modifier
*/
@NotNull
public AttributeOperation getOperation() {
@Deprecated
public @NotNull AttributeOperation getOperation() {
return operation;
}
}

View File

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

View File

@ -4,8 +4,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.item.component.ItemComponentPatch;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.Taggable;
@ -17,8 +16,10 @@ import org.jetbrains.annotations.UnknownNullability;
import java.util.*;
import java.util.function.Consumer;
public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer
permits ItemMetaImpl {
@Deprecated
public sealed interface ItemMeta extends TagReadable permits ItemMetaImpl {
@NotNull ItemComponentPatch components();
@Override
<T> @UnknownNullability T getTag(@NotNull Tag<T> tag);
@ -31,69 +32,48 @@ public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer
@NotNull String toSNBT();
@Contract(pure = true)
default int getDamage() {
return getTag(ItemTags.DAMAGE);
}
int getDamage();
@Contract(pure = true)
default boolean isUnbreakable() {
return getTag(ItemTags.UNBREAKABLE);
}
boolean isUnbreakable();
@Contract(pure = true)
default int getHideFlag() {
return getTag(ItemTags.HIDE_FLAGS);
}
int getHideFlag();
@Contract(pure = true)
default @Nullable Component getDisplayName() {
return getTag(ItemTags.NAME);
}
@Nullable Component getDisplayName();
@Contract(pure = true)
default @NotNull List<@NotNull Component> getLore() {
return getTag(ItemTags.LORE);
}
@NotNull List<@NotNull Component> getLore();
@Contract(pure = true)
default @NotNull Map<Enchantment, Short> getEnchantmentMap() {
return getTag(ItemTags.ENCHANTMENTS);
}
@NotNull Map<Enchantment, Short> getEnchantmentMap();
@Contract(pure = true)
default @NotNull List<@NotNull ItemAttribute> getAttributes() {
return getTag(ItemTags.ATTRIBUTES);
}
@NotNull List<@NotNull ItemAttribute> getAttributes();
@Contract(pure = true)
default int getCustomModelData() {
return getTag(ItemTags.CUSTOM_MODEL_DATA);
}
int getCustomModelData();
@Contract(pure = true)
default @NotNull Set<@NotNull String> getCanDestroy() {
return Set.copyOf(getTag(ItemTags.CAN_DESTROY));
}
@NotNull Set<@NotNull String> getCanDestroy();
@Contract(pure = true)
default boolean canDestroy(@NotNull Block block) {
return getCanDestroy().contains(block.name());
}
boolean canDestroy(@NotNull Block block);
@Contract(pure = true)
default @NotNull Set<@NotNull String> getCanPlaceOn() {
return Set.copyOf(getTag(ItemTags.CAN_PLACE_ON));
}
@NotNull Set<@NotNull String> getCanPlaceOn();
@Contract(pure = true)
default boolean canPlaceOn(@NotNull Block block) {
return getCanPlaceOn().contains(block.name());
}
boolean canPlaceOn(@NotNull Block block);
@Deprecated
sealed interface Builder extends Taggable
permits ItemMetaImpl.Builder, ItemMetaView.Builder {
@NotNull ItemMeta build();
@NotNull ItemComponentPatch.Builder components();
default <T> @NotNull Builder set(@NotNull Tag<T> tag, @Nullable T value) {
setTag(tag, value);
return this;

View File

@ -2,36 +2,38 @@ package net.minestom.server.item;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.network.NetworkBuffer;
import net.kyori.adventure.text.Component;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.item.component.*;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.io.IOException;
import java.util.Objects;
import java.util.*;
import java.util.function.Consumer;
import static net.minestom.server.network.NetworkBuffer.NBT;
record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
static final ItemMetaImpl EMPTY = new ItemMetaImpl(TagHandler.newHandler());
@Deprecated
record ItemMetaImpl(ItemComponentPatch components) implements ItemMeta {
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tagHandler.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
@Override
public @NotNull ItemMeta with(@NotNull Consumer<ItemMeta.@NotNull Builder> builderConsumer) {
Builder builder = new Builder(tagHandler.copy());
Builder builder = new Builder(components.builder());
builderConsumer.accept(builder);
return builder.build();
}
@Override
public @NotNull CompoundBinaryTag toNBT() {
return tagHandler.asCompound();
return components.asCompound();
}
@Override
@ -44,20 +46,81 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NBT, toNBT());
public int getDamage() {
return components.get(ItemComponent.DAMAGE, 0);
}
@Override
public boolean isUnbreakable() {
return components.has(ItemComponent.UNBREAKABLE);
}
@Override
public int getHideFlag() {
return 0;
}
@Override
public @Nullable Component getDisplayName() {
return components.get(ItemComponent.CUSTOM_NAME);
}
@Override
public @NotNull List<@NotNull Component> getLore() {
return components.get(ItemComponent.LORE, List.of());
}
@Override
public @NotNull Map<Enchantment, Short> getEnchantmentMap() {
EnchantmentList enchantments = components.get(ItemComponent.ENCHANTMENTS);
if (enchantments == null) return Map.of();
Map<Enchantment, Short> map = new HashMap<>(enchantments.enchantments().size());
for (Map.Entry<Enchantment, Integer> entry : enchantments.enchantments().entrySet()) {
map.put(entry.getKey(), entry.getValue().shortValue());
}
return map;
}
@Override
public @NotNull List<@NotNull ItemAttribute> getAttributes() {
//todo
}
@Override
public int getCustomModelData() {
return components.get(ItemComponent.CUSTOM_MODEL_DATA, 0);
}
@Override
public @NotNull Set<@NotNull String> getCanDestroy() {
//todo
}
@Override
public boolean canDestroy(@NotNull Block block) {
//todo
}
@Override
public @NotNull Set<@NotNull String> getCanPlaceOn() {
//todo
}
@Override
public boolean canPlaceOn(@NotNull Block block) {
//todo
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ItemMetaImpl itemMeta)) return false;
return toNBT().equals(itemMeta.toNBT());
return components.equals(itemMeta.components);
}
@Override
public int hashCode() {
return Objects.hash(toNBT());
return Objects.hash(components);
}
@Override
@ -65,10 +128,107 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
return toSNBT();
}
record Builder(TagHandler tagHandler) implements ItemMeta.Builder {
static final class Builder implements ItemMeta.Builder {
private final ItemComponentPatch.Builder components;
private TagHandler tagHandler = null;
Builder(ItemComponentPatch.Builder components) {
this.components = components;
}
@Override
public ItemMeta.@NotNull Builder damage(int damage) {
components.set(ItemComponent.DAMAGE, damage);
return this;
}
@Override
public ItemMeta.@NotNull Builder unbreakable(boolean unbreakable) {
if (unbreakable) {
components.set(ItemComponent.UNBREAKABLE, new Unbreakable(
components.get(ItemComponent.UNBREAKABLE, Unbreakable.DEFAULT).showInTooltip()));
} else {
components.remove(ItemComponent.UNBREAKABLE);
}
return this;
}
@Override
public ItemMeta.@NotNull Builder hideFlag(int hideFlag) {
return this; //todo
}
@Override
public ItemMeta.@NotNull Builder displayName(@Nullable Component displayName) {
if (displayName == null) {
components.remove(ItemComponent.CUSTOM_NAME);
} else {
components.set(ItemComponent.CUSTOM_NAME, displayName);
}
return this;
}
@Override
public ItemMeta.@NotNull Builder lore(@NotNull List<? extends Component> lore) {
components.set(ItemComponent.LORE, new ArrayList<>(lore));
return this;
}
@Override
public ItemMeta.@NotNull Builder enchantments(@NotNull Map<Enchantment, Short> enchantments) {
EnchantmentList existing = components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY);
Map<Enchantment, Integer> map = new HashMap<>(enchantments.size());
for (Map.Entry<Enchantment, Short> entry : enchantments.entrySet()) {
map.put(entry.getKey(), (int) entry.getValue());
}
components.set(ItemComponent.ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip()));
return this;
}
@Override
public ItemMeta.@NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) {
components.set(ItemComponent.ENCHANTMENTS, components.get(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY)
.with(enchantment, level));
return this;
}
@Override
public ItemMeta.@NotNull Builder attributes(@NotNull List<@NotNull ItemAttribute> attributes) {
return this; //todo
}
@Override
public ItemMeta.@NotNull Builder customModelData(int customModelData) {
components.set(ItemComponent.CUSTOM_MODEL_DATA, customModelData);
return this;
}
@Override
public ItemMeta.@NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) {
//todo
return this;
}
@Override
public ItemMeta.@NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) {
//todo
return this;
}
@Override
public @NotNull TagHandler tagHandler() {
this.tagHandler = TagHandler.fromCompound(components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).nbt());
return tagHandler;
}
@Override
public @NotNull ItemMetaImpl build() {
return new ItemMetaImpl(tagHandler.copy());
if (tagHandler != null) {
// If tagHandler was called then a tag was probably changed so update custom data.
components.set(ItemComponent.CUSTOM_DATA, new CustomData(tagHandler.asCompound()));
}
return new ItemMetaImpl(components.build());
}
}
}

View File

@ -1,16 +1,15 @@
package net.minestom.server.item;
import net.minestom.server.tag.TagReadable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("ALL")
@ApiStatus.Experimental
@Deprecated
public interface ItemMetaView<T extends ItemMetaView.Builder> extends TagReadable {
@ApiStatus.Experimental
@Deprecated
non-sealed interface Builder extends ItemMeta.Builder {
default @NotNull ItemMeta build() {
return new ItemMetaImpl(tagHandler().copy());
return new ItemMetaImpl(components().build());
}
}
}

View File

@ -1,34 +1,34 @@
package net.minestom.server.item;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.item.component.ItemComponentPatch;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@Deprecated
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) {
static <T extends ItemMetaView<?>> T construct(Class<T> metaClass, ItemComponentPatch components) {
try {
final Constructor<T> cons = metaClass.getDeclaredConstructor(TagReadable.class);
return cons.newInstance(tagReadable);
final Constructor<T> cons = metaClass.getDeclaredConstructor(ItemComponentPatch.class);
return cons.newInstance(components);
} 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) {
static <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> V constructBuilder(Class<T> metaClass, ItemComponentPatch.Builder components) {
final Class<V> clazz = viewType(metaClass);
try {
final Constructor<V> cons = clazz.getDeclaredConstructor(TagHandler.class);
return cons.newInstance(tagHandler);
final Constructor<V> cons = clazz.getDeclaredConstructor(ItemComponentPatch.Builder.class);
return cons.newInstance(components);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);

View File

@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Locale;
import java.util.UUID;
@Deprecated
@ApiStatus.Internal
public final class ItemSerializers {
public static final TagSerializer<EnchantmentEntry> ENCHANTMENT_SERIALIZER = new TagSerializer<>() {

View File

@ -6,12 +6,14 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.inventory.ContainerInventory;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.TagWritable;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.*;
import java.io.IOException;
@ -26,7 +28,7 @@ import java.util.function.UnaryOperator;
* <p>
* An item stack cannot be null, {@link ItemStack#AIR} should be used instead.
*/
public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEvent.ShowItem>
public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEventSource<HoverEvent.ShowItem>
permits ItemStackImpl {
/**
* Constant AIR item. Should be used instead of 'null'.
@ -48,12 +50,14 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
return of(material, 1);
}
@Deprecated(forRemoval = true)
@Contract(value = "_, _, _ -> new", pure = true)
static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable CompoundBinaryTag nbtCompound, int amount) {
if (nbtCompound == null) return of(material, amount);
return builder(material).amount(amount).meta(nbtCompound).build();
}
@Deprecated(forRemoval = true)
@Contract(value = "_, _ -> new", pure = true)
static @NotNull ItemStack fromNBT(@NotNull Material material, @Nullable CompoundBinaryTag nbtCompound) {
return fromNBT(material, nbtCompound, 1);
@ -64,17 +68,16 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
*
* @param nbtCompound The nbt representation of the item
*/
@ApiStatus.Experimental
static @NotNull ItemStack fromItemNBT(@NotNull CompoundBinaryTag nbtCompound) {
String id = nbtCompound.getString("id");
Check.notNull(id, "Item NBT must contain an id field.");
Material material = Material.fromNamespaceId(id);
Check.notNull(material, "Unknown material: {0}", id);
Byte amount = nbtCompound.getByte("Count");
if (amount == null) amount = 1;
final CompoundBinaryTag tag = nbtCompound.getCompound("tag");
return tag != null ? fromNBT(material, tag, amount) : of(material, amount);
// String id = nbtCompound.getString("id");
// Check.notNull(id, "Item NBT must contain an id field.");
// Material material = Material.fromNamespaceId(id);
// Check.notNull(material, "Unknown material: {0}", id);
//
// Byte amount = nbtCompound.getByte("Count");
// if (amount == null) amount = 1;
// final CompoundBinaryTag tag = nbtCompound.getCompound("tag");
// return tag != null ? fromNBT(material, tag, amount) : of(material, amount);
}
@Contract(pure = true)
@ -83,24 +86,9 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
@Contract(pure = true)
int 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)
@NotNull ItemStack with(@NotNull Consumer<@NotNull Builder> consumer);
@Contract(value = "_, _ -> new", pure = true)
@ApiStatus.Experimental
<V extends ItemMetaView.Builder, T extends ItemMetaView<V>> @NotNull ItemStack withMeta(@NotNull Class<T> metaType,
@NotNull Consumer<V> consumer);
@Contract(value = "_ -> new", pure = true)
@NotNull ItemStack withMeta(@NotNull Consumer<ItemMeta.@NotNull Builder> consumer);
@Contract(value = "_, -> new", pure = true)
@NotNull ItemStack withMaterial(@NotNull Material material);
@ -112,44 +100,26 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
return withAmount(intUnaryOperator.applyAsInt(amount()));
}
@ApiStatus.Experimental
@Contract(value = "_, _ -> new", pure = true)
<T> @NotNull ItemStack with(@NotNull ItemComponent<T> component, T value);
@Contract(value = "_, -> new", pure = true)
@NotNull ItemStack without(@NotNull ItemComponent<?> component);
@Contract(value = "_, _ -> new", pure = true)
default <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return withMeta(builder -> builder.set(tag, value));
}
@Override
@Contract(pure = true)
default <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return getOrDefault(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
@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)
@NotNull ItemStack withMeta(@NotNull ItemMeta meta);
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withDisplayName(@Nullable Component displayName) {
return withMeta(builder -> builder.displayName(displayName));
}
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) {
return withDisplayName(componentUnaryOperator.apply(getDisplayName()));
}
@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)
default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) {
return withLore(loreUnaryOperator.apply(getLore()));
}
@Contract(pure = true)
default boolean isAir() {
return material() == Material.AIR;
@ -158,16 +128,76 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
@Contract(pure = true)
boolean isSimilar(@NotNull ItemStack itemStack);
/**
* Converts this item to an NBT tag containing the id (material), count (amount), and components (diff)
*
* @return The nbt representation of the item
*/
@NotNull CompoundBinaryTag toItemNBT();
// BEGIN DEPRECATED PRE-COMPONENT METHODS
@Deprecated(forRemoval = true)
@Contract(pure = true)
@NotNull ItemMeta meta();
@Deprecated(forRemoval = true)
@Contract(pure = true)
@ApiStatus.Experimental
<T extends ItemMetaView<?>> @NotNull T meta(@NotNull Class<T> metaClass);
@Deprecated(forRemoval = true)
@Contract(value = "_, _ -> new", pure = true)
default <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return withMeta(builder -> builder.set(tag, value));
@ApiStatus.Experimental
<V extends ItemMetaView.Builder, T extends ItemMetaView<V>> @NotNull ItemStack withMeta(@NotNull Class<T> metaType,
@NotNull Consumer<V> consumer);
@Deprecated(forRemoval = true)
@Contract(value = "_ -> new", pure = true)
@NotNull ItemStack withMeta(@NotNull Consumer<ItemMeta.@NotNull Builder> consumer);
@Deprecated(forRemoval = true)
@Contract(pure = true)
default @Nullable Component getDisplayName() {
return meta().getDisplayName();
}
@Override
default <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return meta().getTag(tag);
@Deprecated(forRemoval = true)
@Contract(pure = true)
default @NotNull List<@NotNull Component> getLore() {
return meta().getLore();
}
@Deprecated(forRemoval = true)
@Contract(value = "_ -> new", pure = true)
@NotNull ItemStack withMeta(@NotNull ItemMeta meta);
@Deprecated(forRemoval = true)
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withDisplayName(@Nullable Component displayName) {
return withMeta(builder -> builder.displayName(displayName));
}
@Deprecated(forRemoval = true)
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withDisplayName(@NotNull UnaryOperator<@Nullable Component> componentUnaryOperator) {
return withDisplayName(componentUnaryOperator.apply(getDisplayName()));
}
@Deprecated(forRemoval = true)
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withLore(@NotNull List<? extends Component> lore) {
return withMeta(builder -> builder.lore(lore));
}
@Deprecated(forRemoval = true)
@Contract(value = "_, -> new", pure = true)
default @NotNull ItemStack withLore(@NotNull UnaryOperator<@NotNull List<@NotNull Component>> loreUnaryOperator) {
return withLore(loreUnaryOperator.apply(getLore()));
}
// END DEPRECATED PRE-COMPONENT METHODS
@Override
default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
try {
@ -179,56 +209,17 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
}
}
/**
* Converts this item to an NBT tag containing the id (material), count (amount), and tag (meta)
*
* @return The nbt representation of the item
*/
@ApiStatus.Experimental
@NotNull CompoundBinaryTag toItemNBT();
@Deprecated
@Contract(pure = true)
default @NotNull Material getMaterial() {
return material();
}
@Deprecated
@Contract(pure = true)
default int getAmount() {
return amount();
}
@Deprecated
@Contract(pure = true)
default @NotNull ItemMeta getMeta() {
return meta();
}
sealed interface Builder extends TagWritable
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 CompoundBinaryTag compound);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull ItemMeta itemMeta);
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull Consumer<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);
<T> @NotNull Builder set(@NotNull ItemComponent<T> component, T value);
@Contract(value = "-> new", pure = true)
@NotNull ItemStack build();
@Contract(value = "_ -> this")
@NotNull Builder remove(@NotNull ItemComponent<?> component);
@Contract(value = "_, _ -> this")
default <T> @NotNull Builder set(@NotNull Tag<T> tag, @Nullable T value) {
@ -236,16 +227,45 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
return this;
}
@Contract(value = "-> new", pure = true)
@NotNull ItemStack build();
// BEGIN DEPRECATED PRE-COMPONENT METHODS
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull TagHandler tagHandler);
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull CompoundBinaryTag compound);
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull ItemMeta itemMeta);
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
@NotNull Builder meta(@NotNull Consumer<ItemMeta.@NotNull Builder> consumer);
@Deprecated(forRemoval = true)
@Contract(value = "_, _ -> this")
<V extends ItemMetaView.Builder, T extends ItemMetaView<V>> @NotNull Builder meta(@NotNull Class<T> metaType,
@NotNull Consumer<@NotNull V> itemMetaConsumer);
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
default @NotNull Builder displayName(@Nullable Component displayName) {
return meta(builder -> builder.displayName(displayName));
}
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
default @NotNull Builder lore(@NotNull List<? extends Component> lore) {
return meta(builder -> builder.lore(lore));
}
@Deprecated(forRemoval = true)
@Contract(value = "_ -> this")
default @NotNull Builder lore(Component... lore) {
return meta(builder -> builder.lore(lore));

View File

@ -1,8 +1,9 @@
package net.minestom.server.item;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.ServerFlag;
import net.minestom.server.item.rule.VanillaStackingRule;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.Contract;
@ -11,34 +12,25 @@ import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implements ItemStack {
static final @NotNull StackingRule DEFAULT_STACKING_RULE;
record ItemStackImpl(Material material, int amount, ItemComponentPatch components) implements ItemStack {
static {
if (ServerFlag.STACKING_RULE == null) {
DEFAULT_STACKING_RULE = new VanillaStackingRule();
} else {
try {
DEFAULT_STACKING_RULE = (StackingRule) ClassLoader.getSystemClassLoader()
.loadClass(ServerFlag.STACKING_RULE).getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Could not instantiate default stacking rule", e);
}
}
}
static ItemStack create(Material material, int amount, ItemMetaImpl meta) {
static ItemStack create(Material material, int amount, ItemComponentPatch components) {
if (amount <= 0) return AIR;
return new ItemStackImpl(material, amount, meta);
return new ItemStackImpl(material, amount, components);
}
static ItemStack create(Material material, int amount) {
return create(material, amount, ItemMetaImpl.EMPTY);
return create(material, amount, ItemComponentPatch.EMPTY);
}
@Override
public <T extends ItemMetaView<?>> @NotNull T meta(@NotNull Class<T> metaClass) {
return ItemMetaViewImpl.construct(metaClass, meta);
public <T> @Nullable T get(@NotNull ItemComponent<T> component) {
return components.get(component);
}
@Override
public boolean has(@NotNull ItemComponent<?> component) {
return components.has(component);
}
@Override
@ -48,6 +40,65 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement
return builder.build();
}
@Override
public @NotNull ItemStack withMaterial(@NotNull Material material) {
return new ItemStackImpl(material, amount, components);
}
@Override
public @NotNull ItemStack withAmount(int amount) {
return create(material, amount, components);
}
@Override
public @NotNull <T> ItemStack with(@NotNull ItemComponent<T> component, T value) {
return new ItemStackImpl(material, amount, components.with(component, value));
}
@Override
public @NotNull ItemStack without(@NotNull ItemComponent<?> component) {
return new ItemStackImpl(material, amount, components.without(component));
}
@Override
public @NotNull ItemStack consume(int amount) {
int newAmount = amount() - amount;
if (newAmount <= 0) return AIR;
return withAmount(newAmount);
}
@Override
public boolean isSimilar(@NotNull ItemStack itemStack) {
return material == itemStack.material() && components.equals(((ItemStackImpl) itemStack).components);
}
@Override
public @NotNull CompoundBinaryTag toItemNBT() {
// CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
// .putString("id", material.name())
// .putByte("Count", (byte) amount);
// CompoundBinaryTag nbt = meta.toNBT();
// if (nbt.size() > 0) builder.put("tag", nbt);
// return builder.build();
//todo
}
@Contract(value = "-> new", pure = true)
private @NotNull ItemStack.Builder builder() {
return new Builder(material, amount, new ItemMetaImpl.Builder(meta.tagHandler().copy()));
}
// BEGIN DEPRECATED PRE-COMPONENT METHODS
@Override public @NotNull ItemMeta meta() {
return new ItemMetaImpl(components);
}
@Override
public <T extends ItemMetaView<?>> @NotNull T meta(@NotNull Class<T> metaClass) {
return ItemMetaViewImpl.construct(metaClass, meta);
}
@Override
public @NotNull <V extends ItemMetaView.Builder, T extends ItemMetaView<V>> ItemStack withMeta(@NotNull Class<T> metaType,
@NotNull Consumer<V> consumer) {
@ -59,59 +110,20 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement
return builder().meta(consumer).build();
}
@Override
public @NotNull ItemStack withMaterial(@NotNull Material material) {
return new ItemStackImpl(material, amount, meta);
}
@Override
public @NotNull ItemStack withAmount(int amount) {
return create(material, amount, meta);
}
@Override
public @NotNull ItemStack consume(int amount) {
return DEFAULT_STACKING_RULE.apply(this, currentAmount -> currentAmount - amount);
}
@Override
public @NotNull ItemStack withMeta(@NotNull ItemMeta meta) {
return new ItemStackImpl(material, amount, (ItemMetaImpl) meta);
}
@Override
public boolean isSimilar(@NotNull ItemStack itemStack) {
return material == itemStack.material() && meta.equals(itemStack.meta());
}
@Override
public @NotNull CompoundBinaryTag toItemNBT() {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
.putString("id", material.name())
.putByte("Count", (byte) amount);
CompoundBinaryTag nbt = meta.toNBT();
if (nbt.size() > 0) builder.put("tag", nbt);
return builder.build();
}
@Contract(value = "-> new", pure = true)
private @NotNull ItemStack.Builder builder() {
return new Builder(material, amount, new ItemMetaImpl.Builder(meta.tagHandler().copy()));
}
static final class Builder implements ItemStack.Builder {
final Material material;
int amount;
ItemMetaImpl.Builder metaBuilder;
ItemComponentPatch.Builder components;
Builder(Material material, int amount, ItemMetaImpl.Builder metaBuilder) {
Builder(Material material, int amount, ItemComponentPatch.Builder components) {
this.material = material;
this.amount = amount;
this.metaBuilder = metaBuilder;
}
Builder(Material material, int amount) {
this(material, amount, new ItemMetaImpl.Builder(TagHandler.newHandler()));
this.components = components;
}
@Override
@ -120,49 +132,57 @@ record ItemStackImpl(Material material, int amount, ItemMetaImpl meta) implement
return this;
}
@Override
public <T> ItemStack.@NotNull Builder set(@NotNull ItemComponent<T> component, T value) {
components.set(component, value);
return this;
}
@Override
public ItemStack.@NotNull Builder remove(@NotNull ItemComponent<?> component) {
components.remove(component);
return this;
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
components.set(ItemComponent.CUSTOM_DATA, components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).withTag(tag, value));
}
@Override
public @NotNull ItemStack build() {
return ItemStackImpl.create(material, amount, components.build());
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull TagHandler tagHandler) {
return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy()));
return meta(tagHandler.asCompound());
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull CompoundBinaryTag compound) {
return metaBuilder(new ItemMetaImpl.Builder(TagHandler.fromCompound(compound)));
components.set(ItemComponent.CUSTOM_DATA, new CustomData(compound));
return this;
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull ItemMeta itemMeta) {
final TagHandler tagHandler = ((ItemMetaImpl) itemMeta).tagHandler();
return metaBuilder(new ItemMetaImpl.Builder(tagHandler.copy()));
this.components = itemMeta.components().builder();
return this;
}
@Override
public ItemStack.@NotNull Builder meta(@NotNull Consumer<ItemMeta.Builder> consumer) {
consumer.accept(metaBuilder);
consumer.accept(new ItemMetaImpl.Builder(components));
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());
V view = ItemMetaViewImpl.constructBuilder(metaType, components);
itemMetaConsumer.accept(view);
return this;
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
this.metaBuilder.setTag(tag, value);
}
@Override
public @NotNull ItemStack build() {
return ItemStackImpl.create(material, amount, metaBuilder.build());
}
private ItemStack.@NotNull Builder metaBuilder(@NotNull ItemMetaImpl.Builder builder) {
this.metaBuilder = builder;
return this;
}
}
}

View File

@ -1,33 +0,0 @@
package net.minestom.server.item;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.attribute.ItemAttribute;
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<String>> CAN_PLACE_ON = Tag.String("CanPlaceOn").list().defaultValue(List.of());
static final Tag<List<String>> CAN_DESTROY = Tag.String("CanDestroy").list().defaultValue(List.of());
}

View File

@ -1,12 +1,15 @@
package net.minestom.server.item;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.registry.Registry;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Collection;
@ -62,24 +65,22 @@ public sealed interface Material extends StaticProtocolObject, Materials permits
return registry().id();
}
default int maxStackSize() {
return registry().maxStackSize();
}
default boolean isFood() {
return registry().isFood();
}
default boolean isBlock() {
return registry().block() != null;
}
default Block block() {
default @UnknownNullability Block block() {
return registry().block();
}
default @NotNull ItemComponentMap prototype() {
return registry().prototype();
}
default boolean isArmor() {
return registry().isArmor();
//todo how does armor work nowadays
return false;
// return registry().isArmor();
}
default boolean hasState() {
@ -90,6 +91,16 @@ public sealed interface Material extends StaticProtocolObject, Materials permits
}
}
@Deprecated(forRemoval = true)
default int maxStackSize() {
return prototype().getOrDefault(ItemComponent.MAX_STACK_SIZE, 64);
}
@Deprecated(forRemoval = true)
default boolean isFood() {
return prototype().has(ItemComponent.FOOD);
}
static @NotNull Collection<@NotNull Material> values() {
return MaterialImpl.values();
}

View File

@ -1,70 +0,0 @@
package net.minestom.server.item;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.IntUnaryOperator;
/**
* Represents the stacking rule of an {@link ItemStack}.
* This can be used to mimic the vanilla one (using the displayed item quantity)
* or a complete new one which can be stored in lore, name, etc...
*/
public interface StackingRule {
static @NotNull StackingRule get() {
return ItemStackImpl.DEFAULT_STACKING_RULE;
}
/**
* Used to know if two {@link ItemStack} can be stacked together.
*
* @param item1 the first {@link ItemStack}
* @param item2 the second {@link ItemStack}
* @return true if both {@link ItemStack} can be stacked together
* (without taking their amount in consideration)
*/
boolean canBeStacked(@NotNull ItemStack item1, @NotNull ItemStack item2);
/**
* Used to know if an {@link ItemStack} can have the size {@code newAmount} applied.
*
* @param item the {@link ItemStack} to check
* @param amount the desired new amount
* @return true if {@code item} can have its stack size set to newAmount
*/
boolean canApply(@NotNull ItemStack item, int amount);
/**
* Changes the size of the {@link ItemStack} to {@code newAmount}.
* At this point we know that the item can have this stack size applied.
*
* @param item the {@link ItemStack} to applies the size to
* @param newAmount the new item size
* @return a new {@link ItemStack item} with the specified amount
*/
@Contract("_, _ -> new")
@NotNull ItemStack apply(@NotNull ItemStack item, int newAmount);
@Contract("_, _ -> new")
default @NotNull ItemStack apply(@NotNull ItemStack item, @NotNull IntUnaryOperator amountOperator) {
return apply(item, amountOperator.applyAsInt(getAmount(item)));
}
/**
* Used to determine the current stack size of an {@link ItemStack}.
* It is possible to have it stored in its nbt.
*
* @param itemStack the {@link ItemStack} to check the size
* @return the correct size of {@link ItemStack}
*/
int getAmount(@NotNull ItemStack itemStack);
/**
* Gets the max size of a stack.
*
* @param itemStack the item to get the max size from
* @return the max size of a stack
*/
int getMaxSize(@NotNull ItemStack itemStack);
}

View File

@ -1,10 +1,13 @@
package net.minestom.server.item.attribute;
public enum AttributeSlot {
ANY,
MAINHAND,
OFFHAND,
FEET,
LEGS,
CHEST,
HEAD
HEAD,
ARMOR,
BODY,
}

View File

@ -0,0 +1,101 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.*;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.attribute.AttributeModifier;
import net.minestom.server.entity.attribute.AttributeOperation;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.UniqueIdUtils;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public record AttributeList(@NotNull List<Modifier> modifiers, boolean showInTooltip) {
public static final AttributeList EMPTY = new AttributeList(List.of(), true);
public static final NetworkBuffer.Type<AttributeList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, AttributeList value) {
buffer.writeCollection(value.modifiers);
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public AttributeList read(@NotNull NetworkBuffer buffer) {
return new AttributeList(buffer.readCollection(Modifier::new, Short.MAX_VALUE),
buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final BinaryTagSerializer<AttributeList> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull AttributeList value) {
ListBinaryTag.Builder<CompoundBinaryTag> modifiers = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);
for (Modifier modifier : value.modifiers) {
modifiers.add(CompoundBinaryTag.builder()
.putString("type", modifier.attribute.name())
.putString("slot", modifier.slot.name().toLowerCase(Locale.ROOT))
.put("uuid", UniqueIdUtils.toNbt(modifier.modifier.id()))
.putString("name", modifier.modifier.name())
.putDouble("amount", modifier.modifier.amount())
.putString("operation", modifier.modifier.operation().name().toLowerCase(Locale.ROOT))
.build());
}
return CompoundBinaryTag.builder()
.put("modifiers", modifiers.build())
.putBoolean("show_in_tooltip", value.showInTooltip)
.build();
}
@Override
public @NotNull AttributeList read(@NotNull BinaryTag tag) {
boolean showInTooltip = true;
ListBinaryTag modifiersTag;
if (tag instanceof CompoundBinaryTag compound) {
modifiersTag = compound.getList("modifiers", BinaryTagTypes.COMPOUND);
showInTooltip = compound.getBoolean("show_in_tooltip", true);
} else if (tag instanceof ListBinaryTag list) {
modifiersTag = list;
} else return EMPTY;
List<Modifier> modifiers = new ArrayList<>(modifiersTag.size());
for (BinaryTag modifierTagRaw : modifiersTag) {
if (!(modifierTagRaw instanceof CompoundBinaryTag modifierTag)) continue;
Attribute attribute = Attribute.fromNamespaceId(modifierTag.getString("type"));
if (attribute == null) continue; // Unknown attribute, skip
AttributeSlot slot = AttributeSlot.valueOf(modifierTag.getString("slot").toUpperCase(Locale.ROOT));
AttributeModifier modifier = new AttributeModifier(
UniqueIdUtils.fromNbt((IntArrayBinaryTag) modifierTag.get("uuid")),
modifierTag.getString("name"),
modifierTag.getDouble("amount"),
AttributeOperation.valueOf(modifierTag.getString("operation").toUpperCase(Locale.ROOT))
);
modifiers.add(new Modifier(attribute, modifier, slot));
}
return new AttributeList(modifiers, showInTooltip);
}
};
public record Modifier(
@NotNull Attribute attribute,
@NotNull AttributeModifier modifier,
@NotNull AttributeSlot slot
) implements NetworkBuffer.Writer {
public Modifier(@NotNull NetworkBuffer reader) {
this(Attribute.fromId(reader.read(NetworkBuffer.VAR_INT)),
new AttributeModifier(reader),
reader.readEnum(AttributeSlot.class));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.VAR_INT, attribute.id());
modifier.write(writer);
writer.writeEnum(AttributeSlot.class, slot);
}
}
}

View File

@ -3,10 +3,13 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
public record CustomData(@NotNull CompoundBinaryTag nbt) implements ItemComponent {
static final Tag<CustomData> TAG = Tag.Structure("ab", CustomData.class);
public record CustomData(@NotNull CompoundBinaryTag nbt) implements TagReadable {
public static final CustomData EMPTY = new CustomData(CompoundBinaryTag.empty());
static final NetworkBuffer.Type<CustomData> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
@ -20,4 +23,17 @@ public record CustomData(@NotNull CompoundBinaryTag nbt) implements ItemComponen
}
};
static final BinaryTagSerializer<CustomData> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(CustomData::new, CustomData::nbt);
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return tag.read(nbt);
}
public <T> @NotNull CustomData withTag(@NotNull Tag<T> tag, T value) {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
builder.put(nbt);
tag.write(builder, value);
return new CustomData(builder.build());
}
}

View File

@ -0,0 +1,57 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.IntBinaryTag;
import net.minestom.server.color.Color;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record DyedItemColor(@NotNull Color color, boolean showInTooltip) {
public static DyedItemColor LEATHER = new DyedItemColor(new Color(-6265536), true);
public static final NetworkBuffer.Type<DyedItemColor> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, DyedItemColor value) {
buffer.write(NetworkBuffer.COLOR, value.color);
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public DyedItemColor read(@NotNull NetworkBuffer buffer) {
return new DyedItemColor(buffer.read(NetworkBuffer.COLOR), buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final BinaryTagSerializer<DyedItemColor> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull DyedItemColor value) {
return CompoundBinaryTag.builder()
.putInt("color", value.color.asRGB())
.putBoolean("show_in_tooltip", value.showInTooltip)
.build();
}
@Override
public @NotNull DyedItemColor read(@NotNull BinaryTag tag) {
if (tag instanceof CompoundBinaryTag compoundTag) {
int color = compoundTag.getInt("color");
boolean showInTooltip = compoundTag.getBoolean("show_in_tooltip", true);
return new DyedItemColor(new Color(color), showInTooltip);
} else if (tag instanceof IntBinaryTag intTag) {
return new DyedItemColor(new Color(intTag.intValue()), true);
}
return new DyedItemColor(new Color(0), false);
}
};
public @NotNull DyedItemColor withColor(@NotNull Color color) {
return new DyedItemColor(color, showInTooltip);
}
public @NotNull DyedItemColor withTooltip(boolean showInTooltip) {
return new DyedItemColor(color, showInTooltip);
}
}

View File

@ -0,0 +1,81 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.item.Enchantment;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public record EnchantmentList(@NotNull Map<Enchantment, Integer> enchantments, boolean showInTooltip) {
public static final EnchantmentList EMPTY = new EnchantmentList(Map.of(), true);
static NetworkBuffer.Type<EnchantmentList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull EnchantmentList value) {
buffer.write(NetworkBuffer.VAR_INT, value.enchantments.size());
for (Map.Entry<Enchantment, Integer> entry : value.enchantments.entrySet()) {
buffer.write(NetworkBuffer.VAR_INT, entry.getKey().id());
buffer.write(NetworkBuffer.VAR_INT, entry.getValue());
}
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public @NotNull EnchantmentList read(@NotNull NetworkBuffer buffer) {
int size = buffer.read(NetworkBuffer.VAR_INT);
Check.argCondition(size < 0 || size > Short.MAX_VALUE, "Invalid enchantment list size: {0}", size);
Map<Enchantment, Integer> enchantments = new HashMap<>(size);
for (int i = 0; i < size; i++) {
Enchantment enchantment = Enchantment.fromId(buffer.read(NetworkBuffer.VAR_INT));
int level = buffer.read(NetworkBuffer.VAR_INT);
enchantments.put(enchantment, level);
}
boolean showInTooltip = buffer.read(NetworkBuffer.BOOLEAN);
return new EnchantmentList(enchantments, showInTooltip);
}
};
static BinaryTagSerializer<EnchantmentList> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
// We have two variants of the enchantment list, one with {levels: {...}, show_in_tooltip: boolean} and one with {...}.
CompoundBinaryTag levels = tag.keySet().contains("levels") ? tag.getCompound("levels") : tag;
Map<Enchantment, Integer> enchantments = new HashMap<>(levels.size());
for (Map.Entry<String, ? extends BinaryTag> entry : levels) {
Enchantment enchantment = Enchantment.fromNamespaceId(entry.getKey());
Check.notNull(enchantment, "Unknown enchantment: {0}", entry.getKey());
int level = BinaryTagSerializer.INT.read(entry.getValue());
if (level > 0) enchantments.put(enchantment, level);
}
// Doesnt matter which variant we chose, the default will work.
boolean showInTooltip = tag.getBoolean("show_in_tooltip", true);
return new EnchantmentList(enchantments, showInTooltip);
},
value -> {
CompoundBinaryTag.Builder levels = CompoundBinaryTag.builder();
for (Map.Entry<Enchantment, Integer> entry : value.enchantments.entrySet()) {
levels.put(entry.getKey().name(), BinaryTagSerializer.INT.write(entry.getValue()));
}
return CompoundBinaryTag.builder()
.put("levels", levels.build())
.putBoolean("show_in_tooltip", value.showInTooltip)
.build();
}
);
public EnchantmentList {
enchantments = Map.copyOf(enchantments);
}
public @NotNull EnchantmentList with(@NotNull Enchantment enchantment, int level) {
Map<Enchantment, Integer> newEnchantments = new HashMap<>(enchantments);
newEnchantments.put(enchantment, level);
return new EnchantmentList(newEnchantments, showInTooltip);
}
}

View File

@ -0,0 +1,89 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.color.Color;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public record FireworkExplosion(
@NotNull Shape shape,
@NotNull List<Color> colors,
@NotNull List<Color> fadeColors,
boolean hasTrail,
boolean hasTwinkle
) {
public enum Shape {
SMALL_BALL,
LARGE_BALL,
STAR,
CREEPER,
BURST
}
public static final NetworkBuffer.Type<FireworkExplosion> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FireworkExplosion value) {
buffer.writeEnum(Shape.class, value.shape);
buffer.writeCollection(NetworkBuffer.COLOR, value.colors);
buffer.writeCollection(NetworkBuffer.COLOR, value.fadeColors);
buffer.write(NetworkBuffer.BOOLEAN, value.hasTrail);
buffer.write(NetworkBuffer.BOOLEAN, value.hasTwinkle);
}
@Override
public FireworkExplosion read(@NotNull NetworkBuffer buffer) {
return new FireworkExplosion(
buffer.readEnum(Shape.class),
buffer.readCollection(NetworkBuffer.COLOR, Short.MAX_VALUE),
buffer.readCollection(NetworkBuffer.COLOR, Short.MAX_VALUE),
buffer.read(NetworkBuffer.BOOLEAN),
buffer.read(NetworkBuffer.BOOLEAN)
);
}
};
public static final BinaryTagSerializer<FireworkExplosion> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
Shape shape = Shape.valueOf(tag.getString("shape").toUpperCase(Locale.ROOT));
List<Color> colors = new ArrayList<>();
for (int color : tag.getIntArray("colors"))
colors.add(new Color(color));
List<Color> fadeColors = new ArrayList<>();
for (int fadeColor : tag.getIntArray("fadeColors"))
fadeColors.add(new Color(fadeColor));
boolean hasTrail = tag.getBoolean("hasTrail");
boolean hasTwinkle = tag.getBoolean("hasTwinkle");
return new FireworkExplosion(shape, colors, fadeColors, hasTrail, hasTwinkle);
},
value -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
builder.putString("shape", value.shape.name().toLowerCase(Locale.ROOT));
if (!value.colors.isEmpty()) {
int[] colors = new int[value.colors.size()];
for (int i = 0; i < value.colors.size(); i++)
colors[i] = value.colors.get(i).asRGB();
builder.putIntArray("colors", colors);
}
if (!value.fadeColors.isEmpty()) {
int[] fadeColors = new int[value.fadeColors.size()];
for (int i = 0; i < value.fadeColors.size(); i++)
fadeColors[i] = value.fadeColors.get(i).asRGB();
builder.putIntArray("fadeColors", fadeColors);
}
if (value.hasTrail) builder.putBoolean("hasTrail", value.hasTrail);
if (value.hasTwinkle) builder.putBoolean("hasTwinkle", value.hasTwinkle);
return builder.build();
}
);
public FireworkExplosion {
colors = List.copyOf(colors);
fadeColors = List.copyOf(fadeColors);
}
}

View File

@ -0,0 +1,62 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public record FireworkList(byte flightDuration, @NotNull List<FireworkExplosion> explosions) {
public static final FireworkList EMPTY = new FireworkList((byte) 0, List.of());
public static final NetworkBuffer.Type<FireworkList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FireworkList value) {
buffer.write(NetworkBuffer.BYTE, value.flightDuration);
buffer.writeCollection(FireworkExplosion.NETWORK_TYPE, value.explosions);
}
@Override
public FireworkList read(@NotNull NetworkBuffer buffer) {
return new FireworkList(buffer.read(NetworkBuffer.BYTE),
buffer.readCollection(FireworkExplosion.NETWORK_TYPE, 256));
}
};
public static final BinaryTagSerializer<FireworkList> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
byte flightDuration = tag.getByte("flight_duration");
ListBinaryTag explosionsTag = tag.getList("explosions", BinaryTagTypes.COMPOUND);
List<FireworkExplosion> explosions = new ArrayList<>(explosionsTag.size());
for (BinaryTag explosionTag : explosionsTag)
explosions.add(FireworkExplosion.NBT_TYPE.read(explosionTag));
return new FireworkList(flightDuration, explosions);
},
value -> {
ListBinaryTag.Builder<BinaryTag> explosionsTag = ListBinaryTag.builder();
for (FireworkExplosion explosion : value.explosions)
explosionsTag.add(FireworkExplosion.NBT_TYPE.write(explosion));
return CompoundBinaryTag.builder()
.putByte("flight_duration", value.flightDuration)
.put("explosions", explosionsTag.build())
.build();
}
);
public FireworkList {
explosions = List.copyOf(explosions);
}
public @NotNull FireworkList withFlightDuration(byte flightDuration) {
return new FireworkList(flightDuration, explosions);
}
public @NotNull FireworkList withExplosions(@NotNull List<FireworkExplosion> explosions) {
return new FireworkList(flightDuration, explosions);
}
}

View File

@ -1,20 +1,93 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.tag.Tag;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public sealed interface ItemComponent permits CustomData {
import java.util.List;
ItemComponent.Type<CustomData> CUSTOM_DATA = new ItemComponent.Type<>("custom_data", CustomData.NETWORK_TYPE, CustomData.TAG);
import static net.minestom.server.item.component.ItemComponentImpl.declare;
record Type<T extends ItemComponent>(
@NotNull String name,
@NotNull NetworkBuffer.Type<T> network,
@NotNull Tag<T> tag
) {
public sealed interface ItemComponent<T> extends StaticProtocolObject permits ItemComponentImpl {
// Note that even non-networked components are declared here as they still contribute to the component ID counter.
ItemComponent<CustomData> CUSTOM_DATA = declare("custom_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
ItemComponent<Integer> MAX_STACK_SIZE = declare("max_stack_size", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<Integer> MAX_DAMAGE = declare("max_damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<Integer> DAMAGE = declare("damage", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<Unbreakable> UNBREAKABLE = declare("unbreakable", Unbreakable.NETWORK_TYPE, Unbreakable.NBT_TYPE);
ItemComponent<Component> CUSTOM_NAME = declare("custom_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT);
ItemComponent<Component> ITEM_NAME = declare("item_name", NetworkBuffer.COMPONENT, BinaryTagSerializer.JSON_COMPONENT);
ItemComponent<List<Component>> LORE = declare("lore", NetworkBuffer.COMPONENT.list(256), BinaryTagSerializer.JSON_COMPONENT.list());
ItemComponent<ItemRarity> RARITY = declare("rarity", ItemRarity.NETWORK_TYPE, ItemRarity.NBT_TYPE);
ItemComponent<EnchantmentList> ENCHANTMENTS = declare("enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE);
ItemComponent<Void> CAN_PLACE_ON = declare("can_place_on", null, null); //todo
ItemComponent<Void> CAN_BREAK = declare("can_break", null, null); //todo
ItemComponent<AttributeList> ATTRIBUTE_MODIFIERS = declare("attribute_modifiers", AttributeList.NETWORK_TYPE, AttributeList.NBT_TYPE);
ItemComponent<Integer> CUSTOM_MODEL_DATA = declare("custom_model_data", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<Void> HIDE_ADDITIONAL_TOOLTIP = declare("hide_additional_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING);
ItemComponent<Void> HIDE_TOOLTIP = declare("hide_tooltip", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING);
ItemComponent<Integer> REPAIR_COST = declare("repair_cost", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<Void> CREATIVE_SLOT_LOCK = declare("creative_slot_lock", NetworkBuffer.NOTHING, null);
ItemComponent<Boolean> ENCHANTMENT_GLINT_OVERRIDE = declare("enchantment_glint_override", NetworkBuffer.BOOLEAN, BinaryTagSerializer.BOOLEAN);
ItemComponent<Void> INTANGIBLE_PROJECTILE = declare("intangible_projectile", null, BinaryTagSerializer.NOTHING);
ItemComponent<Void> FOOD = declare("food", null, null); //todo
ItemComponent<Void> FIRE_RESISTANT = declare("fire_resistant", NetworkBuffer.NOTHING, BinaryTagSerializer.NOTHING);
ItemComponent<Void> TOOL = declare("tool", null, null); //todo
ItemComponent<EnchantmentList> STORED_ENCHANTMENTS = declare("stored_enchantments", EnchantmentList.NETWORK_TYPE, EnchantmentList.NBT_TYPE);
ItemComponent<DyedItemColor> DYED_COLOR = declare("dyed_color", DyedItemColor.NETWORK_TYPE, DyedItemColor.NBT_TYPE);
ItemComponent<Color> MAP_COLOR = declare("map_color", NetworkBuffer.COLOR, BinaryTagSerializer.INT.map(Color::new, Color::asRGB));
ItemComponent<Integer> MAP_ID = declare("map_id", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<MapDecorations> MAP_DECORATIONS = declare("map_decorations", null, MapDecorations.NBT_TYPE);
ItemComponent<MapPostProcessing> MAP_POST_PROCESSING = declare("map_post_processing", MapPostProcessing.NETWORK_TYPE, null);
ItemComponent<List<ItemStack>> CHARGED_PROJECTILES = declare("charged_projectiles", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list());
ItemComponent<List<ItemStack>> BUNDLE_CONTENTS = declare("bundle_contents", NetworkBuffer.ITEM.list(Short.MAX_VALUE), BinaryTagSerializer.ITEM.list());
ItemComponent<Void> POTION_CONTENTS = declare("potion_contents", null, null); //todo
ItemComponent<Void> SUSPICIOUS_STEW_EFFECTS = declare("suspicious_stew_effects", null, null); //todo
ItemComponent<Void> WRITABLE_BOOK_CONTENT = declare("writable_book_content", null, null); //todo
ItemComponent<Void> WRITTEN_BOOK_CONTENT = declare("written_book_content", null, null); //todo
ItemComponent<Void> TRIM = declare("trim", null, null); //todo
ItemComponent<Void> DEBUG_STICK_STATE = declare("debug_stick_state", null, null); //todo
ItemComponent<CustomData> ENTITY_DATA = declare("entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
ItemComponent<CustomData> BUCKET_ENTITY_DATA = declare("bucket_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
ItemComponent<CustomData> BLOCK_ENTITY_DATA = declare("block_entity_data", CustomData.NETWORK_TYPE, CustomData.NBT_TYPE);
ItemComponent<Void> INSTRUMENT = declare("instrument", null, null); //todo
ItemComponent<Integer> OMINOUS_BOTTLE_AMPLIFIER = declare("ominous_bottle_amplifier", NetworkBuffer.VAR_INT, BinaryTagSerializer.INT);
ItemComponent<List<String>> RECIPES = declare("recipes", NetworkBuffer.STRING.list(Short.MAX_VALUE), BinaryTagSerializer.STRING.list());
ItemComponent<LodestoneTracker> LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE);
ItemComponent<FireworkExplosion> FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE);
ItemComponent<FireworkList> FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE);
ItemComponent<Void> PROFILE = declare("profile", null, null); //todo
ItemComponent<String> NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING);
ItemComponent<Void> BANNER_PATTERNS = declare("banner_patterns", null, null); //todo
ItemComponent<Void> BASE_COLOR = declare("base_color", null, null); //todo dyecolor is the same stringrepresentable as item rarity
ItemComponent<Void> POT_DECORATIONS = declare("pot_decorations", null, null); //todo
ItemComponent<List<ItemStack>> CONTAINER = declare("container", NetworkBuffer.ITEM.list(256), BinaryTagSerializer.ITEM.list());
ItemComponent<Void> BLOCK_STATE = declare("block_state", null, null); //todo
ItemComponent<Void> BEES = declare("bees", null, null); //todo
ItemComponent<String> LOCK = declare("lock", null, BinaryTagSerializer.STRING);
ItemComponent<SeededContainerLoot> CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE);
@NotNull T read(@NotNull BinaryTag tag);
@NotNull T read(@NotNull NetworkBuffer reader);
static @Nullable ItemComponent<?> fromNamespaceId(@NotNull String namespaceId) {
return ItemComponentImpl.NAMESPACES.get(namespaceId);
}
static @Nullable ItemComponent<?> fromNamespaceId(@NotNull NamespaceID namespaceId) {
return fromNamespaceId(namespaceId.asString());
}
static @Nullable ItemComponent<?> fromId(int id) {
return ItemComponentImpl.IDS.get(id);
}
}

View File

@ -0,0 +1,47 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
record ItemComponentImpl<T>(
int id,
@NotNull NamespaceID namespace,
@Nullable NetworkBuffer.Type<T> network,
@Nullable BinaryTagSerializer<T> nbt
) implements ItemComponent<T> {
static final Map<String, ItemComponent<?>> NAMESPACES = new HashMap<>(32);
static final ObjectArray<ItemComponent<?>> IDS = ObjectArray.singleThread(32);
static <T> ItemComponent<T> declare(@NotNull String name, @Nullable NetworkBuffer.Type<T> network, @Nullable BinaryTagSerializer<T> nbt) {
ItemComponent<T> impl = new ItemComponentImpl<>(NAMESPACES.size(), NamespaceID.from(name), network, nbt);
NAMESPACES.put(impl.name(), impl);
IDS.set(impl.id(), impl);
return impl;
}
@Override
public @NotNull T read(@NotNull BinaryTag tag) {
Check.notNull(nbt, "{0} cannot be deserialized from NBT", this);
return nbt.read(tag);
}
@Override
public @NotNull T read(@NotNull NetworkBuffer reader) {
Check.notNull(network, "{0} cannot be deserialized from network", this);
return network.read(reader);
}
@Override
public String toString() {
return name();
}
}

View File

@ -0,0 +1,32 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ItemComponentMap {
static @NotNull Builder builder() {
//todo
}
boolean has(@NotNull ItemComponent<?> component);
<T> @Nullable T get(@NotNull ItemComponent<T> component);
default <T> @NotNull T get(@NotNull ItemComponent<T> component, @NotNull T defaultValue) {
T value = get(component);
return value != null ? value : defaultValue;
}
@NotNull CompoundBinaryTag asCompound();
interface Builder {
<T> @NotNull Builder set(@NotNull ItemComponent<T> component, @NotNull T value);
void remove(@NotNull ItemComponent<?> component);
@NotNull ItemComponentMap build();
}
}

View File

@ -0,0 +1,37 @@
package net.minestom.server.item.component;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ItemComponentPatch extends ItemComponentMap {
static ItemComponentPatch EMPTY = new ItemComponentPatch() {
@Override public boolean has(@NotNull ItemComponent<?> component) {
return false;
}
@Override public <T> @Nullable T get(@NotNull ItemComponent<T> component) {
return null;
}
}; //todo
<T> @NotNull ItemComponentPatch with(@NotNull ItemComponent<T> component, T value);
@NotNull ItemComponentPatch without(@NotNull ItemComponent<?> component);
@NotNull Builder builder();
interface Builder extends ItemComponentMap {
@Contract(value = "_, _ -> this", pure = true)
<T> @NotNull Builder set(@NotNull ItemComponent<T> component, @NotNull T value);
@Contract(value = "_ -> this", pure = true)
@NotNull Builder remove(@NotNull ItemComponent<?> component);
@Contract(value = "-> new", pure = true)
@NotNull ItemComponentPatch build();
}
}

View File

@ -0,0 +1,47 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.IntBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
public enum ItemRarity {
COMMON,
UNCOMMON,
RARE,
EPIC;
private static final ItemRarity[] VALUES = values();
static final NetworkBuffer.Type<ItemRarity> NETWORK_TYPE = new NetworkBuffer.Type<ItemRarity>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ItemRarity value) {
buffer.writeEnum(ItemRarity.class, value);
}
@Override
public ItemRarity read(@NotNull NetworkBuffer buffer) {
return buffer.readEnum(ItemRarity.class);
}
};
static final BinaryTagSerializer<ItemRarity> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull ItemRarity value) {
return IntBinaryTag.intBinaryTag(value.ordinal());
}
@Override
public @NotNull ItemRarity read(@NotNull BinaryTag tag) {
return switch (tag) {
case IntBinaryTag intBinaryTag -> VALUES[intBinaryTag.value()];
case StringBinaryTag stringBinaryTag -> valueOf(stringBinaryTag.value().toUpperCase(Locale.ROOT));
default -> COMMON;
};
}
};
}

View File

@ -0,0 +1,53 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.minestom.server.coordinate.Point;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record LodestoneTracker(@NotNull String dimension, @NotNull Point blockPosition, boolean tracked) {
public static final NetworkBuffer.Type<LodestoneTracker> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull LodestoneTracker value) {
buffer.write(NetworkBuffer.STRING, value.dimension);
buffer.write(NetworkBuffer.BLOCK_POSITION, value.blockPosition);
buffer.write(NetworkBuffer.BOOLEAN, value.tracked);
}
@Override
public @NotNull LodestoneTracker read(@NotNull NetworkBuffer buffer) {
return new LodestoneTracker(
buffer.read(NetworkBuffer.STRING),
buffer.read(NetworkBuffer.BLOCK_POSITION),
buffer.read(NetworkBuffer.BOOLEAN)
);
}
};
public static final BinaryTagSerializer<LodestoneTracker> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull LodestoneTracker value) {
throw new UnsupportedOperationException("Not implemented"); //todo
}
@Override
public @NotNull LodestoneTracker read(@NotNull BinaryTag tag) {
throw new UnsupportedOperationException("Not implemented"); //todo
}
};
public @NotNull LodestoneTracker withDimension(@NotNull String dimension) {
return new LodestoneTracker(dimension, blockPosition, tracked);
}
public @NotNull LodestoneTracker withBlockPosition(@NotNull Point blockPosition) {
return new LodestoneTracker(dimension, blockPosition, tracked);
}
public @NotNull LodestoneTracker withTracked(boolean tracked) {
return new LodestoneTracker(dimension, blockPosition, tracked);
}
}

View File

@ -0,0 +1,44 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public record MapDecorations(@NotNull Map<String, Entry> decorations) {
public record Entry(@NotNull String type, double x, double z, float rotation) {
}
static final BinaryTagSerializer<MapDecorations> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
Map<String, Entry> map = new HashMap<>(tag.size());
for (Map.Entry<String, ? extends BinaryTag> entry : tag) {
if (!(entry instanceof CompoundBinaryTag entryTag)) continue;
map.put(entry.getKey(), new Entry(
entryTag.getString("type"),
entryTag.getDouble("x"),
entryTag.getDouble("z"),
entryTag.getFloat("rotation")
));
}
return new MapDecorations(map);
},
decorations -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
for (Map.Entry<String, Entry> entry : decorations.decorations.entrySet()) {
CompoundBinaryTag entryTag = CompoundBinaryTag.builder()
.putString("type", entry.getValue().type)
.putDouble("x", entry.getValue().x)
.putDouble("z", entry.getValue().z)
.putFloat("rotation", entry.getValue().rotation)
.build();
builder.put(entry.getKey(), entryTag);
}
return builder.build();
}
);
}

View File

@ -0,0 +1,21 @@
package net.minestom.server.item.component;
import net.minestom.server.network.NetworkBuffer;
public enum MapPostProcessing {
LOCK,
SCALE;
private static final MapPostProcessing[] VALUES = values();
public static final NetworkBuffer.Type<MapPostProcessing> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(NetworkBuffer buffer, MapPostProcessing value) {
buffer.write(NetworkBuffer.VAR_INT, value.ordinal());
}
@Override
public MapPostProcessing read(NetworkBuffer buffer) {
return VALUES[buffer.read(NetworkBuffer.VAR_INT)];
}
};
}

View File

@ -0,0 +1,40 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.minestom.server.item.Material;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record PotDecorations(
@NotNull Material back,
@NotNull Material left,
@NotNull Material right,
@NotNull Material front
) {
public static final PotDecorations EMPTY = new PotDecorations(Material.AIR, Material.AIR, Material.AIR, Material.AIR);
public static NetworkBuffer.Type<PotDecorations> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, PotDecorations value) {
}
@Override
public PotDecorations read(@NotNull NetworkBuffer buffer) {
return null;
}
};
public static BinaryTagSerializer<PotDecorations> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull PotDecorations value) {
return null;
}
@Override
public @NotNull PotDecorations read(@NotNull BinaryTag tag) {
return null;
}
};
}

View File

@ -0,0 +1,16 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record SeededContainerLoot(@NotNull String lootTable, long seed) {
public static final BinaryTagSerializer<SeededContainerLoot> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new SeededContainerLoot(tag.getString("loot_table"), tag.getLong("seed")),
loot -> CompoundBinaryTag.builder()
.putString("loot_table", loot.lootTable)
.putLong("seed", loot.seed)
.build()
);
}

View File

@ -0,0 +1,28 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record Unbreakable(boolean showInTooltip) {
public static final Unbreakable DEFAULT = new Unbreakable(false);
public static final NetworkBuffer.Type<Unbreakable> NETWORK_TYPE = new NetworkBuffer.Type<Unbreakable>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Unbreakable value) {
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip());
}
@Override
public Unbreakable read(@NotNull NetworkBuffer buffer) {
return new Unbreakable(buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final BinaryTagSerializer<Unbreakable> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Unbreakable(tag.getBoolean("showInTooltip", false)),
unbreakable -> CompoundBinaryTag.builder().putBoolean("showInTooltip", unbreakable.showInTooltip()).build()
);
}

View File

@ -2,11 +2,13 @@ package net.minestom.server.item.firework;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.color.Color;
import net.minestom.server.item.component.FireworkExplosion;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@Deprecated
public record FireworkEffect(boolean flicker, boolean trail,
@NotNull FireworkEffectType type,
@NotNull List<Color> colors,
@ -16,6 +18,12 @@ public record FireworkEffect(boolean flicker, boolean trail,
fadeColors = List.copyOf(fadeColors);
}
public FireworkEffect(@NotNull FireworkExplosion explosion) {
this(explosion.hasTwinkle(), explosion.hasTrail(),
FireworkEffectType.fromExplosionShape(explosion.shape()),
explosion.colors(), explosion.fadeColors());
}
/**
* Retrieves a firework effect from the given {@code compound}.
*
@ -52,4 +60,11 @@ public record FireworkEffect(boolean flicker, boolean trail,
.putIntArray("FadeColors", fadeColors.stream().mapToInt(Color::asRGB).toArray())
.build();
}
public @NotNull FireworkExplosion toExplosion() {
return new FireworkExplosion(
type.toExplosionShape(),
colors, fadeColors,
trail, flicker);
}
}

View File

@ -2,10 +2,13 @@ package net.minestom.server.item.firework;
import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap;
import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap;
import net.minestom.server.item.component.FireworkExplosion;
import org.jetbrains.annotations.NotNull;
/**
* An enumeration that representing all available firework types.
*/
@Deprecated
public enum FireworkEffectType {
SMALL_BALL((byte) 0),
LARGE_BALL((byte) 1),
@ -37,6 +40,16 @@ public enum FireworkEffectType {
return BY_ID.get(id);
}
public static FireworkEffectType fromExplosionShape(@NotNull FireworkExplosion.Shape shape) {
return switch (shape) {
case SMALL_BALL -> SMALL_BALL;
case LARGE_BALL -> LARGE_BALL;
case STAR -> STAR_SHAPED;
case CREEPER -> CREEPER_SHAPED;
case BURST -> BURST;
};
}
/**
* Retrieves the type of the firework effect.
*
@ -45,5 +58,15 @@ public enum FireworkEffectType {
public byte getType() {
return type;
}
public FireworkExplosion.Shape toExplosionShape() {
return switch (this) {
case SMALL_BALL -> FireworkExplosion.Shape.SMALL_BALL;
case LARGE_BALL -> FireworkExplosion.Shape.LARGE_BALL;
case STAR_SHAPED -> FireworkExplosion.Shape.STAR;
case CREEPER_SHAPED -> FireworkExplosion.Shape.CREEPER;
case BURST -> FireworkExplosion.Shape.BURST;
};
}
}

View File

@ -2,51 +2,51 @@ package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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.jetbrains.annotations.UnknownNullability;
import java.util.ArrayList;
import java.util.List;
@ApiStatus.Experimental
public record BundleMeta(TagReadable readable) implements ItemMetaView<BundleMeta.Builder> {
private static final Tag<List<ItemStack>> ITEMS = Tag.ItemStack("Items").list().defaultValue(List.of());
@Deprecated
public record BundleMeta(ItemComponentPatch components) implements ItemMetaView<BundleMeta.Builder> {
public @NotNull List<ItemStack> getItems() {
return getTag(ITEMS);
return components.get(ItemComponent.BUNDLE_CONTENTS, List.of());
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public Builder items(@NotNull List<ItemStack> items) {
setTag(ITEMS, items);
if (items.isEmpty()) {
components.remove(ItemComponent.BUNDLE_CONTENTS);
} else {
components.set(ItemComponent.BUNDLE_CONTENTS, items);
}
return this;
}
@ApiStatus.Experimental
public Builder addItem(@NotNull ItemStack item) {
var newList = new ArrayList<>(getTag(ITEMS));
var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of()));
newList.add(item);
return items(newList);
}
@ApiStatus.Experimental
public Builder removeItem(@NotNull ItemStack item) {
var newList = new ArrayList<>(getTag(ITEMS));
var newList = new ArrayList<>(components.get(ItemComponent.BUNDLE_CONTENTS, List.of()));
newList.remove(item);
return items(newList);
}
}
}

View File

@ -3,66 +3,58 @@ package net.minestom.server.item.metadata;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
import net.minestom.server.item.component.LodestoneTracker;
import net.minestom.server.tag.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
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);
}
@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());
}
});
@Deprecated
public record CompassMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<CompassMeta.Builder> {
public boolean isLodestoneTracked() {
return getTag(LODESTONE_TRACKED);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER);
return tracker != null && tracker.tracked();
}
public @Nullable String getLodestoneDimension() {
return getTag(LODESTONE_DIMENSION);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER);
return tracker == null ? null : tracker.dimension();
}
public @Nullable Point getLodestonePosition() {
return getTag(LODESTONE_POSITION);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER);
return tracker == null ? null : tracker.blockPosition();
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
// The empty state isnt really valid because the dimension is empty (invalid), but these functions need to set each so its simpler.
private static final LodestoneTracker EMPTY = new LodestoneTracker("", Vec.ZERO, false);
public Builder lodestoneTracked(boolean lodestoneTracked) {
setTag(LODESTONE_TRACKED, lodestoneTracked);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY);
components.set(ItemComponent.LODESTONE_TRACKER, tracker.withTracked(lodestoneTracked));
return this;
}
public Builder lodestoneDimension(@Nullable String lodestoneDimension) {
setTag(LODESTONE_DIMENSION, lodestoneDimension);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY);
components.set(ItemComponent.LODESTONE_TRACKER, tracker.withDimension(lodestoneDimension));
return this;
}
public Builder lodestonePosition(@Nullable Point lodestonePosition) {
setTag(LODESTONE_POSITION, lodestonePosition);
LodestoneTracker tracker = components.get(ItemComponent.LODESTONE_TRACKER, EMPTY);
components.set(ItemComponent.LODESTONE_TRACKER, tracker.withBlockPosition(lodestonePosition));
return this;
}
}

View File

@ -2,48 +2,52 @@ package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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.UnknownNullability;
import java.util.List;
public record CrossbowMeta(TagReadable readable) implements ItemMetaView<CrossbowMeta.Builder> {
private static final Tag<List<ItemStack>> PROJECTILES = Tag.ItemStack("ChargedProjectiles").list().defaultValue(List.of());
private static final Tag<Boolean> CHARGED = Tag.Boolean("Charged").defaultValue(false);
@Deprecated
public record CrossbowMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<CrossbowMeta.Builder> {
public @NotNull List<ItemStack> getProjectiles() {
return getTag(PROJECTILES);
return components.get(ItemComponent.CHARGED_PROJECTILES, List.of());
}
public boolean isCharged() {
return getTag(CHARGED);
return components.has(ItemComponent.CHARGED_PROJECTILES);
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public Builder projectile(@NotNull ItemStack projectile) {
setTag(PROJECTILES, List.of(projectile));
components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile));
return this;
}
public Builder projectiles(@NotNull ItemStack projectile1, @NotNull ItemStack projectile2, @NotNull ItemStack projectile3) {
setTag(PROJECTILES, List.of(projectile1, projectile2, projectile3));
components.set(ItemComponent.CHARGED_PROJECTILES, List.of(projectile1, projectile2, projectile3));
return this;
}
public Builder charged(boolean charged) {
setTag(CHARGED, charged);
if (charged) {
// Only reset to empty list if we dont have any projectiles yet, as to not overwrite the call to projectiles()
if (!components.has(ItemComponent.CHARGED_PROJECTILES))
components.set(ItemComponent.CHARGED_PROJECTILES, List.of());
} else {
components.remove(ItemComponent.CHARGED_PROJECTILES);
}
return this;
}
}

View File

@ -2,55 +2,49 @@ package net.minestom.server.item.metadata;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.ItemSerializers;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.EnchantmentList;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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.UnknownNullability;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static net.minestom.server.item.ItemSerializers.ENCHANTMENT_SERIALIZER;
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());
@Deprecated
public record EnchantedBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<EnchantedBookMeta.Builder> {
public @NotNull Map<Enchantment, Short> getStoredEnchantmentMap() {
return getTag(ENCHANTMENTS);
EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY);
Map<Enchantment, Short> map = new HashMap<>();
for (var entry : value.enchantments().entrySet())
map.put(entry.getKey(), entry.getValue().shortValue());
return map;
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public @NotNull Builder enchantments(@NotNull Map<Enchantment, Short> enchantments) {
setTag(ENCHANTMENTS, Map.copyOf(enchantments));
Map<Enchantment, Integer> map = new HashMap<>();
enchantments.forEach((enchantment, level) -> map.put(enchantment, (int) level));
// Fetch existing to preserve the showInTooltip value.
EnchantmentList existing = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY);
components.set(ItemComponent.STORED_ENCHANTMENTS, new EnchantmentList(map, existing.showInTooltip()));
return this;
}
public @NotNull Builder enchantment(@NotNull Enchantment enchantment, short level) {
var enchantments = new HashMap<>(getTag(ENCHANTMENTS));
enchantments.put(enchantment, level);
return enchantments(enchantments);
EnchantmentList value = components.get(ItemComponent.STORED_ENCHANTMENTS, EnchantmentList.EMPTY);
components.set(ItemComponent.STORED_ENCHANTMENTS, value.with(enchantment, level));
return this;
}
}
}

View File

@ -1,35 +1,34 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.component.*;
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 net.minestom.server.tag.TagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
public record FireworkEffectMeta(TagReadable readable) implements ItemMetaView<FireworkEffectMeta.Builder> {
private static final Tag<FireworkEffect> FIREWORK_EFFECT = Tag.Structure("Explosion",
TagSerializer.fromCompound(FireworkEffect::fromCompound, FireworkEffect::asCompound));
@Deprecated
public record FireworkEffectMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<FireworkEffectMeta.Builder> {
public @Nullable FireworkEffect getFireworkEffect() {
return getTag(FIREWORK_EFFECT);
FireworkExplosion explosion = components.get(ItemComponent.FIREWORK_EXPLOSION);
return explosion == null ? null : new FireworkEffect(explosion);
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder {
public Builder effect(@Nullable FireworkEffect fireworkEffect) {
setTag(FIREWORK_EFFECT, fireworkEffect);
if (fireworkEffect == null) {
components.remove(ItemComponent.FIREWORK_EXPLOSION);
} else {
components.set(ItemComponent.FIREWORK_EXPLOSION, fireworkEffect.toExplosion());
}
return this;
}
}

View File

@ -1,48 +1,48 @@
package net.minestom.server.item.metadata;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.FireworkList;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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 net.minestom.server.tag.TagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.List;
public record FireworkMeta(TagReadable readable) implements ItemMetaView<FireworkMeta.Builder> {
private static final Tag<List<FireworkEffect>> EFFECTS = Tag.Structure("Explosions",
TagSerializer.fromCompound(FireworkEffect::fromCompound, FireworkEffect::asCompound))
.path("Fireworks").list().defaultValue(List.of());
private static final Tag<Byte> FLIGHT_DURATION = Tag.Byte("Flight").path("Fireworks");
@Deprecated
public record FireworkMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<FireworkMeta.Builder> {
public @NotNull List<FireworkEffect> getEffects() {
return getTag(EFFECTS);
FireworkList value = components.get(ItemComponent.FIREWORKS);
return value == null ? List.of() : value.explosions().stream().map(FireworkEffect::new).toList();
}
public @Nullable Byte getFlightDuration() {
return getTag(FLIGHT_DURATION);
FireworkList value = components.get(ItemComponent.FIREWORKS);
return value == null ? null : value.flightDuration();
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public Builder effects(List<FireworkEffect> effects) {
setTag(EFFECTS, effects);
FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY);
components.set(ItemComponent.FIREWORKS, value.withExplosions(effects.stream().map(FireworkEffect::toExplosion).toList()));
return this;
}
public Builder flightDuration(byte flightDuration) {
setTag(FLIGHT_DURATION, flightDuration);
FireworkList value = components.get(ItemComponent.FIREWORKS, FireworkList.EMPTY);
components.set(ItemComponent.FIREWORKS, value.withFlightDuration(flightDuration));
return this;
}
}

View File

@ -2,32 +2,38 @@ package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.DyedItemColor;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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;
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);
@Deprecated
public record LeatherArmorMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<LeatherArmorMeta.Builder> {
public @Nullable Color getColor() {
return getTag(COLOR);
DyedItemColor value = components.get(ItemComponent.DYED_COLOR);
return value == null ? null : value.color();
}
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
@Deprecated
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public Builder color(@Nullable Color color) {
setTag(COLOR, color);
if (color == null) {
components.remove(ItemComponent.DYED_COLOR);
} else {
DyedItemColor value = components.get(ItemComponent.DYED_COLOR, DyedItemColor.LEATHER);
components.set(ItemComponent.DYED_COLOR, value.withColor(color));
}
return this;
}
}

View File

@ -2,14 +2,21 @@ package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.item.component.ItemComponentPatch;
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.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.List;
public record MapMeta(TagReadable readable) implements ItemMetaView<MapMeta.Builder> {
public record MapMeta(@NotNull ItemComponentPatch components) 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>() {
@ -53,13 +60,10 @@ public record MapMeta(TagReadable readable) implements ItemMetaView<MapMeta.Buil
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder {
public Builder mapId(int value) {
setTag(MAP_ID, value);

View File

@ -6,7 +6,14 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.tag.*;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.item.component.ItemComponentPatch;
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.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
@ -15,7 +22,7 @@ import java.util.List;
import java.util.Objects;
import java.util.UUID;
public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView<PlayerHeadMeta.Builder> {
public record PlayerHeadMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<PlayerHeadMeta.Builder> {
public static final Tag<UUID> SKULL_OWNER = Tag.UUID("Id").path("SkullOwner");
public static final Tag<PlayerSkin> SKIN = Tag.Structure("Properties", new TagSerializer<PlayerSkin>() {
private static final Tag<BinaryTag> TEXTURES = Tag.NBT("textures");
@ -50,13 +57,10 @@ public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView<Playe
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder {
public Builder skullOwner(@Nullable UUID skullOwner) {
setTag(SKULL_OWNER, skullOwner);

View File

@ -2,17 +2,24 @@ package net.minestom.server.item.metadata;
import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.item.component.ItemComponentPatch;
import net.minestom.server.potion.CustomPotionEffect;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.tag.*;
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.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.List;
public record PotionMeta(TagReadable readable) implements ItemMetaView<PotionMeta.Builder> {
public record PotionMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<PotionMeta.Builder> {
private static final Tag<PotionType> POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.WATER);
private static final Tag<List<CustomPotionEffect>> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer<CustomPotionEffect>() {
@Override
@ -55,13 +62,10 @@ public record PotionMeta(TagReadable readable) implements ItemMetaView<PotionMet
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder {
public Builder potionType(@NotNull PotionType potionType) {
setTag(POTION_TYPE, potionType);

View File

@ -3,15 +3,16 @@ 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.ItemMetaView;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentPatch;
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.UnknownNullability;
import java.util.List;
public record WritableBookMeta(TagReadable readable) implements ItemMetaView<WritableBookMeta.Builder> {
public record WritableBookMeta(@NotNull ItemComponentPatch components) 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))
@ -23,13 +24,10 @@ public record WritableBookMeta(TagReadable readable) implements ItemMetaView<Wri
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
public record Builder(@NotNull ItemComponentPatch.Builder components) implements ItemMetaView.Builder {
public Builder pages(@NotNull List<@NotNull Component> pages) {
setTag(PAGES, pages);

View File

@ -4,9 +4,11 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.item.component.ItemComponentPatch;
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;
@ -14,7 +16,7 @@ import org.jetbrains.annotations.UnknownNullability;
import java.util.Arrays;
import java.util.List;
public record WrittenBookMeta(TagReadable readable) implements ItemMetaView<WrittenBookMeta.Builder> {
public record WrittenBookMeta(@NotNull ItemComponentPatch components) implements ItemMetaView<WrittenBookMeta.Builder> {
private static final Tag<Boolean> RESOLVED = Tag.Boolean("resolved").defaultValue(false);
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");
@ -45,17 +47,14 @@ public record WrittenBookMeta(TagReadable readable) implements ItemMetaView<Writ
@Override
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
return readable.getTag(tag);
return components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).getTag(tag);
}
public enum WrittenBookGeneration {
ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED
}
public record Builder(TagHandler tagHandler) implements ItemMetaView.Builder {
public Builder() {
this(TagHandler.newHandler());
}
public record Builder(@NotNull ItemComponentMap.Builder components) implements ItemMetaView.Builder {
public Builder resolved(boolean resolved) {
setTag(RESOLVED, resolved);

View File

@ -1,40 +0,0 @@
package net.minestom.server.item.rule;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.StackingRule;
import net.minestom.server.utils.MathUtils;
import org.jetbrains.annotations.NotNull;
public final class VanillaStackingRule implements StackingRule {
@Override
public boolean canBeStacked(@NotNull ItemStack item1, @NotNull ItemStack item2) {
return item1.isSimilar(item2);
}
@Override
public boolean canApply(@NotNull ItemStack item, int newAmount) {
return MathUtils.isBetween(newAmount, 0, getMaxSize(item));
}
@Override
public @NotNull ItemStack apply(@NotNull ItemStack item, int amount) {
return amount > 0 ? item.withAmount(amount) : ItemStack.AIR;
}
@Override
public int getAmount(@NotNull ItemStack itemStack) {
return itemStack.amount();
}
@Override
public int getMaxSize(@NotNull ItemStack itemStack) {
return itemStack.material().maxStackSize();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
return obj != null && getClass() == obj.getClass();
}
}

View File

@ -2,6 +2,7 @@ package net.minestom.server.network;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.color.Color;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.metadata.animal.ArmadilloMeta;
@ -29,6 +30,7 @@ import java.util.function.Function;
@ApiStatus.Experimental
public final class NetworkBuffer {
public static final Type<Void> NOTHING = new NetworkBufferTypeImpl.NothingType();
public static final Type<Boolean> BOOLEAN = new NetworkBufferTypeImpl.BooleanType();
public static final Type<Byte> BYTE = new NetworkBufferTypeImpl.ByteType();
public static final Type<Short> SHORT = new NetworkBufferTypeImpl.ShortType();
@ -76,6 +78,7 @@ public final class NetworkBuffer {
public static final Type<SnifferMeta.State> SNIFFER_STATE = NetworkBufferTypeImpl.fromEnum(SnifferMeta.State.class);
public static final Type<ArmadilloMeta.State> ARMADILLO_STATE = NetworkBufferTypeImpl.fromEnum(ArmadilloMeta.State.class);
public static final Type<Color> COLOR = new NetworkBufferTypeImpl.ColorType();
ByteBuffer nioBuffer;
final boolean resizable;
@ -294,8 +297,21 @@ public final class NetworkBuffer {
public interface Type<T> {
void write(@NotNull NetworkBuffer buffer, T value);
T read(@NotNull NetworkBuffer buffer);
default @NotNull Type<List<T>> list(int maxSize) {
return new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, List<T> value) {
buffer.writeCollection(Type.this, value);
}
@Override
public List<T> read(@NotNull NetworkBuffer buffer) {
return buffer.readCollection(Type.this, maxSize);
}
};
}
}
@FunctionalInterface

View File

@ -5,10 +5,12 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.adventure.serializer.nbt.NbtComponentSerializer;
import net.minestom.server.color.Color;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.network.packet.server.play.data.WorldPos;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.data.ParticleData;
@ -27,6 +29,17 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
int SEGMENT_BITS = 0x7F;
int CONTINUE_BIT = 0x80;
record NothingType() implements NetworkBufferTypeImpl<Void> {
@Override
public void write(@NotNull NetworkBuffer buffer, Void value) {
}
@Override
public Void read(@NotNull NetworkBuffer buffer) {
return null;
}
}
record BooleanType() implements NetworkBufferTypeImpl<Boolean> {
@Override
public void write(@NotNull NetworkBuffer buffer, Boolean value) {
@ -403,8 +416,11 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
buffer.write(VAR_INT, value.amount());
buffer.write(VAR_INT, value.material().id());
buffer.write(VAR_INT, 0); // Added components
buffer.write(VAR_INT, 1); // Added components
buffer.write(VAR_INT, 0); // Removed components
var component = ItemComponent.MAX_STACK_SIZE;
buffer.write(VAR_INT, component.id());
buffer.write(VAR_INT, 16);
}
@Override
@ -600,6 +616,18 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
record ColorType() implements NetworkBufferTypeImpl<Color> {
@Override
public void write(@NotNull NetworkBuffer buffer, Color value) {
buffer.write(NetworkBuffer.INT, value.asRGB());
}
@Override
public Color read(@NotNull NetworkBuffer buffer) {
return new Color(buffer.read(NetworkBuffer.INT));
}
}
static <T extends Enum<?>> NetworkBufferTypeImpl<T> fromEnum(Class<T> enumClass) {
return new NetworkBufferTypeImpl<>() {
@Override

View File

@ -9,20 +9,19 @@ import net.minestom.server.collision.BoundingBox;
import net.minestom.server.collision.CollisionUtils;
import net.minestom.server.collision.Shape;
import net.minestom.server.entity.EntitySpawnType;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray;
import net.minestom.server.utils.nbt.BinaryTagReader;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@ -475,12 +474,11 @@ public final class Registry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
private final int maxStackSize;
private final int maxDamage;
private final boolean isFood;
private final Supplier<Block> blockSupplier;
private final EquipmentSlot equipmentSlot;
private final EntityType entityType;
private final ItemComponentMap prototype;
// private final EquipmentSlot equipmentSlot; //todo
// private final EntityType entityType; //todo
private final Properties custom;
private MaterialEntry(String namespace, Properties main, Properties custom) {
@ -488,35 +486,47 @@ public final class Registry {
this.namespace = NamespaceID.from(namespace);
this.id = main.getInt("id");
this.translationKey = main.getString("translationKey");
this.maxStackSize = main.getInt("maxStackSize", 64);
this.maxDamage = main.getInt("maxDamage", 0);
this.isFood = main.getBoolean("edible", false);
{
final String blockNamespace = main.getString("correspondingBlock", null);
this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null;
}
{
final Properties armorProperties = main.section("armorProperties");
if (armorProperties != null) {
switch (armorProperties.getString("slot")) {
case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS;
case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS;
case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE;
case "head" -> this.equipmentSlot = EquipmentSlot.HELMET;
default -> this.equipmentSlot = null;
}
} else {
this.equipmentSlot = null;
}
}
{
final Properties spawnEggProperties = main.section("spawnEggProperties");
if (spawnEggProperties != null) {
this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType"));
} else {
this.entityType = null;
try {
ItemComponentMap.Builder builder = ItemComponentMap.builder();
for (Map.Entry<String, Object> entry : main.section("components")) {
//noinspection unchecked
ItemComponent<Object> component = (ItemComponent<Object>) ItemComponent.fromNamespaceId(entry.getKey());
Check.notNull(component, "Unknown component: " + entry.getKey());
byte[] rawValue = Base64.getDecoder().decode((String) entry.getValue());
BinaryTagReader reader = new BinaryTagReader(new DataInputStream(new ByteArrayInputStream(rawValue)));
builder.set(component, component.read(reader.readNameless()));
}
this.prototype = builder.build();
} catch (IOException e) {
throw new RuntimeException("failed to parse material registry: " + namespace, e);
}
// {
// final Properties armorProperties = main.section("armorProperties");
// if (armorProperties != null) {
// switch (armorProperties.getString("slot")) {
// case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS;
// case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS;
// case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE;
// case "head" -> this.equipmentSlot = EquipmentSlot.HELMET;
// default -> this.equipmentSlot = null;
// }
// } else {
// this.equipmentSlot = null;
// }
// }
// {
// final Properties spawnEggProperties = main.section("spawnEggProperties");
// if (spawnEggProperties != null) {
// this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType"));
// } else {
// this.entityType = null;
// }
// }
}
public @NotNull NamespaceID namespace() {
@ -527,41 +537,33 @@ public final class Registry {
return id;
}
public String translationKey() {
public @NotNull String translationKey() {
return translationKey;
}
public int maxStackSize() {
return maxStackSize;
}
public int maxDamage() {
return maxDamage;
}
public boolean isFood() {
return isFood;
}
public @Nullable Block block() {
return blockSupplier.get();
}
public boolean isArmor() {
return equipmentSlot != null;
public @NotNull ItemComponentMap prototype() {
return prototype;
}
public @Nullable EquipmentSlot equipmentSlot() {
return equipmentSlot;
}
/**
* Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg)
* @return The entity type it can spawn, or null if it is not a spawn egg
*/
public @Nullable EntityType spawnEntityType() {
return entityType;
}
// public boolean isArmor() {
// return equipmentSlot != null;
// }
//
// public @Nullable EquipmentSlot equipmentSlot() {
// return equipmentSlot;
// }
//
// /**
// * Gets the entity type this item can spawn. Only present for spawn eggs (e.g. wolf spawn egg, skeleton spawn egg)
// * @return The entity type it can spawn, or null if it is not a spawn egg
// */
// public @Nullable EntityType spawnEntityType() {
// return entityType;
// }
@Override
public Properties custom() {

View File

@ -5,8 +5,8 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.ServerFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.utils.UniqueIdUtils;
import java.util.UUID;
import java.util.function.Function;
/**
@ -23,9 +23,7 @@ final class Serializers {
static final Entry<String, StringBinaryTag> STRING = new Entry<>(BinaryTagTypes.STRING, StringBinaryTag::value, StringBinaryTag::stringBinaryTag);
static final Entry<BinaryTag, BinaryTag> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
static final Entry<java.util.UUID, IntArrayBinaryTag> UUID = new Entry<>(BinaryTagTypes.INT_ARRAY,
intArray -> intArrayToUuid(intArray.value()),
uuid -> IntArrayBinaryTag.intArrayBinaryTag(uuidToIntArray(uuid)));
static final Entry<java.util.UUID, IntArrayBinaryTag> UUID = new Entry<>(BinaryTagTypes.INT_ARRAY, UniqueIdUtils::fromNbt, UniqueIdUtils::toNbt);
static final Entry<ItemStack, CompoundBinaryTag> ITEM = new Entry<>(BinaryTagTypes.COMPOUND, ItemStack::fromItemNBT, ItemStack::toItemNBT);
static final Entry<Component, StringBinaryTag> COMPONENT = new Entry<>(BinaryTagTypes.STRING, input -> GsonComponentSerializer.gson().deserialize(input.value()),
component -> StringBinaryTag.stringBinaryTag(GsonComponentSerializer.gson().serialize(component)));
@ -59,22 +57,4 @@ final class Serializers {
return writer.apply(value);
}
}
private static int[] uuidToIntArray(UUID uuid) {
final long uuidMost = uuid.getMostSignificantBits();
final long uuidLeast = uuid.getLeastSignificantBits();
return new int[]{
(int) (uuidMost >> 32),
(int) uuidMost,
(int) (uuidLeast >> 32),
(int) uuidLeast
};
}
private static UUID intArrayToUuid(int[] array) {
final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
return new UUID(uuidMost, uuidLeast);
}
}

View File

@ -263,7 +263,6 @@ public class Tag<T> {
return tag(key, Serializers.STRING);
}
@ApiStatus.Experimental
public static @NotNull Tag<UUID> UUID(@NotNull String key) {
return tag(key, Serializers.UUID);
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.utils;
import net.kyori.adventure.nbt.IntArrayBinaryTag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import java.util.regex.Pattern;
@ -21,4 +23,30 @@ public final class UniqueIdUtils {
public static boolean isUniqueId(String input) {
return input.matches(UNIQUE_ID_PATTERN.pattern());
}
public static @NotNull UUID fromNbt(@NotNull IntArrayBinaryTag tag) {
return intArrayToUuid(tag.value());
}
public static @NotNull IntArrayBinaryTag toNbt(@NotNull UUID uuid) {
return IntArrayBinaryTag.intArrayBinaryTag(uuidToIntArray(uuid));
}
public static int[] uuidToIntArray(UUID uuid) {
final long uuidMost = uuid.getMostSignificantBits();
final long uuidLeast = uuid.getLeastSignificantBits();
return new int[]{
(int) (uuidMost >> 32),
(int) uuidMost,
(int) (uuidLeast >> 32),
(int) uuidLeast
};
}
public static UUID intArrayToUuid(int[] array) {
final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
return new UUID(uuidMost, uuidLeast);
}
}

View File

@ -33,5 +33,4 @@ public class BinaryTagReader {
String name = input.readUTF();
return Map.entry(name, type.read(input));
}
}

View File

@ -0,0 +1,119 @@
package net.minestom.server.utils.nbt;
import net.kyori.adventure.nbt.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public interface BinaryTagSerializer<T> {
BinaryTagSerializer<Void> NOTHING = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull Void value) {
return EndBinaryTag.endBinaryTag();
}
@Override
public @NotNull Void read(@NotNull BinaryTag tag) {
return null;
}
};
BinaryTagSerializer<Byte> BYTE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull Byte value) {
return ByteBinaryTag.byteBinaryTag(value);
}
@Override
public @NotNull Byte read(@NotNull BinaryTag tag) {
return tag instanceof ByteBinaryTag byteBinaryTag ? byteBinaryTag.value() : 0;
}
};
BinaryTagSerializer<Boolean> BOOLEAN = BYTE.map(b -> b != 0, b -> (byte) (b ? 1 : 0));
BinaryTagSerializer<Integer> INT = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull Integer value) {
return IntBinaryTag.intBinaryTag(value);
}
@Override
public @NotNull Integer read(@NotNull BinaryTag tag) {
return tag instanceof IntBinaryTag intBinaryTag ? intBinaryTag.value() : 0;
}
};
BinaryTagSerializer<String> STRING = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull String value) {
return StringBinaryTag.stringBinaryTag(value);
}
@Override
public @NotNull String read(@NotNull BinaryTag tag) {
return tag instanceof StringBinaryTag stringBinaryTag ? stringBinaryTag.value() : "";
}
};
BinaryTagSerializer<CompoundBinaryTag> COMPOUND = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull CompoundBinaryTag value) {
return value;
}
@Override
public @NotNull CompoundBinaryTag read(@NotNull BinaryTag tag) {
return tag instanceof CompoundBinaryTag compoundBinaryTag ? compoundBinaryTag : CompoundBinaryTag.empty();
}
};
BinaryTagSerializer<Component> JSON_COMPONENT = STRING.map(
s -> GsonComponentSerializer.gson().deserialize(s),
c -> GsonComponentSerializer.gson().serialize(c)
);
BinaryTagSerializer<ItemStack> ITEM = COMPOUND.map(ItemStack::fromItemNBT, ItemStack::toItemNBT);
@NotNull BinaryTag write(@NotNull T value);
@NotNull T read(@NotNull BinaryTag tag);
default <S> BinaryTagSerializer<S> map(@NotNull Function<T, S> to, @NotNull Function<S, T> from) {
return new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull S value) {
return BinaryTagSerializer.this.write(from.apply(value));
}
@Override
public @NotNull S read(@NotNull BinaryTag tag) {
return to.apply(BinaryTagSerializer.this.read(tag));
}
};
}
default BinaryTagSerializer<List<T>> list() {
return new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull List<T> value) {
ListBinaryTag.Builder<BinaryTag> builder = ListBinaryTag.builder();
for (T t : value) builder.add(BinaryTagSerializer.this.write(t));
return builder.build();
}
@Override
public @NotNull List<T> read(@NotNull BinaryTag tag) {
if (!(tag instanceof ListBinaryTag listBinaryTag)) return List.of();
List<T> list = new ArrayList<>();
for (BinaryTag element : listBinaryTag)
list.add(BinaryTagSerializer.this.read(element));
return list;
}
};
}
}

View File

@ -36,5 +36,4 @@ public class BinaryTagWriter {
output.writeUTF(name);
type.write(tag, output);
}
}