Compare commits

...

9 Commits

Author SHA1 Message Date
mlus
c1fc137d9f 1.3.4 release 2024-10-11 17:54:16 +08:00
mlus
a2bee9f1d9 update sync 2024-09-14 14:43:49 +08:00
mlus
3a9bd966aa 1.3.0 update 2024-02-11 17:26:59 +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
11 changed files with 318 additions and 185 deletions

View File

@ -2,6 +2,7 @@
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.
Support version now:
1.20.1
1.19-1.19.3
1.18.2
1.16.5

View File

@ -4,16 +4,16 @@ plugins {
id 'net.minecraftforge.gradle' version '5.1.+'
}
version = '1.2.0'
version = mod_version
group = 'vip.fubuki.playersync' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
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'}"
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.
@ -116,20 +116,15 @@ repositories {
}
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.ranged(it, '[1.0,)')
jarJar.ranged(it, '[3685108,)')
}
jarJar("com.zaxxer:HikariCP:3.4.5") {
jarJar.ranged(it, '[3.4.5,)')
}
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")
runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:1.16.5-4.1.0.1")
compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:1.16.5-4.1.0.1:api")
implementation fg.deobf("curse.maven:MySQL-561280:3685108")
implementation group: 'com.zaxxer', name: 'HikariCP', version: '3.4.5'
}
// Example for how to get properties into the manifest for reading at runtime.

View File

@ -2,3 +2,5 @@
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
mod_version=1.16.5-1.3.4

View File

