chore: more components

This commit is contained in:
mworzala 2024-04-12 09:28:07 -04:00 committed by Matt Worzala
parent 65e04d0a94
commit af85459761
10 changed files with 389 additions and 58 deletions

View File

@ -18,6 +18,7 @@ import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag;
@ -48,9 +49,10 @@ import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.metadata.WrittenBookMeta;
import net.minestom.server.item.component.WrittenBookContent;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.message.ChatMessageType;
import net.minestom.server.message.ChatPosition;
@ -181,7 +183,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
protected PlayerInventory inventory;
private Inventory openInventory;
// Used internally to allow the closing of inventory within the inventory listener
private boolean didCloseInventory;
private boolean skipClosePacket;
private byte heldSlot;
@ -1000,13 +1002,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
closeInventory();
}
// TODO: when adventure updates, delete this
String title = PlainTextComponentSerializer.plainText().serialize(book.title());
String author = PlainTextComponentSerializer.plainText().serialize(book.author());
final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
.meta(WrittenBookMeta.class, builder -> builder.resolved(false)
.generation(WrittenBookMeta.WrittenBookGeneration.ORIGINAL)
.author(book.author())
.title(book.title())
.pages(book.pages()))
.set(ItemComponent.WRITTEN_BOOK_CONTENT, new WrittenBookContent(book.pages(), title, author, 0, false))
.build();
// Set book in offhand
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook));
// Open the book
@ -1726,6 +1728,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
return openInventory;
}
private void tryCloseInventory(boolean skipClosePacket) {
var closedInventory = getOpenInventory();
if (closedInventory == null) return;
this.skipClosePacket = skipClosePacket;
if (closedInventory.removeViewer(this)) {
if (closedInventory == getOpenInventory()) {
this.openInventory = null;
}
}
}
/**
* Opens the specified Inventory, close the previous inventory if existing.
*
@ -1736,21 +1751,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
Inventory openInventory = getOpenInventory();
if (openInventory != null) {
openInventory.removeViewer(this);
}
tryCloseInventory(true);
Inventory newInventory = inventoryOpenEvent.getInventory();
if (newInventory == null) {
// just close the inventory
return;
if (newInventory.addViewer(this)) {
this.openInventory = newInventory;
}
sendPacket(new OpenWindowPacket(newInventory.getWindowId(),
newInventory.getInventoryType().getWindowType(), newInventory.getTitle()));
newInventory.addViewer(this);
this.openInventory = newInventory;
});
return !inventoryOpenEvent.isCancelled();
}
@ -1799,27 +1805,16 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
/**
* Used internally to prevent an inventory click to be processed
* when the inventory listeners closed the inventory.
* <p>
* Should only be used within an inventory listener (event or condition).
*
* @return true if the inventory has been closed, false otherwise
*/
public boolean didCloseInventory() {
return didCloseInventory;
}
/**
* Used internally to reset the didCloseInventory field.
* Used internally to reset the skipClosePacket field, which determines when sending the close inventory packet
* should be skipped.
* <p>
* Shouldn't be used externally without proper understanding of its consequence.
*
* @param didCloseInventory the new didCloseInventory field
* @param skipClosePacket the new skipClosePacket field
*/
@ApiStatus.Internal
public void UNSAFE_changeDidCloseInventory(boolean didCloseInventory) {
this.didCloseInventory = didCloseInventory;
public void UNSAFE_changeSkipClosePacket(boolean skipClosePacket) {
this.skipClosePacket = skipClosePacket;
}
public int getNextTeleportId() {

View File

@ -51,8 +51,8 @@ public sealed interface ItemComponent<T> extends StaticProtocolObject permits It
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<WritableBookContent> WRITABLE_BOOK_CONTENT = declare("writable_book_content", WritableBookContent.NETWORK_TYPE, WritableBookContent.NBT_TYPE);
ItemComponent<WrittenBookContent> WRITTEN_BOOK_CONTENT = declare("written_book_content", WrittenBookContent.NETWORK_TYPE, WrittenBookContent.NBT_TYPE);
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);
@ -76,8 +76,11 @@ public sealed interface ItemComponent<T> extends StaticProtocolObject permits It
ItemComponent<SeededContainerLoot> CONTAINER_LOOT = declare("container_loot", null, SeededContainerLoot.NBT_TYPE);
@NotNull T read(@NotNull BinaryTag tag);
@NotNull BinaryTag write(@NotNull T value);
@NotNull T read(@NotNull NetworkBuffer reader);
void write(@NotNull NetworkBuffer writer, @NotNull T value);
static @Nullable ItemComponent<?> fromNamespaceId(@NotNull String namespaceId) {
return ItemComponentImpl.NAMESPACES.get(namespaceId);

View File

@ -5,10 +5,6 @@ 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);
@ -18,12 +14,4 @@ public interface ItemComponentMap {
return value != null ? value : defaultValue;
}
interface Builder {
<T> @NotNull Builder set(@NotNull ItemComponent<T> component, @NotNull T value);
void remove(@NotNull ItemComponent<?> component);
@NotNull ItemComponentMap build();
}
}

