UIPosts

UIPosts.java v3.0
集成好友系统的增强贴子系统,支持社交标识、好友帖子、通知与透传API,数据库db后缀。
作者: ScriptIrc Engine

命令列表

  • uiposts打开或操作多UI贴子界面,参数支持chest/book/friend/reload等

权限列表

  • social.post发帖权限
  • social.friend好友模块权限
  • social.admin社交系统管理权限
package org.sircustom.uiposts;

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.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import javax.sql.DataSource;
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 UIPosts
 * @author ScriptIrc Engine
 * @version 3.0
 * @description 集成好友系统的增强贴子系统,支持社交标识、好友帖子、通知与透传API,数据库db后缀。
 * [command]uiposts|打开或操作多UI贴子界面,参数支持chest/book/friend/reload等[/command]
 * [Permission]social.post|发帖权限[/Permission]
 * [Permission]social.friend|好友模块权限[/Permission]
 * [Permission]social.admin|社交系统管理权限[/Permission]
 */
public class UIPosts extends JavaPlugin implements Listener, TabCompleter {
    // ------- 数据库文件路径及连接 -------
    private static final String DB_FILE = "uiposts.db";
    private Connection dataSource;

    // ------- 好友系统API桥 -------
    /**
     * 好友API桥接口: 需FriendSystem插件注入此桥接实例
     */
    public interface FriendApiBridge {
        boolean isFriend(UUID viewer, UUID target);
        List<UUID> getFriendUUIDs(UUID player);
        String getNickname(UUID uuid); // 可选
    }
    private static FriendApiBridge friendApiBridge = null;
    public static void setFriendApiBridge(FriendApiBridge bridge) { friendApiBridge = bridge; }
    public static FriendApiBridge getFriendApiBridge() { return friendApiBridge; }

    // ------- 配置、界面变量 -------
    private Map<String, ConfigurationSection> guiConfig = new HashMap<>();
    private final Map<UUID, Boolean> awaitingPostInput = new ConcurrentHashMap<>();
    private final Map<UUID, Integer> awaitingReply = new ConcurrentHashMap<>();
    private final Map<Integer, List<Reply>> replyCache = new ConcurrentHashMap<>();
    private String cachedTime;
    private static final int MAX_POST_LENGTH = 100, MAX_REPLY_LENGTH = 50, CACHE_TTL = 300, MAX_PAGES = 100;
    // 社交特性配置
    private boolean enableFriend = true, friendPostNotify = true;
    private String friendSymbol = ChatColor.LIGHT_PURPLE + "❤";

    @Override
    public void onEnable() {
        saveDefaultConfig();
        reloadConfig();
        this.loadFriendConfig();
        Bukkit.getPluginManager().registerEvents(this, this);
        if (getCommand("uiposts") != null) {
            getCommand("uiposts").setTabCompleter(this);
        }
        this.initDatabase();
        this.loadGuiConfig();
        startScheduledTasks();
        getLogger().info("UIPosts v3.0 社交增强版已启动 - 数据库已连接");
    }

    @Override
    public void onDisable() {
        replyCache.clear();
        closeDatabase();
        getLogger().info("UIPosts 已安全关闭");
    }

    // ------- 配置加载 -------
    private void loadGuiConfig() {
        FileConfiguration config = getConfig();
        guiConfig.put("chest", config.getConfigurationSection("gui.chest"));
        guiConfig.put("book", config.getConfigurationSection("gui.book"));
        getLogger().info("已加载GUI配置");
    }
    // 读取社交增强配置
    private void loadFriendConfig() {
        FileConfiguration config = getConfig();
        enableFriend = config.getBoolean("enable_friend_features", true);
        friendPostNotify = config.getBoolean("friend_post_notify", true);
        friendSymbol = ChatColor.translateAlternateColorCodes('&', config.getString("friend_mark_symbol", "&d❤"));
    }

