Now fully reliable and added support for health, max health, etc

feat/data-edit-commands
William 3 years ago
parent ec6f85250d
commit ba8e4ee175

@ -11,8 +11,6 @@ dependencies {
shadowJar {
relocate 'redis.clients', 'me.William278.crossserversync.libraries.jedis'
relocate 'org.bstats', 'me.William278.crossserversync.libraries.plan'
relocate 'org.apache.commons', 'me.William278.crossserversync.libraries.apache-commons'
relocate 'org.slf4j', 'me.William278.crossserversync.libraries.slf4j'
}
tasks.register('prepareKotlinBuildScriptModel'){}

@ -1,4 +1,4 @@
package me.william278.crossserversync.bukkit;
package me.william278.crossserversync;
import me.william278.crossserversync.bukkit.config.ConfigLoader;
import me.william278.crossserversync.bukkit.data.LastDataUpdateUUIDCache;
@ -34,12 +34,12 @@ public final class CrossServerSyncBukkit extends JavaPlugin {
// Initialize last data update UUID cache
lastDataUpdateUUIDCache = new LastDataUpdateUUIDCache();
// Initialize the redis listener
new BukkitRedisListener();
// Initialize event listener
getServer().getPluginManager().registerEvents(new EventListener(), this);
// Initialize the redis listener
new BukkitRedisListener();
// Log to console
getLogger().info("Enabled CrossServerSync (" + getServer().getName() + ") v" + getDescription().getVersion());
}

@ -1,8 +1,8 @@
package me.william278.crossserversync.bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
@ -19,8 +19,9 @@ import java.util.Map;
*
* @author efindus
* @author graywolf336
* @author William278
*/
public final class InventorySerializer {
public final class DataSerializer {
/**
* Converts the player inventory to a Base64 encoded string.
@ -46,35 +47,33 @@ public final class InventorySerializer {
return itemStackArrayToBase64(player.getEnderChest().getContents());
}
/**
* Sets a player's inventory from a set of {@link ItemStack}s
*
* @param player The player to set the inventory of
* @param items The array of {@link ItemStack}s to set
*/
public static void setPlayerItems(Player player, ItemStack[] items) {
setInventoryItems(player.getInventory(), items);
public static String getSerializedEffectData(Player player) {
PotionEffect[] potionEffects = new PotionEffect[player.getActivePotionEffects().size()];
int x = 0;
for (PotionEffect effect : player.getActivePotionEffects()) {
potionEffects[x] = effect;
x++;
}
return effectArrayToBase64(potionEffects);
}
/**
* Sets a player's ender chest from a set of {@link ItemStack}s
*
* @param player The player to set the inventory of
* @param items The array of {@link ItemStack}s to set
*/
public static void setPlayerEnderChest(Player player, ItemStack[] items) {
setInventoryItems(player.getEnderChest(), items);
}
public static String effectArrayToBase64(PotionEffect[] effects) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
dataOutput.writeInt(effects.length);
// Clears, then fills an inventory's items correctly.
private static void setInventoryItems(Inventory inventory, ItemStack[] items) {
inventory.clear();
int index = 0;
for (ItemStack item : items) {
if (item != null) {
inventory.setItem(index, item);
for (PotionEffect effect : effects) {
if (effect != null) {
dataOutput.writeObject(effect.serialize());
} else {
dataOutput.writeObject(null);
}
}
}
index++;
return Base64Coder.encodeLines(outputStream.toByteArray());
} catch (Exception e) {
throw new IllegalStateException("Unable to save potion effects.", e);
}
}
@ -137,4 +136,30 @@ public final class InventorySerializer {
throw new IOException("Unable to decode class type.", e);
}
}
public static PotionEffect[] potionEffectArrayFromBase64(String data) throws IOException {
// Return an empty PotionEffect[] if the data is empty
if (data.isEmpty()) {
return new PotionEffect[0];
}
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data))) {
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
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) {
items[Index] = new PotionEffect(effect);
} else {
items[Index] = null;
}
}
return items;
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
}
}

