9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-19 14:59:21 +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

@@ -0,0 +1,39 @@
package me.william278.husksync.api.events;
import me.william278.husksync.PlayerData;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Represents an event that will be fired when a {@link Player} has finished
* being synchronised with the correct {@link PlayerData}.
*/
public class SyncCompleteEvent extends PlayerEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final PlayerData data;
public SyncCompleteEvent(Player player, PlayerData data) {
super(player);
this.data = data;
}
/**
* Returns the {@link PlayerData} which has just been set on the {@link Player}
* @return The {@link PlayerData} that has been set
*/
public PlayerData getData() {
return data;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,73 @@
package me.william278.husksync.api.events;
import me.william278.husksync.PlayerData;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Represents an event that will be fired before a {@link Player} is about
* to be synchronised with their {@link PlayerData}.
*/
public class SyncEvent extends PlayerEvent implements Cancellable {
private boolean cancelled;
private static final HandlerList HANDLER_LIST = new HandlerList();
private PlayerData data;
public SyncEvent(Player player, PlayerData data) {
super(player);
this.data = data;
}
/**
* Returns the {@link PlayerData} which has just been set on the {@link Player}
* @return The {@link PlayerData} that has been set
*/
public PlayerData getData() {
return data;
}
/**
* Sets the {@link PlayerData} to be synchronised to this player
* @param data The {@link PlayerData} to set to the player
*/
public void setData(PlayerData data) {
this.data = data;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
/**
* Gets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins
*
* @return true if this event is cancelled
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Sets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins.
*
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
}

View File

@@ -1,9 +1,10 @@
dependencies { dependencies {
compileOnly project(':common') compileOnly project(':common')
compileOnly project(':api')
implementation project(path: ':common', configuration: 'shadow') implementation project(path: ':common', configuration: 'shadow')
compileOnly 'redis.clients:jedis:3.7.0'
implementation 'org.bstats:bstats-bukkit:2.2.1' implementation 'org.bstats:bstats-bukkit:2.2.1'
implementation 'redis.clients:jedis:3.7.0'
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
compileOnly 'net.craftersland.data:bridge:3.36.3' compileOnly 'net.craftersland.data:bridge:3.36.3'
@@ -11,7 +12,6 @@ dependencies {
} }
shadowJar { shadowJar {
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'org.bstats', 'me.William278.husksync.libraries.plan'
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown'
} }

View File

@@ -1,12 +1,15 @@
package me.william278.husksync; package me.william278.husksync;
import me.william278.husksync.bukkit.PlayerSetter;
import me.william278.husksync.bukkit.config.ConfigLoader; import me.william278.husksync.bukkit.config.ConfigLoader;
import me.william278.husksync.bukkit.data.BukkitDataCache; import me.william278.husksync.bukkit.data.BukkitDataCache;
import me.william278.husksync.bukkit.listener.BukkitRedisListener; import me.william278.husksync.bukkit.listener.BukkitRedisListener;
import me.william278.husksync.bukkit.listener.EventListener; import me.william278.husksync.bukkit.listener.EventListener;
import me.william278.husksync.bukkit.migrator.MPDBDeserializer; import me.william278.husksync.bukkit.migrator.MPDBDeserializer;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
@@ -17,8 +20,9 @@ import java.util.logging.Level;
public final class HuskSyncBukkit extends JavaPlugin { 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() { public static HuskSyncBukkit getInstance() {
return instance; return instance;
} }
@@ -52,7 +56,8 @@ public final class HuskSyncBukkit extends JavaPlugin {
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null), new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
serverUUID.toString(), serverUUID.toString(),
Boolean.toString(isMySqlPlayerDataBridgeInstalled), Boolean.toString(isMySqlPlayerDataBridgeInstalled),
Bukkit.getName()).send(); Bukkit.getName())
.send();
attempts[0]++; attempts[0]++;
if (attempts[0] == 10) { 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."); 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() { private void closeRedisHandshake() {
if (!handshakeCompleted) return;
try { try {
new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE, new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE,
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null), new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
@@ -114,12 +120,29 @@ public final class HuskSyncBukkit extends JavaPlugin {
// Ensure redis is connected; establish a handshake // Ensure redis is connected; establish a handshake
establishRedisHandshake(); establishRedisHandshake();
// Initialize bStats metrics
try {
new Metrics(this, METRICS_ID);
} catch (Exception e) {
getLogger().info("Skipped metrics initialization");
}
// Log to console // Log to console
getLogger().info("Enabled HuskSync (" + getServer().getName() + ") v" + getDescription().getVersion()); getLogger().info("Enabled HuskSync (" + getServer().getName() + ") v" + getDescription().getVersion());
} }
@Override @Override
public void onDisable() { 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 // Send termination handshake to proxy
closeRedisHandshake(); closeRedisHandshake();

View File

@@ -3,6 +3,8 @@ package me.william278.husksync.bukkit;
import me.william278.husksync.HuskSyncBukkit; import me.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.PlayerData; import me.william278.husksync.PlayerData;
import me.william278.husksync.Settings; 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.bukkit.data.DataSerializer;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import org.bukkit.*; import org.bukkit.*;
@@ -26,6 +28,60 @@ public class PlayerSetter {
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance(); 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 { public static void requestPlayerData(UUID playerUUID) throws IOException {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_REQUEST, new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_REQUEST,
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null), new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
@@ -35,26 +91,34 @@ public class PlayerSetter {
/** /**
* Set a player from their PlayerData, based on settings * Set a player from their PlayerData, based on settings
* *
* @param player The {@link Player} to set * @param player The {@link Player} to set
* @param data The {@link PlayerData} to assign to the player * @param dataToSet The {@link PlayerData} to assign to the player
*/ */
public static void setPlayerFrom(Player player, PlayerData data) { public static void setPlayerFrom(Player player, PlayerData dataToSet) {
// 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
Bukkit.getScheduler().runTask(plugin, () -> { 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 { try {
if (Settings.syncAdvancements) { if (Settings.syncAdvancements) {
setPlayerAdvancements(player, DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()), data); setPlayerAdvancements(player, DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()), data);
@@ -67,7 +131,7 @@ public class PlayerSetter {
setPlayerEnderChest(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedEnderChest())); setPlayerEnderChest(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedEnderChest()));
} }
if (Settings.syncHealth) { 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()); Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).setBaseValue(data.getMaxHealth());
player.setHealth(data.getHealth()); player.setHealth(data.getHealth());
} }
@@ -93,6 +157,9 @@ public class PlayerSetter {
player.setFlying(player.getAllowFlight() && data.isFlying()); player.setFlying(player.getAllowFlight() && data.isFlying());
setPlayerLocation(player, DataSerializer.deserializePlayerLocationData(data.getSerializedLocation())); setPlayerLocation(player, DataSerializer.deserializePlayerLocationData(data.getSerializedLocation()));
} }
// Handle the SyncCompleteEvent
Bukkit.getPluginManager().callEvent(new SyncCompleteEvent(player, data));
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Failed to deserialize PlayerData", e); plugin.getLogger().log(Level.SEVERE, "Failed to deserialize PlayerData", e);
} }

