mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-26 01:59:20 +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)
|
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'
|
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
|
#### 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.
|
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();
|
HuskSyncAPI huskSyncApi = HuskSyncAPI.getInstance();
|
||||||
try {
|
try {
|
||||||
CompletableFuture<PlayerData> playerDataCompletableFuture = huskSyncApi.getPlayerData(playerUUID);
|
CompletableFuture<PlayerData> playerDataCompletableFuture = huskSyncApi.getPlayerData(playerUUID);
|
||||||
@@ -175,14 +175,14 @@ try {
|
|||||||
|
|
||||||
#### Getting ItemStacks and usable data from PlayerData
|
#### 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.
|
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[] inventoryItems = DataSerializer.serializeInventory(playerData.getSerializedInventory());
|
||||||
ItemStack[] enderChestItems = DataSerializer.serializeInventory(playerData.getSerializedEnderChest());
|
ItemStack[] enderChestItems = DataSerializer.serializeInventory(playerData.getSerializedEnderChest());
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Updating PlayerData
|
#### Updating PlayerData
|
||||||
You can then update PlayerData back to the central cache using the `HuskSyncAPI#updatePlayerData(playerData)` method. For example:
|
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
|
// Update a value in the player data object
|
||||||
playerData.setHealth(20);
|
playerData.setHealth(20);
|
||||||
try {
|
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:
|
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 Bukkit metrics](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140)
|
||||||
* [View BungeeCord metrics](https://bstats.org/plugin/bungeecord/HuskSync%20-%20BungeeCord/13141)
|
* [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.
|
You can turn metric collection off by navigating to `plugins/bStats/config.yml` and editing the config to disable plugin metrics.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
* Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues)
|
* Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues)
|
||||||
* Discord support: Join the [HuskHelp Discord](https://discord.gg/tVYhJfyDWG)!
|
* 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 {
|
dependencies {
|
||||||
implementation project(':common')
|
compileOnly project(path: ':common', configuration: 'shadow')
|
||||||
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||||
compileOnly 'org.jetbrains:annotations:22.0.0'
|
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 {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
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) {
|
task javadocs(type: Javadoc) {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
options.addStringOption('Xdoclint:none', '-quiet')
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
source = project(':common').sourceSets.main.allJava
|
source = project(':common').sourceSets.main.allJava
|
||||||
source += project(':api').sourceSets.main.allJava
|
source += project(':api').sourceSets.main.allJava
|
||||||
@@ -39,6 +44,6 @@ task javadocs(type: Javadoc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task javadocsJar(type: Jar, dependsOn: javadocs) {
|
task javadocsJar(type: Jar, dependsOn: javadocs) {
|
||||||
classifier = 'javadoc'
|
archiveClassifier.set 'javadoc'
|
||||||
from javadocs.destinationDir
|
from javadocs.destinationDir
|
||||||
}
|
}
|
||||||
67
build.gradle
67
build.gradle
@@ -1,33 +1,26 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
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'
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
group 'me.william278'
|
||||||
group 'me.William278'
|
version "$ext.plugin_version+${versionMetadata()}"
|
||||||
version '1.3'
|
|
||||||
|
|
||||||
compileJava { options.encoding = 'UTF-8' }
|
ext {
|
||||||
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }
|
set 'version', version.toString()
|
||||||
javadoc { options.encoding = 'UTF-8' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.lifecycle('Building HuskSync v' + version.toString())
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
|
||||||
subprojects {
|
allprojects {
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'maven-publish'
|
|
||||||
|
|
||||||
compileJava {
|
compileJava.options.encoding = 'UTF-8'
|
||||||
options.release = 16
|
javadoc.options.encoding = 'UTF-8'
|
||||||
}
|
|
||||||
|
compileJava.options.release.set 16
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -39,4 +32,40 @@ subprojects {
|
|||||||
maven { url 'https://repo.alessiodp.com/releases/' }
|
maven { url 'https://repo.alessiodp.com/releases/' }
|
||||||
maven { url 'https://jitpack.io' }
|
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 {
|
dependencies {
|
||||||
compileOnly project(':common')
|
implementation project(':api')
|
||||||
compileOnly project(':api')
|
|
||||||
implementation project(path: ':common', configuration: 'shadow')
|
implementation project(path: ':common', configuration: 'shadow')
|
||||||
|
|
||||||
compileOnly 'redis.clients:jedis:3.7.1'
|
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
||||||
implementation 'org.bstats:bstats-bukkit:2.2.1'
|
|
||||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||||
|
|
||||||
compileOnly 'net.craftersland.data:bridge:4.0.1'
|
compileOnly 'net.craftersland.data:bridge:4.0.1'
|
||||||
@@ -13,8 +11,9 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.bukkit'
|
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard'
|
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.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public final class HuskSyncBukkit extends JavaPlugin {
|
public final class HuskSyncBukkit extends JavaPlugin {
|
||||||
@@ -32,6 +31,8 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
|||||||
|
|
||||||
public static BukkitDataCache bukkitCache;
|
public static BukkitDataCache bukkitCache;
|
||||||
|
|
||||||
|
public static BukkitRedisListener redisListener;
|
||||||
|
|
||||||
// Used for establishing a handshake with redis
|
// Used for establishing a handshake with redis
|
||||||
public static UUID serverUUID;
|
public static UUID serverUUID;
|
||||||
|
|
||||||
@@ -120,11 +121,7 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
|||||||
getServer().getPluginManager().registerEvents(new BukkitEventListener(), this);
|
getServer().getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||||
|
|
||||||
// Initialize the redis listener
|
// Initialize the redis listener
|
||||||
if (!new BukkitRedisListener().isActiveAndEnabled) {
|
redisListener = new BukkitRedisListener();
|
||||||
getPluginLoader().disablePlugin(this);
|
|
||||||
getLogger().severe("Failed to initialize Redis; disabling HuskSync (" + getServer().getName() + ") v" + getDescription().getVersion());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure redis is connected; establish a handshake
|
// Ensure redis is connected; establish a handshake
|
||||||
establishRedisHandshake();
|
establishRedisHandshake();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class ConfigLoader {
|
|||||||
Settings.redisHost = config.getString("redis_settings.host", "localhost");
|
Settings.redisHost = config.getString("redis_settings.host", "localhost");
|
||||||
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
||||||
Settings.redisPassword = config.getString("redis_settings.password", "");
|
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.syncInventories = config.getBoolean("synchronisation_settings.inventories", true);
|
||||||
Settings.syncEnderChests = config.getBoolean("synchronisation_settings.ender_chests", 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();
|
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
// When a player leaves a Bukkit server
|
// When a player leaves a Bukkit server
|
||||||
final Player player = event.getPlayer();
|
final Player player = event.getPlayer();
|
||||||
@@ -33,13 +33,14 @@ public class BukkitEventListener implements Listener {
|
|||||||
return;
|
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
|
// Update the player's data
|
||||||
PlayerSetter.updatePlayerData(player);
|
PlayerSetter.updatePlayerData(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
if (!plugin.isEnabled()) return; // If the plugin has not been initialized correctly
|
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
|
// Mark the player as awaiting data fetch
|
||||||
HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId());
|
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)
|
// Send a redis message requesting the player data (if they need to)
|
||||||
if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) {
|
if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) {
|
||||||
@@ -76,7 +78,8 @@ public class BukkitEventListener implements Listener {
|
|||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClose(InventoryCloseEvent event) {
|
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
|
// When a player closes an Inventory
|
||||||
final Player player = (Player) event.getPlayer();
|
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
|
* Events to cancel if the player has not been set yet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onDropItem(PlayerDropItemEvent event) {
|
public void onDropItem(PlayerDropItemEvent event) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||||
event.setCancelled(true); // If the plugin / player has not been set
|
event.setCancelled(true); // If the plugin / player has not been set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onPickupItem(EntityPickupItemEvent event) {
|
public void onPickupItem(EntityPickupItemEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(player.getUniqueId())) {
|
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) {
|
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||||
event.setCancelled(true); // If the plugin / player has not been set
|
event.setCancelled(true); // If the plugin / player has not been set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onBlockPlace(BlockPlaceEvent event) {
|
public void onBlockPlace(BlockPlaceEvent event) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||||
event.setCancelled(true); // If the plugin / player has not been set
|
event.setCancelled(true); // If the plugin / player has not been set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onBlockBreak(BlockBreakEvent event) {
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||||
event.setCancelled(true); // If the plugin / player has not been set
|
event.setCancelled(true); // If the plugin / player has not been set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
public void onInventoryOpen(InventoryOpenEvent event) {
|
public void onInventoryOpen(InventoryOpenEvent event) {
|
||||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(event.getPlayer().getUniqueId())) {
|
||||||
event.setCancelled(true); // If the plugin / player has not been set
|
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
|
// Initialize the listener on the bukkit server
|
||||||
public BukkitRedisListener() {
|
public BukkitRedisListener() {
|
||||||
|
super();
|
||||||
listen();
|
listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class PlayerSetter {
|
|||||||
private static double getMaxHealth(Player player) {
|
private static double getMaxHealth(Player player) {
|
||||||
double maxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue();
|
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) {
|
if (player.hasPotionEffect(PotionEffectType.HEALTH_BOOST) && maxHealth > 20D) {
|
||||||
PotionEffect healthBoostEffect = player.getPotionEffect(PotionEffectType.HEALTH_BOOST);
|
PotionEffect healthBoostEffect = player.getPotionEffect(PotionEffectType.HEALTH_BOOST);
|
||||||
assert healthBoostEffect != null;
|
assert healthBoostEffect != null;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.william278.husksync.bukkit.util.nms;
|
package me.william278.husksync.bukkit.util.nms;
|
||||||
|
|
||||||
import me.william278.husksync.util.ThrowSupplier;
|
import me.william278.husksync.util.ThrowSupplier;
|
||||||
|
import me.william278.husksync.util.VersionUtils;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
public class MinecraftVersionUtils {
|
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 CRAFTBUKKIT_PACKAGE_PATH = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
|
|
||||||
public final static String PACKAGE_VERSION = CRAFTBUKKIT_PACKAGE_PATH.split("\\.")[3];
|
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";
|
"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) {
|
public static Class<?> getBukkitClass(String path) {
|
||||||
return ThrowSupplier.get(() -> Class.forName(CRAFTBUKKIT_PACKAGE_PATH.concat(".").concat(path)));
|
return ThrowSupplier.get(() -> Class.forName(CRAFTBUKKIT_PACKAGE_PATH.concat(".").concat(path)));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ redis_settings:
|
|||||||
host: 'localhost'
|
host: 'localhost'
|
||||||
port: 6379
|
port: 6379
|
||||||
password: ''
|
password: ''
|
||||||
|
use_ssl: false
|
||||||
synchronisation_settings:
|
synchronisation_settings:
|
||||||
inventories: true
|
inventories: true
|
||||||
ender_chests: true
|
ender_chests: true
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
name: HuskSync
|
name: HuskSync
|
||||||
version: @version@
|
version: ${version}
|
||||||
main: me.william278.husksync.HuskSyncBukkit
|
main: me.william278.husksync.HuskSyncBukkit
|
||||||
api-version: 1.16
|
api-version: 1.16
|
||||||
author: William278
|
author: William278
|
||||||
description: 'A modern, cross-server player data synchronization system'
|
description: 'A modern, cross-server player data synchronization system'
|
||||||
|
website: 'https://william278.net'
|
||||||
softdepend: [MysqlPlayerDataBridge]
|
softdepend: [MysqlPlayerDataBridge]
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':common')
|
|
||||||
implementation project(path: ':common', configuration: 'shadow')
|
implementation project(path: ':common', configuration: 'shadow')
|
||||||
|
|
||||||
compileOnly 'redis.clients:jedis:3.7.1'
|
implementation 'com.zaxxer:HikariCP:5.0.1'
|
||||||
implementation 'org.bstats:bstats-bungeecord:2.2.1'
|
implementation 'org.bstats:bstats-bungeecord:3.0.0'
|
||||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||||
implementation 'net.byteflux:libby-bungee:1.1.5'
|
implementation 'net.byteflux:libby-bungee:1.1.5'
|
||||||
|
|
||||||
@@ -11,10 +10,17 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
|
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.bungee'
|
relocate 'net.byteflux', 'me.william278.husksync.libraries'
|
||||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard'
|
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
|
||||||
relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.bungee'
|
|
||||||
}
|
|
||||||
|
|
||||||
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.command.BungeeCommand;
|
||||||
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
||||||
import me.william278.husksync.bungeecord.config.ConfigManager;
|
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.BungeeEventListener;
|
||||||
import me.william278.husksync.bungeecord.listener.BungeeRedisListener;
|
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.BungeeLogger;
|
||||||
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
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.redis.RedisMessage;
|
||||||
import me.william278.husksync.util.Logger;
|
import me.william278.husksync.util.Logger;
|
||||||
import net.byteflux.libby.BungeeLibraryManager;
|
import net.byteflux.libby.BungeeLibraryManager;
|
||||||
@@ -48,6 +48,8 @@ public final class HuskSyncBungeeCord extends Plugin {
|
|||||||
|
|
||||||
public static MPDBMigrator mpdbMigrator;
|
public static MPDBMigrator mpdbMigrator;
|
||||||
|
|
||||||
|
public static BungeeRedisListener redisListener;
|
||||||
|
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
|
|
||||||
public Logger getBungeeLogger() {
|
public Logger getBungeeLogger() {
|
||||||
@@ -98,10 +100,7 @@ public final class HuskSyncBungeeCord extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the redis listener
|
// Initialize the redis listener
|
||||||
if (!new BungeeRedisListener().isActiveAndEnabled) {
|
redisListener = new BungeeRedisListener();
|
||||||
getBungeeLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register listener
|
// Register listener
|
||||||
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
|
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ package me.william278.husksync.bungeecord.command;
|
|||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.MineDown;
|
||||||
import me.william278.husksync.HuskSyncBungeeCord;
|
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.PlayerData;
|
||||||
|
import me.william278.husksync.Server;
|
||||||
import me.william278.husksync.Settings;
|
import me.william278.husksync.Settings;
|
||||||
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
||||||
import me.william278.husksync.bungeecord.config.ConfigManager;
|
import me.william278.husksync.bungeecord.config.ConfigManager;
|
||||||
|
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||||
import me.william278.husksync.migrator.MPDBMigrator;
|
import me.william278.husksync.migrator.MPDBMigrator;
|
||||||
|
import me.william278.husksync.proxy.command.HuskSyncCommand;
|
||||||
import me.william278.husksync.redis.RedisMessage;
|
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.CommandSender;
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
@@ -301,7 +301,7 @@ public class BungeeCommand extends Command implements TabExecutor, HuskSyncComma
|
|||||||
HuskSyncBungeeCord.synchronisedServers)) {
|
HuskSyncBungeeCord.synchronisedServers)) {
|
||||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () ->
|
ProxyServer.getInstance().getScheduler().runAsync(plugin, () ->
|
||||||
HuskSyncBungeeCord.mpdbMigrator.executeMigrationOperations(HuskSyncBungeeCord.dataManager,
|
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());
|
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.redisHost = config.getString("redis_settings.host", "localhost");
|
||||||
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
Settings.redisPort = config.getInt("redis_settings.port", 6379);
|
||||||
Settings.redisPassword = config.getString("redis_settings.password", "");
|
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());
|
Settings.dataStorageType = Settings.DataStorageType.valueOf(config.getString("data_storage_settings.database_type", "sqlite").toUpperCase());
|
||||||
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
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.event.PostLoginEvent;
|
||||||
import net.md_5.bungee.api.plugin.Listener;
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
import net.md_5.bungee.event.EventHandler;
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
import net.md_5.bungee.event.EventPriority;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -18,7 +19,7 @@ public class BungeeEventListener implements Listener {
|
|||||||
|
|
||||||
private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance();
|
private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance();
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onPostLogin(PostLoginEvent event) {
|
public void onPostLogin(PostLoginEvent event) {
|
||||||
final ProxiedPlayer player = event.getPlayer();
|
final ProxiedPlayer player = event.getPlayer();
|
||||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||||
@@ -26,7 +27,7 @@ public class BungeeEventListener implements Listener {
|
|||||||
HuskSyncBungeeCord.dataManager.ensurePlayerExists(player.getUniqueId(), player.getName());
|
HuskSyncBungeeCord.dataManager.ensurePlayerExists(player.getUniqueId(), player.getName());
|
||||||
|
|
||||||
// Get the player's data from SQL
|
// 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
|
// Update the player's data from SQL onto the cache
|
||||||
assert data != null;
|
assert data != null;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class BungeeRedisListener extends RedisListener {
|
|||||||
|
|
||||||
// Initialize the listener on the bungee
|
// Initialize the listener on the bungee
|
||||||
public BungeeRedisListener() {
|
public BungeeRedisListener() {
|
||||||
|
super();
|
||||||
listen();
|
listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: HuskSync
|
name: HuskSync
|
||||||
version: @version@
|
version: ${version}
|
||||||
main: me.william278.husksync.HuskSyncBungeeCord
|
main: me.william278.husksync.HuskSyncBungeeCord
|
||||||
author: William278
|
author: William278
|
||||||
description: 'A modern, cross-server player data synchronization system'
|
description: 'A modern, cross-server player data synchronization system'
|
||||||
@@ -1,28 +1,7 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'redis.clients:jedis:3.7.1'
|
compileOnly 'com.zaxxer:HikariCP:5.0.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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
dependsOn processResources
|
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
|
||||||
|
|
||||||
// Exclude some unnecessary files
|
|
||||||
exclude "**/module-info.class"
|
|
||||||
exclude "module-info.class"
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ public class Settings {
|
|||||||
public static String redisHost;
|
public static String redisHost;
|
||||||
public static int redisPort;
|
public static int redisPort;
|
||||||
public static String redisPassword;
|
public static String redisPassword;
|
||||||
|
public static boolean redisSSL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bungee / Proxy server-only settings
|
* 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.DataManager;
|
||||||
import me.william278.husksync.proxy.data.sql.Database;
|
import me.william278.husksync.proxy.data.sql.Database;
|
||||||
import me.william278.husksync.proxy.data.sql.MySQL;
|
import me.william278.husksync.proxy.data.sql.MySQL;
|
||||||
|
import me.william278.husksync.redis.RedisListener;
|
||||||
import me.william278.husksync.redis.RedisMessage;
|
import me.william278.husksync.redis.RedisMessage;
|
||||||
import me.william278.husksync.util.Logger;
|
import me.william278.husksync.util.Logger;
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ public class MPDBMigrator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Carry out the migration
|
// 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
|
// Prepare the target database for insertion
|
||||||
prepareTargetDatabase(dataManager);
|
prepareTargetDatabase(dataManager);
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ public class MPDBMigrator {
|
|||||||
getExperienceData();
|
getExperienceData();
|
||||||
|
|
||||||
// Send the encoded data to the Bukkit servers for conversion
|
// Send the encoded data to the Bukkit servers for conversion
|
||||||
sendEncodedData(synchronisedServers);
|
sendEncodedData(synchronisedServers, redisListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the new database out of current data
|
// 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) {
|
for (Server processingServer : synchronisedServers) {
|
||||||
if (processingServer.hasMySqlPlayerDataBridge()) {
|
if (processingServer.hasMySqlPlayerDataBridge()) {
|
||||||
for (MPDBPlayerData data : mpdbPlayerData) {
|
for (MPDBPlayerData data : mpdbPlayerData) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import me.william278.husksync.util.Logger;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -240,7 +239,7 @@ public class DataManager {
|
|||||||
try (PreparedStatement statement = connection.prepareStatement(
|
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`=?);")) {
|
"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.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(3, playerData.getSerializedInventory());
|
||||||
statement.setString(4, playerData.getSerializedEnderChest());
|
statement.setString(4, playerData.getSerializedEnderChest());
|
||||||
statement.setDouble(5, playerData.getHealth()); // Health
|
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`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) {
|
"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(1, playerData.getPlayerUUID().toString());
|
||||||
statement.setString(2, playerData.getDataVersionUUID().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(4, playerData.getSerializedInventory());
|
||||||
statement.setString(5, playerData.getSerializedEnderChest());
|
statement.setString(5, playerData.getSerializedEnderChest());
|
||||||
statement.setDouble(6, playerData.getHealth()); // Health
|
statement.setDouble(6, playerData.getHealth()); // Health
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package me.william278.husksync.redis;
|
package me.william278.husksync.redis;
|
||||||
|
|
||||||
import me.william278.husksync.Settings;
|
import me.william278.husksync.Settings;
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.*;
|
||||||
import redis.clients.jedis.JedisClientConfig;
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
import redis.clients.jedis.JedisPubSub;
|
|
||||||
import redis.clients.jedis.exceptions.JedisException;
|
import redis.clients.jedis.exceptions.JedisException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -16,6 +15,35 @@ public abstract class RedisListener {
|
|||||||
*/
|
*/
|
||||||
public boolean isActiveAndEnabled;
|
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}
|
* Handle an incoming {@link RedisMessage}
|
||||||
*
|
*
|
||||||
@@ -31,41 +59,68 @@ public abstract class RedisListener {
|
|||||||
*/
|
*/
|
||||||
public abstract void log(Level level, String message);
|
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
|
* Start the Redis listener
|
||||||
*/
|
*/
|
||||||
public final void listen() {
|
public final void listen() {
|
||||||
try (Jedis jedis = new Jedis(Settings.redisHost, Settings.redisPort)) {
|
new Thread(() -> {
|
||||||
final String jedisPassword = Settings.redisPassword;
|
isActiveAndEnabled = true;
|
||||||
jedis.connect();
|
while (isActiveAndEnabled) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the message
|
Jedis subscriber;
|
||||||
try {
|
if (Settings.redisPassword.isEmpty()) {
|
||||||
handleMessage(new RedisMessage(message));
|
subscriber = new Jedis(Settings.redisHost,
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
Settings.redisPort,
|
||||||
log(Level.SEVERE, "Failed to deserialize message target");
|
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);
|
||||||
}, RedisMessage.REDIS_CHANNEL), "Redis Subscriber").start();
|
} catch (JedisConnectionException connectionException) {
|
||||||
} else {
|
log(Level.SEVERE, "A connection exception occurred with the Jedis listener");
|
||||||
isActiveAndEnabled = false;
|
connectionException.printStackTrace();
|
||||||
log(Level.SEVERE, "Failed to initialize the redis listener!");
|
} catch (JedisException jedisException) {
|
||||||
|
isActiveAndEnabled = false;
|
||||||
|
log(Level.SEVERE, "An exception occurred with the Jedis listener");
|
||||||
|
jedisException.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
subscriber.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (JedisException e) {
|
}, "Redis Subscriber").start();
|
||||||
log(Level.SEVERE, "Failed to establish a connection to the Redis server!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ public class RedisMessage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new 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
|
* @param messageData The message data elements
|
||||||
*/
|
*/
|
||||||
public RedisMessage(MessageType type, MessageTarget target, String... messageData) {
|
public RedisMessage(MessageType type, MessageTarget target, String... messageData) {
|
||||||
@@ -38,6 +39,7 @@ public class RedisMessage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a new RedisMessage from an incoming message string
|
* Get a new RedisMessage from an incoming message string
|
||||||
|
*
|
||||||
* @param messageString The message string to parse
|
* @param messageString The message string to parse
|
||||||
*/
|
*/
|
||||||
public RedisMessage(String messageString) throws IOException, ClassNotFoundException {
|
public RedisMessage(String messageString) throws IOException, ClassNotFoundException {
|
||||||
@@ -49,6 +51,7 @@ public class RedisMessage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full, formatted message string with type, target & data
|
* Returns the full, formatted message string with type, target & data
|
||||||
|
*
|
||||||
* @return The fully formatted message
|
* @return The fully formatted message
|
||||||
*/
|
*/
|
||||||
private String getFullMessage() throws IOException {
|
private String getFullMessage() throws IOException {
|
||||||
@@ -61,21 +64,18 @@ public class RedisMessage {
|
|||||||
* Send the redis message
|
* Send the redis message
|
||||||
*/
|
*/
|
||||||
public void send() throws IOException {
|
public void send() throws IOException {
|
||||||
try (Jedis publisher = new Jedis(Settings.redisHost, Settings.redisPort)) {
|
try (Jedis publisher = RedisListener.getJedisConnection()) {
|
||||||
final String jedisPassword = Settings.redisPassword;
|
publisher.publish(REDIS_CHANNEL, getFullMessage());
|
||||||
publisher.connect();
|
}
|
||||||
if (!jedisPassword.equals("")) {
|
|
||||||
publisher.auth(jedisPassword);
|
|
||||||
}
|
|
||||||
publisher.publish(REDIS_CHANNEL, getFullMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessageData() {
|
public String getMessageData() {
|
||||||
return messageData;
|
return messageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getMessageDataElements() { return messageData.split(MESSAGE_DATA_SEPARATOR); }
|
public String[] getMessageDataElements() {
|
||||||
|
return messageData.split(MESSAGE_DATA_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
public MessageType getMessageType() {
|
public MessageType getMessageType() {
|
||||||
return messageType;
|
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
|
* 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
|
* 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")
|
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("[%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("[• 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("[• 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("[• 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)");
|
.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")
|
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)");
|
.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 static int SPIGOT_PROJECT_ID = 97144;
|
||||||
|
|
||||||
private final String currentVersion;
|
private final VersionUtils.Version currentVersion;
|
||||||
private String latestVersion;
|
private VersionUtils.Version latestVersion;
|
||||||
|
|
||||||
public UpdateChecker(String currentVersion) {
|
public UpdateChecker(String currentVersion) {
|
||||||
this.currentVersion = currentVersion;
|
this.currentVersion = VersionUtils.Version.of(currentVersion);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + SPIGOT_PROJECT_ID);
|
final URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + SPIGOT_PROJECT_ID);
|
||||||
URLConnection urlConnection = url.openConnection();
|
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) {
|
} catch (IOException e) {
|
||||||
log(Level.WARNING, "Failed to check for updates: An IOException occurred.");
|
log(Level.WARNING, "Failed to check for updates: An IOException occurred.");
|
||||||
this.latestVersion = "Unknown";
|
this.latestVersion = new VersionUtils.Version();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log(Level.WARNING, "Failed to check for updates: An exception occurred.");
|
log(Level.WARNING, "Failed to check for updates: An exception occurred.");
|
||||||
this.latestVersion = "Unknown";
|
this.latestVersion = new VersionUtils.Version();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpToDate() {
|
public boolean isUpToDate() {
|
||||||
if (latestVersion.equalsIgnoreCase("Unknown")) {
|
return this.currentVersion.compareTo(latestVersion) >= 0;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return latestVersion.equals(currentVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLatestVersion() {
|
public String getLatestVersion() {
|
||||||
return latestVersion;
|
return latestVersion.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentVersion() {
|
public String getCurrentVersion() {
|
||||||
return currentVersion;
|
return currentVersion.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void log(Level level, String message);
|
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'
|
host: 'localhost'
|
||||||
port: 6379
|
port: 6379
|
||||||
password: ''
|
password: ''
|
||||||
|
use_ssl: false
|
||||||
data_storage_settings:
|
data_storage_settings:
|
||||||
database_type: 'sqlite'
|
database_type: 'sqlite'
|
||||||
mysql_settings:
|
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 {
|
dependencies {
|
||||||
implementation project(path: ":common", configuration: 'shadow')
|
implementation project(path: ':bukkit', configuration: 'shadow')
|
||||||
implementation project(path: ":api", configuration: 'shadow')
|
implementation project(path: ':bungeecord', configuration: 'shadow')
|
||||||
implementation project(path: ":bukkit", configuration: 'shadow')
|
implementation project(path: ':velocity', configuration: 'shadow')
|
||||||
implementation project(path: ":bungeecord", configuration: 'shadow')
|
|
||||||
implementation project(path: ":velocity", configuration: 'shadow')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
// Relocations
|
dependencies {
|
||||||
relocate 'redis.clients', 'me.William278.husksync.libraries.jedis'
|
exclude dependency(':jedis')
|
||||||
|
exclude dependency(':commons-pool2')
|
||||||
destinationDirectory.set(file("$rootDir/target/"))
|
|
||||||
archiveBaseName.set('HuskSync')
|
|
||||||
archiveClassifier.set('')
|
|
||||||
|
|
||||||
build {
|
|
||||||
dependsOn tasks.named("shadowJar")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
mavenJava(MavenPublication) {
|
mavenJava(MavenPublication) {
|
||||||
groupId = 'me.William278'
|
groupId = 'me.william278'
|
||||||
artifactId = 'HuskSync-plugin'
|
artifactId = 'husksync-plugin'
|
||||||
version = "$project.version"
|
version = "$rootProject.version"
|
||||||
|
|
||||||
artifact shadowJar
|
artifact shadowJar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':common')
|
|
||||||
implementation project(path: ':common', configuration: 'shadow')
|
implementation project(path: ':common', configuration: 'shadow')
|
||||||
|
|
||||||
compileOnly 'redis.clients:jedis:3.7.1'
|
implementation 'com.zaxxer:HikariCP:5.0.1'
|
||||||
implementation 'org.bstats:bstats-velocity:2.2.1'
|
implementation 'org.bstats:bstats-velocity:3.0.0'
|
||||||
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||||
implementation 'net.byteflux:libby-velocity:1.1.5'
|
implementation 'net.byteflux:libby-velocity:1.1.5'
|
||||||
|
|
||||||
compileOnly 'com.velocitypowered:velocity-api:3.1.0'
|
compileOnly 'com.velocitypowered:velocity-api:3.1.0'
|
||||||
annotationProcessor 'com.velocitypowered:velocity-api:3.1.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
|
relocate 'de.themoep', 'me.william278.husksync.libraries'
|
||||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.velocity'
|
relocate 'net.byteflux', 'me.william278.husksync.libraries'
|
||||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.adventure'
|
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
|
||||||
relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.velocity'
|
|
||||||
}
|
|
||||||
|
|
||||||
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.ProxyInitializeEvent;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||||
import com.velocitypowered.api.plugin.Plugin;
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import me.william278.husksync.migrator.MPDBMigrator;
|
import me.william278.husksync.migrator.MPDBMigrator;
|
||||||
@@ -31,19 +32,11 @@ import java.util.HashSet;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static me.william278.husksync.HuskSyncVelocity.VERSION;
|
@Plugin(id = "husksync")
|
||||||
|
|
||||||
@Plugin(
|
|
||||||
id = "husksync",
|
|
||||||
name = "HuskSync",
|
|
||||||
version = VERSION,
|
|
||||||
description = "HuskSync for velocity",
|
|
||||||
authors = {"William278"}
|
|
||||||
)
|
|
||||||
public class HuskSyncVelocity {
|
public class HuskSyncVelocity {
|
||||||
|
|
||||||
// Plugin version
|
// Plugin version
|
||||||
public static final String VERSION = "1.3";
|
public static String VERSION = null;
|
||||||
|
|
||||||
// Velocity bStats ID (different from Bukkit and BungeeCord)
|
// Velocity bStats ID (different from Bukkit and BungeeCord)
|
||||||
private static final int METRICS_ID = 13489;
|
private static final int METRICS_ID = 13489;
|
||||||
@@ -68,6 +61,8 @@ public class HuskSyncVelocity {
|
|||||||
|
|
||||||
public static DataManager dataManager;
|
public static DataManager dataManager;
|
||||||
|
|
||||||
|
public static VelocityRedisListener redisListener;
|
||||||
|
|
||||||
public static MPDBMigrator mpdbMigrator;
|
public static MPDBMigrator mpdbMigrator;
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
@@ -92,11 +87,13 @@ public class HuskSyncVelocity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@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.server = server;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.dataDirectory = dataDirectory;
|
this.dataDirectory = dataDirectory;
|
||||||
this.metricsFactory = metricsFactory;
|
this.metricsFactory = metricsFactory;
|
||||||
|
|
||||||
|
pluginContainer.getDescription().getVersion().ifPresent(s -> VERSION = s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@@ -145,10 +142,7 @@ public class HuskSyncVelocity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the redis listener
|
// Initialize the redis listener
|
||||||
if (!new VelocityRedisListener().isActiveAndEnabled) {
|
redisListener = new VelocityRedisListener();
|
||||||
getVelocityLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (Velocity) v" + VERSION);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register listener
|
// Register listener
|
||||||
server.getEventManager().register(this, new VelocityEventListener());
|
server.getEventManager().register(this, new VelocityEventListener());
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand {
|
|||||||
HuskSyncVelocity.synchronisedServers)) {
|
HuskSyncVelocity.synchronisedServers)) {
|
||||||
plugin.getProxyServer().getScheduler().buildTask(plugin, () ->
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () ->
|
||||||
HuskSyncVelocity.mpdbMigrator.executeMigrationOperations(HuskSyncVelocity.dataManager,
|
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());
|
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) {
|
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")
|
@SuppressWarnings("SameParameterValue")
|
||||||
private static boolean getConfigBoolean(ConfigurationNode rootNode, boolean defaultValue, String... nodePath) {
|
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) {
|
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) {
|
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 {
|
public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException {
|
||||||
ConfigurationNode config = copyDefaults(loadedConfig);
|
ConfigurationNode config = copyDefaults(loadedConfig);
|
||||||
//ConfigurationNode config = copyDefaults(loadedConfig);
|
|
||||||
|
|
||||||
Settings.language = getConfigString(config, "en-gb", "language");
|
Settings.language = getConfigString(config, "en-gb", "language");
|
||||||
|
|
||||||
@@ -58,6 +57,7 @@ public class ConfigLoader {
|
|||||||
Settings.redisHost = getConfigString(config, "localhost", "redis_settings", "host");
|
Settings.redisHost = getConfigString(config, "localhost", "redis_settings", "host");
|
||||||
Settings.redisPort = getConfigInt(config, 6379, "redis_settings", "port");
|
Settings.redisPort = getConfigInt(config, 6379, "redis_settings", "port");
|
||||||
Settings.redisPassword = getConfigString(config, "", "redis_settings", "password");
|
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());
|
Settings.dataStorageType = Settings.DataStorageType.valueOf(getConfigString(config, "sqlite", "data_storage_settings", "database_type").toUpperCase());
|
||||||
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class VelocityRedisListener extends RedisListener {
|
|||||||
|
|
||||||
// Initialize the listener on the bungee
|
// Initialize the listener on the bungee
|
||||||
public VelocityRedisListener() {
|
public VelocityRedisListener() {
|
||||||
|
super();
|
||||||
listen();
|
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