From be816cb3596d78c3c6b8e7aa723a65e1de06be37 Mon Sep 17 00:00:00 2001 From: laforetbrut Date: Wed, 22 Apr 2026 20:15:30 +0200 Subject: [PATCH] Phase 19: wire save_on_death + save_on_respawn (dead config) to fix keep-charm edge cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report: Twilight Forest 'Charm of Keeping' + ReviveMe interaction loses inventory over multiple deaths. Analysis concludes this is a Twilight/ReviveMe event-priority interaction, not a PlayerSync bug — but two dead config options hid admin-facing levers that help mitigate the case. (1) save_on_death was declared in JdbcConfig but NEVER read in code. The death snapshot ran unconditionally. Now gated: setting save_on_death=false disables the LivingDeathEvent-driven pre-drop snapshot. The normal onPlayerLogout save still fires on disconnect, so nothing is lost — but admins diagnosing a keeping-charm interaction can quickly turn off the aggressive death snapshot to rule PlayerSync in or out. (2) save_on_respawn was also declared but never read. Added a new @SubscribeEvent PlayerEvent.PlayerRespawnEvent handler that calls snapshotAndQueueSave(player, 'RESPAWN') after the respawn completes. This captures the post-death state AFTER keeping-charms / Corail Tombstone / similar mods have restored their preserved items, so PlayerSync's DB row reflects the actual post-respawn inventory rather than the pre-drop snapshot from onPlayerDeath. Excludes end-portal exit (isEndConquered) since that's not a death respawn — no need to overwrite. Combined effect: if a player dies, charm-keeps items, respawns, the DB ends up with: t=0 death snapshot (pre-drop, full inventory) t=X respawn snapshot (post-drop, kept items + whatever charm restored) The respawn snapshot overwrites the death one by virtue of running later. A disconnect between t=0 and t=X still saves via onPlayerLogout anyway, so no loss window opens. No change to the duplication-safety guarantees from Phases 15-18: onPlayerDeath still checks event.isCanceled() for ReviveMe, the RESPAWN snapshot goes through the normal snapshotAndQueueSave pipeline with all the P0-a/b/c guards and the 2-phase-commit logout_started_at tracking. Answer to the user's question: the keep-charm inventory loss is overwhelmingly likely to be a ReviveMe x Twilight Forest event-priority bug outside PlayerSync's control, but this commit exposes two levers (save_on_death, save_on_respawn) that let admins test whether PlayerSync is contributing — setting save_on_death=false should make the symptom unchanged if the root cause is external. --- .../fubuki/playersync/sync/VanillaSync.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index f6580f0..adac379 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -1136,6 +1136,32 @@ public class VanillaSync { snapshotAndQueueSave(event.getEntity(), "SaveToFile"); } + /** + * PHASE 19: optional save on respawn — gated by {@code save_on_respawn}. + * Runs AFTER the respawn is complete so the snapshot captures the final + * post-death inventory (vanilla drops + whatever keeping-charms preserved). + * This OVERWRITES the pre-death snapshot taken in onPlayerDeath with the + * correct authoritative state, so the next restore sees the real inventory. + * + *

Essential when mods like Twilight Forest's Charm of Keeping or + * Corail Tombstone restore items on respawn — without this event, + * PlayerSync's DB row stays at the pre-death snapshot until the next + * auto-save, and a quick disconnect loses the keep-charm state. + */ + @SubscribeEvent + public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) { + try { + if (!JdbcConfig.SAVE_ON_RESPAWN.get()) return; + if (event.isEndConquered()) return; // End-portal exit, not a death respawn + Player player = event.getEntity(); + SyncLogger.playerEvent(player.getUUID().toString(), "RESPAWN", + "Snapshot post-respawn inventory (keeping-charm / tombstone mods)"); + snapshotAndQueueSave(player, "RESPAWN"); + } catch (Exception e) { + PlayerSync.LOGGER.warn("[respawn-save] trigger failed: {}", e.getMessage()); + } + } + /** * Phase 4: optional save on dimension change — gated by * {@code save_on_dimension_change} config. Protects against mid-teleport @@ -2372,9 +2398,20 @@ public class VanillaSync { // Always cache curios on death (API returns empty for dead players later) CuriosCache.tryStoreCuriosToCache(player); + // PHASE 19: honour save_on_death config. Keeping-charm / death-drop-replacement + // mods (Twilight Forest Charm of Keeping, Corail Tombstone items, etc.) run + // their own event handlers during LivingDeathEvent. When their priority is + // higher than ours (LOW), they've already moved items out of the drops list + // — our snapshot at this point captures the post-keep inventory, which is + // usually the desired behaviour. + // If admins diagnose a keeping-charm interaction, setting save_on_death=false + // disables this snapshot entirely; the normal onPlayerLogout save still fires + // on disconnect and captures the post-respawn state. + if (!JdbcConfig.SAVE_ON_DEATH.get()) return; + // Immediately save ALL player data on death (snapshot + async). - // LivingDeathEvent fires BEFORE items are dropped, so the snapshot captures - // the full pre-death inventory including backpack contents. + // LivingDeathEvent fires BEFORE vanilla items are dropped, so the snapshot + // captures whatever keeping-charms have already reserved + the rest. // This protects against: server crash after death, network disconnect before // onPlayerLogout fires, or any scenario where the logout handler is skipped. // The normal logout save will overwrite this with the final post-death state.