海螺螺
本帖最后由 754503921 于 2019-6-14 17:31 编辑

Forge | LiteLoader 与 Bukkit / Sponge 之间的通信
—— PluginMessengeChannel 与 FMLNetworkEvent
在实际开发中,如果你有一些天才的设想,比如借助 Forge 让你的插件服务器变得更加有特色,但是苦于无法进行数据传输的话,那么现在你就可以学习如何让 Bukkit / Sponge 和 Forge 之间传输信息了。
此教程适用于 1.7.10-1.12 (已测试) 1.13 可以看这里,1.7.10就把包名改成 cpw 那个就行,Bukkit的插件全版本通用。

概述
Bukkit / Sponge 与 Forge 通信的原理为:
服务端发送 PluginMessage 到 Forge 客户端,客户端使用 FMLNetworkEvent.ClientCustomPacketEvent 接受处理信息。
Forge 使用 FMLEventChannel 将 FMLNetworkPacket 发送至 Bukkit,Bukkit 服务器使用 PluginMessageListener 接受处理消息,Sponge 使用注册的频道添加的监听器 RawDataListener 处理消息。
教程使用的包名为 com.ilummc.msgtutor,主类为 MessageMain。

Bukkit 接收消息部分

不知道你在查阅 Bukkit 的 Javadocs 时有没有注意到这样一个包 org.bukkit.plugin.messaging,这就是用于通信的包。
首先你需要一个实现了 PluginMessageListener 的类,本教程我们将其命名为 MessageListener:
package com.ilummc.msgtutor;

import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;

public class MessageListener implements PluginMessageListener {

        @Override
        public void onPluginMessageReceived(String channel, Player player, byte[] data) {

        }
}
自动补全的方法 onPluginMessageReceived 为接收到消息时调用的方法,channel 为通道名称,data 为具体的数据内容。
(2018/7/25 补充) 自 1.13 后,bukkit 对 channel 的名称做出了限制,需要使用 namespace:name 的格式,比如 fmltutor:fmltutor
接着,你需要注册消息输入和输出的通道,通过 Messenger 类的 registerIncomingPluginChannelregisterOutgoingPluginChannel 方法完成,代码如下:
package com.ilummc.msgtutor;

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class MessageMain extends JavaPlugin {

        @Override
        public void onEnable() {
                // 注册消息接受通道
                Bukkit.getMessenger().registerIncomingPluginChannel(this, "msgtutor", new MessageListener());
                // 注册消息发送通道
                Bukkit.getMessenger().registerOutgoingPluginChannel(this, "msgtutor");
        }
}
registerIncomingPluginChannel 注册了接受消息的通道,和使用的 PluginMessageListener 实例,registerOutgoingPluginChannel 则注册了发送消息使用的通道。
到此你完成了Bukkit 接收消息的部分。

Forge 客户端接收消息部分

我们需要先注册一个通道,使用 NetworkRegistry 类的 newEventDrivenChannel 方法:
package com.ilummc.msgtutor;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLEventChannel;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;

@SuppressWarnings("all")
@Mod(modid="msgtutor", version="tutor", name="MessageTutor")
public class MessageMain {
        static FMLEventChannel channel;
        
        @EventHandler
        public void preload(FMLPreInitializationEvent evt) {
                // 注册事件
                MinecraftForge.EVENT_BUS.register(this);
                FMLCommonHandler.instance().bus().register(this);
                // 注册通道
                channel = NetworkRegistry.INSTANCE.newEventDrivenChannel("msgtutor");
                channel.register(this);
        }
}
接着,我们添加一个监听器,监听 FMLNetworkEvent.ClientCustomPacketEvent 事件:
        @SubscribeEvent
        public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent evt) {
                FMLLog.getLogger().info(new String(evt.getPacket().payload().array()));
        }
此事件的 getPacket 方法可以获得一个 FMLProxyPacket 实例数据包,这个实例的方法 payload 可以获得数据包携带的内容 ByteBuf,而 ByteBuf 实例的方法 array 则可以得到 byte[] 类型的数据。
(2018/7/8 补充) 在 forge 的 1.12.2 以后的版本,该事件的 ByteBuf 变成了一个 netty 魔性优化的实例,导致性能的上升以及 array() 方法的失效,你需要手动 new 一个数组然后用 readBytes 来读数据。
到此,你就可以接收来自服务器的消息了。

