9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-24 09:09:18 +00:00

Compare commits

...

38 Commits
1.3 ... 1.3.2

Author SHA1 Message Date
William
2d8f139940 Encode Javadocs with UTF-8 2022-02-16 00:23:48 +00:00
William
07452083cb Merge remote-tracking branch 'origin/master' 2022-02-12 15:36:32 +00:00
William
55ea6bc391 Add spanish locale credit 2022-02-12 15:36:27 +00:00
William
742a033cf7 Add spanish (es-es) localisation by anchelthe 2022-02-12 15:20:12 +00:00
William
a3d745898e Add localisation credit 2022-02-06 14:59:44 +00:00
William
25c4553dd8 Merge branch 'ja-jp' 2022-02-06 14:57:34 +00:00
William
049dcbe589 Fix rare EndOfStream exception with Jedis listener 2022-02-06 14:56:48 +00:00
Namiu/うにたろう
3ffa2dc0ca Create ja-jp.yml
Add japanese language file
2022-02-06 17:18:00 +09:00
William
9bf0fe7bb9 Use a continuous connection for pub/sub to avoid EndOfStreamException and increase exception logging verbosity 2022-02-05 15:15:04 +00:00
William
4ab5070043 Comment typo 2022-02-03 19:36:25 +00:00
William
1664f1bf66 Upgrade bStats to 3.0.0 2022-02-03 19:31:22 +00:00
Harvels X
1a45100907 Add Gradle parallel execution of tasks; 2022-02-02 18:10:41 +03:00
Harvels X
50b07721d9 Resolve conversations https://github.com/WiIIiam278/HuskSync/pull/16; 2022-02-02 17:50:11 +03:00
Harvels X
b450910b5a Fix meta separator in VersionUtils.toString; 2022-01-31 20:14:36 +03:00
Harvels X
28c14ed393 Edit: fix UpdateChecker version check;
Add: version utils;
2022-01-31 20:03:24 +03:00
Harvels X
3c50245540 Edit: relocations & excludes;
Refactoring modules configuration;
2022-01-31 18:23:01 +03:00
Harvels X
6ea8cdb75c Edit: move shadow plugin to global;
Add: shading main modules;
2022-01-31 18:21:31 +03:00
Harvels X
fd42bc99be Edit: move plugin manifests;
Edit: retrieve a version of velocity;
2022-01-31 18:18:10 +03:00
Harvels X
545c0896f0 Add: static project properties; 2022-01-31 16:02:25 +03:00
Harvels X
d67d5b64da Edit: remove use shadowing for apis; 2022-01-31 16:01:32 +03:00
Harvels X
83ddc76075 Edit: cleanup & move to submodule;
Add: filter resource for all projects & use maven style;
Edit: add revision or build number in project version;
2022-01-31 16:00:08 +03:00
William
06e72f0831 Merge remote-tracking branch 'origin/master' 2022-01-27 16:20:18 +00:00
William
c439ad59ac Fixed current timestamp being generated being incorrect 2022-01-27 16:19:56 +00:00
William
aa3e73ea33 Add velocity metrics to README.md 2022-01-24 13:49:40 +00:00
William
37520991e5 Conformity to Bukkit API conventions 2022-01-22 22:41:39 +00:00
William
804f156027 Bump version number 2022-01-22 22:38:37 +00:00
William
56ecb7f76a Use lowest event priority so HuskSync fires first 2022-01-22 22:37:16 +00:00
William
7a89ffdf35 Make event priority LOWEST 2022-01-22 22:32:44 +00:00
William
6719858de1 Add SSL connection option for Redis 2022-01-21 00:57:16 +00:00
William
920d2582f5 Update Redis initialization handling now that it is multithreaded 2022-01-20 18:36:33 +00:00
William
7d46ce076b Authenticate when creating the ConnectionPool 2022-01-20 18:17:25 +00:00
William
027ee0dbbb Escape version string 2022-01-19 17:51:36 +00:00
William
93be26a946 Use JedisPool instead of single Jedis connection 2022-01-19 17:29:25 +00:00
William
4ec4ba9a1e Fix description 2022-01-19 13:56:29 +00:00
William
6d31d28f47 Bump Jedis, fix status message missing a newline 2022-01-19 13:55:03 +00:00
William
de3838873e Merge remote-tracking branch 'origin/master' 2022-01-13 18:01:28 +00:00
William
f01bb7c082 Update author URL 2022-01-13 18:01:23 +00:00
William
051e2c5b72 Update README.md 2022-01-07 20:52:01 +00:00
39 changed files with 419 additions and 251 deletions

View File

@@ -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/v/WiIIiam278/HuskSync.svg)](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.

View File

@@ -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
}

View File

@@ -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')
}

View File

@@ -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'
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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

View File

@@ -26,6 +26,7 @@ public class BukkitRedisListener extends RedisListener {
// Initialize the listener on the bukkit server
public BukkitRedisListener() {
super();
listen();
}

View File

@@ -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;

View File

@@ -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)));

View File

@@ -2,6 +2,7 @@ redis_settings:
host: 'localhost'
port: 6379
password: ''
use_ssl: false
synchronisation_settings:
inventories: true
ender_chests: true

View File

@@ -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]

View File

@@ -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')
}
}

View File

@@ -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());

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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;

View File

@@ -24,6 +24,7 @@ public class BungeeRedisListener extends RedisListener {
// Initialize the listener on the bungee
public BungeeRedisListener() {
super();
listen();
}

View File

@@ -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'

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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)");
}

View File

@@ -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);

View File

@@ -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();
}
}
}

View 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)'

View 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)'

View File

@@ -3,6 +3,7 @@ redis_settings:
host: 'localhost'
port: 6379
password: ''
use_ssl: false
data_storage_settings:
database_type: 'sqlite'
mysql_settings:

View File

@@ -1 +1,5 @@
javaVersion=16
org.gradle.parallel=true
javaVersion=16
plugin_version=1.3.2
plugin_archive=husksync

View File

@@ -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
}

View File

@@ -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')
}
}

View File

@@ -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());

View File

@@ -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());

View File

@@ -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) {

View File

@@ -23,6 +23,7 @@ public class VelocityRedisListener extends RedisListener {
// Initialize the listener on the bungee
public VelocityRedisListener() {
super();
listen();
}

View 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"
}