Luohuayu / CatServer

高性能和高兼容性的1.12.2/1.16.5/1.18.2版本Forge+Bukkit+Spigot服务端 (A high performance and high compatibility 1.12.2/1.16.5/1.18.2 version Forge+Bukkit+Spigot server)
https://catmc.org
GNU Lesser General Public License v3.0
1.98k stars 211 forks source link

修复1.18.2版本导致拔刀剑2物品NBT(ForgeCaps)丢失和发射器发射桶装物品不消耗的BUG #834

Closed CalenXwX closed 9 months ago

CalenXwX commented 10 months ago
  1. ForgeCaps序列化问题(与拔刀剑相关 issues #832 ) 前些日子开了个服务器,玩拔刀剑的时候发现,当玩家打开背包,将背包中的拔刀剑取出后再放回,这把刀有概率变成一把满耐久的“无名”(最基础最菜的那个XwX),如果是放入背包界面4格的合成槽,则几乎必定导致刀被重置为满耐久的“无名”。 经过分析发现,CatServer 在 CatForgeItemCap 类的 setItemCap(...) 方法中将 Forge 的 ItemStack 转换为 Bukkit 的 ItemStack 时,丢失了ForgeCaps这一NBT标签。

参考net.minecraft.world.item.ItemStack类的 copy() 方法:

...
            ItemStack itemstack = new ItemStack(this.getItem(), this.count, this.serializeCaps());
...

在创建新ItemStack对象时,调用 this.serializeCaps() 获取了带有ForgeCaps的完整NBT,而在ItemStack的serializeCaps()方法(继承自 net.minecraftforge.common.capabilities.CapabilityProvider )中,

            CapabilityDispatcher disp = this.getCapabilities();
            return disp != null ? disp.serializeNBT() : null;

使用 getCapabilities() 方法获取到当前ItemStack的capabilities。 在getCapabilities()方法中,若当前ItemStack未被初始化,则下调用了 this.doGatherCapabilities 方法生成 this.capabilities 属性: this.capabilities = ForgeEventFactory.gatherCapabilities(this.baseClass, this.getProvider(), parent); CatServer-1.18.2-6c3f5965版本中,CatForgeItemCap::setItemCap方法直接读取 nmsItemStackcapabilities 属性:

...
        if (nmsItemStack != null && nmsItemStack.capabilities != null) {
            CompoundTag capNBT = nmsItemStack.capabilities.serializeNBT();
...

若nmsItemStack未被初始化,则获取到的capabilities为null,会导致ForgeCaps无法被写入bukkitItemStack。 当玩家打开背包、取出物品并放回时,在某些情况下会调用AbstractContainerMenu类中的m_150430_方法(doClick),在执行其中CatServer插入的代码 org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack3); 时触发这一问题,并在随后向客户端发送更新整个背包所有物品的数据包,导致玩家拿起的物品的所有ForgeCaps丢失,拔刀剑的属性被重置,变为ForgeCaps中多个关键Tag为空的“无名”。

  1. 设置ItemStack物品问题(与发射器相关) 问题描述正如Issues #816 ,在发射器内放置桶装物品(水桶、岩浆桶、细雪桶等,以下都叫水桶好了),向发射器提供红石脉冲后,桶中水被放出,但不会变成空桶。 经分析,该问题是net.minecraft.world.item.ItemStack中插入的setItem(...)方法导致的。 发射器发射物品时,调用net.minecraft.world.level.block.DispenserBlock类中的dispenseFrom方法(m5824),其中 DispenseItemBehavior dispenseitembehavior = this.m_7216_(itemstack); 获取了水桶的DispenseItemBehavior,并在 dispenserblockentity.m_6836_(i, dispenseitembehavior.m_6115_(blocksourceimpl, itemstack)); 执行了DefaultDispenseItemBehavior类的的dispense(...)方法(m6115),在dispense(...)方法中 ItemStack itemstack = this.m_7498_(p_123391_, p_123392_); 调用了水桶的DispenseItemBehavior类(net.minecraft.core.dispenser.DispenseItemBehavior$16)的m7498(...)方法(execute),执行此方法时出现了问题。 DispenseItemBehavior$16定义在DispenseItemBehavior.java.patch文件的第350行起的部分。 其中第394行 + p_123562_.setItem(Items.f_42446_); 调用CatServer在ItemStack类中插入的setItem方法修改了ItemStack中的物品
    +   // CraftBukkit start
    +   @Deprecated
    +   public void setItem(Item item) {
    +      this.f_41589_ = item;
    +   }
    +   // CraftBukkit end

    此方法只修改了this.item属性(f41589),没有修改this.delegate属性,此水桶仍可放出水。 参考ItemStack类的构造方法: this.delegate = p_41604_ == null ? null : p_41604_.m_5456_().delegate; 按照ItemLike的delegate属性设置了ItemStack的delegate属性。 ItemStack类的getItem()方法: return !this.f_41591_ && this.delegate != null ? (Item)this.delegate.get() : Items.f_41852_; 获取的是delegate的物品。

Kotori0629 commented 9 months ago

很棒!看起来这修复了一个历史性遗留问题。

ItemStack#setItem方法中delegate赋值需要换行以保持代码可读性 这看起来可以被合并。