Sponge 接收消息部分

Sponge.getChannelRegistrar() 方法返回 message channel 的注册器,然后通过其 createRawChannel 方法注册一个新的 channel。
通过 addListener 添加新的监听器。
package com.ilummc.msgtutor;
// 省略导入
@Plugin(id = "msgtutor",
        name = "MessageTutor",
        version = "1.0-SNAPSHOT",
        authors = {"IzzelAliz"})
public class ServerGui {

    private static ChannelBinding.RawDataChannel channel;

    @Listener
    public void onServerStart(GameStartedServerEvent event) {
        // 注册频道
        channel = Sponge.getChannelRegistrar().createRawChannel(this, "msgtutor");
        // 添加监听器
        // PlatformType 指定监听来自哪里的信息,我们监听的是客户端,所以使用 CLIENT

        channel.addListener(Platform.Type.CLIENT, (data, connection, side) -> {
            // 将连接类型转换为 PlayerConnection
            if (connection instanceof PlayerConnection) {
                PlayerConnection conn = (PlayerConnection) connection;
                // 示例给玩家发送消息
                conn.getPlayer().sendMessage(Text.of(new String(data.array())));
            }
        });
    }
}

发送消息
Bukkit 发送消息给客户端的方法为
Bukkit.getPlayer("Izzel_Aliz").sendPluginMessage(Plugin plugin, String channel, byte[] data);
Forge 发送消息给服务器的方法为
byte[] array = ...; // 你要发送的消息的 byte 数组
ByteBuf buf = Unpooled.wrappedBuffer(array);
FMLProxyPacket packet = new FMLProxyPacket(new PacketBuffer(buf), "msgtutor"); // 数据包
channel  // FMLEventChannel 实例
    .sendToServer(packet);
Sponge 发送消息给客户端的方法为
ChannelBinding.RawDataChannel channel = ... ; // 你注册的 channel 实例
Player player = .... ; // 目标玩家
channel.sendTo(player, channelBuf -> channelBuf.writeByteArray("发送的消息").getBytes());
// ChannelBuf 有大量方法,可以写入读取不同种类的数据,使用与 ByteBuf 类似
你可以发送任何东西,只要能将其作为 byte 数组发送。byte 数组的长度限制为 32766 字节。

LiteLoader 接受/发送
请移步 @ustc_zzzz 大佬的帖子的章节 与服务端插件交互 http://www.mcbbs.net/thread-659755-1-1.html
关于线程安全

