From 6245443e5729aeded34ec7762aa647601ef151da Mon Sep 17 00:00:00 2001 From: FlorianMichael Date: Sun, 3 Nov 2024 17:01:50 +0100 Subject: [PATCH] Backup inconvertible item component data for creative clients in 1.21+ --- .../BlockItemPacketRewriter1_21_2.java | 314 ++++++++++++++++++ .../rewriter/BlockItemPacketRewriter1_21.java | 81 +++++ gradle.properties | 2 +- 3 files changed, 396 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/BlockItemPacketRewriter1_21_2.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/BlockItemPacketRewriter1_21_2.java index 3f4c08d6..23f2e75b 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/BlockItemPacketRewriter1_21_2.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/BlockItemPacketRewriter1_21_2.java @@ -17,14 +17,33 @@ */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter; +import com.viaversion.nbt.tag.ByteTag; +import com.viaversion.nbt.tag.CompoundTag; +import com.viaversion.nbt.tag.IntArrayTag; +import com.viaversion.nbt.tag.IntTag; +import com.viaversion.nbt.tag.ListTag; +import com.viaversion.nbt.tag.StringTag; +import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.InventoryStateIdStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.RecipeStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.MappingData; +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.StructuredDataContainer; +import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; +import com.viaversion.viaversion.api.minecraft.item.data.Consumable1_21_2; +import com.viaversion.viaversion.api.minecraft.item.data.DeathProtection; +import com.viaversion.viaversion.api.minecraft.item.data.Equippable; +import com.viaversion.viaversion.api.minecraft.item.data.Instrument1_21_2; +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.UseCooldown; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; @@ -38,6 +57,7 @@ import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacke import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.SoundRewriter; import com.viaversion.viaversion.util.Key; +import com.viaversion.viaversion.util.Unit; import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.updateItemData; @@ -251,6 +271,7 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem @Override public Item handleItemToClient(final UserConnection connection, final Item item) { super.handleItemToClient(connection, item); + backupInconvertibleData(item); downgradeItemData(item); return item; } @@ -259,6 +280,299 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem public Item handleItemToServer(final UserConnection connection, final Item item) { super.handleItemToServer(connection, item); updateItemData(item); + restoreInconvertibleData(item); return item; } + + // Backup inconvertible data and later restore to prevent data loss for creative mode clients + private void backupInconvertibleData(final Item item) { + final StructuredDataContainer data = item.dataContainer(); + final Holder instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2); + if (instrument != null && instrument.isDirect()) { + saveTag(createCustomTag(item), instrument.value().description(), "instrument_description"); + } + + final HolderSet repairable = data.get(StructuredDataKey.REPAIRABLE); + if (repairable != null) { + final CompoundTag tag = new CompoundTag(); + convertHolderSet(tag, repairable); + saveTag(createCustomTag(item), tag, "repairable"); + } + + final Integer enchantable = data.get(StructuredDataKey.ENCHANTABLE); + if (enchantable != null) { + saveTag(createCustomTag(item), new IntTag(enchantable), "enchantable"); + } + + final UseCooldown useCooldown = data.get(StructuredDataKey.USE_COOLDOWN); + if (useCooldown != null) { + final CompoundTag tag = new CompoundTag(); + tag.putFloat("seconds", useCooldown.seconds()); + if (useCooldown.cooldownGroup() != null) { + tag.putString("cooldown_group", useCooldown.cooldownGroup()); + } + } + + final String itemModel = data.get(StructuredDataKey.ITEM_MODEL); + if (itemModel != null) { + saveTag(createCustomTag(item), new StringTag(itemModel), "item_model"); + } + + final Equippable equippable = data.get(StructuredDataKey.EQUIPPABLE); + if (equippable != null) { + final CompoundTag tag = new CompoundTag(); + + tag.putInt("equipment_slot", equippable.equipmentSlot()); + convertSoundEventHolder(tag, equippable.soundEvent()); + final String model = equippable.model(); + if (model != null) { + tag.putString("model", model); + } + final String cameraOverlay = equippable.cameraOverlay(); + if (cameraOverlay != null) { + tag.putString("camera_overlay", cameraOverlay); + } + if (equippable.allowedEntities() != null) { + final CompoundTag allowedEntities = new CompoundTag(); + convertHolderSet(allowedEntities, equippable.allowedEntities()); + + tag.put("allowed_entities", allowedEntities); + } + tag.putBoolean("dispensable", equippable.dispensable()); + tag.putBoolean("swappable", equippable.swappable()); + tag.putBoolean("damage_on_hurt", equippable.damageOnHurt()); + + saveTag(createCustomTag(item), tag, "equippable"); + } + + final Unit glider = data.get(StructuredDataKey.GLIDER); + if (glider != null) { + saveTag(createCustomTag(item), new ByteTag(true), "glider"); + } + + final String tooltipStyle = data.get(StructuredDataKey.TOOLTIP_STYLE); + if (tooltipStyle != null) { + saveTag(createCustomTag(item), new StringTag(tooltipStyle), "tooltip_style"); + } + + final DeathProtection deathProtection = data.get(StructuredDataKey.DEATH_PROTECTION); + if (deathProtection == null) { + return; + } + final ListTag tag = new ListTag<>(CompoundTag.class); + for (final Consumable1_21_2.ConsumeEffect effect : deathProtection.deathEffects()) { + final CompoundTag effectTag = new CompoundTag(); + convertConsumableEffect(effectTag, effect); + tag.add(effectTag); + } + saveTag(createCustomTag(item), tag, "death_protection"); + } + + private void convertConsumableEffect(final CompoundTag tag, Consumable1_21_2.ConsumeEffect effect) { + tag.putInt("id", effect.id()); + if (effect.type() == Consumable1_21_2.ApplyStatusEffects.TYPE && effect.value() instanceof Consumable1_21_2.ApplyStatusEffects value) { + tag.putString("type", "apply_effects"); + + final ListTag effects = new ListTag<>(CompoundTag.class); + for (final PotionEffect potionEffect : value.effects()) { + final CompoundTag effectTag = new CompoundTag(); + effectTag.putInt("effect", potionEffect.effect()); + convertPotionEffectData(effectTag, potionEffect.effectData()); + effects.add(effectTag); + } + tag.put("effects", effects); + tag.putFloat("probability", value.probability()); + } else if (effect.type() == Types.HOLDER_SET && effect.value() instanceof HolderSet set) { + tag.putString("type", "remove_effects"); + + if (set.hasIds()) { + tag.put("ids", new IntArrayTag(set.ids())); + } else { + tag.putString("tag", set.tagKey()); + } + } else if (effect.type() == Types.EMPTY) { + tag.putString("type", "clear_all_effects"); + } else if (effect.type() == Types.FLOAT) { + tag.putString("type", "teleport_randomly"); + + tag.putFloat("probability", (Float) effect.value()); + } else if (effect.type() == Types.SOUND_EVENT && effect.value() instanceof Holder sound) { + tag.putString("type", "play_sound"); + + convertSoundEventHolder(tag, sound); + } + } + + private void convertPotionEffectData(final CompoundTag tag, final PotionEffectData data) { + tag.putInt("amplifier", data.amplifier()); + tag.putInt("duration", data.duration()); + tag.putBoolean("ambient", data.ambient()); + tag.putBoolean("show_particles", data.showParticles()); + tag.putBoolean("show_icon", data.showIcon()); + if (data.hiddenEffect() != null) { + final CompoundTag hiddenEffect = new CompoundTag(); + convertPotionEffectData(hiddenEffect, data.hiddenEffect()); + tag.put("hidden_effect", hiddenEffect); + } + } + + private void convertSoundEventHolder(final CompoundTag tag, final Holder holder) { + if (holder.hasId()) { + tag.putInt("sound", holder.id()); + } else { + final SoundEvent event = holder.value(); + tag.putString("identifier", event.identifier()); + if (event.fixedRange() != null) { + tag.putFloat("fixed_range", event.fixedRange()); + } + } + } + + private void convertHolderSet(final CompoundTag tag, final HolderSet set) { + if (set.hasIds()) { + tag.put("ids", new IntArrayTag(set.ids())); + } else { + tag.putString("tag", set.tagKey()); + } + } + + private Consumable1_21_2.ConsumeEffect convertConsumableEffect(final CompoundTag tag) { + final int id = tag.getInt("id"); + final String type = tag.getString("type"); + if ("apply_effects".equals(type)) { + final ListTag effects = tag.getListTag("effects", CompoundTag.class); + final PotionEffect[] potionEffects = new PotionEffect[effects.size()]; + for (int i = 0; i < effects.size(); i++) { + final CompoundTag effectTag = effects.get(i); + final int effect = effectTag.getInt("effect"); + final PotionEffectData data = convertPotionEffectData(effectTag.getCompoundTag("data")); + potionEffects[i] = new PotionEffect(effect, data); + } + final float probability = tag.getFloat("probability"); + return new Consumable1_21_2.ConsumeEffect<>(id, Consumable1_21_2.ApplyStatusEffects.TYPE, new Consumable1_21_2.ApplyStatusEffects(potionEffects, probability)); + } else if ("remove_effects".equals(type)) { + final HolderSet set = convertHolderSet(tag); + return new Consumable1_21_2.ConsumeEffect<>(id, Types.HOLDER_SET, set); + } else if ("clear_all_effects".equals(type)) { + return new Consumable1_21_2.ConsumeEffect<>(id, Types.EMPTY, Unit.INSTANCE); + } else if ("teleport_randomly".equals(type)) { + final float probability = tag.getFloat("probability"); + return new Consumable1_21_2.ConsumeEffect<>(id, Types.FLOAT, probability); + } else if ("play_sound".equals(type)) { + final Holder sound = convertSoundEventHolder(tag); + return new Consumable1_21_2.ConsumeEffect<>(id, Types.SOUND_EVENT, sound); + } + return null; + } + + private PotionEffectData convertPotionEffectData(final CompoundTag tag) { + final int amplifier = tag.getInt("amplifier"); + final int duration = tag.getInt("duration"); + final boolean ambient = tag.getBoolean("ambient"); + final boolean showParticles = tag.getBoolean("show_particles"); + final boolean showIcon = tag.getBoolean("show_icon"); + final CompoundTag hiddenEffect = tag.getCompoundTag("hidden_effect"); + return new PotionEffectData(amplifier, duration, ambient, showParticles, showIcon, hiddenEffect != null ? convertPotionEffectData(hiddenEffect) : null); + } + + private Holder convertSoundEventHolder(final CompoundTag tag) { + final int soundId = tag.getInt("sound"); + if (soundId != 0) { + return Holder.of(soundId); + } + + final String identifier = tag.getString("identifier"); + final Float fixedRange = tag.getFloat("fixed_range"); + return Holder.of(new SoundEvent(identifier, fixedRange)); + } + + private HolderSet convertHolderSet(final CompoundTag tag) { + if (tag == null) { + return null; + } + final IntArrayTag ids = tag.getIntArrayTag("ids"); + if (ids != null) { + return HolderSet.of(ids.getValue()); + } + return HolderSet.of(tag.getString("tag")); + } + + private void restoreInconvertibleData(final Item item) { + final StructuredDataContainer data = item.dataContainer(); + final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); + + final Holder instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2); + if (instrument != null && customData != null) { + final Tag description = customData.remove(nbtTagName("instrument_description")); + if (description != null) { + final Instrument1_21_2 delegate = instrument.value(); + data.set(StructuredDataKey.INSTRUMENT1_21_2, Holder.of(new Instrument1_21_2(delegate.soundEvent(), delegate.useDuration(), delegate.range(), description))); + } + removeCustomTag(data, customData); + } + + final IntArrayTag repairableIds = customData.getIntArrayTag("repairable_ids"); + final String repairableTag = customData.getString("repairable_tag"); + if (repairableIds != null || repairableTag != null) { + data.set(StructuredDataKey.REPAIRABLE, repairableIds != null ? HolderSet.of(repairableIds.getValue()) : HolderSet.of(repairableTag)); + removeCustomTag(data, customData); + } + + final IntTag enchantable = customData.getIntTag("enchantable"); + if (enchantable != null) { + data.set(StructuredDataKey.ENCHANTABLE, enchantable.asInt()); + removeCustomTag(data, customData); + } + + final CompoundTag useCooldown = customData.getCompoundTag("use_cooldown"); + if (useCooldown != null) { + final float seconds = useCooldown.getFloat("seconds"); + final String cooldownGroup = useCooldown.getString("cooldown_group"); + data.set(StructuredDataKey.USE_COOLDOWN, new UseCooldown(seconds, cooldownGroup)); + removeCustomTag(data, customData); + } + + final String itemModel = customData.getString("item_model"); + if (itemModel != null) { + data.set(StructuredDataKey.ITEM_MODEL, itemModel); + removeCustomTag(data, customData); + } + + final CompoundTag equippable = customData.getCompoundTag("equippable"); + if (equippable != null) { + final int equipmentSlot = equippable.getInt("equipment_slot"); + final Holder soundEvent = convertSoundEventHolder(equippable); + final String model = equippable.getString("model"); + final String cameraOverlay = equippable.getString("camera_overlay"); + final HolderSet allowedEntities = convertHolderSet(equippable.getCompoundTag("allowed_entities")); + final boolean dispensable = equippable.getBoolean("dispensable"); + final boolean swappable = equippable.getBoolean("swappable"); + final boolean damageOnHurt = equippable.getBoolean("damage_on_hurt"); + + data.set(StructuredDataKey.EQUIPPABLE, new Equippable(equipmentSlot, soundEvent, model, cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt)); + removeCustomTag(data, customData); + } + + final ByteTag glider = customData.getByteTag("glider"); + if (glider != null) { + data.set(StructuredDataKey.GLIDER, Unit.INSTANCE); + removeCustomTag(data, customData); + } + + final String tooltipStyle = customData.getString("tooltip_style"); + if (tooltipStyle != null) { + data.set(StructuredDataKey.TOOLTIP_STYLE, tooltipStyle); + removeCustomTag(data, customData); + } + + final ListTag deathProtection = customData.getListTag("death_protection", CompoundTag.class); + if (deathProtection != null) { + final Consumable1_21_2.ConsumeEffect[] effects = new Consumable1_21_2.ConsumeEffect[deathProtection.size()]; + for (int i = 0; i < deathProtection.size(); i++) { + effects[i] = convertConsumableEffect(deathProtection.get(i)); + } + data.set(StructuredDataKey.DEATH_PROTECTION, new DeathProtection(effects)); + removeCustomTag(data, customData); + } + } } diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/BlockItemPacketRewriter1_21.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/BlockItemPacketRewriter1_21.java index 96bc8554..d0e0cd3f 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/BlockItemPacketRewriter1_21.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/BlockItemPacketRewriter1_21.java @@ -29,10 +29,13 @@ import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.EnchantmentsPa import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.OpenScreenStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.PlayerRotationStorage; import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.Holder; +import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; +import com.viaversion.viaversion.api.minecraft.item.data.JukeboxPlayable; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.version.Types1_20_5; @@ -49,6 +52,7 @@ import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacke import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.IdRewriteFunction; +import com.viaversion.viaversion.util.Either; import com.viaversion.viaversion.util.SerializerVersion; import java.util.ArrayList; import java.util.List; @@ -188,6 +192,7 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe // Order is important super.handleItemToClient(connection, item); + backupInconvertibleData(item); downgradeItemData(item); if (data.has(StructuredDataKey.RARITY)) { @@ -222,6 +227,7 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe // Order is important super.handleItemToServer(connection, item); updateItemData(item); + restoreInconvertibleData(item); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData == null) { @@ -264,6 +270,81 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe } } + private void backupInconvertibleData(final Item item) { + final StructuredDataContainer data = item.dataContainer(); + final JukeboxPlayable jukeboxPlayable = data.get(StructuredDataKey.JUKEBOX_PLAYABLE); + if (jukeboxPlayable == null) { + return; + } + + final CompoundTag tag = new CompoundTag(); + if (jukeboxPlayable.song().isLeft()) { + final Holder song = jukeboxPlayable.song().left(); + if (song.hasId()) { + tag.putInt("song_id", song.id()); + } else { + final JukeboxPlayable.JukeboxSong songData = song.value(); + final Holder soundEvent = songData.soundEvent(); + if (soundEvent.hasId()) { + tag.putInt("sound", soundEvent.id()); + } else { + final SoundEvent event = soundEvent.value(); + tag.putString("identifier", event.identifier()); + if (event.fixedRange() != null) { + tag.putFloat("fixed_range", event.fixedRange()); + } + } + tag.put("description", songData.description()); + tag.putFloat("length_in_seconds", songData.lengthInSeconds()); + tag.putInt("comparator_output", songData.comparatorOutput()); + } + } else { + tag.putString("song_identifier", jukeboxPlayable.song().right()); + } + tag.putBoolean("show_in_tooltip", jukeboxPlayable.showInTooltip()); + saveTag(createCustomTag(item), tag, "jukebox_playable"); + } + + private void restoreInconvertibleData(final Item item) { + final StructuredDataContainer data = item.dataContainer(); + final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); + if (customData == null) { + return; + } + final CompoundTag tag = customData.removeUnchecked("jukebox_playable"); + if (tag == null) { + return; + } + + final Either, String> song; + if (tag.contains("song_identifier")) { + song = Either.right(tag.getString("song_identifier")); + } else { + final Holder songData; + if (tag.contains("song_id")) { + songData = Holder.of(tag.getInt("song_id")); + } else { + final Holder soundEvent; + if (tag.contains("sound")) { + soundEvent = Holder.of(tag.getInt("sound")); + } else { + final String identifier = tag.getString("identifier"); + final Float fixedRange = tag.contains("fixed_range") ? tag.getFloat("fixed_range") : null; + soundEvent = Holder.of(new SoundEvent(identifier, fixedRange)); + } + final Tag description = tag.get("description"); + final float lengthInSeconds = tag.getFloat("length_in_seconds"); + final int comparatorOutput = tag.getInt("comparator_output"); + songData = Holder.of(new JukeboxPlayable.JukeboxSong(soundEvent, description, lengthInSeconds, comparatorOutput)); + } + song = Either.left(songData); + } + + final JukeboxPlayable jukeboxPlayable = new JukeboxPlayable(song, tag.getBoolean("show_in_tooltip")); + data.set(StructuredDataKey.JUKEBOX_PLAYABLE, jukeboxPlayable); + removeCustomTag(data, customData); + } + private record PendingIdChange(int id, int mappedId, int level) { } } diff --git a/gradle.properties b/gradle.properties index 6a28f64f..f88f29d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -projectVersion=5.1.1 +projectVersion=5.1.2-SNAPSHOT # Smile emoji mcVersions=1.21.3,1.21.2,1.21.1,1.21,1.20.6,1.20.5,1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13, 1.12.2, 1.12.1, 1.12, 1.11.2, 1.11.1, 1.11, 1.10.2, 1.10.1, 1.10