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
This commit is contained in:
laforetbrut 2026-03-26 17:22:21 +01:00
parent fc7d81f914
commit a85131708f
3 changed files with 60 additions and 18 deletions

View File

@ -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(() -> {

View File

@ -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());

View File

@ -259,6 +259,15 @@ public class ModsSupport {
Optional<UUID> 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);