Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 2600|回复: 8

[Mod开发教程] 1.13+ 中 Forge 与 Bukkit 的通信

[复制链接]
海螺螺 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
16560
钻石
性别
保密
注册时间
2015-2-24
查看详细资料
 楼主| 发表于 2019-6-14 17:29:22 | 显示全部楼层 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

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

x
本帖最后由 海螺螺 于 2020-1-27 12:58 编辑

1.13+ 中 Forge 与 Bukkit 的通信

1.12 及以下
1.13 的 Forge 已经更新几个月,变化较大,包括 Plugin Message Channel 的变化。废话在另一篇已经说得比较多了,因此直接上内容。

Forge 接收消息
代码如下,Forge 1.13 自身的一些新用法就不做介绍了。

@Mod("msgtutor")
public class MsgTutorMod {
    private static final int IDX = 233;
    private SimpleChannel channel;
    public MsgTutorMod() {
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup);
    }

    private void clientSetup(FMLClientSetupEvent event) {
        channel = NetworkRegistry.ChannelBuilder
                .named(new ResourceLocation("msgtutor", "test"))
                .networkProtocolVersion(() -> "zzzz")
                .serverAcceptedVersions(NetworkRegistry.ACCEPTVANILLA::equals)
                .clientAcceptedVersions(NetworkRegistry.ACCEPTVANILLA::equals)
                .simpleChannel();
       channel.registerMessage(IDX, String.class, this::enc, this::dec, this::proc);
    }

    private void enc(String str, PacketBuffer buffer) {
        buffer.writeBytes(str.getBytes(StandardCharsets.UTF_8));
    }

    private String dec(PacketBuffer buffer) {
        return buffer.toString(StandardCharsets.UTF_8);
    }

    private void proc(String str, Supplier<NetworkEvent.Context> supplier) {
        System.out.println(str);
        NetworkEvent.Context context = supplier.get();
        context.setPacketHandled(true);
        channel.reply("client hello", context);
    }
}

