9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-23 08:39:19 +00:00

Compare commits

..

50 Commits
1.3.2 ... 1.4

Author SHA1 Message Date
William
633847a254 Fix inventory clearing on world save 2022-06-08 11:17:53 +01:00
William
3cd144088d Support deserializing old package stuff 2022-06-08 11:11:51 +01:00
William
e312e8dd01 Fix hikari shading 2022-06-08 01:47:13 +01:00
William
2b6e2a1ddc Prep 1.4 release 2022-06-08 01:15:54 +01:00
William
aed68b3045 follow redirects in curl 2022-06-08 00:35:52 +01:00
William
6ee5141c8f Missing -Dfile= 2022-06-08 00:19:57 +01:00
William
3ff3c1207c change curl 2022-06-08 00:15:58 +01:00
William
5947bac82c fix param passing 2022-06-08 00:11:41 +01:00
William
baab59d17a fix maven command 2022-06-08 00:03:36 +01:00
William
e98f272dfd another typo 2022-06-08 00:00:21 +01:00
William
f4b680bee7 Typo in script 2022-06-07 23:57:21 +01:00
William
73fe184e70 add mpdbdataconverter install script 2022-06-07 23:55:35 +01:00
William
bdc07f064d Update jitpack.yml 2022-06-07 23:39:19 +01:00
William
01aa28f28c Update jitpack.yml 2022-06-07 23:35:46 +01:00
William
5de81f06d6 Remove MPDB dependency requirement. Now uses a library I made. 2022-06-07 23:16:05 +01:00
William
d5da516f17 or not 2022-06-07 22:38:29 +01:00
William
5e0e3fd27d exclude mpdb from build 2022-06-07 22:36:15 +01:00
William
5cee7cca84 Update build script 2022-06-07 22:05:29 +01:00
William
330627553e Update jitpack.yml 2022-06-07 21:59:27 +01:00
William
359f0d6f18 Refactor API 2022-06-07 21:59:02 +01:00
William
b531196d8a Small typo fix 2022-06-07 21:00:51 +01:00
William
951fc27a67 It's 2022 now 2022-06-07 21:00:33 +01:00
William
a5f7b37fac Fix incompatibility with custom health scalars, close #25 2022-06-07 20:59:59 +01:00
William
da7a85dde9 Save player data on WorldSaveEvent, add config option, close #21 2022-06-07 20:55:47 +01:00
William
0f215c80ea Update jitpack.yml automation 2022-06-07 20:45:43 +01:00
William
a76aecdd23 Refactor package to net.william278; update dependencies & stop shading internal modules 2022-06-07 20:44:53 +01:00
William
2f3b0f37e8 Fix wrong locale being displayed 2022-06-07 20:27:09 +01:00
William
330476ee23 Credit for Ukrainian locales 2022-05-13 21:59:48 +01:00
Thourgard
6bf36bcbb1 Create uk-ua.yml 2022-05-13 22:21:38 +03:00
William
a25b7a2c89 Add command usage details to README 2022-05-01 16:00:54 +01:00
William
ab3271c0ec Add simplified chinese credit 2022-03-15 12:37:46 +00:00
William
0fd5e4eb36 Merge pull request #24 from Ghost-chu/master
Simplified Chinese (China Mainland) translation
2022-03-15 12:37:09 +00:00
William
996b9bc63d Fix zh-tw file formatting, add credit 2022-03-15 12:36:48 +00:00
Ghost_chu
bedb903215 Simplified Chinese (China Mainland) 2022-03-15 14:20:04 +08:00
William
d8b80388fd Merge pull request #23 from davgo0103/master
Add Traditional Chinese (Taiwan) language file
2022-03-14 17:06:04 +00:00
小蔡
3f77b8b5f6 Delete .vs directory 2022-03-14 00:52:02 +08:00
小蔡
bd599081e5 Delete ProjectSettings.json 2022-03-13 00:54:35 +08:00
davgo0103
4d5902132c Add Traditional Chinese (Taiwan) language file 2022-03-13 00:49:10 +08:00
davgo0103
bd3c080b4f Add Traditional Chinese (Taiwan) language file 2022-03-12 21:20:48 +08:00
William
dd46a6cdd5 Disable native_advancement_synchronization by default 2022-03-04 13:18:10 +00:00
William
7d453b7438 Merge remote-tracking branch 'origin/master' 2022-02-26 21:52:39 +00:00
William
c5e0640f83 Async proxy data setting and fetching; add timestamp API to player data; add option for bounceBackSyncrhonization 2022-02-26 21:52:34 +00:00
William
fb3b2bd66e Update README.md 2022-02-19 19:31:03 +00:00
William
cfe3879010 Update README.md 2022-02-19 19:29:38 +00:00
William
aedb517662 Update README.md 2022-02-19 19:27:19 +00:00
William
b7e6861f03 Add credit for ceddix 2022-02-18 12:36:41 +00:00
William
61072bfa51 Merge remote-tracking branch 'origin/master' 2022-02-18 12:35:32 +00:00
William
ae439595ea Async data serialization and add synchronization_timeout_retry_delay config option 2022-02-18 12:35:27 +00:00
William
e1f6e40624 Merge pull request #19 from Ceddix/master
Create de-de.yml
2022-02-16 14:36:54 +00:00
Ceddix
ff17b58473 Create de-de.yml 2022-02-16 15:15:01 +01:00
72 changed files with 660 additions and 543 deletions

