diff --git a/leaf-server/minecraft-patches/features/0078-Hide-specified-item-components.patch b/leaf-server/minecraft-patches/features/0078-Hide-specified-item-components.patch new file mode 100644 index 000000000..3f166fce4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0078-Hide-specified-item-components.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheFloodDragon <1610105206@qq.com> +Date: Fri, 7 Feb 2025 18:41:55 +0800 +Subject: [PATCH] Hide specified item components + + +diff --git a/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java b/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +index 828fbe03e7beb860cd0816c7ac8adbffe196533b..f602c4c55483a189f973929b982f1834ca7e9952 100644 +--- a/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +@@ -53,8 +53,8 @@ public class ClientboundContainerSetContentPacket implements Packet> hiddenTypes = List.of(); + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Controls whether specified component information would be sent to clients. + It may break resource packs and mods that rely on the information. + Also, it can avoid some frequent client animations. + Attention: This is not same as Paper's item-obfuscation, we only hide specified component information from player's inventory.""", + """ + 控制哪些物品组件信息会被发送至客户端. + 可能会导致依赖物品组件的资源包/模组无法正常工作. + 可以避免一些客户端动画效果. + 注意: 此项与 Paper 的 item-obfuscation 不同, 我们只从玩家背包中隐藏物品指定的组件信息."""); + List list = config.getList(getBasePath() + ".hidden-types", new ArrayList<>(), config.pickStringRegionBased(""" + Which type of components will be hidden from clients. + It needs a component type list, incorrect things will not work.""", + """ + 被隐藏的物品组件类型列表. + 该配置项接受一个物品组件列表, 格式不正确将不会启用.""")); + enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased( + "If enabled, specified item component information from player's inventory will be hided.", + "启用后, 玩家背包内物品的指定组件信息会被隐藏." + )); + + final List> types = new ArrayList<>(list.size()); + + for (String componentType : list) { + BuiltInRegistries.DATA_COMPONENT_TYPE.get(ResourceLocation.parse(componentType)).ifPresentOrElse( + optional -> types.add(optional.value()), + () -> LeafConfig.LOGGER.warn("Unknown component type: {}", componentType) + ); + } + + hiddenTypes = types; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/item/ItemStackStripper.java b/leaf-server/src/main/java/org/dreeam/leaf/util/item/ItemStackStripper.java new file mode 100644 index 000000000..9e70d46e3 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/item/ItemStackStripper.java @@ -0,0 +1,80 @@ +package org.dreeam.leaf.util.item; + +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.world.item.ItemStack; +import org.dreeam.leaf.config.modules.gameplay.HideItemComponent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * ItemStackStripper + * + * @author TheFloodDragon + * @since 2025/2/4 19:04 + */ +public class ItemStackStripper { + + public static ItemStack strip(final ItemStack itemStack, final boolean copy) { + if (!HideItemComponent.enabled || itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty()) + return itemStack; + + final ItemStack copied = copy ? itemStack.copy() : itemStack; + + // Remove specified types + for (DataComponentType type : HideItemComponent.hiddenTypes) { + // Only remove, no others + copied.remove(type); + } + + return copied; + } + + public static List strip(final List itemStacks, final boolean copy) { + if (!HideItemComponent.enabled) return itemStacks; + + final List copiedItems = new ArrayList<>(); + + for (ItemStack itemStack : itemStacks) { + if (itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty()) { + copiedItems.add(itemStack); + continue; + } + + final ItemStack copied = copy ? itemStack.copy() : itemStack; + + // Remove specified types + for (DataComponentType type : HideItemComponent.hiddenTypes) { + // Only remove, no others + copied.remove(type); + } + + copiedItems.add(copied); + } + + return copiedItems; + } + + /** + * Check if two ItemStacks are the same after stripping components + */ + public static boolean matchesStripped(ItemStack left, ItemStack right) { + if (HideItemComponent.enabled) { + return left == right || ( + left.is(right.getItem()) && left.getCount() == right.getCount() && + (left.isEmpty() && right.isEmpty() || Objects.equals(strip(left.getComponents()), strip(right.getComponents()))) + ); + } + + return ItemStack.matches(left, right); + } + + /** + * @return a new DataComponentMap with all hidden components removed + */ + private static DataComponentMap strip(final DataComponentMap map) { + return map.filter(c -> !HideItemComponent.hiddenTypes.contains(c)); + } +}