修复PVP竞技场/测试场掉线覆盖主背包:主备份改保留最近3份且竞技场内跳过写入,新增/sdf1_login backup玩家自助还原(最近3份任选)

This commit is contained in:
2026-07-09 22:29:02 +08:00
parent 36b8192dc9
commit e03db026b9
2 changed files with 137 additions and 11 deletions
+19 -8
View File
@@ -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();
}
+118 -3
View File
@@ -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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> 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<String, Object> 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)) {