From 7b28683b6aceb1c20dc84e61fbebc954e0720024 Mon Sep 17 00:00:00 2001 From: FlorianMichael <60033407+FlorianMichael@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:47:44 +0200 Subject: [PATCH] Add remaining data backup handling --- .../minecraft/item/data/BannerPattern.java | 16 +- .../BlockItemPacketRewriter1_20_5.java | 141 +++++++++++- .../rewriter/StructuredDataConverter.java | 209 ++++++++++++++---- 3 files changed, 313 insertions(+), 53 deletions(-) diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/item/data/BannerPattern.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/item/data/BannerPattern.java index baed0263d..7f87d9b54 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/item/data/BannerPattern.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/item/data/BannerPattern.java @@ -32,30 +32,30 @@ public final class BannerPattern { @Override public BannerPattern readDirect(final ByteBuf buffer) throws Exception { final String assetId = Type.STRING.read(buffer); - final String tanslationKey = Type.STRING.read(buffer); - return new BannerPattern(assetId, tanslationKey); + final String translationKey = Type.STRING.read(buffer); + return new BannerPattern(assetId, translationKey); } @Override public void writeDirect(final ByteBuf buffer, final BannerPattern value) throws Exception { Type.STRING.write(buffer, value.assetId); - Type.STRING.write(buffer, value.tanslationKey); + Type.STRING.write(buffer, value.translationKey); } }; private final String assetId; - private final String tanslationKey; + private final String translationKey; - public BannerPattern(final String assetId, final String tanslationKey) { + public BannerPattern(final String assetId, final String translationKey) { this.assetId = assetId; - this.tanslationKey = tanslationKey; + this.translationKey = translationKey; } public String assetId() { return assetId; } - public String tanslationKey() { - return tanslationKey; + public String translationKey() { + return translationKey; } } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java index e0f415998..1b75e8ec9 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java @@ -33,6 +33,7 @@ import com.viaversion.viaversion.api.minecraft.GlobalPosition; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.HolderSet; import com.viaversion.viaversion.api.minecraft.Particle; +import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredData; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; @@ -45,6 +46,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimMaterial; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimPattern; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifier; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers; +import com.viaversion.viaversion.api.minecraft.item.data.BannerPattern; import com.viaversion.viaversion.api.minecraft.item.data.BannerPatternLayer; import com.viaversion.viaversion.api.minecraft.item.data.Bee; import com.viaversion.viaversion.api.minecraft.item.data.BlockPredicate; @@ -55,6 +57,9 @@ import com.viaversion.viaversion.api.minecraft.item.data.FilterableComponent; import com.viaversion.viaversion.api.minecraft.item.data.FilterableString; import com.viaversion.viaversion.api.minecraft.item.data.FireworkExplosion; import com.viaversion.viaversion.api.minecraft.item.data.Fireworks; +import com.viaversion.viaversion.api.minecraft.item.data.FoodEffect; +import com.viaversion.viaversion.api.minecraft.item.data.FoodProperties; +import com.viaversion.viaversion.api.minecraft.item.data.Instrument; import com.viaversion.viaversion.api.minecraft.item.data.LodestoneTracker; import com.viaversion.viaversion.api.minecraft.item.data.ModifierData; import com.viaversion.viaversion.api.minecraft.item.data.PotDecorations; @@ -63,6 +68,8 @@ import com.viaversion.viaversion.api.minecraft.item.data.PotionEffect; import com.viaversion.viaversion.api.minecraft.item.data.PotionEffectData; import com.viaversion.viaversion.api.minecraft.item.data.StatePropertyMatcher; import com.viaversion.viaversion.api.minecraft.item.data.SuspiciousStewEffect; +import com.viaversion.viaversion.api.minecraft.item.data.ToolProperties; +import com.viaversion.viaversion.api.minecraft.item.data.ToolRule; import com.viaversion.viaversion.api.minecraft.item.data.Unbreakable; import com.viaversion.viaversion.api.minecraft.item.data.WrittenBook; import com.viaversion.viaversion.api.type.Type; @@ -498,6 +505,16 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter bannerPatterns = backupTag.getListTag("banner_patterns", CompoundTag.class); + if (bannerPatterns != null) { + restoreBannerPatternsFromBackup(bannerPatterns, data); + } + } + + private void restoreInstrumentFromBackup(final CompoundTag instrument, final StructuredDataContainer data) { + final int useDuration = instrument.getInt("use_duration"); + final float range = instrument.getFloat("range"); + + Holder soundEvent; + final Tag soundEventTag = instrument.get("sound_event"); + if (soundEventTag instanceof IntTag) { + soundEvent = Holder.of(((IntTag) soundEventTag).asInt()); + } else if (soundEventTag instanceof CompoundTag) { + final CompoundTag soundEventCompound = (CompoundTag) soundEventTag; + final StringTag identifier = soundEventCompound.getStringTag("identifier"); + if (identifier == null) { + return; // Nothing we can do about + } + soundEvent = Holder.of(new SoundEvent( + identifier.getValue(), + soundEventCompound.contains("fixed_range") ? + soundEventCompound.getFloat("fixed_range") : null + )); + } else { + return; // Nothing we can do about + } + data.set(StructuredDataKey.INSTRUMENT, Holder.of(new Instrument(soundEvent, useDuration, range))); + } + + private void restoreFoodFromBackup(final CompoundTag food, final StructuredDataContainer data) { + final int nutrition = food.getInt("nutrition"); + final float saturation = food.getFloat("saturation"); + final boolean canAlwaysEat = food.getBoolean("can_always_eat"); + final float eatSeconds = food.getFloat("eat_seconds"); + + final ListTag possibleEffectsTag = food.getListTag("possible_effects", CompoundTag.class); + if (possibleEffectsTag == null) { + return; + } + final List possibleEffects = new ArrayList<>(); + for (final CompoundTag effect : possibleEffectsTag) { + final CompoundTag potionEffectTag = effect.getCompoundTag("effect"); + if (potionEffectTag == null) { + continue; // Nothing we can do about + } + possibleEffects.add(new FoodEffect( + new PotionEffect( + potionEffectTag.getInt("effect"), + readPotionEffectData(potionEffectTag) + ), + effect.getFloat("probability") + )); + } + data.set(StructuredDataKey.FOOD, new FoodProperties(nutrition, saturation, canAlwaysEat, eatSeconds, possibleEffects.toArray(new FoodEffect[0]))); + } + + private void restoreToolFromBackup(final CompoundTag tool, final StructuredDataContainer data) { + final ListTag rulesTag = tool.getListTag("rules", CompoundTag.class); + if (rulesTag == null) { + return; + } + final List rules = new ArrayList<>(); + for (final CompoundTag tag : rulesTag) { + HolderSet blocks = null; + if (tag.get("blocks") instanceof StringTag) { + blocks = HolderSet.of(tag.getString("blocks")); + } else { + final IntArrayTag blockIds = tag.getIntArrayTag("blocks"); + if (blockIds != null) { + blocks = HolderSet.of(blockIds.getValue()); + } + } + if (blocks == null) { + continue; // Nothing we can do about + } + rules.add(new ToolRule( + blocks, + tag.contains("speed") ? tag.getFloat("speed") : null, + tag.contains("correct_for_drops") ? tag.getBoolean("correct_for_drops") : null + )); + } + data.set(StructuredDataKey.TOOL, new ToolProperties( + rules.toArray(new ToolRule[0]), + tool.getFloat("default_mining_speed"), + tool.getInt("damage_per_block") + )); + } + + private void restoreBannerPatternsFromBackup(final ListTag bannerPatterns, final StructuredDataContainer data) { + final List patternLayer = new ArrayList<>(); + for (final CompoundTag tag : bannerPatterns) { + final CompoundTag patternTag = tag.getCompoundTag("pattern"); + if (patternTag == null) { + continue; // Nothing we can do about + } + final String assetId = patternTag.getString("asset_id"); + final String translationKey = patternTag.getString("translation_key"); + final int dyeColor = tag.getInt("dye_color"); + patternLayer.add(new BannerPatternLayer(Holder.of(new BannerPattern(assetId, translationKey)), dyeColor)); + } + data.set(StructuredDataKey.BANNER_PATTERNS, patternLayer.toArray(new BannerPatternLayer[0])); } private int unmappedItemId(final String name) { @@ -706,7 +837,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter materialHolder; if (materialTag instanceof StringTag) { - // Would technically have to be stored and retreived from registry data, but that'd mean a lot of work + // Would technically have to be stored and retrieved from registry data, but that'd mean a lot of work final int id = TrimMaterials1_20_3.keyToId(((StringTag) materialTag).getValue()); if (id == -1) { return; @@ -720,7 +851,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter patternHolder; if (patternTag instanceof StringTag) { - // Would technically have to be stored and retreived from registry data, but that'd mean a lot of work + // Would technically have to be stored and retrieved from registry data, but that'd mean a lot of work final int id = TrimPatterns1_20_3.keyToId(((StringTag) patternTag).getValue()); if (id == -1) { return; @@ -769,7 +900,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter, DataConverter> rewriters = new Reference2ObjectOpenHashMap<>(); - private final boolean backupInconvertibleData; public StructuredDataConverter(final boolean backupInconvertibleData) { - this.backupInconvertibleData = backupInconvertibleData; register(StructuredDataKey.CUSTOM_DATA, (data, tag) -> { // Handled manually }); @@ -244,9 +251,26 @@ public final class StructuredDataConverter { } }); register(StructuredDataKey.INSTRUMENT, (data, tag) -> { + // Can't do anything with direct values if (!data.hasId()) { - // Can't do anything with direct values - // TODO Backup + if (backupInconvertibleData) { + final CompoundTag backupTag = new CompoundTag(); + final Instrument instrument = data.value(); + if (instrument.soundEvent().hasId()) { + backupTag.putInt("sound_event", instrument.soundEvent().id()); + } else { + final CompoundTag soundEventTag = new CompoundTag(); + final SoundEvent soundEvent = instrument.soundEvent().value(); + soundEventTag.putString("identifier", soundEvent.identifier()); + if (soundEvent.fixedRange() != null) { + soundEventTag.putFloat("fixed_range", soundEvent.fixedRange()); + } + backupTag.put("sound_event", soundEventTag); + } + backupTag.putInt("use_duration", instrument.useDuration()); + backupTag.putFloat("range", instrument.range()); + getBackupTag(tag).put("instrument", backupTag); + } return; } @@ -269,9 +293,23 @@ public final class StructuredDataConverter { register(StructuredDataKey.LOCK, (data, tag) -> getBlockEntityTag(tag).put("Lock", data)); register(StructuredDataKey.NOTE_BLOCK_SOUND, (data, tag) -> getBlockEntityTag(tag).putString("note_block_sound", data)); register(StructuredDataKey.POT_DECORATIONS, (data, tag) -> { + IntArrayTag originalSherds = null; + final ListTag sherds = new ListTag<>(StringTag.class); for (final int id : data.itemIds()) { - sherds.add(new StringTag(toItemName(id))); + final String name = toMappedItemName(id); + if (name.isEmpty()) { + // Backup whole data if one of the sherds is inconvertible + // Since we don't want to break the order of the sherds + if (backupInconvertibleData && originalSherds == null) { + originalSherds = new IntArrayTag(data.itemIds()); + } + continue; + } + sherds.add(new StringTag(name)); + } + if (originalSherds != null) { + getBackupTag(tag).put("pot_decorations", originalSherds); } getBlockEntityTag(tag).put("sherds", sherds); }); @@ -302,7 +340,7 @@ public final class StructuredDataConverter { }); register(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putBoolean("enchantment_glint_override", data); + getBackupTag(tag).putBoolean("enchantment_glint_override", data); } if (!data) { // There is no way to remove the glint without removing the enchantments @@ -368,14 +406,32 @@ public final class StructuredDataConverter { tag.put("effects", effectsTag); }); register(StructuredDataKey.BANNER_PATTERNS, (data, tag) -> { + final ListTag originalPatterns = new ListTag<>(CompoundTag.class); + if (backupInconvertibleData) { + // Backup whole data if one of the patterns is inconvertible + // Since we don't want to break the order of the patterns + if (Arrays.stream(data).anyMatch(layer -> layer.pattern().isDirect())) { + for (final BannerPatternLayer layer : data) { + final CompoundTag layerTag = new CompoundTag(); + final CompoundTag patternTag = new CompoundTag(); + final BannerPattern pattern = layer.pattern().value(); + patternTag.putString("asset_id", pattern.assetId()); + patternTag.putString("translation_key", pattern.translationKey()); + layerTag.put("pattern", patternTag); + layerTag.putInt("dye_color", layer.dyeColor()); + originalPatterns.add(layerTag); + } + getBackupTag(tag).put("banner_patterns", originalPatterns); + return; + } + } + final ListTag patternsTag = new ListTag<>(CompoundTag.class); for (final BannerPatternLayer layer : data) { final String pattern = BannerPatterns1_20_5.fullIdToCompact(BannerPatterns1_20_5.idToKey(layer.pattern().id())); if (pattern == null) { - // TODO Backup continue; } - final CompoundTag patternTag = new CompoundTag(); patternTag.putString("Pattern", pattern); patternTag.putInt("Color", layer.dyeColor()); @@ -403,9 +459,9 @@ public final class StructuredDataConverter { final ArmorTrimMaterial material = data.material().value(); materialTag.putString("asset_name", material.assetName()); - final String ingredientName = toItemName(material.itemId()); + final String ingredientName = toMappedItemName(material.itemId()); if (ingredientName.isEmpty()) { - return; + getBackupTag(materialTag).putInt(ITEM_TAG_KEY, material.itemId()); } materialTag.putString("ingredient", ingredientName); materialTag.put("item_model_index", new FloatTag(material.itemModelIndex())); @@ -429,9 +485,9 @@ public final class StructuredDataConverter { final ArmorTrimPattern pattern = data.pattern().value(); patternTag.putString("assetId", pattern.assetName()); - final String itemName = toItemName(pattern.itemId()); + final String itemName = toMappedItemName(pattern.itemId()); if (itemName.isEmpty()) { - return; + getBackupTag(patternTag).putInt(ITEM_TAG_KEY, pattern.itemId()); } patternTag.putString("templateItem", itemName); patternTag.put("description", pattern.description()); @@ -459,60 +515,102 @@ public final class StructuredDataConverter { // Hide everything we can hide putHideFlag(tag, 0xFF); if (backupInconvertibleData) { - createBackupTag(tag).putBoolean("hide_tooltip", true); + getBackupTag(tag).putBoolean("hide_tooltip", true); } }); // New in 1.20.5 register(StructuredDataKey.INTANGIBLE_PROJECTILE, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).put("intangible_projectile", data); + getBackupTag(tag).put("intangible_projectile", data); } }); register(StructuredDataKey.MAX_STACK_SIZE, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putInt("max_stack_size", data); + getBackupTag(tag).putInt("max_stack_size", data); } }); register(StructuredDataKey.MAX_DAMAGE, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putInt("max_damage", data); + getBackupTag(tag).putInt("max_damage", data); } }); register(StructuredDataKey.RARITY, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putInt("rarity", data); + getBackupTag(tag).putInt("rarity", data); } }); register(StructuredDataKey.FOOD, (data, tag) -> { if (backupInconvertibleData) { - //createBackupTag(tag).; // TODO Backup + final CompoundTag backupTag = new CompoundTag(); + backupTag.putInt("nutrition", data.nutrition()); + backupTag.putFloat("saturation_modifier", data.saturationModifier()); + backupTag.putBoolean("can_always_eat", data.canAlwaysEat()); + backupTag.putFloat("eat_seconds", data.eatSeconds()); + + final ListTag possibleEffectsTag = new ListTag<>(CompoundTag.class); + for (final FoodEffect effect : data.possibleEffects()) { + final CompoundTag effectTag = new CompoundTag(); + + final PotionEffect potionEffect = effect.effect(); + final CompoundTag potionEffectTag = new CompoundTag(); + potionEffectTag.putInt("effect", potionEffect.effect()); + potionEffectTag.put("effect_data", convertPotionEffectData(potionEffect.effectData())); + + effectTag.putFloat("probability", effect.probability()); + effectTag.put("effect", potionEffectTag); + possibleEffectsTag.add(effectTag); + } + backupTag.put("possible_effects", possibleEffectsTag); + getBackupTag(tag).put("food", backupTag); } }); register(StructuredDataKey.FIRE_RESISTANT, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putBoolean("fire_resistant", true); + getBackupTag(tag).putBoolean("fire_resistant", true); } }); register(StructuredDataKey.TOOL, (data, tag) -> { if (backupInconvertibleData) { - //createBackupTag(tag).; // TODO Backup + final CompoundTag backupTag = new CompoundTag(); + final ListTag rulesTag = new ListTag<>(CompoundTag.class); + for (final ToolRule rule : data.rules()) { + final CompoundTag ruleTag = new CompoundTag(); + final HolderSet set = rule.blocks(); + if (set.hasTagKey()) { + ruleTag.putString("blocks", set.tagKey()); + } else { + ruleTag.put("blocks", new IntArrayTag(set.ids())); + } + if (rule.speed() != null) { + ruleTag.putFloat("speed", rule.speed()); + } + if (rule.correctForDrops() != null) { + ruleTag.putBoolean("correct_for_drops", rule.correctForDrops()); + } + rulesTag.add(ruleTag); + } + backupTag.put("rules", rulesTag); + backupTag.putFloat("default_mining_speed", data.defaultMiningSpeed()); + backupTag.putInt("damage_per_block", data.damagePerBlock()); + getBackupTag(tag).put("tool", backupTag); } }); register(StructuredDataKey.OMINOUS_BOTTLE_AMPLIFIER, (data, tag) -> { if (backupInconvertibleData) { - createBackupTag(tag).putInt("ominous_bottle_amplifier", data); + getBackupTag(tag).putInt("ominous_bottle_amplifier", data); } }); } - private String toItemName(final int id) { - final int mappedId = Protocol1_20_5To1_20_3.MAPPINGS.getOldItemId(id); - // TODO Backup - return mappedId != -1 ? Protocol1_20_5To1_20_3.MAPPINGS.itemName(mappedId) : ""; + private int unmappedItemId(final int id) { + return Protocol1_20_5To1_20_3.MAPPINGS.getOldItemId(id); } - // If multiple item components which previously were stored in BlockEntityTag are present, we need to merge them + private String toMappedItemName(final int id) { + final int mappedId = unmappedItemId(id); + return mappedId != -1 ? Protocol1_20_5To1_20_3.MAPPINGS.itemName(mappedId) : ""; + } private static CompoundTag getBlockEntityTag(final CompoundTag tag) { return getOrCreate(tag, "BlockEntityTag"); @@ -522,6 +620,11 @@ public final class StructuredDataConverter { return getOrCreate(tag, "display"); } + private static CompoundTag getBackupTag(final CompoundTag tag) { + return getOrCreate(tag, BACKUP_TAG_KEY); + } + + // If multiple item components which previously were stored in BlockEntityTag are present, we need to merge them private static CompoundTag getOrCreate(final CompoundTag tag, final String key) { CompoundTag subTag = tag.getCompoundTag(key); if (subTag == null) { @@ -531,6 +634,22 @@ public final class StructuredDataConverter { return subTag; } + static @Nullable CompoundTag removeBackupTag(final CompoundTag tag) { + final CompoundTag backupTag = tag.getCompoundTag(BACKUP_TAG_KEY); + if (backupTag != null) { + tag.remove(BACKUP_TAG_KEY); + } + return backupTag; + } + + static int getBackupItemId(final CompoundTag tag, final int unmappedId) { + if (unmappedId != -1) { + return unmappedId; + } + final IntTag itemIdTag = tag.getIntTag(ITEM_TAG_KEY); + return itemIdTag != null ? itemIdTag.getTagId() : -1; + } + private void convertBlockPredicates(final CompoundTag tag, final AdventureModePredicate data, final String key, final int hideFlag) { final ListTag predicatedListTag = new ListTag<>(StringTag.class); for (final BlockPredicate predicate : data.predicates()) { @@ -545,7 +664,12 @@ public final class StructuredDataConverter { predicatedListTag.add(serializeBlockPredicate(predicate, tagKey)); } else { for (final int id : holders.ids()) { - predicatedListTag.add(serializeBlockPredicate(predicate, toItemName(id))); + final String name = toMappedItemName(id); + if (name.isEmpty()) { + // TODO HANDLE + continue; + } + predicatedListTag.add(serializeBlockPredicate(predicate, name)); } } } @@ -583,11 +707,28 @@ public final class StructuredDataConverter { return explosionTag; } + private CompoundTag convertPotionEffectData(final PotionEffectData data) { + final CompoundTag effectDataTag = new CompoundTag(); + effectDataTag.putInt("amplifier", data.amplifier()); + effectDataTag.putInt("duration", data.duration()); + effectDataTag.putBoolean("ambient", data.ambient()); + effectDataTag.putBoolean("show_particles", data.showParticles()); + effectDataTag.putBoolean("show_icon", data.showIcon()); + if (data.hiddenEffect() != null) { + effectDataTag.put("hidden_effect", convertPotionEffectData(data.hiddenEffect())); + } + return effectDataTag; + } + private void convertItemList(final Item[] items, final CompoundTag tag, final String key) { final ListTag itemsTag = new ListTag<>(CompoundTag.class); for (final Item item : items) { final CompoundTag savedItem = new CompoundTag(); - savedItem.putString("id", toItemName(item.identifier())); + final String name = toMappedItemName(item.identifier()); + savedItem.putString("id", name); + if (name.isEmpty()) { + savedItem.putInt(ITEM_TAG_KEY, item.identifier()); + } savedItem.putByte("Count", (byte) item.amount()); final CompoundTag itemTag = new CompoundTag(); @@ -649,18 +790,6 @@ public final class StructuredDataConverter { rewriters.put(key, converter); } - private static CompoundTag createBackupTag(final CompoundTag tag) { - return getOrCreate(tag, BACKUP_TAG_KEY); - } - - static @Nullable CompoundTag removeBackupTag(final CompoundTag tag) { - final CompoundTag backupTag = tag.getCompoundTag(BACKUP_TAG_KEY); - if (backupTag != null) { - tag.remove(BACKUP_TAG_KEY); - } - return backupTag; - } - @FunctionalInterface interface DataConverter {