diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts index 11d7367c19..0016eaee7e 100644 --- a/bom/build.gradle.kts +++ b/bom/build.gradle.kts @@ -14,9 +14,11 @@ dependencies { sequenceOf( "api", "annotation-processors", + "dfu", "extra-kotlin", "key", "nbt", + "nbt-dfu", "serializer-configurate4", "text-logger-slf4j", "text-minimessage", diff --git a/dfu/build.gradle.kts b/dfu/build.gradle.kts new file mode 100644 index 0000000000..1a1cde94e6 --- /dev/null +++ b/dfu/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("adventure.common-conventions") +} + +dependencies { + api(libs.dfu8) +} + +indra { + javaVersions().target(17) // minimum supported by DFU +} + +applyJarMetadata("net.kyori.adventure.dfu") diff --git a/dfu/src/main/java/net/kyori/adventure/dfu/AdventureCodecs.java b/dfu/src/main/java/net/kyori/adventure/dfu/AdventureCodecs.java new file mode 100644 index 0000000000..43a9bfb507 --- /dev/null +++ b/dfu/src/main/java/net/kyori/adventure/dfu/AdventureCodecs.java @@ -0,0 +1,13 @@ +package net.kyori.adventure.dfu; + +public final class AdventureCodecs { + private AdventureCodecs() { + } + + // key + // uuid + //TextColor + // TextDecoration + // TranslationArgument + // indexed +} diff --git a/dfu/src/main/java/net/kyori/adventure/dfu/package-info.java b/dfu/src/main/java/net/kyori/adventure/dfu/package-info.java new file mode 100644 index 0000000000..2c3932c120 --- /dev/null +++ b/dfu/src/main/java/net/kyori/adventure/dfu/package-info.java @@ -0,0 +1,6 @@ +/** + * DFU Codecs for adventure types and a ComponentSerializer backed by those codecs. + * + * @since 4.21.0 + */ +package net.kyori.adventure.dfu; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68f0f6676a..4020e5bd26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,10 @@ guava = { module = "com.google.guava:guava", version.ref = "guava" } guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guava" } jetbrainsAnnotations = "org.jetbrains:annotations:26.0.2" +# dfu + +dfu8 = { module = "com.mojang:datafixerupper", version = "8.0.16" } + # extra-kotlin kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } kotlin-testJunit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5" } diff --git a/nbt-dfu/build.gradle.kts b/nbt-dfu/build.gradle.kts new file mode 100644 index 0000000000..322b0483b9 --- /dev/null +++ b/nbt-dfu/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("adventure.common-conventions") +} + +dependencies { + api(libs.dfu8) + api(projects.adventureNbt) + + testImplementation(projects.adventureDfu) + testImplementation(projects.adventureKey) +} + +indra { + javaVersions().target(17) // minimum supported by DFU +} + +applyJarMetadata("net.kyori.adventure.nbt.dfu") diff --git a/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/BinaryTagOps.java b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/BinaryTagOps.java new file mode 100644 index 0000000000..83c3781e57 --- /dev/null +++ b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/BinaryTagOps.java @@ -0,0 +1,392 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 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.nbt.dfu; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.ListBuilder; +import com.mojang.serialization.MapLike; +import com.mojang.serialization.RecordBuilder; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.ByteArrayBinaryTag; +import net.kyori.adventure.nbt.ByteBinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.DoubleBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import net.kyori.adventure.nbt.FloatBinaryTag; +import net.kyori.adventure.nbt.IntArrayBinaryTag; +import net.kyori.adventure.nbt.IntBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.LongArrayBinaryTag; +import net.kyori.adventure.nbt.LongBinaryTag; +import net.kyori.adventure.nbt.ShortBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static java.util.Objects.requireNonNull; + +public class BinaryTagOps implements DynamicOps { + public static final BinaryTagOps UNCOMPRESSED = new BinaryTagOps(false); + public static final BinaryTagOps COMPRESSED = new BinaryTagOps(true); + + static String unwrap(final @NotNull BinaryTag tag) { + if (tag instanceof StringBinaryTag) { + return ((StringBinaryTag) tag).value(); + } + throw new IllegalArgumentException("Unsupported tag type " + tag.type()); + } + + private final boolean compressed; + + public BinaryTagOps(final boolean compressed) { + this.compressed = compressed; + } + + @Override + public BinaryTag empty() { + return EndBinaryTag.endBinaryTag(); + } + + @Override + public U convertTo(final DynamicOps outOps, final BinaryTag input) { + if (input instanceof CompoundBinaryTag) { + return this.convertMap(outOps, input); + } else if (input instanceof ListBinaryTag) { + return this.convertList(outOps, input); + } else if (input instanceof ByteArrayBinaryTag) { + return outOps.createByteList(ByteBuffer.wrap(((ByteArrayBinaryTag) input).value())); + } else if (input instanceof IntArrayBinaryTag) { + return outOps.createIntList(IntStream.of(((IntArrayBinaryTag) input).value())); + } else if (input instanceof LongArrayBinaryTag) { + return outOps.createLongList(LongStream.of(((LongArrayBinaryTag) input).value())); + } else if (input instanceof StringBinaryTag) { + return outOps.createString(((StringBinaryTag) input).value()); + } else if (input instanceof ByteBinaryTag) { + return outOps.createByte(((ByteBinaryTag) input).value()); + } else if (input instanceof ShortBinaryTag) { + return outOps.createShort(((ShortBinaryTag) input).value()); + } else if (input instanceof IntBinaryTag) { + return outOps.createInt(((IntBinaryTag) input).value()); + } else if (input instanceof LongBinaryTag) { + return outOps.createLong(((LongBinaryTag) input).value()); + } else if (input instanceof FloatBinaryTag) { + return outOps.createFloat(((FloatBinaryTag) input).value()); + } else if (input instanceof DoubleBinaryTag) { + return outOps.createDouble(((DoubleBinaryTag) input).value()); + } else if (input instanceof EndBinaryTag) { + return outOps.empty(); + } + throw new IllegalArgumentException("Unknown tag type " + input.getClass()); + } + + @Override + public DataResult getNumberValue(final BinaryTag input) { + if (input instanceof ByteBinaryTag) { + return DataResult.success(((ByteBinaryTag) input).value()); + } else if (input instanceof ShortBinaryTag) { + return DataResult.success(((ShortBinaryTag) input).value()); + } else if (input instanceof IntBinaryTag) { + return DataResult.success(((IntBinaryTag) input).value()); + } else if (input instanceof LongBinaryTag) { + return DataResult.success(((LongBinaryTag) input).value()); + } else if (input instanceof FloatBinaryTag) { + return DataResult.success(((FloatBinaryTag) input).value()); + } else if (input instanceof DoubleBinaryTag) { + return DataResult.success(((DoubleBinaryTag) input).value()); + } else { + return DataResult.error(() -> "not a number"); + } + } + + @Override + public BinaryTag createNumeric(final Number i) { + if (i instanceof Byte) { + return ByteBinaryTag.byteBinaryTag((Byte) i); + } else if (i instanceof Short) { + return ShortBinaryTag.shortBinaryTag((Short) i); + } else if (i instanceof Integer) { + return IntBinaryTag.intBinaryTag((Integer) i); + } else if (i instanceof Long) { + return LongBinaryTag.longBinaryTag((Long) i); + } else if (i instanceof Float) { + return FloatBinaryTag.floatBinaryTag((Float) i); + } else { // oh well? + return DoubleBinaryTag.doubleBinaryTag(i.doubleValue()); + } + } + + @Override + public ByteBinaryTag createByte(final byte value) { + return ByteBinaryTag.byteBinaryTag(value); + } + + @Override + public ShortBinaryTag createShort(final short value) { + return ShortBinaryTag.shortBinaryTag(value); + } + + @Override + public IntBinaryTag createInt(final int value) { + return IntBinaryTag.intBinaryTag(value); + } + + @Override + public LongBinaryTag createLong(final long value) { + return LongBinaryTag.longBinaryTag(value); + } + + @Override + public FloatBinaryTag createFloat(final float value) { + return FloatBinaryTag.floatBinaryTag(value); + } + + @Override + public DoubleBinaryTag createDouble(final double value) { + return DoubleBinaryTag.doubleBinaryTag(value); + } + + @Override + public DataResult getStringValue(final BinaryTag input) { + if (input instanceof StringBinaryTag) { + return DataResult.success(((StringBinaryTag) input).value()); + } + return DataResult.error(() -> "Value was not a string"); + } + + @Override + public BinaryTag createString(final String value) { + return StringBinaryTag.stringBinaryTag(requireNonNull(value, "value")); + } + + @Override + public DataResult mergeToList(final BinaryTag list, final BinaryTag value) { + if (list instanceof ListBinaryTag) { + return DataResult.success(((ListBinaryTag) list).add(value)); + } else if (list.equals(this.empty())) { + return DataResult.success(ListBinaryTag.listBinaryTag(value.type(), Collections.singletonList(value))); + } + return DataResult.error(() -> "Input value is not a list"); + } + + @Override + public DataResult mergeToMap(final BinaryTag map, final BinaryTag key, final BinaryTag value) { + if (requireNonNull(map, "map").equals(this.empty())) { + return DataResult.success(CompoundBinaryTag.builder() + .put(unwrap(key), value) + .build()); + } else if (!(map instanceof CompoundBinaryTag)) { + return DataResult.error(() -> "not a map"); + } + return DataResult.success(((CompoundBinaryTag) map).put(unwrap(key), value)); + } + + @Override + public DataResult>> getMapValues(final BinaryTag input) { + if (input instanceof CompoundBinaryTag) { + return DataResult.success(StreamSupport.stream(((CompoundBinaryTag) input).spliterator(), false) + .map(entry -> Pair.of(StringBinaryTag.stringBinaryTag(entry.getKey()), entry.getValue()))); + } + return DataResult.error(() -> "Tag " + input + " was not a Compound"); + } + + @Override + public BinaryTag createMap(final Stream> map) { + final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + map.forEach(p -> builder.put(unwrap(p.getFirst()), p.getSecond())); + return builder.build(); + } + + @Override + public DataResult> getStream(final BinaryTag input) { + if (input instanceof ListBinaryTag) { + return DataResult.success(StreamSupport.stream(((ListBinaryTag) input).spliterator(), false)); + } + return DataResult.error(() -> "not a list"); + } + + @Override + public BinaryTag createList(final Stream input) { + final ListBinaryTag.Builder builder = ListBinaryTag.builder(); + input.forEach(builder::add); + return builder.build(); + } + + @Override + public DataResult> getMap(final BinaryTag input) { + if (input instanceof CompoundBinaryTag) { + return DataResult.success(new CompoundMaplike((CompoundBinaryTag) input)); + } + return DataResult.error(() -> "not a map"); + } + + @Override + public DataResult>> getList(final BinaryTag input) { + if (input instanceof ListBinaryTag) { + return DataResult.success(((ListBinaryTag) input)::forEach); + } + return DataResult.error(() -> "not a list"); + } + + @Override + public DataResult getByteBuffer(final BinaryTag input) { + if (input instanceof ByteArrayBinaryTag) { + return DataResult.success(ByteBuffer.wrap(((ByteArrayBinaryTag) input).value())); + } else { + return DynamicOps.super.getByteBuffer(input); + } + } + + @Override + public BinaryTag createByteList(final ByteBuffer input) { + return ByteArrayBinaryTag.byteArrayBinaryTag(input.array()); + } + + @Override + public DataResult getIntStream(final BinaryTag input) { + if (input instanceof IntArrayBinaryTag) { + return DataResult.success(IntStream.of(((IntArrayBinaryTag) input).value())); + } + return DataResult.error(() -> "not an int stream"); + } + + @Override + public BinaryTag createIntList(final IntStream input) { + return IntArrayBinaryTag.intArrayBinaryTag(input.toArray()); + } + + @Override + public DataResult getLongStream(final BinaryTag input) { + if (input instanceof LongArrayBinaryTag) { + return DataResult.success(LongStream.of(((LongArrayBinaryTag) input).value())); + } else { + return DynamicOps.super.getLongStream(input); // may be a list of longs + } + } + + @Override + public BinaryTag createLongList(final LongStream input) { + return LongArrayBinaryTag.longArrayBinaryTag(input.toArray()); + } + + @Override + public DataResult get(final BinaryTag input, final String key) { + if (input instanceof CompoundBinaryTag) { + final @Nullable BinaryTag value = ((CompoundBinaryTag) input).get(key); + if (value != null) { + return DataResult.success(value); + } else { + return DataResult.error(() -> "Unknown key " + key + " in " + input); + } + } + return DataResult.error(() -> "not a map"); + } + + @Override + public DataResult getGeneric(final BinaryTag input, final BinaryTag key) { + return this.get(input, unwrap(key)); + } + + @Override + public BinaryTag set(final BinaryTag input, final String key, final BinaryTag value) { + if (this.empty().equals(input)) { + return CompoundBinaryTag.builder().put(key, value).build(); + } else if (input instanceof CompoundBinaryTag) { + return ((CompoundBinaryTag) input).put(key, value); + } + return input; + } + + @Override + public BinaryTag update(final BinaryTag input, final String key, final Function function) { + if (this.empty().equals(input)) { + return input; + } else if (input instanceof CompoundBinaryTag compound) { + final @Nullable BinaryTag old = compound.get(requireNonNull(key, "key")); + if (old != null) { + return compound.put(key, function.apply(old)); + } + } + return input; + } + + @Override + public BinaryTag updateGeneric(final BinaryTag input, final BinaryTag key, final Function function) { + return this.update(input, unwrap(key), function); + } + + @Override + public ListBuilder listBuilder() { + return new ListBinaryTagBuilder(this); + } + + @Override + public RecordBuilder mapBuilder() { + return new CompoundRecordBuilder(this); + } + + @Override + public U convertList(final DynamicOps outOps, final BinaryTag input) { + if (!(input instanceof ListBinaryTag)) { + throw new IllegalStateException("Input was not a list!"); + } + final ListBuilder builder = outOps.listBuilder(); + for (final BinaryTag item : (ListBinaryTag) input) { + builder.add(this.convertTo(outOps, item)); + } + + return builder.build(outOps.empty()).result().orElseThrow(() -> new IllegalArgumentException("Unable to convert tag")); + } + + @Override + public U convertMap(final DynamicOps outOps, final BinaryTag input) { + if (!(input instanceof CompoundBinaryTag)) { + throw new IllegalStateException("Input was not a compound!"); + } + final RecordBuilder builder = outOps.mapBuilder(); + for (final Map.Entry item : (CompoundBinaryTag) input) { + builder.add(item.getKey(), this.convertTo(outOps, item.getValue())); + } + + return builder.build(outOps.empty()).result().orElseThrow(() -> new IllegalArgumentException("Unable to convert tag")); + } + + @Override + public BinaryTag remove(final BinaryTag input, final String key) { + if (input instanceof CompoundBinaryTag) { + return ((CompoundBinaryTag) input).put(key, null); // TODO: actual thing + } + return input; + } +} diff --git a/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundMaplike.java b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundMaplike.java new file mode 100644 index 0000000000..02cfd3c77b --- /dev/null +++ b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundMaplike.java @@ -0,0 +1,56 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 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.nbt.dfu; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.MapLike; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; + +class CompoundMaplike implements MapLike { + private final CompoundBinaryTag tag; + + CompoundMaplike(final CompoundBinaryTag tag) { + this.tag = tag; + } + + @Override + public BinaryTag get(final BinaryTag key) { + return this.tag.get(BinaryTagOps.unwrap(key)); + } + + @Override + public BinaryTag get(final String key) { + return this.tag.get(key); + } + + @Override + public Stream> entries() { + return StreamSupport.stream(this.tag.spliterator(), false) + .map(ent -> Pair.of(StringBinaryTag.stringBinaryTag(ent.getKey()), ent.getValue())); + } +} diff --git a/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundRecordBuilder.java b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundRecordBuilder.java new file mode 100644 index 0000000000..7a11d55b19 --- /dev/null +++ b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/CompoundRecordBuilder.java @@ -0,0 +1,104 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 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.nbt.dfu; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.RecordBuilder; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.jetbrains.annotations.Nullable; + +public class CompoundRecordBuilder implements RecordBuilder { + private final BinaryTagOps ops; + private final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + private Lifecycle life = Lifecycle.experimental(); + private DataResult.@Nullable Error error = null; + + public CompoundRecordBuilder(final BinaryTagOps ops) { + this.ops = ops; + } + + @Override + public DynamicOps ops() { + return this.ops; + } + + @Override + public RecordBuilder add(final BinaryTag key, final BinaryTag value) { + this.builder.put(BinaryTagOps.unwrap(key), value); + return this; + } + + @Override + public RecordBuilder add(final BinaryTag key, final DataResult value) { + if (value.error().isPresent()) { // TODO: probably doing this wrong -- i won't preserve partials (but then partials don't really make sense?) + this.error = value.error().get(); + } else { + this.add(key, value.result().orElseThrow(() -> new IllegalStateException("Neither error or result was present"))); + } + return this; + } + + @Override + public RecordBuilder add(final DataResult key, final DataResult value) { + return null; + } + + @Override + public RecordBuilder withErrorsFrom(final DataResult result) { + if (result.error().isPresent()) { + this.error = result.error().get().map(x -> this.ops.empty()); + } + return this; + } + + @Override + public RecordBuilder setLifecycle(final Lifecycle lifecycle) { + this.life = lifecycle; + return this; + } + + @Override + public RecordBuilder mapError(final UnaryOperator onError) { + if (this.error != null) { + final Supplier oldMessage = this.error.messageSupplier(); + this.error = new DataResult.Error<>(() -> onError.apply(oldMessage.get()), Optional.empty(), this.life); + } + return this; + } + + @Override + public DataResult build(final BinaryTag prefix) { + if (this.error != null) { + return DataResult.error(this.error.messageSupplier(), this.life); + } else { + return DataResult.success(this.builder.build(), this.life); + } + } +} diff --git a/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/ListBinaryTagBuilder.java b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/ListBinaryTagBuilder.java new file mode 100644 index 0000000000..7094f1b7e1 --- /dev/null +++ b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/ListBinaryTagBuilder.java @@ -0,0 +1,99 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 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.nbt.dfu; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.ListBuilder; +import java.util.function.UnaryOperator; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import org.jetbrains.annotations.Nullable; + +public class ListBinaryTagBuilder implements ListBuilder { + private final BinaryTagOps ops; + private final ListBinaryTag.Builder builder = ListBinaryTag.builder(); + + private @Nullable DataResult error = null; + + public ListBinaryTagBuilder(final BinaryTagOps ops) { + this.ops = ops; + } + + @Override + public DynamicOps ops() { + return this.ops; + } + + @Override + public DataResult build(final BinaryTag prefix) { + if (this.error != null) { + return this.error.map(x -> (BinaryTag) null); + } + + if (prefix.equals(this.ops.empty())) { + return DataResult.success(this.builder.build()); + } else if (prefix instanceof ListBinaryTag) { + for (final BinaryTag tag : (ListBinaryTag) prefix) { + this.builder.add(tag); + } + } + return DataResult.success(this.builder.build(), Lifecycle.stable()); + } + + @Override + public ListBuilder add(final BinaryTag value) { + this.builder.add(value); + return this; + } + + @Override + public ListBuilder add(final DataResult value) { + if (value.error().isPresent()) { + return this.withErrorsFrom(value); + } else { + this.add(value.result().get()); + } + return this; + } + + @Override + public ListBuilder withErrorsFrom(final DataResult result) { + if (this.error != null) { + this.error = this.error.flatMap(x -> result); + } else { + this.error = result; + } + return this; + } + + @Override + public ListBuilder mapError(final UnaryOperator onError) { + if (this.error != null) { + this.error = this.error.mapError(onError); + } + return this; + } +} diff --git a/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/package-info.java b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/package-info.java new file mode 100644 index 0000000000..e0aabb4476 --- /dev/null +++ b/nbt-dfu/src/main/java/net/kyori/adventure/nbt/dfu/package-info.java @@ -0,0 +1,6 @@ +/** + * An implementation of Mojang's DynamicOps for the Adventure NBT library. + * + * @since 4.21.0 + */ +package net.kyori.adventure.nbt.dfu; diff --git a/nbt-dfu/src/test/java/net/kyori/adventure/nbt/dfu/BinaryTagOpsTest.java b/nbt-dfu/src/test/java/net/kyori/adventure/nbt/dfu/BinaryTagOpsTest.java new file mode 100644 index 0000000000..826a1a9644 --- /dev/null +++ b/nbt-dfu/src/test/java/net/kyori/adventure/nbt/dfu/BinaryTagOpsTest.java @@ -0,0 +1,94 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 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.nbt.dfu; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.kyori.examination.string.StringExaminer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BinaryTagOpsTest { + + private T valueOrThrow(final DataResult result) { + result.error().ifPresent(err -> { + throw new RuntimeException("Error with data result: " + err.message()); + }); + + return result.result().orElseThrow(() -> new IllegalStateException("Neither result or error was present")); + } + + @Test + public void testStringCreation() { + + } + + @Test + void testComplexObject() { + final TestValue subject = new TestValue("meow", 28, Key.key("adventure", "purr")); + final DataResult tag = TestValue.CODEC.encode(subject, BinaryTagOps.UNCOMPRESSED, BinaryTagOps.UNCOMPRESSED.empty()); + final CompoundBinaryTag compound = (CompoundBinaryTag) this.valueOrThrow(tag); + + final TestValue reversed = this.valueOrThrow(TestValue.CODEC.decode(BinaryTagOps.UNCOMPRESSED, compound)).getFirst(); + assertEquals(subject, reversed); + + System.out.println(compound.examine(StringExaminer.simpleEscaping())); + } + + @Test + void testMergeListWithEmpty() { + final BinaryTag tag = this.valueOrThrow(BinaryTagOps.UNCOMPRESSED.mergeToList(BinaryTagOps.UNCOMPRESSED.empty(), StringBinaryTag.stringBinaryTag("test"))); + assertEquals(ListBinaryTag.builder().add(StringBinaryTag.stringBinaryTag("test")).build(), tag); + } + + @Test + void testMergeWithCompound() { + + } + + @Test + void testListAdd() { + + } + + @Test + void testListAddToEmpty() { + + } + + record TestValue(String name, int amount, Key ident) { + public static final Codec CODEC = RecordCodecBuilder.create(o -> o.group( + Codec.STRING.fieldOf("name").forGetter(t -> t.name), + Codec.INT.fieldOf("amount").forGetter(t -> t.amount), + Key.CODEC.fieldOf("key").forGetter(t -> t.ident) + ).apply(o, TestValue::new)); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2f38c3c52e..bf09d22d5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,12 @@ dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenCentral() + maven(url = "https://libraries.minecraft.net") { + mavenContent { + includeGroup("com.mojang") + releasesOnly() + } + } } } @@ -34,6 +40,8 @@ sequenceOf( "api", "annotation-processors", "bom", + "dfu", + "nbt-dfu", "extra-kotlin", "key", "nbt",