以上接收时的事件全部是在网络线程被触发,所以对于线程不安全的Minecraft来说,线程安全问题需要额外注意。
由于本人对 Forge 的操作并不是很熟练,所以只能以 Bukkit 作为例子,如果你接收到的信息只是一条字符串,并且你只是想将其发送给玩家(Player#sendMessage),那么你可以随意在网络线程中使用,因为这个方法是线程安全的;但是,如果你需要进行踢出(Player#kickPlayer),那么你必须在主线程(Server Thread)进行这个操作,否则可能得到一个报错、崩溃或者意想不到的结果(尽管 Bukkit 会阻止 Async Kick 的行为并发出警告)。
那么,我们可以用以下的方法将数据转交给主线程处理:
  • 使用 Bukkit 或者 Sponge 的调度器

  • 利用管道IO

ByteBuf 的简单使用

ByteBuf 类被提供于 io.netty 包中,使用可以通过 Maven 导入
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-buffer</artifactId>
    <version>4.1.20.Final</version>
</dependency>
使用 Gradle 导入
compile group: 'io.netty', name: 'netty-buffer', version: '4.1.20.Final'
ByteBuf 可以说是 java.nio.ByteBuffer 类的加强,主要有以下优点:
  • 读写指针分离,不用调用 flip() 方法来切换读写状态
  • 写入时可以自动增加容量
  • 提供了 Unpooled 和 Pooled 两种用于不同的场景

创建一个 ByteBuf 的方法很多,比如
Unpooled.buffer() // 创建一个普通的 ByteBuf
Unpooled.wrappedBuffer(byte[]) // 从已有 byte 数组创建
Pooled.buffer() // 创建一个高并发优化的 ByteBuf
Unpooled 和 Pooled 类重载的方法还有很多,详情可以查阅 Javadocs
另及:Minecraft 还叫不上高并发,Pooled 的高并发优化对于 Minecraft 大概没啥用

写入/读取
ByteBuf buf = ... ;
buf.writeInt(int); // 写入 int 型数据
buf.writeBytes(byte[], int, int); // 写入 byte 数组,后两个参数分别是 offset 和 length
buf.readLong(); // 返回一个 long 型数据
buf.toString(int, int, Charset); // 将 ByteBuf 内部的 byte[] 转换为 String 型数据,前两个参数分别是 offset 和 length
重载的方法还有很多,基本什么数据都能往里写,这里介绍一些实用的示例。

ByteBuf 里的两个读写指针,分别可以通过 readerIndex 和 writerIndex 方法获得
ByteBuf 内部维护了一个 byte 数组,其中 ByteBuf 的 capacity 为数组长度,可以通过 getCapacity 方法获得
0 <= readerIndex <= writerIndex <= capacity
  • 0 到 readerIndex 之间的数组区域称为 discardable bytes,discardReadBytes 方法可以将数组从 readerIndex 之后的部分移动到 0,从而增大可用区。
  • readerIndex 到 writerIndex 之间的数组区域称为 readable bytes,这一部分可以进行读取,每次读取之后,readerIndex 都会相应增加(增加数据长度,如 readLong 就会增加 Long.BYTES)。
  • writerIndex 到 capacity 之间的数组区域称为 writable bytes,这一部分可以写入,每次写入之后,writerIndex 都会相应增加(如 writeShort 就会增加 Short.BYTES)。如果写入的长度大于 capacity - writerIndex,则自动扩容。


后记 & 常用链接 & 补充阅读

Forge的通信 http://www.mcbbs.net/thread-711966-1-1.html
Spigot Javadocs https://hub.spigotmc.org/javadocs/spigot/
Sponge Javadocs https://jd.spongepowered.org/5.1.0/?overview-summary.html
Forge 1.7.10的Javadocs http://jd.ddmcloud.com/forge/1.7.10/
Netty Javadocs http://netty.io/4.1/api/index.html
ByteBuf http://blog.csdn.net/z69183787/article/details/52980426
http://www.2cto.com/kf/201604/500997.html
线程通信 http://www.cnblogs.com/hapjin/p/5492619.html
你可以用你的天才般的设想,让插件服务器玩起来像 Mod 一样,一切都是有可能的
我是一名 Bukkit 插件开发者,碰巧会一丁点的 Forge 开发,甚至刚学了一点 Sponge 开发。
使用 Forge 1.8.9 作为示范。


来自群组: PluginsCDTribe
2021.12 数据,可能有更多内容
Forge | LiteLoader 与 Bukkit / Sponge 之间的通信
—— PluginMessengeChannel 与 FMLNetworkEvent
在实际开发中,如果你有一些天才的设想,比如借助 Forge 让你的插件服务器变得更加有特色,但是苦于无法进行数据传输的话,那么现在你就可以学习如何让 Bukkit / Sponge 和 Forge 之间传输信息了。
此教程适用于 1.7.10-1.12 (已测试) 1.13 可以看这里,1.7.10就把包名改成 cpw 那个就行,Bukkit的插件全版本通用。

概述
Bukkit / Sponge 与 Forge 通信的原理为:
服务端发送 PluginMessage 到 Forge 客户端,客户端使用 FMLNetworkEvent.ClientCustomPacketEvent 接受处理信息。
Forge 使用 FMLEventChannel 将 FMLNetworkPacket 发送至 Bukkit,Bukkit 服务器使用 PluginMessageListener 接受处理消息,Sponge 使用注册的频道添加的监听器 RawDataListener 处理消息。
教程使用的包名为 com.ilummc.msgtutor,主类为 MessageMain。

Bukkit 接收消息部分

不知道你在查阅 Bukkit 的 Javadocs 时有没有注意到这样一个包 org.bukkit.plugin.messaging,这就是用于通信的包。
首先你需要一个实现了 PluginMessageListener 的类,本教程我们将其命名为 MessageListener:
package com.ilummc.msgtutor;


import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;


public class MessageListener implements PluginMessageListener {


  @Override
  public void onPluginMessageReceived(String channel, Player player, byte[] data) {


  }
}
自动补全的方法 onPluginMessageReceived 为接收到消息时调用的方法,channel 为通道名称,data 为具体的数据内容。
(2018/7/25 补充) 自 1.13 后,bukkit 对 channel 的名称做出了限制,需要使用 namespace:name 的格式,比如 fmltutor:fmltutor
接着,你需要注册消息输入和输出的通道,通过 Messenger 类的 registerIncomingPluginChannelregisterOutgoingPluginChannel 方法完成,代码如下:
package com.ilummc.msgtutor;


import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;


public class MessageMain extends JavaPlugin {


  @Override
  public void onEnable() {
    // 注册消息接受通道
    Bukkit.getMessenger().registerIncomingPluginChannel(this, &quot;msgtutor&quot;, new MessageListener());
    // 注册消息发送通道
    Bukkit.getMessenger().registerOutgoingPluginChannel(this, &quot;msgtutor&quot;);
  }
}
registerIncomingPluginChannel 注册了接受消息的通道,和使用的 PluginMessageListener 实例,registerOutgoingPluginChannel 则注册了发送消息使用的通道。
到此你完成了Bukkit 接收消息的部分。

Forge 客户端接收消息部分

我们需要先注册一个通道,使用 NetworkRegistry 类的 newEventDrivenChannel 方法:
package com.ilummc.msgtutor;


import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLEventChannel;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;


@SuppressWarnings(&quot;all&quot;)
@Mod(modid=&quot;msgtutor&quot;, version=&quot;tutor&quot;, name=&quot;MessageTutor&quot;)
public class MessageMain {
  static FMLEventChannel channel;
 
  @EventHandler
  public void preload(FMLPreInitializationEvent evt) {
    // 注册事件
    MinecraftForge.EVENT_BUS.register(this);
    FMLCommonHandler.instance().bus().register(this);
    // 注册通道
    channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(&quot;msgtutor&quot;);
    channel.register(this);
  }
}
接着,我们添加一个监听器,监听 FMLNetworkEvent.ClientCustomPacketEvent 事件:
  @SubscribeEvent
  public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent evt) {
    FMLLog.getLogger().info(new String(evt.getPacket().payload().array()));
  }
