mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-19 14:59:21 +00:00
Add advancement, location and flight syncing, fix an issue that sometimes led to inconsistent syncs
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
package me.william278.crossserversync.bukkit;
|
||||
|
||||
import me.william278.crossserversync.redis.RedisMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -15,10 +16,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -120,6 +118,7 @@ public final class DataSerializer {
|
||||
* @return ItemStack array created from the Base64 string.
|
||||
* @throws IOException in the event the class type cannot be decoded
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
|
||||
public static ItemStack[] itemStackArrayFromBase64(String data) throws IOException {
|
||||
// Return an empty ItemStack[] if the data is empty
|
||||
if (data.isEmpty()) {
|
||||
@@ -130,7 +129,6 @@ public final class DataSerializer {
|
||||
ItemStack[] items = new ItemStack[dataInput.readInt()];
|
||||
|
||||
for (int Index = 0; Index < items.length; Index++) {
|
||||
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
|
||||
Map<String, Object> stack = (Map<String, Object>) dataInput.readObject();
|
||||
|
||||
if (stack != null) {
|
||||
@@ -146,6 +144,7 @@ public final class DataSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
|
||||
public static PotionEffect[] potionEffectArrayFromBase64(String data) throws IOException {
|
||||
// Return an empty PotionEffect[] if the data is empty
|
||||
if (data.isEmpty()) {
|
||||
@@ -156,7 +155,6 @@ public final class DataSerializer {
|
||||
PotionEffect[] items = new PotionEffect[dataInput.readInt()];
|
||||
|
||||
for (int Index = 0; Index < items.length; Index++) {
|
||||
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
|
||||
Map<String, Object> effect = (Map<String, Object>) dataInput.readObject();
|
||||
|
||||
if (effect != null) {
|
||||
@@ -172,6 +170,57 @@ public final class DataSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
public static PlayerLocation deserializePlayerLocationData(String serializedLocationData) throws IOException {
|
||||
if (serializedLocationData.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (PlayerLocation) RedisMessage.deserialize(serializedLocationData);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException("Unable to decode class type.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSerializedLocation(Player player) throws IOException {
|
||||
final Location playerLocation = player.getLocation();
|
||||
return RedisMessage.serialize(new PlayerLocation(playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(),
|
||||
playerLocation.getYaw(), playerLocation.getPitch(), player.getWorld().getName(), player.getWorld().getEnvironment()));
|
||||
}
|
||||
|
||||
public record PlayerLocation(double x, double y, double z, float yaw, float pitch,
|
||||
String worldName, World.Environment environment) implements Serializable {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
|
||||
public static ArrayList<AdvancementRecord> deserializeAdvancementData(String serializedAdvancementData) throws IOException {
|
||||
if (serializedAdvancementData.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return (ArrayList<AdvancementRecord>) RedisMessage.deserialize(serializedAdvancementData);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException("Unable to decode class type.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSerializedAdvancements(Player player) throws IOException {
|
||||
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
|
||||
ArrayList<AdvancementRecord> advancementData = new ArrayList<>();
|
||||
|
||||
while (serverAdvancements.hasNext()) {
|
||||
final AdvancementProgress progress = player.getAdvancementProgress(serverAdvancements.next());
|
||||
final NamespacedKey advancementKey = progress.getAdvancement().getKey();
|
||||
final ArrayList<String> awardedCriteria = new ArrayList<>(progress.getAwardedCriteria());
|
||||
advancementData.add(new AdvancementRecord(advancementKey.getNamespace() + ":" + advancementKey.getKey(), awardedCriteria));
|
||||
}
|
||||
|
||||
return RedisMessage.serialize(advancementData);
|
||||
}
|
||||
|
||||
public record AdvancementRecord(String advancementKey,
|
||||
ArrayList<String> awardedAdvancementCriteria) implements Serializable {
|
||||
}
|
||||
|
||||
public static StatisticData deserializeStatisticData(String serializedStatisticData) throws IOException {
|
||||
if (serializedStatisticData.isEmpty()) {
|
||||
return new StatisticData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
|
||||
@@ -184,28 +233,28 @@ public final class DataSerializer {
|
||||
}
|
||||
|
||||
public static String getSerializedStatisticData(Player player) throws IOException {
|
||||
HashMap<Statistic,Integer> untypedStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic,HashMap<Material,Integer>> blockStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic,HashMap<Material,Integer>> itemStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic,HashMap<EntityType,Integer>> entityStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic, Integer> untypedStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic, HashMap<Material, Integer>> blockStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic, HashMap<Material, Integer>> itemStatisticValues = new HashMap<>();
|
||||
HashMap<Statistic, HashMap<EntityType, Integer>> entityStatisticValues = new HashMap<>();
|
||||
for (Statistic statistic : Statistic.values()) {
|
||||
switch (statistic.getType()) {
|
||||
case ITEM -> {
|
||||
HashMap<Material,Integer> itemValues = new HashMap<>();
|
||||
HashMap<Material, Integer> itemValues = new HashMap<>();
|
||||
for (Material itemMaterial : Arrays.stream(Material.values()).filter(Material::isItem).collect(Collectors.toList())) {
|
||||
itemValues.put(itemMaterial, player.getStatistic(statistic, itemMaterial));
|
||||
}
|
||||
itemStatisticValues.put(statistic, itemValues);
|
||||
}
|
||||
case BLOCK -> {
|
||||
HashMap<Material,Integer> blockValues = new HashMap<>();
|
||||
HashMap<Material, Integer> blockValues = new HashMap<>();
|
||||
for (Material blockMaterial : Arrays.stream(Material.values()).filter(Material::isBlock).collect(Collectors.toList())) {
|
||||
blockValues.put(blockMaterial, player.getStatistic(statistic, blockMaterial));
|
||||
}
|
||||
blockStatisticValues.put(statistic, blockValues);
|
||||
}
|
||||
case ENTITY -> {
|
||||
HashMap<EntityType,Integer> entityValues = new HashMap<>();
|
||||
HashMap<EntityType, Integer> entityValues = new HashMap<>();
|
||||
for (EntityType type : Arrays.stream(EntityType.values()).filter(EntityType::isAlive).collect(Collectors.toList())) {
|
||||
entityValues.put(type, player.getStatistic(statistic, type));
|
||||
}
|
||||
@@ -219,8 +268,9 @@ public final class DataSerializer {
|
||||
return RedisMessage.serialize(statisticData);
|
||||
}
|
||||
|
||||
public record StatisticData(HashMap<Statistic,Integer> untypedStatisticValues,
|
||||
HashMap<Statistic,HashMap<Material,Integer>> blockStatisticValues,
|
||||
HashMap<Statistic,HashMap<Material,Integer>> itemStatisticValues,
|
||||
HashMap<Statistic,HashMap<EntityType,Integer>> entityStatisticValues) implements Serializable { }
|
||||
public record StatisticData(HashMap<Statistic, Integer> untypedStatisticValues,
|
||||
HashMap<Statistic, HashMap<Material, Integer>> blockStatisticValues,
|
||||
HashMap<Statistic, HashMap<Material, Integer>> itemStatisticValues,
|
||||
HashMap<Statistic, HashMap<EntityType, Integer>> entityStatisticValues) implements Serializable {
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,9 @@ import me.william278.crossserversync.MessageStrings;
|
||||
import me.william278.crossserversync.PlayerData;
|
||||
import me.william278.crossserversync.Settings;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -17,6 +16,8 @@ import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -39,6 +40,10 @@ public class PlayerSetter {
|
||||
// Set the player's data from the PlayerData
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
try {
|
||||
if (Settings.syncAdvancements) {
|
||||
// Sync advancements first so that any rewards will be overridden
|
||||
setPlayerAdvancements(player, DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()));
|
||||
}
|
||||
if (Settings.syncInventories) {
|
||||
setPlayerInventory(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedInventory()));
|
||||
player.getInventory().setHeldItemSlot(data.getSelectedSlot());
|
||||
@@ -69,6 +74,10 @@ public class PlayerSetter {
|
||||
if (Settings.syncGameMode) {
|
||||
player.setGameMode(GameMode.valueOf(data.getGameMode()));
|
||||
}
|
||||
if (Settings.syncLocation) {
|
||||
player.setFlying(player.getAllowFlight() && data.isFlying());
|
||||
setPlayerLocation(player, DataSerializer.deserializePlayerLocationData(data.getSerializedLocation()));
|
||||
}
|
||||
|
||||
// Send action bar synchronisation message
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageStrings.SYNCHRONISATION_COMPLETE).toComponent());
|
||||
@@ -114,6 +123,7 @@ public class PlayerSetter {
|
||||
|
||||
/**
|
||||
* Set a player's current potion effects from a set of {@link PotionEffect[]}
|
||||
*
|
||||
* @param player The player to set the potion effects of
|
||||
* @param effects The array of {@link PotionEffect}s to set
|
||||
*/
|
||||
@@ -126,8 +136,69 @@ public class PlayerSetter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a player's advancements and progress to match the advancementData
|
||||
*
|
||||
* @param player The player to set the advancements of
|
||||
* @param advancementData The ArrayList of {@link DataSerializer.AdvancementRecord}s to set
|
||||
*/
|
||||
private static void setPlayerAdvancements(Player player, ArrayList<DataSerializer.AdvancementRecord> advancementData) {
|
||||
// Temporarily disable advancement announcing if needed
|
||||
boolean announceAdvancementUpdate = false;
|
||||
if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) {
|
||||
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false);
|
||||
announceAdvancementUpdate = true;
|
||||
}
|
||||
final boolean finalAnnounceAdvancementUpdate = announceAdvancementUpdate;
|
||||
|
||||
// Run async because advancement loading is very slow
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
// Apply the advancements to the player
|
||||
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
|
||||
while (serverAdvancements.hasNext()) {
|
||||
Advancement advancement = serverAdvancements.next();
|
||||
AdvancementProgress playerProgress = player.getAdvancementProgress(advancement);
|
||||
boolean hasAdvancement = false;
|
||||
for (DataSerializer.AdvancementRecord record : advancementData) {
|
||||
if (record.advancementKey().equals(advancement.getKey().getNamespace() + ":" + advancement.getKey().getKey())) {
|
||||
hasAdvancement = true;
|
||||
|
||||
// Save the experience before granting the advancement
|
||||
final int expLevel = player.getLevel();
|
||||
final float expProgress = player.getExp();
|
||||
|
||||
// Grant advancement criteria if the player does not have it
|
||||
for (String awardCriteria : record.awardedAdvancementCriteria()) {
|
||||
if (!playerProgress.getAwardedCriteria().contains(awardCriteria)) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.getAdvancementProgress(advancement).awardCriteria(awardCriteria));
|
||||
}
|
||||
}
|
||||
|
||||
// Set experience back to before granting advancement; nullify exp gained from it
|
||||
player.setLevel(expLevel);
|
||||
player.setExp(expProgress);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasAdvancement) {
|
||||
for (String awardCriteria : playerProgress.getAwardedCriteria()) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.getAdvancementProgress(advancement).revokeCriteria(awardCriteria));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable announcing advancements (back on main thread again)
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (finalAnnounceAdvancementUpdate) {
|
||||
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a player's statistics (in the Statistic menu)
|
||||
*
|
||||
* @param player The player to set the statistics of
|
||||
* @param statisticData The {@link DataSerializer.StatisticData} to set
|
||||
*/
|
||||
@@ -158,4 +229,37 @@ public class PlayerSetter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a player's location from {@link DataSerializer.PlayerLocation} data
|
||||
*
|
||||
* @param player The {@link Player} to teleport
|
||||
* @param location The {@link DataSerializer.PlayerLocation}
|
||||
*/
|
||||
private static void setPlayerLocation(Player player, DataSerializer.PlayerLocation location) {
|
||||
// Don't teleport if the location is invalid
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the world; if the names match, use that
|
||||
World world = Bukkit.getWorld(location.worldName());
|
||||
if (world == null) {
|
||||
|
||||
// If the names don't match, find the corresponding world with the same dimension environment
|
||||
for (World worldOnServer : Bukkit.getWorlds()) {
|
||||
if (worldOnServer.getEnvironment().equals(location.environment())) {
|
||||
world = worldOnServer;
|
||||
}
|
||||
}
|
||||
|
||||
// If that still fails, return
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport the player
|
||||
player.teleport(new Location(world, location.x(), location.y(), location.z(), location.yaw(), location.pitch()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ public class ConfigLoader {
|
||||
Settings.syncPotionEffects = config.getBoolean("synchronisation_settings.potion_effects", true);
|
||||
Settings.syncStatistics = config.getBoolean("synchronisation_settings.statistics", true);
|
||||
Settings.syncGameMode = config.getBoolean("synchronisation_settings.game_mode", true);
|
||||
|
||||
Settings.syncAdvancements = config.getBoolean("synchronisation_settings.advancements", true);
|
||||
Settings.syncLocation = config.getBoolean("synchronisation_settings.location", false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public class BukkitRedisListener extends RedisListener {
|
||||
if (!message.getMessageTarget().targetServerType().equals(Settings.ServerType.BUKKIT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Handle the message for the player
|
||||
if (message.getMessageTarget().targetPlayerUUID() == null) {
|
||||
if (message.getMessageType() == RedisMessage.MessageType.REQUEST_DATA_ON_JOIN) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class EventListener implements Listener {
|
||||
@@ -23,6 +22,7 @@ public class EventListener implements Listener {
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -42,7 +42,10 @@ public class EventListener implements Listener {
|
||||
player.getLevel(),
|
||||
player.getExp(),
|
||||
player.getGameMode().toString(),
|
||||
DataSerializer.getSerializedStatisticData(player)));
|
||||
DataSerializer.getSerializedStatisticData(player),
|
||||
player.isFlying(),
|
||||
DataSerializer.getSerializedAdvancements(player),
|
||||
DataSerializer.getSerializedLocation(player)));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -52,8 +55,8 @@ public class EventListener implements Listener {
|
||||
|
||||
try {
|
||||
// Get the player's last updated PlayerData version UUID
|
||||
final UUID lastUpdatedDataVersion = CrossServerSyncBukkit.bukkitCache.getVersionUUID(player.getUniqueId());
|
||||
if (lastUpdatedDataVersion == null) return; // Return if the player has not been properly updated.
|
||||
//final UUID lastUpdatedDataVersion = CrossServerSyncBukkit.bukkitCache.getVersionUUID(player.getUniqueId());
|
||||
//if (lastUpdatedDataVersion == null) return; // Return if the player has not been properly updated.
|
||||
|
||||
// Send a redis message with the player's last updated PlayerData version UUID and their new PlayerData
|
||||
final String serializedPlayerData = getNewSerializedPlayerData(player);
|
||||
@@ -63,6 +66,13 @@ public class EventListener implements Listener {
|
||||
} 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();
|
||||
|
||||
// Set data version ID to null
|
||||
CrossServerSyncBukkit.bukkitCache.setVersionUUID(player.getUniqueId(), null);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -70,6 +80,10 @@ public class EventListener implements Listener {
|
||||
// When a player joins a Bukkit server
|
||||
final Player player = event.getPlayer();
|
||||
|
||||
// Clear player inventory and ender chest
|
||||
player.getInventory().clear();
|
||||
player.getEnderChest().clear();
|
||||
|
||||
if (CrossServerSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) {
|
||||
try {
|
||||
// Send a redis message requesting the player data
|
||||
|
||||
@@ -11,3 +11,5 @@ synchronisation_settings:
|
||||
potion_effects: true
|
||||
statistics: true
|
||||
game_mode: true
|
||||
advancements: true
|
||||
location: false
|
||||
@@ -80,10 +80,16 @@ public class DataManager {
|
||||
final int expLevel = resultSet.getInt("exp_level");
|
||||
final float expProgress = resultSet.getInt("exp_progress");
|
||||
final String gameMode = resultSet.getString("game_mode");
|
||||
final boolean isFlying = resultSet.getBoolean("is_flying");
|
||||
final String serializedAdvancementData = resultSet.getString("advancements");
|
||||
final String serializedLocationData = resultSet.getString( "location");
|
||||
|
||||
final String serializedStatisticData = resultSet.getString("statistics");
|
||||
|
||||
return new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, health, maxHealth, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects, totalExperience, expLevel, expProgress, gameMode, serializedStatisticData);
|
||||
return new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest,
|
||||
health, maxHealth, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects,
|
||||
totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying,
|
||||
serializedAdvancementData, serializedLocationData);
|
||||
} else {
|
||||
return PlayerData.DEFAULT_PLAYER_DATA(playerUUID);
|
||||
}
|
||||
@@ -111,7 +117,7 @@ public class DataManager {
|
||||
private static void updatePlayerSQLData(PlayerData playerData) {
|
||||
try (Connection connection = CrossServerSyncBungeeCord.getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE " + Database.DATA_TABLE_NAME + " SET `version_uuid`=?, `timestamp`=?, `inventory`=?, `ender_chest`=?, `health`=?, `max_health`=?, `hunger`=?, `saturation`=?, `saturation_exhaustion`=?, `selected_slot`=?, `status_effects`=?, `total_experience`=?, `exp_level`=?, `exp_progress`=?, `game_mode`=?, `statistics`=? WHERE `player_id`=(SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?);")) {
|
||||
"UPDATE " + Database.DATA_TABLE_NAME + " SET `version_uuid`=?, `timestamp`=?, `inventory`=?, `ender_chest`=?, `health`=?, `max_health`=?, `hunger`=?, `saturation`=?, `saturation_exhaustion`=?, `selected_slot`=?, `status_effects`=?, `total_experience`=?, `exp_level`=?, `exp_progress`=?, `game_mode`=?, `statistics`=?, `is_flying`=?, `advancements`=?, `location`=? WHERE `player_id`=(SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?);")) {
|
||||
statement.setString(1, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(2, new Timestamp(Instant.now().getEpochSecond()));
|
||||
statement.setString(3, playerData.getSerializedInventory());
|
||||
@@ -128,8 +134,11 @@ public class DataManager {
|
||||
statement.setFloat(14, playerData.getExpProgress()); // Exp progress
|
||||
statement.setString(15, playerData.getGameMode()); // GameMode
|
||||
statement.setString(16, playerData.getSerializedStatistics()); // Statistics
|
||||
statement.setBoolean(17, playerData.isFlying()); // Is flying
|
||||
statement.setString(18, playerData.getSerializedAdvancements()); // Advancements
|
||||
statement.setString(19, playerData.getSerializedLocation()); // Location
|
||||
|
||||
statement.setString(17, playerData.getPlayerUUID().toString());
|
||||
statement.setString(20, playerData.getPlayerUUID().toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -140,7 +149,7 @@ public class DataManager {
|
||||
private static void insertPlayerData(PlayerData playerData) {
|
||||
try (Connection connection = CrossServerSyncBungeeCord.getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO " + Database.DATA_TABLE_NAME + " (`player_id`,`version_uuid`,`timestamp`,`inventory`,`ender_chest`,`health`,`max_health`,`hunger`,`saturation`,`saturation_exhaustion`,`selected_slot`,`status_effects`,`total_experience`,`exp_level`,`exp_progress`,`game_mode`,`statistics`) VALUES((SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) {
|
||||
"INSERT INTO " + Database.DATA_TABLE_NAME + " (`player_id`,`version_uuid`,`timestamp`,`inventory`,`ender_chest`,`health`,`max_health`,`hunger`,`saturation`,`saturation_exhaustion`,`selected_slot`,`status_effects`,`total_experience`,`exp_level`,`exp_progress`,`game_mode`,`statistics`,`is_flying`,`advancements`,`location`) VALUES((SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) {
|
||||
statement.setString(1, playerData.getPlayerUUID().toString());
|
||||
statement.setString(2, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(3, new Timestamp(Instant.now().getEpochSecond()));
|
||||
@@ -157,8 +166,10 @@ public class DataManager {
|
||||
statement.setInt(14, playerData.getExpLevel()); // Exp level
|
||||
statement.setFloat(15, playerData.getExpProgress()); // Exp progress
|
||||
statement.setString(16, playerData.getGameMode()); // GameMode
|
||||
|
||||
statement.setString(17, playerData.getSerializedStatistics()); // Statistics
|
||||
statement.setBoolean(18, playerData.isFlying()); // Is flying
|
||||
statement.setString(19, playerData.getSerializedAdvancements()); // Advancements
|
||||
statement.setString(20, playerData.getSerializedLocation()); // Location
|
||||
|
||||
statement.executeUpdate();
|
||||
}
|
||||
@@ -234,22 +245,5 @@ public class DataManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a player's {@link PlayerData} from the cache
|
||||
* @param playerUUID The UUID of the player to remove
|
||||
*/
|
||||
public void removePlayer(UUID playerUUID) {
|
||||
PlayerData dataToRemove = null;
|
||||
for (PlayerData data : playerData) {
|
||||
if (data.getPlayerUUID() == playerUUID) {
|
||||
dataToRemove = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dataToRemove != null) {
|
||||
playerData.remove(dataToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public class MySQL extends Database {
|
||||
"`exp_progress` float NOT NULL," +
|
||||
"`game_mode` tinytext NOT NULL," +
|
||||
"`statistics` longtext NOT NULL," +
|
||||
"`is_flying` boolean NOT NULL," +
|
||||
"`advancements` longtext NOT NULL," +
|
||||
"`location` text NOT NULL," +
|
||||
|
||||
"PRIMARY KEY (`player_id`,`uuid`)," +
|
||||
"FOREIGN KEY (`player_id`) REFERENCES " + PLAYER_TABLE_NAME + " (`id`)" +
|
||||
|
||||
@@ -45,6 +45,9 @@ public class SQLite extends Database {
|
||||
"`exp_progress` float NOT NULL," +
|
||||
"`game_mode` tinytext NOT NULL," +
|
||||
"`statistics` longtext NOT NULL," +
|
||||
"`is_flying` boolean NOT NULL," +
|
||||
"`advancements` longtext NOT NULL," +
|
||||
"`location` text NOT NULL," +
|
||||
|
||||
"PRIMARY KEY (`player_id`,`version_uuid`)" +
|
||||
");"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.william278.crossserversync.bungeecord.listener;
|
||||
|
||||
import de.themoep.minedown.MineDown;
|
||||
import me.william278.crossserversync.CrossServerSyncBungeeCord;
|
||||
import me.william278.crossserversync.PlayerData;
|
||||
import me.william278.crossserversync.Settings;
|
||||
@@ -88,24 +87,24 @@ public class BungeeRedisListener extends RedisListener {
|
||||
// Update the data in the cache and SQL
|
||||
DataManager.updatePlayerData(playerData);
|
||||
|
||||
// Reply to set the player's data if they moved servers
|
||||
// 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()) {
|
||||
// Send the reply, serializing the message data
|
||||
try {
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID()),
|
||||
RedisMessage.serialize(playerData)).send();
|
||||
RedisMessage.serialize(playerData))
|
||||
.send();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to serialize data when replying to a data update");
|
||||
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log to console
|
||||
|
||||
@@ -33,9 +33,13 @@ public class PlayerData implements Serializable {
|
||||
private final float expProgress;
|
||||
private final String gameMode;
|
||||
private final String serializedStatistics;
|
||||
private final boolean isFlying;
|
||||
private final String serializedAdvancements;
|
||||
private final String serializedLocation;
|
||||
|
||||
/**
|
||||
* Constructor to create new PlayerData from a bukkit {@code Player}'s data
|
||||
*
|
||||
* @param playerUUID The Player's UUID
|
||||
* @param serializedInventory Their serialized inventory
|
||||
* @param serializedEnderChest Their serialized ender chest
|
||||
@@ -52,7 +56,10 @@ public class PlayerData implements Serializable {
|
||||
* @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, String serializedInventory, String serializedEnderChest, double health, double maxHealth, int hunger, float saturation, float saturationExhaustion, int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode, String serializedStatistics) {
|
||||
public PlayerData(UUID playerUUID, String serializedInventory, String serializedEnderChest, double health,
|
||||
double maxHealth, 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.dataVersionUUID = UUID.randomUUID();
|
||||
this.playerUUID = playerUUID;
|
||||
this.serializedInventory = serializedInventory;
|
||||
@@ -69,10 +76,14 @@ public class PlayerData implements Serializable {
|
||||
this.expProgress = expProgress;
|
||||
this.gameMode = gameMode;
|
||||
this.serializedStatistics = serializedStatistics;
|
||||
this.isFlying = isFlying;
|
||||
this.serializedAdvancements = serializedAdvancements;
|
||||
this.serializedLocation = serializedLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a PlayerData object from an existing object that was stored in SQL
|
||||
*
|
||||
* @param playerUUID The player whose data this is' UUID
|
||||
* @param dataVersionUUID The PlayerData version UUID
|
||||
* @param serializedInventory Their serialized inventory
|
||||
@@ -90,7 +101,11 @@ public class PlayerData implements Serializable {
|
||||
* @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, double health, double maxHealth, int hunger, float saturation, float saturationExhaustion, int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode, String serializedStatistics) {
|
||||
public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest,
|
||||
double health, double maxHealth, 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.serializedInventory = serializedInventory;
|
||||
@@ -107,17 +122,22 @@ public class PlayerData implements Serializable {
|
||||
this.expProgress = expProgress;
|
||||
this.gameMode = gameMode;
|
||||
this.serializedStatistics = serializedStatistics;
|
||||
this.isFlying = isFlying;
|
||||
this.serializedAdvancements = serializedAdvancements;
|
||||
this.serializedLocation = serializedLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default PlayerData for a new user
|
||||
*
|
||||
* @param playerUUID The bukkit Player's UUID
|
||||
* @return Default {@link PlayerData}
|
||||
*/
|
||||
public static PlayerData DEFAULT_PLAYER_DATA(UUID playerUUID) {
|
||||
PlayerData data = new PlayerData(playerUUID, "", "", 20,
|
||||
20, 20, 10, 1, 0,
|
||||
"", 0, 0, 0, "SURVIVAL", "");
|
||||
"", 0, 0, 0, "SURVIVAL",
|
||||
"", false, "", "");
|
||||
data.useDefaultData = true;
|
||||
return data;
|
||||
}
|
||||
@@ -186,7 +206,20 @@ public class PlayerData implements Serializable {
|
||||
return gameMode;
|
||||
}
|
||||
|
||||
public boolean isFlying() {
|
||||
return isFlying;
|
||||
}
|
||||
|
||||
public String getSerializedAdvancements() {
|
||||
return serializedAdvancements;
|
||||
}
|
||||
|
||||
public String getSerializedLocation() {
|
||||
return serializedLocation;
|
||||
}
|
||||
|
||||
public boolean isUseDefaultData() {
|
||||
return useDefaultData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ public class Settings {
|
||||
public static boolean syncPotionEffects;
|
||||
public static boolean syncStatistics;
|
||||
public static boolean syncGameMode;
|
||||
public static boolean syncAdvancements;
|
||||
public static boolean syncLocation;
|
||||
|
||||
/*
|
||||
* Enum definitions
|
||||
|
||||
Reference in New Issue
Block a user