Fix Sophisticated Storage shulkers, RS2 disks, and kick system
1. Sophisticated Storage shulkers/barrels/chests:
- ROOT CAUSE: UUID stored as DataComponent (not in CustomData).
extractStorageUuid() only checked CustomData, missing the UUID.
- FIX: Use StackStorageWrapper.fromStack(provider, item).getContentsUuid()
which reads the DataComponent via the proper API.
- Also scan ender chest for packed storage items.
2. Refined Storage 2 disks:
- ROOT CAUSE: save() on StorageRepositoryImpl returned data in an
unknown codec format that our extraction couldn't parse.
- FIX: Read/write the .dat file directly from disk after forcing
a save flush. This uses the exact NBT format RS2 writes.
- Search multiple NBT structures (direct keys, nested compounds,
list-of-pairs) to handle any codec format.
- On restore: write entries into .dat file, clear DimensionDataStorage
cache via reflection to force RS2 to reload.
3. Kick system:
- ROOT CAUSE: PlayerNegotiationEvent.getConnection().disconnect()
does NOT work in NeoForge 1.21.1 (too early in connection).
- FIX: Full duplicate check moved to PlayerLoggedInEvent with
HIGHEST priority. Uses player.connection.disconnect() which
is reliable on the server thread.
- Marks online=1 synchronously to close race condition.
Vyrriox
This commit is contained in:
parent
e907bcbfb0
commit
6c5807d3c8
|
|
@ -415,27 +415,69 @@ 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.
|
||||
* FIX: Full duplicate-login kick check during PlayerLoggedInEvent.
|
||||
* PlayerNegotiationEvent.getConnection().disconnect() does NOT reliably disconnect
|
||||
* the player in NeoForge 1.21.1. By the time PlayerLoggedInEvent fires, we have
|
||||
* a full ServerPlayer with player.connection.disconnect() which is reliable.
|
||||
*
|
||||
* Also marks online=1 SYNCHRONOUSLY to close the race condition window.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
@SubscribeEvent(priority = net.neoforged.bus.api.EventPriority.HIGHEST)
|
||||
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();
|
||||
|
||||
if (!JdbcConfig.KICK_WHEN_ALREADY_ONLINE.get()) {
|
||||
// Still mark online even if kick is disabled
|
||||
try {
|
||||
JDBCsetUp.executePreparedUpdate(
|
||||
"UPDATE player_data SET online=1, last_server=? WHERE uuid=?",
|
||||
JdbcConfig.SERVER_ID.get(), player_uuid);
|
||||
} catch (SQLException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
boolean online = false;
|
||||
int lastServer = 0;
|
||||
|
||||
try (JDBCsetUp.QueryResult qr = JDBCsetUp.executePreparedQuery(
|
||||
"SELECT online, last_server FROM player_data WHERE uuid=?", player_uuid)) {
|
||||
ResultSet rs = qr.resultSet();
|
||||
if (rs.next()) {
|
||||
online = rs.getBoolean("online");
|
||||
lastServer = rs.getInt("last_server");
|
||||
}
|
||||
}
|
||||
|
||||
if (online && lastServer != JdbcConfig.SERVER_ID.get()) {
|
||||
// Check if the other server is still alive
|
||||
try (JDBCsetUp.QueryResult qr2 = JDBCsetUp.executePreparedQuery(
|
||||
"SELECT last_update, enable FROM server_info WHERE id=?", lastServer)) {
|
||||
ResultSet rs2 = qr2.resultSet();
|
||||
if (rs2.next()) {
|
||||
long lastUpdate = rs2.getLong("last_update");
|
||||
boolean enable = rs2.getBoolean("enable");
|
||||
if (enable && System.currentTimeMillis() < lastUpdate + 300000L) {
|
||||
// Other server is alive → KICK using ServerPlayer.connection which works reliably
|
||||
PlayerSync.LOGGER.warn("Kicking player {} - already online on server {}", player_uuid, lastServer);
|
||||
player.connection.disconnect(Component.translatableWithFallback(
|
||||
"playersync.already_online",
|
||||
"You can't join more than one synchronization server at the same time."));
|
||||
return;
|
||||
}
|
||||
// Other server is dead, disable it
|
||||
JDBCsetUp.executePreparedUpdate("UPDATE server_info SET enable=0 WHERE id=?", lastServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark online=1 SYNCHRONOUSLY
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error during kick check for player {}", player_uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,29 +286,45 @@ public class ModsSupport {
|
|||
*/
|
||||
public static void storeSophisticatedStorageItems(Player player) {
|
||||
PlayerSync.LOGGER.info("Scanning inventory for Sophisticated Storage items for player {}", player.getUUID());
|
||||
Inventory inventory = player.getInventory();
|
||||
scanAndStoreSophisticatedStorageInContainer(player.getInventory());
|
||||
// Also scan ender chest
|
||||
for (int i = 0; i < player.getEnderChestInventory().getContainerSize(); i++) {
|
||||
ItemStack stack = player.getEnderChestInventory().getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
storeSingleSophisticatedStorageItem(stack);
|
||||
}
|
||||
}
|
||||
|
||||
private static void scanAndStoreSophisticatedStorageInContainer(Inventory inventory) {
|
||||
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||
ItemStack stack = inventory.getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
storeSingleSophisticatedStorageItem(stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSophisticatedStorageItem(stack)) continue;
|
||||
private static void storeSingleSophisticatedStorageItem(ItemStack stack) {
|
||||
if (!isSophisticatedStorageItem(stack)) return;
|
||||
|
||||
// FIX: Extract UUID using both "contentsUuid" (backpacks) and "storageUuid" (storage items) keys
|
||||
UUID contentsUuid = extractStorageUuid(stack);
|
||||
if (contentsUuid == null) contentsUuid = extractContentsUuid(stack);
|
||||
if (contentsUuid == null) continue;
|
||||
try {
|
||||
// FIX: Use the StackStorageWrapper API to get the UUID via DataComponent,
|
||||
// NOT CustomData extraction. In 1.21.1, the UUID is a proper DataComponent
|
||||
// managed by ModCoreDataComponents, not an NBT tag in CustomData.
|
||||
net.p3pp3rf1y.sophisticatedstorage.item.StackStorageWrapper wrapper =
|
||||
net.p3pp3rf1y.sophisticatedstorage.item.StackStorageWrapper.fromStack(
|
||||
net.neoforged.neoforge.server.ServerLifecycleHooks.getCurrentServer().registryAccess(), stack);
|
||||
Optional<UUID> uuidOpt = wrapper.getContentsUuid();
|
||||
if (uuidOpt.isEmpty()) return;
|
||||
|
||||
try {
|
||||
// FIX: Use ItemContentsStorage (Sophisticated Storage's own SavedData), NOT BackpackStorage
|
||||
CompoundTag storageNbt = net.p3pp3rf1y.sophisticatedstorage.block.ItemContentsStorage.get().getOrCreateStorageContents(contentsUuid);
|
||||
if (storageNbt != null && !storageNbt.isEmpty()) {
|
||||
saveStorageContents(contentsUuid, storageNbt);
|
||||
PlayerSync.LOGGER.info("Saved Sophisticated Storage item data for UUID {}", contentsUuid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error saving Sophisticated Storage data for UUID {}", contentsUuid, e);
|
||||
UUID contentsUuid = uuidOpt.get();
|
||||
CompoundTag storageNbt = net.p3pp3rf1y.sophisticatedstorage.block.ItemContentsStorage.get()
|
||||
.getOrCreateStorageContents(contentsUuid);
|
||||
if (storageNbt != null && !storageNbt.isEmpty()) {
|
||||
saveStorageContents(contentsUuid, storageNbt);
|
||||
PlayerSync.LOGGER.info("Saved Sophisticated Storage item data for UUID {}", contentsUuid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error saving Sophisticated Storage data for item", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -317,29 +333,45 @@ public class ModsSupport {
|
|||
*/
|
||||
public static void restoreSophisticatedStorageItems(Player player) {
|
||||
PlayerSync.LOGGER.info("Restoring Sophisticated Storage items for player {}", player.getUUID());
|
||||
Inventory inventory = player.getInventory();
|
||||
restoreSophisticatedStorageInContainer(player.getInventory());
|
||||
// Also restore ender chest items
|
||||
for (int i = 0; i < player.getEnderChestInventory().getContainerSize(); i++) {
|
||||
ItemStack stack = player.getEnderChestInventory().getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
restoreSingleSophisticatedStorageItem(stack);
|
||||
}
|
||||
}
|
||||
|
||||
private static void restoreSophisticatedStorageInContainer(Inventory inventory) {
|
||||
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||
ItemStack stack = inventory.getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
restoreSingleSophisticatedStorageItem(stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSophisticatedStorageItem(stack)) continue;
|
||||
private static void restoreSingleSophisticatedStorageItem(ItemStack stack) {
|
||||
if (!isSophisticatedStorageItem(stack)) return;
|
||||
|
||||
// FIX: Try both UUID keys
|
||||
UUID contentsUuid = extractStorageUuid(stack);
|
||||
if (contentsUuid == null) contentsUuid = extractContentsUuid(stack);
|
||||
if (contentsUuid == null) continue;
|
||||
try {
|
||||
net.p3pp3rf1y.sophisticatedstorage.item.StackStorageWrapper wrapper =
|
||||
net.p3pp3rf1y.sophisticatedstorage.item.StackStorageWrapper.fromStack(
|
||||
net.neoforged.neoforge.server.ServerLifecycleHooks.getCurrentServer().registryAccess(), stack);
|
||||
Optional<UUID> uuidOpt = wrapper.getContentsUuid();
|
||||
if (uuidOpt.isEmpty()) return;
|
||||
|
||||
final UUID finalUuid = contentsUuid;
|
||||
restoreStorageContents(contentsUuid, (nbt) -> {
|
||||
UUID finalUuid = uuidOpt.get();
|
||||
restoreStorageContents(finalUuid, (nbt) -> {
|
||||
try {
|
||||
// FIX: Use ItemContentsStorage, NOT BackpackStorage
|
||||
net.p3pp3rf1y.sophisticatedstorage.block.ItemContentsStorage.get().setStorageContents(finalUuid, nbt);
|
||||
net.p3pp3rf1y.sophisticatedstorage.block.ItemContentsStorage.get()
|
||||
.setStorageContents(finalUuid, nbt);
|
||||
PlayerSync.LOGGER.info("Restored Sophisticated Storage item data for UUID {}", finalUuid);
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error restoring Sophisticated Storage data for UUID {}", finalUuid, e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error restoring Sophisticated Storage item", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -406,6 +438,11 @@ public class ModsSupport {
|
|||
* The actual storage data lives in a world-level SavedData (StorageRepositoryImpl).
|
||||
* We extract individual entries from the saved data and store them in our DB.
|
||||
*/
|
||||
/**
|
||||
* Saves RS2 disk storage by reading the SavedData .dat file directly from disk.
|
||||
* This avoids issues with the in-memory API format by reading the raw NBT that RS2 writes.
|
||||
* The SavedData file name is "refinedstorage_storages" and is stored in the overworld's data/ folder.
|
||||
*/
|
||||
public static void storeRefinedStorageDisks(Player player) {
|
||||
if (!ModList.get().isLoaded("refinedstorage")) return;
|
||||
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
|
||||
|
|
@ -414,24 +451,35 @@ public class ModsSupport {
|
|||
if (diskUuids.isEmpty()) return;
|
||||
|
||||
try {
|
||||
// Force RS2's SavedData to flush to disk before reading
|
||||
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
|
||||
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
|
||||
if (repo instanceof net.minecraft.world.level.saveddata.SavedData sd) {
|
||||
sd.setDirty();
|
||||
}
|
||||
sp.getServer().overworld().getDataStorage().save();
|
||||
|
||||
// Serialize the full repository to NBT via SavedData.save()
|
||||
if (repo instanceof net.minecraft.world.level.saveddata.SavedData savedData) {
|
||||
net.minecraft.nbt.CompoundTag fullNbt = new net.minecraft.nbt.CompoundTag();
|
||||
savedData.save(fullNbt, sp.getServer().registryAccess());
|
||||
// Read the .dat file directly (getDataFile is private, use reflection)
|
||||
java.io.File datFile = getRS2DataFile(sp);
|
||||
if (datFile == null || !datFile.exists()) {
|
||||
PlayerSync.LOGGER.warn("RS2 storage data file not found: {}", datFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
for (UUID uuid : diskUuids) {
|
||||
net.minecraft.nbt.CompoundTag entryNbt = extractRS2Entry(fullNbt, uuid);
|
||||
if (entryNbt != null && !entryNbt.isEmpty()) {
|
||||
// Store the entry NBT along with a wrapper that includes the UUID key
|
||||
// so we can reconstruct the map format on restore
|
||||
net.minecraft.nbt.CompoundTag wrapper = new net.minecraft.nbt.CompoundTag();
|
||||
wrapper.put(uuid.toString(), entryNbt);
|
||||
saveStorageContents(uuid, wrapper);
|
||||
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {}", uuid);
|
||||
}
|
||||
net.minecraft.nbt.CompoundTag fileNbt = net.minecraft.nbt.NbtIo.readCompressed(
|
||||
datFile.toPath(), net.minecraft.nbt.NbtAccounter.unlimitedHeap());
|
||||
// .dat file structure: { "data": { ...codec-encoded map... }, "DataVersion": int }
|
||||
net.minecraft.nbt.CompoundTag dataNbt = fileNbt.getCompound("data");
|
||||
|
||||
for (UUID uuid : diskUuids) {
|
||||
String uuidStr = uuid.toString();
|
||||
// Search for the UUID key in the data (may be top-level or nested)
|
||||
net.minecraft.nbt.CompoundTag entryNbt = findRS2EntryInNbt(dataNbt, uuidStr);
|
||||
if (entryNbt != null && !entryNbt.isEmpty()) {
|
||||
saveStorageContents(uuid, entryNbt);
|
||||
PlayerSync.LOGGER.info("Saved RS2 disk data for UUID {} ({} tags)", uuid, entryNbt.getAllKeys().size());
|
||||
} else {
|
||||
PlayerSync.LOGGER.warn("RS2 disk UUID {} not found in saved data. Keys: {}", uuid, dataNbt.getAllKeys());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
@ -440,9 +488,8 @@ public class ModsSupport {
|
|||
}
|
||||
|
||||
/**
|
||||
* Restores RS2 disk storage contents from the database.
|
||||
* Uses reflection to access the StorageRepositoryImpl's codec for proper deserialization,
|
||||
* then calls the public set() method to inject entries into the live repository.
|
||||
* Restores RS2 disk storage by writing entries back into the SavedData .dat file
|
||||
* and reloading the repository. This ensures the data format matches exactly what RS2 expects.
|
||||
*/
|
||||
public static void restoreRefinedStorageDisks(Player player) {
|
||||
if (!ModList.get().isLoaded("refinedstorage")) return;
|
||||
|
|
@ -452,20 +499,66 @@ public class ModsSupport {
|
|||
if (diskUuids.isEmpty()) return;
|
||||
|
||||
try {
|
||||
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
|
||||
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
|
||||
// Read the current .dat file
|
||||
var dataStorage = sp.getServer().overworld().getDataStorage();
|
||||
java.io.File datFile = getRS2DataFile(sp);
|
||||
|
||||
net.minecraft.nbt.CompoundTag fileNbt;
|
||||
if (datFile.exists()) {
|
||||
fileNbt = net.minecraft.nbt.NbtIo.readCompressed(
|
||||
datFile.toPath(), net.minecraft.nbt.NbtAccounter.unlimitedHeap());
|
||||
} else {
|
||||
fileNbt = new net.minecraft.nbt.CompoundTag();
|
||||
fileNbt.put("data", new net.minecraft.nbt.CompoundTag());
|
||||
}
|
||||
net.minecraft.nbt.CompoundTag dataNbt = fileNbt.getCompound("data");
|
||||
|
||||
boolean modified = false;
|
||||
for (UUID uuid : diskUuids) {
|
||||
// FIX: Always overwrite with DB data (source of truth). Previously skipped if storage
|
||||
// existed locally, causing stale data to persist when a player modified a disk on
|
||||
// another server and came back.
|
||||
restoreStorageContents(uuid, (nbt) -> {
|
||||
try {
|
||||
injectRS2StorageEntry(repo, nbt, sp);
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error injecting RS2 storage for UUID {}", uuid, e);
|
||||
final UUID fUuid = uuid;
|
||||
try (JDBCsetUp.QueryResult qr = JDBCsetUp.executePreparedQuery(
|
||||
"SELECT backpack_nbt FROM backpack_data WHERE uuid=?", uuid.toString())) {
|
||||
java.sql.ResultSet rs = qr.resultSet();
|
||||
if (!rs.next()) continue;
|
||||
String serialized = rs.getString("backpack_nbt");
|
||||
if (serialized == null) continue;
|
||||
|
||||
CompoundTag entryNbt;
|
||||
if (serialized.startsWith("BNBT:")) {
|
||||
entryNbt = VanillaSync.deserializeBinaryBase64Tag(serialized);
|
||||
} else {
|
||||
String nbtStr = VanillaSync.deserializeString(serialized);
|
||||
entryNbt = TagParser.parseTag(nbtStr);
|
||||
}
|
||||
});
|
||||
|
||||
// Inject into the data NBT at the right location
|
||||
injectRS2EntryIntoNbt(dataNbt, uuid.toString(), entryNbt);
|
||||
modified = true;
|
||||
PlayerSync.LOGGER.info("Restored RS2 disk data for UUID {}", uuid);
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error restoring RS2 disk data for UUID {}", fUuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
// Write the modified .dat file back and force RS2 to reload
|
||||
fileNbt.put("data", dataNbt);
|
||||
net.minecraft.nbt.NbtIo.writeCompressed(fileNbt, datFile.toPath());
|
||||
PlayerSync.LOGGER.info("Wrote modified RS2 storage data file");
|
||||
|
||||
// Force the StorageRepository to reload from disk
|
||||
// The simplest way is via reflection on the data storage cache
|
||||
try {
|
||||
// Remove the cached SavedData so RS2 reloads from file on next access
|
||||
java.lang.reflect.Field cacheField = dataStorage.getClass().getDeclaredField("cache");
|
||||
cacheField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, ?> cache = (java.util.Map<String, ?>) cacheField.get(dataStorage);
|
||||
cache.remove("refinedstorage_storages");
|
||||
PlayerSync.LOGGER.info("Cleared RS2 storage cache to force reload");
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.warn("Could not clear RS2 cache, data may need server restart to take effect", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Error restoring RS2 disk data for player {}", player.getUUID(), e);
|
||||
|
|
@ -518,67 +611,73 @@ public class ModsSupport {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extracts an individual storage entry from the full StorageRepository NBT by UUID.
|
||||
* The save() format uses UUID strings as CompoundTag keys (unboundedMap codec).
|
||||
* Gets the RS2 SavedData .dat file path using reflection on DimensionDataStorage.
|
||||
*/
|
||||
private static net.minecraft.nbt.CompoundTag extractRS2Entry(net.minecraft.nbt.CompoundTag fullNbt, UUID uuid) {
|
||||
String uuidStr = uuid.toString();
|
||||
// Direct key lookup (standard unboundedMap format)
|
||||
if (fullNbt.contains(uuidStr, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
return fullNbt.getCompound(uuidStr);
|
||||
private static java.io.File getRS2DataFile(net.minecraft.server.level.ServerPlayer sp) {
|
||||
try {
|
||||
var dataStorage = sp.getServer().overworld().getDataStorage();
|
||||
// DimensionDataStorage stores files in a "data" subfolder of the world directory
|
||||
// Use reflection to get the dataFolder field
|
||||
java.lang.reflect.Field dataFolderField = dataStorage.getClass().getDeclaredField("dataFolder");
|
||||
dataFolderField.setAccessible(true);
|
||||
java.io.File dataFolder = (java.io.File) dataFolderField.get(dataStorage);
|
||||
return new java.io.File(dataFolder, "refinedstorage_storages.dat");
|
||||
} catch (Exception e) {
|
||||
// Fallback: construct the path manually from the world directory
|
||||
try {
|
||||
java.nio.file.Path worldDir = sp.getServer().getServerDirectory();
|
||||
java.io.File levelName = worldDir.resolve(
|
||||
sp.getServer().getWorldData().getLevelName()).toFile();
|
||||
return new java.io.File(new java.io.File(levelName, "data"), "refinedstorage_storages.dat");
|
||||
} catch (Exception e2) {
|
||||
PlayerSync.LOGGER.error("Failed to locate RS2 data file", e2);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Some SavedData implementations wrap data under a "data" key
|
||||
for (String key : fullNbt.getAllKeys()) {
|
||||
if (fullNbt.contains(key, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
net.minecraft.nbt.CompoundTag sub = fullNbt.getCompound(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a UUID entry in the RS2 saved data NBT.
|
||||
* Tries multiple levels of nesting since the codec format may vary.
|
||||
*/
|
||||
private static net.minecraft.nbt.CompoundTag findRS2EntryInNbt(net.minecraft.nbt.CompoundTag dataNbt, String uuidStr) {
|
||||
// Direct key at top level
|
||||
if (dataNbt.contains(uuidStr, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
return dataNbt.getCompound(uuidStr);
|
||||
}
|
||||
// Search one level deep in all compound sub-tags
|
||||
for (String key : dataNbt.getAllKeys()) {
|
||||
if (dataNbt.contains(key, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
net.minecraft.nbt.CompoundTag sub = dataNbt.getCompound(key);
|
||||
if (sub.contains(uuidStr, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
return sub.getCompound(uuidStr);
|
||||
}
|
||||
}
|
||||
// Also check ListTag entries (some codecs encode maps as lists of pairs)
|
||||
if (dataNbt.contains(key, net.minecraft.nbt.Tag.TAG_LIST)) {
|
||||
net.minecraft.nbt.ListTag list = dataNbt.getList(key, net.minecraft.nbt.Tag.TAG_COMPOUND);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
net.minecraft.nbt.CompoundTag entry = list.getCompound(i);
|
||||
// Check for {"uuid": "...", "data": {...}} pattern
|
||||
if (entry.getString("uuid").equals(uuidStr) && entry.contains("data", net.minecraft.nbt.Tag.TAG_COMPOUND)) {
|
||||
return entry.getCompound("data");
|
||||
}
|
||||
// Check for {"id": "...", ...} pattern
|
||||
if (entry.getString("id").equals(uuidStr)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a storage entry back into the RS2 StorageRepository.
|
||||
* Uses the repository's codec (via reflection) to properly deserialize the entry,
|
||||
* then calls set() to inject it into the live repository.
|
||||
* Injects an RS2 storage entry back into the saved data NBT.
|
||||
* Mirrors the structure found during save.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void injectRS2StorageEntry(
|
||||
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo,
|
||||
net.minecraft.nbt.CompoundTag wrapperNbt,
|
||||
net.minecraft.server.level.ServerPlayer sp) throws Exception {
|
||||
|
||||
// The wrapper contains { "uuid-string": { ...entry data... } }
|
||||
// We need to decode this using the same codec that StorageRepositoryImpl uses
|
||||
|
||||
// Get the map codec via reflection from StorageRepositoryImpl
|
||||
java.lang.reflect.Method getMapCodecMethod =
|
||||
repo.getClass().getDeclaredMethod("getMapCodec", Runnable.class);
|
||||
getMapCodecMethod.setAccessible(true);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
com.mojang.serialization.Codec codec = (com.mojang.serialization.Codec)
|
||||
getMapCodecMethod.invoke(null, (Runnable) () -> {});
|
||||
|
||||
// Decode the single-entry wrapper using the codec
|
||||
var ops = sp.getServer().registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE);
|
||||
com.mojang.serialization.DataResult<?> dataResult = codec.decode(ops, wrapperNbt);
|
||||
|
||||
Optional<?> resultOpt = dataResult.result();
|
||||
if (resultOpt.isPresent()) {
|
||||
// DataResult contains Pair<Map<UUID, SerializableStorage>, Tag>
|
||||
com.mojang.datafixers.util.Pair<?, ?> pair = (com.mojang.datafixers.util.Pair<?, ?>) resultOpt.get();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<UUID, ?> decoded = (Map<UUID, ?>) pair.getFirst();
|
||||
for (Map.Entry<UUID, ?> entry : decoded.entrySet()) {
|
||||
repo.set(entry.getKey(),
|
||||
(com.refinedmods.refinedstorage.common.api.storage.SerializableStorage) entry.getValue());
|
||||
PlayerSync.LOGGER.info("Restored RS2 disk storage for UUID {}", entry.getKey());
|
||||
}
|
||||
} else {
|
||||
PlayerSync.LOGGER.warn("Failed to decode RS2 storage data from wrapper NBT: {}", wrapperNbt);
|
||||
}
|
||||
private static void injectRS2EntryIntoNbt(net.minecraft.nbt.CompoundTag dataNbt, String uuidStr, net.minecraft.nbt.CompoundTag entryNbt) {
|
||||
// Put at top level (unboundedMap format)
|
||||
dataNbt.put(uuidStr, entryNbt);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user