此事件的 getPacket 方法可以获得一个 FMLProxyPacket 实例数据包,这个实例的方法 payload 可以获得数据包携带的内容 ByteBuf,而 ByteBuf 实例的方法 array 则可以得到 byte[] 类型的数据。
(2018/7/8 补充) 在 forge 的 1.12.2 以后的版本,该事件的 ByteBuf 变成了一个 netty 魔性优化的实例,导致性能的上升以及 array() 方法的失效,你需要手动 new 一个数组然后用 readBytes 来读数据。
到此,你就可以接收来自服务器的消息了。

Sponge 接收消息部分

Sponge.getChannelRegistrar() 方法返回 message channel 的注册器,然后通过其 createRawChannel 方法注册一个新的 channel。
通过 addListener 添加新的监听器。
package com.ilummc.msgtutor;
// 省略导入
@Plugin(id = &quot;msgtutor&quot;,
  name = &quot;MessageTutor&quot;,
  version = &quot;1.0-SNAPSHOT&quot;,
  authors = {&quot;IzzelAliz&quot;})
public class ServerGui {


    private static ChannelBinding.RawDataChannel channel;


    @Listener
    public void onServerStart(GameStartedServerEvent event) {
  // 注册频道
  channel = Sponge.getChannelRegistrar().createRawChannel(this, &quot;msgtutor&quot;);
  // 添加监听器
  // PlatformType 指定监听来自哪里的信息,我们监听的是客户端,所以使用 CLIENT

  channel.addListener(Platform.Type.CLIENT, (data, connection, side) -&gt; {
   // 将连接类型转换为 PlayerConnection
   if (connection instanceof PlayerConnection) {
    PlayerConnection conn = (PlayerConnection) connection;
    // 示例给玩家发送消息
    conn.getPlayer().sendMessage(Text.of(new String(data.array())));
   }
  });
    }
}

