切换风格

默认晚霞 雪山 粉色心情 伦敦 花卉 绿野仙踪 加州 白云 星空 薰衣草 城市 简约黑色 龙珠
回复 1

4

主题

22

帖子

454

积分

注册会员

Rank: 2

积分
454
人气
7 点
钻石粒
644 粒
贡献
1 点
论坛币
101 个
爱心
0 点

论坛注册会员

[1.7.10]子菜单问题的解决方案以及原因分析[复制链接]
发表于 2020-1-5 15:14:06 | 显示全部楼层 |阅读模式

请注册论坛会员,已便查看高清图片!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 star丶星 于 2020-1-12 12:29 编辑

带火早上中午晚上半夜好嗷,我是某水锅头号菜鸡星星,最近几天脚本圈进入了一个全新的时代,许多大佬开始了对箱子GUI的菜单的研究,不过在研究的过程中,带火都遇到了一个问题:在菜单中打开另一个菜单,打开的菜单就会没有任何效果,里面的按钮也可以拿出来,经过几天的研究,我终于找到了解决子菜单问题的方案以及问题的具体产生原因,接下来P话不多说我萌直入主题。

产生原因:
首先带火都知道,菜单的原理就是监听灰灰给提供的PlayerClickContainerEvent,而这个事件是由Container类的slotClick方法触发的,那么子菜单没有效果,极有可能和这个事件出了问题有关,经过我的实验,我确定了子菜单没有效果,就是因为这个事件没被触发,这个事件没有被触发,那么必然是因为slotClick方法没有被触发。
然后带火都知道,一个容器的slotClick方法是由net.minecraft.network.NetHandlerPlayServer类的processClickWindow触发的(这TM谁知道),先上代码
net.minecraft.network.NetHandlerPlayServer.processClickWindow:
  1. public void processClickWindow(C0EPacketClickWindow p_147351_1_)
  2.   {
  3.     this.playerEntity.func_143004_u();
  4.     if ((this.playerEntity.openContainer.windowId == p_147351_1_.func_149548_c()) && (this.playerEntity.openContainer.isPlayerNotUsingContainer(this.playerEntity)))
  5.     {
  6.       ItemStack itemstack = this.playerEntity.openContainer.slotClick(p_147351_1_.func_149544_d(), p_147351_1_.func_149543_e(), p_147351_1_.func_149542_h(), this.playerEntity);
  7.       if (ItemStack.areItemStacksEqual(p_147351_1_.func_149546_g(), itemstack))
  8.       {
  9.         this.playerEntity.playerNetServerHandler.sendPacket(new S32PacketConfirmTransaction(p_147351_1_.func_149548_c(), p_147351_1_.func_149547_f(), true));
  10.         this.playerEntity.isChangingQuantityOnly = true;
  11.         this.playerEntity.openContainer.detectAndSendChanges();
  12.         this.playerEntity.updateHeldItem();
  13.         this.playerEntity.isChangingQuantityOnly = false;
  14.       }
  15.       else
  16.       {
  17.         this.field_147372_n.addKey(this.playerEntity.openContainer.windowId, Short.valueOf(p_147351_1_.func_149547_f()));
  18.         this.playerEntity.playerNetServerHandler.sendPacket(new S32PacketConfirmTransaction(p_147351_1_.func_149548_c(), p_147351_1_.func_149547_f(), false));
  19.         this.playerEntity.openContainer.setPlayerIsPresent(this.playerEntity, false);
  20.         ArrayList arraylist = new ArrayList();
  21.         for (int i = 0; i < this.playerEntity.openContainer.inventorySlots.size(); i++) {
  22.           arraylist.add(((Slot)this.playerEntity.openContainer.inventorySlots.get(i)).getStack());
  23.         }
  24.         this.playerEntity.sendContainerAndContentsToPlayer(this.playerEntity.openContainer, arraylist);
  25.       }
  26.     }
  27.   }
复制代码
我们触发slotClick的位置
  1. if ((this.playerEntity.openContainer.windowId == p_147351_1_.func_149548_c()) && (this.playerEntity.openContainer.isPlayerNotUsingContainer(this.playerEntity)))
  2.     {
  3.       ItemStack itemstack = this.playerEntity.openContainer.slotClick(p_147351_1_.func_149544_d(), p_147351_1_.func_149543_e(), p_147351_1_.func_149542_h(), this.playerEntity);
  4. }
