From a85131708fb9640673e89ae1b67796ef5e874512 Mon Sep 17 00:00:00 2001 From: laforetbrut Date: Thu, 26 Mar 2026 17:22:21 +0100 Subject: [PATCH] Fix NeoForge attachment sync, kick system, and backpack upgrades 1. NeoForge attachments (SOL Onion, Ars Nouveau, etc.): - deserializeAttachments signature is (Provider, CompoundTag) not (CompoundTag) - reflection was failing silently, nothing restored - Use serializeAttachments(Provider) directly for saving instead of saveWithoutId() for cleaner approach - This fixes SOL Onion food diversity, Ars Nouveau mana/glyphs, Iron's Spellbooks, Pehkui scale, and all other NeoForge attachments 2. Multi-server kick: - Add secondary kick check in PlayerLoggedInEvent as fallback - Mark online=1 SYNCHRONOUSLY on login to close race condition where async doPlayerJoin hasn't set online=1 yet 3. Backpack upgrades: - Call refreshInventoryForInputOutput() before reading from BackpackStorage to flush pending wrapper changes Vyrriox --- .../fubuki/playersync/sync/VanillaSync.java | 25 +++++++++++ .../playersync/sync/addons/ModCompatSync.java | 44 +++++++++++-------- .../playersync/sync/addons/ModsSupport.java | 9 ++++ 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 4a0596c..2bf72f7 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -411,6 +411,31 @@ public class VanillaSync { } } + /** + * FIX: Secondary kick check during PlayerLoggedInEvent. + * PlayerNegotiationEvent fires very early and disconnect() may not always work. + * This provides a reliable fallback that kicks the player from the server thread. + * Also marks online=1 SYNCHRONOUSLY here to close the race condition window + * where doPlayerJoin (async) hasn't set online=1 yet. + */ + @SubscribeEvent + public static void onPlayerLoggedInKickCheck(PlayerEvent.PlayerLoggedInEvent event) { + if (!JdbcConfig.KICK_WHEN_ALREADY_ONLINE.get()) return; + ServerPlayer player = (ServerPlayer) event.getEntity(); + String player_uuid = player.getUUID().toString(); + + try { + // Mark online=1 SYNCHRONOUSLY to prevent race conditions. + // Without this, a player joining Server B while still on Server A might slip through + // because the async doPlayerJoin on Server A hasn't set online=1 yet. + JDBCsetUp.executePreparedUpdate( + "UPDATE player_data SET online=1, last_server=? WHERE uuid=?", + JdbcConfig.SERVER_ID.get(), player_uuid); + } catch (SQLException e) { + PlayerSync.LOGGER.error("Error setting online flag for player {}", player_uuid, e); + } + } + @SubscribeEvent public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { executorService.submit(() -> { 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 61eae68..3160b68 100644 --- a/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/addons/ModCompatSync.java @@ -272,20 +272,21 @@ public class ModCompatSync { try { if (!(player instanceof net.minecraft.server.level.ServerPlayer serverPlayer)) return; - net.minecraft.nbt.CompoundTag playerNbt = new net.minecraft.nbt.CompoundTag(); - serverPlayer.saveWithoutId(playerNbt); + // FIX: Use serializeAttachments(Provider) directly instead of saveWithoutId() + // This is the exact method NeoForge uses to save attachments, no full player save needed + java.lang.reflect.Method serializeMethod = net.neoforged.neoforge.attachment.AttachmentHolder.class + .getDeclaredMethod("serializeAttachments", net.minecraft.core.HolderLookup.Provider.class); + serializeMethod.setAccessible(true); + net.minecraft.nbt.CompoundTag attachments = (net.minecraft.nbt.CompoundTag) + serializeMethod.invoke(player, serverPlayer.getServer().registryAccess()); - // NeoForge stores all attachment data under this key - if (playerNbt.contains("neoforge:attachments", net.minecraft.nbt.Tag.TAG_COMPOUND)) { - net.minecraft.nbt.CompoundTag attachments = playerNbt.getCompound("neoforge:attachments"); - if (!attachments.isEmpty()) { - String serialized = VanillaSync.serializeTagToBinaryBase64(attachments); - JDBCsetUp.executePreparedUpdate( - "REPLACE INTO mod_player_data (uuid, mod_id, data_value) VALUES (?, ?, ?)", - player.getUUID().toString(), "neoforge_attachments", serialized); - PlayerSync.LOGGER.debug("Saved NeoForge attachments for player {} ({} keys)", - player.getUUID(), attachments.getAllKeys().size()); - } + if (attachments != null && !attachments.isEmpty()) { + String serialized = VanillaSync.serializeTagToBinaryBase64(attachments); + JDBCsetUp.executePreparedUpdate( + "REPLACE INTO mod_player_data (uuid, mod_id, data_value) VALUES (?, ?, ?)", + player.getUUID().toString(), "neoforge_attachments", serialized); + PlayerSync.LOGGER.debug("Saved NeoForge attachments for player {} ({} keys)", + player.getUUID(), attachments.getAllKeys().size()); } } catch (Exception e) { PlayerSync.LOGGER.error("Error saving NeoForge attachments for player {}", player.getUUID(), e); @@ -296,9 +297,15 @@ public class ModCompatSync { * Restores NeoForge player attachments from the database. * Uses reflection to call NeoForge's internal deserializeAttachments method, * which ensures the exact same deserialization path as a normal player load. + * + * FIX: The method signature is deserializeAttachments(HolderLookup.Provider, CompoundTag), + * NOT deserializeAttachments(CompoundTag). The old code passed wrong parameters causing + * silent failure - no NeoForge attachment data (SOL Onion, Ars Nouveau, etc.) was restored. */ public static void restoreNeoForgeAttachments(Player player) { try { + if (!(player instanceof net.minecraft.server.level.ServerPlayer serverPlayer)) return; + String serialized; try (JDBCsetUp.QueryResult qr = JDBCsetUp.executePreparedQuery( "SELECT data_value FROM mod_player_data WHERE uuid=? AND mod_id=?", @@ -313,16 +320,17 @@ public class ModCompatSync { net.minecraft.nbt.CompoundTag attachments = VanillaSync.deserializeBinaryBase64Tag(serialized); if (attachments.isEmpty()) return; - // Create a wrapper CompoundTag with the attachments key + // FIX: Correct method signature is (HolderLookup.Provider, CompoundTag), not (CompoundTag) + // The wrapper must contain the "neoforge:attachments" key for the method to find the data net.minecraft.nbt.CompoundTag wrapper = new net.minecraft.nbt.CompoundTag(); wrapper.put("neoforge:attachments", attachments); - // Use reflection to call the package-private deserializeAttachments method - // This ensures we use NeoForge's exact deserialization logic java.lang.reflect.Method deserializeMethod = net.neoforged.neoforge.attachment.AttachmentHolder.class - .getDeclaredMethod("deserializeAttachments", net.minecraft.nbt.CompoundTag.class); + .getDeclaredMethod("deserializeAttachments", + net.minecraft.core.HolderLookup.Provider.class, + net.minecraft.nbt.CompoundTag.class); deserializeMethod.setAccessible(true); - deserializeMethod.invoke(player, wrapper); + deserializeMethod.invoke(player, serverPlayer.getServer().registryAccess(), wrapper); PlayerSync.LOGGER.info("Restored NeoForge attachments for player {} ({} keys)", player.getUUID(), attachments.getAllKeys().size()); 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 6c8e9ba..c6058c0 100644 --- a/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/addons/ModsSupport.java @@ -259,6 +259,15 @@ public class ModsSupport { Optional uuidOpt = backpackWrapper.getContentsUuid(); if (uuidOpt.isPresent()) { UUID contentsUuid = uuidOpt.get(); + + // FIX: Read the full contents NBT from the wrapper's in-memory state, + // not from BackpackStorage which may have stale data if the wrapper + // hasn't flushed recent changes (e.g. upgrade modifications). + // refreshInventoryForInputOutput triggers an internal save to BackpackStorage. + try { + backpackWrapper.refreshInventoryForInputOutput(); + } catch (Exception ignored) {} + CompoundTag backpackNbt = net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().getOrCreateBackpackContents(contentsUuid); saveStorageContents(contentsUuid, backpackNbt); PlayerSync.LOGGER.info("Saved backpack data for UUID {}", contentsUuid);