Compare commits
11 Commits
1.20.1
...
1.16.5-EXT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
699ef4a2ce | ||
|
|
266fcf0473 | ||
|
|
1cb3998755 | ||
|
|
079ccb4c09 | ||
|
|
2e3694d7cb | ||
|
|
096ccea733 | ||
|
|
80be4db008 | ||
|
|
a60cd29ece | ||
|
|
e3f4aa888c | ||
|
|
76836f680f | ||
|
|
e7836a37a4 |
|
|
@ -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
|
||||
|
|
|
|||
46
build.gradle
46
build.gradle
|
|
@ -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 {
|
||||
id 'eclipse'
|
||||
id 'maven-publish'
|
||||
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
|
||||
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.
|
||||
//accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
|
||||
runs {
|
||||
client {
|
||||
|
|
@ -116,22 +130,25 @@ 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")
|
||||
implementation fg.deobf("curse.maven:architectury-api-419699:4521290")
|
||||
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")
|
||||
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.
|
||||
jar {
|
||||
manifest {
|
||||
|
|
@ -142,7 +159,8 @@ jar {
|
|||
"Implementation-Title" : project.name,
|
||||
"Implementation-Version" : project.jar.archiveVersion,
|
||||
"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"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.0
|
||||
|
|
@ -1,69 +1,57 @@
|
|||
package vip.fubuki.playersync;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
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.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
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.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 vip.fubuki.playersync.util.PSThreadPoolFactory;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Mod(PlayerSync.MODID)
|
||||
public class 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()
|
||||
{
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, JdbcConfig.COMMON_CONFIG);
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
VanillaSync.register();
|
||||
if(JdbcConfig.SYNC_CHAT.get()){
|
||||
ChatSync.register();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerStarting(ServerStartingEvent event) throws SQLException {
|
||||
JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS "+JdbcConfig.DATABASE_NAME.get(),true);
|
||||
|
||||
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))");
|
||||
public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
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+"','{}');");
|
||||
}
|
||||
LOGGER.info("PlayerSync is ready!");
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,29 +11,20 @@ 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<List<String>> SYNC_WORLD;
|
||||
public static ForgeConfigSpec.BooleanValue USE_SSL;
|
||||
public static ForgeConfigSpec.BooleanValue SYNC_CHAT;
|
||||
|
||||
public static ForgeConfigSpec.ConfigValue<Integer> SERVER_ID;
|
||||
|
||||
|
||||
static {
|
||||
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));
|
||||
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_CONFIG = COMMON_BUILDER.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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,42 @@ 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()+"/playersync?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();
|
||||
PreparedStatement queryStatement = connection.prepareStatement(sql);
|
||||
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{
|
||||
executeUpdate(sql,false);
|
||||
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 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();
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package vip.fubuki.playersync.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
|
@ -14,14 +13,9 @@ public class PSThreadPoolFactory implements ThreadFactory {
|
|||
threadNamePrefix = Prefix;
|
||||
}
|
||||
@Override
|
||||
public Thread newThread(@NotNull Runnable runnable) {
|
||||
public Thread newThread(@Nonnull Runnable runnable) {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setName(threadNamePrefix + "-thread-" + threadIdx.getAndIncrement());
|
||||
return thread;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
12
src/main/resources/playersync.mixin.json
Normal file
12
src/main/resources/playersync.mixin.json
Normal 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"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user