From e03db026b9e6ea955c8be3fb0def2af678078ee6 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Thu, 9 Jul 2026 22:29:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DPVP=E7=AB=9E=E6=8A=80?= =?UTF-8?q?=E5=9C=BA/=E6=B5=8B=E8=AF=95=E5=9C=BA=E6=8E=89=E7=BA=BF?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E4=B8=BB=E8=83=8C=E5=8C=85=EF=BC=9A=E4=B8=BB?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E6=94=B9=E4=BF=9D=E7=95=99=E6=9C=80=E8=BF=91?= =?UTF-8?q?3=E4=BB=BD=E4=B8=94=E7=AB=9E=E6=8A=80=E5=9C=BA=E5=86=85?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E5=86=99=E5=85=A5=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?/sdf1=5Flogin=20backup=E7=8E=A9=E5=AE=B6=E8=87=AA=E5=8A=A9?= =?UTF-8?q?=E8=BF=98=E5=8E=9F(=E6=9C=80=E8=BF=913=E4=BB=BD=E4=BB=BB?= =?UTF-8?q?=E9=80=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Sdf1_login/DatabaseManager.java | 27 ++-- src/main/java/Sdf1_login/Main.java | 121 +++++++++++++++++- 2 files changed, 137 insertions(+), 11 deletions(-) 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)) {