合并远程版本

This commit is contained in:
ypsdf1
2026-04-26 15:34:17 +08:00
5 changed files with 207 additions and 432 deletions
+11
View File
@@ -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
-432
View File
@@ -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<ShopItem> shopItems = new ArrayList<ShopItem>();
private final Map<UUID, Integer> storagePages = new HashMap<UUID, Integer>();
private final Map<UUID, List<ItemStack>> storageCache = new HashMap<UUID, List<ItemStack>>();
@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<Economy> 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<String, Object> getMember(String playerName) {
Map<String, Object> r = new HashMap<String, Object>();
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<String, Object> 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<String, Object> 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<ItemStack> 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<ItemStack> loadStorage(String playerName) {
List<ItemStack> list = new ArrayList<ItemStack>();
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<ItemStack> 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<String, Object> 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<String, Object> m = getMember(player.getName());
int total = getActiveSlots(m);
if (total <= 0) { player.sendMessage("§c[仓库] §f没有存储空间!"); return; }
List<ItemStack> 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<ItemStack> items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList<ItemStack>();
int page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1;
int start = (page - 1) * PAGE_SIZE;
Map<String, Object> 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<String, Object> 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<ItemStack> items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList<ItemStack>();
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<String, Object> 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<ItemStack> items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList<ItemStack>(); 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<Integer, ItemStack> overflow = player.getInventory().addItem(clicked); if (overflow.isEmpty()) { int idx = (page - 1) * PAGE_SIZE + raw; List<ItemStack> 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<ItemStack> items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList<ItemStack>(); 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<String, Object> m = getMember(player.getName());
int total = getActiveSlots(m), page = storagePages.containsKey(uuid) ? storagePages.get(uuid) : 1;
int usable = getPageUsable(page, total);
List<ItemStack> items = storageCache.containsKey(uuid) ? storageCache.get(uuid) : new ArrayList<ItemStack>();
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<ItemStack> 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<String, Object> 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; }
}
}
+143
View File
@@ -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;
}
}
+34
View File
@@ -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, 图标:末影箱"
维护者白名单:
- ""
+19
View File
@@ -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' 也没问题