mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 00:59:18 +00:00
Compare commits
85 Commits
1.3
...
1.4-SNAPSH
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6e2a1ddc | ||
|
|
aed68b3045 | ||
|
|
6ee5141c8f | ||
|
|
3ff3c1207c | ||
|
|
5947bac82c | ||
|
|
baab59d17a | ||
|
|
e98f272dfd | ||
|
|
f4b680bee7 | ||
|
|
73fe184e70 | ||
|
|
bdc07f064d | ||
|
|
01aa28f28c | ||
|
|
5de81f06d6 | ||
|
|
d5da516f17 | ||
|
|
5e0e3fd27d | ||
|
|
5cee7cca84 | ||
|
|
330627553e | ||
|
|
359f0d6f18 | ||
|
|
b531196d8a | ||
|
|
951fc27a67 | ||
|
|
a5f7b37fac | ||
|
|
da7a85dde9 | ||
|
|
0f215c80ea | ||
|
|
a76aecdd23 | ||
|
|
2f3b0f37e8 | ||
|
|
330476ee23 | ||
|
|
6bf36bcbb1 | ||
|
|
a25b7a2c89 | ||
|
|
ab3271c0ec | ||
|
|
0fd5e4eb36 | ||
|
|
996b9bc63d | ||
|
|
bedb903215 | ||
|
|
d8b80388fd | ||
|
|
3f77b8b5f6 | ||
|
|
bd599081e5 | ||
|
|
4d5902132c | ||
|
|
bd3c080b4f | ||
|
|
dd46a6cdd5 | ||
|
|
7d453b7438 | ||
|
|
c5e0640f83 | ||
|
|
fb3b2bd66e | ||
|
|
cfe3879010 | ||
|
|
aedb517662 | ||
|
|
b7e6861f03 | ||
|
|
61072bfa51 | ||
|
|
ae439595ea | ||
|
|
e1f6e40624 | ||
|
|
ff17b58473 | ||
|
|
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 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -106,7 +106,7 @@ build/
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# me.william278.crossserversync.bungeecord.data.DataManager.PlayerDataCache of project
|
||||
# net.william278.crossserversync.bungeecord.data.DataManager.PlayerDataCache of project
|
||||
.gradletasknamecache
|
||||
|
||||
**/build/
|
||||
|
||||
5
.jitpack/install-mpdb-converter
Normal file
5
.jitpack/install-mpdb-converter
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "mvn-installing mpdbdataconverter..."
|
||||
curl "-L" "-O" "https://github.com/WiIIiam278/MPDBDataConverter/releases/download/1.0/mpdbdataconverter-1.0.jar"
|
||||
mvn "install:install-file" "-Dfile=mpdbdataconverter-1.0.jar" "-DgroupId=net.william278" "-DartifactId=mpdbdataconverter" "-Dversion=1.0" "-Dpackaging=jar" "-DgeneratePom=true" "-e"
|
||||
4
LICENSE
4
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright © William278 2021. All rights reserved
|
||||
Copyright © William278 2022. All rights reserved
|
||||
|
||||
LICENSE
|
||||
This source code is provided as reference to licensed individuals that have purchased the HuskSync
|
||||
@@ -8,7 +8,7 @@ not grant you the rights to modify, re-distribute, compile or redistribute this
|
||||
parties that are utilised in the plugin.
|
||||
|
||||
CONTRIBUTOR AGREEMENT
|
||||
By contributing code to this repository, contributors agree that they forefeit their contributions
|
||||
By contributing code to this repository, contributors agree that they forfeit their contributions
|
||||
to the copyright holder and only the copyright holder.
|
||||
In exchange for contributing, the copyright holder may give, at their discretion, permission to use
|
||||
the plugin in commercial contexts
|
||||
|
||||
51
README.md
51
README.md
@@ -37,10 +37,15 @@ To migrate from MySQLPLayerDataBridge, you need a Proxy server with HuskSync ins
|
||||
#### Commands do not function
|
||||
Please check that the plugin is installed and enabled on both the proxy and bukkit server you are trying to execute the command from and that both plugins connected to Redis. (A connection handshake confirmation message is logged to console when communications are successfully established.)
|
||||
|
||||
#### SQL errors in proxy console / data not synchronising
|
||||
#### Data not being synced on player join and SQL errors in proxy console
|
||||
This issue frequently occurs in users running Cracked (illegal) servers. I do not support piracy and so will be limited in my ability to help you.
|
||||
If you are running an offline server for a legitimate reason, however, make sure that in the `paper.yml` of your Bukkit servers `bungee-online-mode` is set to the correct value - and that both your Proxy (BungeeCord, Waterfall, etc.) server and Bukkit (Spigot, paper, etc.) servers are set up correctly to work with offline mode.
|
||||
|
||||
#### Data sometimes not syncing between servers
|
||||
There are two primary reasons this may happen:
|
||||
* On your proxy server, you are running _FlameCord_ or a similar fork of Waterfall. Due to the nature of these forks changing security parameters, they can block or interfere with Redis packets being sent to and from your server. FlameCord, XCord and other forks are not compatible with HuskSync. For security-conscious users, I recommend Velocity.
|
||||
* Your backend servers/proxy and Redis server have noticeably different amounts of latency between each other. This is particularly relevant for users running across multiple machines, where some backend servers / the proxy are installed with Redis and other backend servers are on a different machine. The solution to this is to have your BungeeCord and Redis alone on one machine, and your backend servers across the others - or have a separate machine with equal latency to the others that has Redis on. In the future, I may have a look at automatically correcting and accounting for differences in latency.
|
||||
|
||||
## How it works
|
||||

