diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredData.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredData.java index 45e110e62..4bd08908a 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredData.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredData.java @@ -52,16 +52,14 @@ public interface StructuredData extends IdHolder { return new EmptyStructuredData<>(key, id); } - void setValue(final T value); + @Nullable T value(); - void write(final ByteBuf buffer); + void setValue(final T value); void setId(final int id); StructuredDataKey key(); - @Nullable T value(); - /** * Returns whether the structured data is present. Even if true, the value may be null. * @@ -77,4 +75,6 @@ public interface StructuredData extends IdHolder { * @return true if the structured data is empty */ boolean isEmpty(); + + void write(final ByteBuf buffer); } diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataContainer.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataContainer.java index 740994c3d..3907748d2 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataContainer.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataContainer.java @@ -33,6 +33,26 @@ import java.util.Map; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * Loosely represents Minecraft's data component patch, but may also be used for an item's full data components. + *

+ * The most commonly used methods will ignore empty data (aka empty overrides that remove item defaults) since those will rarely be needed. + * These are: + *

    + *
  • {@link #get(StructuredDataKey)}
  • + *
  • {@link #set(StructuredDataKey, Object)}
  • + *
  • {@link #set(StructuredDataKey)}
  • + *
  • {@link #getNonEmptyData(StructuredDataKey)}
  • + *
  • {@link #hasValue(StructuredDataKey)}
  • + *
+ * + * To interact with empty patches specifically, use: + *
    + *
  • {@link #setEmpty(StructuredDataKey)}
  • + *
  • {@link #hasEmpty(StructuredDataKey)}
  • + *
+ * Other methods (e.g. {@link #getData(StructuredDataKey)} and {@link #has(StructuredDataKey)}) will handle both empty and non-empty data. + */ public final class StructuredDataContainer { private final Map, StructuredData> data; @@ -55,66 +75,45 @@ public final class StructuredDataContainer { } /** - * Returns structured data by id if present. + * Returns the non-empty value by id if present. + * + * @param key serializer id + * @param data type + * @return structured data + * @see #hasEmpty(StructuredDataKey) + */ + public @Nullable T get(final StructuredDataKey key) { + final StructuredData data = this.data.get(key); + if (data == null || data.isEmpty()) { + return null; + } + //noinspection unchecked + return ((StructuredData) data).value(); + } + + /** + * Returns structured data by id if present, either empty or non-empty. * * @param key serializer id * @param data type * @return structured data */ - public @Nullable StructuredData get(final StructuredDataKey key) { + public @Nullable StructuredData getData(final StructuredDataKey key) { //noinspection unchecked return (StructuredData) this.data.get(key); } /** - * Returns structured data by id if not empty. + * Returns non-empty structured data by id if present. * * @param key serializer id * @param data type - * @return structured data if not empty + * @return non-empty structured data */ - public @Nullable StructuredData getNonEmpty(final StructuredDataKey key) { + public @Nullable StructuredData getNonEmptyData(final StructuredDataKey key) { + final StructuredData data = this.data.get(key); //noinspection unchecked - final StructuredData data = (StructuredData) this.data.get(key); - return data != null && data.isPresent() ? data : null; - } - - /** - * Returns structured data by id if not empty, or creates it. - * - * @param key serializer id - * @param mappingFunction function to create structured data if not present - * @param data type - * @return structured data if not empty - */ - public StructuredData computeIfAbsent(final StructuredDataKey key, final Function, T> mappingFunction) { - final StructuredData data = this.getNonEmpty(key); - if (data != null) { - return data; - } - - final int id = serializerId(key); - final StructuredData empty = StructuredData.of(key, mappingFunction.apply(key), id); - this.data.put(key, empty); - return empty; - } - - /** - * Updates and returns the structured data by id if not empty. - * - * @param key serializer id - * @param mappingFunction function to update existing data - * @param data type - * @return updated structured data if not empty - */ - public @Nullable StructuredData updateIfPresent(final StructuredDataKey key, final Function mappingFunction) { - final StructuredData data = this.getNonEmpty(key); - if (data == null) { - return null; - } - - data.setValue(mappingFunction.apply(data.value())); - return data; + return data != null && data.isPresent() ? (StructuredData) data : null; } public void set(final StructuredDataKey key, final T value) { @@ -124,49 +123,105 @@ public final class StructuredDataContainer { } } - public void replaceKey(final StructuredDataKey key, final StructuredDataKey toKey) { - replace(key, toKey, Function.identity()); - } - - public void replace(final StructuredDataKey key, final StructuredDataKey toKey, final Function valueMapper) { - final StructuredData data = remove(key); - if (data == null) { - return; - } - - if (data.isPresent()) { - set(toKey, valueMapper.apply(data.value())); - } else { - addEmpty(toKey); - } - } - public void set(final StructuredDataKey key) { this.set(key, Unit.INSTANCE); } - public void addEmpty(final StructuredDataKey key) { + public void setEmpty(final StructuredDataKey key) { // Empty optional to override the Minecraft default this.data.put(key, StructuredData.empty(key, serializerId(key))); } /** - * Removes and returns structured data by the given key. + * Updates the structured data by id if not empty. * - * @param key serializer key - * @param data type - * @return removed structured data + * @param key serializer id + * @param valueMapper function to update existing data + * @param data type */ - public @Nullable StructuredData remove(final StructuredDataKey key) { - final StructuredData data = this.data.remove(key); - //noinspection unchecked - return data != null ? (StructuredData) data : null; + public void replace(final StructuredDataKey key, final Function valueMapper) { + final StructuredData data = this.getNonEmptyData(key); + if (data == null) { + return; + } + + final T replacement = valueMapper.apply(data.value()); + if (replacement != null) { + data.setValue(replacement); + } else { + this.data.remove(key); + } } - public boolean contains(final StructuredDataKey key) { + public void replaceKey(final StructuredDataKey key, final StructuredDataKey toKey) { + replace(key, toKey, Function.identity()); + } + + public void replace(final StructuredDataKey key, final StructuredDataKey toKey, final Function valueMapper) { + final StructuredData data = this.data.remove(key); + if (data == null) { + return; + } + + if (data.isPresent()) { + //noinspection unchecked + final T value = (T) data.value(); + final V replacement = valueMapper.apply(value); + if (replacement != null) { + set(toKey, replacement); + } + } else { + // Also replace the key for empty data + setEmpty(toKey); + } + } + + /** + * Removes data by the given key. + * + * @param key data key + * @see #replace(StructuredDataKey, Function) + * @see #replace(StructuredDataKey, StructuredDataKey, Function) + * @see #replaceKey(StructuredDataKey, StructuredDataKey) + */ + public void remove(final StructuredDataKey key) { + this.data.remove(key); + } + + /** + * Returns whether there is data for the given key, either empty or non-empty. + * + * @param key data key + * @return whether there data for the given key + * @see #hasEmpty(StructuredDataKey) + * @see #hasValue(StructuredDataKey) + */ + public boolean has(final StructuredDataKey key) { return this.data.containsKey(key); } + /** + * Returns whether there is non-empty data for the given key. + * + * @param key data key + * @return whether there is non-empty data for the given key + */ + public boolean hasValue(final StructuredDataKey key) { + final StructuredData data = this.data.get(key); + return data != null && data.isPresent(); + } + + /** + * Returns whether the structured data has an empty patch/override. + * + * @param key serializer id + * @return whether the structured data has an empty patch/override + */ + public boolean hasEmpty(final StructuredDataKey key) { + final StructuredData data = this.data.get(key); + return data != null && data.isEmpty(); + } + /** * Sets the lookup for serializer ids. Required to call most of the other methods. * diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java index 1e247371c..39204b2da 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java @@ -416,7 +416,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); + final StructuredData customData = data.getNonEmptyData(StructuredDataKey.CUSTOM_DATA); final CompoundTag tag = customData != null ? customData.value() : new CompoundTag(); final DataItem dataItem = new DataItem(item.identifier(), (byte) item.amount(), tag); if (!dataConverter.backupInconvertibleData() && customData != null && tag.remove(nbtTagName()) != null) { diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java index 803add1c2..b71695e51 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java @@ -20,7 +20,6 @@ package com.viaversion.viaversion.protocols.v1_20_5to1_21.rewriter; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viaversion.api.connection.UserConnection; -import com.viaversion.viaversion.api.minecraft.data.StructuredData; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; @@ -123,7 +122,7 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter customData = dataContainer.getNonEmpty(StructuredDataKey.CUSTOM_DATA); + final CompoundTag customData = dataContainer.get(StructuredDataKey.CUSTOM_DATA); if (customData == null) { return; } - if (customData.value().remove(tagName) != null) { + + if (customData.remove(tagName) != null) { dataContainer.remove(StructuredDataKey.RARITY); - if (customData.value().isEmpty()) { + if (customData.isEmpty()) { dataContainer.remove(StructuredDataKey.CUSTOM_DATA); } } diff --git a/common/src/main/java/com/viaversion/viaversion/rewriter/StructuredItemRewriter.java b/common/src/main/java/com/viaversion/viaversion/rewriter/StructuredItemRewriter.java index 21eed2fd9..fe3b72e63 100644 --- a/common/src/main/java/com/viaversion/viaversion/rewriter/StructuredItemRewriter.java +++ b/common/src/main/java/com/viaversion/viaversion/rewriter/StructuredItemRewriter.java @@ -114,27 +114,27 @@ public class StructuredItemRewriter value.rewrite(itemIdRewriter)); - container.updateIfPresent(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter)); + container.replace(StructuredDataKey.TRIM, value -> value.rewrite(itemIdRewriter)); + container.replace(StructuredDataKey.POT_DECORATIONS, value -> value.rewrite(itemIdRewriter)); } if (mappingData.getBlockMappings() != null) { final Int2IntFunction blockIdRewriter = clientbound ? mappingData::getNewBlockId : mappingData::getOldBlockId; - container.updateIfPresent(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter)); - container.updateIfPresent(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter)); - container.updateIfPresent(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter)); + container.replace(StructuredDataKey.TOOL, value -> value.rewrite(blockIdRewriter)); + container.replace(StructuredDataKey.CAN_PLACE_ON, value -> value.rewrite(blockIdRewriter)); + container.replace(StructuredDataKey.CAN_BREAK, value -> value.rewrite(blockIdRewriter)); } if (mappingData.getSoundMappings() != null) { final Int2IntFunction soundIdRewriter = clientbound ? mappingData::getNewSoundId : mappingData::getOldSoundId; - container.updateIfPresent(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value); - container.updateIfPresent(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter)); + container.replace(StructuredDataKey.INSTRUMENT, value -> value.isDirect() ? Holder.of(value.value().rewrite(soundIdRewriter)) : value); + container.replace(StructuredDataKey.JUKEBOX_PLAYABLE, value -> value.rewrite(soundIdRewriter)); } if (clientbound && protocol.getComponentRewriter() != null) { updateComponent(connection, item, StructuredDataKey.ITEM_NAME, "item_name"); updateComponent(connection, item, StructuredDataKey.CUSTOM_NAME, "custom_name"); - final StructuredData loreData = container.getNonEmpty(StructuredDataKey.LORE); - if (loreData != null) { - for (final Tag tag : loreData.value()) { + final Tag[] lore = container.get(StructuredDataKey.LORE); + if (lore != null) { + for (final Tag tag : lore) { protocol.getComponentRewriter().processTag(connection, tag); } } @@ -166,35 +166,35 @@ public class StructuredItemRewriter key, final String backupKey) { - final StructuredData name = item.dataContainer().getNonEmpty(key); + final Tag name = item.dataContainer().get(key); if (name == null) { return; } - final Tag originalName = name.value().copy(); - protocol.getComponentRewriter().processTag(connection, name.value()); - if (!name.value().equals(originalName)) { + final Tag originalName = name.copy(); + protocol.getComponentRewriter().processTag(connection, name); + if (!name.equals(originalName)) { saveTag(createCustomTag(item), originalName, backupKey); } } protected void restoreTextComponents(final Item item) { final StructuredDataContainer data = item.dataContainer(); - final StructuredData customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); + final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData == null) { return; } // Remove custom name - if (customData.value().remove(nbtTagName("added_custom_name")) != null) { + if (customData.remove(nbtTagName("added_custom_name")) != null) { data.remove(StructuredDataKey.CUSTOM_NAME); } else { - final Tag customName = removeBackupTag(customData.value(), "custom_name"); + final Tag customName = removeBackupTag(customData, "custom_name"); if (customName != null) { data.set(StructuredDataKey.CUSTOM_NAME, customName); } - final Tag itemName = removeBackupTag(customData.value(), "item_name"); + final Tag itemName = removeBackupTag(customData, "item_name"); if (itemName != null) { data.set(StructuredDataKey.ITEM_NAME, itemName); } @@ -203,14 +203,12 @@ public class StructuredItemRewriter customData = data.getNonEmpty(StructuredDataKey.CUSTOM_DATA); - if (customData != null) { - return customData.value(); + CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); + if (customData == null) { + customData = new CompoundTag(); + data.set(StructuredDataKey.CUSTOM_DATA, customData); } - - final CompoundTag tag = new CompoundTag(); - data.set(StructuredDataKey.CUSTOM_DATA, tag); - return tag; + return customData; } protected void saveTag(final CompoundTag customData, final Tag tag, final String name) {