View File

@ -1,18 +1,141 @@
package net.minestom.server.item;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public sealed interface ItemComponentPatch extends ItemComponentMap permits ItemComponentPatchImpl {
import java.util.Map;
@NotNull NetworkBuffer.Type<ItemComponentPatch> NETWORK_TYPE = null;
@NotNull BinaryTagSerializer<ItemComponentPatch> NBT_TYPE = null;
/**
* <p>Holds the altered components of an itemstack.</p>
*
* <p>The inner map contains the value for added components, null for removed components, and no entry for unmodified components.</p>
*
* @param patch
*/
record ItemComponentPatch(@NotNull Int2ObjectMap<Object> patch) {
private static final char REMOVAL_PREFIX = '!';
<T> @NotNull ItemComponentPatch with(@NotNull ItemComponent<T> component, T value);
public static final ItemComponentPatch EMPTY = new ItemComponentPatch(new Int2ObjectArrayMap<>(0));
@NotNull ItemComponentPatch without(@NotNull ItemComponent<?> component);
public static final @NotNull NetworkBuffer.Type<ItemComponentPatch> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ItemComponentPatch value) {
int added = 0;
for (Object o : value.patch.values()) {
if (o != null) added++;
}
buffer.write(NetworkBuffer.VAR_INT, added);
buffer.write(NetworkBuffer.VAR_INT, value.patch.size() - added);
for (Int2ObjectMap.Entry<Object> entry : value.patch.int2ObjectEntrySet()) {
if (entry.getValue() != null) {
buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey());
//noinspection unchecked
ItemComponent<Object> type = (ItemComponent<Object>) ItemComponent.fromId(entry.getIntKey());
assert type != null;
type.write(entry.getValue());
}
}
for (Int2ObjectMap.Entry<Object> entry : value.patch.int2ObjectEntrySet()) {
if (entry.getValue() == null) {
buffer.write(NetworkBuffer.VAR_INT, entry.getIntKey());
}
}
}
@Override
public ItemComponentPatch read(@NotNull NetworkBuffer buffer) {
int added = buffer.read(NetworkBuffer.VAR_INT);
int removed = buffer.read(NetworkBuffer.VAR_INT);
Int2ObjectMap<Object> patch = new Int2ObjectArrayMap<>(added + removed);
for (int i = 0; i < added; i++) {
int id = buffer.read(NetworkBuffer.VAR_INT);
//noinspection unchecked
ItemComponent<Object> type = (ItemComponent<Object>) ItemComponent.fromId(id);
Check.notNull(type, "Unknown item component id: {0}", id);
patch.put(id, type.read(buffer));
}
for (int i = 0; i < removed; i++) {
int id = buffer.read(NetworkBuffer.VAR_INT);
patch.put(id, null);
}
return new ItemComponentPatch(patch);
}
};
public static final @NotNull BinaryTagSerializer<ItemComponentPatch> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
if (tag.size() == 0) return EMPTY;
Int2ObjectMap<Object> patch = new Int2ObjectArrayMap<>(tag.size());
for (Map.Entry<String, ? extends BinaryTag> entry : tag) {
String key = entry.getKey();
boolean remove = false;
if (!key.isEmpty() && key.charAt(0) == REMOVAL_PREFIX) {
key = key.substring(1);
remove = true;
}
ItemComponent<?> type = ItemComponent.fromNamespaceId(key);
Check.notNull(type, "Unknown item component: {0}", key);
if (remove) {
patch.put(type.id(), null);
} else {
Object value = type.read(entry.getValue());
patch.put(type.id(), value);
}
}
return new ItemComponentPatch(patch);
},
patch -> {
if (patch.patch.isEmpty()) return CompoundBinaryTag.empty();
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
for (Int2ObjectMap.Entry<Object> entry : patch.patch.int2ObjectEntrySet()) {
//noinspection unchecked
ItemComponent<Object> type = (ItemComponent<Object>) ItemComponent.fromId(entry.getIntKey());
Check.notNull(type, "Unknown item component id: {0}", entry.getIntKey());
if (entry.getValue() == null) {
builder.put(REMOVAL_PREFIX + type.name(), CompoundBinaryTag.empty());
} else {
builder.put(type.name(), type.write(entry.getValue()));
}
}
return builder.build();
}
);
public boolean has(@NotNull ItemComponentMap prototype, @NotNull ItemComponent<?> component) {
if (patch.containsKey(component.id())) {
return patch.get(component.id()) != null;
} else {
return prototype.has(component);
}
}
public <T> @Nullable T get(@NotNull ItemComponentMap prototype, @NotNull ItemComponent<T> component) {
if (patch.containsKey(component.id())) {
return (T) patch.get(component.id());
} else {
return prototype.get(component);
}
}
public <T> @NotNull ItemComponentPatch with(@NotNull ItemComponent<T> component, @NotNull T value) {
Int2ObjectMap<Object> newPatch = new Int2ObjectArrayMap<>(patch);
newPatch.put(component.id(), value);
return new ItemComponentPatch(newPatch);
}
public <T> @NotNull ItemComponentPatch without(@NotNull ItemComponent<T> component) {
Int2ObjectMap<Object> newPatch = new Int2ObjectArrayMap<>(patch);
newPatch.put(component.id(), null);
return new ItemComponentPatch(newPatch);
}
interface Builder extends ItemComponentMap {

View File

@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.item.component.ItemComponent;
import net.minestom.server.item.component.ItemComponentMap;
import net.minestom.server.inventory.ContainerInventory;
@ -33,6 +34,8 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv
*/
@NotNull ItemStack AIR = ItemStack.of(Material.AIR);
@NotNull NetworkBuffer.Type<ItemStack> NETWORK_TYPE = ItemStackImpl.NETWORK_TYPE;
@Contract(value = "_ -> new", pure = true)
static @NotNull Builder builder(@NotNull Material material) {
return new ItemStackImpl.Builder(material, 1);
@ -93,7 +96,7 @@ public sealed interface ItemStack extends TagReadable, ItemComponentMap, HoverEv
@Contract(value = "_, _ -> new", pure = true)
default <T> @NotNull ItemStack withTag(@NotNull Tag<T> tag, @Nullable T value) {
return withMeta(builder -> builder.set(tag, value));
return with(ItemComponent.CUSTOM_DATA, get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).withTag(tag, value));
}
@Override

View File

@ -2,7 +2,10 @@ package net.minestom.server.item;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.item.component.CustomData;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -11,23 +14,26 @@ import java.util.function.Consumer;
record ItemStackImpl(Material material, int amount, ItemComponentPatch components) implements ItemStack {
static final NetworkBuffer.Type<ItemStack> NETWORK_TYPE = null;
static final BinaryTagSerializer<ItemStack> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(ItemStackImpl::fromCompound, ItemStackImpl::toCompound);
static ItemStack create(Material material, int amount, ItemComponentPatch components) {
if (amount <= 0) return AIR;
return new ItemStackImpl(material, amount, components);
}
static ItemStack create(Material material, int amount) {
return create(material, amount, ItemComponentPatch.builder(material).build());
return create(material, amount, ItemComponentPatch.EMPTY);
}
@Override
public <T> @Nullable T get(@NotNull ItemComponent<T> component) {
return components.get(component);
return components.get(material.prototype(), component);
}
@Override
public boolean has(@NotNull ItemComponent<?> component) {
return components.has(component);
return components.has(material.prototype(), component);
}
@Override
@ -71,6 +77,8 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component
@Override
public @NotNull CompoundBinaryTag toItemNBT() {
return (CompoundBinaryTag) NBT_TYPE.write(this);
// CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
// .putString("id", material.name())
// .putByte("Count", (byte) amount);
@ -85,6 +93,23 @@ record ItemStackImpl(Material material, int amount, ItemComponentPatch component
return new Builder(material, amount, components.builder());
}
private static @NotNull ItemStack fromCompound(@NotNull CompoundBinaryTag tag) {
String id = tag.getString("id");
Material material = Material.fromNamespaceId(id);
Check.notNull(material, "Unknown material: {0}", id);
int count = tag.getInt("count", 1);
ItemComponentPatch patch = ItemComponentPatch.NBT_TYPE.read(tag.getCompound("components"));
return new ItemStackImpl(material, count, patch);
}
private static @NotNull CompoundBinaryTag toCompound(@NotNull ItemStack itemStack) {
return CompoundBinaryTag.builder()
.putString("id", itemStack.material().name())
.putInt("count", itemStack.amount())
.put("components", ItemComponentPatch.NBT_TYPE.write(((ItemStackImpl) itemStack).components))
.build();
}
static final class Builder implements ItemStack.Builder {
final Material material;
int amount;

View File

@ -0,0 +1,60 @@
package net.minestom.server.item.book;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record FilteredText<T>(@NotNull T text, @Nullable T filtered) {
public static @NotNull NetworkBuffer.Type<FilteredText<String>> STRING_NETWORK_TYPE = createNetworkType(NetworkBuffer.STRING);
public static @NotNull BinaryTagSerializer<FilteredText<String>> STRING_NBT_TYPE = createNbtType(BinaryTagSerializer.STRING);
public static @NotNull NetworkBuffer.Type<FilteredText<Component>> COMPONENT_NETWORK_TYPE = createNetworkType(NetworkBuffer.COMPONENT);
public static @NotNull BinaryTagSerializer<FilteredText<Component>> COMPONENT_NBT_TYPE = createNbtType(BinaryTagSerializer.JSON_COMPONENT);
private static <T> NetworkBuffer.@NotNull Type<FilteredText<T>> createNetworkType(@NotNull NetworkBuffer.Type<T> inner) {
return new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FilteredText<T> value) {
buffer.write(inner, value.text);
buffer.writeOptional(inner, value.filtered);
}
@Override
public FilteredText<T> read(@NotNull NetworkBuffer buffer) {
return new FilteredText<>(buffer.read(inner), buffer.readOptional(inner));
}
};
}
private static <T> @NotNull BinaryTagSerializer<FilteredText<T>> createNbtType(@NotNull BinaryTagSerializer<T> inner) {
return new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull FilteredText<T> value) {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
builder.put("text", inner.write(value.text));
if (value.filtered != null) {
builder.put("filtered", inner.write(value.filtered));
}
return builder.build();
}
@Override
public @NotNull FilteredText<T> read(@NotNull BinaryTag tag) {
if (tag instanceof CompoundBinaryTag compound) {
BinaryTag textTag = compound.get("text");
if (textTag != null) {
BinaryTag filteredTag = compound.get("filtered");
T filtered = filteredTag == null ? null : inner.read(filteredTag);
return new FilteredText<>(inner.read(textTag), filtered);
}
}
return new FilteredText<>(inner.read(tag), null);
}
};
}
}

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.ListBinaryTag;
import net.minestom.server.item.book.FilteredText;
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 WritableBookContent(@NotNull List<FilteredText<String>> pages) {
public static final WritableBookContent EMPTY = new WritableBookContent(List.of());
public static final NetworkBuffer.Type<WritableBookContent> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, WritableBookContent value) {
buffer.writeCollection(FilteredText.STRING_NETWORK_TYPE, value.pages);
}
@Override
public WritableBookContent read(@NotNull NetworkBuffer buffer) {
return new WritableBookContent(buffer.readCollection(FilteredText.STRING_NETWORK_TYPE, 100));
}
};
public static final BinaryTagSerializer<WritableBookContent> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull WritableBookContent value) {
ListBinaryTag.Builder<BinaryTag> pages = ListBinaryTag.builder();
for (FilteredText<String> page : value.pages) {
pages.add(FilteredText.STRING_NBT_TYPE.write(page));
}
return CompoundBinaryTag.builder().put("pages", pages.build()).build();
}
@Override
public @NotNull WritableBookContent read(@NotNull BinaryTag tag) {
if (!(tag instanceof CompoundBinaryTag compound)) return EMPTY;
ListBinaryTag pagesTag = compound.getList("pages");
if (pagesTag.size() == 0) return EMPTY;
List<FilteredText<String>> pages = new ArrayList<>(pagesTag.size());
for (BinaryTag pageTag : pagesTag) {
pages.add(FilteredText.STRING_NBT_TYPE.read(pageTag));
}
return new WritableBookContent(pages);
}
};
public WritableBookContent {
pages = List.copyOf(pages);
}
}

