跳到主要内容

插件消息

插件消息最早在 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 通道上发送了一个插件消息!我们发送的消息是一个包含两个转换为字节的字符串的字节数组:Connect 和 hub2。

我们的代理服务器通过触发 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。