2
.gitignore vendored
View File

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

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

View File

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

View File

@@ -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
![Flow chart showing different processes of how the plugin works](images/flow-chart.png)
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/v/WiIIiam278/HuskSync.svg)](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/v/WiIIiam278/HuskSync.svg)](https://jitpack.io/#WiIIiam278/HuskSync)
Then add the dependency as follows. Replace `version` with the latest version of HuskSync: [![](https://jitpack.io/v/net.william278/HuskSync.svg)](https://jitpack.io/#net.william278/HuskSync)
```
dependencies {
compileOnly 'com.github.WiIIiam278:HuskSync:version'
dependencies {
compileOnly 'net.william278:HuskSync:version'
}
```
@@ -174,7 +179,7 @@ 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());
@@ -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:
```
@@ -217,7 +221,7 @@ This plugin uses bStats to provide me with metrics about its usage:
* [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)

View File

@@ -1,49 +1,20 @@
//file:noinspection GroovyAssignabilityCheck
plugins {
id 'java-library'
id 'maven-publish'
}
dependencies {
compileOnly project(path: ':common', configuration: 'shadow')
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'
compileOnly 'org.jetbrains:annotations:23.0.0'
}
repositories {
mavenCentral()
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
shadowJar {
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'
}
afterEvaluate {
publishing {
publications {
maven(MavenPublication) {
groupId = "${rootProject.group}.${rootProject.name.toLowerCase()}"
artifactId = project.name
from components.java
artifact javadocsJar
}
}
repositories {
mavenLocal()
}
}
}
task javadocs(type: Javadoc) {
options.encoding = 'UTF-8'
options.addStringOption('Xdoclint:none', '-quiet')
source = project(':common').sourceSets.main.allJava
source += project(':api').sourceSets.main.allJava
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) {
archiveClassifier.set 'javadoc'
from javadocs.destinationDir
java {
withSourcesJar()
withJavadocJar()
}

View File

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

View File

@@ -4,7 +4,7 @@ plugins {
id 'java'
}
group 'me.william278'
group 'net.william278'
version "$ext.plugin_version+${versionMetadata()}"
ext {
@@ -19,6 +19,7 @@ allprojects {
compileJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8'
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
compileJava.options.release.set 16
@@ -34,7 +35,7 @@ allprojects {
}
dependencies {
implementation('redis.clients:jedis:4.1.1') {
implementation('redis.clients:jedis:4.2.3') {
//noinspection GroovyAssignabilityCheck
exclude module: 'slf4j-api'
}
@@ -50,7 +51,7 @@ subprojects {
version rootProject.version
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
if (['bukkit', 'bungeecord', 'velocity', 'plugin'].contains(project.name)) {
if (['bukkit', 'api', 'bungeecord', 'velocity', 'plugin'].contains(project.name)) {
shadowJar {
destinationDirectory.set(file("$rootDir/target"))
archiveClassifier.set('')

View File

@@ -1,19 +1,18 @@
dependencies {
implementation project(':api')
implementation project(path: ':common', configuration: 'shadow')
implementation project(path: ':common')
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 'de.themoep', 'me.william278.husksync.libraries'
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
relocate 'redis.clients', 'me.william278.husksync.libraries'
relocate 'org.apache', 'me.william278.husksync.libraries'
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'
}

View File

@@ -0,0 +1,57 @@
package me.william278.husksync.bukkit.data;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import java.io.Serializable;
import java.time.Instant;
import java.util.*;
/**
* Holds legacy data store methods for data storage
*/
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed")
public class DataSerializer {
/**
* A record used to store data for advancement synchronisation
*
* @deprecated Old format - Use {@link AdvancementRecordDate} instead
*/
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed")
// Suppress deprecation warnings here (still used for backwards compatibility)
public record AdvancementRecord(String advancementKey,
ArrayList<String> awardedAdvancementCriteria) implements Serializable {
}
/**
* A record used to store data for a player's statistics
*/
public record StatisticData(HashMap<Statistic, Integer> untypedStatisticValues,
HashMap<Statistic, HashMap<Material, Integer>> blockStatisticValues,
HashMap<Statistic, HashMap<Material, Integer>> itemStatisticValues,
HashMap<Statistic, HashMap<EntityType, Integer>> entityStatisticValues) implements Serializable {
}
/**
* A record used to store data for native advancement synchronisation, tracking advancement date progress
*/
public record AdvancementRecordDate(String key, Map<String, Date> criteriaMap) implements Serializable {
public AdvancementRecordDate(String key, List<String> criteriaList) {
this(key, new HashMap<>() {{
criteriaList.forEach(s -> put(s, Date.from(Instant.EPOCH)));
}});
}
}
/**
* A record used to store data for a player's location
*/
public record PlayerLocation(double x, double y, double z, float yaw, float pitch,
String worldName, World.Environment environment) implements Serializable {
}
}

View File

@@ -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;
@@ -143,7 +144,11 @@ 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);
// Clear player inventory and ender chest
player.getInventory().clear();
player.getEnderChest().clear();
}
getLogger().info("Data save complete!");
}

View File

@@ -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 {
@@ -27,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);
}
}

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.bukkit.data;
package net.william278.husksync.bukkit.data;
import java.util.HashMap;
import java.util.HashSet;

View File

@@ -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;
@@ -15,12 +15,10 @@ import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
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 {
@@ -194,12 +192,12 @@ public class DataSerializer {
return serializedPotionEffect != null ? new PotionEffect((Map<String, Object>) serializedPotionEffect) : null;
}
public static DataSerializer.PlayerLocation deserializePlayerLocationData(String serializedLocationData) throws IOException {
public static me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation deserializePlayerLocationData(String serializedLocationData) throws IOException {
if (serializedLocationData.isEmpty()) {
return null;
}
try {
return (DataSerializer.PlayerLocation) RedisMessage.deserialize(serializedLocationData);
return (me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation) RedisMessage.deserialize(serializedLocationData);
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
@@ -207,19 +205,19 @@ public class DataSerializer {
public static String getSerializedLocation(Player player) throws IOException {
final Location playerLocation = player.getLocation();
return RedisMessage.serialize(new DataSerializer.PlayerLocation(playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(),
return RedisMessage.serialize(new me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation(playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(),
playerLocation.getYaw(), playerLocation.getPitch(), player.getWorld().getName(), player.getWorld().getEnvironment()));
}
/**
* Deserializes a player's advancement data as serialized with {@link #getSerializedAdvancements(Player)} into {@link AdvancementRecordDate} data.
* Deserializes a player's advancement data as serialized with {@link #getSerializedAdvancements(Player)} into {@link me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate} data.
*
* @param serializedAdvancementData The serialized advancement data {@link String}
* @return The deserialized {@link AdvancementRecordDate} for the player
* @return The deserialized {@link me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate} for the player
* @throws IOException If the deserialization fails
*/
@SuppressWarnings("unchecked") // Ignore the unchecked cast here
public static List<DataSerializer.AdvancementRecordDate> deserializeAdvancementData(String serializedAdvancementData) throws IOException {
public static List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> deserializeAdvancementData(String serializedAdvancementData) throws IOException {
if (serializedAdvancementData.isEmpty()) {
return new ArrayList<>();
}
@@ -227,15 +225,15 @@ public class DataSerializer {
List<?> deserialize = (List<?>) RedisMessage.deserialize(serializedAdvancementData);
// Migrate old AdvancementRecord into date format
if (!deserialize.isEmpty() && deserialize.get(0) instanceof AdvancementRecord) {
deserialize = ((List<AdvancementRecord>) deserialize).stream()
.map(o -> new AdvancementRecordDate(
o.advancementKey,
o.awardedAdvancementCriteria
if (!deserialize.isEmpty() && deserialize.get(0) instanceof me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecord) {
deserialize = ((List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecord>) deserialize).stream()
.map(o -> new me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate(
o.advancementKey(),
o.awardedAdvancementCriteria()
)).toList();
}
return (List<AdvancementRecordDate>) deserialize;
return (List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate>) deserialize;
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
@@ -250,7 +248,7 @@ public class DataSerializer {
*/
public static String getSerializedAdvancements(Player player) throws IOException {
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
ArrayList<DataSerializer.AdvancementRecordDate> advancementData = new ArrayList<>();
ArrayList<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> advancementData = new ArrayList<>();
while (serverAdvancements.hasNext()) {
final AdvancementProgress progress = player.getAdvancementProgress(serverAdvancements.next());
@@ -259,25 +257,25 @@ public class DataSerializer {
final Map<String, Date> awardedCriteria = new HashMap<>();
progress.getAwardedCriteria().forEach(s -> awardedCriteria.put(s, progress.getDateAwarded(s)));
advancementData.add(new DataSerializer.AdvancementRecordDate(advancementKey.getNamespace() + ":" + advancementKey.getKey(), awardedCriteria));
advancementData.add(new me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate(advancementKey.getNamespace() + ":" + advancementKey.getKey(), awardedCriteria));
}
return RedisMessage.serialize(advancementData);
}
/**
* Deserializes a player's statistic data as serialized with {@link #getSerializedStatisticData(Player)} into {@link StatisticData}.
* Deserializes a player's statistic data as serialized with {@link #getSerializedStatisticData(Player)} into {@link me.william278.husksync.bukkit.data.DataSerializer.StatisticData}.
*
* @param serializedStatisticData The serialized statistic data {@link String}
* @return The deserialized {@link StatisticData} for the player
* @return The deserialized {@link me.william278.husksync.bukkit.data.DataSerializer.StatisticData} for the player
* @throws IOException If the deserialization fails
*/
public static DataSerializer.StatisticData deserializeStatisticData(String serializedStatisticData) throws IOException {
public static me.william278.husksync.bukkit.data.DataSerializer.StatisticData deserializeStatisticData(String serializedStatisticData) throws IOException {
if (serializedStatisticData.isEmpty()) {
return new DataSerializer.StatisticData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
return new me.william278.husksync.bukkit.data.DataSerializer.StatisticData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
}
try {
return (DataSerializer.StatisticData) RedisMessage.deserialize(serializedStatisticData);
return (me.william278.husksync.bukkit.data.DataSerializer.StatisticData) RedisMessage.deserialize(serializedStatisticData);
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
@@ -322,46 +320,8 @@ public class DataSerializer {
}
}
DataSerializer.StatisticData statisticData = new DataSerializer.StatisticData(untypedStatisticValues, blockStatisticValues, itemStatisticValues, entityStatisticValues);
me.william278.husksync.bukkit.data.DataSerializer.StatisticData statisticData = new me.william278.husksync.bukkit.data.DataSerializer.StatisticData(untypedStatisticValues, blockStatisticValues, itemStatisticValues, entityStatisticValues);
return RedisMessage.serialize(statisticData);
}
/**
* A record used to store data for a player's location
*/
public record PlayerLocation(double x, double y, double z, float yaw, float pitch,
String worldName, World.Environment environment) implements Serializable {
}
/**
* A record used to store data for advancement synchronisation
*
* @deprecated Old format - Use {@link AdvancementRecordDate} instead
*/
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed") // Suppress deprecation warnings here (still used for backwards compatibility)
public record AdvancementRecord(String advancementKey,
ArrayList<String> awardedAdvancementCriteria) implements Serializable {
}
/**
* A record used to store data for native advancement synchronisation, tracking advancement date progress
*/
public record AdvancementRecordDate(String key, Map<String, Date> criteriaMap) implements Serializable {
AdvancementRecordDate(String key, List<String> criteriaList) {
this(key, new HashMap<>() {{
criteriaList.forEach(s -> put(s, Date.from(Instant.EPOCH)));
}});
}
}
/**
* A record used to store data for a player's statistics
*/
public record StatisticData(HashMap<Statistic, Integer> untypedStatisticValues,
HashMap<Statistic, HashMap<Material, Integer>> blockStatisticValues,
HashMap<Statistic, HashMap<Material, Integer>> itemStatisticValues,
HashMap<Statistic, HashMap<EntityType, Integer>> entityStatisticValues) implements Serializable {
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -37,7 +39,14 @@ public class BukkitEventListener implements Listener {
return; // If the plugin has not been initialized correctly
// Update the player's data
PlayerSetter.updatePlayerData(player);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Update data to proxy
PlayerSetter.updatePlayerData(player, true);
// Clear player inventory and ender chest
player.getInventory().clear();
player.getEnderChest().clear();
});
}
@EventHandler(priority = EventPriority.LOWEST)
@@ -50,19 +59,22 @@ public class BukkitEventListener implements Listener {
// Mark the player as awaiting data fetch
HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId());
if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled)
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())) {
@@ -72,7 +84,7 @@ public class BukkitEventListener implements Listener {
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e);
}
}
}, 5);
}, Settings.synchronizationTimeoutRetryDelay);
}
}
@@ -141,4 +153,14 @@ public class BukkitEventListener implements Listener {
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);
}
}
}

View File

@@ -1,29 +1,32 @@
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();
@@ -111,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");
}
@@ -124,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 -> {

View File

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

View File

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

View File

@@ -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;
@@ -95,22 +95,19 @@ 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);
}
// Clear player inventory and ender chest
player.getInventory().clear();
player.getEnderChest().clear();
}
/**
@@ -158,7 +155,7 @@ public class PlayerSetter {
// Set the player's data from the PlayerData
try {
if (Settings.syncAdvancements) {
List<DataSerializer.AdvancementRecordDate> advancementRecords
List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> advancementRecords
= DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements());
if (Settings.useNativeImplementation) {
@@ -276,11 +273,11 @@ public class PlayerSetter {
}
}
private static void nativeSyncPlayerAdvancements(final Player player, final List<DataSerializer.AdvancementRecordDate> advancementRecords) {
private static void nativeSyncPlayerAdvancements(final Player player, final List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> advancementRecords) {
final Object playerAdvancements = AdvancementUtils.getPlayerAdvancements(player);
// Clear
AdvancementUtils.clearPlayerAdvancements(playerAdvancements);
AdvancementUtils.clearPlayerAdvancements(playerAdvancements);
AdvancementUtils.clearVisibleAdvancements(playerAdvancements);
advancementRecords.forEach(advancementRecord -> {
@@ -315,9 +312,9 @@ public class PlayerSetter {
* Update a player's advancements and progress to match the advancementData
*
* @param player The player to set the advancements of
* @param advancementData The ArrayList of {@link DataSerializer.AdvancementRecordDate}s to set
* @param advancementData The ArrayList of {@link me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate}s to set
*/
private static void setPlayerAdvancements(Player player, List<DataSerializer.AdvancementRecordDate> advancementData, PlayerData data) {
private static void setPlayerAdvancements(Player player, List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> advancementData, PlayerData data) {
// Temporarily disable advancement announcing if needed
boolean announceAdvancementUpdate = false;
if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) {
@@ -330,12 +327,12 @@ 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();
AdvancementProgress playerProgress = player.getAdvancementProgress(advancement);
for (DataSerializer.AdvancementRecordDate record : advancementData) {
for (me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate record : advancementData) {
// If the advancement is one on the data
if (record.key().equals(advancement.getKey().getNamespace() + ":" + advancement.getKey().getKey())) {
@@ -378,9 +375,9 @@ public class PlayerSetter {
* Set a player's statistics (in the Statistic menu)
*
* @param player The player to set the statistics of
* @param statisticData The {@link DataSerializer.StatisticData} to set
* @param statisticData The {@link me.william278.husksync.bukkit.data.DataSerializer.StatisticData} to set
*/
private static void setPlayerStatistics(Player player, DataSerializer.StatisticData statisticData) {
private static void setPlayerStatistics(Player player, me.william278.husksync.bukkit.data.DataSerializer.StatisticData statisticData) {
// Set untyped statistics
for (Statistic statistic : statisticData.untypedStatisticValues().keySet()) {
player.setStatistic(statistic, statisticData.untypedStatisticValues().get(statistic));
@@ -421,12 +418,12 @@ public class PlayerSetter {
}
/**
* Set a player's location from {@link DataSerializer.PlayerLocation} data
* Set a player's location from {@link me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation} data
*
* @param player The {@link Player} to teleport
* @param location The {@link DataSerializer.PlayerLocation}
* @param location The {@link me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation}
*/
private static void setPlayerLocation(Player player, DataSerializer.PlayerLocation location) {
private static void setPlayerLocation(Player player, me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation location) {
// Don't teleport if the location is invalid
if (location == null) {
return;
@@ -463,12 +460,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) {

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package me.william278.husksync.bukkit.util.nms;
package net.william278.husksync.bukkit.util.nms;
import me.william278.husksync.util.ThrowSupplier;
import me.william278.husksync.util.VersionUtils;
import net.william278.husksync.util.ThrowSupplier;
import net.william278.husksync.util.VersionUtils;
import org.bukkit.Bukkit;
public class MinecraftVersionUtils {

View File

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

View File

@@ -1,6 +1,6 @@
name: HuskSync
version: ${version}
main: me.william278.husksync.HuskSyncBukkit
main: net.william278.husksync.HuskSyncBukkit
api-version: 1.16
author: William278
description: 'A modern, cross-server player data synchronization system'

View File

@@ -1,5 +1,5 @@
dependencies {
implementation project(path: ':common', configuration: 'shadow')
implementation project(path: ':common')
implementation 'com.zaxxer:HikariCP:5.0.1'
implementation 'org.bstats:bstats-bungeecord:3.0.0'
@@ -10,14 +10,11 @@ dependencies {
}
shadowJar {
relocate 'de.themoep', 'me.william278.husksync.libraries'
relocate 'net.byteflux', 'me.william278.husksync.libraries'
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
relocate 'redis.clients', 'me.william278.husksync.libraries'
relocate 'org.apache', 'me.william278.husksync.libraries'
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
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'
dependencies {
//noinspection GroovyAssignabilityCheck

View File

@@ -1,20 +1,20 @@
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.bungeecord.listener.BungeeEventListener;
import me.william278.husksync.bungeecord.listener.BungeeRedisListener;
import me.william278.husksync.bungeecord.util.BungeeLogger;
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.Logger;
import net.byteflux.libby.BungeeLibraryManager;
import net.byteflux.libby.Library;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
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 org.bstats.bungeecord.Metrics;
import java.io.IOException;
@@ -155,7 +155,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()

View File

@@ -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.PlayerData;
import me.william278.husksync.Server;
import me.william278.husksync.Settings;
import me.william278.husksync.bungeecord.config.ConfigLoader;
import me.william278.husksync.bungeecord.config.ConfigManager;
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.command.HuskSyncCommand;
import me.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.MessageManager;
import net.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;
@@ -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) {

View File

@@ -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;
@@ -60,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";

View File

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

View File

@@ -1,9 +1,9 @@
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;

View File

@@ -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;
@@ -63,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) {
@@ -114,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)
@@ -126,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
@@ -198,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());
@@ -221,7 +217,7 @@ public class BungeeRedisListener extends RedisListener {
} catch (IOException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API");
}
}
});
}
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
name: HuskSync
version: ${version}
main: me.william278.husksync.HuskSyncBungeeCord
main: net.william278.husksync.HuskSyncBungeeCord
author: William278
description: 'A modern, cross-server player data synchronization system'

View File

@@ -1,7 +1,11 @@
plugins {
id 'java'
}
dependencies {
compileOnly 'com.zaxxer:HikariCP:5.0.1'
}
shadowJar {
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
}

View File

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

View File

@@ -1,4 +1,4 @@
package me.william278.husksync;
package net.william278.husksync;
import java.util.UUID;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync;
package net.william278.husksync;
import java.util.ArrayList;
@@ -36,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;
@@ -67,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

View File

@@ -1,14 +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.RedisListener;
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;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.migrator;
package net.william278.husksync.migrator;
import java.io.Serializable;
import java.util.UUID;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.proxy.command;
package net.william278.husksync.proxy.command;
public interface HuskSyncCommand {

View File

@@ -1,11 +1,11 @@
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.*;
@@ -184,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");
@@ -204,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));
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package me.william278.husksync.redis;
package net.william278.husksync.redis;
import me.william278.husksync.Settings;
import net.william278.husksync.Settings;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

View File

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

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util;
package net.william278.husksync.util;
import java.util.logging.Level;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util;
package net.william278.husksync.util;
import java.util.HashMap;
@@ -18,7 +18,7 @@ public class MessageManager {
.append("[%plugin_description%](gray)\n")
.append("[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)\n")
.append("[• Contributors:](white) [HarvelsX](gray show_text=&7Code)\n")
.append("[• Translators:](white) [Namiu/うにたろう](gray show_text=&7Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es)\n")
.append("[• 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)");

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util;
package net.william278.husksync.util;
public interface ThrowSupplier<T> {
T get() throws Exception;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util;
package net.william278.husksync.util;
import java.io.BufferedReader;
import java.io.IOException;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util;
package net.william278.husksync.util;
import java.util.Arrays;

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ plugins {
dependencies {
implementation project(path: ':bukkit', configuration: 'shadow')
implementation project(path: ':api', configuration: 'shadow')
implementation project(path: ':bungeecord', configuration: 'shadow')
implementation project(path: ':velocity', configuration: 'shadow')
}
@@ -19,7 +20,7 @@ shadowJar {
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'me.william278'
groupId = 'net.william278'
artifactId = 'husksync-plugin'
version = "$rootProject.version"

View File

@@ -1,5 +1,5 @@
dependencies {
implementation project(path: ':common', configuration: 'shadow')
implementation project(path: ':common')
implementation 'com.zaxxer:HikariCP:5.0.1'
implementation 'org.bstats:bstats-velocity:3.0.0'
@@ -10,14 +10,11 @@ dependencies {
}
shadowJar {
relocate 'de.themoep', 'me.william278.husksync.libraries'
relocate 'net.byteflux', 'me.william278.husksync.libraries'
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats'
relocate 'redis.clients', 'me.william278.husksync.libraries'
relocate 'org.apache', 'me.william278.husksync.libraries'
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
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'
dependencies {
//noinspection GroovyAssignabilityCheck

View File

@@ -1,4 +1,4 @@
package me.william278.husksync;
package net.william278.husksync;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
@@ -10,16 +10,18 @@ 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;
@@ -202,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()

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -62,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) {
@@ -109,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
@@ -191,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]);
@@ -214,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();
}
}

View File

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

View File

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

View File

@@ -8,5 +8,5 @@
"William278"
],
"dependencies": [],
"main": "me.william278.husksync.HuskSyncVelocity"
"main": "net.william278.husksync.HuskSyncVelocity"
}