修复三处Bug:附魔书NBT空壳(ID权威重建)、PVP死亡榜世界未销毁不清战机、TPA processedRequests永不清导致秒过期
This commit is contained in:
@@ -410,8 +410,8 @@ public class PVPArenaManager implements Listener {
|
||||
}
|
||||
scoreEntries.clear();
|
||||
// 按击杀数降序排列(手动选择排序,不使用 Comparator/lambda,规避 JDK26 javac 类型推断内部崩溃)
|
||||
List<String> sorted = new ArrayList<String>(inPVPArena.size());
|
||||
sorted.addAll(inPVPArena);
|
||||
// ★ 显示所有曾踏入本世界的参与者(即使已离开但世界未销毁,战绩仍保留在死亡榜上)
|
||||
List<String> sorted = new ArrayList<String>(pvpKills.keySet());
|
||||
for (int i = 0; i < sorted.size(); i++) {
|
||||
for (int j = i + 1; j < sorted.size(); j++) {
|
||||
if (pvpKills.getOrDefault(sorted.get(j), 0) > pvpKills.getOrDefault(sorted.get(i), 0)) {
|
||||
@@ -430,23 +430,18 @@ public class PVPArenaManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/** 玩家离开PVP世界时清除其战绩并恢复默认记分板 */
|
||||
/** 玩家离开PVP世界时仅将其个人记分板切回主世界;其战绩(击杀/死亡)保留在死亡榜上,
|
||||
* 直到 PVP 世界被真正销毁(deletePVPWorld) 才统一清空。
|
||||
* ★ 修复:之前无论世界是否销毁都会删除该玩家战绩,导致“世界不销毁却清空战机”。 */
|
||||
private void cleanupPlayerStats(Player p) {
|
||||
if (p == null) return;
|
||||
String name = p.getName();
|
||||
pvpKills.remove(name);
|
||||
pvpDeaths.remove(name);
|
||||
String entry = scoreEntries.remove(name);
|
||||
if (pvpScoreboard != null && entry != null) {
|
||||
pvpScoreboard.resetScores(entry);
|
||||
}
|
||||
// 仅将离场玩家个人记分板切回主世界(其战绩行仍保留在PVP记分板供其余玩家查看)
|
||||
if (p.isOnline()) {
|
||||
p.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
|
||||
}
|
||||
// 无人则销毁本局记分板,下次重建
|
||||
if (inPVPArena.isEmpty() && pvpScoreboard != null) {
|
||||
pvpScoreboard = null;
|
||||
} else {
|
||||
// 世界未被销毁 → 保留所有参与者战绩,仅刷新榜面(含已离开玩家)
|
||||
if (pvpScoreboard != null) {
|
||||
refreshPVPScoreboard();
|
||||
}
|
||||
}
|
||||
@@ -707,6 +702,11 @@ public class PVPArenaManager implements Listener {
|
||||
*/
|
||||
private void deletePVPWorld(World world) {
|
||||
if (world == null) return;
|
||||
// ★ 世界销毁:清空本局所有玩家战绩(击杀/死亡)与死亡榜(仅此时才清战机)
|
||||
pvpKills.clear();
|
||||
pvpDeaths.clear();
|
||||
scoreEntries.clear();
|
||||
pvpScoreboard = null;
|
||||
// 兜底:确保世界内无玩家(理论上冷却结束时已无人)
|
||||
for (Player p : new ArrayList<>(world.getPlayers())) {
|
||||
World main = Bukkit.getWorlds().get(0);
|
||||
@@ -1761,6 +1761,11 @@ public class PVPArenaManager implements Listener {
|
||||
guiReopening.clear();
|
||||
guiOpenedMillis.clear();
|
||||
selectedTier.clear();
|
||||
// ★ 同时清空本局所有玩家战绩(击杀/死亡)与死亡榜
|
||||
pvpKills.clear();
|
||||
pvpDeaths.clear();
|
||||
scoreEntries.clear();
|
||||
pvpScoreboard = null;
|
||||
// 取消所有超时定时器
|
||||
for (BukkitTask t : kickTimeoutTasks.values()) {
|
||||
if (t != null && !t.isCancelled()) t.cancel();
|
||||
|
||||
@@ -173,9 +173,11 @@ public class ShopManager implements Listener {
|
||||
}
|
||||
private static void regEnchant(String cn, String key) {
|
||||
try {
|
||||
Enchantment e = Enchantment.getByKey(NamespacedKey.minecraft(key));
|
||||
// ★ 优先用现代 Registry API(paper-api 新版 Enchantment.getByKey 可能返回 null,导致 ENCHANT_MAP 为空→附魔书全空壳)
|
||||
Enchantment e = org.bukkit.Registry.ENCHANTMENT.get(NamespacedKey.minecraft(key));
|
||||
if (e == null) e = Enchantment.getByKey(NamespacedKey.minecraft(key));
|
||||
if (e != null) ENCHANT_MAP.put(cn, e);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
public static class CartItem {
|
||||
@@ -1401,6 +1403,68 @@ public class ShopManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 由商品ID权威重建附魔书(ID格式 <enchantKey>_<level>,如 MENDING_I / SHARPNESS_V / FLAME_I / BREACH_IV / WIND_BURST_III)。
|
||||
* ID 使用标准 Bukkit 附魔键,比显示名可靠(显示名多变:火矢/破甲/致密/风爆/水下速掘 等不匹配关键词)。
|
||||
*/
|
||||
private ItemStack buildEnchantedBookFromId(String id) {
|
||||
if (id == null) return null;
|
||||
int us = id.lastIndexOf('_');
|
||||
if (us <= 0 || us >= id.length() - 1) return null;
|
||||
String keyPart = id.substring(0, us).toLowerCase();
|
||||
String lvlPart = id.substring(us + 1).trim();
|
||||
if (lvlPart.isEmpty()) return null;
|
||||
org.bukkit.enchantments.Enchantment ench = null;
|
||||
try {
|
||||
ench = org.bukkit.Registry.ENCHANTMENT.get(NamespacedKey.minecraft(keyPart));
|
||||
} catch (Throwable ignored) {}
|
||||
if (ench == null) {
|
||||
try { ench = Enchantment.getByKey(NamespacedKey.minecraft(keyPart)); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (ench == null) return null;
|
||||
int lvl = parseLevelFromRomanOrArabic(lvlPart);
|
||||
if (lvl < 1) lvl = 1;
|
||||
int max = ench.getMaxLevel();
|
||||
if (max > 0 && lvl > max) lvl = max;
|
||||
ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
|
||||
EnchantmentStorageMeta meta = (EnchantmentStorageMeta) book.getItemMeta();
|
||||
if (meta == null) return null;
|
||||
meta.addStoredEnchant(ench, lvl, true);
|
||||
book.setItemMeta(meta);
|
||||
return book;
|
||||
}
|
||||
|
||||
private int parseLevelFromRomanOrArabic(String s) {
|
||||
if (s == null || s.isEmpty()) return 1;
|
||||
String t = s.trim();
|
||||
java.util.regex.Matcher m = ROMAN_PAT.matcher(t);
|
||||
if (m.find()) { int v = romanToInt(m.group()); if (v >= 1) return v; }
|
||||
java.util.regex.Matcher m2 = ARABIC_PAT.matcher(t);
|
||||
if (m2.find()) {
|
||||
try { int v = Integer.parseInt(m2.group()); if (v >= 1) return v; } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 若传入的附魔书是“空壳”(无存储附魔),则尝试用商品ID权威重建附魔NBT。
|
||||
* 用于 Web 商城发放附魔书兜底,避免显示名解析失败/ENCHANT_MAP 为空导致空壳。
|
||||
*/
|
||||
public ItemStack ensureEnchantedBookNbt(ItemStack itemStack, String itemId, int amount) {
|
||||
if (itemStack == null || itemStack.getType() != Material.ENCHANTED_BOOK) return itemStack;
|
||||
ItemMeta m = itemStack.getItemMeta();
|
||||
boolean empty = !(m instanceof EnchantmentStorageMeta)
|
||||
|| ((EnchantmentStorageMeta) m).getStoredEnchants().isEmpty();
|
||||
if (!empty) return itemStack;
|
||||
ItemStack rebuilt = buildEnchantedBookFromId(itemId);
|
||||
if (rebuilt != null) {
|
||||
rebuilt.setAmount(amount);
|
||||
plugin.getLogger().info("[Shop] 附魔书空壳已用ID重建NBT: " + itemId);
|
||||
return rebuilt;
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
private int parseEnchantLevel(String name, String keyword) {
|
||||
int idx = name.indexOf(keyword);
|
||||
if (idx < 0) return 1;
|
||||
|
||||
@@ -53,9 +53,6 @@ public class TeleportManager implements Listener {
|
||||
// 请求类型(tpa/tpahere/tpaall)仅驻留内存,不落库;落库既画蛇添足又引入墙钟依赖
|
||||
private final Map<String, String> teleportRequestTypes = new ConcurrentHashMap<>();
|
||||
private volatile long teleportCooldownMs = 30_000L; // 可配置的冷却时间
|
||||
|
||||
// ★ 已处理/已拒绝的请求(不能再使用)
|
||||
private final Set<String> processedRequests = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// ★ 管理员配置状态 (玩家名 → 配置状态)
|
||||
// 状态值: "valid" = 等待输入有效时间, "interval" = 等待输入间隔时间
|
||||
@@ -249,11 +246,6 @@ public class TeleportManager implements Listener {
|
||||
private boolean isValidRequest(String receiver, String sender) {
|
||||
String key = sender + ":" + receiver;
|
||||
|
||||
// ★ 检查是否已处理过
|
||||
if (processedRequests.contains(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> senders = incomingRequests.get(receiver);
|
||||
if (senders == null || !senders.contains(sender)) {
|
||||
return false;
|
||||
@@ -261,14 +253,6 @@ public class TeleportManager implements Listener {
|
||||
return !isTeleportRequestExpired(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记请求为已处理(防止二次接受/拒绝)
|
||||
*/
|
||||
private void markRequestProcessed(String sender, String receiver) {
|
||||
String key = sender + ":" + receiver;
|
||||
processedRequests.add(key);
|
||||
}
|
||||
|
||||
// ==================== 判断玩家类型 ====================
|
||||
|
||||
/**
|
||||
@@ -702,8 +686,6 @@ public class TeleportManager implements Listener {
|
||||
// 清理请求
|
||||
removeIncomingRequest(targetName, senderName);
|
||||
removeOutgoingRequest(senderName, targetName);
|
||||
// ★ 标记自动接受的请求为已处理
|
||||
markRequestProcessed(senderName, targetName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -782,9 +764,6 @@ public class TeleportManager implements Listener {
|
||||
removeIncomingRequest(player.getName(), targetName);
|
||||
removeOutgoingRequest(targetName, player.getName());
|
||||
|
||||
// ★ 标记请求已处理(防止二次使用)
|
||||
markRequestProcessed(targetName, player.getName());
|
||||
|
||||
player.sendMessage("§a[传送] 已传送到 §f" + targetName + " §a身边");
|
||||
actualSender.sendMessage("§a[传送] §f" + player.getName() + " §a已接受请求");
|
||||
teleportCooldown.remove(actualSender.getName());
|
||||
@@ -850,9 +829,6 @@ public class TeleportManager implements Listener {
|
||||
removeIncomingRequest(player.getName(), targetName);
|
||||
removeOutgoingRequest(targetName, player.getName());
|
||||
|
||||
// ★ 标记请求已拒绝(防止二次使用)
|
||||
markRequestProcessed(targetName, player.getName());
|
||||
|
||||
player.sendMessage("§6[传送] 已拒绝来自 §f" + targetName + " §c的传送请求");
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6311,6 +6311,10 @@ public class WebManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 附魔书空壳兜底(ID权威重建):若上面任一路径给出无附魔的附魔书
|
||||
// (显示名解析失败、ENCHANT_MAP 为空等),用商品ID(MENDING_I 等)重建NBT。
|
||||
itemStack = plugin.getShopManager().ensureEnchantedBookNbt(itemStack, itemId, amount);
|
||||
|
||||
if (itemStack != null) {
|
||||
java.util.HashMap<Integer, org.bukkit.inventory.ItemStack> leftover = player.getInventory().addItem(itemStack);
|
||||
if (!leftover.isEmpty()) {
|
||||
@@ -6430,6 +6434,8 @@ public class WebManager {
|
||||
itemStack = fromName;
|
||||
}
|
||||
}
|
||||
// ★ 附魔书空壳兜底(ID权威重建)
|
||||
itemStack = plugin.getShopManager().ensureEnchantedBookNbt(itemStack, itemId, amount);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user