ChatMenuPlus

ChatMenuPlus.java v1.0
聊天界面玩家名可点击,弹出可配置操作菜单,含临时屏蔽功能(支持Paper 1.21.1及以上)。
作者: 2a2t.org

命令列表

  • chatmenu重载菜单配置

权限列表

  • chatmenu.admin重载菜单配置权限
package com.scriptirc.chatmenu;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.CommandExecutor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.plugin.java.JavaPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;

/**
 * @pluginName ChatMenuPlus
 * @author 2a2t.org
 * @version 1.0
 * @description 聊天界面玩家名可点击,弹出可配置操作菜单,含临时屏蔽功能(支持Paper 1.21.1及以上)。
 * [command]chatmenu|重载菜单配置[/command]
 * [Permission]chatmenu.admin|重载菜单配置权限[/Permission]
 */
public class ChatMenuPlus extends JavaPlugin implements Listener, CommandExecutor {
    // 菜单配置项结构
    public static class MenuAction {
        public String name;      // 显示文本
        public String command;   // 执行命令(可用变量{player}、{target})
        public String type;      // 类型(run/suggest/ignore)
        public MenuAction(String name, String command, String type) {
            this.name = name;
            this.command = command;
            this.type = type;
        }
    }

    // 菜单配置(内存缓存)
    private List<MenuAction> menuConfig = new ArrayList<>();
    // 临时屏蔽数据 key=操作人,value=被屏蔽玩家名集合
    private final Map<UUID, Set<String>> ignoreMap = new ConcurrentHashMap<>();
    // 配置文件路径
    private File configFile;

    @Override
    public void onEnable() {
        Bukkit.getPluginManager().registerEvents(this, this);
        this.getCommand("chatmenu").setExecutor(this);
        configFile = new File(getDataFolder(), "menu.json");
        if (!getDataFolder().exists()) {
            boolean created = getDataFolder().mkdirs();
            if (!created) {
                getLogger().warning("无法创建插件数据目录!配置文件可能无法保存。");
            }
        }
        loadMenuConfig();
        getLogger().info("ChatMenuPlus (adventure) 启动成功!");
    }

    @Override
    public void onDisable() {
        ignoreMap.clear();
    }

    // 聊天事件拦截,替换玩家名为可点击 adventure 组件
    @EventHandler
    public void onChat(PlayerChatEvent event) {
        Player sender = event.getPlayer();
        String senderName = sender.getName();
        String message = event.getMessage();
        // 过滤被屏蔽
        event.getRecipients().removeIf(p -> {
            Set<String> ignores = ignoreMap.get(p.getUniqueId());
            return ignores != null && ignores.contains(senderName);
        });
        // 构造可点击玩家名组件
        Component nameComp = Component.text(senderName)
                .color(NamedTextColor.AQUA)
                .decorate(TextDecoration.BOLD)
                .hoverEvent(HoverEvent.showText(Component.text("点击操作")))
                .clickEvent(ClickEvent.runCommand("/chatmenu show " + senderName));
        // 构造消息体
        Component msgComp = Component.text(": " + message).color(NamedTextColor.WHITE);
        // 组装
        Component full = Component.empty().append(nameComp).append(msgComp);
        // 发送给所有未屏蔽的接收者
        for (Player p : event.getRecipients()) {
            p.sendMessage(full);
        }
        // 取消原始广播
        event.setCancelled(true);
    }

    // 临时屏蔽失效:玩家退出时清理
    @EventHandler
    public void onQuit(PlayerQuitEvent event) {
        ignoreMap.remove(event.getPlayer().getUniqueId());
    }

