9
0
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:
William
2021-10-25 15:32:19 +01:00
parent 049d92364f
commit 72d38fd443
23 changed files with 314 additions and 108 deletions

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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);