Merge pull request #30 from mchivelli/1.20.1

Added Sophisticated Backpack Mod compatibility
This commit is contained in:
mlus 2025-03-20 22:27:57 +08:00 committed by GitHub
commit d346bd36ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 380 additions and 217 deletions

View File

@ -128,6 +128,8 @@ dependencies {
implementation fg.deobf("curse.maven:curios-309927:5266541")
implementation fg.deobf("curse.maven:MySQL-561280:3685108")
implementation fg.deobf("curse.maven:sophisticated-backpacks-422301:6303388")
implementation fg.deobf("curse.maven:sophisticated-core-618298:6317048")
}
// Example for how to get properties into the manifest for reading at runtime.

View File

@ -17,16 +17,17 @@ import vip.fubuki.playersync.sync.ChatSync;
import vip.fubuki.playersync.sync.VanillaSync;
import vip.fubuki.playersync.util.JDBCsetUp;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@Mod(PlayerSync.MODID)
public class PlayerSync
{
public class PlayerSync {
public static final String MODID = "playersync";
public static final Logger LOGGER = LogUtils.getLogger();
public PlayerSync()
{
public PlayerSync() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, JdbcConfig.COMMON_CONFIG);
modEventBus.addListener(this::commonSetup);
@ -35,71 +36,106 @@ public class PlayerSync
private void commonSetup(final FMLCommonSetupEvent event) {
VanillaSync.register();
if(JdbcConfig.SYNC_CHAT.get()){
if (JdbcConfig.SYNC_CHAT.get()) {
ChatSync.register();
}
}
@SubscribeEvent
public void onServerStarting(ServerStartingEvent event) throws SQLException {
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS "+JdbcConfig.DATABASE_NAME.get(),1);
String dbName = JdbcConfig.DATABASE_NAME.get();
JDBCsetUp.executeUpdate("""
CREATE TABLE IF NOT EXISTS `player_data` (
`uuid` char(36) NOT NULL,
`inventory` mediumblob,
`armor` blob,
`advancements` blob,
`enderchest` mediumblob,
`effects` blob,
`left_hand` blob,
`cursors` blob,
`xp` int DEFAULT NULL,
`food_level` int DEFAULT NULL,
`score` int DEFAULT NULL,
`health` int DEFAULT NULL,
`online` tinyint(1) DEFAULT NULL,
`last_server` int DEFAULT NULL,
PRIMARY KEY (`uuid`)
);""");
// Step 1: Create the database using a connection that does not select a database.
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName, 1);
JDBCsetUp.QueryResult queryResult = JDBCsetUp.executeQuery("""
SELECT COUNT(*) AS column_count
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'player_data';
""");
// Step 2: Explicitly select the database on a connection obtained without default database.
try (Connection conn = JDBCsetUp.getConnection(false);
Statement st = conn.createStatement()) {
st.execute("USE " + dbName);
} catch (SQLException e) {
LOGGER.error("Error selecting database " + dbName, e);
throw e;
}
// Step 3: Create and alter tables using fully qualified names.
// Create player_data table
JDBCsetUp.executeUpdate(
"CREATE TABLE IF NOT EXISTS " + dbName + ".`player_data` (" +
"`uuid` char(36) NOT NULL," +
"`inventory` mediumblob," +
"`armor` blob," +
"`advancements` blob," +
"`enderchest` mediumblob," +
"`effects` blob," +
"`left_hand` blob," +
"`cursors` blob," +
"`xp` int DEFAULT NULL," +
"`food_level` int DEFAULT NULL," +
"`score` int DEFAULT NULL," +
"`health` int DEFAULT NULL," +
"`online` tinyint(1) DEFAULT NULL," +
"`last_server` int DEFAULT NULL," +
"PRIMARY KEY (`uuid`)" +
");"
);
// Check and alter player_data table if columns are missing
JDBCsetUp.QueryResult queryResult = JDBCsetUp.executeQuery(
"SELECT COUNT(*) AS column_count " +
"FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE TABLE_SCHEMA = '" + dbName + "' " +
"AND TABLE_NAME = 'player_data';"
);
ResultSet resultSet = queryResult.resultSet();
int columnCount = 0;
if(resultSet.next()) {
if (resultSet.next()) {
columnCount = resultSet.getInt("column_count");
}
if(columnCount<14){
JDBCsetUp.executeUpdate("""
ALTER TABLE player_data
ADD COLUMN left_hand blob,
ADD COLUMN cursors blob;
""");
if (columnCount < 14) {
JDBCsetUp.executeUpdate(
"ALTER TABLE " + dbName + ".player_data " +
"ADD COLUMN left_hand blob, " +
"ADD COLUMN cursors blob;"
);
}
JDBCsetUp.executeUpdate("""
CREATE TABLE IF NOT EXISTS server_info (
`id` INT NOT NULL,
`enable` boolean NOT NULL,
`last_update` BIGINT NOT NULL,
PRIMARY KEY (`id`));""");
// Create server_info table
JDBCsetUp.executeUpdate(
"CREATE TABLE IF NOT EXISTS " + dbName + ".server_info (" +
"`id` INT NOT NULL," +
"`enable` boolean NOT NULL," +
"`last_update` BIGINT NOT NULL," +
"PRIMARY KEY (`id`)" +
");"
);
long current = System.currentTimeMillis();
JDBCsetUp.executeUpdate("INSERT INTO server_info(id,enable,last_update) " +
"VALUES(" + JdbcConfig.SERVER_ID.get() + ",true," + current + ") " +
"ON DUPLICATE KEY UPDATE id= " + JdbcConfig.SERVER_ID.get() +",enable = 1," +
"last_update=" + current + ";");
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= 1 WHERE id= "+ JdbcConfig.SERVER_ID.get());
JDBCsetUp.executeUpdate(
"INSERT INTO " + dbName + ".server_info(id,enable,last_update) " +
"VALUES(" + JdbcConfig.SERVER_ID.get() + ",true," + current + ") " +
"ON DUPLICATE KEY UPDATE id= " + JdbcConfig.SERVER_ID.get() + ",enable = 1," +
"last_update=" + current + ";"
);
JDBCsetUp.executeUpdate(
"UPDATE " + dbName + ".server_info SET last_update=" + System.currentTimeMillis() +
" WHERE id='" + JdbcConfig.SERVER_ID.get() + "'"
);
if(ModList.get().isLoaded("curios")) {
JDBCsetUp.executeUpdate("CREATE TABLE IF NOT EXISTS curios (uuid CHAR(36) NOT NULL,curios_item BLOB, PRIMARY KEY (uuid))");
// Create curios table if the Curios mod is loaded
if (ModList.get().isLoaded("curios")) {
JDBCsetUp.executeUpdate(
"CREATE TABLE IF NOT EXISTS " + dbName + ".curios (" +
"uuid CHAR(36) NOT NULL, curios_item BLOB, PRIMARY KEY (uuid)" +
")"
);
}
// Create backpack_data table
JDBCsetUp.executeUpdate(
"CREATE TABLE IF NOT EXISTS " + dbName + ".backpack_data (" +
"uuid CHAR(36) NOT NULL, backpack_nbt MEDIUMBLOB, PRIMARY KEY (uuid)" +
");", 1
);
LOGGER.info("PlayerSync is ready!");
}
}

View File

@ -103,7 +103,7 @@ public class ChatSync {
}
private static void reconnectClient() {
//TODO
//TODO
}
@SubscribeEvent

View File

@ -48,10 +48,10 @@ public class ModsSupport {
throw new RuntimeException(e);
}
}
});
});
resultSet.close();
queryResult.connection().close();
resultSet.close();
queryResult.connection().close();
}else{
StoreCurios(player,true);
}
@ -60,7 +60,7 @@ public class ModsSupport {
public void onPlayerLeave(Player player) throws SQLException {
if (ModList.get().isLoaded("curios")) {
StoreCurios(player, false);
StoreCurios(player, false);
}
}

View File

@ -1,6 +1,20 @@
package vip.fubuki.playersync.sync;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.chat.Component;
@ -17,164 +31,227 @@ 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 vip.fubuki.playersync.PlayerSync;
import vip.fubuki.playersync.config.JdbcConfig;
import vip.fubuki.playersync.sync.ModsSupport;
import vip.fubuki.playersync.util.JDBCsetUp;
import vip.fubuki.playersync.util.LocalJsonUtil;
import vip.fubuki.playersync.util.PSThreadPoolFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Mod.EventBusSubscriber
public class VanillaSync {
public static void register(){}
public static void register() {}
static ExecutorService executorService = Executors.newCachedThreadPool(new PSThreadPoolFactory("PlayerSync"));
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, CommandSyntaxException, IOException {
String player_uuid = event.getEntity().getUUID().toString();
JDBCsetUp.QueryResult queryResult=JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='"+player_uuid+"'");
ResultSet resultSet=queryResult.resultSet();
PlayerSync.LOGGER.info("Starting synchronization for player " + player_uuid);
// First query: check basic player data
JDBCsetUp.QueryResult qr1 = JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='" + player_uuid + "'");
ResultSet rs1 = qr1.resultSet();
ServerPlayer serverPlayer = (ServerPlayer) event.getEntity();
if(!resultSet.next()){
store(event.getEntity(),true,Dist.CLIENT.isDedicatedServer());
if (!rs1.next()){
store(event.getEntity(), true, Dist.CLIENT.isDedicatedServer());
return;
}
boolean online = resultSet.getBoolean("online");
int lastServer = resultSet.getInt("last_server");
queryResult=JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='"+player_uuid+"'");
resultSet= queryResult.resultSet();
if(online && lastServer != JdbcConfig.SERVER_ID.get()) {
boolean online = rs1.getBoolean("online");
int lastServer = rs1.getInt("last_server");
queryResult=JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='"+lastServer+"'");
ResultSet getServerInfo = queryResult.resultSet();
if(getServerInfo.next()){
long last_update = getServerInfo.getLong("last_update");
boolean enable = getServerInfo.getBoolean("enable");
if(enable && System.currentTimeMillis() < last_update + 300000.0){
// Second query: retrieve full player data
JDBCsetUp.QueryResult qr2 = JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='" + player_uuid + "'");
ResultSet rs2 = qr2.resultSet();
// Check if player is already online on another server
if (online && lastServer != JdbcConfig.SERVER_ID.get()) {
JDBCsetUp.QueryResult qr3 = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='" + lastServer + "'");
ResultSet rs3 = qr3.resultSet();
if (rs3.next()){
long last_update = rs3.getLong("last_update");
boolean enable = rs3.getBoolean("enable");
if (enable && System.currentTimeMillis() < last_update + 300000.0){
event.getEntity().removeTag("player_synced");
serverPlayer.connection.disconnect(Component.translatable("playersync.already_online"));
return;
}
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' WHERE id=" + lastServer);
}
getServerInfo.close();
rs3.close();
}
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='"+player_uuid+"'");
if(resultSet.next()) {
//Easy Part
serverPlayer.setHealth(resultSet.getInt("health"));
serverPlayer.getFoodData().setFoodLevel(resultSet.getInt("food_level"));
serverPlayer.totalExperience=0;
serverPlayer.experienceLevel=0;
serverPlayer.experienceProgress=0;
serverPlayer.giveExperiencePoints(resultSet.getInt("xp"));
serverPlayer.setScore(resultSet.getInt("score"));
//Left Hand
serverPlayer.setItemInHand(InteractionHand.OFF_HAND,ItemStack.of(NbtUtils.snbtToStructure(resultSet.getString("left_hand").replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'"))));
//Cursor
serverPlayer.containerMenu.setCarried(ItemStack.of(NbtUtils.snbtToStructure(resultSet.getString("cursors").replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'"))));
//Equipment
String armor_data=resultSet.getString("armor");
if(armor_data.length()>2) {
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='" + player_uuid + "'");
if (rs2.next()) {
// Restore basic attributes
serverPlayer.setHealth(rs2.getInt("health"));
serverPlayer.getFoodData().setFoodLevel(rs2.getInt("food_level"));
serverPlayer.totalExperience = 0;
serverPlayer.experienceLevel = 0;
serverPlayer.experienceProgress = 0;
serverPlayer.giveExperiencePoints(rs2.getInt("xp"));
serverPlayer.setScore(rs2.getInt("score"));
// Restore left-hand item
String leftHandEncoded = rs2.getString("left_hand");
String leftHandNBT = deserializeString(leftHandEncoded);
serverPlayer.setItemInHand(InteractionHand.OFF_HAND,
ItemStack.of(NbtUtils.snbtToStructure(leftHandNBT)));
// Restore cursor item
String cursorsEncoded = rs2.getString("cursors");
String cursorsNBT = deserializeString(cursorsEncoded);
serverPlayer.containerMenu.setCarried(
ItemStack.of(NbtUtils.snbtToStructure(cursorsNBT))
);
// Restore armor
String armor_data = rs2.getString("armor");
if (armor_data.length() > 2) {
Map<Integer, String> equipment = LocalJsonUtil.StringToEntryMap(armor_data);
for (Map.Entry<Integer, String> entry : equipment.entrySet()) {
serverPlayer.getInventory().armor.set(entry.getKey(), deserialize(entry));
}
}
//Inventory
Map<Integer,String> inventory = LocalJsonUtil.StringToEntryMap(resultSet.getString("inventory"));
// Restore inventory
Map<Integer, String> inventory = LocalJsonUtil.StringToEntryMap(rs2.getString("inventory"));
for (Map.Entry<Integer, String> entry : inventory.entrySet()) {
serverPlayer.getInventory().setItem(entry.getKey(), deserialize(entry));
}
//Ender chest
Map<Integer,String> ender_chest = LocalJsonUtil.StringToEntryMap(resultSet.getString("enderchest"));
// Restore Ender Chest
Map<Integer, String> ender_chest = LocalJsonUtil.StringToEntryMap(rs2.getString("enderchest"));
for (Map.Entry<Integer, String> entry : ender_chest.entrySet()) {
serverPlayer.getEnderChestInventory().setItem(entry.getKey(), deserialize(entry));
}
//Effects
String effectData=resultSet.getString("effects");
if(effectData.length()>2) {
// Restore Effects
String effectData = rs2.getString("effects");
if (effectData.length() > 2) {
serverPlayer.removeAllEffects();
Map<Integer, String> effects = LocalJsonUtil.StringToEntryMap(effectData);
for (Map.Entry<Integer, String> entry : effects.entrySet()) {
CompoundTag effectTag = NbtUtils.snbtToStructure(entry.getValue().replace("|", ","));
CompoundTag effectTag = NbtUtils.snbtToStructure(deserializeString(entry.getValue()));
MobEffectInstance mobEffectInstance = MobEffectInstance.load(effectTag);
assert mobEffectInstance != null;
serverPlayer.addEffect(mobEffectInstance);
if (mobEffectInstance != null) {
serverPlayer.addEffect(mobEffectInstance);
}
}
}
//Advancements
// Restore Advancements
File gameDir = Objects.requireNonNull(serverPlayer.getServer()).getServerDirectory();
if(Dist.CLIENT.isDedicatedServer()){
File advancements = new File(gameDir, JdbcConfig.SYNC_WORLD.get().get(0)+"/advancements"+"/"+player_uuid+".json");
if (Dist.CLIENT.isDedicatedServer()){
File advancements = new File(gameDir, JdbcConfig.SYNC_WORLD.get().get(0) + "/advancements" + "/" + player_uuid + ".json");
if (!advancements.exists()) {
advancements.createNewFile();
}
byte [] bytes=resultSet.getString("advancements").getBytes();
Files.write(advancements.toPath(),bytes);
}else{
File[] files= scanAdvancementsFile(player_uuid, gameDir);
byte[] bytes = rs2.getString("advancements").getBytes();
Files.write(advancements.toPath(), bytes);
} else {
File[] files = scanAdvancementsFile(player_uuid, gameDir);
for (File file : files) {
if(file==null) continue;
byte [] bytes=resultSet.getString("advancements").getBytes();
Files.write(file.toPath(),bytes);
if (file == null) continue;
byte[] bytes = rs2.getString("advancements").getBytes();
Files.write(file.toPath(), bytes);
}
}
}
//Mod support
// Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerJoin(serverPlayer);
serverPlayer.addTag("player_synced");
resultSet.close();
// --- Begin Backpack Data Restore ---
PlayerSync.LOGGER.info("Restoring backpack data for player " + player_uuid);
net.p3pp3rf1y.sophisticatedbackpacks.util.PlayerInventoryProvider.get().runOnBackpacks(serverPlayer, (ItemStack backpackItem, String handler, String identifier, int slot) -> {
backpackItem.getCapability(net.p3pp3rf1y.sophisticatedbackpacks.api.CapabilityBackpackWrapper.getCapabilityInstance())
.ifPresent(wrapper -> {
// Retrieve the contents UUID from the backpack's NBT using NBTHelper
Optional<UUID> uuidOpt = net.p3pp3rf1y.sophisticatedcore.util.NBTHelper.getUniqueId(wrapper.getBackpack(), "contentsUuid");
if (uuidOpt.isPresent()) {
UUID contentsUuid = uuidOpt.get();
try {
JDBCsetUp.QueryResult qrBackpack = JDBCsetUp.executeQuery("SELECT backpack_nbt FROM backpack_data WHERE uuid='" + contentsUuid.toString() + "'");
ResultSet rsBackpack = qrBackpack.resultSet();
if (rsBackpack.next()) {
String serialized = rsBackpack.getString("backpack_nbt");
String nbtString = deserializeString(serialized);
CompoundTag backpackNbt = NbtUtils.snbtToStructure(nbtString);
// Update BackpackStorage with the retrieved NBT
net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setBackpackContents(contentsUuid, backpackNbt);
PlayerSync.LOGGER.info("Restored backpack data for UUID " + contentsUuid);
}
rsBackpack.close();
qrBackpack.connection().close();
} catch (SQLException e) {
PlayerSync.LOGGER.error("Error restoring backpack data for UUID " + contentsUuid, e);
} catch (CommandSyntaxException e) {
throw new RuntimeException(e);
}
} else {
PlayerSync.LOGGER.warn("Backpack item in slot " + slot + " has no contentsUuid during restore");
}
});
return false;
});
// --- End Backpack Data Restore ---
rs2.close();
}
@SubscribeEvent
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){
executorService.submit(()->{
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
executorService.submit(() -> {
try {
doPlayerJoin(event);
} catch (Exception e) {
e.printStackTrace();
}
});
}
public static ItemStack deserialize(Map.Entry<Integer, String> entry) throws CommandSyntaxException {
String nbt= entry.getValue().replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'");
String nbt = deserializeString(entry.getValue());
CompoundTag compoundTag = NbtUtils.snbtToStructure(nbt);
return ItemStack.of(compoundTag);
}
public static String serialize(String object){
return object.replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~");
public static String deserializeString(String encoded) {
if (encoded.startsWith("B64:")) {
String base64 = encoded.substring(4);
try {
return new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8);
} catch (IllegalArgumentException ex) {
PlayerSync.LOGGER.error("Base64 decoding failed for data: " + encoded, ex);
// fallback to legacy decoding below
}
}
// Legacy fallback using custom replacement
return encoded.replace("|", ",")
.replace("^", "\"")
.replace("<", "{")
.replace(">", "}")
.replace("~", "'");
}
public static String serialize(String object) {
// Base64 encode with a "B64:" marker for new data
return "B64:" + Base64.getEncoder().encodeToString(object.getBytes(StandardCharsets.UTF_8));
}
public static void doPlayerSaveToFile(PlayerEvent.SaveToFile event) throws SQLException, IOException {
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
if(!event.getEntity().getTags().contains("player_synced")) return;
store(event.getEntity(),false,Dist.CLIENT.isDedicatedServer());
if (!event.getEntity().getTags().contains("player_synced")) return;
store(event.getEntity(), false, Dist.CLIENT.isDedicatedServer());
}
@SubscribeEvent
public static void onPlayerSaveToFile(PlayerEvent.SaveToFile event) {
executorService.submit(()->{
executorService.submit(() -> {
try {
doPlayerSaveToFile(event);
} catch (Exception e) {
@ -190,77 +267,102 @@ public class VanillaSync {
public static void doPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException, IOException {
String player_uuid = event.getEntity().getUUID().toString();
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '0' WHERE uuid='"+player_uuid+"'");
store(event.getEntity(),false,Dist.CLIENT.isDedicatedServer());
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '0' WHERE uuid='" + player_uuid + "'");
store(event.getEntity(), false, Dist.CLIENT.isDedicatedServer());
}
@SubscribeEvent
public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException {
//Mod support
// Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerLeave(event.getEntity());
executorService.submit(()->{
executorService.submit(() -> {
try {
doPlayerLogout(event);
} catch (Exception e) {
e.printStackTrace();
}
});
}
public static void store(Player player, boolean init, boolean isServer) throws SQLException, IOException {
String player_uuid = player.getUUID().toString();
//Easy part
PlayerSync.LOGGER.info("Storing data for player " + player_uuid + " (init=" + init + ")");
// Basic Attributes
int XP = player.totalExperience;
int score=player.getScore();
int food_level=player.getFoodData().getFoodLevel();
int health=(int) player.getHealth();
//Left hand
int score = player.getScore();
int food_level = player.getFoodData().getFoodLevel();
int health = (int) player.getHealth();
// Left Hand
String left_hand = serialize(player.getItemInHand(InteractionHand.OFF_HAND).serializeNBT().toString());
//Cursor
// Cursor
String cursors = serialize(player.containerMenu.getCarried().serializeNBT().toString());
//Equipment
Map<Integer,String> equipment =new HashMap<>() ;
// Equipment (Armor)
Map<Integer, String> equipment = new HashMap<>();
for (int i = 0; i < player.getInventory().armor.size(); i++) {
ItemStack itemStack = player.getInventory().armor.get(i);
if(itemStack.isEmpty()) continue;
equipment.put(i,serialize(itemStack.serializeNBT().toString()));
if (itemStack.isEmpty()) continue;
equipment.put(i, serialize(itemStack.serializeNBT().toString()));
}
//inventory
// Inventory
Inventory inventory = player.getInventory();
Map<Integer,String> inventoryMap = new HashMap<>();
Map<Integer, String> inventoryMap = new HashMap<>();
for (int i = 0; i < inventory.items.size(); i++) {
CompoundTag itemNBT = inventory.items.get(i).serializeNBT();
inventoryMap.put(i,serialize(itemNBT.toString()));
inventoryMap.put(i, serialize(itemNBT.toString()));
}
//EnderChest
// Ender Chest
Map<Integer, String> ender_chest = new HashMap<>();
for (int i=0;i< player.getEnderChestInventory().getContainerSize();i++) {
for (int i = 0; i < player.getEnderChestInventory().getContainerSize(); i++) {
CompoundTag itemNBT = player.getEnderChestInventory().getItem(i).serializeNBT();
ender_chest.put(i,serialize(itemNBT.toString()));
ender_chest.put(i, serialize(itemNBT.toString()));
}
//Effects
Map<MobEffect,MobEffectInstance> effects= player.getActiveEffectsMap();
Map<Integer,String> effectMap=new HashMap<>();
// --- Begin Backpack Data Sync (Store) ---
PlayerSync.LOGGER.info("Storing backpack data for player " + player.getUUID());
net.p3pp3rf1y.sophisticatedbackpacks.util.PlayerInventoryProvider.get().runOnBackpacks(player, (ItemStack backpackItem, String handler, String identifier, int slot) -> {
backpackItem.getCapability(net.p3pp3rf1y.sophisticatedbackpacks.api.CapabilityBackpackWrapper.getCapabilityInstance())
.ifPresent(wrapper -> {
// Retrieve the contents UUID from the backpack's NBT using NBTHelper
Optional<UUID> uuidOpt = net.p3pp3rf1y.sophisticatedcore.util.NBTHelper.getUniqueId(wrapper.getBackpack(), "contentsUuid");
if (uuidOpt.isPresent()) {
UUID contentsUuid = uuidOpt.get();
// Get internal backpack data from BackpackStorage (creates it if missing)
CompoundTag backpackNbt = net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().getOrCreateBackpackContents(contentsUuid);
String serialized = VanillaSync.serialize(backpackNbt.toString());
try {
// Use REPLACE INTO so existing records are updated
JDBCsetUp.executeUpdate("REPLACE INTO backpack_data (uuid, backpack_nbt) VALUES ('" + contentsUuid.toString() + "', '" + serialized + "')");
PlayerSync.LOGGER.info("Saved backpack data for UUID " + contentsUuid);
} catch (SQLException e) {
PlayerSync.LOGGER.error("Error saving backpack data for UUID " + contentsUuid, e);
}
} else {
PlayerSync.LOGGER.warn("Backpack item in slot " + slot + " has no contentsUuid");
}
});
return false; // Continue processing all backpack items.
});
// --- End Backpack Data Sync (Store) ---
// Effects
Map<MobEffect, MobEffectInstance> effects = player.getActiveEffectsMap();
Map<Integer, String> effectMap = new HashMap<>();
for (Map.Entry<MobEffect, MobEffectInstance> entry : effects.entrySet()) {
CompoundTag effectTag= entry.getValue().save(new CompoundTag());
effectMap.put(MobEffect.getId(entry.getKey()),effectTag.toString().replace(",","|"));
CompoundTag effectTag = entry.getValue().save(new CompoundTag());
effectMap.put(MobEffect.getId(entry.getKey()), serialize(effectTag.toString()));
}
//Advancements
//File root = serverPlayer.getServer().getServerDirectory();
// Advancements
File advancements = null;
File gameDir = Objects.requireNonNull(player.getServer()).getServerDirectory();
if(isServer){
advancements = new File(gameDir, JdbcConfig.SYNC_WORLD.get().get(0)+"/advancements"+"/"+player_uuid+".json");
}else{
// File gameDir = Minecraft.getInstance().gameDirectory;
File[] files= scanAdvancementsFile(player_uuid, gameDir);
//Get LastModified
if (isServer) {
advancements = new File(gameDir, JdbcConfig.SYNC_WORLD.get().get(0) + "/advancements" + "/" + player_uuid + ".json");
} else {
File[] files = scanAdvancementsFile(player_uuid, gameDir);
long latestModifiedDate = 0;
for (File file : files) {
if(file==null) continue;
if (file == null) continue;
if (file.lastModified() > latestModifiedDate) {
latestModifiedDate = file.lastModified();
advancements = file;
@ -273,17 +375,19 @@ public class VanillaSync {
}
String json = new String(bytes, StandardCharsets.UTF_8);
//SQL Operation
if(init){
JDBCsetUp.executeUpdate("INSERT INTO player_data (uuid,armor,inventory,enderchest,advancements,effects,xp,food_level,health,score,left_hand,cursors,online) VALUES ('"+player_uuid+"','"+equipment+"','"+inventoryMap+"','"+ender_chest+"','"+advancements+"','"+effectMap+"','"+XP+"','"+food_level+"','"+health+"','"+score+"','"+left_hand+"','"+cursors+"',online=true)");
}else JDBCsetUp.executeUpdate("UPDATE player_data SET inventory = '"+inventoryMap+"',armor='"+equipment+"' ,xp='"+XP+"',effects='"+effectMap+"',enderchest='"+ender_chest+"',score='"+score+"',food_level='"+food_level+"',health='"+health+"',advancements='"+json+"',left_hand='"+left_hand+"',cursors='"+cursors+"' WHERE uuid = '"+player_uuid+"'");
// SQL Operation for player data
if (init) {
JDBCsetUp.executeUpdate("INSERT INTO player_data (uuid,armor,inventory,enderchest,advancements,effects,xp,food_level,health,score,left_hand,cursors,online) VALUES ('" + player_uuid + "','" + equipment + "','" + inventoryMap + "','" + ender_chest + "','" + advancements + "','" + effectMap + "','" + XP + "','" + food_level + "','" + health + "','" + score + "','" + left_hand + "','" + cursors + "',online=true)");
} else {
JDBCsetUp.executeUpdate("UPDATE player_data SET inventory = '" + inventoryMap + "',armor='" + equipment + "' ,xp='" + XP + "',effects='" + effectMap + "',enderchest='" + ender_chest + "',score='" + score + "',food_level='" + food_level + "',health='" + health + "',advancements='" + json + "',left_hand='" + left_hand + "',cursors='" + cursors + "' WHERE uuid = '" + player_uuid + "'");
}
}
private static File[] scanAdvancementsFile(String player_uuid, File gameDir) {
File[] files = new File[JdbcConfig.SYNC_WORLD.get().size()];
for (int i = 0; i < JdbcConfig.SYNC_WORLD.get().size(); i++) {
File advanceFile=new File(gameDir, "saves/"+JdbcConfig.SYNC_WORLD.get().get(i)+"/advancements"+"/"+player_uuid+".json");
if(!advanceFile.exists()) continue;
File advanceFile = new File(gameDir, "saves/" + JdbcConfig.SYNC_WORLD.get().get(i) + "/advancements" + "/" + player_uuid + ".json");
if (!advanceFile.exists()) continue;
files[i] = advanceFile;
}
return files;
@ -294,12 +398,10 @@ public class VanillaSync {
@SubscribeEvent
public static void onUpdate(TickEvent.LevelTickEvent event) throws SQLException {
tick++;
if(tick == 1800) {
tick=0;
if (tick == 1800) {
tick = 0;
long current = System.currentTimeMillis();
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update ="+current+" WHERE id= "+ JdbcConfig.SERVER_ID.get());
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update =" + current + " WHERE id= " + JdbcConfig.SERVER_ID.get());
}
}
}

View File

@ -4,61 +4,84 @@ import vip.fubuki.playersync.config.JdbcConfig;
import java.sql.*;
public class JDBCsetUp {
public static Connection getConnection() throws SQLException {
String url= "jdbc:mysql://"+JdbcConfig.HOST.get()+":"+JdbcConfig.PORT.get()+"?useUnicode=true&characterEncoding=utf-8&useSSL="+JdbcConfig.USE_SSL.get()+"&serverTimezone=UTC&allowPublicKeyRetrieval=true";
return DriverManager.getConnection(url, JdbcConfig.USERNAME.get(), JdbcConfig.PASSWORD.get());
/**
* Returns a connection to the MySQL server.
* @param selectDatabase if true, the returned URL includes the configured database name.
* @return a Connection object with the database explicitly selected.
* @throws SQLException if a database access error occurs.
*/
public static Connection getConnection(boolean selectDatabase) throws SQLException {
String dbName = JdbcConfig.DATABASE_NAME.get();
// Build the base URL
String url = "jdbc:mysql://" + JdbcConfig.HOST.get() + ":" + JdbcConfig.PORT.get();
if (selectDatabase && dbName != null && !dbName.isEmpty()) {
url += "/" + dbName;
}
url += "?useUnicode=true&characterEncoding=utf-8&useSSL=" + JdbcConfig.USE_SSL.get()
+ "&serverTimezone=UTC&allowPublicKeyRetrieval=true";
Connection conn = DriverManager.getConnection(url, JdbcConfig.USERNAME.get(), JdbcConfig.PASSWORD.get());
// Ensure that the connection uses the desired database by explicitly issuing "USE dbName"
if (selectDatabase && dbName != null && !dbName.isEmpty()) {
try (Statement st = conn.createStatement()) {
st.execute("USE " + dbName);
}
}
return conn;
}
public static QueryResult executeQuery(String sql) throws SQLException{
Connection connection = getConnection();
// Default connection always includes the database.
public static Connection getConnection() throws SQLException {
return getConnection(true);
}
try (Statement useStatement = connection.createStatement()) {
useStatement.execute("USE " + JdbcConfig.DATABASE_NAME.get());
}
/**
* Executes a query using a connection that includes the database.
*/
public static QueryResult executeQuery(String sql) throws SQLException {
Connection connection = getConnection(); // With database selected (and "USE" already run)
PreparedStatement queryStatement = connection.prepareStatement(sql);
ResultSet resultSet = queryStatement.executeQuery();
return new QueryResult(connection,resultSet);
return new QueryResult(connection, resultSet);
}
public static void executeUpdate(String sql) throws SQLException{
try (Connection connection = getConnection()) {
try (Statement useStatement = connection.createStatement()) {
useStatement.execute("USE " + JdbcConfig.DATABASE_NAME.get());
}
/**
* Executes an update using a connection that includes the database.
*/
public static void executeUpdate(String sql) throws SQLException {
try (Connection connection = getConnection()) { // With database selected
try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
updateStatement.executeUpdate();
}
}
}
public static void update(String sql, String... argument) throws SQLException{
Connection connection = getConnection();
try (Statement useStatement = connection.createStatement()) {
useStatement.execute("USE " + JdbcConfig.DATABASE_NAME.get());
}
PreparedStatement updateStatement = connection.prepareStatement(sql);
for (int i = 1; i <= argument.length; i++) {
updateStatement.setString(i,argument[i]);
}
updateStatement.executeUpdate();
}
public static void executeUpdate(String sql, int i) throws SQLException{
try (Connection connection = getConnection()) {
/**
* Executes an update using a connection that does NOT include a default database.
* This method is used for commands like "CREATE DATABASE IF NOT EXISTS ..."
*/
public static void executeUpdate(String sql, int dummy) throws SQLException {
try (Connection connection = getConnection(false)) { // Without default database
try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
updateStatement.executeUpdate();
}
}
}
/**
* A helper method for updates with parameters.
*/
public static void update(String sql, String... argument) throws SQLException {
try (Connection connection = getConnection()) { // With database selected
PreparedStatement updateStatement = connection.prepareStatement(sql);
for (int i = 0; i < argument.length; i++) {
updateStatement.setString(i + 1, argument[i]);
}
updateStatement.executeUpdate();
}
}
public record QueryResult(Connection connection, ResultSet resultSet) {
}
}