diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index ecda779..6616b67 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -138,6 +138,42 @@ public class PlayerSync { ); } + // ----- NEW BLOCK: Schema Update for backpack_data and player_data ----- + // Check if backpack_data table has the 'uuid' column + JDBCsetUp.QueryResult backpackColCheck = JDBCsetUp.executeQuery( + "SELECT COUNT(*) AS colCount FROM INFORMATION_SCHEMA.COLUMNS " + + "WHERE TABLE_SCHEMA = '" + dbName + "' " + + "AND TABLE_NAME = 'backpack_data' " + + "AND COLUMN_NAME = 'uuid';" + ); + ResultSet rsBackpackCol = backpackColCheck.resultSet(); + if (rsBackpackCol.next() && rsBackpackCol.getInt("colCount") == 0) { + LOGGER.info("Altering backpack_data table to add missing 'uuid' column."); + // Add the missing column and set it as primary key. + JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".backpack_data ADD COLUMN uuid CHAR(36) NOT NULL", 1); + JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".backpack_data ADD PRIMARY KEY (uuid)", 1); + } + rsBackpackCol.close(); + + // Check and alter the 'advancements' column in player_data if necessary + JDBCsetUp.QueryResult advColCheck = JDBCsetUp.executeQuery( + "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS " + + "WHERE TABLE_SCHEMA = '" + dbName + "' " + + "AND TABLE_NAME = 'player_data' " + + "AND COLUMN_NAME = 'advancements';" + ); + ResultSet rsAdvCol = advColCheck.resultSet(); + if (rsAdvCol.next()) { + String dataType = rsAdvCol.getString("DATA_TYPE"); + if (!"mediumblob".equalsIgnoreCase(dataType)) { + LOGGER.info("Altering player_data table to modify 'advancements' column to MEDIUMBLOB."); + JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".player_data MODIFY COLUMN advancements MEDIUMBLOB", 1); + } + } + rsAdvCol.close(); + // ----- END NEW BLOCK ----- + LOGGER.info("PlayerSync is ready!"); } + } diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index 3ab32f2..273a70c 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -3,7 +3,6 @@ package vip.fubuki.playersync.sync; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fml.ModList; @@ -15,6 +14,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; + +import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; +import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; +import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; import java.util.Optional; import java.util.UUID; @@ -23,43 +27,74 @@ import static vip.fubuki.playersync.sync.VanillaSync.deserializeString; public class ModsSupport { - public void onPlayerJoin(Player player) throws SQLException { + /** + * Restores the Curios inventory for a player. + * The saved data is stored as a flat map with composite keys ("slotType:index"). + */ + public void onPlayerJoin(net.minecraft.world.entity.player.Player player) throws SQLException { if (ModList.get().isLoaded("curios")) { - /* - Curios Support - */ - LazyOptional itemHandler = top.theillusivec4.curios.api.CuriosApi.getCuriosInventory(player); - JDBCsetUp.QueryResult queryResult = JDBCsetUp.executeQuery("SELECT curios_item FROM curios WHERE uuid = '"+player.getUUID()+"'"); - ResultSet resultSet = queryResult.resultSet(); - if(resultSet.next()) { - String curios_data=resultSet.getString("curios_item"); - if(curios_data.length()<=2) + // Obtain the handler from the API. + LazyOptional handlerOpt = CuriosApi.getCuriosInventory(player); + JDBCsetUp.QueryResult qr = JDBCsetUp.executeQuery("SELECT curios_item FROM curios WHERE uuid = '" + player.getUUID() + "'"); + ResultSet rs = qr.resultSet(); + if (rs.next()) { + String curiosData = rs.getString("curios_item"); + if (curiosData.length() <= 2) { + rs.close(); + qr.connection().close(); return; - itemHandler.ifPresent(handler ->{ - for (int i = 0; i < handler.getSlots(); i++) { - handler.getEquippedCurios().setStackInSlot(i,ItemStack.EMPTY); - } + } + // Parse the stored data (assumes a simple Map.toString() format: "{key=value, key2=value2, ...}") + Map storedMap = LocalJsonUtil.StringToMap(curiosData); + + // Clear current Curios slots to avoid conflicts. + handlerOpt.ifPresent(handler -> { + handler.getCurios().forEach((slotType, stacksHandler) -> { + // Use the dynamic stack handler to clear slots. + IDynamicStackHandler dynStacks = stacksHandler.getStacks(); + for (int i = 0; i < dynStacks.getSlots(); i++) { + dynStacks.setStackInSlot(i, ItemStack.EMPTY); + } + }); }); - Map curios = LocalJsonUtil.StringToEntryMap(curios_data); - itemHandler.ifPresent(handler -> { - handler.reset(); - for (int i = 0; i < handler.getSlots(); i++) { + // Restore each saved item. + handlerOpt.ifPresent(handler -> { + for (Map.Entry entry : storedMap.entrySet()) { + String compositeKey = entry.getKey(); // Expected format: "slotType:index" + String[] parts = compositeKey.split(":"); + if (parts.length != 2) { + continue; + } + String slotType = parts[0]; + int slotIndex; try { - if (curios.get(i) != null){ - handler.getEquippedCurios().setStackInSlot(i, ItemStack.of(NbtUtils.snbtToStructure(curios.get(i).replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'")))); + slotIndex = Integer.parseInt(parts[1]); + } catch (NumberFormatException ex) { + continue; + } + String serialized = entry.getValue(); + try { + String nbtString = VanillaSync.deserializeString(serialized); + CompoundTag tag = NbtUtils.snbtToStructure(nbtString); + ItemStack stack = ItemStack.of(tag); + if (handler.getCurios().containsKey(slotType)) { + ICurioStacksHandler stacksHandler = handler.getCurios().get(slotType); + IDynamicStackHandler dynStacks = stacksHandler.getStacks(); + if (slotIndex < dynStacks.getSlots()) { + dynStacks.setStackInSlot(slotIndex, stack); + } } - } catch (CommandSyntaxException e) { - throw new RuntimeException(e); + throw new RuntimeException("Error deserializing Curio data for key " + compositeKey, e); } } }); - - resultSet.close(); - queryResult.connection().close(); - }else{ - StoreCurios(player,true); + rs.close(); + qr.connection().close(); + } else { + // No stored data; perform an initial save. + StoreCurios(player, true); } } if(ModList.get().isLoaded("sophisticatedbackpacks")){ @@ -100,27 +135,39 @@ public class ModsSupport { } } - public void onPlayerLeave(Player player) throws SQLException { + /** + * Saves the current Curios inventory for a player. + * It builds a flat map keyed by "slotType:index" using the dynamic stack handler. + */ + public void onPlayerLeave(net.minecraft.world.entity.player.Player player) throws SQLException { if (ModList.get().isLoaded("curios")) { StoreCurios(player, false); } } - public void StoreCurios(Player player,boolean init) throws SQLException { - LazyOptional itemHandler = top.theillusivec4.curios.api.CuriosApi.getCuriosInventory(player); - Map curios = new HashMap<>(); - itemHandler.ifPresent(handler -> { - for (int i = 0; i < handler.getSlots(); i++) { - if (!handler.getEquippedCurios().getStackInSlot(i).isEmpty()) { - String sNBT= VanillaSync.serialize(handler.getEquippedCurios().getStackInSlot(i).serializeNBT().toString()); - curios.put(i, sNBT); + public void StoreCurios(net.minecraft.world.entity.player.Player player, boolean init) throws SQLException { + LazyOptional handlerOpt = CuriosApi.getCuriosInventory(player); + Map flatMap = new HashMap<>(); + + handlerOpt.ifPresent(handler -> { + // Iterate over each slot type. + handler.getCurios().forEach((slotType, stacksHandler) -> { + IDynamicStackHandler dynStacks = stacksHandler.getStacks(); + for (int i = 0; i < dynStacks.getSlots(); i++) { + ItemStack stack = dynStacks.getStackInSlot(i); + if (!stack.isEmpty()) { + String serialized = VanillaSync.serialize(stack.serializeNBT().toString()); + flatMap.put(slotType + ":" + i, serialized); + } } - } + }); }); - if(init) { - JDBCsetUp.executeUpdate("INSERT INTO curios (uuid,curios_item) VALUES ('"+player.getUUID()+"','"+ curios+"')"); + + String serializedData = flatMap.toString(); + if (init) { + JDBCsetUp.executeUpdate("INSERT INTO curios (uuid,curios_item) VALUES ('" + player.getUUID() + "', '" + serializedData + "')"); } else { - JDBCsetUp.executeUpdate("UPDATE curios SET curios_item = '"+ curios+"' WHERE uuid = '"+player.getUUID()+"'"); + JDBCsetUp.executeUpdate("UPDATE curios SET curios_item = '" + serializedData + "' WHERE uuid = '" + player.getUUID() + "'"); } } } diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 1774bac..41ceb77 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -18,6 +18,7 @@ import java.util.concurrent.Executors; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.effect.MobEffect; @@ -31,6 +32,7 @@ import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.server.ServerStoppedEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.server.ServerLifecycleHooks; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.sync.ModsSupport; @@ -369,4 +371,44 @@ public class VanillaSync { JDBCsetUp.executeUpdate("UPDATE server_info SET last_update =" + current + " WHERE id= " + JdbcConfig.SERVER_ID.get()); } } + + + // New fields for auto-save + private static int autoSaveTickCounter = 0; + private static final int AUTO_SAVE_INTERVAL_TICKS = 1200; // Every Minute + + //AutoSave + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + // Run at the end phase to avoid interfering with game logic + if (event.phase == TickEvent.Phase.END) { + autoSaveTickCounter++; + if (autoSaveTickCounter >= AUTO_SAVE_INTERVAL_TICKS) { + autoSaveTickCounter = 0; + // Retrieve the current server instance + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server != null) { + // Iterate through all online players + for (ServerPlayer player : server.getPlayerList().getPlayers()) { + executorService.submit(() -> { + try { + // Call the same store method used in logout and file save events. + store(player, false, server.isDedicatedServer()); + } catch (Exception e) { + PlayerSync.LOGGER.error("Error auto-saving player " + player.getUUID(), e); + } + }); + executorService.submit(() -> { + try { + new ModsSupport().StoreCurios(player, false); + } catch (SQLException e) { + PlayerSync.LOGGER.error("Error auto-saving Curios data for player " + player.getUUID(), e); + } + }); + + } + } + } + } + } }