@ -0,0 +1,83 @@
package me.william278.crossserversync.bukkit;
import me.william278.crossserversync.CrossServerSyncBukkit;
import me.william278.crossserversync.PlayerData;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import java.io.IOException;
import java.util.logging.Level;
public class PlayerSetter {
private static final CrossServerSyncBukkit plugin = CrossServerSyncBukkit.getInstance();
/**
* Set a player from their PlayerData
*
* @param player The {@link Player} to set
* @param data The {@link PlayerData} to assign to the player
*/
public static void setPlayerFrom(Player player, PlayerData data) {
try {
setPlayerInventory(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedInventory()));
setPlayerEnderChest(player, DataSerializer.itemStackArrayFromBase64(data.getSerializedEnderChest()));
player.setHealth(data.getHealth());
player.setMaxHealth(data.getMaxHealth());
player.setFoodLevel(data.getHunger());
player.setSaturation(data.getSaturation());
player.getInventory().setHeldItemSlot(data.getSelectedSlot());
//todo potion effects not working
setPlayerPotionEffects(player, DataSerializer.potionEffectArrayFromBase64(data.getSerializedEffectData()));
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Failed to deserialize PlayerData", e);
}
}
/**
* Sets a player's ender chest from a set of {@link ItemStack}s
*
* @param player The player to set the inventory of
* @param items The array of {@link ItemStack}s to set
*/
private static void setPlayerEnderChest(Player player, ItemStack[] items) {
player.getEnderChest().clear();
int index = 0;
for (ItemStack item : items) {
if (item != null) {
player.getEnderChest().setItem(index, item);
}
index++;
}
}
/**
* Sets a player's inventory from a set of {@link ItemStack}s
*
* @param player The player to set the inventory of
* @param items The array of {@link ItemStack}s to set
*/
private static void setPlayerInventory(Player player, ItemStack[] items) {
player.getInventory().clear();
int index = 0;
for (ItemStack item : items) {
if (item != null) {
player.getInventory().setItem(index, item);
}
index++;
}
}
/**
* 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
*/
private static void setPlayerPotionEffects(Player player, PotionEffect[] effects) {
player.getActivePotionEffects().clear();
for (PotionEffect effect : effects) {
player.getActivePotionEffects().add(effect);
}
}
}

