From f042058e5b1bd49a67c29c860a33a123723ceac8 Mon Sep 17 00:00:00 2001 From: laforetbrut Date: Sun, 5 Apr 2026 20:26:10 +0200 Subject: [PATCH] Fix Accessories/CosmeticArmor duplication + guard remaining online=0 Accessories & CosmeticArmor duplication fix: - snapshotAccessories() and snapshotCosmeticArmor() returned null when slots were empty, causing writeModSnapshot to SKIP the write. The DB kept stale data from when slots had items, restoring them on next join. - Now return "{}" (like snapshotCuriosData already does), so empty state is properly written to DB. On restore, apply*FromData clears slots when it sees "{}" (length <= 2). Guard remaining online=0 writes: - deadPlayerWhileLogging and syncNotCompletedPlayer logout paths now use AND last_server=? to prevent setting online=0 for a player that already moved to another server. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/vip/fubuki/playersync/sync/VanillaSync.java | 6 ++++-- .../fubuki/playersync/sync/addons/ModCompatSync.java | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 7782b95..905eaa9 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -1003,7 +1003,8 @@ public class VanillaSync { if (deadPlayerWhileLogging.remove(player_uuid)) { PlayerSync.LOGGER.warn("A dead or dying player was kicked, uuid: {}", player_uuid); try { - JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", player_uuid); + JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=? AND last_server=?", + player_uuid, JdbcConfig.SERVER_ID.get()); } catch (SQLException e) { PlayerSync.LOGGER.error("Error marking dead player offline: {}", player_uuid, e); } @@ -1015,7 +1016,8 @@ public class VanillaSync { if (syncNotCompletedPlayer.remove(player_uuid)) { PlayerSync.LOGGER.warn("Player {} logged out with uncompleted sync. Data won't be saved for safety.", player_uuid); try { - JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", player_uuid); + JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=? AND last_server=?", + player_uuid, JdbcConfig.SERVER_ID.get()); } catch (SQLException e) { PlayerSync.LOGGER.error("Error marking unsynced player offline: {}", player_uuid, e); } diff --git a/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java b/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java index 56298b2..47c5908 100644 --- a/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java @@ -487,7 +487,10 @@ public class ModCompatSync { } } } - return flatMap.isEmpty() ? null : flatMap.toString(); + // FIX ANTI-DUPLICATION: Return "{}" for empty slots, NOT null. + // Null causes writeModSnapshot to SKIP the write, keeping stale data in DB. + // "{}" is written to DB, and on restore applyAccessoriesFromData clears slots. + return flatMap.toString(); } catch (Exception e) { PlayerSync.LOGGER.error("Error snapshotting Accessories for player {}", player.getUUID(), e); return null; @@ -511,7 +514,10 @@ public class ModCompatSync { flatMap.put(i, VanillaSync.getNbtForStorage(stack)); } } - return flatMap.isEmpty() ? null : flatMap.toString(); + // FIX ANTI-DUPLICATION: Return "{}" for empty slots, NOT null. + // Null causes writeModSnapshot to SKIP the write, keeping stale data in DB. + // "{}" is written to DB, and on restore applyCosmeticArmorFromData clears slots. + return flatMap.toString(); } catch (Exception e) { PlayerSync.LOGGER.error("Error snapshotting CosmeticArmor for player {}", player.getUUID(), e); return null;