初始提交

This commit is contained in:
ypsdf1
2026-04-26 15:33:14 +08:00
commit 3c6c027539
8 changed files with 574 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+10
View File
@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="org.bukkit.event.EventHandler" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cy</groupId>
<artifactId>CY_beibao</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>CY_beibao</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
+432
View File
@@ -0,0 +1,432 @@
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; }
}
}
+9
View File
@@ -0,0 +1,9 @@
name: CY_beibao
version: 1.0
main: com.CY.Main
api-version: '1.20'
author: SDF_Engine
commands:
cy:
description: "Open your cloud vault"
usage: "/cy"