@ -1,9 +1,9 @@
package me.william278.crossserversync.bukkit.listener;
import me.william278.crossserversync.bukkit.InventorySerializer;
import me.william278.crossserversync.PlayerData;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bukkit.CrossServerSyncBukkit;
import me.william278.crossserversync.CrossServerSyncBukkit;
import me.william278.crossserversync.bukkit.PlayerSetter;
import me.william278.crossserversync.redis.RedisListener;
import me.william278.crossserversync.redis.RedisMessage;
import org.bukkit.Bukkit;
@ -29,20 +29,19 @@ public class BukkitRedisListener extends RedisListener {
@Override
public void handleMessage(RedisMessage message) {
// Ignore messages for proxy servers
if (message.getMessageTarget().targetServerType() != Settings.ServerType.BUKKIT) {
if (!message.getMessageTarget().targetServerType().equals(Settings.ServerType.BUKKIT)) {
return;
}
// Handle the message for the player
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.getUniqueId() == message.getMessageTarget().targetPlayerName()) {
if (message.getMessageType() == RedisMessage.MessageType.PLAYER_DATA_REPLY) {
if (player.getUniqueId().equals(message.getMessageTarget().targetPlayerUUID())) {
if (message.getMessageType().equals(RedisMessage.MessageType.PLAYER_DATA_REPLY)) {
try {
// Deserialize the received PlayerData
PlayerData data = (PlayerData) RedisMessage.deserialize(message.getMessageData());
// Set the player's data //todo do more stuff like health etc
InventorySerializer.setPlayerItems(player, InventorySerializer.itemStackArrayFromBase64(data.getSerializedInventory()));
InventorySerializer.setPlayerEnderChest(player, InventorySerializer.itemStackArrayFromBase64(data.getSerializedEnderChest()));
// Set the player's data
PlayerSetter.setPlayerFrom(player, data);
// Update last loaded data UUID
CrossServerSyncBukkit.lastDataUpdateUUIDCache.setVersionUUID(player.getUniqueId(), data.getDataVersionUUID());

@ -1,9 +1,9 @@
package me.william278.crossserversync.bukkit.listener;
import me.william278.crossserversync.CrossServerSyncBukkit;
import me.william278.crossserversync.PlayerData;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bukkit.CrossServerSyncBukkit;
import me.william278.crossserversync.bukkit.InventorySerializer;
import me.william278.crossserversync.bukkit.DataSerializer;
import me.william278.crossserversync.redis.RedisMessage;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -27,8 +27,14 @@ public class EventListener implements Listener {
*/
private static String getNewSerializedPlayerData(Player player) throws IOException {
return RedisMessage.serialize(new PlayerData(player.getUniqueId(),
InventorySerializer.getSerializedInventoryContents(player),
InventorySerializer.getSerializedEnderChestContents(player)));
DataSerializer.getSerializedInventoryContents(player),
DataSerializer.getSerializedEnderChestContents(player),
player.getHealth(),
player.getMaxHealth(),
player.getFoodLevel(),
player.getSaturation(),
player.getInventory().getHeldItemSlot(),
DataSerializer.getSerializedEffectData(player)));
}
@EventHandler
@ -42,15 +48,16 @@ public class EventListener implements Listener {
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);
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null),
lastUpdatedDataVersion.toString(), getNewSerializedPlayerData(player)).send();
serializedPlayerData).send();
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", e);
}
}
@EventHandler
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
// When a player joins a Bukkit server
final Player player = event.getPlayer();

@ -12,8 +12,6 @@ shadowJar {
relocate 'redis.clients', 'me.William278.crossserversync.libraries.jedis'
relocate 'com.zaxxer', 'me.William278.crossserversync.libraries.hikari'
relocate 'org.bstats', 'me.William278.crossserversync.libraries.plan'
relocate 'org.apache.commons', 'me.William278.crossserversync.libraries.apache-commons'
relocate 'org.slf4j', 'me.William278.crossserversync.libraries.slf4j'
}
tasks.register('prepareKotlinBuildScriptModel'){}

@ -1,6 +1,5 @@
package me.william278.crossserversync.bungeecord;
package me.william278.crossserversync;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bungeecord.config.ConfigLoader;
import me.william278.crossserversync.bungeecord.config.ConfigManager;
import me.william278.crossserversync.bungeecord.data.DataManager;
@ -50,7 +49,7 @@ public final class CrossServerSyncBungeeCord extends Plugin {
database.load();
// Setup player data cache
DataManager.setupCache();
DataManager.playerDataCache = new DataManager.PlayerDataCache();
// Initialize PreLoginEvent listener
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());

@ -26,7 +26,6 @@ public class ConfigLoader {
Settings.hikariMaximumLifetime = config.getLong("data_storage_settings.hikari_pool_settings.maximum_lifetime", 1800000);
Settings.hikariKeepAliveTime = config.getLong("data_storage_settings.hikari_pool_settings.keepalive_time", 10);
Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000);
}
}

@ -1,6 +1,6 @@
package me.william278.crossserversync.bungeecord.config;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
@ -23,7 +23,8 @@ public class ConfigManager {
}
File configFile = new File(plugin.getDataFolder(), "config.yml");
if (!configFile.exists()) {
Files.copy(plugin.getResourceAsStream("bungee_config.yml"), configFile.toPath());
Files.copy(plugin.getResourceAsStream("bungee-config.yml"), configFile.toPath());
plugin.getLogger().info("Created CrossServerSync bungee-config.yml file");
}
} catch (Exception e) {
plugin.getLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e);