View File

@ -0,0 +1,72 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.book.FilteredText;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public record WrittenBookContent(@NotNull List<FilteredText<Component>> pages, @NotNull FilteredText<String> title, @NotNull String author, int generation, boolean resolved) {
public static final WrittenBookContent EMPTY = new WrittenBookContent(List.of(), new FilteredText<>("", null), "", 0, true);
public static final @NotNull NetworkBuffer.Type<WrittenBookContent> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, WrittenBookContent value) {
buffer.write(FilteredText.STRING_NETWORK_TYPE, value.title);
buffer.write(NetworkBuffer.STRING, value.author);
buffer.write(NetworkBuffer.VAR_INT, value.generation);
buffer.writeCollection(FilteredText.COMPONENT_NETWORK_TYPE, value.pages);
buffer.write(NetworkBuffer.BOOLEAN, value.resolved);
}
@Override
public WrittenBookContent read(@NotNull NetworkBuffer buffer) {
FilteredText<String> title = buffer.read(FilteredText.STRING_NETWORK_TYPE);
String author = buffer.read(NetworkBuffer.STRING);
int generation = buffer.read(NetworkBuffer.VAR_INT);
List<FilteredText<Component>> pages = buffer.readCollection(FilteredText.COMPONENT_NETWORK_TYPE, 100);
boolean resolved = buffer.read(NetworkBuffer.BOOLEAN);
return new WrittenBookContent(pages, title, author, generation, resolved);
}
};
public static final @NotNull BinaryTagSerializer<WrittenBookContent> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
compound -> {
ListBinaryTag pagesTag = compound.getList("pages");
List<FilteredText<Component>> pages = pagesTag.stream()
.map(FilteredText.COMPONENT_NBT_TYPE::read)
.toList();
FilteredText<String> title = FilteredText.STRING_NBT_TYPE.read(compound.get("title"));
String author = compound.getString("author");
int generation = compound.getInt("generation");
boolean resolved = compound.getBoolean("resolved");
return new WrittenBookContent(pages, title, author, generation, resolved);
},
value -> {
ListBinaryTag.Builder<BinaryTag> pagesTag = ListBinaryTag.builder();
for (FilteredText<Component> page : value.pages) {
pagesTag.add(FilteredText.COMPONENT_NBT_TYPE.write(page));
}
return CompoundBinaryTag.builder()
.put("pages", pagesTag.build())
.put("title", FilteredText.STRING_NBT_TYPE.write(value.title))
.putString("author", value.author)
.putInt("generation", value.generation)
.putBoolean("resolved", value.resolved)
.build();
}
);
public WrittenBookContent {
pages = List.copyOf(pages);
}
public WrittenBookContent(@NotNull List<Component> pages, @NotNull String title, @NotNull String author, int generation, boolean resolved) {
this(pages.stream().map(page -> new FilteredText<>(page, null)).toList(), new FilteredText<>(title, null), author, generation, resolved);
}
}

View File

@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.*;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
public final class BinaryTagUtil {
@ -54,6 +55,10 @@ public final class BinaryTagUtil {
}
}
public static @Nullable String getStringOrNull(@NotNull CompoundBinaryTag tag, @NotNull String key) {
return tag.keySet().contains(key) ? tag.getString(key) : null;
}
private BinaryTagUtil() {
}
}