mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 09:09:18 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d8f139940 | ||
|
|
07452083cb | ||
|
|
55ea6bc391 | ||
|
|
742a033cf7 | ||
|
|
a3d745898e | ||
|
|
25c4553dd8 | ||
|
|
049dcbe589 | ||
|
|
3ffa2dc0ca | ||
|
|
9bf0fe7bb9 | ||
|
|
4ab5070043 | ||
|
|
1664f1bf66 | ||
|
|
1a45100907 | ||
|
|
50b07721d9 | ||
|
|
b450910b5a | ||
|
|
28c14ed393 | ||
|
|
3c50245540 | ||
|
|
6ea8cdb75c | ||
|
|
fd42bc99be | ||
|
|
545c0896f0 | ||
|
|
d67d5b64da | ||
|
|
83ddc76075 | ||
|
|
06e72f0831 | ||
|
|
c439ad59ac | ||
|
|
aa3e73ea33 | ||
|
|
37520991e5 | ||
|
|
804f156027 | ||
|
|
56ecb7f76a | ||
|
|
7a89ffdf35 | ||
|
|
6719858de1 | ||
|
|
920d2582f5 | ||
|
|
7d46ce076b | ||
|
|
027ee0dbbb | ||
|
|
93be26a946 | ||
|
|
4ec4ba9a1e | ||
|
|
6d31d28f47 | ||
|
|
de3838873e | ||
|
|
f01bb7c082 | ||
|
|
051e2c5b72 |
11
README.md
11
README.md
@@ -149,7 +149,7 @@ Or, with Gradle, add the dependency like so to your build.gradle:
|
||||
```
|
||||
Then add the dependency as follows. Replace `version` with the latest version of HuskSync: [](https://jitpack.io/#WiIIiam278/HuskSync)
|
||||
```
|
||||
dependencies {
|
||||
dependencies {
|
||||
compileOnly 'com.github.WiIIiam278:HuskSync:version'
|
||||
}
|
||||
```
|
||||
@@ -160,7 +160,7 @@ Then add the dependency as follows. Replace `version` with the latest version of
|
||||
|
||||
#### Fetching player data on demand
|
||||
To fetch PlayerData from a UUID as you need it, create an instance of the HuskSyncAPI class and use the `#getPlayerData` method. Note that data returned in this method is only the data from the central cache. That is to say, if the player is online, the data returned in this way will not necessarily be the same as the player's actual current data.
|
||||
```
|
||||
```java
|
||||
HuskSyncAPI huskSyncApi = HuskSyncAPI.getInstance();
|
||||
try {
|
||||
CompletableFuture<PlayerData> playerDataCompletableFuture = huskSyncApi.getPlayerData(playerUUID);
|
||||
@@ -175,14 +175,14 @@ try {
|
||||
|
||||
#### Getting ItemStacks and usable data from PlayerData
|
||||
Use the static methods provided in the [DataSerializer class](https://javadoc.jitpack.io/com/github/WiIIiam278/HuskSync/latest/javadoc/me/william278/husksync/bukkit/data/DataSerializer.html). For instance, to get a player's inventory as an `ItemStack[]` from a `PlayerData` object.
|
||||
```
|
||||
```java
|
||||
ItemStack[] inventoryItems = DataSerializer.serializeInventory(playerData.getSerializedInventory());
|
||||
ItemStack[] enderChestItems = DataSerializer.serializeInventory(playerData.getSerializedEnderChest());
|
||||
```
|
||||
|
||||
#### Updating PlayerData
|
||||
You can then update PlayerData back to the central cache using the `HuskSyncAPI#updatePlayerData(playerData)` method. For example:
|
||||
```
|
||||
```java
|
||||
// Update a value in the player data object
|
||||
playerData.setHealth(20);
|
||||
try {
|
||||
@@ -215,10 +215,11 @@ Then, to build the plugin, run the following in the root of the repository:
|
||||
This plugin uses bStats to provide me with metrics about its usage:
|
||||
* [View Bukkit metrics](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140)
|
||||
* [View BungeeCord metrics](https://bstats.org/plugin/bungeecord/HuskSync%20-%20BungeeCord/13141)
|
||||
* [View Velocity metrics](https://bstats.org/plugin/velocity/HuskSync%20-%20Velocity/13489)
|
||||
|
||||
You can turn metric collection off by navigating to `plugins/bStats/config.yml` and editing the config to disable plugin metrics.
|
||||
|
||||
## Support
|
||||
* Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues)
|
||||
* Discord support: Join the [HuskHelp Discord](https://discord.gg/tVYhJfyDWG)!
|
||||
* Proof of purchase is required for support.
|
||||
* Proof of purchase is required for support.
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
//file:noinspection GroovyAssignabilityCheck
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
compileOnly project(path: ':common', configuration: 'shadow')
|
||||
|
||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.jetbrains:annotations:22.0.0'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
shadow.component(it)
|
||||
afterEvaluate {
|
||||
artifact javadocsJar
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier = null
|
||||
relocate ':common', 'me.william278.husksync'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
groupId = "${rootProject.group}.${rootProject.name.toLowerCase()}"
|
||||
artifactId = project.name
|
||||
|
||||
from components.java
|
||||
artifact javadocsJar
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task javadocs(type: Javadoc) {
|
||||
options.encoding = 'UTF-8'
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
source = project(':common').sourceSets.main.allJava
|
||||
source += project(':api').sourceSets.main.allJava
|
||||
@@ -39,6 +44,6 @@ task javadocs(type: Javadoc) {
|
||||
}
|
||||
|
||||
task javadocsJar(type: Jar, dependsOn: javadocs) {
|
||||
classifier = 'javadoc'
|
||||
archiveClassifier.set 'javadoc'
|
||||
from javadocs.destinationDir
|
||||
}
|
||||
67
build.gradle
67
build.gradle
@@ -1,33 +1,26 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
id 'org.ajoberstar.grgit' version '4.1.1'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group 'me.William278'
|
||||
version '1.3'
|
||||
group 'me.william278'
|
||||
version "$ext.plugin_version+${versionMetadata()}"
|
||||
|
||||
compileJava { options.encoding = 'UTF-8' }
|
||||
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }
|
||||
javadoc { options.encoding = 'UTF-8' }
|
||||
ext {
|
||||
set 'version', version.toString()
|
||||
}
|
||||
|
||||
logger.lifecycle('Building HuskSync v' + version.toString())
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
subprojects {
|
||||
allprojects {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
compileJava {
|
||||
options.release = 16
|
||||
}
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
javadoc.options.encoding = 'UTF-8'
|
||||
|
||||
compileJava.options.release.set 16
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -39,4 +32,40 @@ subprojects {
|
||||
maven { url 'https://repo.alessiodp.com/releases/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation('redis.clients:jedis:4.1.1') {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
exclude module: 'slf4j-api'
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
||||
tokens: rootProject.ext.properties
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
version rootProject.version
|
||||
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
||||
|
||||
if (['bukkit', 'bungeecord', 'velocity', 'plugin'].contains(project.name)) {
|
||||
shadowJar {
|
||||
destinationDirectory.set(file("$rootDir/target"))
|
||||
archiveClassifier.set('')
|
||||
}
|
||||
jar.dependsOn shadowJar
|
||||
clean.delete "$rootDir/target"
|
||||
}
|
||||
}
|
||||
|
||||
logger.lifecycle("Building HuskSync ${version} by William278")
|
||||
|
||||
@SuppressWarnings('GrMethodMayBeStatic')
|
||||
def versionMetadata() {
|
||||
if (grgit == null) {
|
||||
return System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown'
|
||||
}
|
||||
return 'rev.' + grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev')
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
compileOnly project(':api')
|
||||
implementation project(':api')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
|
||||
compileOnly 'redis.clients:jedis:3.7.1'
|
||||
implementation 'org.bstats:bstats-bukkit:2.2.1'
|
||||
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||
|
||||
compileOnly 'net.craftersland.data:bridge:4.0.1'
|
||||
@@ -13,8 +11,9 @@ dependencies {
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.bukkit'
|
||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard'
|
||||
}
|
||||
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
relocate 'redis.clients', 'me.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'me.william278.husksync.libraries'
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class HuskSyncBukkit extends JavaPlugin {
|
||||
@@ -32,6 +31,8 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
|
||||
public static BukkitDataCache bukkitCache;
|
||||
|
||||
public static BukkitRedisListener redisListener;
|
||||
|
||||
// Used for establishing a handshake with redis
|
||||
public static UUID serverUUID;
|
||||
|
||||
@@ -120,11 +121,7 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||
|
||||
// Initialize the redis listener
|
||||
if (!new BukkitRedisListener().isActiveAndEnabled) {
|
||||
getPluginLoader().disablePlugin(this);
|
||||
getLogger().severe("Failed to initialize Redis; disabling HuskSync (" + getServer().getName() + ") v" + getDescription().getVersion());
|
||||
return;
|
||||
}
|
||||
redisListener = new BukkitRedisListener();
|
||||
|
||||
// Ensure redis is connected; establish a handshake
|
||||
establishRedisHandshake();
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ConfigLoader {
|
||||
Settings.redisHost = config.getString("redis_settings.host", "localhost");
|
||||
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
||||
Settings.redisPassword = config.getString("redis_settings.password", "");
|
||||
Settings.redisSSL = config.getBoolean("redis_settings.use_ssl", false);
|
||||
|
||||
Settings.syncInventories = config.getBoolean("synchronisation_settings.inventories", true);
|
||||
Settings.syncEnderChests = config.getBoolean("synchronisation_settings.ender_chests", true);
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BukkitEventListener implements Listener {
|
||||
|
||||
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
// When a player leaves a Bukkit server
|
||||
final Player player = event.getPlayer();
|
||||
@@ -33,13 +33,14 @@ public class BukkitEventListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) return; // If the plugin has not been initialized correctly
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled)
|
||||
return; // If the plugin has not been initialized correctly
|
||||
|
||||
// Update the player's data
|
||||
PlayerSetter.updatePlayerData(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
if (!plugin.isEnabled()) return; // If the plugin has not been initialized correctly
|
||||
|
||||
@@ -49,7 +50,8 @@ public class BukkitEventListener implements Listener {
|
||||
// Mark the player as awaiting data fetch
|
||||
HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId());
|
||||
|
||||
if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) return; // If the data handshake has not been completed yet (or MySqlPlayerDataBridge is installed)
|
||||
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())) {
|
||||
@@ -76,7 +78,8 @@ public class BukkitEventListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClose(InventoryCloseEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) return; // If the plugin has not been initialized correctly
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId()))
|
||||
return; // If the plugin has not been initialized correctly
|
||||
|
||||
// When a player closes an Inventory
|
||||
final Player player = (Player) event.getPlayer();
|
||||
@@ -95,14 +98,14 @@ public class BukkitEventListener implements Listener {
|
||||
* Events to cancel if the player has not been set yet
|
||||
*/
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onDropItem(PlayerDropItemEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true); // If the plugin / player has not been set
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPickupItem(EntityPickupItemEvent event) {
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(player.getUniqueId())) {
|
||||
@@ -111,28 +114,28 @@ public class BukkitEventListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true); // If the plugin / player has not been set
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true); // If the plugin / player has not been set
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true); // If the plugin / player has not been set
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onInventoryOpen(InventoryOpenEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true); // If the plugin / player has not been set
|
||||
|
||||
@@ -26,6 +26,7 @@ public class BukkitRedisListener extends RedisListener {
|
||||
|
||||
// Initialize the listener on the bukkit server
|
||||
public BukkitRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public class PlayerSetter {
|
||||
private static double getMaxHealth(Player player) {
|
||||
double maxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue();
|
||||
|
||||
// If the player has additional health bonuses from synchronised potion effects, subtract these from this number as they are synchronised seperately
|
||||
// If the player has additional health bonuses from synchronised potion effects, subtract these from this number as they are synchronised separately
|
||||
if (player.hasPotionEffect(PotionEffectType.HEALTH_BOOST) && maxHealth > 20D) {
|
||||
PotionEffect healthBoostEffect = player.getPotionEffect(PotionEffectType.HEALTH_BOOST);
|
||||
assert healthBoostEffect != null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.william278.husksync.bukkit.util.nms;
|
||||
|
||||
import me.william278.husksync.util.ThrowSupplier;
|
||||
import me.william278.husksync.util.VersionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class MinecraftVersionUtils {
|
||||
@@ -8,27 +9,10 @@ public class MinecraftVersionUtils {
|
||||
public final static String CRAFTBUKKIT_PACKAGE_PATH = Bukkit.getServer().getClass().getPackage().getName();
|
||||
|
||||
public final static String PACKAGE_VERSION = CRAFTBUKKIT_PACKAGE_PATH.split("\\.")[3];
|
||||
public final static String MINECRAFT_PACKAGE = compare("1.17") < 0 ?
|
||||
public final static VersionUtils.Version SERVER_VERSION
|
||||
= VersionUtils.Version.of(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
public final static String MINECRAFT_PACKAGE = SERVER_VERSION.compareTo(VersionUtils.Version.of("1.17")) < 0 ?
|
||||
"net.minecraft.server.".concat(PACKAGE_VERSION) : "net.minecraft.server";
|
||||
public final static String SERVER_VERSION = Bukkit.getBukkitVersion().split("-")[0];
|
||||
|
||||
public static int compare(String version) {
|
||||
if (version == null || SERVER_VERSION == null) return 1;
|
||||
|
||||
String[] as = SERVER_VERSION.split("\\.");
|
||||
String[] bs = version.split("\\.");
|
||||
|
||||
int length = Math.max(as.length, bs.length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
int a = i < as.length ? Integer.parseInt(as[i]) : 0;
|
||||
int b = i < bs.length ? Integer.parseInt(bs[i]) : 0;
|
||||
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Class<?> getBukkitClass(String path) {
|
||||
return ThrowSupplier.get(() -> Class.forName(CRAFTBUKKIT_PACKAGE_PATH.concat(".").concat(path)));
|
||||
|
||||
@@ -2,6 +2,7 @@ redis_settings:
|
||||
host: 'localhost'
|
||||
port: 6379
|
||||
password: ''
|
||||
use_ssl: false
|
||||
synchronisation_settings:
|
||||
inventories: true
|
||||
ender_chests: true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
name: HuskSync
|
||||
version: @version@
|
||||
version: ${version}
|
||||
main: me.william278.husksync.HuskSyncBukkit
|
||||
api-version: 1.16
|
||||
author: William278
|
||||
description: 'A modern, cross-server player data synchronization system'
|
||||
website: 'https://william278.net'
|
||||
softdepend: [MysqlPlayerDataBridge]
|
||||
@@ -1,9 +1,8 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
|
||||
compileOnly 'redis.clients:jedis:3.7.1'
|
||||
implementation 'org.bstats:bstats-bungeecord:2.2.1'
|
||||
implementation 'com.zaxxer:HikariCP:5.0.1'
|
||||
implementation 'org.bstats:bstats-bungeecord:3.0.0'
|
||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||
implementation 'net.byteflux:libby-bungee:1.1.5'
|
||||
|
||||
@@ -11,10 +10,17 @@ dependencies {
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
|
||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.bungee'
|
||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard'
|
||||
relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.bungee'
|
||||
}
|
||||
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||
relocate 'net.byteflux', 'me.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
relocate 'redis.clients', 'me.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'me.william278.husksync.libraries'
|
||||
|
||||
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
|
||||
|
||||
dependencies {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
exclude dependency(':slf4j-api')
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package me.william278.husksync;
|
||||
import me.william278.husksync.bungeecord.command.BungeeCommand;
|
||||
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
||||
import me.william278.husksync.bungeecord.config.ConfigManager;
|
||||
import me.william278.husksync.proxy.data.DataManager;
|
||||
import me.william278.husksync.bungeecord.listener.BungeeEventListener;
|
||||
import me.william278.husksync.bungeecord.listener.BungeeRedisListener;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.bungeecord.util.BungeeLogger;
|
||||
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.proxy.data.DataManager;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.byteflux.libby.BungeeLibraryManager;
|
||||
@@ -48,6 +48,8 @@ public final class HuskSyncBungeeCord extends Plugin {
|
||||
|
||||
public static MPDBMigrator mpdbMigrator;
|
||||
|
||||
public static BungeeRedisListener redisListener;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
public Logger getBungeeLogger() {
|
||||
@@ -98,10 +100,7 @@ public final class HuskSyncBungeeCord extends Plugin {
|
||||
}
|
||||
|
||||
// Initialize the redis listener
|
||||
if (!new BungeeRedisListener().isActiveAndEnabled) {
|
||||
getBungeeLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion());
|
||||
return;
|
||||
}
|
||||
redisListener = new BungeeRedisListener();
|
||||
|
||||
// Register listener
|
||||
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
|
||||
|
||||
@@ -2,16 +2,16 @@ package me.william278.husksync.bungeecord.command;
|
||||
|
||||
import de.themoep.minedown.MineDown;
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.Server;
|
||||
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||
import me.william278.husksync.proxy.command.HuskSyncCommand;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Server;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
||||
import me.william278.husksync.bungeecord.config.ConfigManager;
|
||||
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.proxy.command.HuskSyncCommand;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@@ -301,7 +301,7 @@ public class BungeeCommand extends Command implements TabExecutor, HuskSyncComma
|
||||
HuskSyncBungeeCord.synchronisedServers)) {
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () ->
|
||||
HuskSyncBungeeCord.mpdbMigrator.executeMigrationOperations(HuskSyncBungeeCord.dataManager,
|
||||
HuskSyncBungeeCord.synchronisedServers));
|
||||
HuskSyncBungeeCord.synchronisedServers, HuskSyncBungeeCord.redisListener));
|
||||
}
|
||||
}
|
||||
default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent());
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ConfigLoader {
|
||||
Settings.redisHost = config.getString("redis_settings.host", "localhost");
|
||||
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
||||
Settings.redisPassword = config.getString("redis_settings.password", "");
|
||||
Settings.redisSSL = config.getBoolean("redis_settings.use_ssl", false);
|
||||
|
||||
Settings.dataStorageType = Settings.DataStorageType.valueOf(config.getString("data_storage_settings.database_type", "sqlite").toUpperCase());
|
||||
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -18,7 +19,7 @@ public class BungeeEventListener implements Listener {
|
||||
|
||||
private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance();
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
final ProxiedPlayer player = event.getPlayer();
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
@@ -26,7 +27,7 @@ public class BungeeEventListener implements Listener {
|
||||
HuskSyncBungeeCord.dataManager.ensurePlayerExists(player.getUniqueId(), player.getName());
|
||||
|
||||
// Get the player's data from SQL
|
||||
final Map<Settings.SynchronisationCluster,PlayerData> data = HuskSyncBungeeCord.dataManager.getPlayerData(player.getUniqueId());
|
||||
final Map<Settings.SynchronisationCluster, PlayerData> data = HuskSyncBungeeCord.dataManager.getPlayerData(player.getUniqueId());
|
||||
|
||||
// Update the player's data from SQL onto the cache
|
||||
assert data != null;
|
||||
|
||||
@@ -24,6 +24,7 @@ public class BungeeRedisListener extends RedisListener {
|
||||
|
||||
// Initialize the listener on the bungee
|
||||
public BungeeRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: HuskSync
|
||||
version: @version@
|
||||
version: ${version}
|
||||
main: me.william278.husksync.HuskSyncBungeeCord
|
||||
author: William278
|
||||
description: 'A modern, cross-server player data synchronization system'
|
||||
@@ -1,28 +1,7 @@
|
||||
dependencies {
|
||||
implementation 'redis.clients:jedis:3.7.1'
|
||||
implementation 'com.zaxxer:HikariCP:5.0.0'
|
||||
}
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
task updateVersion(type: Copy) {
|
||||
from('src/main/resources') {
|
||||
include 'plugin.yml'
|
||||
include 'bungee.yml'
|
||||
}
|
||||
into 'build/sources/resources/'
|
||||
filter(ReplaceTokens, tokens: [version: '' + project.version])
|
||||
}
|
||||
|
||||
processResources {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
dependsOn updateVersion
|
||||
from 'build/sources/resources'
|
||||
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependsOn processResources
|
||||
|
||||
// Exclude some unnecessary files
|
||||
exclude "**/module-info.class"
|
||||
exclude "module-info.class"
|
||||
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class Settings {
|
||||
public static String redisHost;
|
||||
public static int redisPort;
|
||||
public static String redisPassword;
|
||||
public static boolean redisSSL;
|
||||
|
||||
/*
|
||||
* Bungee / Proxy server-only settings
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.proxy.data.DataManager;
|
||||
import me.william278.husksync.proxy.data.sql.Database;
|
||||
import me.william278.husksync.proxy.data.sql.MySQL;
|
||||
import me.william278.husksync.redis.RedisListener;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.util.Logger;
|
||||
|
||||
@@ -95,7 +96,7 @@ public class MPDBMigrator {
|
||||
}
|
||||
|
||||
// Carry out the migration
|
||||
public void executeMigrationOperations(DataManager dataManager, HashSet<Server> synchronisedServers) {
|
||||
public void executeMigrationOperations(DataManager dataManager, HashSet<Server> synchronisedServers, RedisListener redisListener) {
|
||||
// Prepare the target database for insertion
|
||||
prepareTargetDatabase(dataManager);
|
||||
|
||||
@@ -109,7 +110,7 @@ public class MPDBMigrator {
|
||||
getExperienceData();
|
||||
|
||||
// Send the encoded data to the Bukkit servers for conversion
|
||||
sendEncodedData(synchronisedServers);
|
||||
sendEncodedData(synchronisedServers, redisListener);
|
||||
}
|
||||
|
||||
// Clear the new database out of current data
|
||||
@@ -200,7 +201,7 @@ public class MPDBMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEncodedData(HashSet<Server> synchronisedServers) {
|
||||
private void sendEncodedData(HashSet<Server> synchronisedServers, RedisListener redisListener) {
|
||||
for (Server processingServer : synchronisedServers) {
|
||||
if (processingServer.hasMySqlPlayerDataBridge()) {
|
||||
for (MPDBPlayerData data : mpdbPlayerData) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import me.william278.husksync.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -240,7 +239,7 @@ public class DataManager {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE " + cluster.dataTableName() + " SET `version_uuid`=?, `timestamp`=?, `inventory`=?, `ender_chest`=?, `health`=?, `max_health`=?, `health_scale`=?, `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 " + cluster.playerTableName() + " WHERE `uuid`=?);")) {
|
||||
statement.setString(1, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(2, new Timestamp(Instant.now().getEpochSecond()));
|
||||
statement.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
|
||||
statement.setString(3, playerData.getSerializedInventory());
|
||||
statement.setString(4, playerData.getSerializedEnderChest());
|
||||
statement.setDouble(5, playerData.getHealth()); // Health
|
||||
@@ -274,7 +273,7 @@ public class DataManager {
|
||||
"INSERT INTO " + cluster.dataTableName() + " (`player_id`,`version_uuid`,`timestamp`,`inventory`,`ender_chest`,`health`,`max_health`,`health_scale`,`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 " + cluster.playerTableName() + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) {
|
||||
statement.setString(1, playerData.getPlayerUUID().toString());
|
||||
statement.setString(2, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(3, new Timestamp(Instant.now().getEpochSecond()));
|
||||
statement.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
|
||||
statement.setString(4, playerData.getSerializedInventory());
|
||||
statement.setString(5, playerData.getSerializedEnderChest());
|
||||
statement.setDouble(6, playerData.getHealth()); // Health
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.william278.husksync.redis;
|
||||
|
||||
import me.william278.husksync.Settings;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisClientConfig;
|
||||
import redis.clients.jedis.JedisPubSub;
|
||||
import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -16,6 +15,35 @@ public abstract class RedisListener {
|
||||
*/
|
||||
public boolean isActiveAndEnabled;
|
||||
|
||||
/**
|
||||
* Pool of connections to the Redis server
|
||||
*/
|
||||
private static JedisPool jedisPool;
|
||||
|
||||
/**
|
||||
* Creates a new RedisListener and initialises the Redis connection
|
||||
*/
|
||||
public RedisListener() {
|
||||
JedisPoolConfig config = new JedisPoolConfig();
|
||||
config.setMaxIdle(0);
|
||||
config.setTestOnBorrow(true);
|
||||
config.setTestOnReturn(true);
|
||||
if (Settings.redisPassword.isEmpty()) {
|
||||
jedisPool = new JedisPool(config,
|
||||
Settings.redisHost,
|
||||
Settings.redisPort,
|
||||
0,
|
||||
Settings.redisSSL);
|
||||
} else {
|
||||
jedisPool = new JedisPool(config,
|
||||
Settings.redisHost,
|
||||
Settings.redisPort,
|
||||
0,
|
||||
Settings.redisPassword,
|
||||
Settings.redisSSL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming {@link RedisMessage}
|
||||
*
|
||||
@@ -31,41 +59,68 @@ public abstract class RedisListener {
|
||||
*/
|
||||
public abstract void log(Level level, String message);
|
||||
|
||||
/**
|
||||
* Fetch a connection to the Redis server from the JedisPool
|
||||
*
|
||||
* @return Jedis instance from the pool
|
||||
*/
|
||||
public static Jedis getJedisConnection() {
|
||||
return jedisPool.getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Redis listener
|
||||
*/
|
||||
public final void listen() {
|
||||
try (Jedis jedis = new Jedis(Settings.redisHost, Settings.redisPort)) {
|
||||
final String jedisPassword = Settings.redisPassword;
|
||||
jedis.connect();
|
||||
if (jedis.isConnected()) {
|
||||
if (!jedisPassword.equals("")) {
|
||||
jedis.auth(jedisPassword);
|
||||
}
|
||||
isActiveAndEnabled = true;
|
||||
log(Level.INFO, "Enabled Redis listener successfully!");
|
||||
new Thread(() -> jedis.subscribe(new JedisPubSub() {
|
||||
@Override
|
||||
public void onMessage(String channel, String message) {
|
||||
// Only accept messages to the HuskSync channel
|
||||
if (!channel.equals(RedisMessage.REDIS_CHANNEL)) {
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
isActiveAndEnabled = true;
|
||||
while (isActiveAndEnabled) {
|
||||
|
||||
// Handle the message
|
||||
try {
|
||||
handleMessage(new RedisMessage(message));
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
log(Level.SEVERE, "Failed to deserialize message target");
|
||||
Jedis subscriber;
|
||||
if (Settings.redisPassword.isEmpty()) {
|
||||
subscriber = new Jedis(Settings.redisHost,
|
||||
Settings.redisPort,
|
||||
0);
|
||||
} else {
|
||||
final JedisClientConfig config = DefaultJedisClientConfig.builder()
|
||||
.password(Settings.redisPassword)
|
||||
.timeoutMillis(0).build();
|
||||
|
||||
subscriber = new Jedis(Settings.redisHost,
|
||||
Settings.redisPort,
|
||||
config);
|
||||
}
|
||||
subscriber.connect();
|
||||
|
||||
log(Level.INFO, "Enabled Redis listener successfully!");
|
||||
try {
|
||||
subscriber.subscribe(new JedisPubSub() {
|
||||
@Override
|
||||
public void onMessage(String channel, String message) {
|
||||
// Only accept messages to the HuskSync channel
|
||||
if (!channel.equals(RedisMessage.REDIS_CHANNEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the message
|
||||
try {
|
||||
handleMessage(new RedisMessage(message));
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
log(Level.SEVERE, "Failed to deserialize message target");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, RedisMessage.REDIS_CHANNEL), "Redis Subscriber").start();
|
||||
} else {
|
||||
isActiveAndEnabled = false;
|
||||
log(Level.SEVERE, "Failed to initialize the redis listener!");
|
||||
}, RedisMessage.REDIS_CHANNEL);
|
||||
} catch (JedisConnectionException connectionException) {
|
||||
log(Level.SEVERE, "A connection exception occurred with the Jedis listener");
|
||||
connectionException.printStackTrace();
|
||||
} catch (JedisException jedisException) {
|
||||
isActiveAndEnabled = false;
|
||||
log(Level.SEVERE, "An exception occurred with the Jedis listener");
|
||||
jedisException.printStackTrace();
|
||||
} finally {
|
||||
subscriber.close();
|
||||
}
|
||||
}
|
||||
} catch (JedisException e) {
|
||||
log(Level.SEVERE, "Failed to establish a connection to the Redis server!");
|
||||
}
|
||||
}, "Redis Subscriber").start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ public class RedisMessage {
|
||||
|
||||
/**
|
||||
* Create a new RedisMessage
|
||||
* @param type The type of the message
|
||||
* @param target Who will receive this message
|
||||
*
|
||||
* @param type The type of the message
|
||||
* @param target Who will receive this message
|
||||
* @param messageData The message data elements
|
||||
*/
|
||||
public RedisMessage(MessageType type, MessageTarget target, String... messageData) {
|
||||
@@ -38,6 +39,7 @@ public class RedisMessage {
|
||||
|
||||
/**
|
||||
* Get a new RedisMessage from an incoming message string
|
||||
*
|
||||
* @param messageString The message string to parse
|
||||
*/
|
||||
public RedisMessage(String messageString) throws IOException, ClassNotFoundException {
|
||||
@@ -49,6 +51,7 @@ public class RedisMessage {
|
||||
|
||||
/**
|
||||
* Returns the full, formatted message string with type, target & data
|
||||
*
|
||||
* @return The fully formatted message
|
||||
*/
|
||||
private String getFullMessage() throws IOException {
|
||||
@@ -61,21 +64,18 @@ public class RedisMessage {
|
||||
* Send the redis message
|
||||
*/
|
||||
public void send() throws IOException {
|
||||
try (Jedis publisher = new Jedis(Settings.redisHost, Settings.redisPort)) {
|
||||
final String jedisPassword = Settings.redisPassword;
|
||||
publisher.connect();
|
||||
if (!jedisPassword.equals("")) {
|
||||
publisher.auth(jedisPassword);
|
||||
}
|
||||
publisher.publish(REDIS_CHANNEL, getFullMessage());
|
||||
}
|
||||
try (Jedis publisher = RedisListener.getJedisConnection()) {
|
||||
publisher.publish(REDIS_CHANNEL, getFullMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessageData() {
|
||||
return messageData;
|
||||
}
|
||||
|
||||
public String[] getMessageDataElements() { return messageData.split(MESSAGE_DATA_SEPARATOR); }
|
||||
public String[] getMessageDataElements() {
|
||||
return messageData.split(MESSAGE_DATA_SEPARATOR);
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return messageType;
|
||||
@@ -173,7 +173,9 @@ 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 targetPlayerUUID, String targetClusterId) implements Serializable { }
|
||||
public record MessageTarget(Settings.ServerType targetServerType, UUID targetPlayerUUID,
|
||||
String targetClusterId) implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an object from a Base64 string
|
||||
|
||||
@@ -16,14 +16,15 @@ public class MessageManager {
|
||||
|
||||
public static StringBuilder PLUGIN_INFORMATION = new StringBuilder().append("[HuskSync](#00fb9a bold) [| %proxy_brand% Version %proxy_version% (%bukkit_brand% v%bukkit_version%)](#00fb9a)\n")
|
||||
.append("[%plugin_description%](gray)\n")
|
||||
.append("[• Author:](white) [William278](gray show_text=&7Click to pay a visit open_url=https://youtube.com/William27528)\n")
|
||||
.append("[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)\n")
|
||||
.append("[• Contributors:](white) [HarvelsX](gray show_text=&7Code)\n")
|
||||
.append("[• Translators:](white) [Namiu/うにたろう](gray show_text=&7Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es)\n")
|
||||
.append("[• Plugin Info:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/)\n")
|
||||
.append("[• Report Issues:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)\n")
|
||||
.append("[• Support Discord:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)");
|
||||
|
||||
public static StringBuilder PLUGIN_STATUS = new StringBuilder().append("[HuskSync](#00fb9a bold) [| Current system status:](#00fb9a)\n")
|
||||
.append("[• Connected servers:](white) [%1%](#00fb9a)")
|
||||
.append("[• Connected servers:](white) [%1%](#00fb9a)\n")
|
||||
.append("[• Cached player data:](white) [%2%](#00fb9a)");
|
||||
|
||||
}
|
||||
@@ -11,38 +11,35 @@ public abstract class UpdateChecker {
|
||||
|
||||
private final static int SPIGOT_PROJECT_ID = 97144;
|
||||
|
||||
private final String currentVersion;
|
||||
private String latestVersion;
|
||||
private final VersionUtils.Version currentVersion;
|
||||
private VersionUtils.Version latestVersion;
|
||||
|
||||
public UpdateChecker(String currentVersion) {
|
||||
this.currentVersion = currentVersion;
|
||||
this.currentVersion = VersionUtils.Version.of(currentVersion);
|
||||
|
||||
try {
|
||||
final URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + SPIGOT_PROJECT_ID);
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
this.latestVersion = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).readLine();
|
||||
this.latestVersion = VersionUtils.Version.of(new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).readLine());
|
||||
} catch (IOException e) {
|
||||
log(Level.WARNING, "Failed to check for updates: An IOException occurred.");
|
||||
this.latestVersion = "Unknown";
|
||||
this.latestVersion = new VersionUtils.Version();
|
||||
} catch (Exception e) {
|
||||
log(Level.WARNING, "Failed to check for updates: An exception occurred.");
|
||||
this.latestVersion = "Unknown";
|
||||
this.latestVersion = new VersionUtils.Version();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUpToDate() {
|
||||
if (latestVersion.equalsIgnoreCase("Unknown")) {
|
||||
return true;
|
||||
}
|
||||
return latestVersion.equals(currentVersion);
|
||||
return this.currentVersion.compareTo(latestVersion) >= 0;
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion;
|
||||
return latestVersion.toString();
|
||||
}
|
||||
|
||||
public String getCurrentVersion() {
|
||||
return currentVersion;
|
||||
return currentVersion.toString();
|
||||
}
|
||||
|
||||
public abstract void log(Level level, String message);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package me.william278.husksync.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class VersionUtils {
|
||||
|
||||
private final static char META_SEPARATOR = '+';
|
||||
private final static String VERSION_SEPARATOR = "\\.";
|
||||
|
||||
|
||||
public static class Version implements Comparable<Version> {
|
||||
public int[] versions = new int[]{};
|
||||
public String metadata = "";
|
||||
|
||||
public Version() {
|
||||
}
|
||||
|
||||
public Version(String version) {
|
||||
this.parse(version);
|
||||
}
|
||||
|
||||
public static Version of(String version) {
|
||||
return new Version(version);
|
||||
}
|
||||
|
||||
private void parse(String version) {
|
||||
int metaIndex = version.indexOf(META_SEPARATOR);
|
||||
if (metaIndex > 0) {
|
||||
this.metadata = version.substring(metaIndex + 1);
|
||||
version = version.substring(0, metaIndex);
|
||||
}
|
||||
String[] versions = version.split(VERSION_SEPARATOR);
|
||||
this.versions = Arrays.stream(versions).mapToInt(Integer::parseInt).toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Version version) {
|
||||
int length = Math.max(this.versions.length, version.versions.length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
int a = i < this.versions.length ? this.versions[i] : 0;
|
||||
int b = i < version.versions.length ? version.versions[i] : 0;
|
||||
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
for (int version : this.versions) {
|
||||
stringBuffer.append(version).append('.');
|
||||
}
|
||||
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
|
||||
return stringBuffer.append('+').append(this.metadata).toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
common/src/main/resources/languages/es-es.yml
Normal file
14
common/src/main/resources/languages/es-es.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[Datos sincronizados!](#00fb9a)'
|
||||
viewing_inventory_of: '[Viendo el inventario de](#00fb9a) [%1%](#00fb9a bold)'
|
||||
viewing_ender_chest_of: '[Viendo el Ender Chest de](#00fb9a) [%1%](#00fb9a bold)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Se ha reiniciado la configuración y los archivos de los mensajes.](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [Sintaxis incorrecta. Uso: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Error:](#ff3300) [No se ha podido encontrar a ese jugador](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [No tienes permiso para ejecutar este comando](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[Error:](#ff3300) [A traves de HuskSync no puedes acceder al inventario de un jugador conectado](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[Error:](#ff3300) [A traves de HuskSync no puedes acceder al Ender Chest de un jugador conectado.](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[Error:](#ff3300) [No puedes acceder a tu inventario!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[Error:](#ff3300) [No puedes acceder a tu Ender Chest!](#ff7e5e)'
|
||||
error_console_command_only: '[Error:](#ff3300) [Ese comando solo puede ser ejecutado desde la %1% consola](#ff7e5e)'
|
||||
error_no_servers_proxied: '[Error:](#ff3300) [Ha ocurrido un error mientras se procesaba la acción; no hay servidores online con HusckSync instalado. Por favor, asegúrate que HuskSync está instalado tanto en el proxy como en todos los servidores entre los que quieres sincronizar datos.](#ff7e5e)'
|
||||
error_invalid_cluster: '[Error:](#ff3300) [Por favor, especifica la ID de un cluster válido.](#ff7e5e)'
|
||||
14
common/src/main/resources/languages/ja-jp.yml
Normal file
14
common/src/main/resources/languages/ja-jp.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[データが同期されました!](#00fb9a)'
|
||||
viewing_inventory_of: '[%1%](#00fb9a bold) [のインベントリを表示します](#00fb9a) '
|
||||
viewing_ender_chest_of: '[%1%](#00fb9a bold) [のエンダーチェストを表示します](#00fb9a) '
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Error:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[Error:](#ff3300) [HuskSyncからオンラインプレイヤーのインベントリにはアクセスできません](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[Error:](#ff3300) [HuskSyncからオンラインプレイヤーのエンダーチェストにはアクセスできません](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[Error:](#ff3300) [自分のインベントリにはアクセスできません!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[Error:](#ff3300) [自分のエンダーチェストにはアクセスできません!](#ff7e5e)'
|
||||
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
||||
error_no_servers_proxied: '[Error:](#ff3300) [操作の処理に失敗; HuskSyncがインストールされているサーバーがオンラインになっていません。プロキシサーバーとデータを同期させたいすべてのサーバーにHuskSyncがインストールされていることを確認してください。](#ff7e5e)'
|
||||
error_invalid_cluster: '[Error:](#ff3300) [有効なクラスターのIDを指定してください。](#ff7e5e)'
|
||||
@@ -3,6 +3,7 @@ redis_settings:
|
||||
host: 'localhost'
|
||||
port: 6379
|
||||
password: ''
|
||||
use_ssl: false
|
||||
data_storage_settings:
|
||||
database_type: 'sqlite'
|
||||
mysql_settings:
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
javaVersion=16
|
||||
org.gradle.parallel=true
|
||||
javaVersion=16
|
||||
|
||||
plugin_version=1.3.2
|
||||
plugin_archive=husksync
|
||||
@@ -1,30 +1,27 @@
|
||||
//file:noinspection GroovyAssignabilityCheck
|
||||
plugins {
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
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')
|
||||
implementation project(path: ":velocity", configuration: 'shadow')
|
||||
implementation project(path: ':bukkit', configuration: 'shadow')
|
||||
implementation project(path: ':bungeecord', configuration: 'shadow')
|
||||
implementation project(path: ':velocity', configuration: 'shadow')
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// Relocations
|
||||
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
|
||||
|
||||
destinationDirectory.set(file("$rootDir/target/"))
|
||||
archiveBaseName.set('HuskSync')
|
||||
archiveClassifier.set('')
|
||||
|
||||
build {
|
||||
dependsOn tasks.named("shadowJar")
|
||||
dependencies {
|
||||
exclude dependency(':jedis')
|
||||
exclude dependency(':commons-pool2')
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
groupId = 'me.William278'
|
||||
artifactId = 'HuskSync-plugin'
|
||||
version = "$project.version"
|
||||
groupId = 'me.william278'
|
||||
artifactId = 'husksync-plugin'
|
||||
version = "$rootProject.version"
|
||||
|
||||
artifact shadowJar
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
|
||||
compileOnly 'redis.clients:jedis:3.7.1'
|
||||
implementation 'org.bstats:bstats-velocity:2.2.1'
|
||||
implementation 'com.zaxxer:HikariCP:5.0.1'
|
||||
implementation 'org.bstats:bstats-velocity:3.0.0'
|
||||
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||
implementation 'net.byteflux:libby-velocity:1.1.5'
|
||||
|
||||
compileOnly 'com.velocitypowered:velocity-api:3.1.0'
|
||||
annotationProcessor 'com.velocitypowered:velocity-api:3.1.0'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
|
||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.velocity'
|
||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.adventure'
|
||||
relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.velocity'
|
||||
}
|
||||
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||
relocate 'net.byteflux', 'me.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
relocate 'redis.clients', 'me.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'me.william278.husksync.libraries'
|
||||
|
||||
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
|
||||
|
||||
dependencies {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
exclude dependency(':slf4j-api')
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
@@ -31,19 +32,11 @@ import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static me.william278.husksync.HuskSyncVelocity.VERSION;
|
||||
|
||||
@Plugin(
|
||||
id = "husksync",
|
||||
name = "HuskSync",
|
||||
version = VERSION,
|
||||
description = "HuskSync for velocity",
|
||||
authors = {"William278"}
|
||||
)
|
||||
@Plugin(id = "husksync")
|
||||
public class HuskSyncVelocity {
|
||||
|
||||
// Plugin version
|
||||
public static final String VERSION = "1.3";
|
||||
public static String VERSION = null;
|
||||
|
||||
// Velocity bStats ID (different from Bukkit and BungeeCord)
|
||||
private static final int METRICS_ID = 13489;
|
||||
@@ -68,6 +61,8 @@ public class HuskSyncVelocity {
|
||||
|
||||
public static DataManager dataManager;
|
||||
|
||||
public static VelocityRedisListener redisListener;
|
||||
|
||||
public static MPDBMigrator mpdbMigrator;
|
||||
|
||||
private final Logger logger;
|
||||
@@ -92,11 +87,13 @@ public class HuskSyncVelocity {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) {
|
||||
public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory, PluginContainer pluginContainer) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
this.dataDirectory = dataDirectory;
|
||||
this.metricsFactory = metricsFactory;
|
||||
|
||||
pluginContainer.getDescription().getVersion().ifPresent(s -> VERSION = s);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -145,10 +142,7 @@ public class HuskSyncVelocity {
|
||||
}
|
||||
|
||||
// Initialize the redis listener
|
||||
if (!new VelocityRedisListener().isActiveAndEnabled) {
|
||||
getVelocityLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (Velocity) v" + VERSION);
|
||||
return;
|
||||
}
|
||||
redisListener = new VelocityRedisListener();
|
||||
|
||||
// Register listener
|
||||
server.getEventManager().register(this, new VelocityEventListener());
|
||||
|
||||
@@ -294,7 +294,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand {
|
||||
HuskSyncVelocity.synchronisedServers)) {
|
||||
plugin.getProxyServer().getScheduler().buildTask(plugin, () ->
|
||||
HuskSyncVelocity.mpdbMigrator.executeMigrationOperations(HuskSyncVelocity.dataManager,
|
||||
HuskSyncVelocity.synchronisedServers)).schedule();
|
||||
HuskSyncVelocity.synchronisedServers, HuskSyncVelocity.redisListener)).schedule();
|
||||
}
|
||||
}
|
||||
default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent());
|
||||
|
||||
@@ -31,25 +31,24 @@ public class ConfigLoader {
|
||||
}
|
||||
|
||||
private static String getConfigString(ConfigurationNode rootNode, String defaultValue, String... nodePath) {
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getString() : defaultValue;
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[]) nodePath).getString() : defaultValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static boolean getConfigBoolean(ConfigurationNode rootNode, boolean defaultValue, String... nodePath) {
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getBoolean() : defaultValue;
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[]) nodePath).getBoolean() : defaultValue;
|
||||
}
|
||||
|
||||
private static int getConfigInt(ConfigurationNode rootNode, int defaultValue, String... nodePath) {
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getInt() : defaultValue;
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[]) nodePath).getInt() : defaultValue;
|
||||
}
|
||||
|
||||
private static long getConfigLong(ConfigurationNode rootNode, long defaultValue, String... nodePath) {
|
||||
return !rootNode.getNode((Object[])nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getLong() : defaultValue;
|
||||
return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[]) nodePath).getLong() : defaultValue;
|
||||
}
|
||||
|
||||
public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException {
|
||||
ConfigurationNode config = copyDefaults(loadedConfig);
|
||||
//ConfigurationNode config = copyDefaults(loadedConfig);
|
||||
|
||||
Settings.language = getConfigString(config, "en-gb", "language");
|
||||
|
||||
@@ -58,6 +57,7 @@ public class ConfigLoader {
|
||||
Settings.redisHost = getConfigString(config, "localhost", "redis_settings", "host");
|
||||
Settings.redisPort = getConfigInt(config, 6379, "redis_settings", "port");
|
||||
Settings.redisPassword = getConfigString(config, "", "redis_settings", "password");
|
||||
Settings.redisSSL = getConfigBoolean(config, false, "redis_settings", "use_ssl");
|
||||
|
||||
Settings.dataStorageType = Settings.DataStorageType.valueOf(getConfigString(config, "sqlite", "data_storage_settings", "database_type").toUpperCase());
|
||||
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
||||
|
||||
@@ -23,6 +23,7 @@ public class VelocityRedisListener extends RedisListener {
|
||||
|
||||
// Initialize the listener on the bungee
|
||||
public VelocityRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
|
||||
12
velocity/src/main/resources/velocity-plugin.json
Normal file
12
velocity/src/main/resources/velocity-plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "husksync",
|
||||
"name": "HuskSync",
|
||||
"version": "${version}",
|
||||
"description": "A modern, cross-server player data synchronization system",
|
||||
"url": "https://william278.net",
|
||||
"authors": [
|
||||
"William278"
|
||||
],
|
||||
"dependencies": [],
|
||||
"main": "me.william278.husksync.HuskSyncVelocity"
|
||||
}
|
||||
Reference in New Issue
Block a user