跳到主要内容

插件消息

插件消息最早在 2012 年引入, 是插件与客户端通信的一种方式。当你的服务器在代理服务器后面时, 它将允许你的插件与代理服务器通信。

BungeeCord 通道

BungeeCord 通道用于你的 Paper 服务器和 BungeeCord(或兼容 BungeeCord 的)代理服务器之间的通信。

最初,BungeeCord 代理支持的通道叫做 BungeeCord。在 1.13 及以上版本中, 通道被重命名为 bungeecord:main,以创建插件消息通道的键结构。

Paper 在内部处理这个变化,并自动将在 BungeeCord 通道上发送的任何消息 改为 bungeecord:main 通道。这意味着你的插件应该继续使用 BungeeCord 通道。

发送插件消息

首先,我们来看看你的 Paper 服务器。你的插件需要注册它 将在任何给定的插件通道上发送消息。你应该在注册其他事件监听器的同时做这个。

PluginMessagingSample.java
public final class PluginMessagingSample extends JavaPlugin {

@Override
public void onEnable() {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
// 其他代码...
}

}

现在我们已经注册了,我们可以在 BungeeCord 通道上发送消息了。

插件消息被格式化为字节数组,可以使用 sendPluginMessage 方法在 Player 对象上发送。 让我们看一个在 BungeeCord 通道上发送插件消息以将我们的玩家发送到另一个服务器的示例。

PluginMessagingSample.java
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 通道上发送了一个插件消息!我们发送的消息是一个包含两个转换为字节的字符串的字节数组:Connecthub2

我们的代理服务器通过触发 PlayerJumpEvent 的玩家接收到了消息。 然后,它识别出这个通道是它自己的,并按照 BungeeCord 的协议,将我们的玩家发送到 hub2 服务器。

对于 BungeeCord,我们可以将这个消息看作是一个区分大小写的命令及其参数。 这里,我们的命令是 Connect,我们唯一的参数是 hub2,但有些"命令"可能有多个参数。 对于由客户端模组引入的其他通道,请参考它们的文档以最好地理解如何格式化你的消息。

插件消息类型

虽然我们向代理发送了一个 Connect 消息,但还有一些其他情况下兼容 BungeeCord 的代理会采取行动。 这些包括:

消息类型描述参数响应
Connect将玩家连接到指定的服务器。server nameN/A
ConnectOther将另一个玩家连接到指定的服务器。player name, server nameN/A
IP返回玩家的 IP。N/AIP, port
IPOther返回指定玩家的 IP。player nameplayer name, IP, port
PlayerCount返回指定服务器上的玩家数量。server nameserver name, player count
PlayerList返回指定服务器上的玩家列表。server nameserver name, CSV player names
GetServers返回所有服务器的列表。N/ACSV server names
Message向指定玩家发送消息。player name, messageN/A
MessageRaw向指定玩家发送原始聊天组件。player name, JSON chat componentN/A
GetServer返回玩家当前连接的服务器。N/Aserver name
GetPlayerServer返回指定玩家的服务器名称。player nameplayer name, server name
UUID返回玩家的 UUID。N/AUUID
UUIDOther返回指定玩家的 UUID。player nameplayer name, UUID
ServerIp返回指定服务器的 IP。server nameserver name, IP, port
KickPlayer踢出指定玩家。player name, reasonN/A
KickPlayerRaw踢出指定玩家。player name, JSON chat componentN/A
Forward将插件消息转发到另一个服务器。server, subchannel, size of plugin message, messagesubchannel, size of plugin message, message
ForwardToPlayer将插件消息转发到另一个玩家。player name, subchannel, size of plugin message, messagesubchannel, size of plugin message, message

PlayerCount

MyPlugin.java
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

MyPlugin.java
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" 表示所有玩家。这对于向代理网络中不同服务器上的玩家发送消息也很有用。

MyPlugin.java
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