GUIFriends

GUIFriends.java v1.0
互联网风格全GUI的数据库好友系统,支持备注、申请、红点提醒、分页等社交互动
作者: ScriptIrc Engine

命令列表

  • friend打开好友主界面,所有操作均用可视交互

权限列表

  • guifriends.adminGUI好友系统管理权限
package org.sircustom.guifriends;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.*;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;

/**
 * @pluginName GUIFriends
 * @author ScriptIrc Engine
 * @version 1.0
 * @description 互联网风格全GUI的数据库好友系统,支持备注、申请、红点提醒、分页等社交互动
 * [command]friend|打开好友主界面,所有操作均用可视交互[/command]
 * [Permission]guifriends.admin|GUI好友系统管理权限[/Permission]
 */

public class GUIFriends extends JavaPlugin implements Listener, TabCompleter {
    private static final String DB_FILE = "guifriends.sqlite";
    private Connection dataSource;

    // 申请/留言/备注输入 等临时状态维护
    private final Map<UUID, InputState> inputStates = new ConcurrentHashMap<>();
    // GUI 页码等状态
    private final Map<UUID, Integer> guiPage = new ConcurrentHashMap<>();
    // GUI上下文 记录玩家在做什么
    private final Map<UUID, String> guiContext = new ConcurrentHashMap<>();

    // 分页默认参数
    private static final int FRIENDS_PER_PAGE = 21; // 一行7个,三行好友格
    private static final int GUI_SIZE = 27; // 箱子样式:3行
    private static final String GUI_TITLE = ChatColor.AQUA + "§l好友广场";

    // -- 自定义输入等待类型 --
    private enum InputType { ADD, REMARK, MSG, LEAVE }
    private static class InputState {
        InputType type;
        String toWho; // 目标玩家
        InputState(InputType t, String n) {
            type = t; toWho = n;
        }
    }

    @Override
    public void onEnable() {
        Bukkit.getPluginManager().registerEvents(this, this);
        Objects.requireNonNull(getCommand("friend")).setTabCompleter(this);
        initDatabase();
        getLogger().info("GUIFriends 数据库/事件/GUIs 已加载");
    }
    @Override
    public void onDisable() {
        try { if (dataSource != null) dataSource.close(); } catch (Exception ignored) {}
        getLogger().info("GUIFriends 关闭");
    }