发送消息
Bukkit 发送消息给客户端的方法为
Bukkit.getPlayer(&quot;Izzel_Aliz&quot;).sendPluginMessage(Plugin plugin, String channel, byte[] data);
Forge 发送消息给服务器的方法为
byte[] array = ...; // 你要发送的消息的 byte 数组
ByteBuf buf = Unpooled.wrappedBuffer(array);
FMLProxyPacket packet = new FMLProxyPacket(new PacketBuffer(buf), &quot;msgtutor&quot;); // 数据包
channel// FMLEventChannel 实例
    .sendToServer(packet);
Sponge 发送消息给客户端的方法为
ChannelBinding.RawDataChannel channel = ... ; // 你注册的 channel 实例
Player player = .... ; // 目标玩家
channel.sendTo(player, channelBuf -&gt; channelBuf.writeByteArray(&quot;发送的消息&quot;).getBytes());
// ChannelBuf 有大量方法,可以写入读取不同种类的数据,使用与 ByteBuf 类似
你可以发送任何东西,只要能将其作为 byte 数组发送。byte 数组的长度限制为 32766 字节。

LiteLoader 接受/发送
请移步 @ustc_zzzz 大佬的帖子的章节 与服务端插件交互 http://www.mcbbs.net/thread-659755-1-1.html
关于线程安全