View File

@@ -40,7 +40,7 @@ public class BukkitRedisListener extends RedisListener {
return; 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) { if (message.getMessageTarget().targetPlayerUUID() == null) {
switch (message.getMessageType()) { switch (message.getMessageType()) {
case REQUEST_DATA_ON_JOIN -> { case REQUEST_DATA_ON_JOIN -> {
@@ -81,18 +81,20 @@ public class BukkitRedisListener extends RedisListener {
case DECODE_MPDB_DATA -> { case DECODE_MPDB_DATA -> {
UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]); UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]);
String encodedData = message.getMessageDataElements()[1]; String encodedData = message.getMessageDataElements()[1];
if (serverUUID.equals(HuskSyncBukkit.serverUUID)) { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try { if (serverUUID.equals(HuskSyncBukkit.serverUUID)) {
MPDBPlayerData data = (MPDBPlayerData) RedisMessage.deserialize(encodedData); try {
new RedisMessage(RedisMessage.MessageType.DECODED_MPDB_DATA_SET, MPDBPlayerData data = (MPDBPlayerData) RedisMessage.deserialize(encodedData);
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null), new RedisMessage(RedisMessage.MessageType.DECODED_MPDB_DATA_SET,
RedisMessage.serialize(MPDBDeserializer.convertMPDBData(data)), new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
data.playerName) RedisMessage.serialize(MPDBDeserializer.convertMPDBData(data)),
.send(); data.playerName)
} catch (IOException | ClassNotFoundException e) { .send();
log(Level.SEVERE, "Failed to serialize encoded MPDB data"); } catch (IOException | ClassNotFoundException e) {
log(Level.SEVERE, "Failed to serialize encoded MPDB data");
}
} }
} });
} }
case RELOAD_CONFIG -> { case RELOAD_CONFIG -> {
plugin.reloadConfig(); plugin.reloadConfig();

View File

@@ -28,35 +28,6 @@ public class EventListener implements Listener {
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance(); 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 @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
// When a player leaves a Bukkit server // 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 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 // Update the player's data
try { PlayerSetter.updatePlayerData(player);
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();
} }
@EventHandler @EventHandler

View File

@@ -21,6 +21,7 @@ public class MPDBDeserializer {
// Instance of MySqlPlayerDataBridge // Instance of MySqlPlayerDataBridge
private static PD mySqlPlayerDataBridge; private static PD mySqlPlayerDataBridge;
public static void setMySqlPlayerDataBridge() { public static void setMySqlPlayerDataBridge() {
mySqlPlayerDataBridge = (PD) Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge"); mySqlPlayerDataBridge = (PD) Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge");
} }
@@ -41,16 +42,36 @@ public class MPDBDeserializer {
// Convert the data // Convert the data
try { try {
// Set inventory // Set inventory contents
Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER); 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)); playerData.setSerializedInventory(DataSerializer.getSerializedInventoryContents(inventory));
inventory.clear();
// Set ender chest // Set ender chest (again, if there is data)
playerData.setSerializedEnderChest(DataSerializer.itemStackArrayToBase64( ItemStack[] enderChestData;
getItemStackArrayFromMPDBBase64String(mpdbPlayerData.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 // Set experience
playerData.setExpLevel(mpdbPlayerData.expLevel); playerData.setExpLevel(mpdbPlayerData.expLevel);

View File

@@ -2,7 +2,8 @@ dependencies {
compileOnly project(':common') compileOnly project(':common')
implementation project(path: ':common', configuration: 'shadow') implementation project(path: ':common', configuration: 'shadow')
implementation 'redis.clients:jedis:3.7.0' compileOnly 'redis.clients:jedis:3.7.0'
implementation 'org.bstats:bstats-bungeecord:2.2.1'
implementation 'com.zaxxer:HikariCP:5.0.0' implementation 'com.zaxxer:HikariCP:5.0.0'
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
@@ -10,7 +11,6 @@ dependencies {
} }
shadowJar { shadowJar {
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'org.bstats', 'me.William278.husksync.libraries.plan'
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown'

View File

@@ -13,6 +13,7 @@ import me.william278.husksync.bungeecord.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection; import java.sql.Connection;
@@ -24,6 +25,8 @@ import java.util.logging.Level;
public final class HuskSyncBungeeCord extends Plugin { public final class HuskSyncBungeeCord extends Plugin {
private static final int METRICS_ID = 13141;
private static HuskSyncBungeeCord instance; private static HuskSyncBungeeCord instance;
public static HuskSyncBungeeCord getInstance() { public static HuskSyncBungeeCord getInstance() {
return instance; return instance;
@@ -58,10 +61,10 @@ public final class HuskSyncBungeeCord extends Plugin {
ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig())); ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig()));
// Load messages // Load messages
ConfigManager.loadMessages(Settings.language); ConfigManager.loadMessages();
// Load locales from messages // Load locales from messages
ConfigLoader.loadMessages(Objects.requireNonNull(ConfigManager.getMessages(Settings.language))); ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages()));
// Initialize the database // Initialize the database
database = switch (Settings.dataStorageType) { database = switch (Settings.dataStorageType) {
@@ -95,6 +98,13 @@ public final class HuskSyncBungeeCord extends Plugin {
// Prepare the migrator for use if needed // Prepare the migrator for use if needed
mpdbMigrator = new MPDBMigrator(); mpdbMigrator = new MPDBMigrator();
// Initialize bStats metrics
try {
new Metrics(this, METRICS_ID);
} catch (Exception e) {
getLogger().info("Skipped metrics initialization");
}
// Log to console // Log to console
getLogger().info("Enabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); getLogger().info("Enabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion());
} }

View File

@@ -97,8 +97,9 @@ public class HuskSyncCommand extends Command implements TabExecutor {
} }
ConfigManager.loadConfig(); ConfigManager.loadConfig();
ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig())); ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig()));
ConfigManager.loadMessages(Settings.language);
ConfigLoader.loadMessages(Objects.requireNonNull(ConfigManager.getMessages(Settings.language))); ConfigManager.loadMessages();
ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages()));
// Send reload request to all bukkit servers // Send reload request to all bukkit servers
try { try {

View File

@@ -9,6 +9,8 @@ import java.util.HashMap;
public class ConfigLoader { public class ConfigLoader {
public static void loadSettings(Configuration config) throws IllegalArgumentException { public static void loadSettings(Configuration config) throws IllegalArgumentException {
Settings.language = config.getString("language", "en-gb");
Settings.serverType = Settings.ServerType.BUNGEECORD; Settings.serverType = Settings.ServerType.BUNGEECORD;
Settings.redisHost = config.getString("redis_settings.host", "localhost"); Settings.redisHost = config.getString("redis_settings.host", "localhost");
Settings.redisPort = config.getInt("redis_settings.port", 6379); Settings.redisPort = config.getInt("redis_settings.port", 6379);
@@ -31,7 +33,7 @@ public class ConfigLoader {
Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000); Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000);
} }
public static void loadMessages(Configuration config) { public static void loadMessageStrings(Configuration config) {
final HashMap<String,String> messages = new HashMap<>(); final HashMap<String,String> messages = new HashMap<>();
for (String messageId : config.getKeys()) { for (String messageId : config.getKeys()) {
messages.put(messageId, config.getString(messageId)); messages.put(messageId, config.getString(messageId));

View File

@@ -1,6 +1,7 @@
package me.william278.husksync.bungeecord.config; package me.william278.husksync.bungeecord.config;
import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.Settings;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration; import net.md_5.bungee.config.YamlConfiguration;
@@ -31,16 +32,16 @@ public class ConfigManager {
} }
} }
public static void loadMessages(String language) { public static void loadMessages() {
try { try {
if (!plugin.getDataFolder().exists()) { if (!plugin.getDataFolder().exists()) {
if (plugin.getDataFolder().mkdir()) { if (plugin.getDataFolder().mkdir()) {
plugin.getLogger().info("Created HuskSync data folder"); plugin.getLogger().info("Created HuskSync data folder");
} }
} }
File messagesFile = new File(plugin.getDataFolder(), "messages_ " + language + ".yml"); File messagesFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml");
if (!messagesFile.exists()) { if (!messagesFile.exists()) {
Files.copy(plugin.getResourceAsStream("languages" + File.separator + language + ".yml"), messagesFile.toPath()); Files.copy(plugin.getResourceAsStream("languages/" + Settings.language + ".yml"), messagesFile.toPath());
plugin.getLogger().info("Created HuskSync messages file"); plugin.getLogger().info("Created HuskSync messages file");
} }
} catch (Exception e) { } catch (Exception e) {
@@ -58,9 +59,9 @@ public class ConfigManager {
} }
} }
public static Configuration getMessages(String language) { public static Configuration getMessages() {
try { try {
File configFile = new File(plugin.getDataFolder(), "messages-" + language + ".yml"); File configFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml");
return ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); return ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().log(Level.CONFIG, "An IOException occurred fetching the messages file", e); plugin.getLogger().log(Level.CONFIG, "An IOException occurred fetching the messages file", e);

View File

@@ -9,6 +9,7 @@ import me.william278.husksync.bungeecord.data.DataManager;
import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.bungeecord.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -70,7 +71,7 @@ public class BungeeRedisListener extends RedisListener {
// Send synchronisation complete message // Send synchronisation complete message
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID); ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
if (player.isConnected()) { if (player.isConnected()) {
player.sendMessage(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
} }
} catch (IOException e) { } catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request"); log(Level.SEVERE, "Failed to serialize data when replying to a data request");
@@ -104,7 +105,7 @@ public class BungeeRedisListener extends RedisListener {
.send(); .send();
// Send synchronisation complete message // Send synchronisation complete message
player.sendMessage(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@@ -54,8 +54,9 @@ public class MPDBMigrator {
} }
} }
if (synchronisedServersWithMpdb < 1) { if (synchronisedServersWithMpdb < 1) {
plugin.getLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server must be online and have both HuskSync and MySqlPlayerDataBridge installed. " + plugin.getLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " +
"Please start one Spigot server with HuskSync installed to begin migration."); "Please start one Spigot server with HuskSync installed to begin migration.");
return;
} }
migratedDataSent = 0; migratedDataSent = 0;
@@ -144,7 +145,7 @@ public class MPDBMigrator {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "An exception occurred getting ender chest", e); plugin.getLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e);
} finally { } finally {
plugin.getLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); plugin.getLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge");
} }
@@ -161,7 +162,7 @@ public class MPDBMigrator {
for (MPDBPlayerData data : mpdbPlayerData) { for (MPDBPlayerData data : mpdbPlayerData) {
if (data.playerUUID.equals(playerUUID)) { if (data.playerUUID.equals(playerUUID)) {
data.expLevel = resultSet.getInt("exp_lvl"); data.expLevel = resultSet.getInt("exp_lvl");
data.expProgress = resultSet.getInt("exp"); data.expProgress = resultSet.getFloat("exp");
data.totalExperience = resultSet.getInt("total_exp"); data.totalExperience = resultSet.getInt("total_exp");
break; break;
} }
@@ -169,7 +170,7 @@ public class MPDBMigrator {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "An exception occurred getting ender chest", e); plugin.getLogger().log(Level.SEVERE, "An exception occurred getting experience data", e);
} finally { } finally {
plugin.getLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); plugin.getLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge");
} }
@@ -229,6 +230,7 @@ public class MPDBMigrator {
the rest of the Spigot servers, then restart them. the rest of the Spigot servers, then restart them.
""".replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated)) """.replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated))
.replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent))); .replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent)));
sourceDatabase.close(); // Close source database
}); });
} }

