/* * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion * Copyright (C) 2016-2024 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viaversion.protocols.protocol1_20_2to1_20.rewriter; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.NumberTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.metadata.ChunkPosition; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.protocols.protocol1_19_4to1_19_3.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.protocol1_19_4to1_19_3.rewriter.RecipeRewriter1_19_4; import com.viaversion.viaversion.protocols.protocol1_20_2to1_20.Protocol1_20_2To1_20; import com.viaversion.viaversion.protocols.protocol1_20_2to1_20.packet.ServerboundPackets1_20_2; import com.viaversion.viaversion.protocols.protocol1_20_2to1_20.util.PotionEffects; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ItemRewriter; import com.viaversion.viaversion.util.MathUtil; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockItemPacketRewriter1_20_2 extends ItemRewriter { public BlockItemPacketRewriter1_20_2(final Protocol1_20_2To1_20 protocol) { super(protocol, Type.ITEM1_13_2, Type.ITEM1_13_2_ARRAY); } @Override public void registerPackets() { final BlockRewriter blockRewriter = BlockRewriter.for1_14(protocol); blockRewriter.registerBlockAction(ClientboundPackets1_19_4.BLOCK_ACTION); blockRewriter.registerBlockChange(ClientboundPackets1_19_4.BLOCK_CHANGE); blockRewriter.registerVarLongMultiBlockChange1_20(ClientboundPackets1_19_4.MULTI_BLOCK_CHANGE); blockRewriter.registerEffect(ClientboundPackets1_19_4.EFFECT, 1010, 2001); protocol.registerServerbound(ServerboundPackets1_20_2.SET_BEACON_EFFECT, wrapper -> { // Effects start at 1 before 1.20.2 if (wrapper.passthrough(Type.BOOLEAN)) { // Primary effect wrapper.write(Type.VAR_INT, wrapper.read(Type.VAR_INT) + 1); } if (wrapper.passthrough(Type.BOOLEAN)) { // Secondary effect wrapper.write(Type.VAR_INT, wrapper.read(Type.VAR_INT) + 1); } }); protocol.registerClientbound(ClientboundPackets1_19_4.UNLOAD_CHUNK, wrapper -> { final int x = wrapper.read(Type.INT); final int z = wrapper.read(Type.INT); wrapper.write(Type.CHUNK_POSITION, new ChunkPosition(x, z)); }); protocol.registerClientbound(ClientboundPackets1_19_4.NBT_QUERY, wrapper -> { wrapper.passthrough(Type.VAR_INT); // Transaction id wrapper.write(Type.COMPOUND_TAG, wrapper.read(Type.NAMED_COMPOUND_TAG)); }); protocol.registerClientbound(ClientboundPackets1_19_4.BLOCK_ENTITY_DATA, wrapper -> { wrapper.passthrough(Type.POSITION1_14); // Position wrapper.passthrough(Type.VAR_INT); // Type wrapper.write(Type.COMPOUND_TAG, handleBlockEntity(wrapper.read(Type.NAMED_COMPOUND_TAG))); }); protocol.registerClientbound(ClientboundPackets1_19_4.CHUNK_DATA, wrapper -> { final EntityTracker tracker = protocol.getEntityRewriter().tracker(wrapper.user()); final Type chunkType = new ChunkType1_18(tracker.currentWorldSectionHeight(), MathUtil.ceilLog2(protocol.getMappingData().getBlockStateMappings().size()), MathUtil.ceilLog2(tracker.biomesSent())); final Chunk chunk = wrapper.read(chunkType); final Type newChunkType = new ChunkType1_20_2(tracker.currentWorldSectionHeight(), MathUtil.ceilLog2(protocol.getMappingData().getBlockStateMappings().mappedSize()), MathUtil.ceilLog2(tracker.biomesSent())); wrapper.write(newChunkType, chunk); for (final ChunkSection section : chunk.getSections()) { final DataPalette blockPalette = section.palette(PaletteType.BLOCKS); for (int i = 0; i < blockPalette.size(); i++) { final int id = blockPalette.idByIndex(i); blockPalette.setIdByIndex(i, protocol.getMappingData().getNewBlockStateId(id)); } } for (final BlockEntity blockEntity : chunk.blockEntities()) { handleBlockEntity(blockEntity.tag()); } }); // Replace the NBT type everywhere protocol.registerClientbound(ClientboundPackets1_19_4.WINDOW_ITEMS, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // Window id map(Type.VAR_INT); // State id handler(wrapper -> { final Item[] items = wrapper.read(Type.ITEM1_13_2_ARRAY); for (final Item item : items) { handleItemToClient(item); } wrapper.write(Type.ITEM1_20_2_ARRAY, items); wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); // Carried item }); } }); protocol.registerClientbound(ClientboundPackets1_19_4.SET_SLOT, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // Window id map(Type.VAR_INT); // State id map(Type.SHORT); // Slot id handler(wrapper -> wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2)))); } }); protocol.registerClientbound(ClientboundPackets1_19_4.ADVANCEMENTS, wrapper -> { wrapper.passthrough(Type.BOOLEAN); // Reset/clear final int size = wrapper.passthrough(Type.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Type.STRING); // Identifier // Parent if (wrapper.passthrough(Type.BOOLEAN)) wrapper.passthrough(Type.STRING); // Display data if (wrapper.passthrough(Type.BOOLEAN)) { wrapper.passthrough(Type.COMPONENT); // Title wrapper.passthrough(Type.COMPONENT); // Description wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); // Icon wrapper.passthrough(Type.VAR_INT); // Frame type final int flags = wrapper.passthrough(Type.INT); // Flags if ((flags & 1) != 0) { wrapper.passthrough(Type.STRING); // Background texture } wrapper.passthrough(Type.FLOAT); // X wrapper.passthrough(Type.FLOAT); // Y } // Remove criterion triggers wrapper.read(Type.STRING_ARRAY); // Criteria final int requirements = wrapper.passthrough(Type.VAR_INT); for (int array = 0; array < requirements; array++) { wrapper.passthrough(Type.STRING_ARRAY); } wrapper.passthrough(Type.BOOLEAN); // Send telemetry } }); protocol.registerClientbound(ClientboundPackets1_19_4.ENTITY_EQUIPMENT, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Entity ID handler(wrapper -> { byte slot; do { slot = wrapper.passthrough(Type.BYTE); wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); } while ((slot & 0xFFFFFF80) != 0); }); } }); protocol.registerServerbound(ServerboundPackets1_20_2.CLICK_WINDOW, new PacketHandlers() { @Override public void register() { map(Type.UNSIGNED_BYTE); // Window Id map(Type.VAR_INT); // State id map(Type.SHORT); // Slot map(Type.BYTE); // Button map(Type.VAR_INT); // Mode handler(wrapper -> { // Affected items final int length = wrapper.passthrough(Type.VAR_INT); for (int i = 0; i < length; i++) { wrapper.passthrough(Type.SHORT); // Slot wrapper.write(Type.ITEM1_13_2, handleItemToServer(wrapper.read(Type.ITEM1_20_2))); } // Carried item wrapper.write(Type.ITEM1_13_2, handleItemToServer(wrapper.read(Type.ITEM1_20_2))); }); } }); protocol.registerClientbound(ClientboundPackets1_19_4.TRADE_LIST, wrapper -> { wrapper.passthrough(Type.VAR_INT); // Container id final int size = wrapper.passthrough(Type.VAR_INT); for (int i = 0; i < size; i++) { wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); // Input wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); // Output wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); // Second Item wrapper.passthrough(Type.BOOLEAN); // Trade disabled wrapper.passthrough(Type.INT); // Number of tools uses wrapper.passthrough(Type.INT); // Maximum number of trade uses wrapper.passthrough(Type.INT); // XP wrapper.passthrough(Type.INT); // Special price wrapper.passthrough(Type.FLOAT); // Price multiplier wrapper.passthrough(Type.INT); // Demand } }); protocol.registerServerbound(ServerboundPackets1_20_2.CREATIVE_INVENTORY_ACTION, new PacketHandlers() { @Override public void register() { map(Type.SHORT); // 0 - Slot handler(wrapper -> wrapper.write(Type.ITEM1_13_2, handleItemToServer(wrapper.read(Type.ITEM1_20_2)))); // 1 - Clicked Item } }); protocol.registerClientbound(ClientboundPackets1_19_4.SPAWN_PARTICLE, new PacketHandlers() { @Override public void register() { map(Type.VAR_INT); // 0 - Particle ID map(Type.BOOLEAN); // 1 - Long Distance map(Type.DOUBLE); // 2 - X map(Type.DOUBLE); // 3 - Y map(Type.DOUBLE); // 4 - Z map(Type.FLOAT); // 5 - Offset X map(Type.FLOAT); // 6 - Offset Y map(Type.FLOAT); // 7 - Offset Z map(Type.FLOAT); // 8 - Particle Data map(Type.INT); // 9 - Particle Count handler(wrapper -> { final int id = wrapper.get(Type.VAR_INT, 0); final ParticleMappings mappings = Protocol1_20_2To1_20.MAPPINGS.getParticleMappings(); if (mappings.isBlockParticle(id)) { final int data = wrapper.read(Type.VAR_INT); wrapper.write(Type.VAR_INT, protocol.getMappingData().getNewBlockStateId(data)); } else if (mappings.isItemParticle(id)) { wrapper.write(Type.ITEM1_20_2, handleItemToClient(wrapper.read(Type.ITEM1_13_2))); } }); } }); new RecipeRewriter1_19_4(protocol) { @Override public void handleCraftingShapeless(final PacketWrapper wrapper) throws Exception { wrapper.passthrough(Type.STRING); // Group wrapper.passthrough(Type.VAR_INT); // Crafting book category handleIngredients(wrapper); final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); } @Override public void handleSmelting(final PacketWrapper wrapper) throws Exception { wrapper.passthrough(Type.STRING); // Group wrapper.passthrough(Type.VAR_INT); // Crafting book category handleIngredient(wrapper); final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); wrapper.passthrough(Type.FLOAT); // EXP wrapper.passthrough(Type.VAR_INT); // Cooking time } @Override public void handleCraftingShaped(final PacketWrapper wrapper) throws Exception { final int ingredients = wrapper.passthrough(Type.VAR_INT) * wrapper.passthrough(Type.VAR_INT); wrapper.passthrough(Type.STRING); // Group wrapper.passthrough(Type.VAR_INT); // Crafting book category for (int i = 0; i < ingredients; i++) { handleIngredient(wrapper); } final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); wrapper.passthrough(Type.BOOLEAN); // Show notification } @Override public void handleStonecutting(final PacketWrapper wrapper) throws Exception { wrapper.passthrough(Type.STRING); // Group handleIngredient(wrapper); final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); } @Override public void handleSmithing(final PacketWrapper wrapper) throws Exception { handleIngredient(wrapper); // Base handleIngredient(wrapper); // Addition final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); } @Override public void handleSmithingTransform(final PacketWrapper wrapper) throws Exception { handleIngredient(wrapper); // Template handleIngredient(wrapper); // Base handleIngredient(wrapper); // Additions final Item result = wrapper.read(itemType()); rewrite(result); wrapper.write(Type.ITEM1_20_2, result); } @Override protected void handleIngredient(final PacketWrapper wrapper) throws Exception { final Item[] items = wrapper.read(itemArrayType()); wrapper.write(Type.ITEM1_20_2_ARRAY, items); for (final Item item : items) { rewrite(item); } } }.register(ClientboundPackets1_19_4.DECLARE_RECIPES); } @Override public @Nullable Item handleItemToClient(@Nullable final Item item) { if (item == null) { return null; } if (item.tag() != null) { to1_20_2Effects(item); } return super.handleItemToClient(item); } @Override public @Nullable Item handleItemToServer(@Nullable final Item item) { if (item == null) { return null; } if (item.tag() != null) { to1_20_1Effects(item); } return super.handleItemToServer(item); } public static void to1_20_2Effects(final Item item) { final Tag customPotionEffectsTag = item.tag().remove("CustomPotionEffects"); if (customPotionEffectsTag instanceof ListTag) { final ListTag effectsTag = (ListTag) customPotionEffectsTag; item.tag().put("custom_potion_effects", customPotionEffectsTag); for (final Tag tag : effectsTag) { if (!(tag instanceof CompoundTag)) { continue; } final CompoundTag effectTag = (CompoundTag) tag; final Tag idTag = effectTag.remove("Id"); if (idTag instanceof NumberTag) { final String key = PotionEffects.idToKey(((NumberTag) idTag).asInt()); if (key != null) { effectTag.put("id", new StringTag(key)); } } renameTag(effectTag, "Amplifier", "amplifier"); renameTag(effectTag, "Duration", "duration"); renameTag(effectTag, "Ambient", "ambient"); renameTag(effectTag, "ShowParticles", "show_particles"); renameTag(effectTag, "ShowIcon", "show_icon"); renameTag(effectTag, "HiddenEffect", "hidden_effect"); renameTag(effectTag, "FactorCalculationData", "factor_calculation_data"); } } } public static void to1_20_1Effects(final Item item) { final Tag customPotionEffectsTag = item.tag().remove("custom_potion_effects"); if (customPotionEffectsTag instanceof ListTag) { final ListTag effectsTag = (ListTag) customPotionEffectsTag; item.tag().put("CustomPotionEffects", effectsTag); for (final Tag tag : effectsTag) { if (!(tag instanceof CompoundTag)) { continue; } final CompoundTag effectTag = (CompoundTag) tag; final Tag idTag = effectTag.remove("id"); if (idTag instanceof StringTag) { final int id = PotionEffects.keyToId(((StringTag) idTag).getValue()); effectTag.put("Id", new IntTag(id)); } renameTag(effectTag, "amplifier", "Amplifier"); renameTag(effectTag, "duration", "Duration"); renameTag(effectTag, "ambient", "Ambient"); renameTag(effectTag, "show_particles", "ShowParticles"); renameTag(effectTag, "show_icon", "ShowIcon"); renameTag(effectTag, "hidden_effect", "HiddenEffect"); renameTag(effectTag, "factor_calculation_data", "FactorCalculationData"); } } } private static void renameTag(final CompoundTag tag, final String entryName, final String toEntryName) { final Tag entry = tag.remove(entryName); if (entry != null) { tag.put(toEntryName, entry); } } private @Nullable CompoundTag handleBlockEntity(@Nullable final CompoundTag tag) { if (tag == null) { return null; } final Tag primaryEffect = tag.remove("Primary"); if (primaryEffect instanceof NumberTag && ((NumberTag) primaryEffect).asInt() != 0) { tag.put("primary_effect", new StringTag(PotionEffects.idToKeyOrLuck(((NumberTag) primaryEffect).asInt()))); } final Tag secondaryEffect = tag.remove("Secondary"); if (secondaryEffect instanceof NumberTag && ((NumberTag) secondaryEffect).asInt() != 0) { tag.put("secondary_effect", new StringTag(PotionEffects.idToKeyOrLuck(((NumberTag) secondaryEffect).asInt()))); } return tag; } }