    // ======================= 数据库初始化 =======================
    private void initDatabase() {
        try {
            File dbFile = new File(getDataFolder(), DB_FILE);
            if (!getDataFolder().exists()) getDataFolder().mkdirs();
            if (!dbFile.exists()) dbFile.createNewFile();
            Class.forName("org.sqlite.JDBC");
            dataSource = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath());
            Statement st = dataSource.createStatement();
            st.executeUpdate("CREATE TABLE IF NOT EXISTS friends (id INTEGER PRIMARY KEY AUTOINCREMENT, player1 VARCHAR(16), player2 VARCHAR(16), since VARCHAR(20), remark1 TEXT, remark2 TEXT, apply_msg TEXT, apply_pending INTEGER, unread INTEGER, last_online1 VARCHAR(20), last_online2 VARCHAR(20), last_msg_time VARCHAR(20))");
            st.executeUpdate("CREATE TABLE IF NOT EXISTS friend_messages (id INTEGER PRIMARY KEY AUTOINCREMENT, sender VARCHAR(16), receiver VARCHAR(16), content TEXT, time VARCHAR(20), is_read INTEGER DEFAULT 0)");
            st.executeUpdate("CREATE TABLE IF NOT EXISTS friend_leaves (id INTEGER PRIMARY KEY AUTOINCREMENT, from_p VARCHAR(16), to_p VARCHAR(16), content TEXT, time VARCHAR(20))");
            st.close();
        } catch (Exception e) {
            getLogger().log(Level.SEVERE, "数据库初始化异常", e);
        }
    }

    // ======================= 指令入口:打开主界面 =======================
    @Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (!(sender instanceof Player)) { sender.sendMessage("玩家专用!"); return true; }
        Player p = (Player)sender;
        openMainGUI(p, 1);
        return true;
    }

    // ======================= 主箱子GUI渲染 =======================
    public void openMainGUI(Player player, int page) {
        guiContext.put(player.getUniqueId(), "main");
        guiPage.put(player.getUniqueId(), page);
        // 查自己好友
        List<FriendListEntry> friends = getFriendEntries(player.getName());
        int totalPage = (int)Math.ceil(friends.size() * 1.0 / FRIENDS_PER_PAGE);
        page = Math.max(1, Math.min(totalPage == 0 ? 1 : totalPage, page));
        List<FriendListEntry> show = new ArrayList<>();
        int start = (page-1)*FRIENDS_PER_PAGE;
        for (int i=start; i<start+FRIENDS_PER_PAGE && i<friends.size(); i++)
            show.add(friends.get(i));
        Inventory inv = Bukkit.createInventory(null, GUI_SIZE, GUI_TITLE + ChatColor.DARK_GRAY + " §7[" + page + "/" + (totalPage==0?1:totalPage) + "]");
        int[] friendSlots = {10,11,12,13,14,15,16, 19,20,21,22,23,24,25};
        int idx=0;
        for (FriendListEntry ent : show) {
            if (idx>=friendSlots.length) break;
            ItemStack head = buildFriendHead(ent);
            inv.setItem(friendSlots[idx], head); idx++;
        }
        // 右下角添加好友按钮
        ItemStack addFriend = new ItemStack(Material.EMERALD_BLOCK);
        ItemMeta im = addFriend.getItemMeta();
        im.setDisplayName(ChatColor.GOLD + "➕ 添加好友");
        addFriend.setItemMeta(im);
        inv.setItem(8, addFriend);

        // 导航按钮
        if (page > 1) inv.setItem(18, navItem("上一页", Material.ARROW));
        if (page < totalPage) inv.setItem(26, navItem("下一页", Material.ARROW));

        // 搜索好友按钮
        inv.setItem(0, navItem("🔍 搜索/申请", Material.PAPER));
        player.openInventory(inv);
    }
    // 构造好友头像条目
    private ItemStack buildFriendHead(FriendListEntry ent) {
        ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
        ItemMeta meta = skull.getItemMeta();
        String disp = (ent.online?ChatColor.GREEN:"")
                + "§l" + ent.friendName
                + (ent.unread>0?ChatColor.RED+" ●":"");
        if (ent.remark!=null && !ent.remark.isEmpty()) {
            disp += ChatColor.DARK_AQUA + " (" + ent.remark + ")";
        }
        meta.setDisplayName(disp);
        List<String> lore = new ArrayList<>();
        if (ent.online) lore.add(ChatColor.GREEN+"[在线] 互动于: "+ent.lastMsgTime);
        else lore.add(ChatColor.GRAY+"[离线] 最后在线: "+ent.lastOnline);
        if (ent.applyPending) lore.add(ChatColor.YELLOW+"[等待验证对方同意]");
        if (ent.remark!=null && !ent.remark.isEmpty()) lore.add(ChatColor.AQUA+"备注: "+ent.remark);
        lore.add("");
        lore.add(ChatColor.GOLD+"左键:进入聊天对话");
        lore.add(ChatColor.YELLOW+"右键:更多操作(删除/备注/留言)");
        meta.setLore(lore);
        skull.setItemMeta(meta);
        return skull;
    }
    // 分页/导航物品
    private ItemStack navItem(String name, Material mat) {
        ItemStack it = new ItemStack(mat); ItemMeta im = it.getItemMeta();
        im.setDisplayName(ChatColor.AQUA + name);
        it.setItemMeta(im); return it;
    }
    // ======================= 交互事件:箱子点击 =======================
    @EventHandler
    public void onInventoryClick(InventoryClickEvent e) {
        if (!(e.getWhoClicked() instanceof Player)) return;
        Player p = (Player)e.getWhoClicked();
        Inventory inv = e.getInventory();
        if (e.getView().getTitle().startsWith(GUI_TITLE)) {
            e.setCancelled(true);
            int slot = e.getRawSlot();
            if (slot<0 || slot>=GUI_SIZE) return;
            ItemStack clicked = e.getCurrentItem();
            if (clicked==null || !clicked.hasItemMeta() || !clicked.getItemMeta().hasDisplayName()) return;
            String dn = ChatColor.stripColor(clicked.getItemMeta().getDisplayName());
            if (slot == 8) { // 添加好友
                inputStates.put(p.getUniqueId(), new InputState(InputType.ADD, null));
                p.closeInventory(); p.sendMessage(ChatColor.GOLD+"请输入要添加的玩家名:");
                return;
            }
            if (slot == 0) {// 搜索/申请入口
                inputStates.put(p.getUniqueId(), new InputState(InputType.ADD, null));
                p.closeInventory(); p.sendMessage(ChatColor.YELLOW+"请输入部分ID回车,可模糊查找;如输入完整ID将直接发起申请。");
                return;
            }
            if (slot == 18) { // 上一页
                openMainGUI(p, guiPage.getOrDefault(p.getUniqueId(), 1)-1); return;
            }
            if (slot == 26) { //下一页
                openMainGUI(p, guiPage.getOrDefault(p.getUniqueId(), 1)+1); return;
            }
            // 判断是否好友条目:通过Material.Player_Head
            if (clicked.getType()==Material.PLAYER_HEAD) {
                String friendName = dn.replaceAll("●.*", "").trim();
                if (e.isLeftClick()) {
                    // 打开聊天历史/输入界面
                    openFriendChatGUI(p, friendName, 1);
                } else if (e.isRightClick()) {
                    // 弹出操作面板:备注、留言、删除
                    openFriendOpsGUI(p, friendName);
                }
            }
        }
        // 其他界面略,可拓展更多界面类型如openFriendChatGUI操作
    }

    // 右键好友弹窗(菜单)
    public void openFriendOpsGUI(Player p, String friendName) {
        Inventory inv = Bukkit.createInventory(null, 9, ChatColor.AQUA+"好友管理:"+friendName);
        inv.setItem(2, navItem("📝 备注", Material.PAPER));
        inv.setItem(4, navItem("📨 留言", Material.BOOK));
        inv.setItem(6, navItem("❌ 删除", Material.BARRIER));
        p.openInventory(inv);
        guiContext.put(p.getUniqueId(), "ops:"+friendName);
    }

    @EventHandler
    public void onSubInvClick(InventoryClickEvent e) {
        if (!(e.getWhoClicked() instanceof Player)) return;
        Player p = (Player)e.getWhoClicked();
        String ctx = guiContext.getOrDefault(p.getUniqueId(),"null");
        if (!e.getView().getTitle().startsWith("好友管理:")) return;
        e.setCancelled(true);
        ItemStack it = e.getCurrentItem();
        if (it==null || !it.hasItemMeta()) return;
        String name = ChatColor.stripColor(it.getItemMeta().getDisplayName());
        String friendName = e.getView().getTitle().replace("好友管理:","").trim();
        if (name.contains("备注")) {
            inputStates.put(p.getUniqueId(), new InputState(InputType.REMARK, friendName));
            p.closeInventory(); p.sendMessage(ChatColor.YELLOW+"请输入新的备注内容,或输入“清空”删除备注。");
        } else if (name.contains("留言")) {
            inputStates.put(p.getUniqueId(), new InputState(InputType.LEAVE, friendName));
            p.closeInventory(); p.sendMessage(ChatColor.YELLOW+"请输入留言内容:");
        } else if (name.contains("删除")) {
            delFriend(p, friendName); p.closeInventory();
            p.sendMessage(ChatColor.RED+"已删除好友 "+friendName);
            Bukkit.getScheduler().runTaskLater(this,()->openMainGUI(p,1),10L);
        }
    }

    // 好友聊天GUI[简要,用书本组件实现]
    public void openFriendChatGUI(Player p, String friendName, int page) {
        List<MsgEntry> msgs = getFriendChatMsgs(p.getName(), friendName, 10, (page-1)*10);
        // 这里只简单用聊天栏互动,请自行参考进一步用书本/箱子多页完善
        p.closeInventory();
        p.sendMessage(ChatColor.AQUA+"与 "+friendName+" 聊天(共"+msgs.size()+"条):");
        for (MsgEntry m:msgs) {
            p.sendMessage((m.from.equals(p.getName()) ? ChatColor.YELLOW + "我: " : ChatColor.GRAY + m.from + ": ") + m.content + ChatColor.GRAY + " ["+m.time+"]");
        }
        inputStates.put(p.getUniqueId(), new InputState(InputType.MSG, friendName));
        p.sendMessage(ChatColor.GOLD+"请输入聊天消息内容,或输入“退出”返回主界面。");
    }

    // ======================= 聊天/备注/申请/留言输入交互 =======================
    @EventHandler
    public void onChatInput(AsyncPlayerChatEvent e) {
        Player p = e.getPlayer();
        UUID uid = p.getUniqueId();
        if (!inputStates.containsKey(uid)) return;
        e.setCancelled(true);
        String text = e.getMessage();
        InputState state = inputStates.remove(uid);
        switch (state.type) {
            case ADD:
                if (text.length()<=0) { p.sendMessage(ChatColor.RED+"不可为空名!"); return;}
                addFriendApply(p, text, ""); // 本例演示默认备注与申请留言留空
                break;
            case MSG:
                if ("退出".equalsIgnoreCase(text)) {
                    Bukkit.getScheduler().runTask(this,()->openMainGUI(p,1));
                    return;
                }
                sendChatMsg(p, state.toWho, text);
                Bukkit.getScheduler().runTask(this,()->openFriendChatGUI(p, state.toWho, 1));
                break;
            case LEAVE:
                leaveMsg(p, state.toWho, text);
                p.sendMessage(ChatColor.GREEN+"已留言给 "+state.toWho);
                Bukkit.getScheduler().runTask(this,()->openMainGUI(p,1));
                break;
            case REMARK:
                setRemark(p, state.toWho, "清空".equalsIgnoreCase(text) ? "" : text);
                p.sendMessage(ChatColor.GREEN+"备注已更新!");
                Bukkit.getScheduler().runTask(this,()->openMainGUI(p,1));
                break;
        }
    }

    // ========== 数据库相关 - 好友条目 ==========
    private static class FriendListEntry {
        String friendName, remark, lastOnline, lastMsgTime;
        boolean online = false, applyPending = false;
        int unread = 0;
        FriendListEntry(String n, boolean o, String r, boolean ap, int ur, String lo, String lm) {
            friendName=n; online=o; remark=r; applyPending=ap; unread=ur; lastOnline=lo; lastMsgTime=lm;
        }
    }
    private List<FriendListEntry> getFriendEntries(String myName) {
        List<FriendListEntry> res = new ArrayList<>();
        try {
            PreparedStatement ps = dataSource.prepareStatement(
                "SELECT * FROM friends WHERE player1=? OR player2=?"); ps.setString(1, myName); ps.setString(2,myName);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                String p1=rs.getString("player1"), p2=rs.getString("player2");
                boolean meP1=myName.equalsIgnoreCase(p1);
                String other = meP1?p2:p1;
                String remark = rs.getString(meP1?"remark1":"remark2");
                boolean pending = rs.getInt("apply_pending")==1;
                int unread = rs.getInt("unread");
                String lo = rs.getString(meP1?"last_online2":"last_online1");
                String lm = rs.getString("last_msg_time");
                boolean online = Bukkit.getPlayerExact(other)!=null;
                res.add(new FriendListEntry(other, online, remark, pending, unread, lo, lm));
            }
            rs.close(); ps.close();
        } catch (Exception e) {}
        res.sort((a,b)->Boolean.compare(b.online,a.online)); // 在线优先
        return res;
    }
    // 聊天记录获取(简版,分页,未读管理略)
    private static class MsgEntry { String from, content, time;
        MsgEntry(String f, String c, String t){from=f;content=c;time=t;}
    }
    private List<MsgEntry> getFriendChatMsgs(String my, String fr, int count, int skip) {
        List<MsgEntry> res = new ArrayList<>();
        try {
            PreparedStatement ps = dataSource.prepareStatement(
                "SELECT * FROM friend_messages WHERE (sender=? AND receiver=?) OR (sender=? AND receiver=?) ORDER BY id DESC LIMIT ? OFFSET ?");
            ps.setString(1,my);ps.setString(2,fr);ps.setString(3,fr);ps.setString(4,my);ps.setInt(5,count);ps.setInt(6,skip);
            ResultSet rs=ps.executeQuery();
            while (rs.next()) res.add(new MsgEntry(rs.getString("sender"),rs.getString("content"), rs.getString("time")));
            rs.close(); ps.close();
        } catch(Exception x){}
        Collections.reverse(res);
        return res;
    }

    // ========== 数据库相关 - 添加/删除/备注/聊天/留言 ==========
    private void addFriendApply(Player p, String to, String msg) {
        if (p.getName().equalsIgnoreCase(to)) { p.sendMessage("不可以添加自己为好友"); return; }
        if (isFriend(p.getName(), to)) { p.sendMessage("已是好友"); return; }
        // 暂简化为直接添加双向好友记录且等待同意,实际可做申请流程
        try (PreparedStatement st = dataSource.prepareStatement(
            "INSERT INTO friends(player1, player2, since, apply_msg, apply_pending, unread, last_online1, last_online2, last_msg_time) VALUES (?,?,?,?,1,0,?,?,?)")) {
            String now = nowStr();
            st.setString(1, p.getName()); st.setString(2, to);
            st.setString(3, now); st.setString(4, msg);
            st.setString(5, now); st.setString(6, now); st.setString(7, now);
            st.executeUpdate();
            p.sendMessage(ChatColor.GREEN+"已发出添加好友请求给 "+to);
            Player t = Bukkit.getPlayerExact(to);
            if (t!=null) t.sendMessage(ChatColor.YELLOW+p.getName()+" 请求添加你为好友, 请同意!");
        } catch (Exception e) { p.sendMessage("申请失败:"+e.getMessage()); }
    }
    private void delFriend(Player p, String fr) {
        try (PreparedStatement st=dataSource.prepareStatement(
            "DELETE FROM friends WHERE (player1=? AND player2=?) OR (player1=? AND player2=?)")) {
            st.setString(1,p.getName()); st.setString(2,fr); st.setString(3,fr); st.setString(4,p.getName());
            st.executeUpdate();
        } catch (Exception ignored){}
    }
    private void setRemark(Player p, String fr, String remark) {
        String sql = "UPDATE friends SET remark1=? WHERE player1=? AND player2=?";
        String alt = "UPDATE friends SET remark2=? WHERE player2=? AND player1=?";
        try {
            int n = 0;
            try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
                ps.setString(1, remark); ps.setString(2,p.getName()); ps.setString(3,fr);
                n = ps.executeUpdate();
            }
            if (n==0) try (PreparedStatement ps = dataSource.prepareStatement(alt)) {
                ps.setString(1, remark); ps.setString(2,p.getName()); ps.setString(3,fr);
                ps.executeUpdate();
            }
        } catch (Exception ignored) {}
    }
    private boolean isFriend(String a, String b) {
        try (PreparedStatement ps = dataSource.prepareStatement("SELECT 1 FROM friends WHERE (player1=? AND player2=?) OR (player1=? AND player2=?)")) {
            ps.setString(1,a);ps.setString(2,b);ps.setString(3,b);ps.setString(4,a);
            try(ResultSet rs=ps.executeQuery()){ return rs.next(); }
        }catch(Exception e){return false;}
    }
    private void sendChatMsg(Player p, String fr, String msg) {
        String now=nowStr();
        try (PreparedStatement st=dataSource.prepareStatement(
            "INSERT INTO friend_messages(sender,receiver,content,time,is_read) VALUES(?,?,?,?,'0')")) {
            st.setString(1,p.getName());st.setString(2,fr);st.setString(3,msg);st.setString(4,now);st.executeUpdate();
            p.sendMessage("§e[我→"+fr+"]:"+msg);
            Player t=Bukkit.getPlayerExact(fr);
            if (t!=null) t.sendMessage("§a["+p.getName()+"(好友) →你]:"+msg);
        } catch (Exception e){}
    }
    private void leaveMsg(Player p, String fr, String msg) {
        try (PreparedStatement st=dataSource.prepareStatement(
            "INSERT INTO friend_leaves(from_p, to_p, content, time) VALUES (?,?,?,?)")) {
            st.setString(1,p.getName());st.setString(2,fr);st.setString(3,msg);st.setString(4,nowStr());
            st.executeUpdate();
        } catch (Exception ignored) {}
    }
    private String nowStr(){
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
    }

    // 自动Tab补全
    @Override
    public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
        return Collections.emptyList(); // 全部GUI操作无需补全
    }

    // 离线更新好友表最后一次在线
    @EventHandler
    public void onQuit(PlayerQuitEvent event) {
        String name=event.getPlayer().getName(), now=nowStr();
        try (PreparedStatement ps=dataSource.prepareStatement(
            "UPDATE friends SET last_online1=CASE WHEN player1=? THEN ? ELSE last_online1 END, last_online2=CASE WHEN player2=? THEN ? ELSE last_online2 END WHERE player1=? OR player2=?")) {
            ps.setString(1, name);ps.setString(2,now);ps.setString(3,name);ps.setString(4,now);ps.setString(5,name);ps.setString(6,name);
            ps.executeUpdate();
        }catch(Exception ignore){}
    }
}

上一篇: UIPosts下一篇: UIPosts

举报内容

意见反馈