diff --git a/api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java b/api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java new file mode 100644 index 00000000..ab1d6cd9 --- /dev/null +++ b/api/src/main/java/me/william278/husksync/api/events/SyncCompleteEvent.java @@ -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; + } +} diff --git a/api/src/main/java/me/william278/husksync/api/events/SyncEvent.java b/api/src/main/java/me/william278/husksync/api/events/SyncEvent.java new file mode 100644 index 00000000..f5c4a395 --- /dev/null +++ b/api/src/main/java/me/william278/husksync/api/events/SyncEvent.java @@ -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; + } +} diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 6df6f69c..756719a3 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -1,9 +1,10 @@ dependencies { compileOnly project(':common') + compileOnly project(':api') implementation project(path: ':common', configuration: 'shadow') + compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-bukkit:2.2.1' - implementation 'redis.clients:jedis:3.7.0' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' compileOnly 'net.craftersland.data:bridge:3.36.3' @@ -11,7 +12,6 @@ dependencies { } shadowJar { - relocate 'redis.clients', 'me.William278.husksync.libraries.jedis' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' } diff --git a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java index 54c4e825..eded38b6 100644 --- a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java +++ b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java @@ -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(); diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/PlayerSetter.java b/bukkit/src/main/java/me/william278/husksync/bukkit/PlayerSetter.java index 0e768b1f..c83d988a 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/PlayerSetter.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/PlayerSetter.java @@ -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); } diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java index 01507e6f..78d01f23 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java @@ -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(); diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/EventListener.java b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/EventListener.java index 345f25ae..c07b312e 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/EventListener.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/EventListener.java @@ -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 diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/migrator/MPDBDeserializer.java b/bukkit/src/main/java/me/william278/husksync/bukkit/migrator/MPDBDeserializer.java index a028a4f5..45d3d87e 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/migrator/MPDBDeserializer.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/migrator/MPDBDeserializer.java @@ -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); diff --git a/bungeecord/build.gradle b/bungeecord/build.gradle index 209e9e9c..0b09ebf1 100644 --- a/bungeecord/build.gradle +++ b/bungeecord/build.gradle @@ -2,7 +2,8 @@ dependencies { compileOnly project(':common') 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 'de.themoep:minedown:1.7.1-SNAPSHOT' @@ -10,7 +11,6 @@ dependencies { } shadowJar { - relocate 'redis.clients', 'me.William278.husksync.libraries.jedis' relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' diff --git a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java index f269dfbc..13d18f00 100644 --- a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java +++ b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java @@ -13,6 +13,7 @@ import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; +import org.bstats.bungeecord.Metrics; import java.io.IOException; import java.sql.Connection; @@ -24,6 +25,8 @@ import java.util.logging.Level; public final class HuskSyncBungeeCord extends Plugin { + private static final int METRICS_ID = 13141; + private static HuskSyncBungeeCord instance; public static HuskSyncBungeeCord getInstance() { return instance; @@ -58,10 +61,10 @@ public final class HuskSyncBungeeCord extends Plugin { ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig())); // Load messages - ConfigManager.loadMessages(Settings.language); + ConfigManager.loadMessages(); // Load locales from messages - ConfigLoader.loadMessages(Objects.requireNonNull(ConfigManager.getMessages(Settings.language))); + ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages())); // Initialize the database database = switch (Settings.dataStorageType) { @@ -95,6 +98,13 @@ public final class HuskSyncBungeeCord extends Plugin { // Prepare the migrator for use if needed mpdbMigrator = new MPDBMigrator(); + // Initialize bStats metrics + try { + new Metrics(this, METRICS_ID); + } catch (Exception e) { + getLogger().info("Skipped metrics initialization"); + } + // Log to console getLogger().info("Enabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java index 4e038d9a..a391217d 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java @@ -97,8 +97,9 @@ public class HuskSyncCommand extends Command implements TabExecutor { } ConfigManager.loadConfig(); 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 try { diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java index d8248554..d12712de 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java @@ -9,6 +9,8 @@ import java.util.HashMap; public class ConfigLoader { public static void loadSettings(Configuration config) throws IllegalArgumentException { + Settings.language = config.getString("language", "en-gb"); + Settings.serverType = Settings.ServerType.BUNGEECORD; Settings.redisHost = config.getString("redis_settings.host", "localhost"); 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); } - public static void loadMessages(Configuration config) { + public static void loadMessageStrings(Configuration config) { final HashMap messages = new HashMap<>(); for (String messageId : config.getKeys()) { messages.put(messageId, config.getString(messageId)); diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java index 93f4d4ea..bf6371ea 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java @@ -1,6 +1,7 @@ package me.william278.husksync.bungeecord.config; import me.william278.husksync.HuskSyncBungeeCord; +import me.william278.husksync.Settings; import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; 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 { if (!plugin.getDataFolder().exists()) { if (plugin.getDataFolder().mkdir()) { 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()) { - 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"); } } catch (Exception e) { @@ -58,9 +59,9 @@ public class ConfigManager { } } - public static Configuration getMessages(String language) { + public static Configuration getMessages() { 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); } catch (IOException e) { plugin.getLogger().log(Level.CONFIG, "An IOException occurred fetching the messages file", e); diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java index 59290228..e8641e43 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java @@ -9,6 +9,7 @@ import me.william278.husksync.bungeecord.data.DataManager; import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisListener; 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.connection.ProxiedPlayer; @@ -70,7 +71,7 @@ public class BungeeRedisListener extends RedisListener { // Send synchronisation complete message ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID); 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) { log(Level.SEVERE, "Failed to serialize data when replying to a data request"); @@ -104,7 +105,7 @@ public class BungeeRedisListener extends RedisListener { .send(); // 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) { diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java index b5b86d0f..ab551aaa 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java @@ -54,8 +54,9 @@ public class MPDBMigrator { } } 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."); + return; } migratedDataSent = 0; @@ -144,7 +145,7 @@ public class MPDBMigrator { } } } 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 { plugin.getLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); } @@ -161,7 +162,7 @@ public class MPDBMigrator { for (MPDBPlayerData data : mpdbPlayerData) { if (data.playerUUID.equals(playerUUID)) { data.expLevel = resultSet.getInt("exp_lvl"); - data.expProgress = resultSet.getInt("exp"); + data.expProgress = resultSet.getFloat("exp"); data.totalExperience = resultSet.getInt("total_exp"); break; } @@ -169,7 +170,7 @@ public class MPDBMigrator { } } } 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 { 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. """.replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated)) .replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent))); + sourceDatabase.close(); // Close source database }); } diff --git a/bungeecord/src/main/resources/bungee-config.yml b/bungeecord/src/main/resources/bungee-config.yml index 1055e630..ea286ff7 100644 --- a/bungeecord/src/main/resources/bungee-config.yml +++ b/bungeecord/src/main/resources/bungee-config.yml @@ -1,3 +1,4 @@ +language: 'en-gb' redis_settings: host: 'localhost' port: 6379 @@ -16,4 +17,5 @@ data_storage_settings: minimum_idle: 10 maximum_lifetime: 1800000 keepalive_time: 0 - connection_timeout: 5000 \ No newline at end of file + connection_timeout: 5000 +config_file_version: 1.0 \ No newline at end of file diff --git a/common/src/main/resources/languages/en-gb.yml b/bungeecord/src/main/resources/languages/en-gb.yml similarity index 100% rename from common/src/main/resources/languages/en-gb.yml rename to bungeecord/src/main/resources/languages/en-gb.yml diff --git a/common/build.gradle b/common/build.gradle index 5951d822..5ce26ca2 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -24,7 +24,4 @@ shadowJar { // Exclude some unnecessary files exclude "**/module-info.class" exclude "module-info.class" - - // Relocations - relocate 'redis.clients', 'me.William278.husksync.libraries.jedis' } \ No newline at end of file diff --git a/common/src/main/java/me/william278/husksync/PlayerData.java b/common/src/main/java/me/william278/husksync/PlayerData.java index 51ab36b0..030169b5 100644 --- a/common/src/main/java/me/william278/husksync/PlayerData.java +++ b/common/src/main/java/me/william278/husksync/PlayerData.java @@ -140,7 +140,7 @@ public class PlayerData implements Serializable { */ public static PlayerData DEFAULT_PLAYER_DATA(UUID playerUUID) { PlayerData data = new PlayerData(playerUUID, "", "", 20, - 20, 0, 20, 10, 1, 0, + 20, 20, 20, 10, 1, 0, "", 0, 0, 0, "SURVIVAL", "", false, "", ""); data.useDefaultData = true; diff --git a/common/src/main/resources/bungee.yml b/common/src/main/resources/bungee.yml index a0966277..5c2b9e2d 100644 --- a/common/src/main/resources/bungee.yml +++ b/common/src/main/resources/bungee.yml @@ -2,7 +2,7 @@ name: HuskSync version: @version@ main: me.william278.husksync.HuskSyncBungeeCord author: William278 -description: 'A modern, cross-server player data synchronisation system' +description: 'A modern, cross-server player data synchronization system' libraries: - mysql:mysql-connector-java:8.0.25 - org.xerial:sqlite-jdbc:3.36.0.3 \ No newline at end of file diff --git a/common/src/main/resources/plugin.yml b/common/src/main/resources/plugin.yml index 30118be1..9ce27781 100644 --- a/common/src/main/resources/plugin.yml +++ b/common/src/main/resources/plugin.yml @@ -3,5 +3,5 @@ version: @version@ main: me.william278.husksync.HuskSyncBukkit api-version: 1.16 author: William278 -description: 'A modern, cross-server player data synchronisation system' +description: 'A modern, cross-server player data synchronization system' softdepend: [MysqlPlayerDataBridge] \ No newline at end of file diff --git a/plugin/build.gradle b/plugin/build.gradle index c4998acd..dd8cb259 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -1,10 +1,14 @@ dependencies { implementation project(path: ":common", configuration: 'shadow') + implementation project(path: ":api", configuration: 'shadow') implementation project(path: ":bukkit", configuration: 'shadow') implementation project(path: ":bungeecord", configuration: 'shadow') } shadowJar { + // Relocations + relocate 'redis.clients', 'me.William278.husksync.libraries.jedis' + destinationDirectory.set(file("$rootDir/target/")) archiveBaseName.set('HuskSync') archiveClassifier.set('') diff --git a/settings.gradle b/settings.gradle index f3ebae97..c0198879 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ pluginManagement { rootProject.name = 'HuskSync' include 'common' +include 'api' include 'bukkit' include 'bungeecord' include 'plugin' \ No newline at end of file