diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbbecab --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# CY_beibao 来自Sdf1的背包插件 +## 功能介绍 +### 1. 独立云存储,解决背包爆满、箱子爆满的问题 +### 2. api对接,预留接口可供其他插件对接 +### 3. 操作简便 + +## 食用方法: +## 1. 下载,从[点击此处](https://github.com/ypsdf1/CY_beibao/releases)下载我们的最新版 +## 2. 安装,把jar丢进你服务器的plugin +## 3. 启动,如果你之前使用过我们的1.01内测版、1.0测试版,记得删除旧的CY_beibao文件,如果没有,直接启动服务器或者运行的服务器打个stop +## 4. 打开背包:/cy diff --git a/src/main/java/com/CY/Main.java b/src/main/java/com/CY/Main.java deleted file mode 100644 index 96661f5..0000000 --- a/src/main/java/com/CY/Main.java +++ /dev/null @@ -1,432 +0,0 @@ -package com.CY; - -import net.milkbowl.vault.economy.Economy; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -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.inventory.InventoryCloseEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.logging.Level; - -public class Main extends JavaPlugin implements CommandExecutor, Listener { - - private static final int PAGE_SIZE = 45; - private static final int UNLOCK_THRESHOLD = 40; - private static final String MAIN_TITLE = "§6§l会员中心"; - private static final String INFO_TITLE = "§6§l会员信息"; - private static final String SHOP_TITLE = "§6§l商城"; - private static final String STORAGE_TITLE = "§6§l主仓"; - - private Economy economy; - private Connection db; - private final List shopItems = new ArrayList(); - private final Map storagePages = new HashMap(); - private final Map> storageCache = new HashMap>(); - - @Override - public void onEnable() { - getDataFolder().mkdirs(); - setupEconomy(); - initDatabase(); - loadShopConfig(); - getCommand("cy").setExecutor(this); - getServer().getPluginManager().registerEvents(this, this); - getLogger().info("CyPlugin loaded! shop=" + shopItems.size()); - } - - @Override - public void onDisable() { try { if (db != null && !db.isClosed()) db.close(); } catch (Exception ignored) {} } - - private void setupEconomy() { - if (getServer().getPluginManager().getPlugin("Vault") == null) return; - RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); - if (rsp != null) economy = rsp.getProvider(); - } - - private void initDatabase() { - try { - Class.forName("org.sqlite.JDBC"); - db = DriverManager.getConnection("jdbc:sqlite:" + new File(getDataFolder(), "members.db").getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS members (player_name TEXT PRIMARY KEY,permanent_slots INTEGER DEFAULT 0,membership_slots INTEGER DEFAULT 0,expire_time INTEGER DEFAULT 0,activated INTEGER DEFAULT 0,unlocked_pages INTEGER DEFAULT 1,data TEXT DEFAULT '')"); - try { st.execute("ALTER TABLE members ADD COLUMN permanent_slots INTEGER DEFAULT 0"); } catch (Exception ignored) {} - try { st.execute("ALTER TABLE members ADD COLUMN membership_slots INTEGER DEFAULT 0"); } catch (Exception ignored) {} - try { st.execute("ALTER TABLE members ADD COLUMN unlocked_pages INTEGER DEFAULT 1"); } catch (Exception ignored) {} - st.close(); - } catch (Exception e) { getLogger().severe("SQLite failed: " + e.getMessage()); } - } - - private Map getMember(String playerName) { - Map r = new HashMap(); - if (db == null) return r; - try { - PreparedStatement ps = db.prepareStatement("SELECT * FROM members WHERE player_name=?"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - r.put("permanent_slots", rs.getInt("permanent_slots")); - r.put("membership_slots", rs.getInt("membership_slots")); - r.put("expire_time", rs.getLong("expire_time")); - r.put("activated", rs.getLong("activated")); - r.put("unlocked_pages", rs.getInt("unlocked_pages")); - r.put("data", rs.getString("data")); - } - rs.close(); ps.close(); - } catch (SQLException e) { getLogger().log(Level.WARNING, "getMember failed", e); } - return r; - } - - private void upsertMember(String playerName, int addPerm, int addMem, int addDays) { - if (db == null) return; - long now = System.currentTimeMillis(); - try { - Map old = getMember(playerName); - boolean has = !old.isEmpty(); - int oldPerm = has ? ((Number) old.get("permanent_slots")).intValue() : 0; - int oldMem = has ? ((Number) old.get("membership_slots")).intValue() : 0; - long oldExpire = has ? ((Number) old.get("expire_time")).longValue() : 0L; - long act = has ? ((Number) old.get("activated")).longValue() : now; - int unlocked = has ? ((Number) old.get("unlocked_pages")).intValue() : 1; - int newPerm = oldPerm + addPerm; - int newMem = oldMem + addMem; - long newExpire = addDays > 0 ? Math.max(now, oldExpire) + (long) addDays * 86400000L : oldExpire; - if (has) { - PreparedStatement ps = db.prepareStatement("UPDATE members SET permanent_slots=?,membership_slots=?,expire_time=?,activated=?,unlocked_pages=? WHERE player_name=?"); - ps.setInt(1, newPerm); ps.setInt(2, newMem); ps.setLong(3, newExpire); ps.setLong(4, act); ps.setInt(5, unlocked); ps.setString(6, playerName); - ps.executeUpdate(); ps.close(); - } else { - PreparedStatement ps = db.prepareStatement("INSERT INTO members (player_name,permanent_slots,membership_slots,expire_time,activated,unlocked_pages) VALUES(?,?,?,?,?,?)"); - ps.setString(1, playerName); ps.setInt(2, newPerm); ps.setInt(3, newMem); ps.setLong(4, newExpire); ps.setLong(5, act); ps.setInt(6, unlocked); - ps.executeUpdate(); ps.close(); - } - } catch (SQLException e) { getLogger().log(Level.WARNING, "upsert failed", e); } - } - - private void updateUnlockedPages(String playerName, int pages) { - if (db == null) return; - try { PreparedStatement ps = db.prepareStatement("UPDATE members SET unlocked_pages=? WHERE player_name=?"); ps.setInt(1, pages); ps.setString(2, playerName); ps.executeUpdate(); ps.close(); } catch (SQLException e) {} - } - - private boolean deleteMember(String playerName) { - if (db == null) return false; - try { PreparedStatement ps = db.prepareStatement("DELETE FROM members WHERE player_name=?"); ps.setString(1, playerName); boolean ok = ps.executeUpdate() > 0; ps.close(); return ok; } catch (SQLException e) { return false; } - } - - private int getActiveSlots(Map m) { - if (m.isEmpty()) return 0; - int perm = ((Number) m.get("permanent_slots")).intValue(); - int mem = ((Number) m.get("membership_slots")).intValue(); - long expireTime = ((Number) m.get("expire_time")).longValue(); - return perm + ((expireTime == 0 || expireTime > System.currentTimeMillis()) ? mem : 0); - } - - private int getTotalPages(int total) { return Math.max(1, (total + PAGE_SIZE - 1) / PAGE_SIZE); } - private int getPageUsable(int page, int total) { return Math.min(PAGE_SIZE, Math.max(0, total - (page - 1) * PAGE_SIZE)); } - - private int countPageItems(List items, int page, int total) { - int start = (page - 1) * PAGE_SIZE, usable = getPageUsable(page, total), count = 0; - for (int i = 0; i < usable; i++) { int idx = start + i; if (idx < items.size() && items.get(idx) != null) count++; } - return count; - } - - private List loadStorage(String playerName) { - List list = new ArrayList(); - if (db == null) return list; - try { PreparedStatement ps = db.prepareStatement("SELECT data FROM members WHERE player_name=?"); ps.setString(1, playerName); ResultSet rs = ps.executeQuery(); if (rs.next()) { String b64 = rs.getString("data"); if (b64 != null && !b64.isEmpty()) Collections.addAll(list, itemsFromBase64(b64)); } rs.close(); ps.close(); } catch (Exception e) {} - return list; - } - - private void saveStorage(String playerName, List items) { - if (db == null) return; - try { String b64 = itemsToBase64(items.toArray(new ItemStack[0])); PreparedStatement ps = db.prepareStatement("UPDATE members SET data=? WHERE player_name=?"); ps.setString(1, b64); ps.setString(2, playerName); ps.executeUpdate(); ps.close(); } catch (Exception e) {} - } - - public static String itemsToBase64(ItemStack[] items) throws IOException { - try { ByteArrayOutputStream out = new ByteArrayOutputStream(); BukkitObjectOutputStream data = new BukkitObjectOutputStream(out); data.writeInt(items.length); for (ItemStack item : items) data.writeObject(item); data.close(); return Base64.getEncoder().encodeToString(out.toByteArray()); } catch (Exception e) { throw new IOException("serialize failed", e); } - } - - public static ItemStack[] itemsFromBase64(String b64) throws IOException { - try { ByteArrayInputStream in = new ByteArrayInputStream(Base64.getDecoder().decode(b64)); BukkitObjectInputStream data = new BukkitObjectInputStream(in); ItemStack[] items = new ItemStack[data.readInt()]; for (int i = 0; i < items.length; i++) items[i] = (ItemStack) data.readObject(); data.close(); return items; } catch (Exception e) { throw new IOException("deserialize failed", e); } - } - - public boolean onSdf1Activation(String playerName, int slots, int days) { - try { if (days > 0) upsertMember(playerName, 0, slots, days); else upsertMember(playerName, slots, 0, 0); return true; } catch (Exception e) { return false; } - } - - private void loadShopConfig() { - shopItems.clear(); - File f = findShopFile(); - if (f == null) { loadShopDefaults(); return; } - try { - BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) continue; - String[] parts = line.split("\\|", 5); - if (parts.length < 5) continue; - try { shopItems.add(new ShopItem(parts[0].trim(), parts[1].trim(), Integer.parseInt(parts[2].trim()), Integer.parseInt(parts[3].trim()), Double.parseDouble(parts[4].trim()))); } catch (NumberFormatException ignored) {} - } - r.close(); - if (shopItems.isEmpty()) loadShopDefaults(); - } catch (IOException e) { loadShopDefaults(); } - } - - private void loadShopDefaults() { - shopItems.add(new ShopItem("vip1", "VIP-1天", 1, 1, 100)); - shopItems.add(new ShopItem("vip7", "VIP-7天", 7, 3, 500)); - shopItems.add(new ShopItem("vip30", "VIP-30天", 30, 5, 2000)); - shopItems.add(new ShopItem("vip终身", "VIP-终身", 0, 10, 50000)); - getLogger().info("loaded " + shopItems.size() + " default shop items"); - } - - private File findShopFile() { - File txt = new File(getDataFolder(), "商品.txt"); if (txt.exists()) return txt; - File md = new File(getDataFolder(), "商品.md"); if (md.exists()) return md; - return null; - } - - private void openMainMenu(Player player) { - Inventory gui = Bukkit.createInventory(null, 27, MAIN_TITLE); - ItemStack glass = makeItem(Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < 27; i++) gui.setItem(i, glass); - gui.setItem(2, makeItem(Material.GREEN_WOOL, "§a§l会员信息", "§7查看会员状态和格子")); - gui.setItem(4, makeItem(Material.EMERALD_BLOCK, "§a§l商城", "§7购买会员服务")); - gui.setItem(6, makeItem(Material.ENDER_CHEST, "§a§l主仓", "§7管理存储空间")); - gui.setItem(13, makeItem(Material.BARRIER, "§c§l关闭")); - player.openInventory(gui); - } - - private void openMemberInfo(Player player) { - Inventory gui = Bukkit.createInventory(null, 27, INFO_TITLE); - ItemStack glass = makeItem(Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < 27; i++) gui.setItem(i, glass); - Map m = getMember(player.getName()); - int perm = m.isEmpty() ? 0 : ((Number) m.get("permanent_slots")).intValue(); - int mem = m.isEmpty() ? 0 : ((Number) m.get("membership_slots")).intValue(); - long expireTime = m.isEmpty() ? 0L : ((Number) m.get("expire_time")).longValue(); - int total = getActiveSlots(m), pages = getTotalPages(total); - int unlocked = m.isEmpty() ? 1 : ((Number) m.get("unlocked_pages")).intValue(); - boolean active = (expireTime == 0) || (expireTime > System.currentTimeMillis()); - gui.setItem(4, makeItem(Material.NAME_TAG, "§e" + player.getName())); - gui.setItem(10, makeItem(Material.CLOCK, "§a剩余时间", "§7" + formatTime(expireTime))); - gui.setItem(12, makeItem(Material.DIAMOND_BLOCK, "§b永久格子: §e" + perm, "§7不会过期")); - gui.setItem(14, makeItem(Material.EMERALD_BLOCK, "§a会员格子: §e" + mem, "§7" + (active ? "§a有效" : "§c已过期"))); - gui.setItem(16, makeItem(Material.CHEST, "§e总格子: §f" + total, "§7总页数: " + pages)); - gui.setItem(22, makeItem(Material.BOOK, "§e已解锁页: §f" + Math.min(unlocked, pages) + "/" + pages, "§7每页" + PAGE_SIZE + "格", "§7存满≥" + UNLOCK_THRESHOLD + "个解锁下一页")); - gui.setItem(26, makeItem(Material.ARROW, "§e返回")); - player.openInventory(gui); - } - - private void openShopGUI(Player player) { - int size = Math.max(9, ((shopItems.size() / 9) + 1) * 9); - if (size > 54) size = 54; - Inventory gui = Bukkit.createInventory(null, size, SHOP_TITLE); - Material[] mats = {Material.DIAMOND, Material.GOLD_INGOT, Material.EMERALD, Material.NETHERITE_INGOT, Material.IRON_INGOT, Material.LAPIS_LAZULI, Material.REDSTONE, Material.COAL, Material.QUARTZ}; - for (int i = 0; i < shopItems.size() && i < 45; i++) { - ShopItem item = shopItems.get(i); - gui.setItem(i, makeItem(mats[i % mats.length], "§a" + item.name, "§7ID: " + item.id, "§7时间: §e" + (item.days == 0 ? "终身" : item.days + "天"), "§7格子: §e+" + item.slots, "§7价格: §a$" + fmt(item.price), "", "§e点击购买")); - } - gui.setItem(size - 1, makeItem(Material.ARROW, "§e返回")); - player.openInventory(gui); - } - - private void openStorageGUI(Player player) { - Map m = getMember(player.getName()); - int total = getActiveSlots(m); - if (total <= 0) { player.sendMessage("§c[仓库] §f没有存储空间!"); return; } - List items = loadStorage(player.getName()); - while (items.size() < total) items.add(null); - storageCache.put(player.getUniqueId(), items); - storagePages.put(player.getUniqueId(), 1); - player.openInventory(Bukkit.createInventory(null, 54, STORAGE_TITLE)); - refreshStoragePage(player); - } - - private void refreshStoragePage(Player player) { - Inventory gui = player.getOpenInventory().getTopInventory(); - UUID uuid = player.getUniqueId(); - List items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList(); - int page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1; - int start = (page - 1) * PAGE_SIZE; - Map m = getMember(player.getName()); - int total = getActiveSlots(m), usable = getPageUsable(page, total), totalPages = getTotalPages(total); - int unlocked = Math.min(((Number) m.getOrDefault("unlocked_pages", 1)).intValue(), totalPages); - ItemStack glass = makeItem(Material.GRAY_STAINED_GLASS_PANE, "§7锁定"); - for (int i = 0; i < PAGE_SIZE; i++) { - if (i < usable) { int idx = start + i; gui.setItem(i, (idx < items.size() && items.get(idx) != null) ? items.get(idx) : null); } - else gui.setItem(i, glass); - } - gui.setItem(45, page > 1 ? makeItem(Material.ARROW, "§a◀ 上一页") : makeItem(Material.GRAY_STAINED_GLASS_PANE, "§7◀")); - gui.setItem(46, makeItem(Material.PAPER, "§e第" + page + "/" + totalPages + "页", "§7物品: " + countPageItems(items, page, total) + "/" + usable)); - gui.setItem(49, makeItem(Material.BARRIER, "§c返回")); - if (page < totalPages) { - boolean canGo = (page < unlocked) || (countPageItems(items, page, total) >= UNLOCK_THRESHOLD); - gui.setItem(52, canGo ? makeItem(Material.ARROW, "§a下一页 ▶") : makeItem(Material.RED_STAINED_GLASS_PANE, "§c下一页 ▶", "§7需≥" + UNLOCK_THRESHOLD + "个物品")); - } else gui.setItem(52, makeItem(Material.GRAY_STAINED_GLASS_PANE, "§7▶")); - } - - private boolean tryNextPage(Player player) { - UUID uuid = player.getUniqueId(); - int page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1; - int next = page + 1; - Map m = getMember(player.getName()); - int total = getActiveSlots(m), totalPages = getTotalPages(total); - int unlocked = ((Number) m.getOrDefault("unlocked_pages", 1)).intValue(); - if (next > totalPages) { player.sendMessage("§c[仓库] §f已到最大页!"); return false; } - if (next <= unlocked) { storagePages.put(uuid, next); refreshStoragePage(player); return true; } - List items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList(); - int have = countPageItems(items, page, total); - if (have >= UNLOCK_THRESHOLD) { unlocked++; updateUnlockedPages(player.getName(), unlocked); storagePages.put(uuid, next); refreshStoragePage(player); player.sendMessage("§a[仓库] §f已解锁第" + next + "页!"); return true; } - player.sendMessage("§c[仓库] §f存储量不足(" + have + "/" + UNLOCK_THRESHOLD + ")"); - return false; - } - - @EventHandler - public void onInventoryClick(InventoryClickEvent event) { - if (!(event.getWhoClicked() instanceof Player)) return; - Player player = (Player) event.getWhoClicked(); - String title = event.getView().getTitle(); - int raw = event.getRawSlot(); - if (title.equals(MAIN_TITLE)) { event.setCancelled(true); if (raw == 2) openMemberInfo(player); else if (raw == 4) openShopGUI(player); else if (raw == 6) openStorageGUI(player); else if (raw == 13) player.closeInventory(); return; } - if (title.equals(INFO_TITLE)) { event.setCancelled(true); if (raw == 26) openMainMenu(player); return; } - if (title.equals(SHOP_TITLE)) { event.setCancelled(true); int size = event.getInventory().getSize(); if (raw == size - 1) { openMainMenu(player); return; } if (raw >= 0 && raw < shopItems.size() && raw < 45) buyItem(player, shopItems.get(raw)); return; } - if (title.equals(STORAGE_TITLE)) { - event.setCancelled(true); - UUID uuid = player.getUniqueId(); - if (raw == 49) { openMainMenu(player); return; } - if (raw == 45) { int p = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1; if (p > 1) { storagePages.put(uuid, p - 1); refreshStoragePage(player); } return; } - if (raw == 52) { tryNextPage(player); return; } - if (raw >= 0 && raw < PAGE_SIZE) { - Map m = getMember(player.getName()); - int total = getActiveSlots(m), page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1; - int usable = getPageUsable(page, total); - if (raw >= usable) return; - ItemStack clicked = event.getCurrentItem(), cursor = event.getCursor(); - boolean cEmpty = (clicked == null || clicked.getType() == Material.AIR); - boolean cGlass = (!cEmpty && clicked.getType() == Material.GRAY_STAINED_GLASS_PANE); - boolean curEmpty = (cursor == null || cursor.getType() == Material.AIR); - if (cEmpty && !curEmpty) { int idx = (page - 1) * PAGE_SIZE + raw; List items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList(); while (items.size() <= idx) items.add(null); items.set(idx, cursor.clone()); event.getInventory().setItem(raw, cursor.clone()); event.setCursor(null); refreshStoragePage(player); return; } - if (!cEmpty && !cGlass && curEmpty) { HashMap overflow = player.getInventory().addItem(clicked); if (overflow.isEmpty()) { int idx = (page - 1) * PAGE_SIZE + raw; List items = storageCache.get(uuid); if (items != null && idx < items.size()) items.set(idx, null); event.getInventory().setItem(raw, null); } else player.sendMessage("§c[仓库] §f背包已满!"); return; } - if (!cEmpty && !cGlass && !curEmpty) { int idx = (page - 1) * PAGE_SIZE + raw; List items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList(); while (items.size() <= idx) items.add(null); items.set(idx, cursor.clone()); event.getInventory().setItem(raw, cursor.clone()); event.setCursor(clicked); refreshStoragePage(player); return; } - return; - } - if (event.isShiftClick() && raw >= 54) { - ItemStack held = event.getCurrentItem(); - if (held != null && held.getType() != Material.AIR) { - Map m = getMember(player.getName()); - int total = getActiveSlots(m), page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1; - int usable = getPageUsable(page, total); - List items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList(); - int start = (page - 1) * PAGE_SIZE; - for (int i = 0; i < usable; i++) { int idx = start + i; while (items.size() <= idx) items.add(null); if (items.get(idx) == null) { items.set(idx, held.clone()); event.setCurrentItem(null); refreshStoragePage(player); return; } } - player.sendMessage("§c[仓库] §f当前页已满!"); - } - } - } - } - - @EventHandler - public void onInventoryClose(InventoryCloseEvent event) { - if (!(event.getPlayer() instanceof Player)) return; - Player player = (Player) event.getPlayer(); - if (event.getView().getTitle().equals(STORAGE_TITLE)) { - List items = storageCache.remove(player.getUniqueId()); - storagePages.remove(player.getUniqueId()); - if (items != null) saveStorage(player.getName(), items); - } - } - - private void buyItem(Player player, ShopItem item) { - if (economy == null) { player.sendMessage("§c[商城] §f经济不可用!"); return; } - if (!economy.has(player, item.price)) { player.sendMessage("§c[商城] §f余额不足!需要§e" + fmt(item.price)); return; } - economy.withdrawPlayer(player, item.price); - if (item.days > 0) upsertMember(player.getName(), 0, item.slots, item.days); else upsertMember(player.getName(), item.slots, 0, 0); - player.sendMessage("§a[商城] §f" + item.name + " | +" + item.slots + "格 | -$" + fmt(item.price)); - } - - @Override - public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { - if (args.length == 0) { if (sender instanceof Player) openMainMenu((Player) sender); else showStatus(sender); return true; } - switch (args[0].toLowerCase()) { - case "reload": if (!sender.hasPermission("cy.admin")) { sender.sendMessage("§c权限不足!"); return true; } loadShopConfig(); sender.sendMessage("§a重载!商品=" + shopItems.size()); break; - case "status": if (!sender.hasPermission("cy.admin")) { sender.sendMessage("§c权限不足!"); return true; } showStatus(sender); break; - case "info": if (!sender.hasPermission("cy.admin")) { sender.sendMessage("§c权限不足!"); return true; } if (args.length < 2) { sender.sendMessage("§e用法: /cy info <玩家>"); break; } showMemberInfo(sender, args[1]); break; - case "remove": if (!sender.hasPermission("cy.admin")) { sender.sendMessage("§c权限不足!"); return true; } if (args.length < 2) { sender.sendMessage("§e用法: /cy remove <玩家>"); break; } sender.sendMessage(deleteMember(args[1]) ? "§a已移除: " + args[1] : "§c不存在: " + args[1]); break; - case "shop": if (sender instanceof Player) openShopGUI((Player) sender); break; - default: sender.sendMessage("§c/cy [reload|status|info|remove|shop]"); - } - return true; - } - - private void showStatus(CommandSender sender) { - int count = 0, totalSlots = 0; - try { Statement st = db.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*),COALESCE(SUM(permanent_slots+membership_slots),0) FROM members"); if (rs.next()) { count = rs.getInt(1); totalSlots = rs.getInt(2); } rs.close(); st.close(); } catch (Exception ignored) {} - sender.sendMessage("§e成员:" + count + " 总格子:" + totalSlots + " 商品:" + shopItems.size()); - } - - private void showMemberInfo(CommandSender sender, String name) { - Map m = getMember(name); - if (m.isEmpty()) { sender.sendMessage("§c" + name + " 不是成员"); return; } - sender.sendMessage("§e" + name + ": 永久=" + ((Number) m.get("permanent_slots")).intValue() + " 会员=" + ((Number) m.get("membership_slots")).intValue() + " 过期=" + formatTime(((Number) m.get("expire_time")).longValue())); - } - - private String formatTime(long expire) { - if (expire == 0) return "终身"; - long now = System.currentTimeMillis(); - if (expire <= now) return "已过期"; - long remain = expire - now, days = remain / 86400000L, hours = (remain % 86400000L) / 3600000L; - return days > 0 ? days + "天" + hours + "小时" : hours + "小时"; - } - - private ItemStack makeItem(Material mat, String name, String... lore) { - ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); - if (meta != null) { meta.setDisplayName(name); if (lore.length > 0) meta.setLore(Arrays.asList(lore)); item.setItemMeta(meta); } - return item; - } - - private String fmt(double v) { return v == (long) v ? String.valueOf((long) v) : String.format("%.2f", v); } - - public static class ShopItem { - public final String id, name; - public final int days, slots; - public final double price; - public ShopItem(String id, String name, int days, int slots, double price) { this.id = id; this.name = name; this.days = days; this.slots = slots; this.price = price; } - } -} diff --git a/src/main/java/com/cy/Main.java b/src/main/java/com/cy/Main.java new file mode 100644 index 0000000..0bf96ec --- /dev/null +++ b/src/main/java/com/cy/Main.java @@ -0,0 +1,143 @@ +package com.CY; + +import org.bukkit.*; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.event.inventory.*; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.io.*; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; +import java.io.*; +import java.sql.*; +import java.util.*; + +public class Main extends JavaPlugin implements Listener, CommandExecutor { + private static Connection db; + + @Override + public void onEnable() { + try { + if (!getDataFolder().exists()) getDataFolder().mkdirs(); + db = DriverManager.getConnection("jdbc:sqlite:" + new File(getDataFolder(), "storage.db")); + Statement st = db.createStatement(); + + // 爆破式表结构修复 + try { + st.executeQuery("SELECT page FROM v LIMIT 1"); + } catch (SQLException e) { + st.execute("DROP TABLE IF EXISTS v"); + Bukkit.getConsoleSender().sendMessage("§e[CY] 数据库格式过旧,已重置表结构。"); + } + st.execute("CREATE TABLE IF NOT EXISTS v (p TEXT, page INT, slot INT, data TEXT)"); + Bukkit.getConsoleSender().sendMessage("§a[CY] 仓库系统已就绪,等待主控握手信号。"); + } catch (Exception e) { e.printStackTrace(); } + getCommand("cy").setExecutor(this); + getServer().getPluginManager().registerEvents(this, this); + } + + @Override + public boolean onCommand(CommandSender s, Command c, String l, String[] a) { + if (s instanceof Player) openPage((Player) s, 0); + return true; + } + + public void openPage(Player p, int page) { + p.setMetadata("CY_PAGE", new FixedMetadataValue(this, page)); + p.setMetadata("CY_LOCK", new FixedMetadataValue(this, System.currentTimeMillis())); + + Inventory inv = Bukkit.createInventory(null, 54, "§0[云端仓库] 第 " + (page + 1) + " 页"); + for (int i = 45; i < 54; i++) inv.setItem(i, btn(Material.GRAY_STAINED_GLASS_PANE, " ", null)); + inv.setItem(45, btn(Material.ARROW, "§f上一页", null)); + inv.setItem(49, btn(Material.EMERALD, "§a§l商城与统计", "§7当前存量: §e" + count(p, page) + " / 40")); + inv.setItem(53, btn(Material.ARROW, "§f下一页", null)); + + try (PreparedStatement ps = db.prepareStatement("SELECT slot, data FROM v WHERE p = ? AND page = ?")) { + ps.setString(1, p.getName()); ps.setInt(2, page); + ResultSet rs = ps.executeQuery(); + while (rs.next()) inv.setItem(rs.getInt("slot"), deserialize(rs.getString("data"))); + } catch (Exception ignored) {} + p.openInventory(inv); + } + + @EventHandler + public void onClick(InventoryClickEvent e) { + if (e.getClickedInventory() == null || !e.getView().getTitle().contains("[云端仓库]")) return; + Player p = (Player) e.getWhoClicked(); + int slot = e.getRawSlot(); + + if (p.hasMetadata("CY_LOCK") && System.currentTimeMillis() - p.getMetadata("CY_LOCK").get(0).asLong() < 400) { + e.setCancelled(true); return; + } + + if (slot >= 45 && slot <= 53) { + e.setCancelled(true); + int page = p.getMetadata("CY_PAGE").get(0).asInt(); + if (slot == 45 && page > 0) { save(p, page, e.getInventory()); openPage(p, page - 1); } + if (slot == 53) { + save(p, page, e.getInventory()); + // 翻页限制:前三页必须满 40 格 + if (page < 2 || count(p, page) >= 40) openPage(p, page + 1); + else p.sendMessage("§c§l[拒绝] §7当前仓库分拣度不足 40 格。"); + } + } + } + + @EventHandler + public void onClose(InventoryCloseEvent e) { + if (e.getView().getTitle().contains("[云端仓库]") && e.getPlayer().hasMetadata("CY_PAGE")) { + save((Player) e.getPlayer(), e.getPlayer().getMetadata("CY_PAGE").get(0).asInt(), e.getInventory()); + } + } + + private void save(Player p, int page, Inventory inv) { + try { + PreparedStatement del = db.prepareStatement("DELETE FROM v WHERE p = ? AND page = ?"); + del.setString(1, p.getName()); del.setInt(2, page); del.executeUpdate(); + PreparedStatement ins = db.prepareStatement("INSERT INTO v VALUES (?,?,?,?)"); + for (int i = 0; i < 45; i++) { + ItemStack it = inv.getItem(i); + if (it != null && it.getType() != Material.AIR) { + ins.setString(1, p.getName()); ins.setInt(2, page); + ins.setInt(3, i); ins.setString(4, serialize(it)); + ins.executeUpdate(); + } + } + } catch (Exception ignored) {} + } + + private int count(Player p, int page) { + try (PreparedStatement ps = db.prepareStatement("SELECT COUNT(*) FROM v WHERE p=? AND page=?")) { + ps.setString(1, p.getName()); ps.setInt(2, page); + ResultSet rs = ps.executeQuery(); return rs.next() ? rs.getInt(1) : 0; + } catch (Exception e) { return 0; } + } + + // --- 反射握手入口 --- + public static void apiReceive(String n, int s, int d) { + Bukkit.getConsoleSender().sendMessage("§6[CY-Memory] 收到来自主控的扩容指令!"); + Bukkit.getConsoleSender().sendMessage("§7- 玩家: " + n + " | 增加格子: " + s + " | 增加时长: " + d); + // 这里对接具体的扩容逻辑 + } + + private String serialize(ItemStack item) { + try (ByteArrayOutputStream o = new ByteArrayOutputStream(); BukkitObjectOutputStream d = new BukkitObjectOutputStream(o)) { + d.writeObject(item); return Base64Coder.encodeLines(o.toByteArray()); + } catch (Exception e) { return ""; } + } + + private ItemStack deserialize(String b64) { + try (ByteArrayInputStream i = new ByteArrayInputStream(Base64Coder.decodeLines(b64)); BukkitObjectInputStream d = new BukkitObjectInputStream(i)) { + return (ItemStack) d.readObject(); + } catch (Exception e) { return null; } + } + + private ItemStack btn(Material m, String n, String lore) { + ItemStack item = new ItemStack(m); ItemMeta mt = item.getItemMeta(); + mt.setDisplayName(n); if (lore != null) mt.setLore(Arrays.asList(lore.split("\n"))); + item.setItemMeta(mt); return item; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..756f8db --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,34 @@ +# ============================================================ +# [ CY_beibao 积木配置 ] +# ============================================================ + +设置: + # 变量: [玩家] [金额] [时间] [格子] + 反馈格式: "&b&l[系统] &f尊敬的 [玩家],积木激活成功!获得: [时间]" + +# 积木轨道:供主控联控调用 +积木轨道: + 1: "{名字:初级包, 天:30天, 图标:金锭}" + 2: "[名字:扩容包, 格子:45, 图标:金块]" + 3: "(名字:盲盒礼包, 金额:100, 图标:箱子, 介绍:收100块给个激活)" + +# 本地商城:多种写法混搭测试 +商城: + "item1": + # JSON写法 + 内容: > + { + "名字": "§e§l黄金礼包", + "价格": 500, + "天": "永久", + "图标": "金块" + } + "item2": + # 括弧写法 + 内容: "(名字:&b&l普通礼包, 价格:100, 天:10天, 图标:金锭)" + "item3": + # 键值对写法 + 内容: "名字:扩容插件, 价格:50, 格子:5, 图标:末影箱" + +维护者白名单: + - "" \ No newline at end of file diff --git a/src/main/resources/icons.yml b/src/main/resources/icons.yml new file mode 100644 index 0000000..31c6858 --- /dev/null +++ b/src/main/resources/icons.yml @@ -0,0 +1,19 @@ +# ============================================================ +# [ CY_beibao 图标库 ] +# +# 如何添加新图标? +# 1. 在游戏内将准星对准物品,按 F3 + H 开启高级提示框。 +# 2. 查看物品名称,例如看到 'minecraft:netherite_ingot'。 +# 3. 在下方按格式添加: '自定义中文名: 材质ID' +# ============================================================ + +库: + 金锭: "minecraft:gold_ingot" + 金块: "gold_block" + 钻石: "DIAMOND" + 下界合金: "netherite_ingot" + 末影箱: "ender_chest" + 箱子: "chest" + 书本: "book" + 面包: "bread" + # 玩家可以无限续写,哪怕写了 '我的头: player_head' 也没问题 \ No newline at end of file