diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitEventListener.java b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitEventListener.java index 95c3610e..8503d344 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitEventListener.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitEventListener.java @@ -51,8 +51,9 @@ public class BukkitEventListener implements Listener { // Mark the player as awaiting data fetch HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId()); - if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) + if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) { return; // If the data handshake has not been completed yet (or MySqlPlayerDataBridge is installed) + } // Send a redis message requesting the player data (if they need to) if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) { @@ -64,7 +65,7 @@ public class BukkitEventListener implements Listener { } }); } else { - // If the player's data wasn't set after 20 ticks, ensure it will be + // If the player's data wasn't set after the synchronization timeout retry delay ticks, ensure it will be Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> { if (player.isOnline()) { try { diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java index 562939ed..0dda1aaa 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java @@ -330,7 +330,7 @@ public class PlayerSetter { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { // Apply the advancements to the player - Iterator serverAdvancements = Bukkit.getServer().advancementIterator(); + final Iterator serverAdvancements = Bukkit.getServer().advancementIterator(); while (serverAdvancements.hasNext()) { // Iterate through all advancements boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update Advancement advancement = serverAdvancements.next(); 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 d088356a..32991e3d 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 @@ -60,6 +60,8 @@ public class ConfigLoader { Settings.hikariKeepAliveTime = config.getLong("data_storage_settings.hikari_pool_settings.keepalive_time", 0); Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000); + Settings.bounceBackSynchronisation = config.getBoolean("bounce_back_synchronization", true); + // Read cluster data Configuration section = config.getSection("clusters"); final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; 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 19796b0b..9da0e646 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 @@ -63,37 +63,33 @@ public class BungeeRedisListener extends RedisListener { } switch (message.getMessageType()) { - case PLAYER_DATA_REQUEST -> { + case PLAYER_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { // Get the UUID of the requesting player final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData()); - ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - try { - // Send the reply, serializing the message data - new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), - RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) - .send(); + try { + // Send the reply, serializing the message data + new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), + RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) + .send(); - // Send an update to all bukkit servers removing the player from the requester cache - new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), - RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) - .send(); + // Send an update to all bukkit servers removing the player from the requester cache + new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) + .send(); - // Send synchronisation complete message - ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID); - if (player != null) { - if (player.isConnected()) { - 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"); - e.printStackTrace(); + // Send synchronisation complete message + ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID); + if (player != null) { + player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); } - }); - } - case PLAYER_DATA_UPDATE -> { + } catch (IOException e) { + log(Level.SEVERE, "Failed to serialize data when replying to a data request"); + e.printStackTrace(); + } + }); + case PLAYER_DATA_UPDATE -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { // Deserialize the PlayerData received PlayerData playerData; final String serializedPlayerData = message.getMessageData(); @@ -114,10 +110,10 @@ public class BungeeRedisListener extends RedisListener { } // Reply with the player data if they are still online (switching server) - try { - ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID()); - if (player != null) { - if (player.isConnected()) { + if (Settings.bounceBackSynchronisation) { + try { + ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID()); + if (player != null) { new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), serializedPlayerData) @@ -126,12 +122,12 @@ public class BungeeRedisListener extends RedisListener { // Send synchronisation complete message player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); } + } catch (IOException e) { + log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); + e.printStackTrace(); } - } catch (IOException e) { - log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); - e.printStackTrace(); } - } + }); case CONNECTION_HANDSHAKE -> { // Reply to a Bukkit server's connection handshake to complete the process if (HuskSyncBungeeCord.isDisabling) return; // Return if the Proxy is disabling @@ -198,10 +194,9 @@ public class BungeeRedisListener extends RedisListener { HuskSyncBungeeCord.dataManager)); } } - case API_DATA_REQUEST -> { + case API_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); - try { final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId()); @@ -221,7 +216,7 @@ public class BungeeRedisListener extends RedisListener { } catch (IOException e) { plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); } - } + }); } } diff --git a/common/src/main/java/me/william278/husksync/PlayerData.java b/common/src/main/java/me/william278/husksync/PlayerData.java index 79817200..8a311ff7 100644 --- a/common/src/main/java/me/william278/husksync/PlayerData.java +++ b/common/src/main/java/me/william278/husksync/PlayerData.java @@ -1,6 +1,7 @@ package me.william278.husksync; import java.io.*; +import java.time.Instant; import java.util.UUID; /** @@ -18,6 +19,11 @@ public class PlayerData implements Serializable { */ private final UUID dataVersionUUID; + /** + * Epoch time identifying when the data was last updated or created + */ + private long timestamp; + /** * A special flag that will be {@code true} if the player is new to the network and should not have their data set when joining the Bukkit */ @@ -70,6 +76,7 @@ public class PlayerData implements Serializable { String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) { this.dataVersionUUID = UUID.randomUUID(); + this.timestamp = Instant.now().getEpochSecond(); this.playerUUID = playerUUID; this.serializedInventory = serializedInventory; this.serializedEnderChest = serializedEnderChest; @@ -109,16 +116,17 @@ public class PlayerData implements Serializable { * @param totalExperience Their total experience points ("Score") * @param expLevel Their exp level * @param expProgress Their exp progress to the next level - * @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc) + * @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc.) * @param serializedStatistics Their serialized statistics data (Displayed in Statistics menu in ESC menu) */ - public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest, + public PlayerData(UUID playerUUID, UUID dataVersionUUID, long timestamp, String serializedInventory, String serializedEnderChest, double health, double maxHealth, double healthScale, int hunger, float saturation, float saturationExhaustion, int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) { this.playerUUID = playerUUID; this.dataVersionUUID = dataVersionUUID; + this.timestamp = timestamp; this.serializedInventory = serializedInventory; this.serializedEnderChest = serializedEnderChest; this.health = health; @@ -172,6 +180,15 @@ public class PlayerData implements Serializable { return dataVersionUUID; } + /** + * Get the timestamp when this data was created or last updated + * + * @return time since epoch of last data update or creation + */ + public long getDataTimestamp() { + return timestamp; + } + /** * Returns the serialized player {@code ItemStack[]} inventory * @@ -341,6 +358,7 @@ public class PlayerData implements Serializable { */ public void setSerializedInventory(String serializedInventory) { this.serializedInventory = serializedInventory; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -350,6 +368,7 @@ public class PlayerData implements Serializable { */ public void setSerializedEnderChest(String serializedEnderChest) { this.serializedEnderChest = serializedEnderChest; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -359,6 +378,7 @@ public class PlayerData implements Serializable { */ public void setHealth(double health) { this.health = health; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -368,6 +388,7 @@ public class PlayerData implements Serializable { */ public void setMaxHealth(double maxHealth) { this.maxHealth = maxHealth; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -377,6 +398,7 @@ public class PlayerData implements Serializable { */ public void setHealthScale(double healthScale) { this.healthScale = healthScale; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -386,6 +408,7 @@ public class PlayerData implements Serializable { */ public void setHunger(int hunger) { this.hunger = hunger; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -395,6 +418,7 @@ public class PlayerData implements Serializable { */ public void setSaturation(float saturation) { this.saturation = saturation; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -404,6 +428,7 @@ public class PlayerData implements Serializable { */ public void setSaturationExhaustion(float saturationExhaustion) { this.saturationExhaustion = saturationExhaustion; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -413,6 +438,7 @@ public class PlayerData implements Serializable { */ public void setSelectedSlot(int selectedSlot) { this.selectedSlot = selectedSlot; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -422,6 +448,7 @@ public class PlayerData implements Serializable { */ public void setSerializedEffectData(String serializedEffectData) { this.serializedEffectData = serializedEffectData; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -431,6 +458,7 @@ public class PlayerData implements Serializable { */ public void setTotalExperience(int totalExperience) { this.totalExperience = totalExperience; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -440,6 +468,7 @@ public class PlayerData implements Serializable { */ public void setExpLevel(int expLevel) { this.expLevel = expLevel; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -449,6 +478,7 @@ public class PlayerData implements Serializable { */ public void setExpProgress(float expProgress) { this.expProgress = expProgress; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -458,6 +488,7 @@ public class PlayerData implements Serializable { */ public void setGameMode(String gameMode) { this.gameMode = gameMode; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -467,6 +498,7 @@ public class PlayerData implements Serializable { */ public void setSerializedStatistics(String serializedStatistics) { this.serializedStatistics = serializedStatistics; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -476,6 +508,7 @@ public class PlayerData implements Serializable { */ public void setFlying(boolean flying) { isFlying = flying; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -485,6 +518,7 @@ public class PlayerData implements Serializable { */ public void setSerializedAdvancements(String serializedAdvancements) { this.serializedAdvancements = serializedAdvancements; + this.timestamp = Instant.now().getEpochSecond(); } /** @@ -494,5 +528,6 @@ public class PlayerData implements Serializable { */ public void setSerializedLocation(String serializedLocation) { this.serializedLocation = serializedLocation; + this.timestamp = Instant.now().getEpochSecond(); } } diff --git a/common/src/main/java/me/william278/husksync/Settings.java b/common/src/main/java/me/william278/husksync/Settings.java index 4b9f35b1..5013fe16 100644 --- a/common/src/main/java/me/william278/husksync/Settings.java +++ b/common/src/main/java/me/william278/husksync/Settings.java @@ -36,6 +36,9 @@ public class Settings { // SQL settings public static DataStorageType dataStorageType; + // Bounce-back synchronisation (default) + public static boolean bounceBackSynchronisation; + // MySQL specific settings public static String mySQLHost; public static String mySQLDatabase; diff --git a/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java b/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java index c378b149..2bafbf03 100644 --- a/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java +++ b/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java @@ -184,7 +184,7 @@ public class DataManager { ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid")); - //final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp"); + final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp"); final String serializedInventory = resultSet.getString("inventory"); final String serializedEnderChest = resultSet.getString("ender_chest"); final double health = resultSet.getDouble("health"); @@ -204,10 +204,10 @@ public class DataManager { final String serializedLocationData = resultSet.getString("location"); final String serializedStatisticData = resultSet.getString("statistics"); - data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, - health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects, - totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying, - serializedAdvancementData, serializedLocationData)); + data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, dataSaveTimestamp.toInstant().getEpochSecond(), + serializedInventory, serializedEnderChest, health, maxHealth, healthScale, hunger, saturation, + saturationExhaustion, selectedSlot, serializedStatusEffects, totalExperience, expLevel, expProgress, + gameMode, serializedStatisticData, isFlying, serializedAdvancementData, serializedLocationData)); } else { data.put(cluster, PlayerData.DEFAULT_PLAYER_DATA(playerUUID)); } diff --git a/common/src/main/resources/proxy-config.yml b/common/src/main/resources/proxy-config.yml index 9f35fb7f..6d442d22 100644 --- a/common/src/main/resources/proxy-config.yml +++ b/common/src/main/resources/proxy-config.yml @@ -19,6 +19,7 @@ data_storage_settings: maximum_lifetime: 1800000 keepalive_time: 0 connection_timeout: 5000 +bounce_back_synchronization: true clusters: main: player_table: 'husksync_players' diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java index 9e98d716..e3d52dea 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java @@ -75,6 +75,8 @@ public class ConfigLoader { Settings.hikariKeepAliveTime = getConfigLong(config, 0, "data_storage_settings", "hikari_pool_settings", "keepalive_time"); Settings.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout"); + Settings.bounceBackSynchronisation = getConfigBoolean(config, true,"bounce_back_synchronization"); + // Read cluster data ConfigurationNode clusterSection = config.getNode("clusters"); final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java index d6c3b882..6434f336 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java @@ -62,33 +62,31 @@ public class VelocityRedisListener extends RedisListener { } switch (message.getMessageType()) { - case PLAYER_DATA_REQUEST -> { + case PLAYER_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { // Get the UUID of the requesting player final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData()); - plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { - try { - // Send the reply, serializing the message data - new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), - RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) - .send(); + try { + // Send the reply, serializing the message data + new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), + RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) + .send(); - // Send an update to all bukkit servers removing the player from the requester cache - new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), - RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) - .send(); + // Send an update to all bukkit servers removing the player from the requester cache + new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) + .send(); - // Send synchronisation complete message - Optional player = plugin.getProxyServer().getPlayer(requestingPlayerUUID); - player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent())); - } catch (IOException e) { - log(Level.SEVERE, "Failed to serialize data when replying to a data request"); - e.printStackTrace(); - } - }).schedule(); - } - case PLAYER_DATA_UPDATE -> { + // Send synchronisation complete message + Optional player = plugin.getProxyServer().getPlayer(requestingPlayerUUID); + player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent())); + } catch (IOException e) { + log(Level.SEVERE, "Failed to serialize data when replying to a data request"); + e.printStackTrace(); + } + }).schedule(); + case PLAYER_DATA_UPDATE -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { // Deserialize the PlayerData received PlayerData playerData; final String serializedPlayerData = message.getMessageData(); @@ -109,23 +107,24 @@ public class VelocityRedisListener extends RedisListener { } // Reply with the player data if they are still online (switching server) - Optional updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID()); - updatingPlayer.ifPresent(player -> { - try { - new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), - RedisMessage.serialize(playerData)) - .send(); - - // Send synchronisation complete message - player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); - } catch (IOException e) { - log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); - e.printStackTrace(); - } - }); - - } + if (Settings.bounceBackSynchronisation) { + Optional updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID()); + updatingPlayer.ifPresent(player -> { + try { + new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), + RedisMessage.serialize(playerData)) + .send(); + + // Send synchronisation complete message + player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); + } catch (IOException e) { + log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); + e.printStackTrace(); + } + }); + } + }).schedule(); case CONNECTION_HANDSHAKE -> { // Reply to a Bukkit server's connection handshake to complete the process if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling @@ -191,7 +190,7 @@ public class VelocityRedisListener extends RedisListener { migrator.loadIncomingData(migrator.incomingPlayerData, HuskSyncVelocity.dataManager); } } - case API_DATA_REQUEST -> { + case API_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); @@ -214,7 +213,7 @@ public class VelocityRedisListener extends RedisListener { } catch (IOException e) { plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); } - } + }).schedule(); } }