Fix backpack crash loss, ender chest restore, ReviveMe compat, effect sync
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) <noreply@anthropic.com>
This commit is contained in:
parent
1d30184aba
commit
badc87c84e
|
|
@ -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<Integer, String> effectMap = new HashMap<>();
|
||||
if (!player.isDeadOrDying()) {
|
||||
for (Map.Entry<Holder<MobEffect>, 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<UUID, CompoundTag> 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 {
|
||||
|
|
|
|||
|
|
@ -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<UUID> 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<UUID> 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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user