Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NBT Component Serializer #1084

Open
wants to merge 24 commits into
base: main/4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
29c997a
feat: basic component serializing to nbt (without styling)
Codestech1 Jun 8, 2024
34a381a
feat: serialize component children and types
Codestech1 Jun 8, 2024
5bb4c5a
misc: i'm too lazy to name this commit, but it fixes and adds a lot o…
Codestech1 Jun 9, 2024
97809cf
chore: remove useless gson serializer
Codestech1 Jun 9, 2024
1c750c3
chore: add support for legacy item tags
Codestech1 Jun 9, 2024
92ea881
feat: component deserialization
Codestech1 Jun 9, 2024
9858a9f
fix: translatable component arguments aren't deserializing
Codestech1 Jun 9, 2024
4c7b00c
fix: legacy hover events aren't serialized properly
Codestech1 Jun 12, 2024
0a52c01
fix: serializing show entity as a legacy hover event throws an except…
Codestech1 Jun 12, 2024
a020c0a
misc: split the style serialization and deserialization to other class
Codestech1 Jun 13, 2024
a39a101
feat: add an emit rgb flag
Codestech1 Jun 13, 2024
8e74088
feat: add a builder for the nbt component serializer
Codestech1 Jun 13, 2024
f96ca38
chore: add a non-null requirement to NBTComponentSerializerImpl.Build…
Codestech1 Jun 13, 2024
bba80fb
feat: nbt component providers
Codestech1 Jun 13, 2024
0ec4d53
chore: add remaining serializer options to builder
Codestech1 Jun 15, 2024
e8b5efd
chore: cleanup
Codestech1 Jun 15, 2024
f8427a0
chore: add javadocs to and simplify NBTSerializerOptions and add lice…
Codestech1 Jun 15, 2024
8c43bac
chore: add javadocs for public classes
Codestech1 Jun 15, 2024
2f4c78c
chore: add tests
Codestech1 Jun 15, 2024
2021707
fix: hover events are not serialized properly, feat: add a test for h…
Codestech1 Jun 15, 2024
9021233
feat: add a test for click event
Codestech1 Jun 15, 2024
c7f406a
chore: resolve requested changes
Codestech1 Jun 19, 2024
cc298cb
feat: add a possibility to (de)-serialize a style without a component
Codestech1 Jun 19, 2024
8996ab0
chore: simplify serializing text colors with TextColor#toString
Codestech1 Jun 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ sequenceOf(
"text-serializer-legacy",
"text-serializer-plain",
"text-serializer-ansi",
"text-serializer-nbt"
).forEach {
include("adventure-$it")
project(":adventure-$it").projectDir = file(it)
Expand Down
11 changes: 11 additions & 0 deletions text-serializer-nbt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("adventure.common-conventions")
}

dependencies {
api(libs.option)
api(projects.adventureApi)
api(projects.adventureNbt)
}

applyJarMetadata("net.kyori.adventure.text.serializer.nbt")
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.text.serializer.nbt;

import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.NotNull;

