diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java index 5256f8d..1d4d252 100644 --- a/src/main/java/Sdf1_login/DatabaseManager.java +++ b/src/main/java/Sdf1_login/DatabaseManager.java @@ -914,19 +914,17 @@ public class DatabaseManager { // ==================== 背包备份 ==================== + /** + * 保存背包主备份 —— 追加写入,每位玩家仅保留最近 3 份(更早的自动清理)。 + * ★ 2026-07-09 改版:原为「DELETE 全部再 INSERT 单份」,会导致 PVP 竞技场/测试场 + * 掉线时用当前(竞技场装备)背包覆盖玩家真实背包。改为滚动保留 3 份后, + * 即便发生误覆盖,玩家仍可从「最近 3 份」里挑出真实背包还原。 + */ public void saveInventoryBackup(String name, String contents, String armor, String extra, int level, double experience) { try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); PreparedStatement ps = db.prepareStatement( "INSERT INTO " @@ -950,6 +948,19 @@ public class DatabaseManager { System.currentTimeMillis()); ps.executeUpdate(); ps.close(); + + // 仅保留最近 3 份,删除该玩家更早的备份 + PreparedStatement del = + db.prepareStatement( + "DELETE FROM inventory_backups " + + "WHERE player_name=? AND id NOT IN (" + + "SELECT id FROM inventory_backups " + + "WHERE player_name=? " + + "ORDER BY save_time DESC LIMIT 3)"); + del.setString(1, name); + del.setString(2, name); + del.executeUpdate(); + del.close(); } catch (SQLException e) { e.printStackTrace(); } diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index 2f24318..fafe966 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -2241,7 +2241,19 @@ public class Main extends JavaPlugin } } - if (wasLoggedIn || hasRealItems) { + // ★ 2026-07-09:PVP 竞技场 / 测试场内的玩家,其真实背包已存于独立的 + // pvp_inventory_backup,当前身上的背包是竞技场装备, + // 绝不能写回主备份,否则真实装备会被覆盖丢失。 + boolean inArenaBackup = pvPArenaManager != null + && pvPArenaManager.isInPVPArena(name); + boolean inTestBackup = pvpTestManager != null + && pvpTestManager.isInTest(p); + + if (inArenaBackup || inTestBackup) { + getLogger().info("[Sdf1_login] " + name + + " 在PVP竞技场/测试场内退出,跳过主备份保存" + + "(真实背包已存于独立备份)"); + } else if (wasLoggedIn || hasRealItems) { try { String contents = InventorySerializer @@ -6097,12 +6109,12 @@ public class Main extends JavaPlugin } } else { List> backups = - db.getInventoryBackups(backTarget, 5); + db.getInventoryBackups(backTarget, 3); if (backups.isEmpty()) { sender.sendMessage("§c" + backTarget + " 没有背包备份"); return true; } - sender.sendMessage("§e§l=== " + backTarget + " 背包备份 ==="); + sender.sendMessage("§e§l=== " + backTarget + " 背包备份(最近3份)==="); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("MM-dd HH:mm:ss"); for (Map b : backups) { @@ -6121,6 +6133,109 @@ public class Main extends JavaPlugin return true; } +// ===== backup(玩家自助还原主背包,最近3份任选)===== + if (sub.equals("backup")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + Player self = (Player) sender; + if (pvPArenaManager != null + && pvPArenaManager.isInPVPArena( + self.getName())) { + sender.sendMessage( + "§c竞技场内无法还原主背包,请先 /pvp leave"); + return true; + } + if (pvpTestManager != null + && pvpTestManager.isInTest(self)) { + sender.sendMessage( + "§c测试场内无法还原主背包,请先 /pvp leave"); + return true; + } + // 带 #编号 → 还原指定备份到自己的背包 + if (args.length >= 2 + && args[1].startsWith("#")) { + int bid; + try { + bid = Integer.parseInt( + args[1].replace("#", "")); + } catch (NumberFormatException ex) { + sender.sendMessage("§c无效编号"); + return true; + } + String[] bk = + db.getInventoryBackupById(bid); + if (bk == null) { + sender.sendMessage("§c备份 #" + bid + + " 不存在"); + return true; + } + try { + self.getInventory().setContents( + InventorySerializer.fromBase64( + bk[0])); + if (bk[1] != null + && !bk[1].isEmpty()) + self.getInventory() + .setArmorContents( + InventorySerializer + .fromBase64( + bk[1])); + if (bk[2] != null + && !bk[2].isEmpty()) + self.getInventory() + .setExtraContents( + InventorySerializer + .fromBase64( + bk[2])); + int lv = Integer.parseInt(bk[3]); + if (lv > 0) self.setLevel(lv); + self.setExp((float) Double + .parseDouble(bk[4])); + sender.sendMessage( + "§a已还原主背包备份 #" + bid); + } catch (Exception e) { + sender.sendMessage("§c还原失败: " + + e.getMessage()); + } + return true; + } + // 不带编号 → 列出最近 3 份供选择 + List> myList = + db.getInventoryBackups( + self.getName(), 3); + if (myList.isEmpty()) { + sender.sendMessage( + "§c你没有任何背包备份"); + return true; + } + sender.sendMessage( + "§e§l=== 你的背包备份(最近3份)==="); + java.text.SimpleDateFormat sdf = + new java.text.SimpleDateFormat( + "MM-dd HH:mm:ss"); + for (Map b : myList) { + int bid = ((Number) b.get("id")) + .intValue(); + Object stObj = b.get("save_time"); + long st = stObj != null + ? ((Number) stObj) + .longValue() + : 0; + String time = st > 0 + ? sdf.format( + new java.util.Date(st)) + : "未知时间"; + sender.sendMessage("§7#" + bid + " §f" + + time + " §7: " + + getBackupItemPreview(bid)); + } + sender.sendMessage( + "§7使用 §e/sdf1_login backup #编号 §7还原对应备份"); + return true; + } + // ===== pw / password ===== if (sub.equals("pw") || sub.equals("password")) { if (!(sender instanceof Player)) {