    // ------- 数据库初始化/升级 -------
    private void initDatabase() {
        try {
            File dbFile = new File(getDataFolder(), DB_FILE);
            if (!getDataFolder().exists()) getDataFolder().mkdirs();
            boolean isNewDB = !dbFile.exists();
            if (isNewDB) dbFile.createNewFile();
            Class.forName("org.sqlite.JDBC");
            dataSource = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath());
            // 新表结构支持UUID
            try (Statement st = dataSource.createStatement()) {
                st.executeUpdate("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, player_uuid TEXT, player VARCHAR(16) NOT NULL, content TEXT NOT NULL, time VARCHAR(20) NOT NULL)");
                st.executeUpdate("CREATE TABLE IF NOT EXISTS replies (id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, player_uuid TEXT, player VARCHAR(16) NOT NULL, content TEXT NOT NULL, time VARCHAR(20) NOT NULL, FOREIGN KEY(post_id) REFERENCES posts(id))");
                st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_post_uuid ON posts(player_uuid)");
                st.executeUpdate("CREATE INDEX IF NOT EXISTS idx_reply_postid ON replies(post_id)");
            }
        } catch (Exception e) {
            getLogger().log(Level.SEVERE, "数据库初始化失败", e);
        }
    }
    private void closeDatabase() {
        try { if (dataSource != null && !dataSource.isClosed()) dataSource.close(); } catch (SQLException e) { getLogger().warning("关闭数据库失败: " + e.getMessage()); }
    }

    // ------- 指令 -------
    @Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (!(sender instanceof Player)) { sender.sendMessage(ChatColor.RED + "仅玩家可以使用此命令"); return true; }
        Player player = (Player) sender;
        // 管理编辑
        if (args.length > 0 && "reload".equalsIgnoreCase(args[0]) && player.hasPermission("social.admin")) {
            reloadConfig();
            loadGuiConfig();
            loadFriendConfig();
            player.sendMessage(ChatColor.GREEN + "UIPosts 配置已重载"); return true;
        }
        // 好友贴子专属视图:/uiposts friend x
        if (args.length > 0 && ("friend".equalsIgnoreCase(args[0])||"friends".equalsIgnoreCase(args[0]))) {
            openFriendPostsGUI(player, args.length>=2? Math.max(1, Math.min(MAX_PAGES, getInt(args[1],1))) : 1);
            return true;
        }
        String type = (args.length == 0) ? "chest" : args[0].toLowerCase();
        int page = 1;
        if (args.length >= 2) {
            page = Math.max(1, Math.min(MAX_PAGES, getInt(args[1],1)));
        }
        switch (type) {
            case "chest": openChestGUI(player, page); break;
            case "book": openBookGUI(player, page); break;
            default: player.sendMessage(ChatColor.YELLOW + "/uiposts [chest|book|friend] [页码]");
        }
        return true;
    }
    @Override
    public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
        if (args.length == 1) return Arrays.asList("chest", "book", "friend", "reload");
        return Collections.emptyList();
    }
    private int getInt(String s, int def) { try { return Integer.parseInt(s);}catch(Exception e){return def;} }

    // ------- 核心功能:箱子界面UI --------
    private void openChestGUI(Player player, int page) {
        ConfigurationSection cfg = guiConfig.get("chest");
        if (cfg == null) { player.sendMessage(ChatColor.RED + "未配置箱子界面"); return; }
        int size = cfg.getInt("size", 27);
        String title = parsePlaceholders(cfg.getString("title", "§b§l帖子广场"), player, page);
        Inventory inv = Bukkit.createInventory(null, size, title);
        int postsPerPage = cfg.getIntegerList("post_slot").size();
        List<Post> posts = getPostsByPage(page, postsPerPage);
        List<Integer> postSlots = cfg.getIntegerList("post_slot");
        for (int i = 0; i < Math.min(posts.size(), postSlots.size()); i++) {
            Post post = posts.get(i);
            inv.setItem(postSlots.get(i), createPostItem(post, player));
        }
        inv.setItem(cfg.getInt("post_button_slot", 4), createButton(
                Material.ANVIL,
                cfg.getString("post_button", "§a§l我要发帖")
        ));
        inv.setItem(cfg.getInt("book_button_slot", 22), createButton(
                Material.WRITTEN_BOOK,
                cfg.getString("jump_book", "§6查看书本模式")
        ));
        // ★好友帖子入口
        if (enableFriend) {
            inv.setItem(cfg.getInt("friend_posts_slot", 24), createButton(
                    Material.PLAYER_HEAD,
                    ChatColor.AQUA + "好友帖子"
            ));
        }
        if (hasNextPage(page, postsPerPage)) {
            inv.setItem(cfg.getInt("next_slot", 25), createButton(
                    Material.ARROW, "§e下一页"
            ));
        }
        if (page > 1) {
            inv.setItem(cfg.getInt("prev_slot", 19), createButton(
                    Material.ARROW, "§e上一页"
            ));
        }
        player.openInventory(inv);
        storePlayerState(player, "chest", page);
    }

    // 好友帖子界面(只显示好友的帖子)
    public void openFriendPostsGUI(Player player, int page) {
        if (!enableFriend || getFriendApiBridge()==null) {
            player.sendMessage(ChatColor.RED + "好友功能未启用或未联动!");
            return;
        }
        List<UUID> friends = getFriendApiBridge().getFriendUUIDs(player.getUniqueId());
        if (friends.isEmpty()) {
            player.sendMessage(ChatColor.YELLOW+"你还没有好友,快去添加吧!");
            return;
        }
        ConfigurationSection cfg = guiConfig.get("chest");
        int size = cfg.getInt("size", 27);
        String title = ChatColor.AQUA+"好友帖子 §7第"+page+"页";
        Inventory inv = Bukkit.createInventory(null, size, title);
        int postsPerPage = cfg.getIntegerList("post_slot").size();
        List<Post> posts = getPostsByPlayers(friends, page, postsPerPage);
        List<Integer> postSlots = cfg.getIntegerList("post_slot");
        for (int i = 0; i < Math.min(posts.size(), postSlots.size()); i++)
            inv.setItem(postSlots.get(i), createPostItem(posts.get(i), player));
        inv.setItem(cfg.getInt("post_button_slot", 4), createButton(
            Material.ANVIL, cfg.getString("post_button", "§a§l我要发帖") ));
        if (hasNextPageCustom(posts.size(), page, postsPerPage)) {
            inv.setItem(cfg.getInt("next_slot", 25), createButton(
                    Material.ARROW, "§e下一页"
            ));
        }
        if (page > 1) {
            inv.setItem(cfg.getInt("prev_slot", 19), createButton(
                    Material.ARROW, "§e上一页"
            ));
        }
        player.openInventory(inv);
        storePlayerState(player, "friend", page);
    }

    // ---------------------- 事件监听/交互控制 -----------------------
    @EventHandler
    public void onInventoryClick(InventoryClickEvent event) {
        if (!(event.getWhoClicked() instanceof Player)) return;
        Player player = (Player) event.getWhoClicked();
        ItemStack clicked = event.getCurrentItem();
        if (clicked == null || !clicked.hasItemMeta()) return;
        if (!isUIPostsInventory(event.getView().getTitle())) return;
        event.setCancelled(true);
        PlayerState state = getPlayerState(player);
        if (state == null) return;
        String displayName = ChatColor.stripColor(clicked.getItemMeta().getDisplayName());
        if (clicked.getType() == Material.ANVIL && displayName.contains("发帖")) {
            player.closeInventory();
            askForPostInput(player);
        } else if (clicked.getType() == Material.WRITTEN_BOOK && displayName.contains("书本模式")) {
            player.closeInventory();
            openBookGUI(player, 1);
        } else if (clicked.getType() == Material.PLAYER_HEAD && displayName.contains("好友帖子")) {
            player.closeInventory();
            openFriendPostsGUI(player, 1);
        } else if (clicked.getType() == Material.ARROW) {
            if (state.guiType.equals("friend")) {
                // 分页
                int page = state.page;
                if (displayName.contains("上一页")) openFriendPostsGUI(player, Math.max(1,page-1));
                if (displayName.contains("下一页")) openFriendPostsGUI(player, page+1);
            } else {
                handlePaginationClick(player, state, displayName);
            }
        } else if (clicked.getType() == Material.WRITABLE_BOOK) {
            handlePostClick(player, event.getSlot(), state.page);
        }
    }
    // ------- 聊天栏发帖/回复 -------
    @EventHandler
    public void onChatInput(AsyncPlayerChatEvent event) {
        Player player = event.getPlayer();
        UUID uuid = player.getUniqueId();
        String message = event.getMessage();
        if (awaitingPostInput.containsKey(uuid)) {
            event.setCancelled(true);
            if ("取消".equalsIgnoreCase(message)) {
                player.sendMessage(ChatColor.RED + "已取消发帖");
                awaitingPostInput.remove(uuid);
                return;
            }
            Bukkit.getScheduler().runTask(this, () -> {
                savePost(player, message);
                awaitingPostInput.remove(uuid);
                openChestGUI(player, 1);
            });
            return;
        }
        if (awaitingReply.containsKey(uuid)) {
            event.setCancelled(true);
            if ("取消".equalsIgnoreCase(message)) {
                player.sendMessage(ChatColor.RED + "已取消留言");
                awaitingReply.remove(uuid);
                return;
            }
            int postId = awaitingReply.get(uuid);
            Bukkit.getScheduler().runTask(this, () -> {
                saveReply(player, postId, message);
                awaitingReply.remove(uuid);
                showBookForPost(player, getPost(postId), player);
            });
        }
    }

    // ------- 帖子相关操作核心 -------
    private List<Post> getPostsByPage(int page, int perPage) {
        int offset = (page - 1) * perPage;
        List<Post> posts = new ArrayList<>();
        if (perPage <= 0) return posts;
        String sql = "SELECT id, player_uuid, player, content, time FROM posts ORDER BY id DESC LIMIT ? OFFSET ?";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setInt(1, perPage);
            ps.setInt(2, offset);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    posts.add(new Post(
                        rs.getInt("id"),
                        rs.getString("player_uuid"),
                        rs.getString("player"),
                        rs.getString("content"),
                        rs.getString("time")
                    ));
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "查询帖子失败", e); }
        return posts;
    }
    // 查询多个UUID作者帖子(分页)
    private List<Post> getPostsByPlayers(List<UUID> uuids, int page, int perPage) {
        List<Post> posts = new ArrayList<>();
        if (uuids.isEmpty()) return posts;
        int offset = (page - 1) * perPage;
        // 拼接uuid列表
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < uuids.size(); i++) {
            sb.append("?"); if (i<uuids.size()-1) sb.append(",");
        }
        String sql = "SELECT id, player_uuid, player, content, time FROM posts WHERE player_uuid IN ("+sb+") ORDER BY id DESC LIMIT ? OFFSET ?";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            int idx = 1;
            for (UUID uuid : uuids) ps.setString(idx++, uuid.toString());
            ps.setInt(idx++, perPage);
            ps.setInt(idx, offset);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    posts.add(new Post(
                        rs.getInt("id"),
                        rs.getString("player_uuid"),
                        rs.getString("player"),
                        rs.getString("content"),
                        rs.getString("time")
                    ));
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "好友帖子查询失败", e); }
        return posts;
    }
    // 新增:保存时支持uuid
    private void savePost(Player player, String content) {
        String time = getCurrentTime();
        String playerName = player.getName();
        String uuid = player.getUniqueId().toString();
        if (content.length() > MAX_POST_LENGTH) { content = content.substring(0, MAX_POST_LENGTH); }
        String sql = "INSERT INTO posts(player_uuid, player, content, time) VALUES(?,?,?,?)";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setString(1, uuid);
            ps.setString(2, playerName);
            ps.setString(3, content);
            ps.setString(4, time);
            ps.executeUpdate();
            replyCache.clear();
            player.sendMessage(ChatColor.GREEN + "帖子发布成功!");
            // ★发帖通知
            if (enableFriend && friendPostNotify && getFriendApiBridge()!=null) {
                List<UUID> friends = getFriendApiBridge().getFriendUUIDs(player.getUniqueId());
                for (UUID friend : friends) {
                    Player onlineFriend = Bukkit.getPlayer(friend);
                    if (onlineFriend != null) {
                        onlineFriend.sendMessage(ChatColor.YELLOW + player.getName() + " 发布了新帖子: " + truncate(content, 20));
                    }
                }
            }
        } catch (SQLException e) {
            getLogger().log(Level.SEVERE, "保存帖子失败", e);
            player.sendMessage(ChatColor.RED + "发帖失败,请稍后再试");
        }
    }
    private void saveReply(Player player, int postId, String content) {
        String time = getCurrentTime();
        String playerName = player.getName();
        String uuid = player.getUniqueId().toString();
        if (content.length() > MAX_REPLY_LENGTH) { content = content.substring(0, MAX_REPLY_LENGTH); }
        String sql = "INSERT INTO replies(post_id, player_uuid, player, content, time) VALUES(?,?,?,?,?)";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setInt(1, postId);
            ps.setString(2, uuid);
            ps.setString(3, playerName);
            ps.setString(4, content);
            ps.setString(5, time);
            ps.executeUpdate();
            replyCache.remove(postId);
            player.sendMessage(ChatColor.GREEN + "留言成功!");
        } catch (SQLException e) {
            getLogger().log(Level.SEVERE, "保存留言失败", e);
            player.sendMessage(ChatColor.RED + "留言失败,请稍后再试");
        }
    }

    // ------- 帖子、UI展示相关 -------
    // 帖子条目(带好友标记)
    private ItemStack createPostItem(Post post, Player viewer) {
        ItemStack item = new ItemStack(Material.WRITABLE_BOOK);
        ItemMeta meta = item.getItemMeta();
        String authorDisplay = post.player;
        boolean friend = false;
        if (enableFriend && viewer!=null && getFriendApiBridge()!=null && post.player_uuid != null) {
            try { UUID au = UUID.fromString(post.player_uuid); friend = getFriendApiBridge().isFriend(viewer.getUniqueId(), au); } catch(Exception ignore){}
        }
        if (friend) authorDisplay += " " + friendSymbol;
        meta.setDisplayName(ChatColor.GREEN + "【" + authorDisplay + "】 - " + post.time);
        List<String> lore = new ArrayList<>();
        lore.add(ChatColor.WHITE + truncate(post.content, 25));
        lore.add(friend ? (ChatColor.LIGHT_PURPLE + "您的好友发帖!") : ChatColor.GRAY + "点击查看/留言");
        meta.setLore(lore);
        item.setItemMeta(meta);
        return item;
    }
    private ItemStack createButton(Material material, String name) {
        ItemStack item = new ItemStack(material);
        ItemMeta meta = item.getItemMeta();
        meta.setDisplayName(name);
        item.setItemMeta(meta);
        return item;
    }
    // 书本内容格式化,带好友标识、回复标识
    private String formatPostForBook(Post post, Player viewer) {
        StringBuilder sb = new StringBuilder();
        boolean isFriend = false;
        if (enableFriend && viewer != null && getFriendApiBridge()!=null && post.player_uuid != null) try {
            isFriend = getFriendApiBridge().isFriend(viewer.getUniqueId(), UUID.fromString(post.player_uuid));
        } catch(Exception ignore){}
        sb.append(ChatColor.BLUE).append("【").append(post.player).append(isFriend?friendSymbol:"").append("】 ")
                .append(ChatColor.DARK_GRAY).append(post.time).append("\n");
        sb.append(ChatColor.BLACK).append(post.content).append("\n\n");
        List<Reply> replies = getReplies(post.id);
        if (!replies.isEmpty()) {
            sb.append(ChatColor.DARK_GRAY).append("--- 留言 ---\n");
            int maxReplies = Math.min(replies.size(), 3);
            for (int i = 0; i < maxReplies; i++) {
                Reply reply = replies.get(i);
                boolean friendReply = false;
                if (enableFriend && viewer != null && getFriendApiBridge()!=null && reply.player_uuid != null) try {
                    friendReply = getFriendApiBridge().isFriend(viewer.getUniqueId(), UUID.fromString(reply.player_uuid));
                } catch(Exception ignore2){}
                sb.append(ChatColor.GRAY).append("• ").append(reply.player)
                        .append(friendReply?friendSymbol:"").append(": ")
                        .append(ChatColor.DARK_GREEN).append(reply.content)
                        .append("\n");
            }
            if (replies.size() > 3) {
                sb.append(ChatColor.GRAY).append("... 还有").append(replies.size() - 3).append("条留言\n");
            }
            sb.append("\n").append(ChatColor.DARK_AQUA).append("点击书本外区域输入留言内容");
        } else {
            sb.append(ChatColor.GRAY).append("暂无留言\n");
        }
        sb.append("\n");
        return sb.toString();
    }
    // 打开书本UI
    private void openBookGUI(Player player, int page) {
        ConfigurationSection cfg = guiConfig.get("book");
        if (cfg == null) { player.sendMessage(ChatColor.RED + "未配置书本界面"); return; }
        int postsPerPage = cfg.getInt("posts_per_page", 5);
        ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
        BookMeta meta = (BookMeta) book.getItemMeta();
        meta.setTitle(parsePlaceholders(cfg.getString("title", "§b§l帖子广场"), player, page));
        meta.setAuthor("UIPosts");
        List<String> pages = generateBookPages(page, postsPerPage, player);
        meta.setPages(pages);
        book.setItemMeta(meta);
        ItemStack oldItem = player.getInventory().getItemInMainHand().clone();
        player.getInventory().setItemInMainHand(book);
        new BukkitRunnable() {
            @Override
            public void run() {
                player.openBook(book);
                player.getInventory().setItemInMainHand(oldItem);
            }
        }.runTaskLater(this, 2L);
        storePlayerState(player, "book", page);
    }
    private void showBookForPost(Player player, Post post, Player viewer) {
        if (post == null) {
            player.sendMessage(ChatColor.RED + "帖子不存在或已被删除");
            return;
        }
        ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
        BookMeta meta = (BookMeta) book.getItemMeta();
        meta.setTitle("帖子详情");
        meta.setAuthor("UIPosts");
        meta.setPages(Collections.singletonList(formatPostForBook(post, viewer)));
        book.setItemMeta(meta);
        ItemStack oldItem = player.getInventory().getItemInMainHand().clone();
        player.getInventory().setItemInMainHand(book);
        new BukkitRunnable() {
            @Override
            public void run() {
                player.openBook(book);
                player.getInventory().setItemInMainHand(oldItem);
            }
        }.runTaskLater(this, 2L);
        askForReplyInput(player, post.id);
    }
    private List<String> generateBookPages(int currentPage, int postsPerPage, Player viewer) {
        List<String> pages = new ArrayList<>();
        List<Post> posts = getPostsByPage(currentPage, postsPerPage);
        if (posts.isEmpty()) {
            pages.add("§7暂无帖子\n§a请使用箱子界面发帖!");
            return pages;
        }
        StringBuilder currentContent = new StringBuilder();
        for (Post post : posts) {
            String postText = formatPostForBook(post, viewer);
            if (currentContent.length() + postText.length() > 1500) {
                pages.add(currentContent.toString());
                currentContent = new StringBuilder();
            }
            currentContent.append(postText);
        }
        if (currentContent.length() > 0) {
            pages.add(currentContent.toString());
        }
        pages.add(generateNavigationPage(currentPage));
        return pages;
    }
    private String generateNavigationPage(int currentPage) {
        int totalPages = getTotalPages(guiConfig.get("book").getInt("posts_per_page", 5));
        StringBuilder sb = new StringBuilder("§l§n导航菜单§r\n\n");
        sb.append("§7当前页: §e").append(currentPage).append("§7/§e").append(totalPages).append("\n\n");
        sb.append("§6[命令导航]\n");
        if (currentPage > 1) {
            sb.append("§a/up book ").append(currentPage - 1).append(" §7- 上一页\n");
        }
        if (currentPage < totalPages) {
            sb.append("§a/up book ").append(currentPage + 1).append(" §7- 下一页\n");
        }
        sb.append("§a/up chest §7- 返回箱子界面\n");
        return sb.toString();
    }
    // ------------------- 通用方法与内部类 ---------------------
    private List<Reply> getReplies(int postId) {
        if (replyCache.containsKey(postId)) {
            return replyCache.get(postId);
        }
        List<Reply> replies = new ArrayList<>();
        String sql = "SELECT player_uuid, player, content, time FROM replies WHERE post_id = ? ORDER BY id ASC";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setInt(1, postId);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    replies.add(new Reply(
                        rs.getString("player_uuid"),
                        rs.getString("player"),
                        rs.getString("content"),
                        rs.getString("time")
                    ));
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "查询留言失败", e); }
        replyCache.put(postId, replies);
        return replies;
    }
    private void askForPostInput(Player player) {
        player.sendMessage(ChatColor.GOLD + "请在聊天栏输入您的帖子内容(最多100字)");
        player.sendMessage(ChatColor.GRAY + "输入" + ChatColor.RED + "取消" + ChatColor.GRAY + "来取消操作");
        awaitingPostInput.put(player.getUniqueId(), true);
    }
    private void askForReplyInput(Player player, int postId) {
        player.sendMessage(ChatColor.GOLD + "请输入您的留言内容(最多50字)");
        player.sendMessage(ChatColor.GRAY + "输入" + ChatColor.RED + "取消" + ChatColor.GRAY + "来取消操作");
        awaitingReply.put(player.getUniqueId(), postId);
    }
    private int getTotalPages(int perPage) {
        if (perPage <= 0) return 1;
        String sql = "SELECT COUNT(*) AS total FROM posts";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    int totalPosts = rs.getInt("total");
                    return (int) Math.ceil((double) totalPosts / perPage);
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "计算总页数失败", e); }
        return 1;
    }
    private boolean hasNextPage(int currentPage, int perPage) {
        int totalPages = getTotalPages(perPage);
        return currentPage < totalPages;
    }
    private boolean hasNextPageCustom(int total, int curPage, int pageSize) {
        int pageCt = (total + pageSize - 1) / pageSize;
        return curPage < pageCt;
    }
    private void handlePaginationClick(Player player, PlayerState state, String buttonName) {
        if (buttonName.contains("上一页") && state.page > 1) {
            if ("chest".equals(state.guiType)) openChestGUI(player, state.page - 1);
            else openBookGUI(player, state.page - 1);
        } else if (buttonName.contains("下一页")) {
            if ("chest".equals(state.guiType)) openChestGUI(player, state.page + 1);
            else openBookGUI(player, state.page + 1);
        }
    }
    private void handlePostClick(Player player, int slot, int page) {
        ConfigurationSection cfg = guiConfig.get("chest");
        if (cfg == null) return;
        List<Integer> postSlots = cfg.getIntegerList("post_slot");
        if (!postSlots.contains(slot)) return;
        int index = postSlots.indexOf(slot);
        int postsPerPage = postSlots.size();
        int offset = (page - 1) * postsPerPage + index;
        Post post = getPostByOffset(offset);
        if (post != null) {
            showBookForPost(player, post, player);
        }
    }
    private Post getPostByOffset(int offset) {
        String sql = "SELECT id, player_uuid, player, content, time FROM posts ORDER BY id DESC LIMIT 1 OFFSET ?";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setInt(1, offset);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return new Post(
                            rs.getInt("id"),
                            rs.getString("player_uuid"),
                            rs.getString("player"),
                            rs.getString("content"),
                            rs.getString("time")
                    );
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "按偏移量查询帖子失败", e); }
        return null;
    }
    private Post getPost(int postId) {
        String sql = "SELECT player_uuid, player, content, time FROM posts WHERE id = ?";
        try (PreparedStatement ps = dataSource.prepareStatement(sql)) {
            ps.setInt(1, postId);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return new Post(
                            postId,
                            rs.getString("player_uuid"),
                            rs.getString("player"),
                            rs.getString("content"),
                            rs.getString("time")
                    );
                }
            }
        } catch (SQLException e) { getLogger().log(Level.SEVERE, "查询单个帖子失败", e); }
        return null;
    }
    private String truncate(String text, int maxLength) { return text.length() > maxLength ? text.substring(0, maxLength) + "..." : text; }
    private String getCurrentTime() { if (cachedTime != null) { return cachedTime; } return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); }
    private void startScheduledTasks() {
        new BukkitRunnable() { @Override public void run() { updateTimeCache(); } }.runTaskTimerAsynchronously(this, 0, 20 * 60 * 60);
        new BukkitRunnable() { @Override public void run() { replyCache.clear(); } }.runTaskTimerAsynchronously(this, 0, 20 * CACHE_TTL);
    }
    private void updateTimeCache() {
        try { cachedTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); } catch (Exception e) { getLogger().warning("时间更新失败: " + e.getMessage()); } }
    private String parsePlaceholders(String text, Player player, int page) {
        String parsed = text
                .replace("{player}", player.getName())
                .replace("{page}", String.valueOf(page))
                .replace("{pages}", String.valueOf(getTotalPages(guiConfig.get("chest").getIntegerList("post_slot").size())));
        if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
            try { return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, parsed); } catch (Exception e) { getLogger().warning("PlaceholderAPI 处理失败: " + e.getMessage()); }
        }
        return parsed;
    }

    // --------------------- 内部数据结构 ---------------------
    private static class Post {
        final int id; final String player_uuid; final String player; final String content; final String time;
        Post(int id, String player_uuid, String player, String content, String time) {
            this.id = id; this.player_uuid = player_uuid; this.player = player;
            this.content = content; this.time = time;
        }
    }
    private static class Reply {
        final String player_uuid; final String player; final String content; final String time;
        Reply(String player_uuid, String player, String content, String time) {
            this.player_uuid = player_uuid; this.player = player; this.content = content; this.time = time;
        }
    }
    private static class PlayerState {
        final String guiType; final int page;
        PlayerState(String guiType, int page) { this.guiType = guiType; this.page = page; }
    }
    private void storePlayerState(Player player, String type, int page) {
        player.setMetadata("uiposts_state", new FixedMetadataValue(this, type + ":" + page));
    }
    private PlayerState getPlayerState(Player player) {
        if (!player.hasMetadata("uiposts_state")) return null;
        String[] parts = player.getMetadata("uiposts_state").get(0).asString().split(":");
        if (parts.length != 2) return null;
        return new PlayerState(parts[0], Integer.parseInt(parts[1]));
    }
    private boolean isUIPostsInventory(String title) { return title.contains("帖子广场")||title.contains("好友帖子"); }
}

上一篇: UIPosts下一篇: FriendSystem

举报内容

意见反馈