@ -1,7 +1,7 @@
package me.william278.crossserversync.bungeecord.data;
import me.william278.crossserversync.PlayerData;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import me.william278.crossserversync.bungeecord.data.sql.Database;
import java.sql.*;
@ -15,10 +15,6 @@ public class DataManager {
private static final CrossServerSyncBungeeCord plugin = CrossServerSyncBungeeCord.getInstance();
public static PlayerDataCache playerDataCache;
public static void setupCache() {
playerDataCache = new PlayerDataCache();
}
/**
* Checks if the player is registered on the database; register them if not.
*
@ -75,11 +71,12 @@ public class DataManager {
final String serializedEnderChest = resultSet.getString("ender_chest");
final double health = resultSet.getDouble("health");
final double maxHealth = resultSet.getDouble("max_health");
final double hunger = resultSet.getDouble("hunger");
final double saturation = resultSet.getDouble("saturation");
final int hunger = resultSet.getInt("hunger");
final float saturation = resultSet.getFloat("saturation");
final int selectedSlot = resultSet.getInt("selected_slot");
final String serializedStatusEffects = resultSet.getString("status_effects");
return new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, health, maxHealth, hunger, saturation, serializedStatusEffects);
return new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, health, maxHealth, hunger, saturation, selectedSlot, serializedStatusEffects);
} else {
return PlayerData.EMPTY_PLAYER_DATA(playerUUID);
}
@ -90,41 +87,35 @@ public class DataManager {
}
}
public static void updatePlayerData(PlayerData playerData, UUID lastDataUUID) {
public static void updatePlayerData(PlayerData playerData) {
// Ignore if the Spigot server didn't properly sync the previous data
PlayerData oldPlayerData = playerDataCache.getPlayer(playerData.getPlayerUUID());
if (oldPlayerData != null) {
if (oldPlayerData.getDataVersionUUID() != lastDataUUID) {
return;
}
}
// Add the new player data to the cache
playerDataCache.updatePlayer(playerData);
// SQL: If the player has cached data, update it, otherwise insert new data.
if (playerHasCachedData(playerData.getPlayerUUID())) {
updatePlayerData(playerData);
updatePlayerSQLData(playerData);
} else {
insertPlayerData(playerData);
}
}
private static void updatePlayerData(PlayerData playerData) {
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`=?, `status_effects`=? 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`=?, `selected_slot`=?, `status_effects`=? 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());
statement.setString(4, playerData.getSerializedEnderChest());
statement.setDouble(5, 20D); // Health
statement.setDouble(6, 20D); // Max health
statement.setDouble(7, 20D); // Hunger
statement.setDouble(8, 20D); // Saturation
statement.setString(9, ""); // Status effects
statement.setString(10, playerData.getPlayerUUID().toString());
statement.setDouble(5, playerData.getHealth()); // Health
statement.setDouble(6, playerData.getMaxHealth()); // Max health
statement.setInt(7, playerData.getHunger()); // Hunger
statement.setFloat(8, playerData.getSaturation()); // Saturation
statement.setInt(9, playerData.getSelectedSlot());
statement.setString(10, playerData.getSerializedEffectData()); // Status effects
statement.setString(11, playerData.getPlayerUUID().toString());
statement.executeUpdate();
}
} catch (SQLException e) {
@ -135,17 +126,18 @@ 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`,`status_effects`) 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`,`selected_slot`,`status_effects`) 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()));
statement.setString(4, playerData.getSerializedInventory());
statement.setString(5, playerData.getSerializedEnderChest());
statement.setDouble(6, 20D); // Health
statement.setDouble(7, 20D); // Max health
statement.setDouble(8, 20D); // Hunger
statement.setDouble(9, 20D); // Saturation
statement.setString(10, ""); // Status effects
statement.setDouble(6, playerData.getHealth()); // Health
statement.setDouble(7, playerData.getMaxHealth()); // Max health
statement.setInt(8, playerData.getHunger()); // Hunger
statement.setFloat(9, playerData.getSaturation()); // Saturation
statement.setInt(10, playerData.getSelectedSlot());
statement.setString(11, playerData.getSerializedEffectData()); // Status effects
statement.executeUpdate();
}
@ -178,7 +170,6 @@ public class DataManager {
* A cache of PlayerData
*/
public static class PlayerDataCache {
// The cached player data
public HashSet<PlayerData> playerData;

@ -1,7 +1,7 @@
package me.william278.crossserversync.bungeecord.data.sql;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import java.sql.Connection;
import java.sql.SQLException;

@ -2,7 +2,7 @@ package me.william278.crossserversync.bungeecord.data.sql;
import com.zaxxer.hikari.HikariDataSource;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import java.sql.Connection;
import java.sql.SQLException;
@ -27,8 +27,9 @@ public class MySQL extends Database {
"`ender_chest` longtext NOT NULL," +
"`health` double NOT NULL," +
"`max_health` double NOT NULL," +
"`hunger` double NOT NULL," +
"`saturation` double NOT NULL," +
"`hunger` integer NOT NULL," +
"`saturation` float NOT NULL," +
"`selected_slot` integer NOT NULL," +
"`status_effects` longtext NOT NULL," +
"PRIMARY KEY (`player_id`,`uuid`)," +

@ -1,7 +1,7 @@
package me.william278.crossserversync.bungeecord.data.sql;
import com.zaxxer.hikari.HikariDataSource;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import java.io.File;
import java.io.IOException;
@ -23,28 +23,25 @@ public class SQLite extends Database {
"PRAGMA encoding = 'UTF-8';",
"CREATE TABLE IF NOT EXISTS " + PLAYER_TABLE_NAME + " (" +
"`id` integer NOT NULL AUTO_INCREMENT," +
"`uuid` char(36) NOT NULL UNIQUE," +
"PRIMARY KEY (`id`)" +
"`id` integer PRIMARY KEY," +
"`uuid` char(36) NOT NULL UNIQUE" +
");",
"CREATE TABLE IF NOT EXISTS " + DATA_TABLE_NAME + " (" +
"`player_id` integer NOT NULL," +
"`player_id` integer NOT NULL REFERENCES " + PLAYER_TABLE_NAME + "(`id`)," +
"`version_uuid` char(36) NOT NULL UNIQUE," +
"`timestamp` datetime NOT NULL," +
"`inventory` longtext NOT NULL," +
"`ender_chest` longtext NOT NULL," +
"`health` double NOT NULL," +
"`max_health` double NOT NULL," +
"`hunger` double NOT NULL," +
"`saturation` double NOT NULL," +
"`hunger` integer NOT NULL," +
"`saturation` float NOT NULL," +
"`selected_slot` integer NOT NULL," +
"`status_effects` longtext NOT NULL," +
"PRIMARY KEY (`player_id`,`uuid`)," +
"FOREIGN KEY (`player_id`) REFERENCES " + PLAYER_TABLE_NAME + "(`id`)" +
"PRIMARY KEY (`player_id`,`version_uuid`)" +
");"
};
private static final String DATABASE_NAME = "CrossServerSyncData";
@ -80,9 +77,10 @@ public class SQLite extends Database {
createDatabaseFileIfNotExist();
// Create new HikariCP data source
final String jdbcUrl = "jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + "/" + DATABASE_NAME + ".db";
final String jdbcUrl = "jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + DATABASE_NAME + ".db";
dataSource = new HikariDataSource();
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setDataSourceClassName("org.sqlite.SQLiteDataSource");
dataSource.addDataSourceProperty("url", jdbcUrl);
// Set various additional parameters
dataSource.setMaximumPoolSize(hikariMaximumPoolSize);

@ -1,10 +1,10 @@
package me.william278.crossserversync.bungeecord.listener;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import me.william278.crossserversync.PlayerData;
import me.william278.crossserversync.bungeecord.data.DataManager;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
@ -20,16 +20,12 @@ public class BungeeEventListener implements Listener {
// Ensure the player has data on SQL
DataManager.ensurePlayerExists(player.getUniqueId());
// Get the player's data from SQL
final PlayerData data = DataManager.getPlayerData(player.getUniqueId());
// Update the player's data from SQL onto the cache
DataManager.playerDataCache.updatePlayer(DataManager.getPlayerData(player.getUniqueId()));
DataManager.playerDataCache.updatePlayer(data);
});
}
@EventHandler
public void onDisconnect(PlayerDisconnectEvent event) {
final ProxiedPlayer player = event.getPlayer();
// Remove the player's data from the cache
DataManager.playerDataCache.removePlayer(player.getUniqueId());
}
}

@ -1,16 +1,14 @@
package me.william278.crossserversync.bungeecord.listener;
import me.william278.crossserversync.CrossServerSyncBungeeCord;
import me.william278.crossserversync.PlayerData;
import me.william278.crossserversync.Settings;
import me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord;
import me.william278.crossserversync.bungeecord.data.DataManager;
import me.william278.crossserversync.redis.RedisListener;
import me.william278.crossserversync.redis.RedisMessage;
import net.md_5.bungee.api.ProxyServer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.UUID;
import java.util.logging.Level;
@ -66,17 +64,11 @@ public class BungeeRedisListener extends RedisListener {
});
}
case PLAYER_DATA_UPDATE -> {
// Get the update data
final String[] updateData = message.getMessageDataSeparated();
// Get UUID of the last-updated data on the spigot
final UUID lastDataUpdateUUID = UUID.fromString(updateData[0]);
// Deserialize the PlayerData
// Deserialize the PlayerData received
PlayerData playerData;
final String serializedPlayerData = updateData[1];
try (ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(serializedPlayerData.getBytes()))) {
playerData = (PlayerData) stream.readObject();
final String serializedPlayerData = message.getMessageData();
try {
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
} catch (IOException | ClassNotFoundException e) {
log(Level.SEVERE, "Failed to deserialize PlayerData when handling a player update request");
e.printStackTrace();
@ -84,7 +76,7 @@ public class BungeeRedisListener extends RedisListener {
}
// Update the data in the cache and SQL
DataManager.updatePlayerData(playerData, lastDataUpdateUUID);
DataManager.updatePlayerData(playerData);
}
}
}