    // /chatmenu 命令处理
    @Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (args.length == 0) {
            sender.sendMessage(Component.text("用法: /chatmenu reload | show <玩家名>").color(NamedTextColor.YELLOW));
            return true;
        }
        if (args[0].equalsIgnoreCase("reload")) {
            if (!sender.hasPermission("chatmenu.admin")) {
                sender.sendMessage(Component.text("没有权限").color(NamedTextColor.RED));
                return true;
            }
            loadMenuConfig();
            sender.sendMessage(Component.text("菜单配置已重载").color(NamedTextColor.GREEN));
            return true;
        }
        if (args[0].equalsIgnoreCase("show") && args.length >= 2) {
            if (!(sender instanceof Player)) {
                sender.sendMessage(Component.text("只能玩家使用").color(NamedTextColor.RED));
                return true;
            }
            Player p = (Player) sender;
            String target = args[1];
            sendMenuToPlayer(p, target);
            return true;
        }
        sender.sendMessage(Component.text("用法: /chatmenu reload | show <玩家名>").color(NamedTextColor.YELLOW));
        return true;
    }

    // 发送菜单消息(adventure组件)
    private void sendMenuToPlayer(Player player, String targetName) {
        Component menu = Component.text("对 " + targetName + " 可用操作: ", NamedTextColor.GOLD);
        for (MenuAction act : menuConfig) {
            Component btn = Component.text("[" + act.name + "]")
                    .color(NamedTextColor.GREEN)
                    .decorate(TextDecoration.BOLD)
                    .hoverEvent(HoverEvent.showText(Component.text("点击执行: " + act.name)));
            String cmd = act.command.replace("{player}", player.getName()).replace("{target}", targetName);
            if ("run".equalsIgnoreCase(act.type)) {
                btn = btn.clickEvent(ClickEvent.runCommand(cmd));
            } else if ("suggest".equalsIgnoreCase(act.type)) {
                btn = btn.clickEvent(ClickEvent.suggestCommand(cmd));
            } else if ("ignore".equalsIgnoreCase(act.type)) {
                btn = btn.clickEvent(ClickEvent.runCommand("/chatmenu ignore " + targetName));
            } else {
                btn = btn.clickEvent(ClickEvent.suggestCommand(cmd));
            }
            menu = menu.append(Component.space()).append(btn);
        }
        player.sendMessage(menu);
    }

    // 监听命令执行(屏蔽)
    @EventHandler
    public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
        String msg = event.getMessage();
        if (msg.startsWith("/chatmenu ignore ")) {
            String[] arr = msg.split(" ", 3);
            if (arr.length < 3) return;
            String target = arr[2];
            Player p = event.getPlayer();
            ignoreMap.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>()).add(target);
            p.sendMessage(Component.text("你已临时屏蔽 " + target + " 的发言(重登失效)", NamedTextColor.GRAY));
            event.setCancelled(true);
        }
    }

    // 加载菜单配置
    private void loadMenuConfig() {
        menuConfig.clear();
        if (!configFile.exists()) {
            // 默认菜单
            menuConfig.add(new MenuAction("请求传送", "/tpa {target}", "run"));
            menuConfig.add(new MenuAction("私聊", "/msg {target} ", "suggest"));
            menuConfig.add(new MenuAction("屏蔽", "", "ignore"));
            saveMenuConfig();
            return;
        }
        try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) sb.append(line);
            String json = sb.toString();
            // 简单解析(无第三方库)
            json = json.replace("[","").replace("]","");
            String[] items = json.split("},");
            for (String item : items) {
                item = item.replace("{","").replace("}","");
                String[] fields = item.split(",");
                String name = "", cmd = "", type = "run";
                for (String f : fields) {
                    String[] kv = f.split(":",2);
                    if (kv.length < 2) continue;
                    String k = kv[0].replace("\"","").trim();
                    String v = kv[1].replace("\"","").trim();
                    if (k.equals("name")) name = v;
                    else if (k.equals("command")) cmd = v;
                    else if (k.equals("type")) type = v;
                }
                if (!name.isEmpty()) menuConfig.add(new MenuAction(name, cmd, type));
            }
        } catch (Exception e) {
            getLogger().warning("菜单配置加载失败,使用默认菜单");
            menuConfig.clear();
            menuConfig.add(new MenuAction("请求传送", "/tpa {target}", "run"));
            menuConfig.add(new MenuAction("私聊", "/msg {target} ", "suggest"));
            menuConfig.add(new MenuAction("屏蔽", "", "ignore"));
        }
    }

    // 保存菜单配置
    private void saveMenuConfig() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(configFile))) {
            writer.write("[");
            boolean first = true;
            for (MenuAction act : menuConfig) {
                if (!first) writer.write(",");
                writer.write(String.format("{\"name\":\"%s\",\"command\":\"%s\",\"type\":\"%s\"}",
                    act.name.replace("\"", ""), act.command.replace("\"", ""), act.type.replace("\"", "")));
                first = false;
            }
            writer.write("]");
        } catch (Exception e) {
            getLogger().warning("菜单配置保存失败");
        }
    }
}

上一篇: WordFireworkShow

举报内容

意见反馈