final class ClickEventSerializer {

private static final String ACTION = "action";
private static final String VALUE = "value";

private ClickEventSerializer() {
}

static @NotNull ClickEvent deserialize(@NotNull CompoundBinaryTag tag) {
BinaryTag actionTag = tag.get(ACTION);

if (actionTag == null) {
throw new IllegalArgumentException("The serialized click event doesn't contain an action");
}

String actionString = ((StringBinaryTag) actionTag).value();
ClickEvent.Action action = ClickEvent.Action.NAMES.valueOrThrow(actionString);

return ClickEvent.clickEvent(action, tag.getString(VALUE));
}

static @NotNull CompoundBinaryTag serialize(@NotNull ClickEvent event) {
return CompoundBinaryTag.builder()
.putString(ACTION, ClickEvent.Action.NAMES.keyOrThrow(event.action()))
.putString(VALUE, event.value())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.text.serializer.nbt;

import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.DataComponentValue;
import net.kyori.adventure.text.event.HoverEvent;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

final class HoverEventSerializer {

private static final String HOVER_EVENT_ACTION = "action";
private static final String HOVER_EVENT_CONTENTS = "contents";

private static final String HOVER_EVENT_SHOW_TEXT = "show_text";
private static final String HOVER_EVENT_SHOW_ITEM = "show_item";
private static final String HOVER_EVENT_SHOW_ENTITY = "show_entity";

private static final String SHOW_ITEM_ID = "id";
private static final String SHOW_ITEM_COUNT = "count";
private static final String SHOW_ITEM_COMPONENTS = "components";

private static final String SHOW_ENTITY_TYPE = "type";
private static final String SHOW_ENTITY_ID = "id";
private static final String SHOW_ENTITY_NAME = "name";

private HoverEventSerializer() {
}

static @NotNull HoverEvent<?> deserialize(@NotNull CompoundBinaryTag compound, @NotNull NBTComponentSerializerImpl serializer) {
String actionString = compound.getString(HOVER_EVENT_ACTION);
HoverEvent.Action<?> action = HoverEvent.Action.NAMES.valueOrThrow(actionString);
Class<?> actionType = action.type();

BinaryTag contents = compound.get(HOVER_EVENT_CONTENTS);
if (contents == null) {
throw new IllegalArgumentException("The hover event doesn't contain any contents");
}

if (Component.class.isAssignableFrom(actionType)) {
return HoverEvent.showText(serializer.deserialize(contents));
} else if (HoverEvent.ShowItem.class.isAssignableFrom(actionType)) {
CompoundBinaryTag showItemContents = (CompoundBinaryTag) contents;

Key itemId = Key.key(showItemContents.getString(SHOW_ITEM_ID));
int itemCount = showItemContents.getInt(SHOW_ITEM_COUNT);

BinaryTag components = showItemContents.get(SHOW_ITEM_COMPONENTS);

if (components != null) {
CompoundBinaryTag componentsCompound = (CompoundBinaryTag) components;
Map<Key, DataComponentValue> componentValues = new HashMap<>();

for (String string : componentsCompound.keySet()) {
BinaryTag value = componentsCompound.get(string);
if (value == null) continue;
componentValues.put(Key.key(string), NBTDataComponentValue.nbtDataComponentValue(value));
}

return HoverEvent.showItem(itemId, itemCount, componentValues);
} else {
return HoverEvent.showItem(itemId, itemCount);
}
} else if (HoverEvent.ShowEntity.class.isAssignableFrom(actionType)) {
CompoundBinaryTag showEntityContents = (CompoundBinaryTag) contents;

Key entityType = Key.key(showEntityContents.getString(SHOW_ENTITY_TYPE));
UUID entityId = UUID.fromString(showEntityContents.getString(SHOW_ENTITY_ID));

BinaryTag entityName = showEntityContents.get(SHOW_ENTITY_NAME);

if (entityName != null) {
return HoverEvent.showEntity(entityType, entityId, serializer.deserialize(entityName));
} else {
return HoverEvent.showEntity(entityType, entityId);
}
} else {
throw new IllegalArgumentException("Don't know how to deserialize a hoverEvent with action of " + actionString + " from a binary tag");
}
}

static <V> @NotNull CompoundBinaryTag serialize(@NotNull HoverEvent<V> event, @NotNull NBTComponentSerializerImpl serializer) {
HoverEvent.Action<V> action = event.action();

BinaryTag contents;
String actionString;

if (action == HoverEvent.Action.SHOW_TEXT) {
contents = serializer.serialize((Component) event.value());
actionString = HOVER_EVENT_SHOW_TEXT;
} else if (action == HoverEvent.Action.SHOW_ITEM) {
HoverEvent.ShowItem item = (HoverEvent.ShowItem) event.value();

CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
.putString(SHOW_ITEM_ID, item.item().asString())
.putInt(SHOW_ITEM_COUNT, item.count());

Map<Key, NBTDataComponentValue> components = item.dataComponentsAs(NBTDataComponentValue.class);

if (!components.isEmpty()) {
CompoundBinaryTag.Builder dataComponentsBuilder = CompoundBinaryTag.builder();

for (Map.Entry<Key, NBTDataComponentValue> entry : components.entrySet()) {
dataComponentsBuilder.put(entry.getKey().asString(), entry.getValue().binaryTag());
}

builder.put(SHOW_ITEM_COMPONENTS, dataComponentsBuilder.build());
}

contents = builder.build();
actionString = HOVER_EVENT_SHOW_ITEM;
} else if (action == HoverEvent.Action.SHOW_ENTITY) {
HoverEvent.ShowEntity item = (HoverEvent.ShowEntity) event.value();

CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
.putString(SHOW_ENTITY_TYPE, item.type().asString())
.putString(SHOW_ENTITY_ID, item.id().toString());

Component customName = item.name();
if (customName != null) {
builder.put(SHOW_ENTITY_NAME, serializer.serialize(customName));
}

contents = builder.build();
actionString = HOVER_EVENT_SHOW_ENTITY;
} else {
throw new IllegalArgumentException("Don't know how to serialize " + event + " as a binary tag");
}

return CompoundBinaryTag.builder()
.putString(HOVER_EVENT_ACTION, actionString)
.put(HOVER_EVENT_CONTENTS, contents)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.text.serializer.nbt;

import net.kyori.adventure.builder.AbstractBuilder;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import net.kyori.adventure.util.PlatformAPI;
import net.kyori.option.OptionState;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import java.util.function.Consumer;

/**
* A NBT component serializer.
*
* @since 4.18.0
*/
public interface NBTComponentSerializer extends ComponentSerializer<Component, Component, BinaryTag> {
/**
* Gets a component serializer for NBT serialization and deserialization.
*
* @return a NBT component serializer
* @since 4.18.0
*/
static @NotNull NBTComponentSerializer nbt() {
return NBTComponentSerializerImpl.Instances.INSTANCE;
}

/**
* Creates a new {@link NBTComponentSerializer.Builder}.
*
* @return a builder
* @since 4.18.0
*/
static @NotNull Builder builder() {
return new NBTComponentSerializerImpl.BuilderImpl();
}

/**
* A builder for {@link NBTComponentSerializer}.
*
* @since 4.18.0
*/
interface Builder extends AbstractBuilder<NBTComponentSerializer> {
/**
* Set the option state to apply on this serializer.
*
* <p>This controls how the serializer emits and interprets components.</p>
*
* @param flags the flag set to use
* @return this builder
* @see NBTSerializerOptions
* @since 4.18.0
*/
@NotNull Builder options(final @NotNull OptionState flags);

/**
* Edit the active set of serializer options.
*
* @param optionEditor the consumer operating on the existing flag set
* @return this builder
* @see NBTSerializerOptions
* @since 4.18.0
*/
@NotNull Builder editOptions(final @NotNull Consumer<OptionState.Builder> optionEditor);

/**
* Builds the serializer.
*
* @return the built serializer
* @since 4.18.0
*/
@Override
@NotNull NBTComponentSerializer build();
}

/**
* A {@link NBTComponentSerializer} service provider.
*
* @since 4.18.0
*/
@ApiStatus.Internal
@PlatformAPI
interface Provider {
/**
* Provides a standard {@link NBTComponentSerializer}.
*
* @return a {@link NBTComponentSerializer}
* @since 4.18.0
*/
@ApiStatus.Internal
@PlatformAPI
@NotNull NBTComponentSerializer nbt();

/**
* Completes the building process of {@link Builder}.
*
* @return a {@link Consumer}
* @since 4.18.0
*/
@ApiStatus.Internal
@PlatformAPI
@NotNull Consumer<Builder> builder();
}
}
Loading