插件消息
插件消息最早在 2012 年引入, 是插件与客户端通信的一种方式。当你的服务器在代理服务器后面时, 它将允许你的插件与代理服务器通信。
BungeeCord 通道
BungeeCord 通道用于你的 Paper 服务器和 BungeeCord(或兼容 BungeeCord 的)代理服务器之间的通信。
最初,BungeeCord 代理支持的通道叫做 BungeeCord。在 1.13 及以上版本中,
通道被重命名为 bungeecord:main,以创建插件消息通道的键结构。
Paper 在内部处理这个变化,并自动将在 BungeeCord 通道上发送的任何消息
改为 bungeecord:main 通道。这意味着你的插件应该继续使用 BungeeCord 通道。
发送插件消息
首先,我们来看看你的 Paper 服务器。你的插件需要注册它 将在任何给定的插件通道上发送消息。你应该在注册其他事件监听器的同时做这个。
public final class PluginMessagingSample extends JavaPlugin {
@Override
public void onEnable() {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
// 其他代码...
}
}
现在我们已经注册了,我们可以在 BungeeCord 通道上发送消息了。
插件消息被格式化为字节数组,可以使用 sendPluginMessage
方法在 Player 对象上发送。
让我们看一个在 BungeeCord 通道上发送插件消息以将我们的玩家发送到另一个服务器的示例。
public final class PluginMessagingSample extends JavaPlugin implements Listener {
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
}
@EventHandler
public void onPlayerJump(PlayerJumpEvent event) {
Player player = event.getPlayer();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF("hub2");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
}
}
这些通道依赖于 Minecraft 协议,作为一种特殊类型的数据包(称为 插件消息)发送。它们依附于玩家连接,所以如果 服务器上没有玩家连接,它将无法发送或接收插件消息。
我们刚才做了什么?
我们在 BungeeCord 通道上发送了一个插件消息!我们发送的消息是一个包含两个转换为字节的字符串的字节数组:Connect 和 hub2。
我们的代理服务器通过触发 PlayerJumpEvent 的玩家接收到了消息。
然后,它识别出这个通道是它自己的,并按照 BungeeCord 的协议,将我们的玩家发送到 hub2 服务器。
对于 BungeeCord,我们可以将这个消息看作是一个区分大小写的命令及其参数。
这里,我们的命令是 Connect,我们唯一的参数是 hub2,但有些"命令"可能有多个参数。
对于由客户端模组引入的其他通道,请参考它们的文档以最好地理解如何格式化你的消息。
插件消息类型
虽然我们向代理发送了一个 Connect 消息,但还有一些其他情况下兼容 BungeeCord 的代理会采取行动。
这些包括:
| 消息类型 | 描述 | 参数 | 响应 |
|---|---|---|---|
Connect | 将玩家连接到指定的服务器。 | server name | N/A |
ConnectOther | 将另一个玩家连接到指定的服务器。 | player name, server name | N/A |
IP | 返回玩家的 IP。 | N/A | IP, port |
IPOther | 返回指定玩家的 IP。 | player name | player name, IP, port |
PlayerCount | 返回指定服务器上的玩家数量。 | server name | server name, player count |
PlayerList | 返回指定服务器上的玩家列表。 | server name | server name, CSV player names |
GetServers | 返回所有服务器的列表。 | N/A | CSV server names |
Message | 向指定玩家发送消息。 | player name, message | N/A |
MessageRaw | 向指定玩家发送原始聊天组件。 | player name, JSON chat component | N/A |
GetServer | 返回玩家当前连接的服务器。 | N/A | server name |
GetPlayerServer | 返回指定玩家的服务器名称。 | player name | player name, server name |
UUID | 返回玩家的 UUID。 | N/A | UUID |
UUIDOther | 返回指定玩家的 UUID。 | player name | player name, UUID |
ServerIp | 返回指定服务器的 IP。 | server name | server name, IP, port |
KickPlayer | 踢出指定玩家。 | player name, reason | N/A |
KickPlayerRaw | 踢出指定玩家。 | player name, JSON chat component | N/A |
Forward | 将插件消息转发到另一个服务器。 | server, subchannel, size of plugin message, message | subchannel, size of plugin message, message |
ForwardToPlayer | 将插件消息转发到另一个玩家。 | player name, subchannel, size of plugin message, message | subchannel, size of plugin message, message |
PlayerCount
public class MyPlugin extends JavaPlugin implements PluginMessageListener {
@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);
Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("PlayerCount");
out.writeUTF("lobby");
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// 响应将在 onPluginMessageReceived 中处理
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("PlayerCount")) {
// 这是我们对 PlayerCount 请求的响应
String server = in.readUTF();
int playerCount = in.readInt();
}
}
}
Forward
public class MyPlugin extends JavaPlugin implements PluginMessageListener {
@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);
Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Forward");
out.writeUTF("ALL"); // 这是目标服务器。"ALL" 将向除发送消息的服务器外的所有服务器发送消息
out.writeUTF("SecretInternalChannel"); // 这是通道。
ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
DataOutputStream msgout = new DataOutputStream(msgbytes);
msgout.writeUTF("Paper 是生命的意义"); // 你可以用 msgout 做任何你想做的事
msgout.writeShort(42); // 写入一个随机的短整数
out.writeShort(msgbytes.toByteArray().length); // 这是长度。
out.write(msgbytes.toByteArray()); // 这是消息。
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
// 响应将在 onPluginMessageReceived 中处理
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if (subchannel.equals("SecretInternalChannel")) {
short len = in.readShort();
byte[] msgbytes = new byte[len];
in.readFully(msgbytes);
DataInputStream msgIn = new DataInputStream(new ByteArrayInputStream(msgbytes));
String secretMessage = msgIn.readUTF(); // 以与写入相同的方式读取数据
short meaningofLife = msgIn.readShort();
}
}
}
这个消息用于将插件消息转发到另一个服务器。这对于代理网络内的服务器间通信很有用。 例如,如果某个玩家在一个服务器上被封禁,你可以转发一个消息到所有其他服务器,在那里也封禁他们。
由于目标服务器上可能没有人在线,这不是封禁玩家的推荐方式, 但这是一个如何使用它的示例。
MessageRaw
MessageRaw 消息类型用于向玩家发送原始聊天组件。目标玩家由第二个参数指定 -
玩家名称或 "ALL" 表示所有玩家。这对于向代理网络中不同服务器上的玩家发送消息也很有用。
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
Player player = ...;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("MessageRaw");
out.writeUTF("ALL");
out.writeUTF(GsonComponentSerializer.gson().serialize(
Component.text("点击我!").clickEvent(ClickEvent.openUrl("https://papermc.io"))
));
player.sendPluginMessage(this, "BungeeCord", out.toByteArray());
}
}
这将向玩家发送一个可点击的消息,显示"点击我!",点击时会打开 https://papermc.io。