package vip.fubuki.playersync; import com.mojang.logging.LogUtils; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModList; import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.server.ServerStartingEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import org.slf4j.Logger; import vip.fubuki.playersync.config.JdbcConfig; 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 static final String MODID = "playersync"; public static final Logger LOGGER = LogUtils.getLogger(); public PlayerSync(IEventBus modEventBus, ModContainer modContainer) { modEventBus.addListener(this::commonSetup); NeoForge.EVENT_BUS.register(this); modContainer.registerConfig(ModConfig.Type.COMMON, JdbcConfig.COMMON_CONFIG); } private void commonSetup(final FMLCommonSetupEvent event) { VanillaSync.register(); event.enqueueWork(() -> { // read SYNC_CHAT only within the enqueueWork to reliably get the real // config value and not its default value. if (JdbcConfig.SYNC_CHAT.get()) { LOGGER.info("Chat sync enabled."); ChatSync.register(); } }); } @SubscribeEvent public void onServerStarting(ServerStartingEvent event) throws SQLException { String dbName = JdbcConfig.DATABASE_NAME.get(); // Step 1: Create the database using a connection that does not select a database. JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS `" + dbName + "`", 1); // 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()) { columnCount = resultSet.getInt("column_count"); } if (columnCount < 14) { JDBCsetUp.executeUpdate( "ALTER TABLE `" + dbName + "`.`player_data` " + "ADD COLUMN left_hand blob, " + "ADD COLUMN cursors blob;" ); } // 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 `" + 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() + "'" ); // 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 Cobblemon table if(ModList.get().isLoaded("cobblemon")){ JDBCsetUp.executeUpdate( "CREATE TABLE IF NOT EXISTS `" + dbName + "`.`cobblemon`(" + "uuid CHAR(36) NOT NULL," + "inv BLOB," + "pokedex MEDIUMBLOB," + "pc MEDIUMBLOB," + "general BLOB," + "PRIMARY KEY (uuid)" + ")" ); JDBCsetUp.executeUpdate( "ALTER TABLE `" + dbName + "`.`cobblemon` MODIFY COLUMN pc MEDIUMBLOB" ); JDBCsetUp.executeUpdate( "ALTER TABLE `" + dbName + "`.`cobblemon` MODIFY COLUMN pokedex MEDIUMBLOB" ); } // Create backpack_data table if (ModList.get().isLoaded("sophisticatedbackpacks")) { JDBCsetUp.executeUpdate( "CREATE TABLE IF NOT EXISTS `" + dbName + "`.`backpack_data` (" + "uuid CHAR(36) NOT NULL, backpack_nbt MEDIUMBLOB, PRIMARY KEY (uuid)" + ");", 1 ); // 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(); backpackColCheck.connection().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 ----- // Create generic mod_player_data table for mod compatibility (Accessories, CosmeticArmor, Aether, etc.) JDBCsetUp.executeUpdate( "CREATE TABLE IF NOT EXISTS `" + dbName + "`.`mod_player_data` (" + "`uuid` CHAR(36) NOT NULL," + "`mod_id` VARCHAR(64) NOT NULL," + "`data_value` MEDIUMBLOB," + "PRIMARY KEY (`uuid`, `mod_id`)" + ");" ); try { JDBCsetUp.executeUpdate("UPDATE player_data SET online=0 WHERE last_server=" + JdbcConfig.SERVER_ID.get() +" AND online=1 LIMIT 1000"); } catch (Exception e) { LOGGER.error("An exception occurred while trying change wrong player-status\n" + e.getMessage()); } LOGGER.info("PlayerSync is ready!"); } @SubscribeEvent public void onServerStopping(ServerStoppingEvent event){ ChatSync.shutdown(); } }