Add generic NeoForge attachment sync for full mod compatibility

Adds a generic system that syncs ALL NeoForge player attachments,
covering per-player data from every mod in the modpack:

- Ars Nouveau: mana, glyph/spell knowledge
- Iron's Spellbooks: mana, learned spells, cooldowns
- Pehkui: player scale
- Spice of Life: Onion: food diversity history
- And ANY other mod using NeoForge's attachment system

Implementation:
- Save: extracts neoforge:attachments tag from player.saveWithoutId()
- Restore: uses reflection to call NeoForge's deserializeAttachments()
  which ensures exact same deserialization path as normal player load
- Stored as BNBT in mod_player_data table (mod_id=neoforge_attachments)

Also verified CosmeticArmours (mod id: cosmeticarmoursmod) and
CosmeticWeapons (mod id: cosmeticweaponsmod) are content-only mods
that add craftable items - no custom player storage, fully handled
by existing inventory sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
laforetbrut 2026-03-26 11:43:42 +01:00
parent 5576d7f7e2
commit f37e58be53

View File

@ -252,6 +252,85 @@ public class ModCompatSync {
}
}
// ============================
// Generic NeoForge Attachment Sync
// ============================
/**
* Saves ALL NeoForge player attachments to the database.
* This covers per-player data from ALL mods, including:
* - Ars Nouveau (mana, glyph knowledge)
* - Iron's Spellbooks (mana, learned spells)
* - Pehkui (player scale)
* - Spice of Life: Onion (food diversity)
* - Any other mod using NeoForge's attachment system
*
* Uses player.saveWithoutId() to extract the attachments tag from the
* player's full serialized NBT, ensuring we capture ALL mod data.
*/
public static void storeNeoForgeAttachments(Player player) {
try {
if (!(player instanceof net.minecraft.server.level.ServerPlayer serverPlayer)) return;
net.minecraft.nbt.CompoundTag playerNbt = new net.minecraft.nbt.CompoundTag();
serverPlayer.saveWithoutId(playerNbt);
// 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());
}
}
} catch (Exception e) {
PlayerSync.LOGGER.error("Error saving NeoForge attachments for player {}", player.getUUID(), e);
}
}
/**
* 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.
*/
public static void restoreNeoForgeAttachments(Player player) {
try {
String serialized;
try (JDBCsetUp.QueryResult qr = JDBCsetUp.executePreparedQuery(
"SELECT data_value FROM mod_player_data WHERE uuid=? AND mod_id=?",
player.getUUID().toString(), "neoforge_attachments")) {
ResultSet rs = qr.resultSet();
if (!rs.next()) return;
serialized = rs.getString("data_value");
}
if (serialized == null || !serialized.startsWith("BNBT:")) return;
net.minecraft.nbt.CompoundTag attachments = VanillaSync.deserializeBinaryBase64Tag(serialized);
if (attachments.isEmpty()) return;
// Create a wrapper CompoundTag with the attachments key
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);
deserializeMethod.setAccessible(true);
deserializeMethod.invoke(player, wrapper);
PlayerSync.LOGGER.info("Restored NeoForge attachments for player {} ({} keys)",
player.getUUID(), attachments.getAllKeys().size());
} catch (Exception e) {
PlayerSync.LOGGER.error("Error restoring NeoForge attachments for player {}", player.getUUID(), e);
}
}
// ============================
// Convenience methods
// ============================
@ -263,6 +342,7 @@ public class ModCompatSync {
public static void storeAll(Player player) {
storeAccessories(player);
storeCosmeticArmor(player);
storeNeoForgeAttachments(player);
}
/**
@ -272,5 +352,6 @@ public class ModCompatSync {
public static void restoreAll(Player player) {
restoreAccessories(player);
restoreCosmeticArmor(player);
restoreNeoForgeAttachments(player);
}
}