recently commit port
This commit is contained in:
parent
b5e5f9fb65
commit
29da0f28ad
|
|
@ -177,6 +177,11 @@ public class PlayerSync {
|
|||
rsAdvCol.close();
|
||||
// ----- END NEW BLOCK -----
|
||||
|
||||
try {
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online=0 WHERE last_server=" + JdbcConfig.SERVER_ID.get() +" AND online=1 LIMIT 1000");
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("An exception occurred while trying change wrong player-status\n" + e.getMessage());
|
||||
}
|
||||
LOGGER.info("PlayerSync is ready!");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package vip.fubuki.playersync.sync;
|
|||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
|
|
@ -11,9 +12,11 @@ import net.minecraft.nbt.NbtUtils;
|
|||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.PlayerAdvancements;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.effect.MobEffect;
|
||||
|
|
@ -24,18 +27,23 @@ import net.minecraft.world.item.ItemStack;
|
|||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.WorldData;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.event.OnDatapackSyncEvent;
|
||||
import net.neoforged.neoforge.event.entity.living.LivingDeathEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerNegotiationEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStoppedEvent;
|
||||
import net.neoforged.neoforge.event.tick.LevelTickEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.server.ServerLifecycleHooks;
|
||||
import vip.fubuki.playersync.PlayerSync;
|
||||
import vip.fubuki.playersync.config.JdbcConfig;
|
||||
import vip.fubuki.playersync.sync.addons.CuriosCache;
|
||||
import vip.fubuki.playersync.sync.addons.ModsSupport;
|
||||
import vip.fubuki.playersync.util.JDBCsetUp;
|
||||
import vip.fubuki.playersync.util.LocalJsonUtil;
|
||||
import vip.fubuki.playersync.util.PSThreadPoolFactory;
|
||||
|
|
@ -47,6 +55,7 @@ import java.nio.file.Files;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
|
@ -141,106 +150,223 @@ public class VanillaSync {
|
|||
}
|
||||
}
|
||||
|
||||
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, CommandSyntaxException, IOException {
|
||||
String player_uuid = event.getEntity().getUUID().toString();
|
||||
PlayerSync.LOGGER.info("Starting synchronization for player " + player_uuid);
|
||||
public static void doPlayerConnect(PlayerNegotiationEvent event) {
|
||||
try {
|
||||
String player_uuid = event.getProfile().getId().toString();
|
||||
PlayerSync.LOGGER.info("Detected connection from player" + player_uuid + ",starting checking");
|
||||
boolean online;
|
||||
int lastServer;
|
||||
|
||||
// First query: check basic player data
|
||||
JDBCsetUp.QueryResult qr1 = JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='" + player_uuid + "'");
|
||||
ResultSet rs1 = qr1.resultSet();
|
||||
ServerPlayer serverPlayer = (ServerPlayer) event.getEntity();
|
||||
// First query: check basic player data and check whether player can join into server.
|
||||
JDBCsetUp.QueryResult qr1 = JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='" + player_uuid + "'");
|
||||
|
||||
// Mod support
|
||||
ModsSupport modsSupport = new ModsSupport();
|
||||
modsSupport.onPlayerJoin(serverPlayer);
|
||||
|
||||
if (!rs1.next()){
|
||||
store(event.getEntity(), true);
|
||||
return;
|
||||
}
|
||||
boolean online = rs1.getBoolean("online");
|
||||
int lastServer = rs1.getInt("last_server");
|
||||
|
||||
// Second query: retrieve full player data
|
||||
JDBCsetUp.QueryResult qr2 = JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='" + player_uuid + "'");
|
||||
ResultSet rs2 = qr2.resultSet();
|
||||
|
||||
// Check if player is already online on another server
|
||||
if (online && lastServer != JdbcConfig.SERVER_ID.get()) {
|
||||
JDBCsetUp.QueryResult qr3 = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='" + lastServer + "'");
|
||||
ResultSet rs3 = qr3.resultSet();
|
||||
if (rs3.next()){
|
||||
long last_update = rs3.getLong("last_update");
|
||||
boolean enable = rs3.getBoolean("enable");
|
||||
if (enable && System.currentTimeMillis() < last_update + 300000.0){
|
||||
event.getEntity().removeTag("player_synced");
|
||||
serverPlayer.connection.disconnect(Component.translatable("playersync.already_online"));
|
||||
try (ResultSet rs1 = qr1.resultSet()) {
|
||||
if (!rs1.next()) {
|
||||
PlayerSync.LOGGER.info("A new-player connection detected");
|
||||
qr1.connection().close();
|
||||
return;
|
||||
}
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' WHERE id=" + lastServer);
|
||||
online = rs1.getBoolean("online");
|
||||
lastServer = rs1.getInt("last_server");
|
||||
qr1.connection().close();
|
||||
}
|
||||
rs3.close();
|
||||
}
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='" + player_uuid + "'");
|
||||
|
||||
if (rs2.next()) {
|
||||
// Restore basic attributes
|
||||
serverPlayer.setHealth(rs2.getInt("health"));
|
||||
serverPlayer.getFoodData().setFoodLevel(rs2.getInt("food_level"));
|
||||
|
||||
setXpForPlayer(serverPlayer, rs2.getInt("xp"));
|
||||
serverPlayer.setScore(rs2.getInt("score"));
|
||||
|
||||
// Restore left-hand item
|
||||
String leftHandEncoded = rs2.getString("left_hand");
|
||||
serverPlayer.setItemInHand(InteractionHand.OFF_HAND,
|
||||
deserializeAndCreatePlaceholderIfNeeded(leftHandEncoded));
|
||||
|
||||
// Restore cursor item
|
||||
String cursorsEncoded = rs2.getString("cursors");
|
||||
serverPlayer.containerMenu.setCarried(
|
||||
deserializeAndCreatePlaceholderIfNeeded(cursorsEncoded));
|
||||
|
||||
// Restore armor
|
||||
String armor_data = rs2.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(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
// Second query: Check if player is already online on another server
|
||||
if (online && lastServer != JdbcConfig.SERVER_ID.get()) {
|
||||
JDBCsetUp.QueryResult qr2 = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='" + lastServer + "'");
|
||||
try (ResultSet rs2 = qr2.resultSet()) {
|
||||
if (rs2.next()) {
|
||||
long last_update = rs2.getLong("last_update");
|
||||
boolean enable = rs2.getBoolean("enable");
|
||||
if (enable && System.currentTimeMillis() < last_update + 300000.0) {
|
||||
event.getConnection().disconnect(Component.translatable("playersync.already_online"));
|
||||
qr2.connection().close();
|
||||
return;
|
||||
}
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' WHERE id=" + lastServer);
|
||||
}
|
||||
qr2.connection().close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("SqlException detected!", e);
|
||||
event.getConnection().disconnect(Component.translatable("playersync.sqlexception"));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore inventory
|
||||
Map<Integer, String> inventory = LocalJsonUtil.StringToEntryMap(rs2.getString("inventory"));
|
||||
for (Map.Entry<Integer, String> entry : inventory.entrySet()) {
|
||||
serverPlayer.getInventory().setItem(entry.getKey(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
// Use string uuid as key
|
||||
public static Set<String> deadPlayerWhileLogging = ConcurrentHashMap.newKeySet();
|
||||
public static Set<String> syncNotCompletedPlayer = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public static void doPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) throws SQLException, CommandSyntaxException, IOException {
|
||||
ServerPlayer joinedPlayer = (ServerPlayer) event.getEntity();
|
||||
String player_uuid = joinedPlayer.getUUID().toString();
|
||||
if (joinedPlayer.isDeadOrDying()) {
|
||||
deadPlayerWhileLogging.add(player_uuid);
|
||||
joinedPlayer.removeTag("player_synced");
|
||||
|
||||
// Simulate normal death behavior
|
||||
MinecraftServer server = joinedPlayer.getServer();
|
||||
if (server != null) {
|
||||
ResourceKey<Level> respawnLevel = joinedPlayer.getRespawnDimension();
|
||||
BlockPos respawnPos = joinedPlayer.getRespawnPosition();
|
||||
double respawnX;
|
||||
double respawnY;
|
||||
double respawnZ;
|
||||
if (respawnPos != null && respawnLevel != null) {
|
||||
ServerLevel level = server.getLevel(respawnLevel);
|
||||
respawnX = respawnPos.getX();
|
||||
respawnY = respawnPos.getY();
|
||||
respawnZ = respawnPos.getZ();
|
||||
if (level != null) {
|
||||
joinedPlayer.teleportTo(level, respawnX, respawnY + 1, respawnZ, 0, 0);
|
||||
}
|
||||
} else {
|
||||
PlayerSync.LOGGER.debug("Player " + player_uuid + " has no respawn point");
|
||||
}
|
||||
} else {
|
||||
PlayerSync.LOGGER.warn("Trying to get server,but got a null");
|
||||
}
|
||||
|
||||
// Restore Ender Chest
|
||||
Map<Integer, String> ender_chest = LocalJsonUtil.StringToEntryMap(rs2.getString("enderchest"));
|
||||
for (Map.Entry<Integer, String> entry : ender_chest.entrySet()) {
|
||||
serverPlayer.getEnderChestInventory().setItem(entry.getKey(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
joinedPlayer.setHealth(1);
|
||||
try {
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='" + player_uuid + "'");
|
||||
} catch (SQLException e) {
|
||||
PlayerSync.LOGGER.error("An error occurred while trying to execute a dead or dying player" + e.getMessage());
|
||||
}
|
||||
joinedPlayer.connection.disconnect(Component.translatableWithFallback("playersync.wrong_entity_status","An error occurred while creating playerEntity in the world,please login again."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore Effects
|
||||
String effectData = rs2.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(deserializeString(entry.getValue()));
|
||||
MobEffectInstance mobEffectInstance = MobEffectInstance.load(effectTag);
|
||||
if (mobEffectInstance != null) {
|
||||
serverPlayer.addEffect(mobEffectInstance);
|
||||
try {
|
||||
PlayerSync.LOGGER.info("Starting synchronization for player " + player_uuid);
|
||||
|
||||
// First query: check basic player data
|
||||
JDBCsetUp.QueryResult qr1 = JDBCsetUp.executeQuery("SELECT online, last_server FROM player_data WHERE uuid='" + player_uuid + "'");
|
||||
ResultSet rs1 = qr1.resultSet();
|
||||
ServerPlayer serverPlayer = (ServerPlayer) event.getEntity();
|
||||
|
||||
// Mod support
|
||||
ModsSupport modsSupport = new ModsSupport();
|
||||
modsSupport.onPlayerJoin(serverPlayer);
|
||||
|
||||
if (!rs1.next()) {
|
||||
store(event.getEntity(), true);
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='" + player_uuid + "'");
|
||||
rs1.close();
|
||||
qr1.close();
|
||||
PlayerSync.LOGGER.info("New player detected,init completed.");
|
||||
syncNotCompletedPlayer.remove(player_uuid);
|
||||
return;
|
||||
}
|
||||
boolean online = rs1.getBoolean("online");
|
||||
int lastServer = rs1.getInt("last_server");
|
||||
|
||||
// Second query: retrieve full player data
|
||||
JDBCsetUp.QueryResult qr2 = JDBCsetUp.executeQuery("SELECT * FROM player_data WHERE uuid='" + player_uuid + "'");
|
||||
ResultSet rs2 = qr2.resultSet();
|
||||
|
||||
// Check if player is already online on another server
|
||||
if (online && lastServer != JdbcConfig.SERVER_ID.get()) {
|
||||
JDBCsetUp.QueryResult qr3 = JDBCsetUp.executeQuery("SELECT last_update,enable FROM server_info WHERE id='" + lastServer + "'");
|
||||
ResultSet rs3 = qr3.resultSet();
|
||||
if (rs3.next()) {
|
||||
long last_update = rs3.getLong("last_update");
|
||||
boolean enable = rs3.getBoolean("enable");
|
||||
if (enable && System.currentTimeMillis() < last_update + 300000.0) {
|
||||
event.getEntity().removeTag("player_synced");
|
||||
serverPlayer.connection.disconnect(Component.translatableWithFallback("playersync.already_online", "You can't join more than one synchronization server at the same time."));
|
||||
return;
|
||||
}
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET enable= '0' WHERE id=" + lastServer);
|
||||
}
|
||||
rs3.close();
|
||||
}
|
||||
JDBCsetUp.executeUpdate("UPDATE server_info SET last_update=" + System.currentTimeMillis() + " WHERE id=" + JdbcConfig.SERVER_ID.get());
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '1',last_server=" + JdbcConfig.SERVER_ID.get() + " WHERE uuid='" + player_uuid + "'");
|
||||
|
||||
if (rs2.next()) {
|
||||
// Restore basic attributes
|
||||
serverPlayer.setHealth(rs2.getInt("health"));
|
||||
serverPlayer.getFoodData().setFoodLevel(rs2.getInt("food_level"));
|
||||
|
||||
setXpForPlayer(serverPlayer, rs2.getInt("xp"));
|
||||
serverPlayer.setScore(rs2.getInt("score"));
|
||||
|
||||
// Restore left-hand item
|
||||
String leftHandEncoded = rs2.getString("left_hand");
|
||||
serverPlayer.setItemInHand(InteractionHand.OFF_HAND,
|
||||
deserializeAndCreatePlaceholderIfNeeded(leftHandEncoded));
|
||||
|
||||
// Restore cursor item
|
||||
String cursorsEncoded = rs2.getString("cursors");
|
||||
serverPlayer.containerMenu.setCarried(
|
||||
deserializeAndCreatePlaceholderIfNeeded(cursorsEncoded));
|
||||
|
||||
// Restore armor
|
||||
String armor_data = rs2.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(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore inventory
|
||||
Map<Integer, String> inventory = LocalJsonUtil.StringToEntryMap(rs2.getString("inventory"));
|
||||
for (Map.Entry<Integer, String> entry : inventory.entrySet()) {
|
||||
serverPlayer.getInventory().setItem(entry.getKey(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
}
|
||||
|
||||
// Restore Ender Chest
|
||||
Map<Integer, String> ender_chest = LocalJsonUtil.StringToEntryMap(rs2.getString("enderchest"));
|
||||
for (Map.Entry<Integer, String> entry : ender_chest.entrySet()) {
|
||||
serverPlayer.getEnderChestInventory().setItem(entry.getKey(), deserializeAndCreatePlaceholderIfNeeded(entry.getValue()));
|
||||
}
|
||||
|
||||
// Restore Effects
|
||||
String effectData = rs2.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(deserializeString(entry.getValue()));
|
||||
MobEffectInstance mobEffectInstance = MobEffectInstance.load(effectTag);
|
||||
if (mobEffectInstance != null) {
|
||||
serverPlayer.addEffect(mobEffectInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverPlayer.addTag("player_synced");
|
||||
|
||||
PlayerSync.LOGGER.info("Sync data for player {} completed.", player_uuid);
|
||||
|
||||
rs2.close();
|
||||
qr2.close();
|
||||
rs1.close();
|
||||
qr1.close();
|
||||
|
||||
PlayerSync.LOGGER.info("Sync data for player {} completed.", player_uuid);
|
||||
syncNotCompletedPlayer.remove(player_uuid);
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Internal Exception detected!", e);
|
||||
syncNotCompletedPlayer.remove(player_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
serverPlayer.addTag("player_synced");
|
||||
|
||||
rs2.close();
|
||||
@SubscribeEvent
|
||||
public static void onPlayerConnect(PlayerNegotiationEvent event) {
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
doPlayerConnect(event);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
|
@ -430,16 +556,23 @@ public class VanillaSync {
|
|||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) throws SQLException {
|
||||
// Mod support
|
||||
ModsSupport modsSupport = new ModsSupport();
|
||||
modsSupport.onPlayerLeave(event.getEntity());
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
doPlayerLogout(event);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
String player_uuid = event.getEntity().getUUID().toString();
|
||||
if (deadPlayerWhileLogging.contains(player_uuid)) {
|
||||
PlayerSync.LOGGER.warn("A dead or dying player was kicked,which uuid is:" + player_uuid);
|
||||
JDBCsetUp.executeUpdate("UPDATE player_data SET online= '0' WHERE uuid='" + player_uuid + "'");
|
||||
deadPlayerWhileLogging.remove(player_uuid);
|
||||
} else {
|
||||
// Mod support
|
||||
ModsSupport modsSupport = new ModsSupport();
|
||||
modsSupport.onPlayerLeave(event.getEntity());
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
doPlayerLogout(event);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get the NBT string to be saved
|
||||
|
|
@ -599,12 +732,15 @@ public class VanillaSync {
|
|||
// New fields for auto-save
|
||||
private static int autoSaveTickCounter = 0;
|
||||
private static final int AUTO_SAVE_INTERVAL_TICKS = 1200; // Every Minute
|
||||
private static int autoCleanCuriosCacheTickCounter = 0;
|
||||
private static final int AUTO_CLEAN_CURIOS_CACHE_INTERVAL_TICKS = 36000; // Every 30 min
|
||||
|
||||
//AutoSave
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(ServerTickEvent.Post event) {
|
||||
// Run at the end phase to avoid interfering with game logic
|
||||
autoSaveTickCounter++;
|
||||
autoCleanCuriosCacheTickCounter++;
|
||||
if (autoSaveTickCounter >= AUTO_SAVE_INTERVAL_TICKS) {
|
||||
autoSaveTickCounter = 0;
|
||||
// Retrieve the current server instance
|
||||
|
|
@ -629,8 +765,18 @@ public class VanillaSync {
|
|||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (autoCleanCuriosCacheTickCounter >= AUTO_CLEAN_CURIOS_CACHE_INTERVAL_TICKS) {
|
||||
autoCleanCuriosCacheTickCounter = 0;
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
CuriosCache.RemoveExpiredCuriosCache();
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("An error occurred while cleaning curios cache:" + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void setXpForPlayer(ServerPlayer serverPlayer, int databaseXp) {
|
||||
|
|
@ -680,4 +826,12 @@ public class VanillaSync {
|
|||
|
||||
return totalXp;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
//Don't know what will happen if a fake player is killed,need more test.
|
||||
public static void onPlayerDeath(LivingDeathEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player && !deadPlayerWhileLogging.contains(event.getEntity().getUUID().toString())) {
|
||||
CuriosCache.tryStoreCuriosToCache(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
119
src/main/java/vip/fubuki/playersync/sync/addons/CuriosCache.java
Normal file
119
src/main/java/vip/fubuki/playersync/sync/addons/CuriosCache.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package vip.fubuki.playersync.sync.addons;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
|
||||
import net.neoforged.fml.ModList;
|
||||
import top.theillusivec4.curios.api.CuriosApi;
|
||||
import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler;
|
||||
import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler;
|
||||
import vip.fubuki.playersync.PlayerSync;
|
||||
import vip.fubuki.playersync.sync.VanillaSync;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CuriosCache {
|
||||
private static final long CACHE_EXPIRY_MS = 3600000;
|
||||
public static final ConcurrentHashMap<UUID, CuriosCacheEntry> curiosCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static class CuriosCacheEntry {
|
||||
final long timeStamp;
|
||||
final String serializedData;
|
||||
|
||||
CuriosCacheEntry(String data) {
|
||||
this.timeStamp = System.currentTimeMillis();
|
||||
this.serializedData = data;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return System.currentTimeMillis() - timeStamp > CACHE_EXPIRY_MS;
|
||||
}
|
||||
}
|
||||
|
||||
//If player logged out by "Title Screen" button,you will not be able to get the handlerOpt,and it will make the curios inventory sync failed.
|
||||
//Create a method to store temporary curios data when player is dead.
|
||||
//Then check player status in the logged out event,and take a normal sync if player is alive.
|
||||
//If player is dead or dying,the cache will be used to prevent the empty data from the failure of getting handlerOpt.
|
||||
public static void tryStoreCuriosToCache(net.minecraft.world.entity.player.Player player) {
|
||||
if (!ModList.get().isLoaded("curios") || !CuriosCache.isKeepInventoryActive(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<ICuriosItemHandler> handlerOpt = CuriosApi.getCuriosInventory(player);
|
||||
if (handlerOpt.isEmpty()) {
|
||||
PlayerSync.LOGGER.error("Obtain the curios api failed,cannot create the cache.");
|
||||
return;
|
||||
}
|
||||
|
||||
ICuriosItemHandler handler = handlerOpt.get();
|
||||
String serializedData = serializeCuriosInventory(handler);
|
||||
|
||||
if (serializedData.startsWith("{}")) {
|
||||
PlayerSync.LOGGER.debug("No curios data found,skipping the step of creating cache");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerUuid = player.getUUID();
|
||||
curiosCache.put(playerUuid, new CuriosCacheEntry(serializedData));
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("An error occurred while creating curios cache:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String serializeCuriosInventory(ICuriosItemHandler handler) {
|
||||
Map<String, String> flatMap = new HashMap<>();
|
||||
try {
|
||||
handler.getCurios().forEach((slotType, stacksHandler) -> {
|
||||
IDynamicStackHandler dynStacks = stacksHandler.getStacks();
|
||||
for (int i = 0; i < dynStacks.getSlots(); i++) {
|
||||
ItemStack stack = dynStacks.getStackInSlot(i);
|
||||
if (!stack.isEmpty()) {
|
||||
String serialized = VanillaSync.serialize(VanillaSync.serializeNBT(stack).toString());
|
||||
flatMap.put(slotType + ":" + i, serialized);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
PlayerSync.LOGGER.error("Failed to serialize curios data:" + e.getMessage());
|
||||
}
|
||||
return flatMap.isEmpty() ? "{}" : flatMap.toString();
|
||||
}
|
||||
|
||||
public static boolean isKeepInventoryActive(Player player) {
|
||||
MinecraftServer server = player.getServer();
|
||||
if (server == null) {
|
||||
PlayerSync.LOGGER.error("Trying to get the gamerule(KeepInventory),but server is null");
|
||||
return false;
|
||||
}
|
||||
return server.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY);
|
||||
}
|
||||
|
||||
public static void RemoveExpiredCuriosCache() {
|
||||
long startMs = System.currentTimeMillis();
|
||||
int cacheSize = curiosCache.size();
|
||||
|
||||
if (cacheSize == 0) {
|
||||
PlayerSync.LOGGER.debug("No curios caches,skipping cleaning");
|
||||
return;
|
||||
}
|
||||
|
||||
int removed = 0;
|
||||
Iterator<Map.Entry<UUID, CuriosCacheEntry>> iterator = curiosCache.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
if (iterator.next().getValue().isExpired()) {
|
||||
iterator.remove();
|
||||
removed ++;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
PlayerSync.LOGGER.info("Cleaned {} curios cache(s),{} left,took {} Ms",
|
||||
removed, curiosCache.size(), System.currentTimeMillis() - startMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package vip.fubuki.playersync.sync;
|
||||
package vip.fubuki.playersync.sync.addons;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
|
@ -12,6 +12,7 @@ import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler;
|
|||
import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler;
|
||||
import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler;
|
||||
import vip.fubuki.playersync.PlayerSync;
|
||||
import vip.fubuki.playersync.sync.VanillaSync;
|
||||
import vip.fubuki.playersync.util.JDBCsetUp;
|
||||
import vip.fubuki.playersync.util.LocalJsonUtil;
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ public class ModsSupport {
|
|||
CompoundTag backpackNbt = NbtUtils.snbtToStructure(nbtString);
|
||||
// Update BackpackStorage with the retrieved NBT
|
||||
net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setBackpackContents(contentsUuid, backpackNbt);
|
||||
net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setDirty();
|
||||
PlayerSync.LOGGER.info("Restored backpack data for UUID " + contentsUuid);
|
||||
}
|
||||
rsBackpack.close();
|
||||
|
|
@ -24,9 +24,6 @@ public class ChatSyncClient {
|
|||
private static final int RECONNECT_DELAY = 5000;
|
||||
private static final int MAX_RECONNECT_ATTEMPTS = 10;
|
||||
|
||||
private static volatile long lastHeartbeat = System.currentTimeMillis();
|
||||
private static final long HEARTBEAT_INTERVAL = 15000;
|
||||
|
||||
public void run() {
|
||||
int reconnectAttempts = 0;
|
||||
|
||||
|
|
@ -49,33 +46,25 @@ public class ChatSyncClient {
|
|||
15000
|
||||
);
|
||||
|
||||
clientSocket.setSoTimeout(30000);
|
||||
clientSocket.setSoTimeout(0);
|
||||
|
||||
out = new PrintWriter(new BufferedWriter(
|
||||
new OutputStreamWriter(clientSocket.getOutputStream())), true);
|
||||
|
||||
PlayerSync.LOGGER.info("Successfully connected to chat server");
|
||||
reconnectAttempts = 0;
|
||||
lastHeartbeat = System.currentTimeMillis();
|
||||
|
||||
startHeartbeatMonitor();
|
||||
|
||||
BufferedReader in = new BufferedReader(
|
||||
new InputStreamReader(clientSocket.getInputStream()));
|
||||
|
||||
String serverMessage;
|
||||
while (running && (serverMessage = in.readLine()) != null) {
|
||||
lastHeartbeat = System.currentTimeMillis();
|
||||
|
||||
if ("<heartbeat>".equals(serverMessage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage);
|
||||
Component textComponents = Component.nullToEmpty(serverMessage);
|
||||
if(playerList != null){
|
||||
playerList.getServer().execute(() ->
|
||||
playerList.broadcastSystemMessage(textComponents, false));
|
||||
}else {
|
||||
PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,32 +94,6 @@ public class ChatSyncClient {
|
|||
}
|
||||
}
|
||||
|
||||
private void startHeartbeatMonitor() {
|
||||
Thread heartbeatThread = new Thread(() -> {
|
||||
while (running && clientSocket != null && !clientSocket.isClosed()) {
|
||||
try {
|
||||
Thread.sleep(10000); // 每10秒检查一次
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastHeartbeat > HEARTBEAT_INTERVAL) {
|
||||
PlayerSync.LOGGER.warn("No heartbeat for {}ms, sending test message",
|
||||
now - lastHeartbeat);
|
||||
|
||||
// 发送测试消息检查连接
|
||||
if (out != null) {
|
||||
out.println("<heartbeat>");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, "ChatSync-Heartbeat");
|
||||
heartbeatThread.setDaemon(true);
|
||||
heartbeatThread.start();
|
||||
}
|
||||
|
||||
private void closeConnection() {
|
||||
try {
|
||||
if (out != null) {
|
||||
|
|
|
|||
|
|
@ -26,12 +26,10 @@ public class ChatSyncServer {
|
|||
serverSocket.setReuseAddress(true);
|
||||
PlayerSync.LOGGER.info("Chat server started successfully on port {}", JdbcConfig.CHAT_SERVER_PORT.get());
|
||||
|
||||
startHeartbeatBroadcast();
|
||||
|
||||
while (running && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
Socket newSocket = serverSocket.accept();
|
||||
newSocket.setSoTimeout(30000);
|
||||
newSocket.setSoTimeout(0);
|
||||
SocketList.add(newSocket);
|
||||
executorService.submit(() -> handleClient(newSocket));
|
||||
PlayerSync.LOGGER.info("New client connected, total clients: {}", SocketList.size());
|
||||
|
|
@ -54,7 +52,6 @@ public class ChatSyncServer {
|
|||
|
||||
String message;
|
||||
while (running && (message = reader.readLine()) != null) {
|
||||
PlayerSync.LOGGER.info("Received message from {}: {}", clientInfo, message);
|
||||
broadcastMessage(socket, message);
|
||||
}
|
||||
|
||||
|
|
@ -96,47 +93,6 @@ public class ChatSyncServer {
|
|||
}
|
||||
}
|
||||
|
||||
private void startHeartbeatBroadcast() {
|
||||
Thread heartbeatThread = new Thread(() -> {
|
||||
while (running) {
|
||||
try {
|
||||
Thread.sleep(20000);
|
||||
broadcastHeartbeat();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, "ChatSync-Server-Heartbeat");
|
||||
heartbeatThread.setDaemon(true);
|
||||
heartbeatThread.start();
|
||||
}
|
||||
|
||||
private void broadcastHeartbeat() {
|
||||
Iterator<Socket> iterator = SocketList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Socket socket = iterator.next();
|
||||
if (!socket.isClosed()) {
|
||||
try {
|
||||
PrintWriter writer = new PrintWriter(
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(socket.getOutputStream())), true);
|
||||
writer.println("<heartbeat>");
|
||||
} catch (IOException e) {
|
||||
PlayerSync.LOGGER.warn("Failed to send heartbeat to client, removing: {}", e.getMessage());
|
||||
iterator.remove();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
running = false;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package vip.fubuki.playersync.util;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
import vip.fubuki.playersync.config.JdbcConfig;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
public class JDBCsetUp {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
|
@ -22,14 +20,14 @@ public class JDBCsetUp {
|
|||
String dbName = JdbcConfig.DATABASE_NAME.get();
|
||||
// Build the base URL
|
||||
String url = "jdbc:mysql://" + JdbcConfig.HOST.get() + ":" + JdbcConfig.PORT.get();
|
||||
if (selectDatabase && dbName != null && !dbName.isEmpty()) {
|
||||
if (selectDatabase && !dbName.isEmpty()) {
|
||||
url += "/" + dbName;
|
||||
}
|
||||
url += "?useUnicode=true&characterEncoding=utf-8&useSSL=" + JdbcConfig.USE_SSL.get()
|
||||
+ "&serverTimezone=UTC&allowPublicKeyRetrieval=true";
|
||||
Connection conn = DriverManager.getConnection(url, JdbcConfig.USERNAME.get(), JdbcConfig.PASSWORD.get());
|
||||
// Ensure that the connection uses the desired database by explicitly issuing "USE dbName"
|
||||
if (selectDatabase && dbName != null && !dbName.isEmpty()) {
|
||||
if (selectDatabase && !dbName.isEmpty()) {
|
||||
try (Statement st = conn.createStatement()) {
|
||||
st.execute("USE " + dbName);
|
||||
}
|
||||
|
|
@ -45,18 +43,20 @@ public class JDBCsetUp {
|
|||
/**
|
||||
* Executes a query using a connection that includes the database.
|
||||
*/
|
||||
public static QueryResult executeQuery(String sql) throws SQLException {
|
||||
public static QueryResult executeQuery(String sqlFormatString, Object... args) throws SQLException {
|
||||
String sql = String.format(sqlFormatString, args);
|
||||
LOGGER.trace(sql);
|
||||
Connection connection = getConnection(); // With database selected (and "USE" already run)
|
||||
PreparedStatement queryStatement = connection.prepareStatement(sql);
|
||||
ResultSet resultSet = queryStatement.executeQuery();
|
||||
return new QueryResult(connection, resultSet);
|
||||
return new QueryResult(connection, queryStatement, resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an update using a connection that includes the database.
|
||||
* Executes an update using a connection with or without the database within the JDBC URL
|
||||
*/
|
||||
public static void executeUpdate(String sql) throws SQLException {
|
||||
private static void executeUpdate(boolean selectDatabase, String sqlFormatString, Object... args) throws SQLException {
|
||||
String sql = String.format(sqlFormatString, args);
|
||||
LOGGER.trace(sql);
|
||||
try (Connection connection = getConnection()) { // With database selected
|
||||
try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
|
||||
|
|
@ -65,6 +65,13 @@ public class JDBCsetUp {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an update using a connection that includes the database in the JDBC URL
|
||||
*/
|
||||
public static void executeUpdate(String sqlFormatString, Object... args) throws SQLException {
|
||||
executeUpdate(true, sqlFormatString, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an update using a connection that does NOT include a default database.
|
||||
* This method is used for commands like "CREATE DATABASE IF NOT EXISTS ..."
|
||||
|
|
@ -92,6 +99,32 @@ public class JDBCsetUp {
|
|||
}
|
||||
}
|
||||
|
||||
public record QueryResult(Connection connection, ResultSet resultSet) {
|
||||
public record QueryResult(Connection connection,PreparedStatement preparedStatement, ResultSet resultSet) implements AutoCloseable {
|
||||
@Override
|
||||
public void close() {
|
||||
if (resultSet != null) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Error closing ResultSet", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (preparedStatement != null) {
|
||||
try {
|
||||
preparedStatement.close();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Error closing PreparedStatement", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Error closing Connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.",
|
||||
"playersync.item_placeholder_title": "Item Voucher",
|
||||
"playersync.already_online": "You can't join more than one synchronization server at the same time."
|
||||
"playersync.already_online": "You can't join more than one synchronization server at the same time.",
|
||||
"playersync.sqlexception": "SqlException detected!Connection lost,please contact with your admin.",
|
||||
"playersync.wrong_entity_status": "An error occurred while creating playerEntity in the world,please login again."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"playersync.item_placeholder_description": "物品在此服务器未知。这可能是一个模组物品,或是不同版本的原版物品。\n这张券将会在加入可识别此物品的服务器后自动替换为对应物品。",
|
||||
"playersync.item_placeholder_title": "物品券",
|
||||
"playersync.already_online": "你不能同时加入多个同步的服务器。"
|
||||
"playersync.already_online": "你不能同时加入多个同步的服务器。",
|
||||
"playersync.sqlexception": "检测到Sql异常!连接已中断,请联系管理员",
|
||||
"playersync.wrong_entity_status": "在世界中尝试创建玩家实体时发生了错误,请尝试重新进入"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user