Paper插件开发:如何获取玩家执行命令后的输出内容?
问题:捕获Paper服务器插件中玩家命令执行后的所有输出内容
开发Paper服务器插件时,需要获取玩家执行命令后看到的输出内容(例如玩家执行/tpa命令时收到的「传送请求已发送给XXX」提示,而服务器控制台仅显示「用户执行了命令:/tpa」)。目前实现的代码仅能处理/tell和/msg命令,需要改进以支持所有玩家命令的输出捕获。
当前代码实现
package cherry.plugins; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerChatEvent; public class CommandLogger implements Listener { private final CherryCostume plugin; // 用于在命令输出发送到聊天栏前捕获输出的变量 private String currentCommandSender; private String lastCommandOutput; public CommandLogger(CherryCostume plugin) { this.plugin = plugin; Bukkit.getPluginManager().registerEvents(this, plugin); } @EventHandler public void onPlayerCommand(PlayerCommandPreprocessEvent event) { String command = event.getMessage().substring(1); // 移除开头的斜杠 String playerName = event.getPlayer().getName(); // 检查命令是否为/tell或/msg类型 if (command.startsWith("tell") || command.startsWith("msg")) { String[] commandParts = command.split(" ", 3); if (commandParts.length >= 3) { String targetPlayer = commandParts[1]; // 目标玩家 String message = commandParts[2]; // 命令消息 // 保存发送命令的玩家,用于后续对比 currentCommandSender = playerName; // 记录执行的命令 plugin.getLogger().info("执行的命令: " + command); plugin.getLogger().info("输出: 已向" + targetPlayer + "发送消息: " + message); } } } @EventHandler public void onPlayerChat(AsyncPlayerChatEvent event) { String message = event.getMessage(); // 检查消息是否为悄悄话格式 if (message.startsWith("§7[§8[.*§r")) { String sender = event.getPlayer().getName(); // 仅记录刚执行完命令的玩家发送的聊天消息 if (sender.equals(currentCommandSender)) { // 捕获聊天栏中显示的精确输出 lastCommandOutput = message; // 记录命令生成的聊天栏精确输出 plugin.getLogger().info("输出: " + lastCommandOutput); } } } }
改进方案
直接监听玩家接收消息的事件,并结合命令执行前的记录,就能捕获所有命令的输出。具体步骤如下:
1. 替换监听事件
移除原有的AsyncPlayerChatEvent监听,改用Paper API提供的PlayerReceiveMessageEvent——这个事件会捕获所有发送给玩家的消息,包括命令执行后的反馈提示。
2. 使用线程安全的存储记录最近执行命令的玩家
因为命令执行事件和消息接收事件可能在不同线程触发,所以用ConcurrentHashMap存储最近执行命令的玩家及其命令内容,避免线程安全问题,同时设置定时清理机制防止内存泄漏。
3. 改进事件处理逻辑
- 在
PlayerCommandPreprocessEvent中记录所有执行命令的玩家,不再限制命令类型 - 在
PlayerReceiveMessageEvent中检查当前玩家是否刚执行过命令,若是则捕获消息作为命令输出,记录后清除该玩家的标记
改进后的代码
package cherry.plugins; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerReceiveMessageEvent; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class CommandLogger implements Listener { private final CherryCostume plugin; // 存储最近执行命令的玩家:键为玩家名,值为命令内容 private final ConcurrentHashMap<String, String> recentCommandPlayers = new ConcurrentHashMap<>(); public CommandLogger(CherryCostume plugin) { this.plugin = plugin; Bukkit.getPluginManager().registerEvents(this, plugin); // 定时清理过期的记录(10秒后移除,可根据需求调整) Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> { recentCommandPlayers.clear(); }, 200L, 200L); // 200 ticks = 10秒 } @EventHandler public void onPlayerCommand(PlayerCommandPreprocessEvent event) { String command = event.getMessage().substring(1); String playerName = event.getPlayer().getName(); // 记录玩家和执行的命令 recentCommandPlayers.put(playerName, command); plugin.getLogger().info("执行的命令: " + command); } @EventHandler public void onPlayerReceiveMessage(PlayerReceiveMessageEvent event) { String playerName = event.getPlayer().getName(); String commandOutput = event.getMessage(); // 检查该玩家是否刚执行过命令 if (recentCommandPlayers.containsKey(playerName)) { String executedCommand = recentCommandPlayers.remove(playerName); // 记录命令和对应的输出 plugin.getLogger().info("命令[" + executedCommand + "]的输出: " + commandOutput); } } }
补充说明
- 如果某些命令会发送多条输出消息,可调整定时清理的时间,或者修改逻辑收集一段时间内的所有消息再统一记录
PlayerReceiveMessageEvent是Paper专属事件,若需要兼容Spigot,可改用PlayerSendMessageEvent(注意Spigot的该事件可能需要额外处理)- 定时清理任务的周期可根据实际需求调整,避免无关消息被误捕获
内容的提问来源于stack exchange,提问作者user378555
相关产品推荐
相关产品推荐