复制代码
从这几行我们可以看见,slotClick的触发是被if判断包起来的,那么如果if判断的东西为false,这个方法必然不会被触发,那么我们看if判断的条件,第一条就是玩家当前打开窗口的id和发包的窗口的ID一样,经过实验我发现这条是true没有问题,那么问题就出在第二个条件,经过实验,第二个条件在打开子菜单之前一直是true,然而打开子菜单的一瞬间,第二个条件变成了false,于是就不会触发slotClick方法。

于是问题逐渐明了了,不过这不是我们的目的,我们还要继续深入。
那么,为什么在打开子菜单之后,第二个条件就会被判断为false呢?我们去研究一下第二个条件中涉及的方法,直接上代码
net.minecraft.inventory.Container.isPlayerNotUsingContainer:
  1.   public boolean isPlayerNotUsingContainer(EntityPlayer p_75129_1_)
  2.   {
  3.     return !this.playerList.contains(p_75129_1_);
  4.   }
复制代码
可以看见这个方法就是判断容器的playerList有没有玩家对象,那么playerList是由什么处理的呢?
net.minecraft.inventory.Container.setPlayerIsPresent:
  1. public void setPlayerIsPresent(EntityPlayer p_75128_1_, boolean p_75128_2_)
  2.   {
  3.     if (p_75128_2_) {
  4.       this.playerList.remove(p_75128_1_);
  5.     } else {
  6.       this.playerList.add(p_75128_1_);
  7.     }
  8.   }
复制代码

如果你刚才够细心,你可以发现processClickWindow里有调用了这个方法,我们看刚才剩下的代码

  1.       if (ItemStack.areItemStacksEqual(p_147351_1_.func_149546_g(), itemstack))
  2.       {
  3.         this.playerEntity.playerNetServerHandler.sendPacket(new S32PacketConfirmTransaction(p_147351_1_.func_149548_c(), p_147351_1_.func_149547_f(), true));
  4.         this.playerEntity.isChangingQuantityOnly = true;
  5.         this.playerEntity.openContainer.detectAndSendChanges();
  6.         this.playerEntity.updateHeldItem();
  7.         this.playerEntity.isChangingQuantityOnly = false;
  8.       }
  9.       else
  10.       {
  11.         this.field_147372_n.addKey(this.playerEntity.openContainer.windowId, Short.valueOf(p_147351_1_.func_149547_f()));
  12.         this.playerEntity.playerNetServerHandler.sendPacket(new S32PacketConfirmTransaction(p_147351_1_.func_149548_c(), p_147351_1_.func_149547_f(), false));
  13.         <font color="#ff0000">this.playerEntity.openContainer.setPlayerIsPresent(this.playerEntity, false)</font>;
  14.         ArrayList arraylist = new ArrayList();
  15.         for (int i = 0; i < this.playerEntity.openContainer.inventorySlots.size(); i++) {
  16.           arraylist.add(((Slot)this.playerEntity.openContainer.inventorySlots.get(i)).getStack());
  17.         }
  18.         this.playerEntity.sendContainerAndContentsToPlayer(this.playerEntity.openContainer, arraylist);
  19.       }

复制代码

由此我们可以看出,如果slotClick方法返回的物品和实际物品不一样的话,就会触发else里的内容,而如果你打开子菜单,格子里的物品就会出现变化,于是子菜单问题就出现了。

解决方案:
刚才我们分析出,问题出现的原因是isPlayerNotUsingContainer返回了false,所以我们只需要用setPlayerIsPresent改一下就好了,由于过于简单,代码就不上了,感谢支持

版权声明

你不能转载此贴

评分

参与人数 1人气 +2 钻石粒 +40 收起 理由
万年萌新小浩 + 2 + 40 星星牛逼!!!!(破音)

查看全部评分

我是憨批
回复

使用道具 举报

0

主题

19

帖子

126

积分

论坛萌新

Rank: 1

积分
126
人气
0 点
钻石粒
107 粒
贡献
0 点
论坛币
33 个
爱心
0 点

论坛注册会员

发表于 2020-3-16 05:03:14 | 显示全部楼层
简单的话,发上来呗= =
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|自定义NPC脚本中文论坛 ( 蜀ICP备17005795号-3 )

GMT+8, 2020-9-19 03:43 , Processed in 0.074250 second(s), 27 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

返回顶部