- Add Accessories API sync for Aether mod accessory slots (pendant, cape, gloves, rings, shield, misc). Uses same pattern as Curios: validate data before clearing slots, PreparedStatements for DB operations - Add Cosmetic Armor Reworked sync for 4 cosmetic armor slots via InventoryManager/CosArmorAPI - Add Apotheosis + Placebo as compileOnly deps. Apotheosis item data (affixes, gems, sockets, rarity) travels with items via DataComponents and is already synced by the inventory sync - New generic mod_player_data DB table with composite key (uuid, mod_id) for extensible mod-specific data storage - Integrated save/restore in join, logout, and auto-save pipelines Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
225 lines
9.8 KiB
Java
225 lines
9.8 KiB
Java
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();
|
|
}
|
|
|
|
}
|