mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2026-01-06 15:41:56 +00:00
MPDB Migration Fixes, server shutdown synchronisation fixes and polish
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
package me.william278.husksync;
|
||||
|
||||
import me.william278.husksync.bukkit.PlayerSetter;
|
||||
import me.william278.husksync.bukkit.config.ConfigLoader;
|
||||
import me.william278.husksync.bukkit.data.BukkitDataCache;
|
||||
import me.william278.husksync.bukkit.listener.BukkitRedisListener;
|
||||
import me.william278.husksync.bukkit.listener.EventListener;
|
||||
import me.william278.husksync.bukkit.migrator.MPDBDeserializer;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
@@ -17,8 +20,9 @@ import java.util.logging.Level;
|
||||
|
||||
public final class HuskSyncBukkit extends JavaPlugin {
|
||||
|
||||
private static HuskSyncBukkit instance;
|
||||
private static final int METRICS_ID = 13140;
|
||||
|
||||
private static HuskSyncBukkit instance;
|
||||
public static HuskSyncBukkit getInstance() {
|
||||
return instance;
|
||||
}
|
||||
@@ -52,7 +56,8 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
serverUUID.toString(),
|
||||
Boolean.toString(isMySqlPlayerDataBridgeInstalled),
|
||||
Bukkit.getName()).send();
|
||||
Bukkit.getName())
|
||||
.send();
|
||||
attempts[0]++;
|
||||
if (attempts[0] == 10) {
|
||||
getInstance().getLogger().log(Level.WARNING, "Failed to complete handshake with the Proxy server; Please make sure your Proxy server is online and has HuskSync installed in its' /plugins/ folder. HuskSync will continue to try and establish a connection.");
|
||||
@@ -64,6 +69,7 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void closeRedisHandshake() {
|
||||
if (!handshakeCompleted) return;
|
||||
try {
|
||||
new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
@@ -114,12 +120,29 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
// Ensure redis is connected; establish a handshake
|
||||
establishRedisHandshake();
|
||||
|
||||
// Initialize bStats metrics
|
||||
try {
|
||||
new Metrics(this, METRICS_ID);
|
||||
} catch (Exception e) {
|
||||
getLogger().info("Skipped metrics initialization");
|
||||
}
|
||||
|
||||
// Log to console
|
||||
getLogger().info("Enabled HuskSync (" + getServer().getName() + ") v" + getDescription().getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Update player data for disconnecting players
|
||||
if (HuskSyncBukkit.handshakeCompleted && !HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled && Bukkit.getOnlinePlayers().size() > 0) {
|
||||
getLogger().info("Saving data for remaining online players...");
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
PlayerSetter.updatePlayerData(player);
|
||||
}
|
||||
getLogger().info("Data save complete!");
|
||||
}
|
||||
|
||||
|
||||
// Send termination handshake to proxy
|
||||
closeRedisHandshake();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package me.william278.husksync.bukkit;
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.api.events.SyncCompleteEvent;
|
||||
import me.william278.husksync.api.events.SyncEvent;
|
||||
import me.william278.husksync.bukkit.data.DataSerializer;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import org.bukkit.*;
|
||||
@@ -26,6 +28,60 @@ public class PlayerSetter {
|
||||
|
||||
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||
|
||||
/**
|
||||
* Returns the new serialized PlayerData for a player.
|
||||
*
|
||||
* @param player The {@link Player} to get the new serialized PlayerData for
|
||||
* @return The {@link PlayerData}, serialized as a {@link String}
|
||||
* @throws IOException If the serialization fails
|
||||
*/
|
||||
private static String getNewSerializedPlayerData(Player player) throws IOException {
|
||||
return RedisMessage.serialize(new PlayerData(player.getUniqueId(),
|
||||
DataSerializer.getSerializedInventoryContents(player.getInventory()),
|
||||
DataSerializer.getSerializedEnderChestContents(player),
|
||||
player.getHealth(),
|
||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue(),
|
||||
player.getHealthScale(),
|
||||
player.getFoodLevel(),
|
||||
player.getSaturation(),
|
||||
player.getExhaustion(),
|
||||
player.getInventory().getHeldItemSlot(),
|
||||
DataSerializer.getSerializedEffectData(player),
|
||||
player.getTotalExperience(),
|
||||
player.getLevel(),
|
||||
player.getExp(),
|
||||
player.getGameMode().toString(),
|
||||
DataSerializer.getSerializedStatisticData(player),
|
||||
player.isFlying(),
|
||||
DataSerializer.getSerializedAdvancements(player),
|
||||
DataSerializer.getSerializedLocation(player)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a {@link Player}'s data, sending it to the proxy
|
||||
* @param player {@link Player} to send data to proxy
|
||||
*/
|
||||
public static void updatePlayerData(Player player) {
|
||||
// Send a redis message with the player's last updated PlayerData version UUID and their new PlayerData
|
||||
try {
|
||||
final String serializedPlayerData = getNewSerializedPlayerData(player);
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
serializedPlayerData).send();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", e);
|
||||
}
|
||||
|
||||
// Clear player inventory and ender chest
|
||||
player.getInventory().clear();
|
||||
player.getEnderChest().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a {@link Player}'s data from the proxy
|
||||
* @param playerUUID The {@link UUID} of the {@link Player} to fetch PlayerData from
|
||||
* @throws IOException If the request Redis message data fails to serialize
|
||||
*/
|
||||
public static void requestPlayerData(UUID playerUUID) throws IOException {
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_REQUEST,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
@@ -35,26 +91,34 @@ public class PlayerSetter {
|
||||
/**
|
||||
* Set a player from their PlayerData, based on settings
|
||||
*
|
||||
* @param player The {@link Player} to set
|
||||
* @param data The {@link PlayerData} to assign to the player
|
||||
* @param player The {@link Player} to set
|
||||
* @param dataToSet The {@link PlayerData} to assign to the player
|
||||
*/
|
||||
public static void setPlayerFrom(Player player, PlayerData data) {
|
||||
// If the data is flagged as being default data, skip setting
|
||||
if (data.isUseDefaultData()) {
|
||||
HuskSyncBukkit.bukkitCache.removeAwaitingDataFetch(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear player
|
||||
player.getInventory().clear();
|
||||
player.getEnderChest().clear();
|
||||
player.setExp(0);
|
||||
player.setLevel(0);
|
||||
|
||||
HuskSyncBukkit.bukkitCache.removeAwaitingDataFetch(player.getUniqueId());
|
||||
|
||||
// Set the player's data from the PlayerData
|
||||
public static void setPlayerFrom(Player player, PlayerData dataToSet) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
// Handle the SyncEvent
|
||||
SyncEvent syncEvent = new SyncEvent(player, dataToSet);
|
||||
Bukkit.getPluginManager().callEvent(syncEvent);
|
||||
final PlayerData data = syncEvent.getData();
|
||||
if (syncEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the data is flagged as being default data, skip setting
|
||||
if (data.isUseDefaultData()) {
|
||||
HuskSyncBukkit.bukkitCache.removeAwaitingDataFetch(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear player
|
||||
player.getInventory().clear();
|
||||
player.getEnderChest().clear();
|
||||
player.setExp(0);
|
||||
player.setLevel(0);
|
||||
|
||||
HuskSyncBukkit.bukkitCache.removeAwaitingDataFetch(player.getUniqueId());
|
||||
|
||||
// Set the player's data from the PlayerData
|
||||
try {
|
||||
if (Settings.syncAdvancements) {
|
||||
setPlayerAdvancements(player, DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()), data);
|
||||
@@ -67,7 +131,7 @@ public class PlayerSetter {
|
||||
setPlayerEnderChest(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedEnderChest()));
|
||||
}
|
||||
if (Settings.syncHealth) {
|
||||
player.setHealthScale(data.getHealthScale() > 0 ? data.getHealthScale() : 0D);
|
||||
player.setHealthScale(data.getHealthScale() <= 0 ? data.getHealthScale() : 20D);
|
||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).setBaseValue(data.getMaxHealth());
|
||||
player.setHealth(data.getHealth());
|
||||
}
|
||||
@@ -93,6 +157,9 @@ public class PlayerSetter {
|
||||
player.setFlying(player.getAllowFlight() && data.isFlying());
|
||||
setPlayerLocation(player, DataSerializer.deserializePlayerLocationData(data.getSerializedLocation()));
|
||||
}
|
||||
|
||||
// Handle the SyncCompleteEvent
|
||||
Bukkit.getPluginManager().callEvent(new SyncCompleteEvent(player, data));
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to deserialize PlayerData", e);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class BukkitRedisListener extends RedisListener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the message for the player
|
||||
// Handle the incoming redis message; either for a specific player or the system
|
||||
if (message.getMessageTarget().targetPlayerUUID() == null) {
|
||||
switch (message.getMessageType()) {
|
||||
case REQUEST_DATA_ON_JOIN -> {
|
||||
@@ -81,18 +81,20 @@ public class BukkitRedisListener extends RedisListener {
|
||||
case DECODE_MPDB_DATA -> {
|
||||
UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||
String encodedData = message.getMessageDataElements()[1];
|
||||
if (serverUUID.equals(HuskSyncBukkit.serverUUID)) {
|
||||
try {
|
||||
MPDBPlayerData data = (MPDBPlayerData) RedisMessage.deserialize(encodedData);
|
||||
new RedisMessage(RedisMessage.MessageType.DECODED_MPDB_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
RedisMessage.serialize(MPDBDeserializer.convertMPDBData(data)),
|
||||
data.playerName)
|
||||
.send();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
log(Level.SEVERE, "Failed to serialize encoded MPDB data");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
if (serverUUID.equals(HuskSyncBukkit.serverUUID)) {
|
||||
try {
|
||||
MPDBPlayerData data = (MPDBPlayerData) RedisMessage.deserialize(encodedData);
|
||||
new RedisMessage(RedisMessage.MessageType.DECODED_MPDB_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
RedisMessage.serialize(MPDBDeserializer.convertMPDBData(data)),
|
||||
data.playerName)
|
||||
.send();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
log(Level.SEVERE, "Failed to serialize encoded MPDB data");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
case RELOAD_CONFIG -> {
|
||||
plugin.reloadConfig();
|
||||
|
||||
@@ -28,35 +28,6 @@ public class EventListener implements Listener {
|
||||
|
||||
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||
|
||||
/**
|
||||
* Returns the new serialized PlayerData for a player.
|
||||
*
|
||||
* @param player The {@link Player} to get the new serialized PlayerData for
|
||||
* @return The {@link PlayerData}, serialized as a {@link String}
|
||||
* @throws IOException If the serialization fails
|
||||
*/
|
||||
private static String getNewSerializedPlayerData(Player player) throws IOException {
|
||||
return RedisMessage.serialize(new PlayerData(player.getUniqueId(),
|
||||
DataSerializer.getSerializedInventoryContents(player.getInventory()),
|
||||
DataSerializer.getSerializedEnderChestContents(player),
|
||||
player.getHealth(),
|
||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue(),
|
||||
player.getHealthScale(),
|
||||
player.getFoodLevel(),
|
||||
player.getSaturation(),
|
||||
player.getExhaustion(),
|
||||
player.getInventory().getHeldItemSlot(),
|
||||
DataSerializer.getSerializedEffectData(player),
|
||||
player.getTotalExperience(),
|
||||
player.getLevel(),
|
||||
player.getExp(),
|
||||
player.getGameMode().toString(),
|
||||
DataSerializer.getSerializedStatisticData(player),
|
||||
player.isFlying(),
|
||||
DataSerializer.getSerializedAdvancements(player),
|
||||
DataSerializer.getSerializedLocation(player)));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
// When a player leaves a Bukkit server
|
||||
@@ -70,19 +41,8 @@ public class EventListener implements Listener {
|
||||
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) return; // If the plugin has not been initialized correctly
|
||||
|
||||
// Send a redis message with the player's last updated PlayerData version UUID and their new PlayerData
|
||||
try {
|
||||
final String serializedPlayerData = getNewSerializedPlayerData(player);
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
|
||||
serializedPlayerData).send();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", e);
|
||||
}
|
||||
|
||||
// Clear player inventory and ender chest
|
||||
player.getInventory().clear();
|
||||
player.getEnderChest().clear();
|
||||
// Update the player's data
|
||||
PlayerSetter.updatePlayerData(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
||||
@@ -21,6 +21,7 @@ public class MPDBDeserializer {
|
||||
|
||||
// Instance of MySqlPlayerDataBridge
|
||||
private static PD mySqlPlayerDataBridge;
|
||||
|
||||
public static void setMySqlPlayerDataBridge() {
|
||||
mySqlPlayerDataBridge = (PD) Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge");
|
||||
}
|
||||
@@ -41,16 +42,36 @@ public class MPDBDeserializer {
|
||||
|
||||
// Convert the data
|
||||
try {
|
||||
// Set inventory
|
||||
// Set inventory contents
|
||||
Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER);
|
||||
PlayerSetter.setInventory(inventory, getItemStackArrayFromMPDBBase64String(mpdbPlayerData.inventoryData));
|
||||
if (!mpdbPlayerData.inventoryData.isEmpty() && !mpdbPlayerData.inventoryData.equalsIgnoreCase("none")) {
|
||||
PlayerSetter.setInventory(inventory, getItemStackArrayFromMPDBBase64String(mpdbPlayerData.inventoryData));
|
||||
}
|
||||
|
||||
// Set armor (if there is data; MPDB stores empty data with literally the word "none". Obviously.)
|
||||
int armorSlot = 36;
|
||||
if (!mpdbPlayerData.armorData.isEmpty() && !mpdbPlayerData.armorData.equalsIgnoreCase("none")) {
|
||||
ItemStack[] armorItems = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.armorData);
|
||||
for (ItemStack armorPiece : armorItems) {
|
||||
if (armorPiece != null) {
|
||||
inventory.setItem(armorSlot, armorPiece);
|
||||
}
|
||||
armorSlot++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Now apply the contents and clear the temporary inventory variable
|
||||
playerData.setSerializedInventory(DataSerializer.getSerializedInventoryContents(inventory));
|
||||
inventory.clear();
|
||||
|
||||
// Set ender chest
|
||||
playerData.setSerializedEnderChest(DataSerializer.itemStackArrayToBase64(
|
||||
getItemStackArrayFromMPDBBase64String(mpdbPlayerData.enderChestData)));
|
||||
// Set ender chest (again, if there is data)
|
||||
ItemStack[] enderChestData;
|
||||
if (!mpdbPlayerData.enderChestData.isEmpty() && !mpdbPlayerData.enderChestData.equalsIgnoreCase("none")) {
|
||||
enderChestData = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.enderChestData);
|
||||
} else {
|
||||
enderChestData = new ItemStack[0];
|
||||
}
|
||||
playerData.setSerializedEnderChest(DataSerializer.itemStackArrayToBase64(enderChestData));
|
||||
|
||||
// Set experience
|
||||
playerData.setExpLevel(mpdbPlayerData.expLevel);
|
||||
|
||||
Reference in New Issue
Block a user