Add Refined Storage 2 disk sync + Extra Disks support

- Sync RS2 disk storage contents between servers (storageReference UUID)
- Support both refinedstorage and extradisks namespaces
- Save: extract individual entries from StorageRepository SavedData
- Restore: decode via RS2 codec and inject into target server repository
- Skip restore if storage already exists on target server (no overwrite)
- Scan inventory + ender chest for disks

Vyrriox
This commit is contained in:
laforetbrut 2026-03-26 15:07:28 +01:00
parent 87d320c1f4
commit 2e0269ee62
3 changed files with 202 additions and 4 deletions

View File

@ -129,6 +129,9 @@ dependencies {
// Mod compatibility - The Aether + Accessories API
compileOnly "curse.maven:aether-255308:7043502"
compileOnly "curse.maven:accessories-938917:7046407"
// Mod compatibility - Refined Storage 2 + Extra Disks
compileOnly "curse.maven:refined-storage-243076:7610477"
compileOnly "curse.maven:extra-disks-351491:7032487"
runtimeOnly "curse.maven:curios-309927:6529130"
runtimeOnly "curse.maven:sophisticated-backpacks-422301:7169832"

View File

@ -378,6 +378,9 @@ public class VanillaSync {
if (ModList.get().isLoaded("sophisticatedstorage")) {
ModsSupport.restoreSophisticatedStorageItems(serverPlayer);
}
if (ModList.get().isLoaded("refinedstorage")) {
ModsSupport.restoreRefinedStorageDisks(serverPlayer);
}
// Restore mod compatibility data (Accessories/Aether, CosmeticArmor)
ModCompatSync.restoreAll(serverPlayer);
@ -629,6 +632,9 @@ public class VanillaSync {
if (ModList.get().isLoaded("sophisticatedstorage")) {
ModsSupport.storeSophisticatedStorageItems(player);
}
if (ModList.get().isLoaded("refinedstorage")) {
ModsSupport.storeRefinedStorageDisks(player);
}
JDBCsetUp.executePreparedUpdate("UPDATE player_data SET online=0 WHERE uuid=?", player.getUUID().toString());
PlayerSync.LOGGER.info("Saved player {} data on server shutdown", player.getUUID());
} catch (Exception e) {
@ -785,6 +791,9 @@ public class VanillaSync {
if(ModList.get().isLoaded("sophisticatedstorage")){
ModsSupport.storeSophisticatedStorageItems(player);
}
if(ModList.get().isLoaded("refinedstorage")){
ModsSupport.storeRefinedStorageDisks(player);
}
// Effects
Map<Holder<MobEffect>, MobEffectInstance> effects = player.getActiveEffectsMap();

View File

@ -21,10 +21,7 @@ import vip.fubuki.playersync.util.LocalJsonUtil;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
public class ModsSupport {
@ -376,4 +373,193 @@ public class ModsSupport {
}
return null;
}
// ============================
// Refined Storage 2 Disks
// ============================
/**
* Saves RS2 disk storage contents for all disks in the player's inventory.
* RS2 disks reference their storage via a UUID DataComponent (storageReference).
* 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.
*/
public static void storeRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
List<UUID> diskUuids = collectRS2DiskUuids(player);
if (diskUuids.isEmpty()) return;
try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
// 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());
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);
}
}
}
} catch (Exception e) {
PlayerSync.LOGGER.error("Error saving RS2 disk data for player {}", player.getUUID(), e);
}
}
/**
* 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.
*/
public static void restoreRefinedStorageDisks(Player player) {
if (!ModList.get().isLoaded("refinedstorage")) return;
if (!(player instanceof net.minecraft.server.level.ServerPlayer sp)) return;
List<UUID> diskUuids = collectRS2DiskUuids(player);
if (diskUuids.isEmpty()) return;
try {
com.refinedmods.refinedstorage.common.api.storage.StorageRepository repo =
com.refinedmods.refinedstorage.common.api.RefinedStorageApi.INSTANCE.getStorageRepository(sp.serverLevel());
for (UUID uuid : diskUuids) {
// Check if storage already exists on this server (don't overwrite)
if (repo.get(uuid).isPresent()) {
PlayerSync.LOGGER.debug("RS2 storage {} already exists on this server, skipping restore", uuid);
continue;
}
restoreStorageContents(uuid, (nbt) -> {
try {
injectRS2StorageEntry(repo, nbt, sp);
} catch (Exception e) {
PlayerSync.LOGGER.error("Error injecting RS2 storage for UUID {}", uuid, e);
}
});
}
} catch (Exception e) {
PlayerSync.LOGGER.error("Error restoring RS2 disk data for player {}", player.getUUID(), e);
}
}
/**
* Collects all RS2/ExtraDisks storage reference UUIDs from the player's inventory and ender chest.
*/
private static List<UUID> collectRS2DiskUuids(Player player) {
List<UUID> uuids = new ArrayList<>();
// Check main inventory
collectRS2DiskUuidsFromContainer(player.getInventory(), uuids);
// Check ender chest
for (int i = 0; i < player.getEnderChestInventory().getContainerSize(); i++) {
ItemStack stack = player.getEnderChestInventory().getItem(i);
if (stack.isEmpty()) continue;
UUID ref = getRS2StorageReference(stack);
if (ref != null) uuids.add(ref);
}
return uuids;
}
private static void collectRS2DiskUuidsFromContainer(Inventory inv, List<UUID> uuids) {
for (int i = 0; i < inv.getContainerSize(); i++) {
ItemStack stack = inv.getItem(i);
if (stack.isEmpty()) continue;
UUID ref = getRS2StorageReference(stack);
if (ref != null) uuids.add(ref);
}
}
/**
* Extracts the storageReference UUID from an RS2 disk item using the RS2 DataComponent.
* Returns null if the item is not an RS2 disk or doesn't have a storage reference.
*/
private static UUID getRS2StorageReference(ItemStack stack) {
try {
net.minecraft.resources.ResourceLocation loc =
net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(stack.getItem());
if (!loc.getNamespace().equals("refinedstorage") && !loc.getNamespace().equals("extradisks")) {
return null;
}
net.minecraft.core.component.DataComponentType<UUID> storageRefType =
com.refinedmods.refinedstorage.common.content.DataComponents.INSTANCE.getStorageReference();
return stack.get(storageRefType);
} catch (Exception e) {
return null;
}
}
/**
* Extracts an individual storage entry from the full StorageRepository NBT by UUID.
* The save() format uses UUID strings as CompoundTag keys (unboundedMap codec).
*/
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);
}
// 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);
if (sub.contains(uuidStr, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
return sub.getCompound(uuidStr);
}
}
}
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.
*/
@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);
}
}
}