Compare commits

...

11 Commits

Author SHA1 Message Date
mlus
699ef4a2ce backport 2025-06-05 00:28:12 +08:00
mlus
266fcf0473 Code Clean 2024-02-11 16:17:26 +08:00
mlus
1cb3998755 FTB init 2024-01-17 14:20:27 +08:00
mlus-Asuka
079ccb4c09 for cat server 2024-01-06 20:23:09 +08:00
mlus-Asuka
2e3694d7cb For AstralSorcery and FTB 2024-01-04 22:07:20 +08:00
mlus-Asuka
096ccea733 stupid sql 2023-09-29 20:07:52 +08:00
mlus-Asuka
80be4db008 bugs fix 2023-09-29 16:44:52 +08:00
mlus-Asuka
a60cd29ece back port to jdk 8 2023-09-28 19:22:47 +08:00
mlus-Asuka
e3f4aa888c update 1.2.0 2023-09-28 17:38:46 +08:00
mlus-Asuka
76836f680f 1.2.0 release 2023-08-09 16:00:09 +08:00
mlus-Asuka
e7836a37a4 init 1.18 2023-08-03 22:27:50 +08:00
14 changed files with 143 additions and 547 deletions

View File

@ -2,6 +2,7 @@
This is a Minecraft forge mod using Mysql backend to make player data synchronization between different servers. This is a Minecraft forge mod using Mysql backend to make player data synchronization between different servers.
Such as equipment,inventory,effects,experience,food level.Any other mods support is also possible. Such as equipment,inventory,effects,experience,food level.Any other mods support is also possible.
Support version now: Support version now:
1.20.1
1.19-1.19.3 1.19-1.19.3
1.18.2 1.18.2
1.16.5 1.16.5

View File

