From aea44ef92d7baaef4f6e5dd0acb88b7128f42332 Mon Sep 17 00:00:00 2001 From: ypsdf1 Date: Fri, 5 Jun 2026 18:17:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E9=99=84=E9=AD=94=E5=8F=AA=E8=83=BD=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E9=99=84=E9=AD=94=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Sdf1_game/BondBridge.java | 28 + src/main/java/Sdf1_game/FileGenerator.java | 17 + src/main/java/Sdf1_game/Main.java | 83 +- src/main/java/Sdf1_game/TreasureConfig.java | 44 +- .../java/Sdf1_game/TreasureInventory.java | 579 +++++-- src/main/java/Sdf1_game/TreasureListener.java | 347 ++++ src/main/java/Sdf1_game/TreasureManager.java | 1479 +++++++++++++++-- src/main/java/Sdf1_game/TreasureReward.java | 176 +- .../java/Sdf1_game/TreasureSpawnTask.java | 70 +- 9 files changed, 2449 insertions(+), 374 deletions(-) diff --git a/src/main/java/Sdf1_game/BondBridge.java b/src/main/java/Sdf1_game/BondBridge.java index b337b77..c14ca32 100644 --- a/src/main/java/Sdf1_game/BondBridge.java +++ b/src/main/java/Sdf1_game/BondBridge.java @@ -52,6 +52,34 @@ public class BondBridge { return false; } } + /** 查询玩家当前债券余额 */ + public int getBonds(String playerName) { + if (!isHooked()) return 0; + try { + org.bukkit.plugin.Plugin plug = + org.bukkit.Bukkit + .getPluginManager() + .getPlugin("Sdf1_login"); + if (plug == null) return 0; + + Object bondManager = + plug.getClass() + .getMethod("getBondManager") + .invoke(plug); + + Object result = + bondManager.getClass() + .getMethod("getBonds", + String.class) + .invoke(bondManager, + playerName); + + return (int) result; + } catch (Exception e) { + return 0; + } + } + private Object findManager(Plugin sdf1) { Class c = sdf1.getClass(); diff --git a/src/main/java/Sdf1_game/FileGenerator.java b/src/main/java/Sdf1_game/FileGenerator.java index 152aa22..76476c8 100644 --- a/src/main/java/Sdf1_game/FileGenerator.java +++ b/src/main/java/Sdf1_game/FileGenerator.java @@ -95,5 +95,22 @@ public class FileGenerator { l.add("| M.C的全称是什么? | Mojang Studios | 10 |"); return l; } + public static void ensureTreasureConfig( + File dir) { + dir.mkdirs(); + File f = new File(dir, "设置.txt"); + if (f.exists()) return; + try { + OutputStreamWriter w = + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8); + w.write("# 寻宝全局设置\n"); + w.write("保底债券: 是\n"); + w.write("随机数量: 1~3\n"); + w.close(); + } catch (IOException ignored) { + } + } } \ No newline at end of file diff --git a/src/main/java/Sdf1_game/Main.java b/src/main/java/Sdf1_game/Main.java index 3f8548c..c1eeb61 100644 --- a/src/main/java/Sdf1_game/Main.java +++ b/src/main/java/Sdf1_game/Main.java @@ -59,9 +59,9 @@ public class Main extends JavaPlugin new TreasureListener( this, treasureManager), this); - spawnTask = new TreasureSpawnTask( - this, treasureManager) - .runTaskTimer(this, 600L, 600L); + new TreasureSpawnTask(this, treasureManager) + .runTaskTimer(this, 100L, 100L); + // ★ 安全注册 Tab 补全 if (getCommand("quiz") != null) { @@ -81,6 +81,14 @@ public class Main extends JavaPlugin .setTabCompleter(this); } + // ★ 启动时清除残留宝箱 + if (treasureManager != null) { + treasureManager.cleanResidualChests(); + } + if (treasureManager != null) { + treasureManager.forceCleanClaims(); + treasureManager.cleanResidualChests(); + } getLogger().info("=== Sdf1_game v1.0 ==="); getLogger().info("债券桥接: " + (bondBridge.isHooked() @@ -118,6 +126,41 @@ public class Main extends JavaPlugin public void onDisable() { if (quizManager != null) quizManager.stopAuto(); if (spawnTask != null) spawnTask.cancel(); + // ★ 卸载时清除所有宝箱 + treasureManager.cleanResidualChests(); + treasureManager.removeAllChests(); + treasureManager.forceCleanClaims(); + if (treasureManager != null) { + treasureManager.forceCleanClaims(); + treasureManager.cleanResidualChests(); + } + saveConfig(); + getLogger().info("\n" + + " __ __ _ \n" + + " \\ \\ / / | | \n" + + " \\ \\ /\\ / /__ ___ ___ _ __ ___ ___ | |_ ___ \n" + + " \\ \\/ \\/ / _ \\/ __/ _ \\| '_ ` _ \\ / _ \\ | __/ _ \\ \n" + + " \\ /\\ / __/ (_| (_) | | | | | | __/ | || (_) | \n" + + " \\/ \\/ \\___|\\___\\___/|_| |_| |_|\\___| \\__\\___/ _ \n" + + " | | (_) \n" + + " ___ __ _ ___ _ _ _ _ __ _ _ __ | |_ __ _ _ __ __ ___ __ _ _ __ ___ ___ _ ____ _____ _ __ \n" + + " / __/ _` |/ _ \\ | | | | | | |/ _` | '_ \\ | __/ _` | '_ \\ \\ \\/ / |/ _` | '_ \\ / __|/ _ \\ '__\\ \\ / / _ \\ '__| \n" + + " | (_| (_| | (_) | | |_| | |_| | (_| | | | | | || (_| | | | | > <| | (_| | | | | \\__ \\ __/ | \\ V / __/ | \n" + + " \\___\\__,_|\\___/ \\__, |\\__,_|\\__,_|_| |_| \\__\\__,_|_| |_|_/_/\\_\\_|\\__,_|_| |_|_|___/\\___|_| _ \\_/ \\___|_| \n" + + " __/ | (_) _ |__ \\ | | (_) | (_)/ _| \n" + + " ___ ___ _ ____ |___/ _ __ _ _ __ (_) _ __ ___ ___ ) | _ _ _ __ ___| |__ _ __| |_| |_ _ _ ___ _ __ \n" + + " / __|/ _ \\ '__\\ \\ / / _ \\ '__| | '_ \\ | '_ ` _ \\ / __| / / | | | | '_ \\/ __| '_ \\| |/ _` | | _| | | | / __| '_ \\ \n" + + " \\__ \\ __/ | \\ V / __/ | | | |_) | _ | | | | | | (__ / /_ | |_| | |_) \\__ \\ | | | | (_| | | | | |_| || (__| | | |\n" + + " |___/\\___|_| \\_/ \\___|_| |_| .__/ (_) |_| |_| |_|\\___|____(_)__, | .__/|___/_| |_|_|\\__,_|_|_| \\__,_(_)___|_| |_|\n" + + " | | __/ | | \n" + + " _ ____ _|_| ________ ___ |___/|_| \n" + + " | | _|___ \\ / _ \\ / /____ / _ \\ \n" + + " _ __ ___ ___| |_(_) __) | | | |/ /_ / / (_) | \n" + + " | '_ \\ / _ \\/ __| __| |__ <| | | | '_ \\ / / \\__, | \n" + + " | |_) | (_) \\__ \\ |_ _ ___) | |_| | (_) / / / / \n" + + " | .__/ \\___/|___/\\__(_)____/ \\___/ \\___/_/ /_/ \n" + + " | | \n" + + " |_| "); } // ========== 命令 ========== @@ -199,6 +242,10 @@ public class Main extends JavaPlugin return false; } + public TreasureManager getTreasureManager() { + return treasureManager; + } + // ========== 快问快答 ========== private String[] shiftArgs(String[] a) { @@ -342,8 +389,19 @@ public class Main extends JavaPlugin sendTreasureList(s); return true; case "reload": - treasureManager.loadAll(); - s.sendMessage("§a已重载"); + case "重载": + treasureManager.forceCleanClaims(); + for (org.bukkit.entity.Player p : + Bukkit.getOnlinePlayers()) { + treasureManager + .reclaimAllTreasureItems(p); + } + treasureManager.reload(); + s.sendMessage( + "§a[寻宝] 重载完成,领取记录已清空"); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); return true; case "工具": case "tool": @@ -359,6 +417,9 @@ public class Main extends JavaPlugin } treasureManager.removeRegion(a[1]); s.sendMessage("§a已删除: " + a[1]); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); return true; // ★ on/off 代替 border case "on": @@ -394,6 +455,9 @@ public class Main extends JavaPlugin .containsKey(regionName)) { s.sendMessage("§c区域不存在: " + regionName); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); return true; } @@ -426,6 +490,9 @@ public class Main extends JavaPlugin if (tr.getPos1() == null) { s.sendMessage("§c请先获取工具并设置AB点"); s.sendMessage("§7或: /寻宝 on 区域名"); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); return true; } @@ -456,6 +523,9 @@ public class Main extends JavaPlugin TreasureRegion.giveTool((Player) s); s.sendMessage("§a已获得圈地工具"); s.sendMessage("§7左键=A点 右键=B点"); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); return true; } @@ -605,6 +675,9 @@ public class Main extends JavaPlugin + hMin + "~Y" + hMax); s.sendMessage("§7使用 §e/寻宝 off " + name + " §7关闭边框"); + s.sendMessage("§b§l\n欢迎游玩草原探险服务器"); + s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679"); + s.sendMessage(""); } boolean o1k = treasureManager.saveRegion( diff --git a/src/main/java/Sdf1_game/TreasureConfig.java b/src/main/java/Sdf1_game/TreasureConfig.java index f1b0653..2a65a6a 100644 --- a/src/main/java/Sdf1_game/TreasureConfig.java +++ b/src/main/java/Sdf1_game/TreasureConfig.java @@ -1,5 +1,10 @@ package Sdf1_game; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -18,20 +23,47 @@ public class TreasureConfig { public List rewards = new ArrayList<>(); + // ★ 开箱设置 + public boolean guaranteeBond = true; + public int bondMin = 1; + public int bondMax = 3; + public int randomMin = 1; + public int randomMax = 3; + public long lastSpawnTime = 0; + // ★ 中文名映射:奖励索引→显示名 + public java.util.Map + rewardNames = new java.util.LinkedHashMap<>(); + + + public TreasureConfig(String name) { this.name = name; } public int getTotalWeight() { int w = 0; - for (TreasureReward r : rewards) { + for (TreasureReward r : rewards) w += r.getWeight(); - } return w; } - - public String getBoundsStr() { - return "[" + minX + "," + minZ + "] ~ [" - + maxX + "," + maxZ + "]"; + public static void ensureTreasureConfig( + File dir) { + dir.mkdirs(); + File f = new File(dir, "设置.txt"); + if (f.exists()) return; + try { + OutputStreamWriter w = + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8); + w.write("# 寻宝全局设置\n"); + w.write("# 保底债券: 是/否\n"); + w.write("保底债券: 是\n"); + w.write("# 随机奖励数量范围\n"); + w.write("随机数量: 1~3\n"); + w.close(); + } catch (IOException ignored) { + } } + } diff --git a/src/main/java/Sdf1_game/TreasureInventory.java b/src/main/java/Sdf1_game/TreasureInventory.java index d53bd87..db9a8d1 100644 --- a/src/main/java/Sdf1_game/TreasureInventory.java +++ b/src/main/java/Sdf1_game/TreasureInventory.java @@ -3,6 +3,8 @@ package Sdf1_game; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; @@ -19,6 +21,9 @@ public class TreasureInventory { // ★ 奖励索引标记(写在lore里用于精确匹配) private static final String LORE_INDEX_PREFIX = "§0§k"; + // ★ 唯一的标记前缀 + private static final String MARK = "§0§k"; + public static class GuiHolder implements InventoryHolder { @@ -26,6 +31,8 @@ public class TreasureInventory { private final Player player; private Inventory inv; + + public GuiHolder(TreasureConfig config, Player player) { this.config = config; @@ -50,46 +57,456 @@ public class TreasureInventory { } } + public static void open(Player player, TreasureConfig config) { - GuiHolder holder = - new GuiHolder(config, player); + Main plugin = (Main) Bukkit + .getPluginManager() + .getPlugin("Sdf1_game"); - Inventory inv = Bukkit.createInventory( - holder, 27, - "§6§l寻宝宝箱 - " + config.name); - - holder.setInventory(inv); - - List selected = - rollRewards(config, 6); - - int[] slots = {10, 12, 14, 16, 22, 24}; - for (int i = 0; - i < selected.size() - && i < slots.length; i++) { - TreasureReward r = selected.get(i); - inv.setItem(slots[i], - buildDisplayItem(r, i)); - } - - // 边框 - ItemStack glass = new ItemStack( - Material.BLACK_STAINED_GLASS_PANE); - ItemMeta gm = glass.getItemMeta(); - if (gm != null) { - gm.displayName(Component.text(" ")); - glass.setItemMeta(gm); - } - for (int i = 0; i < 27; i++) { - if (inv.getItem(i) == null) { - inv.setItem(i, glass); + // 保底债券 + int bondAmount = 0; + if (config.guaranteeBond) { + for (TreasureReward r : config.rewards) { + if (r.getType() + == TreasureReward.Type.BOND) { + bondAmount = r.rollBondAmount(); + break; + } } } - player.openInventory(inv); + // 随机非债券奖励 + List others = + new ArrayList<>(); + for (TreasureReward r : config.rewards) { + if (r.getType() + != TreasureReward.Type.BOND) { + others.add(r); + } + } + + List rolled = + new ArrayList<>(); + int count = ThreadLocalRandom.current() + .nextInt( + config.randomMin, + config.randomMax + 1); + int totalW = 0; + for (TreasureReward r : others) + totalW += r.getWeight(); + + if (totalW > 0 && !others.isEmpty()) { + List pool = + new ArrayList<>(others); + for (int i = 0; + i < count && !pool.isEmpty(); i++) { + int roll = ThreadLocalRandom + .current().nextInt(totalW); + int acc = 0; + TreasureReward pick = null; + for (TreasureReward r : pool) { + acc += r.getWeight(); + if (roll < acc) { + pick = r; + break; + } + } + if (pick == null) + pick = pool.get( + pool.size() - 1); + rolled.add(pick); + pool.remove(pick); + totalW = 0; + for (TreasureReward r : pool) + totalW += r.getWeight(); + } + // ★ 显示GUI让玩家确认 + GuiHolder gh = new GuiHolder( + config, player); + Inventory inv = Bukkit.createInventory( + gh, 27, + "§6§l═══ 寻宝 ═══ " + config.name); + + // 奖励预览 + List preview = + new ArrayList<>(); + preview.add(new TreasureReward( + TreasureReward.Type.BOND, null, + 1, bondAmount, null, 0, + "§6债券+" + bondAmount)); + preview.addAll(rolled); + + for (int i = 0; + i < preview.size() && i < 9; i++) { + TreasureReward r = preview.get(i); + Material mat; + if (r.getType() + == TreasureReward.Type.BOND) { + mat = Material.PAPER; + } else { + mat = Material.matchMaterial( + r.getMaterialName()); + if (mat == null) + mat = Material.PAPER; + } + ItemStack slot = + new ItemStack(mat, + r.getAmount()); + ItemMeta sm = slot.getItemMeta(); + if (sm != null) { + sm.displayName( + Component.text( + r.getDisplayName())); + slot.setItemMeta(sm); + } + inv.setItem(i, slot); + } + + // 装饰边框 + ItemStack filler = new ItemStack( + Material.BLACK_STAINED_GLASS_PANE); + ItemMeta fm = filler.getItemMeta(); + if (fm != null) { + fm.displayName(Component.text("")); + filler.setItemMeta(fm); + } + for (int i = 0; i < 27; i++) { + if (inv.getItem(i) == null) + inv.setItem(i, filler); + } + + gh.setInventory(inv); + player.openInventory(inv); + + } + + // ★ 直接发放+延迟显示结果 + final int fBond = bondAmount; + final List fRolled = + rolled; + Bukkit.getScheduler().runTaskLater(plugin, + () -> giveRewards(player, config, + fBond, fRolled), 40L); } + // ★ isCustomItem + public static boolean isCustomItem( + ItemStack item) { + if (item == null) return false; + if (!item.hasItemMeta()) return false; + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + if (!meta.hasLore()) return false; + List lore = meta.lore(); + if (lore == null) return false; + for (Component lc : lore) { + String line = net.kyori.adventure.text + .serializer.legacy + .LegacyComponentSerializer + .legacySection().serialize(lc); + if (line.contains(MARK + "CUSTOM")) + return true; + } + return false; + } + // ===== buildCustomItem ===== + private static ItemStack buildCustomItem( + TreasureReward r, + String playerName, + String regionName, + Main plugin) { + Material mat = Material.matchMaterial( + r.getMaterialName()); + if (mat == null) { + mat = Material.PAPER; + } + ItemStack item = + new ItemStack(mat, r.getAmount()); + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return item; + } + meta.displayName(Component.text( + "§b" + r.getDisplayName())); + // 附魔 + String enchStr = r.getEnchant(); + if (enchStr != null + && !enchStr.isEmpty()) { + String[] pairs = enchStr.split(";"); + for (String pair : pairs) { + String[] ep = pair.split(","); + if (ep.length != 2) continue; + String id = ep[0].trim() + .toLowerCase(); + int lvl = 1; + try { + lvl = Integer.parseInt( + ep[1].trim()); + } catch (Exception ignore) { + } + Enchantment ench = + Enchantment.getByKey( + NamespacedKey + .minecraft(id)); + if (ench != null) { + // ★ 每个附魔单独打,不共享meta + meta.addEnchant(ench, lvl, true); + } + } + // ★ 所有附魔加完后一次性设置 + item.setItemMeta(meta); + + } + + + String tag = "§0§kCUSTOM|" + + playerName + "|" + + regionName + "|" + + r.getDisplayName() + "|" + + r.getAttackUsesLimit() + "|0"; + List lore = new ArrayList<>(); + if (r.getDurationSec() > 0) { + lore.add(Component.text( + "§7时限: " + + TreasureReward + .formatTime( + r.getDurationSec()))); + lore.add(Component.text( + "§c§o到期自动收回")); + } + if (r.getAttackUsesLimit() > 0) { + lore.add(Component.text( + "§c攻击上限: " + + r.getAttackUsesLimit() + + "次")); + } + lore.add(Component.text(tag)); + meta.lore(lore); + item.setItemMeta(meta); + return item; + } + + // ===== giveRewards ===== + private static void giveRewards( + Player player, + TreasureConfig config, + int bondAmount, + List rolled) { + Main plugin = (Main) Bukkit + .getPluginManager() + .getPlugin("Sdf1_game"); + BondBridge bb = plugin.getBondBridge(); + TreasureManager tm = + plugin.getTreasureManager(); + StringBuilder msg = new StringBuilder(); + msg.append("§6§l═══ 寻宝结果 ═══\n"); + // ★ 控制台打印开箱结果 + StringBuilder log = new StringBuilder(); + log.append("[寻宝开箱] "); + log.append(player.getName()); + log.append(" | 区域=" + config.name); + + // 债券 + if (bondAmount > 0) { + int oldBond = 0; + if (bb != null && bb.isHooked()) { + // ★ 先查原始余额 + oldBond = bb.getBonds( + player.getName()); + bb.addBonds(player.getName(), + bondAmount, "寻宝奖励"); + } + int newBond = oldBond + bondAmount; + msg.append("§6保底债券: §e") + .append(oldBond) + .append(" §7→§a ") + .append(newBond) + .append(" §6(+" + bondAmount + ")\n"); + + msg.append("§6债券: §e+") + .append(bondAmount) + .append(" 张\n"); + } + for (TreasureReward r : rolled) { + if (r.getType() + == TreasureReward.Type.ITEM) { + if (r.getDurationSec() > 0 + && tm.isClaimed( + player.getName(), + r.getDisplayName(), + config.name)) { + msg.append("§c") + .append(r.getDisplayName()) + .append(" 已领过\n"); + continue; + } + ItemStack give; + if (r.getDurationSec() > 0 + || r.getAttackUsesLimit() + > 0) { + give = buildCustomItem(r, + player.getName(), + config.name, plugin); + if (r.getDurationSec() > 0) { + tm.addClaim( + player.getName(), + r.getDisplayName(), + config.name); + } + } else { + Material m = + Material.matchMaterial( + r.getMaterialName()); + if (m == null) { + m = Material.PAPER; + } + give = new ItemStack(m, + r.getAmount()); + } + player.getInventory() + .addItem(give); + msg.append("§f") + .append(r.getDisplayName()) + .append("\n"); + if (r.getDurationSec() > 0) { + final int dur = + r.getDurationSec(); + final String rn = + r.getDisplayName(); + final String pn = + player.getName(); + final String rc = + config.name; + Bukkit.getScheduler() + .runTaskLater(plugin, + () -> { + for (int i = 0; + i < player + .getInventory() + .getSize(); + i++) { + ItemStack it = + player + .getInventory() + .getItem(i); + if (it != null + && isCustomItem( + it) + && getMarkName( + it) + .equals( + rn)) { + player + .getInventory() + .setItem( + i, + null); + break; + } + } + tm.removeClaim( + pn, rn, rc); + if (player + .isOnline()) { + player + .sendMessage( + "§c[寻宝] " + + rn + + " 已到期收回"); + } + }, + dur * 20L); + } + } else if (r.getType() + == TreasureReward.Type.COMMAND) { + String cmd = r.getCommand() + .replace("{player}", + player.getName()); + Bukkit.dispatchCommand( + Bukkit.getConsoleSender(), + cmd); + msg.append("§a命令已执行\n"); + } + } + msg.append("§6§l══════════════"); + // ★ 打印到控制台 + plugin.getLogger().info(log.toString()); + + player.sendMessage(msg.toString()); + } + + // ★ parseMark + public static String[] parseMark( + ItemStack item) { + if (!isCustomItem(item)) return null; + ItemMeta meta = item.getItemMeta(); + if (meta == null) return null; + List lore = meta.lore(); + if (lore == null) return null; + for (Component lc : lore) { + String line = net.kyori.adventure.text + .serializer.legacy + .LegacyComponentSerializer + .legacySection().serialize(lc); + if (line.contains(MARK + "CUSTOM")) { + String data = line.substring( + line.indexOf(MARK + "CUSTOM") + + MARK.length()); + String[] p = data.split("\\|"); + if (p.length >= 6) + return new String[]{ + p[1], p[2], p[3], + p[4], p[5]}; + } + } + return null; + } + + // ★ getMarkName + private static String getMarkName( + ItemStack item) { + String[] m = parseMark(item); + return m != null ? m[2] : ""; + } + + + + + private static void expiryTimer( + Player player, + TreasureManager tm, + int dur, + String rName, + String pName, + String regionName) { + // 收回物品 + for (int si = 0; + si < player.getInventory().getSize(); + si++) { + ItemStack siItem = + player.getInventory() + .getItem(si); + if (siItem != null + && isCustomItem(siItem) + && getMarkName(siItem) + .equals(rName)) { + player.getInventory() + .setItem(si, null); + break; + } + } + // 清记录 + tm.removeClaim(pName, rName, regionName); + if (player.isOnline()) { + player.sendMessage( + "§c[寻宝] " + rName + + " 已到期收回"); + } + } + + + /** * 构建GUI显示物品(含索引标记用于匹配) */ @@ -240,7 +657,9 @@ public class TreasureInventory { TreasureReward matched = config.rewards.get(rewardIndex); - giveReward(player, matched, bb, plugin); + player.closeInventory(); + open(player, config); + // 领取后删除GUI物品 event.getInventory() @@ -251,101 +670,7 @@ public class TreasureInventory { return true; } - // ===== 发放奖励 ===== - private static void giveReward(Player player, - TreasureReward r, - BondBridge bb, - Main plugin) { - switch (r.getType()) { - case BOND: - // ★ 直接发债券 - if (bb != null && bb.isHooked()) { - bb.addBonds(player.getName(), - r.getBondAmount(), - "寻宝奖励"); - } - player.sendMessage("§e获得了 §6" - + r.getBondAmount() - + " §e债券"); - break; - - case COMMAND: - String cmd = r.getCommand() - .replace("{player}", - player.getName()); - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), cmd); - player.sendMessage( - "§e执行了命令奖励"); - break; - - case ITEM: - // ★ 给原版物品:不设自定义名称 - Material mat = - Material.matchMaterial( - r.getMaterialName()); - if (mat == null) mat = Material.PAPER; - ItemStack give = - new ItemStack(mat, - r.getAmount()); - - if (r.getDurationSec() > 0) { - // ★ 限时贴标物品 - ItemMeta meta = - give.getItemMeta(); - if (meta != null) { - meta.displayName( - Component.text( - "§b" + r.getDisplayName())); - List lore = - new ArrayList<>(); - lore.add(Component.text( - "§7时限: " - + TreasureReward - .formatTime( - r.getDurationSec()))); - lore.add(Component.text( - "§c§o到期自动收回")); - meta.lore(lore); - TreasureReward.applyEnchant( - meta, r.getEnchant()); - give.setItemMeta(meta); - } - player.getInventory() - .addItem(give); - - // 定时收回 - final ItemStack ref = - give.clone(); - final int dur = - r.getDurationSec(); - final String rName = - r.getDisplayName(); - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> { - player.getInventory() - .remove(ref); - if (player.isOnline()) { - player.sendMessage( - "§c[寻宝] " + rName - + " §7已到期,自动收回"); - } - }, - dur * 20L); - } else { - // ★ 普通物品:纯原版,无自定义名称 - player.getInventory() - .addItem(give); - } - - player.sendMessage("§e获得了 §f" - + mat.name() - + " §ex" + r.getAmount()); - break; - } - } // ===== 权重随机 ===== diff --git a/src/main/java/Sdf1_game/TreasureListener.java b/src/main/java/Sdf1_game/TreasureListener.java index 33ca787..345cff3 100644 --- a/src/main/java/Sdf1_game/TreasureListener.java +++ b/src/main/java/Sdf1_game/TreasureListener.java @@ -1,5 +1,6 @@ package Sdf1_game; +import net.kyori.adventure.text.Component; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -11,7 +12,10 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import java.util.List; import java.util.Map; public class TreasureListener implements Listener { @@ -24,6 +28,213 @@ public class TreasureListener implements Listener { this.plugin = plugin; this.manager = manager; } + // ★ 玩家攻击玩家时计数 + @org.bukkit.event.EventHandler + public void onDamage( + org.bukkit.event.entity + .EntityDamageByEntityEvent e) { + + if (!(e.getDamager() + instanceof org.bukkit.entity.Player)) + return; + if (!(e.getEntity() + instanceof org.bukkit.entity.Player)) + return; + + org.bukkit.entity.Player attacker = + (org.bukkit.entity.Player) + e.getDamager(); + ItemStack hand = + attacker.getInventory() + .getItemInMainHand(); + + if (hand == null) return; + if (!TreasureInventory + .isCustomItem(hand)) return; + + String[] mark = + TreasureInventory.parseMark(hand); + if (mark == null) return; + + String owner = mark[0]; + String region = mark[1]; + String rName = mark[2]; + int maxUse = + Integer.parseInt(mark[3]); + int useCount = + Integer.parseInt(mark[4]) + 1; + + // ★ 只有攻击上限>0的物品才计数 + if (maxUse <= 0) return; + + // ★ 检查归属 + if (!owner.equalsIgnoreCase( + attacker.getName())) return; + + plugin.getLogger().info( + "[寻宝] ★攻击: " + + attacker.getName() + + " " + rName + + " " + useCount + + "/" + maxUse); + + // ★ 更新lore里的次数 + ItemMeta meta = hand.getItemMeta(); + if (meta != null && meta.hasLore()) { + List lore = meta.lore(); + for (int i = 0; + i < lore.size(); i++) { + String line = + net.kyori.adventure.text + .serializer.legacy + .LegacyComponentSerializer + .legacySection() + .serialize(lore.get(i)); + if (line.contains("§0§kCUSTOM")) { + String newMark = + "§0§kCUSTOM|" + + owner + "|" + + region + "|" + + rName + "|" + + maxUse + "|" + + useCount; + lore.set(i, + Component.text(newMark)); + break; + } + } + meta.lore(lore); + hand.setItemMeta(meta); + } + + // ★ 打印给玩家看 + attacker.sendMessage( + "§e[寻宝] " + rName + + " 攻击次数: " + + useCount + "/" + maxUse); + + // 达到上限 → 回收 + if (useCount >= maxUse) { + attacker.getInventory() + .setItemInMainHand(null); + plugin.getTreasureManager() + .removeClaim(owner, rName, + region); + attacker.sendMessage( + "§c[寻宝] " + rName + + " 攻击次数用完,已回收"); + attacker.getWorld().dropItemNaturally( + attacker.getLocation(), + new ItemStack(Material.AIR)); + } + } + + + // ★ 禁止丢弃寻宝物品 + @org.bukkit.event.EventHandler + public void onDrop( + org.bukkit.event.player + .PlayerDropItemEvent e) { + if (TreasureInventory + .isCustomItem( + e.getItemDrop().getItemStack())) { + e.setCancelled(true); + e.getPlayer().sendMessage( + "§c[寻宝] 寻宝物品不可丢弃"); + } + } + // ★ 死亡时回收寻宝物品(不掉落) + @org.bukkit.event.EventHandler + public void onDeath( + org.bukkit.event.entity + .PlayerDeathEvent e) { + org.bukkit.entity.Player player = + e.getEntity(); + List drops = e.getDrops(); + + // ★ 检查背包里所有物品 + ItemStack[] contents = + player.getInventory().getContents(); + for (int i = 0; + i < contents.length; i++) { + ItemStack item = contents[i]; + if (item == null) continue; + if (!TreasureInventory + .isCustomItem(item)) continue; + + String[] mark = + TreasureInventory.parseMark(item); + if (mark != null) { + // 从掉落列表移除 + drops.remove(item); + // 清除记录 + plugin.getTreasureManager() + .removeClaim(mark[0], + mark[2], mark[1]); + } + } + // 清空背包里所有寻宝物品 + for (int i = 0; + i < contents.length; i++) { + ItemStack item = contents[i]; + if (item == null) continue; + if (TreasureInventory + .isCustomItem(item)) { + player.getInventory() + .setItem(i, null); + } + } + } + // ★ 物品掉落到地上时检测(丢弃后立即清除) + @org.bukkit.event.EventHandler + public void onItemSpawn( + org.bukkit.event.entity + .ItemSpawnEvent e) { + ItemStack item = + e.getEntity().getItemStack(); + if (!TreasureInventory + .isCustomItem(item)) return; + + // ★ 延迟1秒后检查,如果还在地上就销毁 + // (已捡起的不会被销毁) + final org.bukkit.Location loc = + e.getLocation(); + final String itemKey = + item.getType().name() + + "_" + item.getAmount(); + org.bukkit.Bukkit.getScheduler() + .runTaskLater(plugin, () -> { + for (org.bukkit.entity.Entity ent : + loc.getChunk() + .getEntities()) { + if (ent instanceof + org.bukkit.entity.Item) { + org.bukkit.entity.Item di = + (org.bukkit.entity.Item) + ent; + if (TreasureInventory + .isCustomItem( + di.getItemStack())) { + String[] mark = + TreasureInventory + .parseMark( + di.getItemStack()); + if (mark != null) { + // 清除领取记录 + plugin + .getTreasureManager() + .removeClaim( + mark[0], + mark[2], + mark[1]); + } + di.remove(); + break; + } + } + } + }, 20L); + } // ===== 圈地工具选点 ===== @@ -113,6 +324,16 @@ public class TreasureListener implements Listener { } } } + // ★ 玩家下线时标记待回收 + @org.bukkit.event.EventHandler + public void onQuit( + org.bukkit.event.player.PlayerQuitEvent e) { + String name = e.getPlayer().getName(); + // 标记该玩家有物品需要在上线时回收 + plugin.getTreasureManager() + .getPendingReclaims().add(name); + } + // ===== 阻止破坏宝箱 ===== @@ -140,6 +361,132 @@ public class TreasureListener implements Listener { } } + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onClick( + org.bukkit.event.inventory + .InventoryClickEvent e) { + if (e.getCursor() == null) return; + if (!TreasureInventory + .isCustomItem(e.getCursor())) + return; + String type = e.getView() + .getTopInventory().getType().name(); + if (!type.equals("PLAYER") + && !type.equals("CRAFTING") + && !type.equals("CREATIVE")) { + e.setCancelled(true); + if (e.getWhoClicked() + instanceof org.bukkit.entity.Player) { + ((org.bukkit.entity.Player) + e.getWhoClicked()) + .sendMessage( + "§c[寻宝] 不可放入容器"); + } + } + } + + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onDrag( + org.bukkit.event.inventory + .InventoryDragEvent e) { + for (ItemStack item : + e.getNewItems().values()) { + if (TreasureInventory + .isCustomItem(item)) { + String type = e.getView() + .getTopInventory() + .getType().name(); + if (!type.equals("PLAYER") + && !type.equals("CRAFTING")) { + e.setCancelled(true); + return; + } + } + } + } + + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onAnvil( + org.bukkit.event.inventory + .PrepareAnvilEvent e) { + ItemStack first = + e.getInventory().getItem(0); + if (first != null + && TreasureInventory + .isCustomItem(first)) { + e.setResult(null); + } + } + + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onInteract( + org.bukkit.event.player + .PlayerInteractEvent e) { + if (e.getItem() == null) return; + if (!TreasureInventory + .isCustomItem(e.getItem())) return; + if (!e.getAction().name() + .contains("RIGHT_CLICK")) return; + if (e.getClickedBlock() == null) return; + String b = e.getClickedBlock() + .getType().name(); + if (b.contains("SHULKER") + || b.contains("CHEST") + || b.equals("BARREL") + || b.equals("ENDER_CHEST") + || b.equals("HOPPER") + || b.equals("DROPPER") + || b.equals("DISPENSER")) { + e.setCancelled(true); + e.getPlayer().sendMessage( + "§c[寻宝] 不可放入容器"); + } + } + + // ★ 上线时扫描背包回收 + @org.bukkit.event.EventHandler + public void onJoin( + org.bukkit.event.player + .PlayerJoinEvent e) { + plugin.getTreasureManager() + .reclaimPlayerItems( + e.getPlayer().getName()); + } + + // ★ 手持寻宝物品切换槽位时检查归属 + @org.bukkit.event.EventHandler + public void onHeld( + org.bukkit.event.player + .PlayerItemHeldEvent e) { + ItemStack item = + e.getPlayer().getInventory() + .getItem( + e.getNewSlot()); + if (item == null) return; + if (!TreasureInventory + .isCustomItem(item)) return; + + String[] mark = + TreasureInventory.parseMark(item); + if (mark == null) return; + + if (!mark[0].equalsIgnoreCase( + e.getPlayer().getName())) { + // 不是本人的→立即回收 + e.getPlayer().getInventory() + .setItem(e.getNewSlot(), null); + plugin.getTreasureManager() + .removeClaim(mark[0], mark[2], + mark[1]); + e.getPlayer().sendMessage( + "§c[寻宝] " + mark[2] + + " 不属于你,已回收"); + } + } // ===== 预览区域范围 ===== diff --git a/src/main/java/Sdf1_game/TreasureManager.java b/src/main/java/Sdf1_game/TreasureManager.java index c052de0..2421ea2 100644 --- a/src/main/java/Sdf1_game/TreasureManager.java +++ b/src/main/java/Sdf1_game/TreasureManager.java @@ -1,8 +1,13 @@ package Sdf1_game; +import net.kyori.adventure.text.Component; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; @@ -10,24 +15,188 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ThreadLocalRandom; +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; + +import static Sdf1_game.TreasureInventory.isCustomItem; +import static Sdf1_game.TreasureInventory.parseMark; + public class TreasureManager { + private static final String CONFIG_SUFFIX = + ".sdf1.txt"; + + private final Main plugin; - private final Map configs - = new LinkedHashMap<>(); - private final Map activeChests - = new LinkedHashMap<>(); - public int minX, maxX; - public int minZ, maxZ; - public int minY = 64; // 最低高度 - public int maxY = 80; // 最高高度 + private final Map + configs = new LinkedHashMap<>(); + private final Map + activeChests = new LinkedHashMap<>(); + private static final String MARKER = "SDF1"; + private final Set pendingReclaims = + new HashSet<>(); + + public Set getPendingReclaims() { + return pendingReclaims; + } + + + // ★ 领取记录 + private final File claimFile; + private final Set claimedSet = + new HashSet<>(); + + // ★ 边框 + private BukkitTask borderTask; + private boolean borderShowing = false; + private String borderRegionName; + + public TreasureManager(Main plugin) { this.plugin = plugin; + // ★ 必须在构造器里初始化 + this.claimFile = new File( + plugin.getDataFolder(), + "寻宝/领取限制.txt"); + claimFile.getParentFile().mkdirs(); + loadClaims(); + // 构造器里加(最后一行): + new org.bukkit.scheduler.BukkitRunnable() { + @Override + public void run() { + for (org.bukkit.entity.Player p : + plugin.getServer() + .getOnlinePlayers()) { + reclaimPlayerItems(p.getName()); + } + } + }.runTaskTimer(plugin, 600L, 600L); } + /** 加载领取记录 */ + private void loadClaims() { + claimedSet.clear(); + if (!claimFile.exists()) return; + try { + BufferedReader br = new BufferedReader( + new InputStreamReader( + new FileInputStream(claimFile), + StandardCharsets.UTF_8)); + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty() + && !line.startsWith("#")) { + claimedSet.add(line); + } + } + br.close(); + } catch (IOException e) { + plugin.getLogger().warning( + "[寻宝] 加载领取记录失败"); + } + } + public void reload() { + forceCleanClaims(); + for (org.bukkit.entity.Player p : + plugin.getServer() + .getOnlinePlayers()) { + reclaimAllTreasureItems(p); + } + removeAllChests(); + configs.clear(); + activeChests.clear(); + loadAll(); + plugin.getLogger().info( + "[寻宝] ★重载完成," + + configs.size() + " 个区域"); + } + + /** 回收玩家身上所有寻宝物品(重载/关闭时用) */ + public void reclaimAllTreasureItems( + org.bukkit.entity.Player player) { + if (player == null) return; + int removed = 0; + for (int i = 0; + i < player.getInventory().getSize(); i++) { + ItemStack item = + player.getInventory().getItem(i); + if (item == null) continue; + if (!TreasureInventory + .isCustomItem(item)) continue; + player.getInventory().setItem(i, null); + removed++; + } + if (removed > 0) { + player.sendMessage( + "§c[寻宝] 插件重载," + + removed + + "件寻宝物品已回收"); + } + } + + public void migrateOldFiles() { + File dir = new File( + plugin.getDataFolder(), "寻宝"); + dir.mkdirs(); + + File[] files = dir.listFiles( + (d, n) -> n.endsWith(".txt") + && !n.endsWith(CONFIG_SUFFIX)); + + if (files == null) return; + + // ★ 黑名单 + java.util.Set blacklist = + new java.util.HashSet<>(); + blacklist.add("领取限制"); + blacklist.add("配置说明"); + blacklist.add("设置"); + + for (File f : files) { + String baseName = f.getName() + .replace(".txt", ""); + + // ★ 黑名单跳过 + if (blacklist.contains(baseName)) { + continue; + } + + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8)); + String first = br.readLine(); + br.close(); + + if (first != null + && first.trim() + .startsWith("区域名")) { + String newName = baseName + + CONFIG_SUFFIX; + File newFile = new File( + f.getParent(), newName); + if (f.renameTo(newFile)) { + plugin.getLogger().info( + "[寻宝] 迁移: " + + f.getName() + + " → " + + newName); + } + } + } catch (Exception ignored) { + } + } + } + + // ========== 加载 ========== public void loadAll() { @@ -37,21 +206,29 @@ public class TreasureManager { dir.mkdirs(); File[] files = dir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("配置说明.txt")); + (d, n) -> n.endsWith(CONFIG_SUFFIX)); + if (files == null) return; + // ★ 黑名单 + java.util.List skip = + java.util.Arrays.asList( + "领取限制", + "配置说明", + "设置"); for (File f : files) { try { TreasureConfig tc = parseConfig(f); if (tc != null) { configs.put(tc.name, tc); - plugin.getLogger().info( + loadSettings(tc); + configs.put(tc.name, tc); + plugin.getLogger().info( "[寻宝] ★加载: " + tc.name + " 奖励数量=" + tc.rewards.size()); // ★ 打印每条奖励 - for (int i = 0; + for (int i = 0; i < tc.rewards.size(); i++) { TreasureReward r = tc.rewards.get(i); @@ -81,37 +258,159 @@ public class TreasureManager { + "个区域"); } + /** 扫描玩家背包,回收不属于自己的物品 + 攻击次数用完的物品 */ + public void reclaimPlayerItems( + String playerName) { + org.bukkit.entity.Player player = + plugin.getServer() + .getPlayerExact(playerName); + if (player == null) return; + + int removed = 0; + for (int i = 0; + i < player.getInventory() + .getSize(); i++) { + ItemStack item = + player.getInventory() + .getItem(i); + if (item == null) continue; + if (!TreasureInventory + .isCustomItem(item)) continue; + + String[] mark = + TreasureInventory + .parseMark(item); + if (mark == null) continue; + + // mark[0]=原始玩家, mark[1]=区域, + // mark[2]=名称, mark[3]=上限, mark[4]=次数 + String owner = mark[0]; + int maxUse = + Integer.parseInt(mark[3]); + int useCount = + Integer.parseInt(mark[4]); + + boolean notMine = !owner + .equalsIgnoreCase(playerName); + boolean usedUp = + maxUse > 0 + && useCount >= maxUse; + + if (notMine || usedUp) { + player.getInventory() + .setItem(i, null); + removeClaim(owner, mark[2], + mark[1]); + removed++; + if (notMine) { + player.sendMessage( + "§c[寻宝] " + mark[2] + + " 不属于你,已回收"); + } + } + } + + if (removed > 0) { + plugin.getLogger().info( + "[寻宝] 回收: " + + playerName + + " " + removed + "件"); + } + } + + + /** ★ 统一标点:中文→英文 */ + private String normalizePunctuation(String s) { + return s + .replace(":", ":") + .replace(";", ";") + .replace(",", ",") + .replace("。", ".") + .replace("(", "(") + .replace(")", ")") + .replace("【", "[") + .replace("】", "]") + .replace("!", "!") + .replace("?", "?") + .replace("~", "~") + .replace("、", ",") + .replace("=", "=") + .replace("+", "+") + .replace("-", "-") + .replace(" ", " ") + .replace("\uFEFF", ""); + } + private TreasureConfig parseConfig(File f) throws IOException { + + TreasureConfig tc = new TreasureConfig( + f.getName().replace(".txt", "") + .replace(CONFIG_SUFFIX, "")); + + java.util.List lines = + new java.util.ArrayList<>(); + BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(f), StandardCharsets.UTF_8)); + String raw; + while ((raw = br.readLine()) != null) { + lines.add(raw); + } + br.close(); - TreasureConfig tc = new TreasureConfig( - f.getName().replace(".txt", "")); + plugin.getLogger().info( + "[寻宝] 文件共 " + lines.size() + + " 行: " + f.getName()); - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); + for (int idx = 0; + idx < lines.size(); idx++) { + + String rawLine = lines.get(idx); + int lineNum = idx + 1; + + // ★ 打印原始行 + plugin.getLogger().info( + "[寻宝] 行" + lineNum + + ": [" + rawLine + "]"); + + String line = rawLine.trim(); if (line.isEmpty() - || line.startsWith("#")) continue; + || line.startsWith("#")) { + plugin.getLogger().info( + "[寻宝] → 跳过"); + continue; + } + + // ★ 统一标点 + line = normalizePunctuation(line); + int ci = line.indexOf(':'); - if (ci < 0) continue; + if (ci < 0) { + plugin.getLogger().warning( + "[寻宝] ★无冒号: " + line); + continue; + } + String k = line.substring(0, ci).trim(); String v = line.substring(ci + 1).trim(); + plugin.getLogger().info( + "[寻宝] key=[" + k + + "] val=[" + v + "]"); + switch (k) { case "区域名": - tc.name = v; break; + tc.name = v; + break; case "世界": - tc.world = v; break; + tc.world = v; + break; case "坐标": - // ★ 兼容中文逗号 - String coordStr = v - .replace(",", ","); - String[] pp = coordStr.split(","); + String[] pp = v.split(","); if (pp.length == 4) { int a = Integer.parseInt( pp[0].trim()); @@ -127,43 +426,68 @@ public class TreasureManager { tc.maxZ = Math.max(b, d); } break; + case "高度": + String[] yp = v.split(","); + if (yp.length == 2) { + tc.minY = Math.min( + Integer.parseInt( + yp[0].trim()), + Integer.parseInt( + yp[1].trim())); + tc.maxY = Math.max( + Integer.parseInt( + yp[0].trim()), + Integer.parseInt( + yp[1].trim())); + } + break; case "最多宝箱": tc.maxChests = - Integer.parseInt(v); + Integer.parseInt(v.trim()); break; case "检查间隔": - tc.spawnInterval = parseTime(v); + tc.spawnInterval = + parseTime(v); break; case "生成概率": tc.spawnChance = - Integer.parseInt(v); + Integer.parseInt(v.trim()); break; case "随机高度": tc.randomY = v.equals("是") || v.equals("true"); break; case "奖励": - TreasureReward r = - parseReward(v); - if (r != null) - tc.rewards.add(r); - break; - case "高度": - // 格式: minY,maxY - String[] yp1 = v.split(","); - if (yp1.length == 2) { - int y1 = Integer.parseInt( - yp1[0].trim()); - int y2 = Integer.parseInt( - yp1[1].trim()); - tc.minY = Math.min(y1, y2); - tc.maxY = Math.max(y1, y2); + try { + TreasureReward rr = + parseReward(v); + if (rr != null) { + tc.rewards.add(rr); + int idx1 = + tc.rewards.size() - 1; + // ★ 记录中文名 + tc.rewardNames.put(idx, + rr.getDisplayName()); + } + } catch (Exception e) { + plugin.getLogger().warning( + "[寻宝] 奖励异常: " + + e.getMessage()); } break; + default: + plugin.getLogger().info( + "[寻宝] 未知key: [" + + k + "]"); + break; } } - br.close(); + + plugin.getLogger().info( + "[寻宝] 解析完成: " + + tc.name + + " 奖励=" + tc.rewards.size()); return tc; } @@ -186,10 +510,7 @@ public class TreasureManager { try { return Integer.parseInt(s); } catch (Exception e) { return 300; } } - // ★ 粒子边框任务 - private BukkitTask borderTask; - private boolean borderShowing = false; - private String borderRegionName; + /** 显示已保存区域的粒子边框 */ public boolean showRegionBorder(String name) { @@ -255,6 +576,704 @@ public class TreasureManager { return true; } + public void removeAllChests() { + int count = 0; + java.util.Iterator it = + activeChests.keySet().iterator(); + while (it.hasNext()) { + Location loc = it.next(); + it.remove(); + if (loc.getWorld() == null) continue; + if (loc.getBlock().getType() + == Material.CHEST) { + loc.getBlock() + .setType(Material.AIR); + count++; + } + } + if (count > 0) { + plugin.getLogger().info( + "[寻宝] 收回 " + count + " 个"); + } + // ★ 不清除领取记录 + } + + public void forceCleanClaims() { + // ★ 暴力方案:直接删文件再重建 + if (claimFile.exists()) { + boolean deleted = claimFile.delete(); + plugin.getLogger().info( + "[寻宝] ★删除领取记录: " + + deleted); + } + try { + claimFile.getParentFile().mkdirs(); + claimFile.createNewFile(); + plugin.getLogger().info( + "[寻宝] ★重建领取记录完成"); + } catch (IOException e) { + plugin.getLogger().warning( + "[寻宝] ★重建失败: " + + e.getMessage()); + } + claimedSet.clear(); + } + + + // ===== NBT 标记宝箱 ===== + + /** 生成宝箱时写入NBT标记 */ + private void tagChest(Location loc, + String regionName) { + org.bukkit.block.Block block = + loc.getBlock(); + if (!(block.getState() + instanceof org.bukkit.block.TileState)) + return; + org.bukkit.block.TileState state = + (org.bukkit.block.TileState) + block.getState(); + org.bukkit.persistence + .PersistentDataContainer pdc = + state.getPersistentDataContainer(); + org.bukkit.NamespacedKey kMarker = + new org.bukkit.NamespacedKey( + plugin, "sdf1_marker"); + org.bukkit.NamespacedKey kTime = + new org.bukkit.NamespacedKey( + plugin, "sdf1_time"); + org.bukkit.NamespacedKey kRegion = + new org.bukkit.NamespacedKey( + plugin, "sdf1_region"); + pdc.set(kMarker, + org.bukkit.persistence + .PersistentDataType.BOOLEAN, + true); + pdc.set(kTime, + org.bukkit.persistence + .PersistentDataType.LONG, + System.currentTimeMillis()); + pdc.set(kRegion, + org.bukkit.persistence + .PersistentDataType.STRING, + regionName); + state.update(true); + } + + /** 检查箱子是否是我们的 */ + private boolean isOurChest(Location loc) { + org.bukkit.block.Block block = + loc.getBlock(); + if (!(block.getState() + instanceof org.bukkit.block.TileState)) + return false; + org.bukkit.block.TileState state = + (org.bukkit.block.TileState) + block.getState(); + org.bukkit.persistence + .PersistentDataContainer pdc = + state.getPersistentDataContainer(); + org.bukkit.NamespacedKey kMarker = + new org.bukkit.NamespacedKey( + plugin, "sdf1_marker"); + return pdc.has(kMarker, + org.bukkit.persistence + .PersistentDataType.BOOLEAN); + } + + /** 读取箱子的区域名 */ + private String getChestRegion(Location loc) { + org.bukkit.block.Block block = + loc.getBlock(); + if (!(block.getState() + instanceof org.bukkit.block.TileState)) + return null; + org.bukkit.block.TileState state = + (org.bukkit.block.TileState) + block.getState(); + org.bukkit.persistence + .PersistentDataContainer pdc = + state.getPersistentDataContainer(); + org.bukkit.NamespacedKey kRegion = + new org.bukkit.NamespacedKey( + plugin, "sdf1_region"); + return pdc.getOrDefault(kRegion, + org.bukkit.persistence + .PersistentDataType.STRING, + ""); + } + + /** 读取箱子生成时间 */ + private long getChestTime(Location loc) { + org.bukkit.block.Block block = + loc.getBlock(); + if (!(block.getState() + instanceof org.bukkit.block.TileState)) + return 0; + org.bukkit.block.TileState state = + (org.bukkit.block.TileState) + block.getState(); + org.bukkit.persistence + .PersistentDataContainer pdc = + state.getPersistentDataContainer(); + org.bukkit.NamespacedKey kTime = + new org.bukkit.NamespacedKey( + plugin, "sdf1_time"); + return pdc.getOrDefault(kTime, + org.bukkit.persistence + .PersistentDataType.LONG, + 0L); + } + + // ★ 1. parseChineseReward + private TreasureReward parseChineseReward( + String line) { + try { + return parseChineseRewardInternal(line); + } catch (Exception e) { + plugin.getLogger().warning( + "[寻宝] ★中文解析异常: " + line + + " 错误: " + e.getMessage()); + return null; + } + } + private TreasureReward parseChineseRewardInternal( + String line) { + plugin.getLogger().info( + "[寻宝] 中文解析: " + line); + + // 1. 提取物品名 + String itemName = line + .replace("给予玩家", "") + .replace("给玩家", "") + .replace("给予", "") + .replace("给", "") + .trim(); + + String cleanName = itemName + .replaceAll( + "[一二三四五六七八九十\\d]+[个根把件套只]", + "") + .trim(); + + // 物品匹配 + String[][] items = { + {"旋风棒", "BREEZE_ROD"}, + {"烈焰棒", "BLAZE_ROD"}, + {"附魔金苹果", "ENCHANTED_GOLDEN_APPLE"}, + {"金苹果", "GOLDEN_APPLE"}, + {"下界合金剑", "NETHERITE_SWORD"}, + {"下界合金镐", "NETHERITE_PICKAXE"}, + {"下界合金锭", "NETHERITE_INGOT"}, + {"钻石剑", "DIAMOND_SWORD"}, + {"钻石镐", "DIAMOND_PICKAXE"}, + {"钻石斧", "DIAMOND_AXE"}, + {"钻石甲", "DIAMOND_CHESTPLATE"}, + {"钻石靴子", "DIAMOND_BOOTS"}, + {"钻石护腿", "DIAMOND_LEGGINGS"}, + {"钻石头盔", "DIAMOND_HELMET"}, + {"末影珍珠", "ENDER_PEARL"}, + {"绿宝石", "EMERALD"}, + {"金锭", "GOLD_INGOT"}, + {"铁剑", "IRON_SWORD"}, + {"铁锭", "IRON_INGOT"}, + {"钻石", "DIAMOND"}, + {"木棍", "STICK"}, + {"木剑", "WOODEN_SWORD"}, + {"弓", "BOW"}, + {"箭", "ARROW"}, + {"盾牌", "SHIELD"}, + {"面包", "BREAD"}, + {"不死图腾", "TOTEM_OF_UNDYING"}, + {"煤炭", "COAL"}, + {"红石", "REDSTONE"} + }; + + String matName = null; + for (String[] pair : items) { + if (cleanName.contains(pair[0])) { + matName = pair[1]; + break; + } + } + if (matName == null) { + plugin.getLogger().warning( + "[寻宝] ★无法识别: " + line); + return null; + } + matName = matName.toUpperCase(); + + + + // 2. 数量 + int amount = 1; + java.util.regex.Matcher am = + java.util.regex.Pattern + .compile( + "[一二三四五六七八九十\\d]+[个根把件套只]") + .matcher(itemName); + if (am.find()) { + String n = am.group() + .replaceAll( + "[个根把件套只]", ""); + amount = parseChineseNumber(n); + } + + // 3. ★ 泛解析附魔(全行扫描) + String[] enchDefs = { + "水下速掘", "aqua_affinity", + "节肢杀手", "bane_of_arthropods", + "爆炸保护", "blast_protection", + "破甲", "breach", + "引雷", "channeling", + "绑定诅咒", "binding_curse", + "消失诅咒", "vanishing_curse", + "致密", "density", + "深海探索者", "depth_strider", + "效率", "efficiency", + "掉落缓冲", "feather_falling", + "火焰附加", "fire_aspect", + "火焰保护", "fire_protection", + "火矢", "flame", + "时运", "fortune", + "冰霜行者", "frost_walker", + "穿刺", "impaling", + "无限", "infinity", + "击退", "knockback", + "抢夺", "looting", + "忠诚", "loyalty", + "海之眷顾", "luck_of_the_sea", + "突进", "lure", + "经验修补", "mending", + "多重射击", "multishot", + "穿透", "piercing", + "力量", "power", + "弹射物保护", "projectile_protection", + "保护", "protection", + "冲击", "punch", + "快速装填", "quick_charge", + "水下呼吸", "respiration", + "激流", "riptide", + "锋利", "sharpness", + "精准采集", "silk_touch", + "亡灵杀手", "smite", + "灵魂疾行", "soul_speed", + "横扫之刃", "sweeping_edge", + "迅捷潜行", "swift_sneak", + "荆棘", "thorns", + "耐久", "unbreaking", + "风暴", "wind_burst", + "风爆", "wind_burst", + + }; + + List enchIds = new ArrayList<>(); + List enchLvls = new ArrayList<>(); + + // ★ 用"与"分割多个附魔 + String[] segments = line + .replace("与", "|") + .replace("、", "|") + .replace(",", "|") + .replace(";", "|") + .split("\\|"); + + for (String seg : segments) { + String segTrim = seg.trim(); + + // ★ 先用全行匹配所有附魔 + for (int i = 0; i < enchDefs.length; + i += 2) { + String cn = enchDefs[i]; + String id = enchDefs[i + 1]; + + if (!line.contains(cn)) continue; + + int lvl = 0; + + // 方式1: "附魔火焰附加,等级255" + // → "等级255"在附魔名之后的文本中 + java.util.regex.Matcher m1 = + java.util.regex.Pattern + .compile( + cn + "[,,]?\\s*等级?\\s*[::]?\\s*(\\d+)") + .matcher(line); + if (m1.find()) { + lvl = Integer.parseInt( + m1.group(1)); + } + + // 方式2: "附魔击退255" 紧跟数字 + if (lvl <= 0) { + java.util.regex.Matcher m2 = + java.util.regex.Pattern + .compile( + cn + "\\s*(\\d+)") + .matcher(line); + if (m2.find()) { + lvl = Integer.parseInt( + m2.group(1)); + } + } + + // 方式3: "255级火焰附加" + if (lvl <= 0) { + java.util.regex.Matcher m3 = + java.util.regex.Pattern + .compile( + "(\\d+)\\s*级?" + + cn) + .matcher(line); + if (m3.find()) { + lvl = Integer.parseInt( + m3.group(1)); + } + } + + // 方式4: "锋利255与风暴3" 分割后 + // 在整个line中找该附魔名后最近的数字 + if (lvl <= 0) { + int pos = line.indexOf(cn); + if (pos >= 0) { + String after = + line.substring( + pos + cn.length()); + java.util.regex.Matcher m4 = + java.util.regex.Pattern + .compile( + "\\D*(\\d+)") + .matcher(after); + if (m4.find()) { + int val = Integer.parseInt( + m4.group(1)); + if (val > 0 && val <= 255) { + lvl = val; + } + } + } + } + + if (lvl <= 0) lvl = 1; + enchIds.add(id); + enchLvls.add(lvl); + } + } + + + // ★ 额外安全网:直接全行扫数字 + // 如果上面没匹配到但全行有附魔名+数字 + if (enchIds.isEmpty()) { + for (int i = 0; i < enchDefs.length; + i += 2) { + String cn = enchDefs[i]; + String id = enchDefs[i + 1]; + if (!line.contains(cn)) continue; + + java.util.regex.Matcher m = + java.util.regex.Pattern + .compile( + "(\\d+)") + .matcher(line); + int lvl = 0; + while (m.find()) { + int val = Integer.parseInt( + m.group(1)); + if (val > 0 && val <= 255) { + lvl = val; + break; + } + } + if (lvl <= 0) lvl = 1; + enchIds.add(id); + enchLvls.add(lvl); + } + } + + StringBuilder enchSB = new StringBuilder(); + for (int i = 0; i < enchIds.size(); i++) { + if (i > 0) enchSB.append(";"); + enchSB.append(enchIds.get(i)) + .append(",") + .append(enchLvls.get(i)); + } + String enchant = enchSB.length() > 0 + ? enchSB.toString() : null; + + // 4. 时长 + int duration = 0; + java.util.regex.Matcher dm = + java.util.regex.Pattern + .compile( + "时长[::]?\\s*([^,,。]+)") + .matcher(line); + if (dm.find()) { + duration = TreasureReward + .parseDuration( + dm.group(1).trim()); + } + + // 5. 攻击次数 + int attackLimit = 0; + java.util.regex.Matcher atm = + java.util.regex.Pattern + .compile( + "攻击[^::]*[::]?\\s*(\\d+)") + .matcher(line); + if (atm.find()) { + attackLimit = Integer.parseInt( + atm.group(1)); + } + + plugin.getLogger().info( + "[寻宝] ★结果: " + + matName + " x" + amount + + " 附魔=" + enchant + + " 时长=" + duration + + " 攻击=" + attackLimit); + + return new TreasureReward( + TreasureReward.Type.ITEM, + matName, amount, 0, null, 10, + "§b" + cleanName, + duration, enchant, null, + attackLimit); + } + + // ★ 2. isClaimed + public boolean isClaimed(String player, + String itemName, + String regionName) { + return claimedSet.contains( + player + "|" + itemName + + "|" + regionName); + } + + // ★ 3. addClaim + public void addClaim(String player, + String itemName, + String regionName) { + String key = player + "|" + itemName + + "|" + regionName; + if (claimedSet.contains(key)) return; + claimedSet.add(key); + try { + OutputStreamWriter w = + new OutputStreamWriter( + new FileOutputStream( + claimFile, true), + StandardCharsets.UTF_8); + w.write(key + "\n"); + w.close(); + } catch (IOException e) { + plugin.getLogger().warning( + "[寻宝] 写入记录失败"); + } + } + + // ★ 4. removeClaim + public void removeClaim(String player, + String itemName, + String regionName) { + String key = player + "|" + itemName + + "|" + regionName; + claimedSet.remove(key); + rewriteClaimsFile(); + } + + // ★ 5. rewriteClaimsFile + private void rewriteClaimsFile() { + try { + OutputStreamWriter w = + new OutputStreamWriter( + new FileOutputStream( + claimFile), + StandardCharsets.UTF_8); + for (String e : claimedSet) + w.write(e + "\n"); + w.close(); + } catch (IOException ignored) { + } + } + + private int parseChineseNumber(String s) { + s = s.trim(); + switch (s) { + case "一": case "1": return 1; + case "二": case "两": case "2": return 2; + case "三": case "3": return 3; + case "四": case "4": return 4; + case "五": case "5": return 5; + case "六": case "6": return 6; + case "七": case "7": return 7; + case "八": case "8": return 8; + case "九": case "9": return 9; + case "十": case "10": return 10; + default: + try { return Integer.parseInt(s); } + catch (Exception e) { return 1; } + } + } + + private String matchChineseEnchant(String name) { + name = name.trim(); + switch (name) { + case "击退": return "KNOCKBACK"; + case "锋利": return "SHARPNESS"; + case "火焰附加": return "FIRE_ASPECT"; + case "抢夺": return "LOOTING"; + case "效率": return "EFFICIENCY"; + case "时运": return "FORTUNE"; + case "耐久": return "UNBREAKING"; + case "经验修补": return "MENDING"; + case "保护": return "PROTECTION"; + case "力量": return "POWER"; + case "火矢": return "FLAME"; + case "无限": return "INFINITY"; + case "忠诚": return "LOYALTY"; + default: return name.toUpperCase().replace(" ", "_"); + } + } + + // ★ 只在玩家攻击玩家时计数 + // ★ 只拦截自定义物品放入容器 + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onClick( + org.bukkit.event.inventory + .InventoryClickEvent e) { + if (e.getWhoClicked() + instanceof org.bukkit.entity.Player) { + org.bukkit.entity.Player p = + (org.bukkit.entity.Player) + e.getWhoClicked(); + + // ★ 检查光标上的物品 + if (e.getCursor() != null + && TreasureInventory + .isCustomItem( + e.getCursor())) { + String[] mark = + TreasureInventory.parseMark( + e.getCursor()); + if (mark != null + && !mark[0] + .equalsIgnoreCase( + p.getName())) { + // 不是本人的→立即回收 + e.setCancelled(true); + e.getCursor().setAmount(0); + p.sendMessage( + "§c[寻宝] 该物品不属于你"); + return; + } + // ★ 光标有自定义物品+目标是容器→拦截 + Inventory top = e.getView() + .getTopInventory(); + InventoryType type = top.getType(); + if (type != InventoryType.PLAYER + && type != InventoryType.CRAFTING + && type != InventoryType.CREATIVE) { + e.setCancelled(true); + p.sendMessage( + "§c[寻宝] 该物品不可放入容器"); + return; + } + } + + // ★ 检查被点击槽位的物品 + ItemStack clicked = e.getCurrentItem(); + if (clicked != null + && TreasureInventory + .isCustomItem(clicked)) { + String[] mark = + TreasureInventory.parseMark( + clicked); + if (mark != null + && !mark[0] + .equalsIgnoreCase( + p.getName())) { + // 槽位物品不属于本人→回收 + e.setCancelled(true); + e.getCurrentItem().setAmount(0); + p.sendMessage( + "§c[寻宝] 该物品不属于你"); + } + } + } + } + + // ★ 拖拽拦截 + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onDrag( + org.bukkit.event.inventory + .InventoryDragEvent e) { + if (!(e.getWhoClicked() + instanceof org.bukkit.entity.Player)) + return; + org.bukkit.entity.Player p = + (org.bukkit.entity.Player) + e.getWhoClicked(); + for (ItemStack item : + e.getNewItems().values()) { + if (TreasureInventory + .isCustomItem(item)) { + InventoryType type = e.getView() + .getTopInventory() + .getType(); + if (type != InventoryType.PLAYER + && type != InventoryType.CRAFTING) { + e.setCancelled(true); + p.sendMessage( + "§c[寻宝] 该物品不可放入容器"); + return; + } + } + } + } + + // ★ 潜影盒/容器右键拦截 + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onInteract( + org.bukkit.event.player + .PlayerInteractEvent e) { + if (e.getItem() == null) return; + if (!TreasureInventory + .isCustomItem(e.getItem())) return; + if (!e.getAction().name() + .contains("RIGHT_CLICK")) return; + if (e.getClickedBlock() == null) return; + String btype = e.getClickedBlock() + .getType().name(); + if (btype.contains("SHULKER") + || btype.contains("CHEST") + || btype.equals("BARREL") + || btype.equals("ENDER_CHEST") + || btype.equals("HOPPER") + || btype.equals("DROPPER") + || btype.equals("DISPENSER")) { + e.setCancelled(true); + e.getPlayer().sendMessage( + "§c[寻宝] 该物品不可放入容器"); + } + } + + // ★ 铁砧拦截 + @org.bukkit.event.EventHandler(priority = + org.bukkit.event.EventPriority.HIGH) + public void onAnvil( + org.bukkit.event.inventory + .PrepareAnvilEvent e) { + ItemStack first = + e.getInventory().getItem(0); + if (first != null + && TreasureInventory + .isCustomItem(first)) { + e.setResult(null); + } + } + /** 隐藏边框 */ public boolean hideRegionBorder() { if (borderTask != null) { @@ -296,93 +1315,201 @@ public class TreasureManager { */ private TreasureReward parseReward(String line) { plugin.getLogger().info( - "[寻宝] 解析奖励行: " + line); + "[寻宝] ★parseReward收到: [" + + line + "]"); + line = normalizePunctuation(line); + plugin.getLogger().info( + "[寻宝] ★parseReward归一化: [" + + line + "]"); + line = normalizePunctuation(line); + if (line.charAt(0) == '\uFEFF') + line = line.substring(1); + line = line.replace(",", ",") + .replace(":", ":"); if (line.startsWith("贴标:")) { - TreasureReward r = parseCustomItem( + return parseCustomItem( line.substring("贴标:".length())); + } + plugin.getLogger().info( + "[寻宝] 解析奖励: " + line); + + if (line.startsWith("贴标:")) { + return parseCustomItem( + line.substring("贴标:".length())); + } + + if (line.contains("给予") + || line.contains("附魔") + || line.contains("时长")) { plugin.getLogger().info( - "[寻宝] → 贴标结果: " - + (r != null ? "OK" : "NULL")); - return r; + "[寻宝] → 走中文分支"); + return parseChineseReward(line); } - // ★ 统一分隔符:中文逗号→英文逗号 - String normalized = line - .replace(",", ",") - .replace("。", ".") - .replace(":", ":") - .replace(";", ";"); - - String[] p = normalized.split(","); - if (p.length < 2) { - plugin.getLogger().warning( - "[寻宝] ★参数不足: " - + p.length + "个"); - return null; - } - - // ★ 去首尾空格 + 转大写 + String[] p = line.split(","); + if (p.length < 2) return null; String type = p[0].trim().toUpperCase(); - // 债券 + // ★ 债券 if (type.equals("债券")) { - int amt = Integer.parseInt(p[1].trim()); + String amtStr = p[1].trim(); int w = p.length > 2 ? Integer.parseInt(p[2].trim()) : 10; - TreasureReward r = new TreasureReward( + int bMin, bMax; + if (amtStr.contains("~")) { + String[] r = amtStr.split("~"); + bMin = Integer.parseInt(r[0].trim()); + bMax = Integer.parseInt(r[1].trim()); + } else if (amtStr.contains("-")) { + String[] r = amtStr.split("-"); + bMin = Integer.parseInt(r[0].trim()); + bMax = Integer.parseInt(r[1].trim()); + } else { + bMin = Integer.parseInt(amtStr); + bMax = bMin; + } + TreasureReward tr = new TreasureReward( TreasureReward.Type.BOND, - null, 1, amt, null, w, - "§6" + amt + "债券"); - plugin.getLogger().info( - "[寻宝] → 债券: " + amt - + " 权重=" + w); - return r; + null, 1, bMin, null, w, + "§6" + bMin + "~" + bMax + "债券"); + tr.setBondRange(bMin, bMax); + return tr; } - // 命令 + // ★ 命令 if (type.equals("命令")) { String cmd = p[1].trim(); int w = p.length > 2 ? Integer.parseInt(p[2].trim()) : 10; - TreasureReward r = new TreasureReward( + return new TreasureReward( TreasureReward.Type.COMMAND, null, 1, 0, cmd, w, "§a命令奖励"); - plugin.getLogger().info( - "[寻宝] → 命令: " + cmd); - return r; } - // ★ 物品名转大写 - String matName = type; + // ★ 物品 int amt = Integer.parseInt(p[1].trim()); int w = p.length > 2 ? Integer.parseInt(p[2].trim()) : 10; +//打好包了,直接上传测试即可 + // ★ 中文名映射 + String[][] cnNames = { + {"DIAMOND", "钻石"}, + {"GOLD_INGOT", "金锭"}, + {"IRON_INGOT", "铁锭"}, + {"EMERALD", "绿宝石"}, + {"COAL", "煤炭"}, + {"REDSTONE", "红石"}, + {"DIAMOND_SWORD", "钻石剑"}, + {"DIAMOND_PICKAXE", "钻石镐"}, + {"DIAMOND_AXE", "钻石斧"}, + {"DIAMOND_CHESTPLATE", "钻石甲"}, + {"DIAMOND_BOOTS", "钻石靴子"}, + {"DIAMOND_LEGGINGS", "钻石护腿"}, + {"DIAMOND_HELMET", "钻石头盔"}, + {"NETHERITE_SWORD", "下界合金剑"}, + {"NETHERITE_PICKAXE", "下界合金镐"}, + {"NETHERITE_INGOT", "下界合金锭"}, + {"BLAZE_ROD", "烈焰棒"}, + {"BREEZE_ROD", "旋风棒"}, + {"ENDER_PEARL", "末影珍珠"}, + {"GOLDEN_APPLE", "金苹果"}, + {"ENCHANTED_GOLDEN_APPLE", + "附魔金苹果"}, + {"TOTEM_OF_UNDYING", "不死图腾"}, + {"BOW", "弓"}, + {"ARROW", "箭"}, + {"SHIELD", "盾牌"}, + {"BREAD", "面包"}, + {"STICK", "木棍"}, + {"IRON_SWORD", "铁剑"}, + {"WOODEN_SWORD", "木剑"}, + }; - Material m = Material.matchMaterial(matName); - if (m == null) { - plugin.getLogger().warning( - "[寻宝] ★物品识别失败: " - + matName); - } else { - plugin.getLogger().info( - "[寻宝] → 物品: " + m.name() - + " x" + amt - + " 权重=" + w); + String cnName = type; + for (String[] pair : cnNames) { + if (pair[0].equals(type)) { + cnName = pair[1]; + break; + } } - String dn = m != null - ? "§f" + m.name() : "§f" + matName; + String displayName = + "§f" + cnName + " x" + amt; + return new TreasureReward( TreasureReward.Type.ITEM, - matName, amt, 0, null, w, dn); + type, amt, 0, null, w, displayName); } + public void loadSettings(TreasureConfig tc) { + + File f = new File( + plugin.getDataFolder(), + "寻宝/设置.txt"); + if (!f.exists()) { + File dir = new File( + plugin.getDataFolder(), + "寻宝"); + FileGenerator + .ensureTreasureConfig(dir); + } + if (!f.exists()) return; + try (BufferedReader br = + new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() + || line.startsWith("#")) + continue; + line = normalizePunctuation(line); + int ci = line.indexOf(':'); + if (ci < 0) continue; + String k = line.substring(0, ci) + .trim(); + String v = line.substring(ci + 1) + .trim(); + switch (k) { + case "保底债券": + tc.guaranteeBond = + v.equals("是") + || v.equals("true"); + break; + case "随机数量": + String[] rr = + v.split("[~\\-]"); + if (rr.length == 2) { + tc.randomMin = + Integer.parseInt( + rr[0].trim()); + tc.randomMax = + Integer.parseInt( + rr[1].trim()); + } + break; + } + } + } catch (Exception e) { + plugin.getLogger().warning( + "[寻宝] 设置加载失败: " + + e.getMessage()); + } + plugin.getLogger().info( + "[寻宝] 设置: 保底=" + + tc.guaranteeBond + + " 随机=" + tc.randomMin + + "~" + tc.randomMax); + } + /** * 贴标物品解析 * 格式: 物品ID,数量,权重,附魔ID,附魔等级,显示名,时长秒 @@ -571,55 +1698,139 @@ public class TreasureManager { } public boolean spawnChest(TreasureConfig tc) { - if (getChestCount(tc.name) >= tc.maxChests) - return false; - World w = plugin.getServer() .getWorld(tc.world); if (w == null) return false; normalize(tc); - int x = ThreadLocalRandom.current() - .nextInt(tc.minX, tc.maxX + 1); - int z = ThreadLocalRandom.current() - .nextInt(tc.minZ, tc.maxZ + 1); - - // ★ 在Y范围内找可放置的位置 - int y = findPlaceableY(w, x, z, - tc.minY, tc.maxY); - if (y < 0) { - plugin.getLogger().warning( - "[寻宝] 找不到位置: [" + x - + "," + z + "] Y范围=" - + tc.minY + "~" - + tc.maxY); - return false; + // 统计现有宝箱 + java.util.List existing = + new ArrayList<>(); + for (java.util.Map.Entry e + : activeChests.entrySet()) { + if (e.getValue().equals(tc.name) + && e.getKey().getWorld() != null + && e.getKey().getWorld().getName() + .equals(tc.world)) { + existing.add(e.getKey()); + } } - Location loc = new Location(w, x, y, z); - loc.getBlock().setType(Material.CHEST); - activeChests.put(loc, tc.name); + // 超过上限→销毁最早的 + while (existing.size() >= tc.maxChests) { + Location oldest = existing.remove(0); + if (oldest.getBlock().getType() + == Material.CHEST) { + oldest.getBlock() + .setType(Material.AIR); + } + activeChests.remove(oldest); + } - plugin.getLogger().info( - "[寻宝] 宝箱:" - + " " + tc.name - + " [" + x + "," + y - + "," + z + "]" - + " X=" + tc.minX - + "~" + tc.maxX - + " Z=" + tc.minZ - + "~" + tc.maxZ - + " Y=" + tc.minY - + "~" + tc.maxY); + // ★ 50次尝试找合法位置 + for (int attempt = 0; + attempt < 50; attempt++) { + int x = ThreadLocalRandom.current() + .nextInt(tc.minX, tc.maxX + 1); + int z = ThreadLocalRandom.current() + .nextInt(tc.minZ, tc.maxZ + 1); + int y = ThreadLocalRandom.current() + .nextInt(tc.minY, tc.maxY + 1); - Bukkit.broadcastMessage( - "§e§l[寻宝]§r §7" + tc.name - + " §e区域刷新了宝箱!"); + Location loc = + new Location(w, x, y, z); + Block block = loc.getBlock(); + Block above = loc.getBlock() + .getRelative( + org.bukkit.block.BlockFace.UP); - return true; + // ★ 条件1: 当前位置不是箱子 + if (block.getType() == Material.CHEST) + continue; + + // ★ 条件2: 上方1格是空气(开箱空间) + if (above.getType() != Material.AIR) + continue; + + // ★ 放置箱子 + block.setType(Material.CHEST); + tagChest(loc, tc.name); + activeChests.put(loc, tc.name); + + plugin.getLogger().info( + "[寻宝] ★生成: " + tc.name + + " [" + x + "," + + y + "," + z + "]"); + + if (borderShowing + && borderRegionName != null + && borderRegionName + .equals(tc.name)) { + showRegionBorder(tc.name); + } + return true; + } + + plugin.getLogger().warning( + "[寻宝] ★生成失败: " + tc.name); + return false; } + + /** 清除带NBT标记但不在记录中的残留宝箱 */ + public void cleanResidualChests() { + int total = 0; + for (TreasureConfig tc + : configs.values()) { + World w = plugin.getServer() + .getWorld(tc.world); + if (w == null) continue; + normalize(tc); + for (int x = tc.minX; + x <= tc.maxX; x++) { + for (int z = tc.minZ; + z <= tc.maxZ; z++) { + for (int y = tc.minY; + y <= tc.maxY + 2; y++) { + Location loc = + new Location( + w, x, y, z); + if (loc.getBlock().getType() + != Material.CHEST) + continue; + // ★ 有NBT标记→是我们的箱子 + if (!isOurChest(loc)) + continue; + // ★ 不在记录中→残留 + if (!activeChests + .containsKey(loc)) { + String region = + getChestRegion(loc); + long time = + getChestTime(loc); + loc.getBlock().setType( + Material.AIR); + total++; + plugin.getLogger().info( + "[寻宝] 清除残留: [" + + x + "," + + y + "," + + z + "] 区域=" + + region); + } + } + } + } + } + if (total > 0) { + plugin.getLogger().info( + "[寻宝] ★清除残留: " + + total + " 个"); + } + } + + /** * 在Y范围内找可放箱子的位置 * 从Y范围底部向上找: diff --git a/src/main/java/Sdf1_game/TreasureReward.java b/src/main/java/Sdf1_game/TreasureReward.java index 628e7df..c5cd620 100644 --- a/src/main/java/Sdf1_game/TreasureReward.java +++ b/src/main/java/Sdf1_game/TreasureReward.java @@ -1,32 +1,26 @@ package Sdf1_game; -import net.kyori.adventure.text.Component; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.ThreadLocalRandom; public class TreasureReward { public enum Type { - ITEM, BOND, COMMAND + BOND, COMMAND, ITEM } - private final Type type; - private final String materialName; - private final int amount; - private final int bondAmount; - private final String command; - private final int weight; - private final String displayName; - private final int durationSec; - private final String enchant; - private final String rewardLore; + private Type type; + private String materialName; + private int amount; + private int bondAmount; + private int bondMin; + private int bondMax; + private String command; + private int weight; + private String displayName; + private int durationSec; + private String enchant; + private String rewardLore; + private int attackUsesLimit; public TreasureReward(Type type, String materialName, @@ -37,112 +31,94 @@ public class TreasureReward { String displayName, int durationSec, String enchant, - String rewardLore) { + String lore, + int attackUsesLimit) { this.type = type; this.materialName = materialName; - this.amount = Math.max(1, amount); + this.amount = amount; this.bondAmount = bondAmount; + this.bondMin = bondAmount; + this.bondMax = bondAmount; this.command = command; this.weight = weight; this.displayName = displayName; this.durationSec = durationSec; this.enchant = enchant; - this.rewardLore = rewardLore; + this.rewardLore = lore; + this.attackUsesLimit = attackUsesLimit; } public TreasureReward(Type type, - String materialName, - int amount, - int bondAmount, - String command, - int weight, - String displayName) { - this(type, materialName, amount, bondAmount, - command, weight, displayName, - 0, null, null); + String mat, int amt, + int bond, String cmd, + int w, String name) { + this(type, mat, amt, bond, cmd, w, name, + 0, null, null, 0); + } + + public TreasureReward(Type type, + String mat, int amt, + int bond, String cmd, + int w, String name, + int dur, String ench, + String lore) { + this(type, mat, amt, bond, cmd, w, name, + dur, ench, lore, 0); } public Type getType() { return type; } public String getMaterialName() { return materialName; } public int getAmount() { return amount; } public int getBondAmount() { return bondAmount; } + public int getBondMin() { return bondMin; } + public int getBondMax() { return bondMax; } public String getCommand() { return command; } public int getWeight() { return weight; } public String getDisplayName() { return displayName; } public int getDurationSec() { return durationSec; } public String getEnchant() { return enchant; } + public String getLore() { return rewardLore; } + public int getAttackUsesLimit() { return attackUsesLimit; } - public ItemStack toItemStack() { - Material mat = Material.PAPER; - if (materialName != null - && !materialName.isEmpty()) { - Material found = - Material.matchMaterial(materialName); - if (found != null) mat = found; - } - - ItemStack item = new ItemStack(mat, amount); - ItemMeta meta = item.getItemMeta(); - if (meta != null) { - meta.displayName( - Component.text(displayName)); - - List lore = new ArrayList<>(); - if (durationSec > 0) { - lore.add(Component.text( - "§7时限: " + formatTime(durationSec))); - } - if (rewardLore != null - && !rewardLore.isEmpty()) { - lore.add(Component.text(rewardLore)); - } - lore.add(Component.text("§7点击领取")); - meta.lore(lore); - - applyEnchant(meta, enchant); - - item.setItemMeta(meta); - } - return item; + public void setBondRange(int min, int max) { + this.bondMin = min; + this.bondMax = max; + this.bondAmount = min; } - /** ★ 安全附魔:用 NamespacedKey */ - static void applyEnchant(ItemMeta meta, - String enchant) { - if (enchant == null || enchant.isEmpty()) - return; - String[] ep = enchant.split(","); - if (ep.length != 2) return; - try { - // 支持 "KNOCKBACK" 或 "minecraft:knockback" - String key = ep[0].trim(); - if (!key.contains(":")) { - key = "minecraft:" + key.toLowerCase(); - } - NamespacedKey nk = - NamespacedKey.fromString(key); - if (nk == null) return; - Enchantment ench = - Enchantment.getByKey(nk); - if (ench == null) return; - int lvl = Integer.parseInt(ep[1].trim()); - meta.addEnchant(ench, lvl, true); - meta.addItemFlags( - ItemFlag.HIDE_ENCHANTS); - } catch (Exception ignored) { - } + public int rollBondAmount() { + if (bondMax <= bondMin) return bondMin; + return ThreadLocalRandom.current() + .nextInt(bondMin, bondMax + 1); } - static String formatTime(int sec) { - if (sec >= 3600) { - int h = sec / 3600; - int m = (sec % 3600) / 60; - return m > 0 ? h + "小时" + m + "分钟" - : h + "小时"; + public static String formatTime(int s) { + if (s <= 0) return "永久"; + int h = s / 3600; + int m = (s % 3600) / 60; + int sec = s % 60; + if (h > 0) return h + "小时" + m + "分钟"; + if (m > 0) return m + "分钟" + sec + "秒"; + return sec + "秒"; + } + + public static int parseDuration(String input) { + if (input == null || input.isEmpty()) return 0; + String s = input.trim().toLowerCase(); + if (s.matches("\\d+")) return Integer.parseInt(s); + if (s.contains("小时") || s.contains("时")) { + return Integer.parseInt(s.replaceAll("[^\\d]", "")) * 3600; } - int m = sec / 60; - int s = sec % 60; - return s > 0 ? m + "分钟" + s + "秒" - : m + "分钟"; + if (s.contains("分钟") || s.contains("分")) { + return Integer.parseInt(s.replaceAll("[^\\d]", "")) * 60; + } + if (s.contains("秒")) { + return Integer.parseInt(s.replaceAll("[^\\d]", "")); + } + if (s.contains("天")) { + return Integer.parseInt(s.replaceAll("[^\\d]", "")) * 86400; + } + try { return Integer.parseInt(s); } + catch (Exception e) { return 0; } } } diff --git a/src/main/java/Sdf1_game/TreasureSpawnTask.java b/src/main/java/Sdf1_game/TreasureSpawnTask.java index bcbec98..83b1d23 100644 --- a/src/main/java/Sdf1_game/TreasureSpawnTask.java +++ b/src/main/java/Sdf1_game/TreasureSpawnTask.java @@ -1,5 +1,6 @@ package Sdf1_game; +import org.bukkit.Bukkit; import org.bukkit.scheduler.BukkitRunnable; import java.util.concurrent.ThreadLocalRandom; @@ -8,6 +9,8 @@ public class TreasureSpawnTask extends BukkitRunnable { private final Main plugin; private final TreasureManager manager; + private int offlineCount = 0; + private boolean paused = false; public TreasureSpawnTask(Main plugin, TreasureManager manager) { @@ -17,12 +20,75 @@ public class TreasureSpawnTask extends BukkitRunnable { @Override public void run() { + int online = + Bukkit.getOnlinePlayers().size(); + + // 无人在线 + if (online == 0) { + offlineCount++; + if (offlineCount == 1) { + plugin.getLogger().info( + "[寻宝] 无人在线,60秒后暂停"); + } + if (offlineCount >= 12 && !paused) { + paused = true; + manager.removeAllChests(); + plugin.getLogger().info( + "[寻宝] ★暂停+收回"); + } + return; + } + + if (paused) { + paused = false; + plugin.getLogger().info( + "[寻宝] ★玩家上线"); + } + + offlineCount = 0; + long now = System.currentTimeMillis(); + for (TreasureConfig tc : manager.getConfigs().values()) { + + // ★ 首次生成 + if (tc.lastSpawnTime == 0) { + manager.spawnChest(tc); + tc.lastSpawnTime = now; + // ★ 通知所有玩家 + broadcastSpawn(tc); + continue; + } + + // 正常计时 + long elapsed = now - tc.lastSpawnTime; + long need = tc.spawnInterval * 1000L; + if (elapsed < need) continue; + int roll = ThreadLocalRandom.current() - .nextInt(100); - if (roll >= tc.spawnChance) continue; + .nextInt(1, 101); + if (roll > tc.spawnChance) { + tc.lastSpawnTime = now; + continue; + } + manager.spawnChest(tc); + tc.lastSpawnTime = now; + // ★ 通知所有玩家 + broadcastSpawn(tc); + plugin.getLogger().info( + "[寻宝] ★生成: " + tc.name); } } + private void broadcastSpawn(TreasureConfig tc) { + for (org.bukkit.entity.Player p + : Bukkit.getOnlinePlayers()) { + p.sendMessage("§6§l[寻宝] §e" + + tc.name + " §a区域刷新了宝箱!"); + } + plugin.getLogger().info( + "[寻宝] " + tc.name + + " 区域刷新了宝箱!"); + } + }