@ -27,6 +27,4 @@ shadowJar {
// Relocations
relocate 'redis.clients', 'me.William278.crossserversync.libraries.jedis'
relocate 'org.apache.commons', 'me.William278.crossserversync.libraries.apache-commons'
relocate 'org.slf4j', 'me.William278.crossserversync.libraries.slf4j'
}

@ -15,41 +15,59 @@ public class PlayerData implements Serializable {
*/
private final UUID dataVersionUUID;
/**
* Serialized inventory data
*/
private final String serializedInventory;
/**
* Serialized ender chest data
*/
// Player data
private final String serializedInventory;
private final String serializedEnderChest;
private final double health;
private final double maxHealth;
private final int hunger;
private final float saturation;
private final int selectedSlot;
private final String serializedEffectData;
/**
* Create a new PlayerData object; a random data version UUID will be selected.
*
* @param playerUUID The UUID of the player
* @param serializedInventory The player's serialized inventory data
* @param playerUUID UUID of the player
* @param serializedInventory Serialized inventory data
* @param serializedEnderChest Serialized ender chest data
* @param health Player health
* @param maxHealth Player max health
* @param hunger Player hunger
* @param saturation Player saturation
* @param selectedSlot Player selected slot
* @param serializedStatusEffects Serialized status effect data
*/
//todo add more stuff, like player health, max health, hunger, saturation and status effects
public PlayerData(UUID playerUUID, String serializedInventory, String serializedEnderChest) {
public PlayerData(UUID playerUUID, String serializedInventory, String serializedEnderChest, double health, double maxHealth, int hunger, float saturation, int selectedSlot, String serializedStatusEffects) {
this.dataVersionUUID = UUID.randomUUID();
this.playerUUID = playerUUID;
this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest;
this.health = health;
this.maxHealth = maxHealth;
this.hunger = hunger;
this.saturation = saturation;
this.selectedSlot = selectedSlot;
this.serializedEffectData = serializedStatusEffects;
}
public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest, double health, double maxHealth, double hunger, double saturation, String serializedStatusEffects) {
public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest, double health, double maxHealth, int hunger, float saturation, int selectedSlot, String serializedStatusEffects) {
this.playerUUID = playerUUID;
this.dataVersionUUID = dataVersionUUID;
this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest;
//todo Incorporate more of these
this.health = health;
this.maxHealth = maxHealth;
this.hunger = hunger;
this.saturation = saturation;
this.selectedSlot = selectedSlot;
this.serializedEffectData = serializedStatusEffects;
}
public static PlayerData EMPTY_PLAYER_DATA(UUID playerUUID) {
return new PlayerData(playerUUID, "", "");
return new PlayerData(playerUUID, "", "", 20,
20, 20, 20, 0, "");
}
public UUID getPlayerUUID() {
@ -67,4 +85,28 @@ public class PlayerData implements Serializable {
public String getSerializedEnderChest() {
return serializedEnderChest;
}
public double getHealth() {
return health;
}
public double getMaxHealth() {
return maxHealth;
}
public int getHunger() {
return hunger;
}
public float getSaturation() {
return saturation;
}
public int getSelectedSlot() {
return selectedSlot;
}
public String getSerializedEffectData() {
return serializedEffectData;
}
}

@ -71,10 +71,6 @@ public class RedisMessage {
}
}
public String[] getMessageDataSeparated() {
return messageData.split(MESSAGE_DATA_SEPARATOR);
}
public String getMessageData() {
return messageData;
}
@ -90,7 +86,7 @@ public class RedisMessage {
/**
* Defines the type of the message
*/
public enum MessageType {
public enum MessageType implements Serializable {
/**
* Sent by Bukkit servers to proxy when a player disconnects with a player's updated data, alongside the UUID of the last loaded {@link PlayerData} for the user
*/
@ -111,7 +107,7 @@ public class RedisMessage {
* A record that defines the target of a plugin message; a spigot server or the proxy server(s).
* For Bukkit servers, the name of the server must also be specified
*/
public record MessageTarget(Settings.ServerType targetServerType, UUID targetPlayerName) implements Serializable { }
public record MessageTarget(Settings.ServerType targetServerType, UUID targetPlayerUUID) implements Serializable { }
/**
* Deserialize an object from a Base64 string

@ -1,5 +1,8 @@
name: CrossServerSync
version: @version@
main: me.william278.crossserversync.bungeecord.CrossServerSyncBungeeCord
main: me.william278.crossserversync.CrossServerSyncBungeeCord
author: William278
description: 'Synchronize data cross-server'
description: 'Synchronize data cross-server'
libraries:
- mysql:mysql-connector-java:8.0.25
- org.xerial:sqlite-jdbc:3.36.0.3

@ -1,6 +1,6 @@
name: CrossServerSync
version: @version@
main: me.william278.crossserversync.bukkit.CrossServerSyncBukkit
main: me.william278.crossserversync.CrossServerSyncBukkit
api-version: 1.16
author: William278
description: 'Synchronize data cross-server'
Loading…
Cancel
Save