以上接收时的事件全部是在网络线程被触发,所以对于线程不安全的Minecraft来说,线程安全问题需要额外注意。
由于本人对 Forge 的操作并不是很熟练,所以只能以 Bukkit 作为例子,如果你接收到的信息只是一条字符串,并且你只是想将其发送给玩家(Player#sendMessage),那么你可以随意在网络线程中使用,因为这个方法是线程安全的;但是,如果你需要进行踢出(Player#kickPlayer),那么你必须在主线程(Server Thread)进行这个操作,否则可能得到一个报错、崩溃或者意想不到的结果(尽管 Bukkit 会阻止 Async Kick 的行为并发出警告)。
那么,我们可以用以下的方法将数据转交给主线程处理:
  • 使用 Bukkit 或者 Sponge 的调度器
Bukkit#getScheduler 返回一个 Bukkit 的调度器,Sponge#getScheduler 返回一个 Sponge 的调度器。
实例如下:
Bukkit 错误的做法:

代码:

  1.   @Override
  2.   public void onPluginMessageReceived(String channel, Player player, byte[] data) {
  3.     player.kickPlayer("你因为给服务器发 plugin message 被踢了");
  4.     <font color="#808080">// 报错</font>
  5.   }
Bukkit 的正确做法,将涉及服务器的操作使用调度器交给主线程完成:

代码:

  1.   @Override
  2.   public void onPluginMessageReceived(String channel, Player player, byte[] data) {
  3.     Bukkit.getScheduler().runTask(插件实例, () -> player.kickPlayer("被服务器用调度器踢出"));
  4.   }
Sponge 的调度器操作类似。
  • 利用管道IO
管道IO,PipedInputStream 和 PipedOutputStream 用于线程间的通信,主要思想为:
连接管道(PipedOutputStream#connect(PipedInputStream in))-&gt; 线程W写入流(即网络线程) -&gt; 线程R读取流(即Server Thread)
示例用法见帖尾链接
管道流可以用于线程间的通信,使用 byte[] 传输。
PipedInputStream 的 read 方法,如果没有读取到管道流中的数据,将会阻塞线程,请务必注意!
还需要注意的是,Minecraft 使用的网络线程可能有多条,需要额外注意。默认的 Netty IO 线程有 4 条。
ByteBuf 的简单使用

ByteBuf 类被提供于 io.netty 包中,使用可以通过 Maven 导入
&lt;dependency&gt;
    &lt;groupId&gt;io.netty&lt;/groupId&gt;
    &lt;artifactId&gt;netty-buffer&lt;/artifactId&gt;
    &lt;version&gt;4.1.20.Final&lt;/version&gt;
&lt;/dependency&gt;
使用 Gradle 导入
compile group: 'io.netty', name: 'netty-buffer', version: '4.1.20.Final'
ByteBuf 可以说是 java.nio.ByteBuffer 类的加强,主要有以下优点:
  • 读写指针分离,不用调用 flip() 方法来切换读写状态
  • 写入时可以自动增加容量
  • 提供了 Unpooled 和 Pooled 两种用于不同的场景

创建一个 ByteBuf 的方法很多,比如
Unpooled.buffer() // 创建一个普通的 ByteBuf
Unpooled.wrappedBuffer(byte[]) // 从已有 byte 数组创建
Pooled.buffer() // 创建一个高并发优化的 ByteBuf
Unpooled 和 Pooled 类重载的方法还有很多,详情可以查阅 Javadocs
另及:Minecraft 还叫不上高并发,Pooled 的高并发优化对于 Minecraft 大概没啥用


写入/读取
ByteBuf buf = ... ;
buf.writeInt(int); // 写入 int 型数据
buf.writeBytes(byte[], int, int); // 写入 byte 数组,后两个参数分别是 offset 和 length
buf.readLong(); // 返回一个 long 型数据
buf.toString(int, int, Charset); // 将 ByteBuf 内部的 byte[] 转换为 String 型数据,前两个参数分别是 offset 和 length
重载的方法还有很多,基本什么数据都能往里写,这里介绍一些实用的示例。


ByteBuf 里的两个读写指针,分别可以通过 readerIndex 和 writerIndex 方法获得
ByteBuf 内部维护了一个 byte 数组,其中 ByteBuf 的 capacity 为数组长度,可以通过 getCapacity 方法获得
0 &lt;= readerIndex &lt;= writerIndex &lt;= capacity
  • 0 到 readerIndex 之间的数组区域称为 discardable bytes,discardReadBytes 方法可以将数组从 readerIndex 之后的部分移动到 0,从而增大可用区。
  • readerIndex 到 writerIndex 之间的数组区域称为 readable bytes,这一部分可以进行读取,每次读取之后,readerIndex 都会相应增加(增加数据长度,如 readLong 就会增加 Long.BYTES)。
  • writerIndex 到 capacity 之间的数组区域称为 writable bytes,这一部分可以写入,每次写入之后,writerIndex 都会相应增加(如 writeShort 就会增加 Short.BYTES)。如果写入的长度大于 capacity - writerIndex,则自动扩容。


后记 &amp; 常用链接 &amp; 补充阅读

Forge的通信 http://www.mcbbs.net/thread-711966-1-1.html
Spigot Javadocs https://hub.spigotmc.org/javadocs/spigot/
Sponge Javadocs https://jd.spongepowered.org/5.1.0/?overview-summary.html
Forge 1.7.10的Javadocs http://jd.ddmcloud.com/forge/1.7.10/
Netty Javadocs http://netty.io/4.1/api/index.html
ByteBuf http://blog.csdn.net/z69183787/article/details/52980426
http://www.2cto.com/kf/201604/500997.html
线程通信 http://www.cnblogs.com/hapjin/p/5492619.html
你可以用你的天才般的设想,让插件服务器玩起来像 Mod 一样,一切都是有可能的
我是一名 Bukkit 插件开发者,碰巧会一丁点的 Forge 开发,甚至刚学了一点 Sponge 开发。
使用 Forge 1.8.9 作为示范。




wt6654499
支持技术!!!!

653224281
支持一下 学习一下

Spedin
哇,这样子的话以后的插件和mod是不是可以联合在一起去实现一些功能了!

支持下大佬!感谢分享。。。终于有一篇系统地讲述Forge和Bukkit通过Plm进行通讯的教程了!
为什么不早点写出来呢??回忆起我当初请教了N个大佬才勉强整会,当时要是有这个教程就好了


海螺螺
1582952890 发表于 2017-9-8 12:01
支持下大佬!感谢分享。。。终于有一篇系统地讲述Forge和Bukkit通过Plm进行通讯的教程了!
为什么不早点 ...

早点。。
我也想写,但是感觉没啥人想要呢
其实是之前没想过来编程版写教程,而且论坛有一个验证机器码的插件来着,可以用那个学习的

li709854423
问题是,在spigot的服务端里。能往独立的MOD里发消息吗?

3TUSK
li709854423 发表于 2017-10-10 10:37
问题是,在spigot的服务端里。能往独立的MOD里发消息吗?

Spigot 服务器插件 -> Forge 客户端的 Mod 是可以的。
Forge 客户端的 Mod -> Spigot 服务器插件也是可以的。
甚至 Spigot 服务器插件 <-> LiteLoader 客户端 Mod也是可以的。(理论上是这样。我没有试过。)

但是如果你是说 Spigot 服务器插件 -> Forge 服务器的 Mod.. 等等你是怎么把 MinecraftForge 装到 Spigot 服务器端上的

li709854423
u.s.knowledge 发表于 2017-10-10 10:51
Spigot 服务器插件 -> Forge 客户端的 Mod 是可以的。
Forge 客户端的 Mod -> Spigot 服务器插件也是可以 ...

我的意思就是独立MOD。。独立MOD的意思就是不存在服务端。。。
也就说你应该是解决了我的一个误区。。我一直以为spigot服务端是无法往客户端MOD发消息的。。现在看来大有可为了

3TUSK
li709854423 发表于 2017-10-10 11:07
我的意思就是独立MOD。。独立MOD的意思就是不存在服务端。。。
也就说你应该是解决了我的一个误区。。我 ...

我从未听说过有这样使用“独立Mod”这个词的。
独立Mod这个词通常指不需要任何前置(不含library mod)的Mod,比如像IC2、暮色这样的就可以用独立Mod这个词来描述,但是IC2高级机器这个Mod需要IC2作为前置,就不能称之为独立Mod了。

我一直以为spigot服务端是无法往客户端MOD发消息的。。


这篇文章讲述的就是 Bukkit 服务器插件与 MinecraftForge 客户端 Mod 之间如何通信。Spigot 仍然是 Bukkit
的超集
。难道 Spigot 摆脱了Bukkit API?

li709854423
u.s.knowledge 发表于 2017-10-10 11:47
我从未听说过有这样使用“独立Mod”这个词的。
独立Mod这个词通常指不需要任何前置(不含library mod)的 ...

事实上我之前一直用的在KC端进行插件控制MOD发包。当时我以为是因为KC端才允许这样..也没去spigot试过仔细想想在通道一致地址一致的情况下。。肯定是可以互相收包发包的
至于独立MOD。。个人理解or误解的问题就没必要讨论了

RINNDA
        神乎其技,不服不行! 这个教程很易懂

Ynglife
非常感谢,刚好脱离了1.7.10的bukkit,正愁该怎么翻Sponge的文档就看到了这个。

wysljjzzh
楼主的帖子很实用,收藏了~

GiNYAi
Forge的SimpleNetworkWrapper(SimpleImpl) 实际上也是走的PluginMessageChannel

格式是
1位byte的 `id`
其他你写到ByteBuf里的东西

也就是
只要格式正确 Forge的接受和发送部分可以使用 SimpleImpl来处理
或者说Bukkit插件也可以处理ForgeMod用SimpleImpl发送过来的数据

德芙DakFu
技术贴啊支持支持

slmjun002
大佬nb,我啥玩意都不会

龙吟y
楼主 1.7.10的FMLNetworkEvent.ClientCustomPacketEvent事件下没有getpacket方法吗

c666666
支持一下 学习一下

米迦勒c
现在已经有整合好的插件交互MOD的引擎了

第一页 上一页 下一页 最后一页