@ -1,8 +1,6 @@
package vip.fubuki.playersync;
import com.mojang.logging.LogUtils;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModList;
@ -10,20 +8,20 @@ import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
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.ResultSet;
import java.sql.SQLException;
@Mod(PlayerSync.MODID)
public class PlayerSync
{
public static final String MODID = "playersync";
public static final Logger LOGGER = LogUtils.getLogger();
public PlayerSync()
{
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
@ -40,30 +38,38 @@ public class PlayerSync
}
@SubscribeEvent
public void onServerStarting(ServerStartingEvent event) throws SQLException {
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS "+JdbcConfig.DATABASE_NAME.get(),true);
public void onServerStarting(FMLServerStartingEvent event) throws SQLException {
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS "+JdbcConfig.DATABASE_NAME.get(),1);
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`));""");
"inventory MEDIUMBLOB,armor BLOB,advancements BLOB,enderchest MEDIUMBLOB,effects BLOB,left_hand BLOB,cursors BLOB" +
"xp int,food_level int,score int,health int,online boolean, last_server int, PRIMARY KEY (uuid));");
JDBCsetUp.QueryResult queryResult = JDBCsetUp.executeQuery("SELECT COUNT(*) AS column_count FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'player_data';");
ResultSet resultSet = queryResult.getResultSet();
int columnCount = 0;
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; ");
}
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 + ";");
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= 1 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))");
}
LOGGER.info("PlayerSync is ready!");
}
}

View File

@ -11,13 +11,16 @@ import java.util.Random;
public class JdbcConfig {
public static ForgeConfigSpec COMMON_CONFIG;
public static ForgeConfigSpec.ConfigValue<String> HOST;
public static ForgeConfigSpec.ConfigValue<String> DATABASE_NAME;
public static ForgeConfigSpec.IntValue PORT;
public static ForgeConfigSpec.ConfigValue<String> USERNAME;
public static ForgeConfigSpec.ConfigValue<String> PASSWORD;
public static ForgeConfigSpec.ConfigValue<String> DATABASE_NAME;
public static ForgeConfigSpec.ConfigValue<List<String>> SYNC_WORLD;
public static ForgeConfigSpec.BooleanValue USE_SSL;
public static ForgeConfigSpec.BooleanValue SYNC_CHAT;
public static ForgeConfigSpec.BooleanValue IS_CHAT_SERVER;
public static ForgeConfigSpec.ConfigValue<String> CHAT_SERVER_IP;
public static ForgeConfigSpec.IntValue CHAT_SERVER_PORT;
public static ForgeConfigSpec.ConfigValue<Integer> SERVER_ID;
@ -26,14 +29,17 @@ public class JdbcConfig {
ForgeConfigSpec.Builder COMMON_BUILDER = new ForgeConfigSpec.Builder();
COMMON_BUILDER.comment("General settings").push("general");
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);
USE_SSL = COMMON_BUILDER.comment("whether use SSL").define("use_ssl", false);
USERNAME = COMMON_BUILDER.comment("username").define("user_name", "root");
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));
DATABASE_NAME = COMMON_BUILDER.comment("database name").define("db_name","playersync");
SERVER_ID = COMMON_BUILDER.comment("the server id should be unique").define("Server_id", new Random().nextInt());
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);
IS_CHAT_SERVER = COMMON_BUILDER.comment("Whether recieve messages from other servers as host").define("IsChatServer",false);
CHAT_SERVER_IP = COMMON_BUILDER.define("ChatServerIP","127.0.0.1");
CHAT_SERVER_PORT = COMMON_BUILDER.defineInRange("ChatServerPort",7900,0,65535);
COMMON_BUILDER.pop();
COMMON_CONFIG = COMMON_BUILDER.build();
}

View File

@ -1,44 +1,126 @@
package vip.fubuki.playersync.sync;
import net.minecraft.network.chat.Component;
import net.minecraft.server.players.PlayerList;
import net.minecraft.server.management.PlayerList;
import net.minecraft.util.text.ChatType;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import vip.fubuki.playersync.util.JDBCsetUp;
import vip.fubuki.playersync.config.JdbcConfig;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Mod.EventBusSubscriber
public class ChatSync {
static int tick = 0;
static long current = System.currentTimeMillis();
public static void register(){}
static PlayerList playerList;
@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 + "')");
static ServerSocket serverSocket;
static Socket clientSocket;
static Set<Socket> SocketList;
static ExecutorService executorService = Executors.newCachedThreadPool();
public static void register(){
if(JdbcConfig.IS_CHAT_SERVER.get())
new Thread(ChatSync::ServerSocket).start();
ClientSocket();
MinecraftForge.EVENT_BUS.register(ChatSync.class);
}
private static void ServerSocket() {
try {
serverSocket = new ServerSocket(JdbcConfig.CHAT_SERVER_PORT.get());
while (true) {
Socket newSocket = serverSocket.accept();
SocketList.add(newSocket);
executorService.submit(() -> handleClient(newSocket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void handleClient(Socket socket) {
try (InputStream inputStream = socket.getInputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
String message = new String(buffer, 0, bytesRead);
broadcastMessage(socket, message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
SocketList.remove(socket);
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void broadcastMessage(Socket sender, String message) {
for (Socket socket : SocketList) {
if (!socket.equals(sender)) {
try {
OutputStream outputStream = socket.getOutputStream();
outputStream.write(message.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void ClientSocket() {
try {
clientSocket = new Socket(JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_PORT.get());
Scanner scanner = new Scanner(clientSocket.getInputStream());
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
playerList.broadcastMessage(ITextComponent.nullToEmpty(line), ChatType.CHAT, UUID.randomUUID());
}
} catch (IOException e) {
e.printStackTrace();
reconnectClient();
}
}
private static void reconnectClient() {
//TODO
}
@SubscribeEvent
public static void Tick(net.minecraftforge.event.TickEvent.ServerTickEvent event) throws SQLException {
tick++;
if(tick == 20) {
ReadMessage(event.getServer().getPlayerList());
}
public static void onPlayerChat(net.minecraftforge.event.ServerChatEvent event) throws IOException {
String message= event.getUsername()+":"+event.getMessage();
OutputStream outputStream = clientSocket.getOutputStream();
outputStream.write(message.getBytes());
}
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);
@SubscribeEvent
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){
playerList= Objects.requireNonNull(event.getEntity().getServer()).getPlayerList();
}
resultSet.close();
@SubscribeEvent
public static void onPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event){
playerList= Objects.requireNonNull(event.getEntity().getServer()).getPlayerList();
}
}

View File

@ -1,9 +1,9 @@
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.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.JsonToNBT;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.items.IItemHandlerModifiable;
@ -15,15 +15,17 @@ 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 {
public void onPlayerJoin(PlayerEntity player) throws SQLException {
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()+"'");
/*
Curios Support
*/
LazyOptional<IItemHandlerModifiable> itemHandler = top.theillusivec4.curios.api.CuriosApi.getCuriosHelper().getEquippedCurios(player);
JDBCsetUp.QueryResult queryResult=JDBCsetUp.executeQuery("SELECT curios_item FROM curios WHERE uuid = '"+player.getUUID()+"';");
ResultSet resultSet = queryResult.getResultSet();
if(resultSet.next()) {
String curios_data=resultSet.getString("curios_item");
if(curios_data.length()>2) {
@ -32,7 +34,7 @@ public class ModsSupport {
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("|", ","))));
handler.setStackInSlot(i, ItemStack.of(JsonToNBT.parseTag(curios.get(i).replace("|", ",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'"))));
} catch (CommandSyntaxException e) {
throw new RuntimeException(e);
}
@ -40,26 +42,26 @@ public class ModsSupport {
});
}
resultSet.close();
queryResult.getConnection().close();
}else{
StoreCurios(player,true);
}
}
}
public void onPlayerLeave(Player player) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
public void onPlayerLeave(PlayerEntity player) throws SQLException {
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);
public void StoreCurios(PlayerEntity player,boolean init) throws SQLException {
LazyOptional<IItemHandlerModifiable> itemHandler = top.theillusivec4.curios.api.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(",", "|");
String sNBT= VanillaSync.serialize(handler.getStackInSlot(i).serializeNBT().toString());
curios.put(i, sNBT);
}
}

View File

@ -1,20 +1,22 @@
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.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.potion.Effect;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.Hand;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.TickEvent;
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.fml.event.server.FMLServerStoppedEvent;
import vip.fubuki.playersync.config.JdbcConfig;
import vip.fubuki.playersync.util.JDBCsetUp;
import vip.fubuki.playersync.util.LocalJsonUtil;
@ -39,38 +41,40 @@ public class VanillaSync {
static ExecutorService executorService = Executors.newCachedThreadPool(new PSThreadPoolFactory("PlayerSync"));
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException, CommandSyntaxException, IOException {
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, 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();
JDBCsetUp.QueryResult queryResult=JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='"+player_uuid+"';");
ResultSet resultSet=queryResult.getResultSet();
ServerPlayerEntity serverPlayer = (ServerPlayerEntity) event.getEntity();
if(!resultSet.next()){
Store(event.getEntity(),true,Dist.CLIENT.isDedicatedServer());
store(event.getPlayer(),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) {
queryResult=JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='"+player_uuid+"'");
resultSet= queryResult.getResultSet();
if(online && lastServer != JdbcConfig.SERVER_ID.get()) {
ResultSet getServerInfo = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='"+lastServer+"'");
queryResult=JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='"+lastServer+"';");
ResultSet getServerInfo = queryResult.getResultSet();
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"));
serverPlayer.connection.disconnect(new StringTextComponent("playersync.already_online"));
return;
}
JDBCsetUp.executeUpdate("UPDATE server_info SET enable=false WHERE id=" + lastServer);
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' 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+"'");
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"));
@ -80,23 +84,27 @@ public class VanillaSync {
serverPlayer.experienceProgress=0;
serverPlayer.giveExperiencePoints(resultSet.getInt("xp"));
serverPlayer.setScore(resultSet.getInt("score"));
//Left Hand
serverPlayer.setItemInHand(Hand.OFF_HAND,ItemStack.of(JsonToNBT.parseTag(resultSet.getString("left_hand").replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'"))));
//Cursor
serverPlayer.inventory.setCarried(ItemStack.of(JsonToNBT.parseTag(resultSet.getString("cursors").replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'"))));
//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));
serverPlayer.inventory.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));
serverPlayer.inventory.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));
serverPlayer.getEnderChestInventory().setItem(entry.getKey(),deserialize(entry));
}
//Effects
String effectData=resultSet.getString("effects");
@ -104,9 +112,8 @@ public class VanillaSync {
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;
CompoundNBT effectTag = JsonToNBT.parseTag(entry.getValue().replace("|", ","));
EffectInstance mobEffectInstance = EffectInstance.load(effectTag);
serverPlayer.addEffect(mobEffectInstance);
}
}
@ -120,7 +127,7 @@ public class VanillaSync {
byte [] bytes=resultSet.getString("advancements").getBytes();
Files.write(advancements.toPath(),bytes);
}else{
File[] files= ScanAdvancementsFile(player_uuid, gameDir);
File[] files= scanAdvancementsFile(player_uuid, gameDir);
for (File file : files) {
if(file==null) continue;
byte [] bytes=resultSet.getString("advancements").getBytes();
@ -137,7 +144,7 @@ public class VanillaSync {
}
@SubscribeEvent
public static void OnPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event){
executorService.submit(()->{
try {
doPlayerJoin(event);
@ -148,19 +155,20 @@ public class VanillaSync {
}
private static ItemStack Deserialize(Map.Entry<Integer, String> entry) throws CommandSyntaxException {
public static ItemStack deserialize(Map.Entry<Integer, String> entry) throws CommandSyntaxException {
String nbt= entry.getValue().replace("|",",").replace("^","\"").replace("<","{").replace(">","}").replace("~", "'");
CompoundTag compoundTag = NbtUtils.snbtToStructure(nbt);
CompoundNBT compoundTag = JsonToNBT.parseTag(nbt);
return ItemStack.of(compoundTag);
}
public static void doPlayerSaveToFile(PlayerEvent.SaveToFile event) throws SQLException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
public static String serialize(String object){
return object.replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~");
}
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());
//Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerLeave(event.getEntity());
store(event.getPlayer(),false,Dist.CLIENT.isDedicatedServer());
}
@SubscribeEvent
@ -175,19 +183,18 @@ public class VanillaSync {
}
@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 onServerShutdown(FMLServerStoppedEvent event) throws SQLException {
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' WHERE id=" + JdbcConfig.SERVER_ID.get()+";");
}
public static void doPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
public static void doPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException, 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());
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '0' WHERE uuid='"+player_uuid+"';");
store(event.getPlayer(),false,Dist.CLIENT.isDedicatedServer());
//Mod support
ModsSupport modsSupport = new ModsSupport();
modsSupport.onPlayerLeave(event.getEntity());
event.getEntity().removeTag("player_synced");
modsSupport.onPlayerLeave(event.getPlayer());
}
@SubscribeEvent
@ -202,39 +209,43 @@ public class VanillaSync {
}
public static void Store(Player player, boolean init,boolean isServer) throws SQLException, IOException {
public static void store(PlayerEntity 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();
//Left hand
String left_hand = serialize(player.getItemInHand(Hand.OFF_HAND).serializeNBT().toString());
//Cursor
String cursors = serialize(player.inventory.getCarried().serializeNBT().toString());
//Equipment
Map<Integer,String> equipment =new HashMap<>() ;
for (int i = 0; i < player.getInventory().armor.size(); i++) {
ItemStack itemStack = player.getInventory().armor.get(i);
for (int i = 0; i < player.inventory.armor.size(); i++) {
ItemStack itemStack = player.inventory.armor.get(i);
if(itemStack.isEmpty()) continue;
equipment.put(i,itemStack.serializeNBT().toString().replace(",","|").replace("\"","^").replace("{","<").replace("}",">").replace("'","~"));
equipment.put(i,serialize(itemStack.serializeNBT().toString()));
}
//inventory
Inventory inventory = player.getInventory();
PlayerInventory inventory = player.inventory;
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("'","~"));
CompoundNBT itemNBT = inventory.items.get(i).serializeNBT();
inventoryMap.put(i,serialize(itemNBT.toString()));
}
//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("'","~"));
CompoundNBT itemNBT = player.getEnderChestInventory().getItem(i).serializeNBT();
ender_chest.put(i,serialize(itemNBT.toString()));
}
//Effects
Map<MobEffect,MobEffectInstance> effects= player.getActiveEffectsMap();
Map<Effect, EffectInstance> 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(",","|"));
for (Map.Entry<Effect, EffectInstance> entry : effects.entrySet()) {
CompoundNBT effectTag= entry.getValue().save(new CompoundNBT());
effectMap.put(Effect.getId(entry.getKey()),effectTag.toString().replace(",","|"));
}
//Advancements
//File root = serverPlayer.getServer().getServerDirectory();
@ -244,7 +255,7 @@ public class VanillaSync {
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);
File[] files= scanAdvancementsFile(player_uuid, gameDir);
//Get LastModified
long latestModifiedDate = 0;
for (File file : files) {
@ -263,11 +274,11 @@ public class VanillaSync {
//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+"'");
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) {
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");
@ -277,15 +288,17 @@ public class VanillaSync {
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;
// })
// );
// }
static int tick = 0;
@SubscribeEvent
public static void onUpdate(TickEvent.ServerTickEvent event) throws SQLException {
tick++;
if(tick == 1800) {
tick=0;
long current = System.currentTimeMillis();
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update ="+current+" WHERE id= "+ JdbcConfig.SERVER_ID.get());
}
}
}

View File

@ -1,7 +1,5 @@
package vip.fubuki.playersync.util;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import vip.fubuki.playersync.config.JdbcConfig;
import java.sql.*;
@ -9,40 +7,73 @@ import java.sql.*;
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 {
if (dataSource == null) {
initDataSource();
}
return dataSource.getConnection();
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());
}
public static ResultSet executeQuery(String sql) throws SQLException{
try (Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
return preparedStatement.executeQuery();
public static QueryResult executeQuery(String sql) throws SQLException{
Connection connection = getConnection();
try (Statement useStatement = connection.createStatement()) {
useStatement.execute("USE " + JdbcConfig.DATABASE_NAME.get());
}
PreparedStatement queryStatement = connection.prepareStatement(sql);
ResultSet resultSet = queryStatement.executeQuery();
return new QueryResult(connection,resultSet);
}
public static void executeUpdate(String sql) throws SQLException{
executeUpdate(sql,false);
try (Connection connection = getConnection()) {
try (Statement useStatement = connection.createStatement()) {
useStatement.execute("USE " + JdbcConfig.DATABASE_NAME.get());
}
public static void executeUpdate(String sql,boolean init) throws SQLException{
try (Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
if(!init) preparedStatement.executeUpdate("USE "+JdbcConfig.DATABASE_NAME.get());
preparedStatement.executeUpdate();
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()) {
try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
updateStatement.executeUpdate();
}
}
}
public static class QueryResult{
private final Connection connection;
private final ResultSet resultSet;
public QueryResult(Connection connection, ResultSet resultSet) {
this.connection = connection;
this.resultSet = resultSet;
}
public Connection getConnection() {
return connection;
}
public ResultSet getResultSet() {
return resultSet;
}
}
}

View File

@ -1,7 +1,5 @@
package vip.fubuki.playersync.util;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@ -14,7 +12,7 @@ public class PSThreadPoolFactory implements ThreadFactory {
threadNamePrefix = Prefix;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setName(threadNamePrefix + "-thread-" + threadIdx.getAndIncrement());
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
modLoader="javafml" #mandatory
# 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.
# 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"
@ -16,10 +16,7 @@ license="GPL-3.0 license"
[[mods]] #mandatory
# The modid of the mod
modId="playersync" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
# ${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
version="${file.jarVersion}" #mandatory
# A display name for the mod
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/
@ -51,7 +48,7 @@ make multiserver players' data sync
# Does this dependency have to exist - if not, ordering below must be specified
mandatory=true #mandatory
# 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
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT or SERVER
@ -61,6 +58,6 @@ make multiserver players' data sync
modId="minecraft"
mandatory=true
# 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"
side="BOTH"