@ -1,21 +1,35 @@
buildscript{
repositories{
mavenCentral()
maven { url = 'https://maven.minecraftforge.net' }
maven { url='https://repo.spongepowered.org/repository/maven-public' }
}
dependencies {
classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT'
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
}
}
plugins { plugins {
id 'eclipse' id 'eclipse'
id 'maven-publish' id 'maven-publish'
id 'net.minecraftforge.gradle' version '5.1.+' id 'net.minecraftforge.gradle' version '5.1.+'
} }
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'org.spongepowered.mixin'
version = '1.2.0' version = mod_version
group = 'vip.fubuki.playersync' // http://maven.apache.org/guides/mini/guide-naming-conventions.html group = 'vip.fubuki.playersync' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'playersync' archivesBaseName = 'playersync'
java.toolchain.languageVersion = JavaLanguageVersion.of(17) java.toolchain.languageVersion = JavaLanguageVersion.of(8)
println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}"
minecraft { minecraft {
mappings channel: 'official', version: '1.19.2' mappings channel: 'official', version: '1.16.5'
//accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') // Currently, this location cannot be changed from the default. //accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
runs { runs {
client { client {
@ -116,22 +130,25 @@ repositories {
} }
dependencies { dependencies {
minecraft 'net.minecraftforge:forge:1.19.2-43.1.1' minecraft 'net.minecraftforge:forge:1.16.5-36.2.0'
jarJar("curse.maven:MySQL-561280:3685108") { jarJar("curse.maven:MySQL-561280:3685108") {
jarJar.ranged(it, '[1.0,)') jarJar.ranged(it, '[3685108,)')
} }
jarJar("com.zaxxer:HikariCP:3.4.5") { implementation fg.deobf("curse.maven:architectury-api-419699:4521290")
jarJar.ranged(it, '[3.4.5,)') implementation fg.deobf("curse.maven:astral-sorcery-241721:3813365")
} implementation fg.deobf("curse.maven:ftb-library-forge-404465:3553840")
implementation fg.deobf("curse.maven:ftb-quests-forge-289412:4297999")
runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:1.19.2-5.1.1.0")
compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:1.19.2-5.1.1.0:api")
implementation fg.deobf("curse.maven:MySQL-561280:3685108") implementation fg.deobf("curse.maven:MySQL-561280:3685108")
implementation group: 'com.zaxxer', name: 'HikariCP', version: '3.4.5'
} }
mixin {
add sourceSets.main, "playersync.refmap.json"
config 'playersync.mixin.json'
}
// Example for how to get properties into the manifest for reading at runtime. // Example for how to get properties into the manifest for reading at runtime.
jar { jar {
manifest { manifest {
@ -142,7 +159,8 @@ jar {
"Implementation-Title" : project.name, "Implementation-Title" : project.name,
"Implementation-Version" : project.jar.archiveVersion, "Implementation-Version" : project.jar.archiveVersion,
"Implementation-Vendor" : "examplemodsareus", "Implementation-Vendor" : "examplemodsareus",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs" : "playersync.mixin.json"
]) ])
} }
} }

View File

@ -1,4 +1,6 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties. # Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process. # This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false org.gradle.daemon=false
mod_version=1.16.5-1.3.0

View File

@ -1,69 +1,57 @@
package vip.fubuki.playersync; package vip.fubuki.playersync;
import com.mojang.logging.LogUtils; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.slf4j.Logger;
import vip.fubuki.playersync.config.JdbcConfig; 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 vip.fubuki.playersync.util.JDBCsetUp;
import vip.fubuki.playersync.util.PSThreadPoolFactory;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Mod(PlayerSync.MODID) @Mod(PlayerSync.MODID)
public class PlayerSync public class PlayerSync
{ {
public static final String MODID = "playersync"; public static final String MODID = "playersync";
public static final Logger LOGGER = LogUtils.getLogger(); public static ExecutorService executorService = Executors.newCachedThreadPool(new PSThreadPoolFactory("PlayerSync"));
public PlayerSync() public PlayerSync()
{ {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, JdbcConfig.COMMON_CONFIG); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, JdbcConfig.COMMON_CONFIG);
modEventBus.addListener(this::commonSetup);
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
} }
private void commonSetup(final FMLCommonSetupEvent event) {
VanillaSync.register();
if(JdbcConfig.SYNC_CHAT.get()){
ChatSync.register();
}
}
@SubscribeEvent @SubscribeEvent
public void onServerStarting(ServerStartingEvent event) throws SQLException { public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS "+JdbcConfig.DATABASE_NAME.get(),true); executorService.submit(() -> {
try {
if (event.getPlayer() instanceof ServerPlayerEntity) {
String player_uuid = event.getEntity().getUUID().toString();
JDBCsetUp.QueryResult queryResult=JDBCsetUp.executeQuery("SELECT * FROM AstralSorcery WHERE player='" + player_uuid + "';");
ResultSet resultSet=queryResult.getResultSet();
if(!resultSet.next()){
JDBCsetUp.executeUpdate("INSERT INTO AstralSorcery(player,tag) VALUES('"+player_uuid+"','{}');");
}
JDBCsetUp.QueryResult queryResult1=JDBCsetUp.executeQuery("SELECT * FROM FTB WHERE player='" + player_uuid + "';");
ResultSet resultSet1=queryResult1.getResultSet();
if(!resultSet1.next()){
JDBCsetUp.executeUpdate("INSERT INTO FTB(player,tag) VALUES('"+player_uuid+"','{}');");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
});
JDBCsetUp.executeUpdate("CREATE TABLE IF NOT EXISTS player_data (uuid CHAR(36) NOT NULL," +
"inventory MEDIUMBLOB,armor BLOB,advancements BLOB,enderchest MEDIUMBLOB,effects BLOB," +
"xp int,food_level int,score int,health int,online boolean, last_server int, PRIMARY KEY (uuid))");
JDBCsetUp.executeUpdate("CREATE TABLE IF NOT EXISTS chat (player CHAR(36) NOT NULL,message TEXT," +
"timestamp BIGINT)");
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`));""");
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 + ";");
if(ModList.get().isLoaded("curios")) {
JDBCsetUp.executeUpdate("CREATE TABLE IF NOT EXISTS curios (uuid CHAR(36) NOT NULL,curios_item BLOB, PRIMARY KEY (uuid))");
}
LOGGER.info("PlayerSync is ready!");
} }
} }

View File

@ -11,29 +11,20 @@ import java.util.Random;
public class JdbcConfig { public class JdbcConfig {
public static ForgeConfigSpec COMMON_CONFIG; public static ForgeConfigSpec COMMON_CONFIG;
public static ForgeConfigSpec.ConfigValue<String> HOST; public static ForgeConfigSpec.ConfigValue<String> HOST;
public static ForgeConfigSpec.ConfigValue<String> DATABASE_NAME;
public static ForgeConfigSpec.IntValue PORT; public static ForgeConfigSpec.IntValue PORT;
public static ForgeConfigSpec.ConfigValue<String> USERNAME; public static ForgeConfigSpec.ConfigValue<String> USERNAME;
public static ForgeConfigSpec.ConfigValue<String> PASSWORD; public static ForgeConfigSpec.ConfigValue<String> PASSWORD;
public static ForgeConfigSpec.ConfigValue<List<String>> SYNC_WORLD;
public static ForgeConfigSpec.BooleanValue USE_SSL; public static ForgeConfigSpec.BooleanValue USE_SSL;
public static ForgeConfigSpec.BooleanValue SYNC_CHAT;
public static ForgeConfigSpec.ConfigValue<Integer> SERVER_ID;
static { static {
ForgeConfigSpec.Builder COMMON_BUILDER = new ForgeConfigSpec.Builder(); ForgeConfigSpec.Builder COMMON_BUILDER = new ForgeConfigSpec.Builder();
COMMON_BUILDER.comment("General settings").push("general"); COMMON_BUILDER.comment("General settings").push("general");
HOST=COMMON_BUILDER.comment("The host of the database").define("host", "localhost"); HOST=COMMON_BUILDER.comment("The host of the database").define("host", "localhost");
DATABASE_NAME= COMMON_BUILDER.comment("Database name").define("database_name", "playersync");
PORT = COMMON_BUILDER.comment("database port").defineInRange("db_port", 3306, 0, 65535); PORT = COMMON_BUILDER.comment("database port").defineInRange("db_port", 3306, 0, 65535);
USE_SSL = COMMON_BUILDER.comment("whether use SSL").define("use_ssl", false); USE_SSL = COMMON_BUILDER.comment("whether use SSL").define("use_ssl", false);
USERNAME = COMMON_BUILDER.comment("username").define("user_name", "root"); USERNAME = COMMON_BUILDER.comment("username").define("user_name", "root");
PASSWORD = COMMON_BUILDER.comment("password").define("password", "password"); PASSWORD = COMMON_BUILDER.comment("password").define("password", "password");
SERVER_ID = COMMON_BUILDER.comment("the server id should be unique").define("Server_id", new Random().nextInt(1,Integer.MAX_VALUE-1));
SYNC_WORLD = COMMON_BUILDER.comment("The worlds that will be synchronized.If running in server it is supposed to have only one").define("sync_world", new ArrayList<>());
SYNC_CHAT= COMMON_BUILDER.comment("Whether synchronize chat").define("sync_chat", true);
COMMON_BUILDER.pop(); COMMON_BUILDER.pop();
COMMON_CONFIG = COMMON_BUILDER.build(); COMMON_CONFIG = COMMON_BUILDER.build();
} }

View File

@ -0,0 +1,33 @@
package vip.fubuki.playersync.mixin;
import hellfirepvp.astralsorcery.common.data.research.PlayerProgress;
import net.minecraft.nbt.CompoundNBT;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import vip.fubuki.playersync.PlayerSync;
import vip.fubuki.playersync.util.JDBCsetUp;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@Mixin(PlayerProgress.class)
public class MixinPlayerProgress{
@ModifyArg(method = "store", at = @At(value = "INVOKE", target = "Lhellfirepvp/astralsorcery/common/data/research/PlayerPerkData;save(Lnet/minecraft/nbt/CompoundNBT;)V"))
private CompoundNBT save(CompoundNBT tag) {
String nbt = tag.toString();
nbt.replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~");
PlayerSync.executorService.submit(() -> {
try {
PreparedStatement preparedStatement= JDBCsetUp.getConnection().prepareStatement("UPDATE AstralSorcery SET tag=? WHERE player=?");
preparedStatement.setString(2,tag.getString("UUID"));
preparedStatement.setString(1,nbt);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
});
return tag;
}
}

View File

@ -1,44 +0,0 @@
package vip.fubuki.playersync.sync;
import net.minecraft.network.chat.Component;
import net.minecraft.server.players.PlayerList;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import vip.fubuki.playersync.util.JDBCsetUp;
import java.sql.ResultSet;
import java.sql.SQLException;
@Mod.EventBusSubscriber
public class ChatSync {
static int tick = 0;
static long current = System.currentTimeMillis();
public static void register(){}
@SubscribeEvent
public static void onPlayerChat(net.minecraftforge.event.ServerChatEvent event) throws SQLException {
JDBCsetUp.executeUpdate("INSERT INTO chat (player, message, timestamp) VALUES ('" + event.getUsername() + "', '" + event.getRawText() + "', '" + current + "')");
}
@SubscribeEvent
public static void Tick(net.minecraftforge.event.TickEvent.ServerTickEvent event) throws SQLException {
tick++;
if(tick == 20) {
ReadMessage(event.getServer().getPlayerList());
}
}
public static void ReadMessage(PlayerList playerList) throws SQLException {
ResultSet resultSet= JDBCsetUp.executeQuery("SELECT * FROM chat WHERE timestamp > " + current);
current = System.currentTimeMillis();
tick = 0;
while(resultSet.next()) {
String player = resultSet.getString("player");
String message = resultSet.getString("message");
Component textComponents = Component.literal(player+": "+message);
playerList.broadcastSystemMessage(textComponents, true);
}
resultSet.close();
}
}

View File

@ -1,73 +0,0 @@
package vip.fubuki.playersync.sync;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
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;
import net.minecraftforge.items.IItemHandlerModifiable;
import vip.fubuki.playersync.util.JDBCsetUp;
import vip.fubuki.playersync.util.LocalJsonUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"InstantiationOfUtilityClass", "AccessStaticViaInstance"})
public class ModsSupport {
public void onPlayerJoin(Player player) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
if (ModList.get().isLoaded("curios")) {
//TODO curios support
top.theillusivec4.curios.api.CuriosApi CuriosApi = new top.theillusivec4.curios.api.CuriosApi();
LazyOptional<IItemHandlerModifiable> itemHandler = CuriosApi.getCuriosHelper().getEquippedCurios(player);
ResultSet resultSet = JDBCsetUp.executeQuery("SELECT curios_item FROM curios WHERE uuid = '"+player.getUUID()+"'");
if(resultSet.next()) {
String curios_data=resultSet.getString("curios_item");
if(curios_data.length()>2) {
Map<Integer, String> curios = LocalJsonUtil.StringToEntryMap(curios_data);
itemHandler.ifPresent(handler -> {
for (int i = 0; i < handler.getSlots(); i++) {
try {
if (curios.get(i) == null) continue;
handler.setStackInSlot(i, ItemStack.of(NbtUtils.snbtToStructure(curios.get(i).replace("|", ","))));
} catch (CommandSyntaxException e) {
throw new RuntimeException(e);
}
}
});
}
resultSet.close();
}else{
StoreCurios(player,true);
}
}
}
public void onPlayerLeave(Player player) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
if (ModList.get().isLoaded("curios")) {
StoreCurios(player, false);
}
}
public void StoreCurios(Player player,boolean init) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
top.theillusivec4.curios.api.CuriosApi CuriosApi = new top.theillusivec4.curios.api.CuriosApi();
LazyOptional<IItemHandlerModifiable> itemHandler = CuriosApi.getCuriosHelper().getEquippedCurios(player);
Map<Integer, String> curios = new HashMap<>();
itemHandler.ifPresent(handler -> {
for (int i = 0; i < handler.getSlots(); i++) {
if (!handler.getStackInSlot(i).isEmpty()) {
String sNBT= handler.getStackInSlot(i).serializeNBT().toString().replace(",", "|");
curios.put(i, sNBT);
}
}
});
if(init) {
JDBCsetUp.executeUpdate("INSERT INTO curios (uuid,curios_item) VALUES ('"+player.getUUID()+"','"+ curios+"')");
} else {
JDBCsetUp.executeUpdate("UPDATE curios SET curios_item = '"+ curios+"' WHERE uuid = '"+player.getUUID()+"'");
}
}
}

View File

@ -1,291 +0,0 @@
package vip.fubuki.playersync.sync;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
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.config.JdbcConfig;
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(){}
static ExecutorService executorService = Executors.newCachedThreadPool(new PSThreadPoolFactory("PlayerSync"));
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException, CommandSyntaxException, IOException {
String player_uuid = event.getEntity().getUUID().toString();
ResultSet resultSet=JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='"+player_uuid+"'");
ServerPlayer serverPlayer = (ServerPlayer) event.getEntity();
if(!resultSet.next()){
Store(event.getEntity(),true,Dist.CLIENT.isDedicatedServer());
return;
}
boolean online = resultSet.getBoolean("online");
int lastServer = resultSet.getInt("last_server");
resultSet=JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='"+player_uuid+"'");
if(online) {
ResultSet getServerInfo = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='"+lastServer+"'");
if(getServerInfo.next()){
long last_update = getServerInfo.getLong("last_update");
boolean enable = getServerInfo.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=false WHERE id=" + lastServer);
}
getServerInfo.close();
}
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
JDBCsetUp.executeUpdate("UPDATE player_data SET online=true,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"));
//Equipment
String armor_data=resultSet.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"));
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"));
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) {
serverPlayer.removeAllEffects();
Map<Integer, String> effects = LocalJsonUtil.StringToEntryMap(effectData);
for (Map.Entry<Integer, String> entry : effects.entrySet()) {
CompoundTag effectTag = NbtUtils.snbtToStructure(entry.getValue().replace("|", ","));
MobEffectInstance mobEffectInstance = MobEffectInstance.load(effectTag);
assert mobEffectInstance != null;
serverPlayer.addEffect(mobEffectInstance);
}
}
//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 (!advancements.exists()) {
advancements.createNewFile();
}
byte [] bytes=resultSet.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);
}
}
}
//Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerJoin(serverPlayer);
serverPlayer.addTag("player_synced");
resultSet.close();
}
@SubscribeEvent
public static void OnPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){
executorService.submit(()->{
try {
doPlayerJoin(event);
} catch (Exception e) {
e.printStackTrace();
}
});
}
private static ItemStack Deserialize(Map.Entry<Integer, String> entry) throws CommandSyntaxException {
String nbt= entry.getValue().replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'");
CompoundTag compoundTag = NbtUtils.snbtToStructure(nbt);
return ItemStack.of(compoundTag);
}
public static void doPlayerSaveToFile(PlayerEvent.SaveToFile event) throws SQLException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
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());
//Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerLeave(event.getEntity());
}
@SubscribeEvent
public static void onPlayerSaveToFile(PlayerEvent.SaveToFile event) {
executorService.submit(()->{
try {
doPlayerSaveToFile(event);
} catch (Exception e) {
e.printStackTrace();
}
});
}
@SubscribeEvent
public static void onServerShutdown(ServerStoppedEvent event) throws SQLException {
JDBCsetUp.executeUpdate("UPDATE server_info SET enable=false WHERE id=" + JdbcConfig.SERVER_ID.get());
}
public static void doPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
if(!event.getEntity().getTags().contains("player_synced")) return;
String player_uuid = event.getEntity().getUUID().toString();
JDBCsetUp.executeUpdate("UPDATE player_data SET online=false WHERE uuid='"+player_uuid+"'");
Store(event.getEntity(),false,Dist.CLIENT.isDedicatedServer());
//Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerLeave(event.getEntity());
event.getEntity().removeTag("player_synced");
}
@SubscribeEvent
public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
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
int XP = player.totalExperience;
int score=player.getScore();
int food_level=player.getFoodData().getFoodLevel();
int health=(int) player.getHealth();
//Equipment
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,itemStack.serializeNBT().toString().replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~"));
}
//inventory
Inventory inventory = player.getInventory();
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,itemNBT.toString().replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~"));
}
//EnderChest
Map<Integer, String> ender_chest=new HashMap<>();
for (int i=0;i< player.getEnderChestInventory().getContainerSize();i++) {
CompoundTag itemNBT = player.getEnderChestInventory().getItem(i).serializeNBT();
ender_chest.put(i,itemNBT.toString().replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~"));
}
//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(",","|"));
}
//Advancements
//File root = serverPlayer.getServer().getServerDirectory();
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
long latestModifiedDate = 0;
for (File file : files) {
if(file==null) continue;
if (file.lastModified() > latestModifiedDate) {
latestModifiedDate = file.lastModified();
advancements = file;
}
}
}
byte[] bytes = new byte[0];
if (advancements != null) {
bytes = Files.readAllBytes(advancements.toPath());
}
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,online) VALUES ('"+player_uuid+"','"+equipment+"','"+inventoryMap+"','"+ender_chest+"','"+advancements+"','"+effectMap+"','"+XP+"','"+food_level+"','"+health+"','"+score+"',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+"' 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;
files[i] = advanceFile;
}
return files;
}
// @SubscribeEvent
// public void RegisterCommand(RegisterCommandsEvent event){
// CommandDispatcher<CommandSourceStack> dispatcher=event.getDispatcher();
// LiteralCommandNode<CommandSourceStack> cmd = dispatcher.register(
// Commands.literal("serializeNBT").executes(context -> {context.getSource().sendSuccess(Component.literal(context.getSource().getPlayer().getItemInHand(InteractionHand.MAIN_HAND).serializeNBT().toString()),true);
// return 0;
// })
// );
// }
}

View File

@ -1,7 +1,5 @@
package vip.fubuki.playersync.util; package vip.fubuki.playersync.util;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.config.JdbcConfig;
import java.sql.*; import java.sql.*;
@ -9,40 +7,42 @@ import java.sql.*;
public class JDBCsetUp { public class JDBCsetUp {
private static HikariDataSource dataSource;
public static void initDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://"+JdbcConfig.HOST.get()+":"+JdbcConfig.PORT.get()+"?useUnicode=true&characterEncoding=utf-8&useSSL="+JdbcConfig.USE_SSL.get()+"&serverTimezone=UTC&allowPublicKeyRetrieval=true");
config.setUsername(JdbcConfig.USERNAME.get());
config.setPassword(JdbcConfig.PASSWORD.get());
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException { public static Connection getConnection() throws SQLException {
if (dataSource == null) { String url= "jdbc:mysql://"+JdbcConfig.HOST.get()+":"+JdbcConfig.PORT.get()+"/playersync?useUnicode=true&characterEncoding=utf-8&useSSL="+JdbcConfig.USE_SSL.get()+"&serverTimezone=UTC&allowPublicKeyRetrieval=true";
initDataSource(); return DriverManager.getConnection(url, JdbcConfig.USERNAME.get(), JdbcConfig.PASSWORD.get());
}
return dataSource.getConnection();
} }
public static ResultSet executeQuery(String sql) throws SQLException{ public static QueryResult executeQuery(String sql) throws SQLException{
try (Connection connection = getConnection(); Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { PreparedStatement queryStatement = connection.prepareStatement(sql);
return preparedStatement.executeQuery(); ResultSet resultSet = queryStatement.executeQuery();
return new QueryResult(connection,resultSet);
}
public static int executeUpdate(String sql) throws SQLException{
try (Connection connection = getConnection()) {
try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
return updateStatement.executeUpdate();
}
} }
} }
public static void executeUpdate(String sql) throws SQLException{ public static class QueryResult{
executeUpdate(sql,false); private final Connection connection;
} private final ResultSet resultSet;
public static void executeUpdate(String sql,boolean init) throws SQLException{ public QueryResult(Connection connection, ResultSet resultSet) {
try (Connection connection = getConnection(); this.connection = connection;
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { this.resultSet = resultSet;
if(!init) preparedStatement.executeUpdate("USE "+JdbcConfig.DATABASE_NAME.get()); }
preparedStatement.executeUpdate();
public Connection getConnection() {
return connection;
}
public ResultSet getResultSet() {
return resultSet;
} }
} }
} }

View File

@ -1,32 +0,0 @@
package vip.fubuki.playersync.util;
import java.util.HashMap;
import java.util.Map;
public class LocalJsonUtil {
public static Map<String,String> StringToMap(String param) {
Map<String,String> map = new HashMap<>();
String s1 = param.substring(1,param.length()-1);
String s2 = s1.trim();
String[] split = s2.split(",");
for (int i = split.length - 1; i >= 0; i--) {
String trim = split[i].trim();
String[] split1 = trim.split("=");
map.put(split1[0],split1[1]);
}
return map;
}
public static Map<Integer,String> StringToEntryMap(String param) {
Map<Integer,String> map = new HashMap<>();
String s1 = param.substring(1,param.length()-1);
String s2 = s1.trim();
String[] split = s2.split(",");
for (int i = split.length - 1; i >= 0; i--) {
String trim = split[i].trim();
String[] split1 = trim.split("=");
map.put(Integer.parseInt(split1[0]),split1[1]);
}
return map;
}
}

View File

@ -1,7 +1,6 @@
package vip.fubuki.playersync.util; package vip.fubuki.playersync.util;
import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -14,14 +13,9 @@ public class PSThreadPoolFactory implements ThreadFactory {
threadNamePrefix = Prefix; threadNamePrefix = Prefix;
} }
@Override @Override
public Thread newThread(@NotNull Runnable runnable) { public Thread newThread(@Nonnull Runnable runnable) {
Thread thread = new Thread(runnable); Thread thread = new Thread(runnable);
thread.setName(threadNamePrefix + "-thread-" + threadIdx.getAndIncrement()); thread.setName(threadNamePrefix + "-thread-" + threadIdx.getAndIncrement());
return thread; return thread;
} }
} }

View File

@ -6,7 +6,7 @@
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version # A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. loaderVersion="[36,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="GPL-3.0 license" license="GPL-3.0 license"
@ -16,10 +16,7 @@ license="GPL-3.0 license"
[[mods]] #mandatory [[mods]] #mandatory
# The modid of the mod # The modid of the mod
modId="playersync" #mandatory modId="playersync" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it version="${file.jarVersion}" #mandatory
# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
# see the associated build.gradle script for how to populate this completely automatically during a build
version="1.2.0" #mandatory
# A display name for the mod # A display name for the mod
displayName="PlayerSync" #mandatory displayName="PlayerSync" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ # A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
@ -51,7 +48,7 @@ make multiserver players' data sync
# Does this dependency have to exist - if not, ordering below must be specified # Does this dependency have to exist - if not, ordering below must be specified
mandatory=true #mandatory mandatory=true #mandatory
# The version range of the dependency # The version range of the dependency
versionRange="[43,)" #mandatory versionRange="[36,37)" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
ordering="NONE" ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT or SERVER # Side this dependency is applied on - BOTH, CLIENT or SERVER
@ -61,6 +58,6 @@ make multiserver players' data sync
modId="minecraft" modId="minecraft"
mandatory=true mandatory=true
# This version range declares a minimum of the current minecraft version up to but not including the next major version # This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange="[1.19.2,1.20)" versionRange="[1.16.5,1.17)"
ordering="NONE" ordering="NONE"
side="BOTH" side="BOTH"

View File

@ -0,0 +1,12 @@
{
"required": true,
"package": "vip.fubuki.playersync.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "thirst.refmap.json",
"mixins": [
"MixinPlayerProgress"
],
"client": [
],
"minVersion": "0.8"
}