View File

@@ -1,3 +1,4 @@
language: 'en-gb'
redis_settings: redis_settings:
host: 'localhost' host: 'localhost'
port: 6379 port: 6379
@@ -16,4 +17,5 @@ data_storage_settings:
minimum_idle: 10 minimum_idle: 10
maximum_lifetime: 1800000 maximum_lifetime: 1800000
keepalive_time: 0 keepalive_time: 0
connection_timeout: 5000 connection_timeout: 5000
config_file_version: 1.0

View File

@@ -24,7 +24,4 @@ shadowJar {
// Exclude some unnecessary files // Exclude some unnecessary files
exclude "**/module-info.class" exclude "**/module-info.class"
exclude "module-info.class" exclude "module-info.class"
// Relocations
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
} }

View File

@@ -140,7 +140,7 @@ public class PlayerData implements Serializable {
*/ */
public static PlayerData DEFAULT_PLAYER_DATA(UUID playerUUID) { public static PlayerData DEFAULT_PLAYER_DATA(UUID playerUUID) {
PlayerData data = new PlayerData(playerUUID, "", "", 20, PlayerData data = new PlayerData(playerUUID, "", "", 20,
20, 0, 20, 10, 1, 0, 20, 20, 20, 10, 1, 0,
"", 0, 0, 0, "SURVIVAL", "", 0, 0, 0, "SURVIVAL",
"", false, "", ""); "", false, "", "");
data.useDefaultData = true; data.useDefaultData = true;

View File

@@ -2,7 +2,7 @@ name: HuskSync
version: @version@ version: @version@
main: me.william278.husksync.HuskSyncBungeeCord main: me.william278.husksync.HuskSyncBungeeCord
author: William278 author: William278
description: 'A modern, cross-server player data synchronisation system' description: 'A modern, cross-server player data synchronization system'
libraries: libraries:
- mysql:mysql-connector-java:8.0.25 - mysql:mysql-connector-java:8.0.25
- org.xerial:sqlite-jdbc:3.36.0.3 - org.xerial:sqlite-jdbc:3.36.0.3

View File

@@ -3,5 +3,5 @@ version: @version@
main: me.william278.husksync.HuskSyncBukkit main: me.william278.husksync.HuskSyncBukkit
api-version: 1.16 api-version: 1.16
author: William278 author: William278
description: 'A modern, cross-server player data synchronisation system' description: 'A modern, cross-server player data synchronization system'
softdepend: [MysqlPlayerDataBridge] softdepend: [MysqlPlayerDataBridge]

View File

@@ -1,10 +1,14 @@
dependencies { dependencies {
implementation project(path: ":common", configuration: 'shadow') implementation project(path: ":common", configuration: 'shadow')
implementation project(path: ":api", configuration: 'shadow')
implementation project(path: ":bukkit", configuration: 'shadow') implementation project(path: ":bukkit", configuration: 'shadow')
implementation project(path: ":bungeecord", configuration: 'shadow') implementation project(path: ":bungeecord", configuration: 'shadow')
} }
shadowJar { shadowJar {
// Relocations
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
destinationDirectory.set(file("$rootDir/target/")) destinationDirectory.set(file("$rootDir/target/"))
archiveBaseName.set('HuskSync') archiveBaseName.set('HuskSync')
archiveClassifier.set('') archiveClassifier.set('')

View File

@@ -7,6 +7,7 @@ pluginManagement {
rootProject.name = 'HuskSync' rootProject.name = 'HuskSync'
include 'common' include 'common'
include 'api'
include 'bukkit' include 'bukkit'
include 'bungeecord' include 'bungeecord'
include 'plugin' include 'plugin'