|
||||
HuskSync saves a player's data when they log out to a cache on your proxy server, and redistributes that data to players when they join another HuskSync-enabled server. Player data in the cache is then saved to a database (be it SQLite or MySQL) and this is loaded from when a player joins your network.
|
||||
@@ -69,15 +74,15 @@ Everything except player locations are synchronised by default. You can enable o
|
||||
### Commands
|
||||
Commands are handled by the proxy server, rather than each spigot server. Some will only work on Spigot servers with HuskSync installed. Please remember that you will need a Proxy permission plugin (e.g. LuckPermsBungee) to set permissions for proxy commands.
|
||||
|
||||
| Command | Description | Permission |
|
||||
|---------------------|--------------------------------------|--------------------------------|
|
||||
| `/husksync about` | View plugin information | _None_ |
|
||||
| `/husksync update` | Check if an update is available | `husksync.command.admin` |
|
||||
| `/husksync status` | View system status information | `husksync.command.admin` |
|
||||
| `/husksync reload` | Reload config & message files | `husksync.command.admin` |
|
||||
| `/husksync invsee` | View an offline player's inventory | `husksync.command.inventory` |
|
||||
| `/husksync echest` | View an offline player's ender chest | `husksync.command.ender_chest` |
|
||||
| `/husksync migrate` | Migrate data from MPDB | _Console-only_ |
|
||||
| Command | Description | Permission |
|
||||
|---------------------------------------|--------------------------------------|--------------------------------|
|
||||
| `/husksync about` | View plugin information | _None_ |
|
||||
| `/husksync update` | Check if an update is available | `husksync.command.admin` |
|
||||
| `/husksync status` | View system status information | `husksync.command.admin` |
|
||||
| `/husksync reload` | Reload config & message files | `husksync.command.admin` |
|
||||
| `/husksync invsee <player> [cluster]` | View an offline player's inventory | `husksync.command.inventory` |
|
||||
| `/husksync echest <player> [cluster]` | View an offline player's ender chest | `husksync.command.ender_chest` |
|
||||
| `/husksync migrate [args] ` | Migrate data from MPDB | _Console-only_ |
|
||||
|
||||
### Frequently Asked Questions (FAQs)
|
||||
#### Is Redis required?
|
||||
@@ -131,7 +136,7 @@ With Maven, add the repository to your pom.xml:
|
||||
Then, add the dependency. Replace `version` with the latest version of HuskSync: [](https://jitpack.io/#WiIIiam278/HuskSync)
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.WiIIiam278</groupId>
|
||||
<groupId>net.william278</groupId>
|
||||
<artifactId>HuskSync</artifactId>
|
||||
<version>version</version>
|
||||
<scope>provided</scope>
|
||||
@@ -147,10 +152,10 @@ 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/#net.william278/HuskSync)
|
||||
```
|
||||
dependencies {
|
||||
compileOnly 'com.github.WiIIiam278:HuskSync:version'
|
||||
compileOnly 'net.william278:HuskSync:version'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -160,7 +165,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);
|
||||
@@ -174,15 +179,15 @@ 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.
|
||||
```
|
||||
Use the static methods provided in the [DataSerializer class](https://javadoc.jitpack.io/net.william278/HuskSync/latest/javadoc/net/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 {
|
||||
@@ -201,10 +206,9 @@ A code bounty program is in place for HuskSync, where developers making signific
|
||||
While the code bounty program is not available for translation contributors, they are still strongly appreciated in making the plugin more accessible. If you'd like to contribute translated message strings for your language, you can submit a Pull Request that creates a .yml file in `bungeecord/src/main/resources/languages` with the correct translations.
|
||||
|
||||
### Building
|
||||
To build HuskSync you will first need to download MySqlPlayerDataBridge and `mvn install:install-file` the jar file to your local maven repository.
|
||||
```
|
||||
mvn install:install-file -Dfile=MysqlPlayerDataBridge-v4.0.1.jar -DgroupId=net.craftersland.data -DartifactId=bridge -Dversion=4.0.1 -Dpackaging=jar
|
||||
```
|
||||
You can build HuskSync yourself, though please read the license and buy yourself a copy as HuskSync is indeed a premium resource.
|
||||
|
||||
To build HuskSync, you'll need to get the [MPDBConverter](https://github.com/WiIIiam278/MPDBDataConverter) library, either by authenticating through GitHub packages or by downloading and running `mvn install-file` to publish it to your local maven repository.
|
||||
|
||||
Then, to build the plugin, run the following in the root of the repository:
|
||||
```
|
||||
@@ -215,10 +219,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.
|
||||
You can turn metric collection off by navigating to `~/plugins/bStats/config.yml` and editing the config to disable plugin metrics.
|
||||
|
||||
## Support
|
||||
* Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues)
|
||||
* Discord support: Join the [HuskHelp Discord](https://discord.gg/tVYhJfyDWG)!
|
||||
* Proof of purchase is required for support.
|
||||
* Proof of purchase is required for support.
|
||||
|
||||
@@ -1,44 +1,20 @@
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
compileOnly project(path: ':common')
|
||||
implementation project(path: ':bukkit')
|
||||
|
||||
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()
|
||||
}
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier = null
|
||||
relocate ':common', 'me.william278.husksync'
|
||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||
}
|
||||
|
||||
task javadocs(type: Javadoc) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
source = project(':common').sourceSets.main.allJava
|
||||
source += project(':api').sourceSets.main.allJava
|
||||
classpath = files(project(':common').sourceSets.main.compileClasspath)
|
||||
classpath += files(project(':api').sourceSets.main.compileClasspath)
|
||||
destinationDir = file("${buildDir}/docs/javadoc")
|
||||
}
|
||||
|
||||
task javadocsJar(type: Jar, dependsOn: javadocs) {
|
||||
classifier = 'javadoc'
|
||||
from javadocs.destinationDir
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package me.william278.husksync.bukkit.api;
|
||||
package net.william278.husksync.bukkit.api;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.listener.BukkitRedisListener;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* API method class for HuskSync. To access methods, use the {@link #getInstance()} entrypoint.
|
||||
* HuskSync's API. To access methods, use the {@link #getInstance()} entrypoint.
|
||||
*
|
||||
* @author William
|
||||
*/
|
||||
public class HuskSyncAPI {
|
||||
|
||||
@@ -20,7 +22,7 @@ public class HuskSyncAPI {
|
||||
private static HuskSyncAPI instance;
|
||||
|
||||
/**
|
||||
* API entry point. Returns an instance of the {@link HuskSyncAPI}
|
||||
* The API entry point. Returns an instance of the {@link HuskSyncAPI}
|
||||
*
|
||||
* @return instance of the {@link HuskSyncAPI}
|
||||
*/
|
||||
@@ -32,35 +34,38 @@ public class HuskSyncAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* (INTERNAL) Map of API requests that are processed by the bukkit plugin that implements the API.
|
||||
*/
|
||||
public static HashMap<UUID, CompletableFuture<PlayerData>> apiRequests = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Returns a {@link CompletableFuture} that will fetch the {@link PlayerData} for a user given their {@link UUID}, which contains synchronised data that can then be deserialized into ItemStacks and other usable values using the {@link me.william278.husksync.bukkit.data.DataSerializer} class. If no data could be returned, such as if an invalid UUID is specified, the CompletableFuture will be cancelled. Note that this only returns the last cached data of the user; not necessarily the current state of their inventory if they are online.
|
||||
* Returns a {@link CompletableFuture} that will fetch the {@link PlayerData} for a user given their {@link UUID},
|
||||
* which contains serialized synchronised data.
|
||||
* <p>
|
||||
* This can then be deserialized into ItemStacks and other usable values using the {@code DataSerializer} class.
|
||||
* <p>
|
||||
* If no data could be returned, such as if an invalid UUID is specified, the CompletableFuture will be cancelled.
|
||||
*
|
||||
* @param playerUUID The {@link UUID} of the player to get data for
|
||||
* @return a {@link CompletableFuture} with the user's {@link PlayerData} accessible on completion
|
||||
* @throws IOException If an exception occurs with serializing during processing of the request
|
||||
* @apiNote This only returns the latest saved and cached data of the user. This is <b>not</b> necessarily the current state of their inventory if they are online.
|
||||
*/
|
||||
public CompletableFuture<PlayerData> getPlayerData(UUID playerUUID) throws IOException {
|
||||
// Create the request to be completed
|
||||
final UUID requestUUID = UUID.randomUUID();
|
||||
apiRequests.put(requestUUID, new CompletableFuture<>());
|
||||
BukkitRedisListener.apiRequests.put(requestUUID, new CompletableFuture<>());
|
||||
|
||||
// Remove the request from the map on completion
|
||||
apiRequests.get(requestUUID).whenComplete((playerData, throwable) -> apiRequests.remove(requestUUID));
|
||||
BukkitRedisListener.apiRequests.get(requestUUID).whenComplete((playerData, throwable) -> BukkitRedisListener.apiRequests.remove(requestUUID));
|
||||
|
||||
// Request the data via the proxy
|
||||
new RedisMessage(RedisMessage.MessageType.API_DATA_REQUEST,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
|
||||
playerUUID.toString(), requestUUID.toString()).send();
|
||||
|
||||
return apiRequests.get(requestUUID);
|
||||
return BukkitRedisListener.apiRequests.get(requestUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a player's {@link PlayerData} to the central cache and database. If the player is online on the Proxy network, they will be updated and overwritten with this data.
|
||||
* Updates a player's {@link PlayerData} to the proxy cache and database.
|
||||
* <p>
|
||||
* If the player is online on the Proxy network, they will be updated and overwritten with this data.
|
||||
*
|
||||
* @param playerData The {@link PlayerData} (which contains the {@link UUID}) of the player data to update to the central cache and database
|
||||
* @throws IOException If an exception occurs with serializing during processing of the update
|
||||
@@ -70,7 +75,7 @@ public class HuskSyncAPI {
|
||||
final String serializedPlayerData = RedisMessage.serialize(playerData);
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
|
||||
serializedPlayerData).send();
|
||||
serializedPlayerData, Boolean.toString(true)).send();
|
||||
}
|
||||
|
||||
}
|
||||
68
build.gradle
68
build.gradle
@@ -1,33 +1,27 @@
|
||||
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 'net.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'
|
||||
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
|
||||
|
||||
compileJava.options.release.set 16
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -39,4 +33,40 @@ subprojects {
|
||||
maven { url 'https://repo.alessiodp.com/releases/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation('redis.clients:jedis:4.2.3') {
|
||||
//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', 'api', '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,20 +1,18 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
compileOnly project(':api')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
implementation project(path: ':common')
|
||||
|
||||
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'
|
||||
implementation 'net.william278:mpdbdataconverter:1.0'
|
||||
|
||||
compileOnly 'net.craftersland.data:bridge:4.0.1'
|
||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.jetbrains:annotations:22.0.0'
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.bstats', 'me.William278.husksync.libraries.bstats.bukkit'
|
||||
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard'
|
||||
}
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package me.william278.husksync.bukkit.util.nms;
|
||||
|
||||
import me.william278.husksync.util.ThrowSupplier;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
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 ?
|
||||
"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)));
|
||||
}
|
||||
|
||||
public static Class<?> getMinecraftClass(String path) {
|
||||
return ThrowSupplier.get(() -> Class.forName(MINECRAFT_PACKAGE.concat(".").concat(path)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package me.william278.husksync;
|
||||
package net.william278.husksync;
|
||||
|
||||
import me.william278.husksync.bukkit.util.BukkitUpdateChecker;
|
||||
import me.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import me.william278.husksync.bukkit.config.ConfigLoader;
|
||||
import me.william278.husksync.bukkit.data.BukkitDataCache;
|
||||
import me.william278.husksync.bukkit.listener.BukkitRedisListener;
|
||||
import me.william278.husksync.bukkit.listener.BukkitEventListener;
|
||||
import me.william278.husksync.bukkit.migrator.MPDBDeserializer;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.util.BukkitUpdateChecker;
|
||||
import net.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import net.william278.husksync.bukkit.config.ConfigLoader;
|
||||
import net.william278.husksync.bukkit.data.BukkitDataCache;
|
||||
import net.william278.husksync.bukkit.listener.BukkitRedisListener;
|
||||
import net.william278.husksync.bukkit.listener.BukkitEventListener;
|
||||
import net.william278.husksync.bukkit.migrator.MPDBDeserializer;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -17,7 +18,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 +32,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 +122,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();
|
||||
@@ -146,7 +144,7 @@ public final class HuskSyncBukkit extends JavaPlugin {
|
||||
if (HuskSyncBukkit.handshakeCompleted && !HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled && Bukkit.getOnlinePlayers().size() > 0) {
|
||||
getLogger().info("Saving data for remaining online players...");
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
PlayerSetter.updatePlayerData(player);
|
||||
PlayerSetter.updatePlayerData(player, false);
|
||||
}
|
||||
getLogger().info("Data save complete!");
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.config;
|
||||
package net.william278.husksync.bukkit.config;
|
||||
|
||||
import me.william278.husksync.Settings;
|
||||
import net.william278.husksync.Settings;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
public class ConfigLoader {
|
||||
@@ -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);
|
||||
@@ -26,6 +27,8 @@ public class ConfigLoader {
|
||||
Settings.syncFlight = config.getBoolean("synchronisation_settings.flight", false);
|
||||
|
||||
Settings.useNativeImplementation = config.getBoolean("native_advancement_synchronization", false);
|
||||
Settings.saveOnWorldSave = config.getBoolean("save_on_world_save", true);
|
||||
Settings.synchronizationTimeoutRetryDelay = config.getLong("synchronization_timeout_retry_delay", 15L);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.bukkit.data;
|
||||
package net.william278.husksync.bukkit.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.data;
|
||||
package net.william278.husksync.bukkit.data;
|
||||
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
@@ -20,7 +20,7 @@ import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Class that contains static methods for serializing and deserializing data from {@link me.william278.husksync.PlayerData}
|
||||
* Class that contains static methods for serializing and deserializing data from {@link net.william278.husksync.PlayerData}
|
||||
*/
|
||||
public class DataSerializer {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package me.william278.husksync.bukkit.data;
|
||||
package net.william278.husksync.bukkit.data;
|
||||
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
@@ -58,7 +58,7 @@ public class DataViewer {
|
||||
// Send a redis message with the updated data after the viewing
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
|
||||
RedisMessage.serialize(playerData))
|
||||
RedisMessage.serialize(playerData), Boolean.toString(true))
|
||||
.send();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.api.events;
|
||||
package net.william278.husksync.bukkit.events;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.api.events;
|
||||
package net.william278.husksync.bukkit.events;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
@@ -1,8 +1,9 @@
|
||||
package me.william278.husksync.bukkit.listener;
|
||||
package net.william278.husksync.bukkit.listener;
|
||||
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.bukkit.data.DataViewer;
|
||||
import me.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.data.DataViewer;
|
||||
import net.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -14,6 +15,7 @@ import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
@@ -22,7 +24,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 +35,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);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> PlayerSetter.updatePlayerData(player, true));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
if (!plugin.isEnabled()) return; // If the plugin has not been initialized correctly
|
||||
|
||||
@@ -49,18 +52,22 @@ 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())) {
|
||||
try {
|
||||
PlayerSetter.requestPlayerData(player.getUniqueId());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e);
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
PlayerSetter.requestPlayerData(player.getUniqueId());
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the player's data wasn't set after 10 ticks, ensure it will be
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||
// If the player's data wasn't set after the synchronization timeout retry delay ticks, ensure it will be
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
|
||||
if (player.isOnline()) {
|
||||
try {
|
||||
if (HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(player.getUniqueId())) {
|
||||
@@ -70,13 +77,14 @@ public class BukkitEventListener implements Listener {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e);
|
||||
}
|
||||
}
|
||||
}, 5);
|
||||
}, Settings.synchronizationTimeoutRetryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
@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 +103,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,31 +119,41 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void onWorldSave(WorldSaveEvent event) {
|
||||
if (!plugin.isEnabled() || !HuskSyncBukkit.handshakeCompleted) {
|
||||
return;
|
||||
}
|
||||
for (Player playerInWorld : event.getWorld().getPlayers()) {
|
||||
PlayerSetter.updatePlayerData(playerInWorld, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,35 @@
|
||||
package me.william278.husksync.bukkit.listener;
|
||||
package net.william278.husksync.bukkit.listener;
|
||||
|
||||
import de.themoep.minedown.MineDown;
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.bukkit.api.HuskSyncAPI;
|
||||
import me.william278.husksync.bukkit.config.ConfigLoader;
|
||||
import me.william278.husksync.bukkit.data.DataViewer;
|
||||
import me.william278.husksync.bukkit.migrator.MPDBDeserializer;
|
||||
import me.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import me.william278.husksync.migrator.MPDBPlayerData;
|
||||
import me.william278.husksync.redis.RedisListener;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.config.ConfigLoader;
|
||||
import net.william278.husksync.bukkit.data.DataViewer;
|
||||
import net.william278.husksync.bukkit.migrator.MPDBDeserializer;
|
||||
import net.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import net.william278.husksync.migrator.MPDBPlayerData;
|
||||
import net.william278.husksync.redis.RedisListener;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BukkitRedisListener extends RedisListener {
|
||||
|
||||
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||
|
||||
public static HashMap<UUID, CompletableFuture<PlayerData>> apiRequests = new HashMap<>();
|
||||
|
||||
// Initialize the listener on the bukkit server
|
||||
public BukkitRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
@@ -110,10 +114,10 @@ public class BukkitRedisListener extends RedisListener {
|
||||
}
|
||||
case API_DATA_RETURN -> {
|
||||
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||
if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) {
|
||||
if (apiRequests.containsKey(requestUUID)) {
|
||||
try {
|
||||
final PlayerData data = (PlayerData) RedisMessage.deserialize(message.getMessageDataElements()[1]);
|
||||
HuskSyncAPI.apiRequests.get(requestUUID).complete(data);
|
||||
apiRequests.get(requestUUID).complete(data);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
log(Level.SEVERE, "Failed to serialize returned API-requested player data");
|
||||
}
|
||||
@@ -123,8 +127,8 @@ public class BukkitRedisListener extends RedisListener {
|
||||
case API_DATA_CANCEL -> {
|
||||
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||
// Cancel requests if no data could be found on the proxy
|
||||
if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) {
|
||||
HuskSyncAPI.apiRequests.get(requestUUID).cancel(true);
|
||||
if (apiRequests.containsKey(requestUUID)) {
|
||||
apiRequests.get(requestUUID).cancel(true);
|
||||
}
|
||||
}
|
||||
case RELOAD_CONFIG -> {
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.william278.husksync.bukkit.migrator;
|
||||
package net.william278.husksync.bukkit.migrator;
|
||||
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import me.william278.husksync.bukkit.data.DataSerializer;
|
||||
import me.william278.husksync.migrator.MPDBPlayerData;
|
||||
import net.craftersland.data.bridge.PD;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.bukkit.data.DataSerializer;
|
||||
import net.william278.husksync.bukkit.util.PlayerSetter;
|
||||
import net.william278.husksync.migrator.MPDBPlayerData;
|
||||
import net.william278.mpdbconverter.MPDBConverter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class MPDBDeserializer {
|
||||
@@ -19,10 +19,12 @@ public class MPDBDeserializer {
|
||||
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
|
||||
|
||||
// Instance of MySqlPlayerDataBridge
|
||||
private static PD mySqlPlayerDataBridge;
|
||||
private static MPDBConverter mpdbConverter;
|
||||
|
||||
public static void setMySqlPlayerDataBridge() {
|
||||
mySqlPlayerDataBridge = (PD) Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge");
|
||||
Plugin mpdbPlugin = Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge");
|
||||
assert mpdbPlugin != null;
|
||||
mpdbConverter = MPDBConverter.getInstance(mpdbPlugin);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,13 +46,13 @@ public class MPDBDeserializer {
|
||||
// Set inventory contents
|
||||
Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER);
|
||||
if (!mpdbPlayerData.inventoryData.isEmpty() && !mpdbPlayerData.inventoryData.equalsIgnoreCase("none")) {
|
||||
PlayerSetter.setInventory(inventory, getItemStackArrayFromMPDBBase64String(mpdbPlayerData.inventoryData));
|
||||
PlayerSetter.setInventory(inventory, mpdbConverter.getItemStackFromSerializedData(mpdbPlayerData.inventoryData));
|
||||
}
|
||||
|
||||
// Set armor (if there is data; MPDB stores empty data with literally the word "none". Obviously.)
|
||||
int armorSlot = 36;
|
||||
if (!mpdbPlayerData.armorData.isEmpty() && !mpdbPlayerData.armorData.equalsIgnoreCase("none")) {
|
||||
ItemStack[] armorItems = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.armorData);
|
||||
ItemStack[] armorItems = mpdbConverter.getItemStackFromSerializedData(mpdbPlayerData.armorData);
|
||||
for (ItemStack armorPiece : armorItems) {
|
||||
if (armorPiece != null) {
|
||||
inventory.setItem(armorSlot, armorPiece);
|
||||
@@ -66,7 +68,7 @@ public class MPDBDeserializer {
|
||||
// Set ender chest (again, if there is data)
|
||||
ItemStack[] enderChestData;
|
||||
if (!mpdbPlayerData.enderChestData.isEmpty() && !mpdbPlayerData.enderChestData.equalsIgnoreCase("none")) {
|
||||
enderChestData = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.enderChestData);
|
||||
enderChestData = mpdbConverter.getItemStackFromSerializedData(mpdbPlayerData.enderChestData);
|
||||
} else {
|
||||
enderChestData = new ItemStack[0];
|
||||
}
|
||||
@@ -82,19 +84,4 @@ public class MPDBDeserializer {
|
||||
}
|
||||
return playerData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ItemStack array from a decoded base 64 string in MySQLPlayerDataBridge's format
|
||||
*
|
||||
* @param data The encoded ItemStack[] string from MySQLPlayerDataBridge
|
||||
* @return The {@link ItemStack[]} array
|
||||
* @throws InvocationTargetException If an error occurs during decoding
|
||||
* @throws IllegalAccessException If an error occurs during decoding
|
||||
*/
|
||||
public static ItemStack[] getItemStackArrayFromMPDBBase64String(String data) throws InvocationTargetException, IllegalAccessException {
|
||||
if (data.isEmpty()) {
|
||||
return new ItemStack[0];
|
||||
}
|
||||
return mySqlPlayerDataBridge.getItemStackSerializer().fromBase64(data);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.bukkit.util;
|
||||
package net.william278.husksync.bukkit.util;
|
||||
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.util.UpdateChecker;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.util.UpdateChecker;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package me.william278.husksync.bukkit.util;
|
||||
package net.william278.husksync.bukkit.util;
|
||||
|
||||
import me.william278.husksync.HuskSyncBukkit;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.bukkit.api.events.SyncCompleteEvent;
|
||||
import me.william278.husksync.bukkit.api.events.SyncEvent;
|
||||
import me.william278.husksync.bukkit.data.DataSerializer;
|
||||
import me.william278.husksync.bukkit.util.nms.AdvancementUtils;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncBukkit;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bukkit.events.SyncCompleteEvent;
|
||||
import net.william278.husksync.bukkit.events.SyncEvent;
|
||||
import net.william278.husksync.bukkit.data.DataSerializer;
|
||||
import net.william278.husksync.bukkit.util.nms.AdvancementUtils;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
@@ -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;
|
||||
@@ -95,15 +95,16 @@ public class PlayerSetter {
|
||||
/**
|
||||
* Update a {@link Player}'s data, sending it to the proxy
|
||||
*
|
||||
* @param player {@link Player} to send data to proxy
|
||||
* @param player {@link Player} to send data to proxy
|
||||
* @param bounceBack whether the plugin should bounce-back the updated data to the player (used for server switching)
|
||||
*/
|
||||
public static void updatePlayerData(Player player) {
|
||||
public static void updatePlayerData(Player player, boolean bounceBack) {
|
||||
// Send a redis message with the player's last updated PlayerData version UUID and their new PlayerData
|
||||
try {
|
||||
final String serializedPlayerData = getNewSerializedPlayerData(player);
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
|
||||
serializedPlayerData).send();
|
||||
serializedPlayerData, Boolean.toString(bounceBack)).send();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", e);
|
||||
}
|
||||
@@ -280,7 +281,7 @@ public class PlayerSetter {
|
||||
final Object playerAdvancements = AdvancementUtils.getPlayerAdvancements(player);
|
||||
|
||||
// Clear
|
||||
AdvancementUtils.clearPlayerAdvancements(playerAdvancements);
|
||||
AdvancementUtils.clearPlayerAdvancements(playerAdvancements);
|
||||
AdvancementUtils.clearVisibleAdvancements(playerAdvancements);
|
||||
|
||||
advancementRecords.forEach(advancementRecord -> {
|
||||
@@ -330,7 +331,7 @@ public class PlayerSetter {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
|
||||
// Apply the advancements to the player
|
||||
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
|
||||
final Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
|
||||
while (serverAdvancements.hasNext()) { // Iterate through all advancements
|
||||
boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update
|
||||
Advancement advancement = serverAdvancements.next();
|
||||
@@ -463,12 +464,13 @@ public class PlayerSetter {
|
||||
*/
|
||||
private static void setPlayerHealth(Player player, double health, double maxHealth, double healthScale) {
|
||||
// Set max health
|
||||
if (maxHealth != 0.0D) {
|
||||
if (maxHealth != 0D) {
|
||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).setBaseValue(maxHealth);
|
||||
}
|
||||
|
||||
// Set health
|
||||
player.setHealth(player.getHealth() > maxHealth ? maxHealth : health);
|
||||
double currentHealth = player.getHealth();
|
||||
if (health != currentHealth) player.setHealth(currentHealth > maxHealth ? maxHealth : health);
|
||||
|
||||
// Set health scaling if needed
|
||||
if (healthScale != 0D) {
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.util.nms;
|
||||
package net.william278.husksync.bukkit.util.nms;
|
||||
|
||||
import me.william278.husksync.util.ThrowSupplier;
|
||||
import net.william278.husksync.util.ThrowSupplier;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bukkit.util.nms;
|
||||
package net.william278.husksync.bukkit.util.nms;
|
||||
|
||||
import me.william278.husksync.util.ThrowSupplier;
|
||||
import net.william278.husksync.util.ThrowSupplier;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.william278.husksync.bukkit.util.nms;
|
||||
|
||||
import net.william278.husksync.util.ThrowSupplier;
|
||||
import net.william278.husksync.util.VersionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
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 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 static Class<?> getBukkitClass(String path) {
|
||||
return ThrowSupplier.get(() -> Class.forName(CRAFTBUKKIT_PACKAGE_PATH.concat(".").concat(path)));
|
||||
}
|
||||
|
||||
public static Class<?> getMinecraftClass(String path) {
|
||||
return ThrowSupplier.get(() -> Class.forName(MINECRAFT_PACKAGE.concat(".").concat(path)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ redis_settings:
|
||||
host: 'localhost'
|
||||
port: 6379
|
||||
password: ''
|
||||
use_ssl: false
|
||||
synchronisation_settings:
|
||||
inventories: true
|
||||
ender_chests: true
|
||||
@@ -16,4 +17,6 @@ synchronisation_settings:
|
||||
flight: false
|
||||
cluster_id: 'main'
|
||||
check_for_updates: true
|
||||
native_advancement_synchronization: true
|
||||
synchronization_timeout_retry_delay: 15
|
||||
save_on_world_save: true
|
||||
native_advancement_synchronization: false
|
||||
@@ -1,7 +1,8 @@
|
||||
name: HuskSync
|
||||
version: @version@
|
||||
main: me.william278.husksync.HuskSyncBukkit
|
||||
version: ${version}
|
||||
main: net.william278.husksync.HuskSyncBukkit
|
||||
api-version: 1.16
|
||||
author: William278
|
||||
description: 'A modern, cross-server player data synchronization system'
|
||||
website: 'https://william278.net'
|
||||
softdepend: [MysqlPlayerDataBridge]
|
||||
@@ -1,9 +1,8 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
implementation project(path: ':common')
|
||||
|
||||
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,15 @@ 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', 'net.william278.husksync.libraries'
|
||||
relocate 'net.byteflux', 'net.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
dependencies {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
exclude dependency(':slf4j-api')
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package me.william278.husksync;
|
||||
package net.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.redis.RedisMessage;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bungeecord.command.BungeeCommand;
|
||||
import net.william278.husksync.bungeecord.config.ConfigLoader;
|
||||
import net.william278.husksync.bungeecord.config.ConfigManager;
|
||||
import net.william278.husksync.bungeecord.listener.BungeeEventListener;
|
||||
import net.william278.husksync.bungeecord.listener.BungeeRedisListener;
|
||||
import net.william278.husksync.bungeecord.util.BungeeLogger;
|
||||
import net.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.proxy.data.DataManager;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.byteflux.libby.BungeeLibraryManager;
|
||||
import net.byteflux.libby.Library;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
@@ -48,6 +50,8 @@ public final class HuskSyncBungeeCord extends Plugin {
|
||||
|
||||
public static MPDBMigrator mpdbMigrator;
|
||||
|
||||
public static BungeeRedisListener redisListener;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
public Logger getBungeeLogger() {
|
||||
@@ -98,10 +102,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());
|
||||
@@ -156,7 +157,7 @@ public final class HuskSyncBungeeCord extends Plugin {
|
||||
Library mySqlLib = Library.builder()
|
||||
.groupId("mysql")
|
||||
.artifactId("mysql-connector-java")
|
||||
.version("8.0.27")
|
||||
.version("8.0.29")
|
||||
.build();
|
||||
|
||||
Library sqLiteLib = Library.builder()
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.william278.husksync.bungeecord.command;
|
||||
package net.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.Settings;
|
||||
import me.william278.husksync.bungeecord.config.ConfigLoader;
|
||||
import me.william278.husksync.bungeecord.config.ConfigManager;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.bungeecord.config.ConfigLoader;
|
||||
import net.william278.husksync.bungeecord.config.ConfigManager;
|
||||
import net.william278.husksync.bungeecord.util.BungeeUpdateChecker;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.proxy.command.HuskSyncCommand;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.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());
|
||||
@@ -317,7 +317,7 @@ public class BungeeCommand extends Command implements TabExecutor, HuskSyncComma
|
||||
// View the inventory of a player specified by their name
|
||||
private void openInventory(ProxiedPlayer viewer, String targetPlayerName, String clusterId) {
|
||||
if (viewer.getName().equalsIgnoreCase(targetPlayerName)) {
|
||||
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent());
|
||||
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_inventory")).toComponent());
|
||||
return;
|
||||
}
|
||||
if (ProxyServer.getInstance().getPlayer(targetPlayerName) != null) {
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.william278.husksync.bungeecord.config;
|
||||
package net.william278.husksync.bungeecord.config;
|
||||
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -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) {
|
||||
@@ -59,6 +60,8 @@ public class ConfigLoader {
|
||||
Settings.hikariKeepAliveTime = config.getLong("data_storage_settings.hikari_pool_settings.keepalive_time", 0);
|
||||
Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000);
|
||||
|
||||
Settings.bounceBackSynchronisation = config.getBoolean("bounce_back_synchronization", true);
|
||||
|
||||
// Read cluster data
|
||||
Configuration section = config.getSection("clusters");
|
||||
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync";
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.bungeecord.config;
|
||||
package net.william278.husksync.bungeecord.config;
|
||||
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.Settings;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
@@ -1,14 +1,15 @@
|
||||
package me.william278.husksync.bungeecord.listener;
|
||||
package net.william278.husksync.bungeecord.listener;
|
||||
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
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;
|
||||
@@ -1,14 +1,14 @@
|
||||
package me.william278.husksync.bungeecord.listener;
|
||||
package net.william278.husksync.bungeecord.listener;
|
||||
|
||||
import de.themoep.minedown.MineDown;
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.Server;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.redis.RedisListener;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.redis.RedisListener;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@@ -24,6 +24,7 @@ public class BungeeRedisListener extends RedisListener {
|
||||
|
||||
// Initialize the listener on the bungee
|
||||
public BungeeRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
@@ -62,40 +63,37 @@ public class BungeeRedisListener extends RedisListener {
|
||||
}
|
||||
|
||||
switch (message.getMessageType()) {
|
||||
case PLAYER_DATA_REQUEST -> {
|
||||
case PLAYER_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
// Get the UUID of the requesting player
|
||||
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
try {
|
||||
// Send the reply, serializing the message data
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
|
||||
.send();
|
||||
try {
|
||||
// Send the reply, serializing the message data
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
|
||||
.send();
|
||||
|
||||
// Send an update to all bukkit servers removing the player from the requester cache
|
||||
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
|
||||
.send();
|
||||
// Send an update to all bukkit servers removing the player from the requester cache
|
||||
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
|
||||
.send();
|
||||
|
||||
// Send synchronisation complete message
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
|
||||
if (player != null) {
|
||||
if (player.isConnected()) {
|
||||
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
|
||||
e.printStackTrace();
|
||||
// Send synchronisation complete message
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
|
||||
if (player != null) {
|
||||
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||
}
|
||||
});
|
||||
}
|
||||
case PLAYER_DATA_UPDATE -> {
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
case PLAYER_DATA_UPDATE -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
// Deserialize the PlayerData received
|
||||
PlayerData playerData;
|
||||
final String serializedPlayerData = message.getMessageData();
|
||||
final String serializedPlayerData = message.getMessageDataElements()[0];
|
||||
final boolean bounceBack = Boolean.parseBoolean(message.getMessageDataElements()[1]);
|
||||
try {
|
||||
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
@@ -113,10 +111,10 @@ public class BungeeRedisListener extends RedisListener {
|
||||
}
|
||||
|
||||
// Reply with the player data if they are still online (switching server)
|
||||
try {
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID());
|
||||
if (player != null) {
|
||||
if (player.isConnected()) {
|
||||
if (Settings.bounceBackSynchronisation && bounceBack) {
|
||||
try {
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID());
|
||||
if (player != null) {
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
|
||||
serializedPlayerData)
|
||||
@@ -125,12 +123,12 @@ public class BungeeRedisListener extends RedisListener {
|
||||
// Send synchronisation complete message
|
||||
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
case CONNECTION_HANDSHAKE -> {
|
||||
// Reply to a Bukkit server's connection handshake to complete the process
|
||||
if (HuskSyncBungeeCord.isDisabling) return; // Return if the Proxy is disabling
|
||||
@@ -197,10 +195,9 @@ public class BungeeRedisListener extends RedisListener {
|
||||
HuskSyncBungeeCord.dataManager));
|
||||
}
|
||||
}
|
||||
case API_DATA_REQUEST -> {
|
||||
case API_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
|
||||
|
||||
try {
|
||||
final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId());
|
||||
|
||||
@@ -220,7 +217,7 @@ public class BungeeRedisListener extends RedisListener {
|
||||
} catch (IOException e) {
|
||||
plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.bungeecord.util;
|
||||
package net.william278.husksync.bungeecord.util;
|
||||
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.bungeecord.util;
|
||||
package net.william278.husksync.bungeecord.util;
|
||||
|
||||
import me.william278.husksync.HuskSyncBungeeCord;
|
||||
import me.william278.husksync.util.UpdateChecker;
|
||||
import net.william278.husksync.HuskSyncBungeeCord;
|
||||
import net.william278.husksync.util.UpdateChecker;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: HuskSync
|
||||
version: @version@
|
||||
main: me.william278.husksync.HuskSyncBungeeCord
|
||||
version: ${version}
|
||||
main: net.william278.husksync.HuskSyncBungeeCord
|
||||
author: William278
|
||||
description: 'A modern, cross-server player data synchronization system'
|
||||
@@ -1,28 +1,11 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
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', 'net.william278.husksync.libraries'
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
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.exceptions.JedisException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class RedisListener {
|
||||
|
||||
/**
|
||||
* Determines if the RedisListener is working properly
|
||||
*/
|
||||
public boolean isActiveAndEnabled;
|
||||
|
||||
/**
|
||||
* Handle an incoming {@link RedisMessage}
|
||||
*
|
||||
* @param message The {@link RedisMessage} to handle
|
||||
*/
|
||||
public abstract void handleMessage(RedisMessage message);
|
||||
|
||||
/**
|
||||
* Log to console
|
||||
*
|
||||
* @param level The {@link Level} to log
|
||||
* @param message Message to log
|
||||
*/
|
||||
public abstract void log(Level level, String message);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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!");
|
||||
}
|
||||
} catch (JedisException e) {
|
||||
log(Level.SEVERE, "Failed to establish a connection to the Redis server!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.william278.husksync;
|
||||
package net.william278.husksync;
|
||||
|
||||
import java.io.*;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,11 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
private final UUID dataVersionUUID;
|
||||
|
||||
/**
|
||||
* Epoch time identifying when the data was last updated or created
|
||||
*/
|
||||
private long timestamp;
|
||||
|
||||
/**
|
||||
* A special flag that will be {@code true} if the player is new to the network and should not have their data set when joining the Bukkit
|
||||
*/
|
||||
@@ -70,6 +76,7 @@ public class PlayerData implements Serializable {
|
||||
String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode,
|
||||
String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) {
|
||||
this.dataVersionUUID = UUID.randomUUID();
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
this.playerUUID = playerUUID;
|
||||
this.serializedInventory = serializedInventory;
|
||||
this.serializedEnderChest = serializedEnderChest;
|
||||
@@ -109,16 +116,17 @@ public class PlayerData implements Serializable {
|
||||
* @param totalExperience Their total experience points ("Score")
|
||||
* @param expLevel Their exp level
|
||||
* @param expProgress Their exp progress to the next level
|
||||
* @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc)
|
||||
* @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc.)
|
||||
* @param serializedStatistics Their serialized statistics data (Displayed in Statistics menu in ESC menu)
|
||||
*/
|
||||
public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest,
|
||||
public PlayerData(UUID playerUUID, UUID dataVersionUUID, long timestamp, String serializedInventory, String serializedEnderChest,
|
||||
double health, double maxHealth, double healthScale, int hunger, float saturation, float saturationExhaustion,
|
||||
int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress,
|
||||
String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements,
|
||||
String serializedLocation) {
|
||||
this.playerUUID = playerUUID;
|
||||
this.dataVersionUUID = dataVersionUUID;
|
||||
this.timestamp = timestamp;
|
||||
this.serializedInventory = serializedInventory;
|
||||
this.serializedEnderChest = serializedEnderChest;
|
||||
this.health = health;
|
||||
@@ -172,6 +180,15 @@ public class PlayerData implements Serializable {
|
||||
return dataVersionUUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp when this data was created or last updated
|
||||
*
|
||||
* @return time since epoch of last data update or creation
|
||||
*/
|
||||
public long getDataTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized player {@code ItemStack[]} inventory
|
||||
*
|
||||
@@ -341,6 +358,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedInventory(String serializedInventory) {
|
||||
this.serializedInventory = serializedInventory;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,6 +368,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedEnderChest(String serializedEnderChest) {
|
||||
this.serializedEnderChest = serializedEnderChest;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,6 +378,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setHealth(double health) {
|
||||
this.health = health;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +388,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setMaxHealth(double maxHealth) {
|
||||
this.maxHealth = maxHealth;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,6 +398,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setHealthScale(double healthScale) {
|
||||
this.healthScale = healthScale;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,6 +408,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setHunger(int hunger) {
|
||||
this.hunger = hunger;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,6 +418,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSaturation(float saturation) {
|
||||
this.saturation = saturation;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,6 +428,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSaturationExhaustion(float saturationExhaustion) {
|
||||
this.saturationExhaustion = saturationExhaustion;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,6 +438,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSelectedSlot(int selectedSlot) {
|
||||
this.selectedSlot = selectedSlot;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,6 +448,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedEffectData(String serializedEffectData) {
|
||||
this.serializedEffectData = serializedEffectData;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -431,6 +458,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setTotalExperience(int totalExperience) {
|
||||
this.totalExperience = totalExperience;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,6 +468,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setExpLevel(int expLevel) {
|
||||
this.expLevel = expLevel;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,6 +478,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setExpProgress(float expProgress) {
|
||||
this.expProgress = expProgress;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -458,6 +488,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setGameMode(String gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,6 +498,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedStatistics(String serializedStatistics) {
|
||||
this.serializedStatistics = serializedStatistics;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,6 +508,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setFlying(boolean flying) {
|
||||
isFlying = flying;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,6 +518,7 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedAdvancements(String serializedAdvancements) {
|
||||
this.serializedAdvancements = serializedAdvancements;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,5 +528,6 @@ public class PlayerData implements Serializable {
|
||||
*/
|
||||
public void setSerializedLocation(String serializedLocation) {
|
||||
this.serializedLocation = serializedLocation;
|
||||
this.timestamp = Instant.now().getEpochSecond();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync;
|
||||
package net.william278.husksync;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync;
|
||||
package net.william278.husksync;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -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
|
||||
@@ -35,6 +36,9 @@ public class Settings {
|
||||
// SQL settings
|
||||
public static DataStorageType dataStorageType;
|
||||
|
||||
// Bounce-back synchronisation (default)
|
||||
public static boolean bounceBackSynchronisation;
|
||||
|
||||
// MySQL specific settings
|
||||
public static String mySQLHost;
|
||||
public static String mySQLDatabase;
|
||||
@@ -66,8 +70,8 @@ public class Settings {
|
||||
public static boolean syncAdvancements;
|
||||
public static boolean syncLocation;
|
||||
public static boolean syncFlight;
|
||||
|
||||
// Future
|
||||
public static long synchronizationTimeoutRetryDelay;
|
||||
public static boolean saveOnWorldSave;
|
||||
public static boolean useNativeImplementation;
|
||||
|
||||
// This Cluster ID
|
||||
@@ -1,13 +1,14 @@
|
||||
package me.william278.husksync.migrator;
|
||||
package net.william278.husksync.migrator;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Server;
|
||||
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.RedisMessage;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.proxy.data.DataManager;
|
||||
import net.william278.husksync.proxy.data.sql.Database;
|
||||
import net.william278.husksync.proxy.data.sql.MySQL;
|
||||
import net.william278.husksync.redis.RedisListener;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
@@ -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) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.migrator;
|
||||
package net.william278.husksync.migrator;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.proxy.command;
|
||||
package net.william278.husksync.proxy.command;
|
||||
|
||||
public interface HuskSyncCommand {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package me.william278.husksync.proxy.data;
|
||||
package net.william278.husksync.proxy.data;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.proxy.data.sql.Database;
|
||||
import me.william278.husksync.proxy.data.sql.MySQL;
|
||||
import me.william278.husksync.proxy.data.sql.SQLite;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.proxy.data.sql.Database;
|
||||
import net.william278.husksync.proxy.data.sql.MySQL;
|
||||
import net.william278.husksync.proxy.data.sql.SQLite;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -185,7 +184,7 @@ public class DataManager {
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid"));
|
||||
//final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp");
|
||||
final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp");
|
||||
final String serializedInventory = resultSet.getString("inventory");
|
||||
final String serializedEnderChest = resultSet.getString("ender_chest");
|
||||
final double health = resultSet.getDouble("health");
|
||||
@@ -205,10 +204,10 @@ public class DataManager {
|
||||
final String serializedLocationData = resultSet.getString("location");
|
||||
final String serializedStatisticData = resultSet.getString("statistics");
|
||||
|
||||
data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest,
|
||||
health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects,
|
||||
totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying,
|
||||
serializedAdvancementData, serializedLocationData));
|
||||
data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, dataSaveTimestamp.toInstant().getEpochSecond(),
|
||||
serializedInventory, serializedEnderChest, health, maxHealth, healthScale, hunger, saturation,
|
||||
saturationExhaustion, selectedSlot, serializedStatusEffects, totalExperience, expLevel, expProgress,
|
||||
gameMode, serializedStatisticData, isFlying, serializedAdvancementData, serializedLocationData));
|
||||
} else {
|
||||
data.put(cluster, PlayerData.DEFAULT_PLAYER_DATA(playerUUID));
|
||||
}
|
||||
@@ -240,7 +239,7 @@ public class DataManager {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE " + cluster.dataTableName() + " SET `version_uuid`=?, `timestamp`=?, `inventory`=?, `ender_chest`=?, `health`=?, `max_health`=?, `health_scale`=?, `hunger`=?, `saturation`=?, `saturation_exhaustion`=?, `selected_slot`=?, `status_effects`=?, `total_experience`=?, `exp_level`=?, `exp_progress`=?, `game_mode`=?, `statistics`=?, `is_flying`=?, `advancements`=?, `location`=? WHERE `player_id`=(SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?);")) {
|
||||
statement.setString(1, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(2, new Timestamp(Instant.now().getEpochSecond()));
|
||||
statement.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
|
||||
statement.setString(3, playerData.getSerializedInventory());
|
||||
statement.setString(4, playerData.getSerializedEnderChest());
|
||||
statement.setDouble(5, playerData.getHealth()); // Health
|
||||
@@ -274,7 +273,7 @@ public class DataManager {
|
||||
"INSERT INTO " + cluster.dataTableName() + " (`player_id`,`version_uuid`,`timestamp`,`inventory`,`ender_chest`,`health`,`max_health`,`health_scale`,`hunger`,`saturation`,`saturation_exhaustion`,`selected_slot`,`status_effects`,`total_experience`,`exp_level`,`exp_progress`,`game_mode`,`statistics`,`is_flying`,`advancements`,`location`) VALUES((SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) {
|
||||
statement.setString(1, playerData.getPlayerUUID().toString());
|
||||
statement.setString(2, playerData.getDataVersionUUID().toString());
|
||||
statement.setTimestamp(3, new Timestamp(Instant.now().getEpochSecond()));
|
||||
statement.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
|
||||
statement.setString(4, playerData.getSerializedInventory());
|
||||
statement.setString(5, playerData.getSerializedEnderChest());
|
||||
statement.setDouble(6, playerData.getHealth()); // Health
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.proxy.data.sql;
|
||||
package net.william278.husksync.proxy.data.sql;
|
||||
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.william278.husksync.proxy.data.sql;
|
||||
package net.william278.husksync.proxy.data.sql;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.william278.husksync.proxy.data.sql;
|
||||
package net.william278.husksync.proxy.data.sql;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -0,0 +1,126 @@
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import net.william278.husksync.Settings;
|
||||
import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class RedisListener {
|
||||
|
||||
/**
|
||||
* Determines if the RedisListener is working properly
|
||||
*/
|
||||
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}
|
||||
*
|
||||
* @param message The {@link RedisMessage} to handle
|
||||
*/
|
||||
public abstract void handleMessage(RedisMessage message);
|
||||
|
||||
/**
|
||||
* Log to console
|
||||
*
|
||||
* @param level The {@link Level} to log
|
||||
* @param message Message to log
|
||||
*/
|
||||
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() {
|
||||
new Thread(() -> {
|
||||
isActiveAndEnabled = true;
|
||||
while (isActiveAndEnabled) {
|
||||
|
||||
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);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
}, "Redis Subscriber").start();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.redis;
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.io.*;
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.util;
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.util;
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -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), [Ceddix](gray show_text=&7German, de-de), [小蔡](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua)\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)");
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.util;
|
||||
package net.william278.husksync.util;
|
||||
|
||||
public interface ThrowSupplier<T> {
|
||||
T get() throws Exception;
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync.util;
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -11,38 +11,35 @@ public abstract class UpdateChecker {
|
||||
|
||||
private final static int SPIGOT_PROJECT_ID = 97144;
|
||||
|
||||
private final String currentVersion;
|
||||
private String latestVersion;
|
||||
private final VersionUtils.Version currentVersion;
|
||||
private VersionUtils.Version latestVersion;
|
||||
|
||||
public UpdateChecker(String currentVersion) {
|
||||
this.currentVersion = currentVersion;
|
||||
this.currentVersion = VersionUtils.Version.of(currentVersion);
|
||||
|
||||
try {
|
||||
final URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + SPIGOT_PROJECT_ID);
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
this.latestVersion = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).readLine();
|
||||
this.latestVersion = VersionUtils.Version.of(new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).readLine());
|
||||
} catch (IOException e) {
|
||||
log(Level.WARNING, "Failed to check for updates: An IOException occurred.");
|
||||
this.latestVersion = "Unknown";
|
||||
this.latestVersion = new VersionUtils.Version();
|
||||
} catch (Exception e) {
|
||||
log(Level.WARNING, "Failed to check for updates: An exception occurred.");
|
||||
this.latestVersion = "Unknown";
|
||||
this.latestVersion = new VersionUtils.Version();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUpToDate() {
|
||||
if (latestVersion.equalsIgnoreCase("Unknown")) {
|
||||
return true;
|
||||
}
|
||||
return latestVersion.equals(currentVersion);
|
||||
return this.currentVersion.compareTo(latestVersion) >= 0;
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion;
|
||||
return latestVersion.toString();
|
||||
}
|
||||
|
||||
public String getCurrentVersion() {
|
||||
return currentVersion;
|
||||
return currentVersion.toString();
|
||||
}
|
||||
|
||||
public abstract void log(Level level, String message);
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.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/de-de.yml
Normal file
14
common/src/main/resources/languages/de-de.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[Daten synchronisiert!](#00fb9a)'
|
||||
viewing_inventory_of: '[Einsicht in das Inventar von](#00fb9a) [%1%](#00fb9a bold)'
|
||||
viewing_ender_chest_of: '[Einsicht in die Endertruhe von](#00fb9a) [%1%](#00fb9a bold)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Meldungsdateien wurden aktualisiert.](#00fb9a)'
|
||||
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Fehler:](#ff3300) [Dieser Spieler konnte nicht gefunden werden](#ff7e5e)'
|
||||
error_no_permission: '[Fehler:](#ff3300) [Du hast nicht die benötigten Berechtigungen um diesen Befehl auszuführen](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[Fehler:](#ff3300) [Du kannst nicht über HuskSync auf das Inventar eines Online-Spielers zugreifen](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[Fehler:](#ff3300) [Du kannst nicht über HuskSync auf die Endertruhe eines Online-Spielers zugreifen](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[Fehler:](#ff3300) [Du kannst nicht auf dein eigenes Inventar zugreifen!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[Fehler:](#ff3300) [Du kannst nicht auf deine eigene Endertruhe zugreifen!](#ff7e5e)'
|
||||
error_console_command_only: '[Fehler:](#ff3300) [Dieser Befehl kann nur über die %1% Konsole ausgeführt werden](#ff7e5e)'
|
||||
error_no_servers_proxied: '[Fehler:](#ff3300) [Vorgang konnte nicht verarbeitet werden; Es sind keine Server online, auf denen HuskSync installiert ist. Bitte stelle sicher, dass HuskSync sowohl auf dem Proxy-Server als auch auf allen Servern installiert ist, zwischen denen du Daten synchronisieren möchtest.](#ff7e5e)'
|
||||
error_invalid_cluster: '[Fehler:](#ff3300) [Bitte gib die ID eines gültigen Clusters an.](#ff7e5e)'
|
||||
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)'
|
||||
14
common/src/main/resources/languages/uk-ua.yml
Normal file
14
common/src/main/resources/languages/uk-ua.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[Дані синхронізовано!](#00fb9a)'
|
||||
viewing_inventory_of: '[Переглядання інвентару](#00fb9a) [%1%](#00fb9a bold)'
|
||||
viewing_ender_chest_of: '[Переглядання скрині енду](#00fb9a) [%1%](#00fb9a bold)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)'
|
||||
error_invalid_syntax: '[Помилка:](#ff3300) [Неправильний синтакс. Використання: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Помилка:](#ff3300) [Гравця не знайдено](#ff7e5e)'
|
||||
error_no_permission: '[Помилка:](#ff3300) [Ввас немає дозволу на використання цієї команди](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[Помилка:](#ff3300) [Ви не можете переглядати інвентар гравців, що знаходяться онлайн, з допомогую HuskSync](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[Помилка:](#ff3300) [Ви не можете переглядати скриню енду гравців, що знаходяться онлайн, з допомогую HuskSync](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[Помилка:](#ff3300) [Ви не можете переглядати власний інвентар!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[Помилка:](#ff3300) [Ви не можете переглядати власну скриню енду!](#ff7e5e)'
|
||||
error_console_command_only: '[Помилка:](#ff3300) [Ця команда може бути використана лише з допомогою %1% консолі](#ff7e5e)'
|
||||
error_no_servers_proxied: '[Помилка:](#ff3300) [Не вдалося опрацювати операцію; не знайдено жодного сервера із встановленим HuskSync. Запевніться, будьласка, що HuskSync встановлено на Проксі та усіх серверах між якими ви хочете синхроніхувати дані.](#ff7e5e)'
|
||||
error_invalid_cluster: '[Помилка:](#ff3300) [Зазнчте будь ласка ID слушного кластеру.](#ff7e5e)'
|
||||
14
common/src/main/resources/languages/zh-cn.yml
Normal file
14
common/src/main/resources/languages/zh-cn.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[数据同步完成](#00fb9a)'
|
||||
viewing_inventory_of: '[查看玩家背包:](#00fb9a) [%1%](#00fb9a bold)'
|
||||
viewing_ender_chest_of: '[查看玩家末影箱:](#00fb9a) [%1%](#00fb9a bold)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 配置与语言文件重载完成.](#00fb9a)'
|
||||
error_invalid_syntax: '[错误:](#ff3300) [格式错误. 使用方法: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[错误:](#ff3300) [未找到目标玩家](#ff7e5e)'
|
||||
error_no_permission: '[错误:](#ff3300) [你没有权限执行此命令](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[错误:](#ff3300) [你不能在玩家在线时通过 HuskSync 查看与编辑玩家物品栏](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[错误:](#ff3300) [你不能在玩家在线时通过 HuskSync 查看与编辑玩家末影箱](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[错误:](#ff3300) [你不能查看和编辑自己的物品栏!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[错误:](#ff3300) [你不能查看和编辑自己的末影箱!](#ff7e5e)'
|
||||
error_console_command_only: '[错误:](#ff3300) [该命令只能由 %1% 控制台执行](#ff7e5e)'
|
||||
error_no_servers_proxied: '[错误:](#ff3300) [操作处理失败; 没有任何安装了 HuskSync 的后端服务器在线. 请确认 HuskSync 已在 BungeeCord/Velocity 等代理服务器和所有你希望互相同步数据的后端服务器间安装.](#ff7e5e)'
|
||||
error_invalid_cluster: '[错误:](#ff3300) [请指定一个有效的集群(cluster) ID.](#ff7e5e)'
|
||||
14
common/src/main/resources/languages/zh-tw.yml
Normal file
14
common/src/main/resources/languages/zh-tw.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
synchronisation_complete: '[資料已同步!!](#00fb9a)'
|
||||
viewing_inventory_of: '[查看](#00fb9a) [%1%](#00fb9a bold) [的背包](#00fb9a)'
|
||||
viewing_ender_chest_of: '[查看](#00fb9a) [%1%](#00fb9a bold) [的終界箱](#00fb9a)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)'
|
||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
||||
error_no_permission: '[錯誤:](#ff3300) [您沒有權限執行這個指令](#ff7e5e)'
|
||||
error_cannot_view_inventory_online: '[錯誤:](#ff3300) [您無法通過 HuskSync 查看在線玩家的背包](#ff7e5e)'
|
||||
error_cannot_view_ender_chest_online: '[錯誤:](#ff3300) [您無法通過 HuskSync 查看在線玩家的終界箱](#ff7e5e)'
|
||||
error_cannot_view_own_inventory: '[錯誤:](#ff3300) [您無法查看自己的背包!](#ff7e5e)'
|
||||
error_cannot_view_own_ender_chest: '[錯誤:](#ff3300) [你無法查看自己的終界箱!](#ff7e5e)'
|
||||
error_console_command_only: '[錯誤:](#ff3300) [該指令只能通過 %1% 控制台運行](#ff7e5e)'
|
||||
error_no_servers_proxied: '[錯誤:](#ff3300) [處理操作失敗: 沒有安裝 HuskSync 的伺服器在線。 請確保在 Proxy 伺服器和您希望在其他同步數據的所有伺服器上都安裝了 HuskSync。](#ff7e5e)'
|
||||
error_invalid_cluster: '[錯誤:](#ff3300) [請提供有效的 Cluster ID](#ff7e5e)'
|
||||
@@ -3,6 +3,7 @@ redis_settings:
|
||||
host: 'localhost'
|
||||
port: 6379
|
||||
password: ''
|
||||
use_ssl: false
|
||||
data_storage_settings:
|
||||
database_type: 'sqlite'
|
||||
mysql_settings:
|
||||
@@ -18,6 +19,7 @@ data_storage_settings:
|
||||
maximum_lifetime: 1800000
|
||||
keepalive_time: 0
|
||||
connection_timeout: 5000
|
||||
bounce_back_synchronization: true
|
||||
clusters:
|
||||
main:
|
||||
player_table: 'husksync_players'
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
javaVersion=16
|
||||
javaVersion=16
|
||||
plugin_version=1.4
|
||||
plugin_archive=husksync
|
||||
@@ -6,7 +6,8 @@ before_install:
|
||||
- 'chmod +x gradlew'
|
||||
- 'chmod +x ./.jitpack/ensure-java-16'
|
||||
- 'bash ./.jitpack/ensure-java-16 install'
|
||||
- 'bash ./.jitpack/install-mpdb-converter'
|
||||
install:
|
||||
- 'if ! ./.jitpack/ensure-java-16 use; then source ~/.sdkman/bin/sdkman-init.sh; fi'
|
||||
- 'java -version'
|
||||
- './gradlew api:clean api:publishToMavenLocal'
|
||||
- './gradlew clean publishToMavenLocal'
|
||||
@@ -1,30 +1,28 @@
|
||||
//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: ':api', 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 = 'net.william278'
|
||||
artifactId = 'husksync-plugin'
|
||||
version = "$rootProject.version"
|
||||
|
||||
artifact shadowJar
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
dependencies {
|
||||
compileOnly project(':common')
|
||||
implementation project(path: ':common', configuration: 'shadow')
|
||||
implementation project(path: ':common')
|
||||
|
||||
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', 'net.william278.husksync.libraries'
|
||||
relocate 'net.byteflux', 'net.william278.husksync.libraries'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel'){}
|
||||
dependencies {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
exclude dependency(':slf4j-api')
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.william278.husksync;
|
||||
package net.william278.husksync;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.command.CommandManager;
|
||||
@@ -7,18 +7,21 @@ 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;
|
||||
import me.william278.husksync.proxy.data.DataManager;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.velocity.command.VelocityCommand;
|
||||
import me.william278.husksync.velocity.config.ConfigLoader;
|
||||
import me.william278.husksync.velocity.config.ConfigManager;
|
||||
import me.william278.husksync.velocity.listener.VelocityEventListener;
|
||||
import me.william278.husksync.velocity.listener.VelocityRedisListener;
|
||||
import me.william278.husksync.velocity.util.VelocityLogger;
|
||||
import me.william278.husksync.velocity.util.VelocityUpdateChecker;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.proxy.data.DataManager;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.velocity.command.VelocityCommand;
|
||||
import net.william278.husksync.velocity.config.ConfigLoader;
|
||||
import net.william278.husksync.velocity.config.ConfigManager;
|
||||
import net.william278.husksync.velocity.listener.VelocityEventListener;
|
||||
import net.william278.husksync.velocity.listener.VelocityRedisListener;
|
||||
import net.william278.husksync.velocity.util.VelocityLogger;
|
||||
import net.william278.husksync.velocity.util.VelocityUpdateChecker;
|
||||
import net.byteflux.libby.Library;
|
||||
import net.byteflux.libby.VelocityLibraryManager;
|
||||
import org.bstats.velocity.Metrics;
|
||||
@@ -31,19 +34,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 +63,8 @@ public class HuskSyncVelocity {
|
||||
|
||||
public static DataManager dataManager;
|
||||
|
||||
public static VelocityRedisListener redisListener;
|
||||
|
||||
public static MPDBMigrator mpdbMigrator;
|
||||
|
||||
private final Logger logger;
|
||||
@@ -92,11 +89,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 +144,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());
|
||||
@@ -208,7 +204,7 @@ public class HuskSyncVelocity {
|
||||
Library mySqlLib = Library.builder()
|
||||
.groupId("mysql")
|
||||
.artifactId("mysql-connector-java")
|
||||
.version("8.0.27")
|
||||
.version("8.0.29")
|
||||
.build();
|
||||
|
||||
Library sqLiteLib = Library.builder()
|
||||
@@ -1,20 +1,20 @@
|
||||
package me.william278.husksync.velocity.command;
|
||||
package net.william278.husksync.velocity.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Server;
|
||||
import me.william278.husksync.Settings;
|
||||
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 me.william278.husksync.velocity.util.VelocityUpdateChecker;
|
||||
import me.william278.husksync.velocity.config.ConfigLoader;
|
||||
import me.william278.husksync.velocity.config.ConfigManager;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.proxy.command.HuskSyncCommand;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.velocity.util.VelocityUpdateChecker;
|
||||
import net.william278.husksync.velocity.config.ConfigLoader;
|
||||
import net.william278.husksync.velocity.config.ConfigManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
@@ -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());
|
||||
@@ -310,7 +310,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand {
|
||||
// View the inventory of a player specified by their name
|
||||
private void openInventory(Player viewer, String targetPlayerName, String clusterId) {
|
||||
if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) {
|
||||
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent());
|
||||
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_inventory")).toComponent());
|
||||
return;
|
||||
}
|
||||
if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) {
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.william278.husksync.velocity.config;
|
||||
package net.william278.husksync.velocity.config;
|
||||
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -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) {
|
||||
@@ -75,6 +75,8 @@ public class ConfigLoader {
|
||||
Settings.hikariKeepAliveTime = getConfigLong(config, 0, "data_storage_settings", "hikari_pool_settings", "keepalive_time");
|
||||
Settings.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout");
|
||||
|
||||
Settings.bounceBackSynchronisation = getConfigBoolean(config, true,"bounce_back_synchronization");
|
||||
|
||||
// Read cluster data
|
||||
ConfigurationNode clusterSection = config.getNode("clusters");
|
||||
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync";
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.velocity.config;
|
||||
package net.william278.husksync.velocity.config;
|
||||
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.Settings;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.Settings;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
@@ -1,12 +1,13 @@
|
||||
package me.william278.husksync.velocity.listener;
|
||||
package net.william278.husksync.velocity.listener;
|
||||
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -16,7 +17,7 @@ public class VelocityEventListener {
|
||||
|
||||
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
|
||||
|
||||
@Subscribe
|
||||
@Subscribe(order = PostOrder.FIRST)
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||
@@ -1,15 +1,15 @@
|
||||
package me.william278.husksync.velocity.listener;
|
||||
package net.william278.husksync.velocity.listener;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.PlayerData;
|
||||
import me.william278.husksync.Server;
|
||||
import me.william278.husksync.Settings;
|
||||
import me.william278.husksync.migrator.MPDBMigrator;
|
||||
import me.william278.husksync.redis.RedisListener;
|
||||
import me.william278.husksync.redis.RedisMessage;
|
||||
import me.william278.husksync.util.MessageManager;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.PlayerData;
|
||||
import net.william278.husksync.Server;
|
||||
import net.william278.husksync.Settings;
|
||||
import net.william278.husksync.migrator.MPDBMigrator;
|
||||
import net.william278.husksync.redis.RedisListener;
|
||||
import net.william278.husksync.redis.RedisMessage;
|
||||
import net.william278.husksync.util.MessageManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
@@ -23,6 +23,7 @@ public class VelocityRedisListener extends RedisListener {
|
||||
|
||||
// Initialize the listener on the bungee
|
||||
public VelocityRedisListener() {
|
||||
super();
|
||||
listen();
|
||||
}
|
||||
|
||||
@@ -61,36 +62,35 @@ public class VelocityRedisListener extends RedisListener {
|
||||
}
|
||||
|
||||
switch (message.getMessageType()) {
|
||||
case PLAYER_DATA_REQUEST -> {
|
||||
case PLAYER_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||
// Get the UUID of the requesting player
|
||||
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
|
||||
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||
try {
|
||||
// Send the reply, serializing the message data
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
|
||||
.send();
|
||||
try {
|
||||
// Send the reply, serializing the message data
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
|
||||
.send();
|
||||
|
||||
// Send an update to all bukkit servers removing the player from the requester cache
|
||||
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
|
||||
.send();
|
||||
// Send an update to all bukkit servers removing the player from the requester cache
|
||||
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
|
||||
.send();
|
||||
|
||||
// Send synchronisation complete message
|
||||
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
|
||||
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).schedule();
|
||||
}
|
||||
case PLAYER_DATA_UPDATE -> {
|
||||
// Send synchronisation complete message
|
||||
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
|
||||
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).schedule();
|
||||
case PLAYER_DATA_UPDATE -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||
// Deserialize the PlayerData received
|
||||
PlayerData playerData;
|
||||
final String serializedPlayerData = message.getMessageData();
|
||||
final String serializedPlayerData = message.getMessageDataElements()[0];
|
||||
final boolean bounceBack = Boolean.parseBoolean(message.getMessageDataElements()[1]);
|
||||
try {
|
||||
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
@@ -108,23 +108,24 @@ public class VelocityRedisListener extends RedisListener {
|
||||
}
|
||||
|
||||
// Reply with the player data if they are still online (switching server)
|
||||
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
|
||||
updatingPlayer.ifPresent(player -> {
|
||||
try {
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(playerData))
|
||||
.send();
|
||||
if (Settings.bounceBackSynchronisation && bounceBack) {
|
||||
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
|
||||
updatingPlayer.ifPresent(player -> {
|
||||
try {
|
||||
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
|
||||
RedisMessage.serialize(playerData))
|
||||
.send();
|
||||
|
||||
// Send synchronisation complete message
|
||||
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
// Send synchronisation complete message
|
||||
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||
} catch (IOException e) {
|
||||
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).schedule();
|
||||
case CONNECTION_HANDSHAKE -> {
|
||||
// Reply to a Bukkit server's connection handshake to complete the process
|
||||
if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling
|
||||
@@ -190,7 +191,7 @@ public class VelocityRedisListener extends RedisListener {
|
||||
migrator.loadIncomingData(migrator.incomingPlayerData, HuskSyncVelocity.dataManager);
|
||||
}
|
||||
}
|
||||
case API_DATA_REQUEST -> {
|
||||
case API_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||
final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
|
||||
|
||||
@@ -213,7 +214,7 @@ public class VelocityRedisListener extends RedisListener {
|
||||
} catch (IOException e) {
|
||||
plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API");
|
||||
}
|
||||
}
|
||||
}).schedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.william278.husksync.velocity.util;
|
||||
package net.william278.husksync.velocity.util;
|
||||
|
||||
import me.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.Logger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.william278.husksync.velocity.util;
|
||||
package net.william278.husksync.velocity.util;
|
||||
|
||||
import me.william278.husksync.HuskSyncVelocity;
|
||||
import me.william278.husksync.util.UpdateChecker;
|
||||
import net.william278.husksync.HuskSyncVelocity;
|
||||
import net.william278.husksync.util.UpdateChecker;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
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": "net.william278.husksync.HuskSyncVelocity"
|
||||
}
|
||||
Reference in New Issue
Block a user