1.13 中引入了一个新的 ChannelBuilder,显然比较强。其中
  • named 这个,在另一篇帖子里也说过,1.13 中消息通道也使用了类似的 namespace:path 的格式
  • networkProtocolVersion 为网络协议版本,现在看来乱写一个是可以的,如果不能的话请通知我(
  • server/clientAcceptedVersions 望文生义,如果需要原版可以加入服务器就这么写,如果仅限 Forge 客户端的话,Predicates.not() 就可以了
  • simpleChannel 创建一个 SimpleChannel,还有一个方法创建一个事件驱动的 channel,但是限于作者水平用不来

因为种种原因我们选择了 SimpleChannel,在创建完 channel 之后,我们就可以着手处理消息了,比如注册一个消息。

使用 registerMessage 注册一种消息,其中
  • 第一个参数 index,用于区分不同的消息种类,看上去像是一个 int,但是实际上内部存储是 short,但是实际上还 index & 0xff,所以是个 unsigned byte,所以我们这个教程选择了 233 这个有趣的数字
  • 第二个是数据类型,为了方便用 String,但是这个东西的初衷应该是想让你写一个有编码解码处理方法的数据类来
  • 第三四五个参数自然就是编码解码处理的方法了,主要是操作一个 PacketBuffer,而 PacketBuffer 对 ByteBuf 包装了一下,而 ByteBuf 怎么用在另一篇帖子和这篇帖子都讲了一点

然后我们就注册了一条消息,只要服务器发过来了消息,我们就能接收到。

再看到 proc 方法,这是我们处理接收到消息的方法,输出就不说了,但是
  • context.setPacketHandled(true) 这个方法在处理完消息后需要调用一次,否则控制台会打印一条无关痛痒的警告
  • 可以看到最后我们用了 reply 来回复这条消息,你也可以用 channel.sendToServer 来向服务器发送消息

Bukkit 发送接收部分
代码如下
public final class Test extends JavaPlugin implements Listener {
    private static final int IDX = 233;
    private final String channel = "msgtutor:test";

    @Override
    public void onEnable() {
        getServer().getMessenger().registerIncomingPluginChannel(this, channel,
            (channel, player, message) ->
                        System.out.println("awsl " + read(message)));
        getServer().getMessenger().registerOutgoingPluginChannel(this, channel);
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        try {
            Class<? extends CommandSender> senderClass = player.getClass();
            Method addChannel = senderClass.getDeclaredMethod("addChannel", String.class);
            addChannel.setAccessible(true);
            addChannel.invoke(player, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Bukkit.getScheduler().runTaskLater(this,
                () -> send(player, "server hello"), 100);
    }

    private void send(Player player, String msg) {
        byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
        ByteBuf buf = Unpooled.buffer(bytes.length + 1);
        buf.writeByte(IDX);
        buf.writeBytes(bytes);
        player.sendPluginMessage(this, channel, buf.array());
    }

    private String read(byte[] array) {
       ByteBuf buf = Unpooled.wrappedBuffer(array);
       if (buf.readUnsignedByte() == IDX) {
           return buf.toString(StandardCharsets.UTF_8);
       } else throw new RuntimeException();
    }

}

在 onEnable 中
  • 我们按正常流程注册,包括一个简易的打印接收到的客户端消息的东西
  • 因为 Forge 现在要帮我们分消息种类了,所以我们要在编码消息的时候在开头读一个 byte,这也就是 read 方法里第一个 readUnsignedByte 的用处。至于为什么是 unsigned,因为 byte 的范围是 -128 - 127,而我们写的是 233
  • channel 的名称和客户端对应,且必须是 namespace:path 的格式

为演示,我们监听玩家加入游戏的事件
  • 那个看起来莫名其妙的反射一会儿再说
  • 延迟 100 tick,也就是 5 秒发送

发送的逻辑在 send 方法里
  • 如上文所述,Forge 帮我们分了数据类型,所以我们要先写一个我们的 233 进去
  • Unpooled.buffer 相关的是 netty buffer 的操作,如果不知道咋用,应该看看帖子开头的那个帖子
  • 至于为什么要 + 1,因为 byte 的长度是 1

现在来到了最后一个问题,那个莫名其妙的反射是啥?
在 Forge 1.13 以前,Forge 客户端在加入服务器之前,会向服务器发送 register 包来注册插件消息通道,而 1.13 和之后的版本却不这么做了,而服务器在没有收到 register 包之前,调用的 sendPluginMessage 方法都不会真正发送出去。详情在这个 issue 里,cpw 表示关我 Forge 什么事,找 Spigot 去。
我们没有办法,毕竟我们不是 cpw 或者 Lex,所以我们只能自己动手丰衣足食,也就是假装我们收到了 register 包,也就是那个反射。
至于反射很丑,而由于作者懒的原因,没有研究用客户端发 register 包的方法。不过也好,这样子的话,想发包就不用像老版本那样等那几秒钟之后再发了。这也是为什么老版本需要等几秒才能发包的原因。

file_1560506922000.jpg


来自群组: PluginsCDTribe

评分

参与人数 11人气 +24 金粒 +195 收起 理由
wodewode + 2 + 20 MCBBS有你更精彩~
利姆露酱 + 1 MCBBS有你更精彩~
404565377 + 1 + 20 MCBBS有你更精彩~
+ 2 MCBBS有你更精彩~
hemp + 2 + 40 MCBBS有你更精彩~
gooding300 + 3 + 25 MCBBS有你更精彩~
白羊羊 + 3 &amp;lt;font style=&amp;quot;vertical-align: inh
森林蝙蝠 + 2 + 40 吃海螺
skyliye + 2 神乎其技,不服不行!
ItIsEnderman + 3 + 50 MCBBS有你更精彩~
+ 3 神乎其技,不服不行!

查看全部评分

skyliye 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
14199
钻石
性别
保密
注册时间
2016-11-24
查看详细资料
发表于 2019-6-14 18:15:27 | 显示全部楼层
我在想,会不会出来1.14的Bukkit+mod的服务端,其实蛮期待的
如果能同时兼容Forgemod+Fabricmod+bukkit+sponge插件岂不美哉(有生之年系列)

评分

参与人数 1金粒 +5 收起 理由
帕帕武 + 5 这种东西多半没有吧...就像白天和夜晚一起.

查看全部评分

回复

使用道具 举报

森林蝙蝠 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
9431
钻石
性别
保密
注册时间
2016-6-16
查看详细资料
发表于 2019-6-14 19:23:16 | 显示全部楼层
skyliye 发表于 2019-6-14 18:15
我在想,会不会出来1.14的Bukkit+mod的服务端,其实蛮期待的
如果能同时兼容Forgemod+Fabricmod+bukkit+spo ...

fabric+bukkit目前开发者在做。
回复

使用道具 举报

MC_Huo_Shen 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
143
钻石
性别
保密
注册时间
2019-6-9
查看详细资料
发表于 2019-6-15 13:56:47 | 显示全部楼层
看不懂554654455454
回复

使用道具 举报

老油人 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
200
钻石
性别
保密
注册时间
2019-6-13
查看详细资料
发表于 2019-6-16 22:59:06 来自手机 | 显示全部楼层
23333333333
回复

使用道具 举报

简爱爱 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
565
钻石
性别
保密
注册时间
2019-6-8
查看详细资料
头像被屏蔽
发表于 2019-6-16 23:48:56 来自手机 | 显示全部楼层
互通又有什么用呢?大佬的想法我不懂。
回复

使用道具 举报

slyz 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
151
钻石
性别
保密
注册时间
2019-5-26
查看详细资料
发表于 2019-6-17 11:00:57 来自手机 | 显示全部楼层
6666666666
回复

使用道具 举报

404565377 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
1008
钻石
性别
保密
注册时间
2018-8-20
查看详细资料
发表于 2019-6-18 16:40:22 | 显示全部楼层
简爱爱 发表于 2019-6-16 23:48
互通又有什么用呢?大佬的想法我不懂。

和客户端通信,就不用在服务端装mod了
回复

使用道具 举报

Deeplypie 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
24
钻石
性别
保密
注册时间
2018-7-14
查看详细资料
发表于 2019-6-20 00:00:27 | 显示全部楼层
感谢服主的分享!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2020-7-14 23:29 , Processed in 0.066235 second(s), Total 19, Slave 18 queries, Release: Build.2020.07.08.1452, Gzip On, Redis On.

"Minecraft"以及"我的世界"为Mojang Synergies AB的商标 本站与Mojang以及微软公司没有从属关系

© 2010-2020 我的世界中文论坛 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载

快速回复 返回顶部 返回列表