From badc87c84e4cb1aa1508fae65c9a439d6bf6b599 Mon Sep 17 00:00:00 2001 From: laforetbrut Date: Wed, 15 Apr 2026 11:24:18 +0200 Subject: [PATCH] Fix backpack crash loss, ender chest restore, ReviveMe compat, effect sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backpack data loss on server crash: - Periodic auto-save (every 5min) now includes backpack content snapshots. Previously backpacks were only saved on logout/shutdown — hard crashes (OOM, watchdog, kill -9) skipped both, losing all backpack changes. - snapshotBackpackData captures NBT with .copy() on main thread. Backpack ender chest restore mismatch: - doBackPackRestore now scans ender chest in addition to main inventory. Save side already scanned ender chest, but restore didn't — backpacks in ender chest were saved to DB but never restored on join. ReviveMe mod compatibility: - Dead player kick check now uses health <= 0 instead of isDeadOrDying(). ReviveMe puts players in a "downed" state (alive but isDeadOrDying=true) — previously these players were kicked on join. Infinite effect filtering (phantom effects fix): - Effects with infinite duration are now skipped during save. These come from ReviveMe (downed state effects with MAX_VALUE duration), beacons, and other mods. Syncing them across servers caused phantom effects. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fubuki/playersync/sync/VanillaSync.java | 27 ++++++++----- .../playersync/sync/addons/ModsSupport.java | 40 +++++++++++++------ 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 5ca16d7..ed36887 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -250,7 +250,10 @@ public class VanillaSync { // FIX: If the player entity spawned dead/dying, kick+respawn them. // All entity modifications (removeTag, teleport, disconnect) are scheduled on the // main thread — the old code called removeTag from this background thread which is unsafe. - if (serverPlayer.isDeadOrDying()) { + // FIX: ReviveMe compatibility — check if the player is in a "downed" state (not truly dead). + // ReviveMe cancels LivingDeathEvent and puts players at low health with special effects. + // These players have health > 0 and should NOT be kicked. Only kick if actually dead (health <= 0). + if (serverPlayer.isDeadOrDying() && serverPlayer.getHealth() <= 0) { deadPlayerWhileLogging.add(player_uuid); server.execute(() -> { serverPlayer.removeTag("player_synced"); @@ -1335,7 +1338,15 @@ public class VanillaSync { Map effectMap = new HashMap<>(); if (!player.isDeadOrDying()) { for (Map.Entry, MobEffectInstance> entry : player.getActiveEffectsMap().entrySet()) { - Tag effectTag = entry.getValue().save(); + MobEffectInstance effect = entry.getValue(); + // FIX: Skip infinite-duration effects. These come from: + // - ReviveMe mod (downed state effects with Integer.MAX_VALUE duration) + // - Beacons (ambient effects re-applied every tick while in range) + // - Other mods that add permanent effects + // Syncing these across servers causes phantom effects (player gets + // downed-state effects or beacon effects on a server without the source). + if (effect.isInfiniteDuration()) continue; + Tag effectTag = effect.save(); effectMap.put(BuiltInRegistries.MOB_EFFECT.getId(entry.getKey().value()), serialize(effectTag.toString())); } } @@ -1498,9 +1509,9 @@ public class VanillaSync { // non-thread-safe way. All entity reads are now done in snapshotPlayerData() // on the main thread, and the background task only does DB writes. // - // Backpack / SophisticatedStorage / RS2 contents live in server-side SavedData - // and are always saved completely on player logout + server shutdown — no need - // to include them in the periodic auto-save. + // FIX: Backpack/SS contents are NOW included in the periodic auto-save. + // Previously only saved on logout + shutdown, but hard crashes skip both + // → backpack changes lost. snapshotBackpackData is fast (~1ms per backpack). if (autoSaveTickCounter >= AUTO_SAVE_INTERVAL_TICKS) { autoSaveTickCounter = 0; MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); @@ -1514,17 +1525,15 @@ public class VanillaSync { ReentrantLock lock = getPlayerLock(puuid); if (!lock.tryLock()) continue; try { - // === MAIN THREAD: snapshot ALL entity state (no DB I/O) === - // snapshotPlayerData now includes curios, accessories, - // cosmeticarmor, and neoforge attachments. final PlayerDataSnapshot snapshot = snapshotPlayerData(player); + final Map backpackSnapshots = ModsSupport.snapshotBackpackData(player); - // === BACKGROUND THREAD: DB writes only (no entity access) === executorService.submit(() -> { ReentrantLock bgLock = getPlayerLock(puuid); if (!bgLock.tryLock()) return; try { writeSnapshotToDB(snapshot); + ModsSupport.saveBackpackSnapshots(backpackSnapshots); } catch (Exception e) { PlayerSync.LOGGER.error("Error auto-saving player {}", puuid, e); } finally { diff --git a/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java index 5992c7b..656fb4f 100644 --- a/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java @@ -28,25 +28,39 @@ public class ModsSupport { public void doBackPackRestore(Player player) { if (ModList.get().isLoaded("sophisticatedbackpacks")) { PlayerSync.LOGGER.info("Restoring backpack data for player {}", player.getUUID()); + // Restore backpacks from main inventory net.p3pp3rf1y.sophisticatedbackpacks.util.PlayerInventoryProvider.get().runOnBackpacks(player, (ItemStack backpackItem, String handler, String identifier, int slot) -> { - net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.IBackpackWrapper backpackWrapper = net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.BackpackWrapper - .fromStack(backpackItem); - - Optional uuidOpt = backpackWrapper.getContentsUuid(); - if (uuidOpt.isPresent()) { - UUID contentsUuid = uuidOpt.get(); - restoreStorageContents(contentsUuid, (nbt) -> { - net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setBackpackContents(contentsUuid, nbt); - PlayerSync.LOGGER.info("Restored backpack data for UUID {}", contentsUuid); - }); - } else { - PlayerSync.LOGGER.warn("Backpack item in slot {} has no contentsUuid during restore", slot); - } + restoreSingleBackpack(backpackItem); return false; }); + // FIX: Also restore backpacks from ender chest (save side scans ender chest too) + for (int i = 0; i < player.getEnderChestInventory().getContainerSize(); i++) { + ItemStack stack = player.getEnderChestInventory().getItem(i); + if (!stack.isEmpty()) { + restoreSingleBackpack(stack); + } + } } } + private void restoreSingleBackpack(ItemStack stack) { + try { + net.minecraft.resources.ResourceLocation loc = net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(stack.getItem()); + if (loc == null || !loc.getNamespace().equals("sophisticatedbackpacks")) return; + + net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.IBackpackWrapper backpackWrapper = + net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.BackpackWrapper.fromStack(stack); + Optional uuidOpt = backpackWrapper.getContentsUuid(); + if (uuidOpt.isPresent()) { + UUID contentsUuid = uuidOpt.get(); + restoreStorageContents(contentsUuid, (nbt) -> { + net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setBackpackContents(contentsUuid, nbt); + PlayerSync.LOGGER.info("Restored backpack data for UUID {}", contentsUuid); + }); + } + } catch (Exception ignored) {} + } + /** * Generic method to restore storage contents from DB for a given UUID. * Used for both Sophisticated Backpacks and Sophisticated Storage items.