9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-23 16:49: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 # Ignore Gradle GUI config
gradle-app.setting gradle-app.setting
# me.william278.crossserversync.bungeecord.data.DataManager.PlayerDataCache of project # net.william278.crossserversync.bungeecord.data.DataManager.PlayerDataCache of project
.gradletasknamecache .gradletasknamecache
**/build/ **/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 LICENSE
This source code is provided as reference to licensed individuals that have purchased the HuskSync 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. parties that are utilised in the plugin.
CONTRIBUTOR AGREEMENT 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. to the copyright holder and only the copyright holder.
In exchange for contributing, the copyright holder may give, at their discretion, permission to use In exchange for contributing, the copyright holder may give, at their discretion, permission to use
the plugin in commercial contexts 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 #### 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.) 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. 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. 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 ## How it works
![Flow chart showing different processes of how the plugin works](images/flow-chart.png) ![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. 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
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. 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 | | Command | Description | Permission |
|---------------------|--------------------------------------|--------------------------------| |---------------------------------------|--------------------------------------|--------------------------------|
| `/husksync about` | View plugin information | _None_ | | `/husksync about` | View plugin information | _None_ |
| `/husksync update` | Check if an update is available | `husksync.command.admin` | | `/husksync update` | Check if an update is available | `husksync.command.admin` |
| `/husksync status` | View system status information | `husksync.command.admin` | | `/husksync status` | View system status information | `husksync.command.admin` |
| `/husksync reload` | Reload config & message files | `husksync.command.admin` | | `/husksync reload` | Reload config & message files | `husksync.command.admin` |
| `/husksync invsee` | View an offline player's inventory | `husksync.command.inventory` | | `/husksync invsee <player> [cluster]` | View an offline player's inventory | `husksync.command.inventory` |
| `/husksync echest` | View an offline player's ender chest | `husksync.command.ender_chest` | | `/husksync echest <player> [cluster]` | View an offline player's ender chest | `husksync.command.ender_chest` |
| `/husksync migrate` | Migrate data from MPDB | _Console-only_ | | `/husksync migrate [args] ` | Migrate data from MPDB | _Console-only_ |
### Frequently Asked Questions (FAQs) ### Frequently Asked Questions (FAQs)
#### Is Redis required? #### 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) 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 ```xml
<dependency> <dependency>
<groupId>com.github.WiIIiam278</groupId> <groupId>net.william278</groupId>
<artifactId>HuskSync</artifactId> <artifactId>HuskSync</artifactId>
<version>version</version> <version>version</version>
<scope>provided</scope> <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 { dependencies {
compileOnly 'com.github.WiIIiam278:HuskSync:version' compileOnly 'net.william278:HuskSync:version'
} }
``` ```
@@ -174,7 +179,7 @@ try {
``` ```
#### Getting ItemStacks and usable data from PlayerData #### Getting ItemStacks and usable data from PlayerData
Use the static methods provided in the [DataSerializer class](https://javadoc.jitpack.io/com/github/WiIIiam278/HuskSync/latest/javadoc/me/william278/husksync/bukkit/data/DataSerializer.html). For instance, to get a player's inventory as an `ItemStack[]` from a `PlayerData` object. Use the static methods provided in the [DataSerializer class](https://javadoc.jitpack.io/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 ```java
ItemStack[] inventoryItems = DataSerializer.serializeInventory(playerData.getSerializedInventory()); ItemStack[] inventoryItems = DataSerializer.serializeInventory(playerData.getSerializedInventory());
ItemStack[] enderChestItems = DataSerializer.serializeInventory(playerData.getSerializedEnderChest()); ItemStack[] enderChestItems = DataSerializer.serializeInventory(playerData.getSerializedEnderChest());
@@ -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. 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 ### Building
To build HuskSync you will first need to download MySqlPlayerDataBridge and `mvn install:install-file` the jar file to your local maven repository. You can build HuskSync yourself, though please read the license and buy yourself a copy as HuskSync is indeed a premium resource.
```
mvn install:install-file -Dfile=MysqlPlayerDataBridge-v4.0.1.jar -DgroupId=net.craftersland.data -DartifactId=bridge -Dversion=4.0.1 -Dpackaging=jar 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: 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 BungeeCord metrics](https://bstats.org/plugin/bungeecord/HuskSync%20-%20BungeeCord/13141)
* [View Velocity metrics](https://bstats.org/plugin/velocity/HuskSync%20-%20Velocity/13489) * [View Velocity metrics](https://bstats.org/plugin/velocity/HuskSync%20-%20Velocity/13489)
You can turn metric collection off by navigating to `plugins/bStats/config.yml` and editing the config to disable plugin metrics. You can turn metric collection off by navigating to `~/plugins/bStats/config.yml` and editing the config to disable plugin metrics.
## Support ## Support
* Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues) * Report bugs: [Click here](https://github.com/WiIIiam278/HuskSync/issues)

View File

@@ -1,49 +1,20 @@
//file:noinspection GroovyAssignabilityCheck
plugins {
id 'java-library'
id 'maven-publish'
}
dependencies { 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.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
compileOnly 'org.jetbrains:annotations:22.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
} }
repositories { shadowJar {
mavenCentral() relocate 'de.themoep', 'net.william278.husksync.libraries'
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 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 { java {
publishing { withSourcesJar()
publications { withJavadocJar()
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
} }

View File

@@ -1,16 +1,18 @@
package me.william278.husksync.bukkit.api; package net.william278.husksync.bukkit.api;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.bukkit.listener.BukkitRedisListener;
import net.william278.husksync.redis.RedisMessage;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; 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 { public class HuskSyncAPI {
@@ -20,7 +22,7 @@ public class HuskSyncAPI {
private static HuskSyncAPI instance; 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} * @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. * Returns a {@link CompletableFuture} that will fetch the {@link PlayerData} for a user given their {@link UUID},
*/ * which contains serialized synchronised data.
public static HashMap<UUID, CompletableFuture<PlayerData>> apiRequests = new HashMap<>(); * <p>
* This can then be deserialized into ItemStacks and other usable values using the {@code DataSerializer} class.
/** * <p>
* 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. * 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 * @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 * @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 * @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 { public CompletableFuture<PlayerData> getPlayerData(UUID playerUUID) throws IOException {
// Create the request to be completed // Create the request to be completed
final UUID requestUUID = UUID.randomUUID(); final UUID requestUUID = UUID.randomUUID();
apiRequests.put(requestUUID, new CompletableFuture<>()); BukkitRedisListener.apiRequests.put(requestUUID, new CompletableFuture<>());
// Remove the request from the map on completion // 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 // Request the data via the proxy
new RedisMessage(RedisMessage.MessageType.API_DATA_REQUEST, new RedisMessage(RedisMessage.MessageType.API_DATA_REQUEST,
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
playerUUID.toString(), requestUUID.toString()).send(); 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 * @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 * @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); final String serializedPlayerData = RedisMessage.serialize(playerData);
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE, new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), 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' id 'java'
} }
group 'me.william278' group 'net.william278'
version "$ext.plugin_version+${versionMetadata()}" version "$ext.plugin_version+${versionMetadata()}"
ext { ext {
@@ -19,6 +19,7 @@ allprojects {
compileJava.options.encoding = 'UTF-8' compileJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8' javadoc.options.encoding = 'UTF-8'
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
compileJava.options.release.set 16 compileJava.options.release.set 16
@@ -34,7 +35,7 @@ allprojects {
} }
dependencies { dependencies {
implementation('redis.clients:jedis:4.1.1') { implementation('redis.clients:jedis:4.2.3') {
//noinspection GroovyAssignabilityCheck //noinspection GroovyAssignabilityCheck
exclude module: 'slf4j-api' exclude module: 'slf4j-api'
} }
@@ -50,7 +51,7 @@ subprojects {
version rootProject.version version rootProject.version
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}" archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
if (['bukkit', 'bungeecord', 'velocity', 'plugin'].contains(project.name)) { if (['bukkit', 'api', 'bungeecord', 'velocity', 'plugin'].contains(project.name)) {
shadowJar { shadowJar {
destinationDirectory.set(file("$rootDir/target")) destinationDirectory.set(file("$rootDir/target"))
archiveClassifier.set('') archiveClassifier.set('')

View File

@@ -1,19 +1,18 @@
dependencies { dependencies {
implementation project(':api') implementation project(path: ':common')
implementation project(path: ':common', configuration: 'shadow')
implementation 'org.bstats:bstats-bukkit:3.0.0' implementation 'org.bstats:bstats-bukkit:3.0.0'
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' 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.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
compileOnly 'org.jetbrains:annotations:22.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
} }
shadowJar { shadowJar {
relocate 'de.themoep', 'me.william278.husksync.libraries' relocate 'de.themoep', 'net.william278.husksync.libraries'
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
relocate 'redis.clients', 'net.william278.husksync.libraries'
relocate 'redis.clients', 'me.william278.husksync.libraries' relocate 'org.apache', 'net.william278.husksync.libraries'
relocate 'org.apache', 'me.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 net.william278.husksync.Settings;
import me.william278.husksync.bukkit.util.PlayerSetter; import net.william278.husksync.bukkit.util.BukkitUpdateChecker;
import me.william278.husksync.bukkit.config.ConfigLoader; import net.william278.husksync.bukkit.util.PlayerSetter;
import me.william278.husksync.bukkit.data.BukkitDataCache; import net.william278.husksync.bukkit.config.ConfigLoader;
import me.william278.husksync.bukkit.listener.BukkitRedisListener; import net.william278.husksync.bukkit.data.BukkitDataCache;
import me.william278.husksync.bukkit.listener.BukkitEventListener; import net.william278.husksync.bukkit.listener.BukkitRedisListener;
import me.william278.husksync.bukkit.migrator.MPDBDeserializer; import net.william278.husksync.bukkit.listener.BukkitEventListener;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.bukkit.migrator.MPDBDeserializer;
import net.william278.husksync.redis.RedisMessage;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -143,7 +144,11 @@ public final class HuskSyncBukkit extends JavaPlugin {
if (HuskSyncBukkit.handshakeCompleted && !HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled && Bukkit.getOnlinePlayers().size() > 0) { if (HuskSyncBukkit.handshakeCompleted && !HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled && Bukkit.getOnlinePlayers().size() > 0) {
getLogger().info("Saving data for remaining online players..."); getLogger().info("Saving data for remaining online players...");
for (Player player : Bukkit.getOnlinePlayers()) { 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!"); 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; import org.bukkit.configuration.file.FileConfiguration;
public class ConfigLoader { public class ConfigLoader {
@@ -27,6 +27,8 @@ public class ConfigLoader {
Settings.syncFlight = config.getBoolean("synchronisation_settings.flight", false); Settings.syncFlight = config.getBoolean("synchronisation_settings.flight", false);
Settings.useNativeImplementation = config.getBoolean("native_advancement_synchronization", 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.HashMap;
import java.util.HashSet; 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.*;
import org.bukkit.advancement.Advancement; import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress; import org.bukkit.advancement.AdvancementProgress;
@@ -15,12 +15,10 @@ import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;
import java.util.*; 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 { public class DataSerializer {
@@ -194,12 +192,12 @@ public class DataSerializer {
return serializedPotionEffect != null ? new PotionEffect((Map<String, Object>) serializedPotionEffect) : null; 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()) { if (serializedLocationData.isEmpty()) {
return null; return null;
} }
try { try {
return (DataSerializer.PlayerLocation) RedisMessage.deserialize(serializedLocationData); return (me.william278.husksync.bukkit.data.DataSerializer.PlayerLocation) RedisMessage.deserialize(serializedLocationData);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", 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 { public static String getSerializedLocation(Player player) throws IOException {
final Location playerLocation = player.getLocation(); 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())); 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} * @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 * @throws IOException If the deserialization fails
*/ */
@SuppressWarnings("unchecked") // Ignore the unchecked cast here @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()) { if (serializedAdvancementData.isEmpty()) {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -227,15 +225,15 @@ public class DataSerializer {
List<?> deserialize = (List<?>) RedisMessage.deserialize(serializedAdvancementData); List<?> deserialize = (List<?>) RedisMessage.deserialize(serializedAdvancementData);
// Migrate old AdvancementRecord into date format // Migrate old AdvancementRecord into date format
if (!deserialize.isEmpty() && deserialize.get(0) instanceof AdvancementRecord) { if (!deserialize.isEmpty() && deserialize.get(0) instanceof me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecord) {
deserialize = ((List<AdvancementRecord>) deserialize).stream() deserialize = ((List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecord>) deserialize).stream()
.map(o -> new AdvancementRecordDate( .map(o -> new me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate(
o.advancementKey, o.advancementKey(),
o.awardedAdvancementCriteria o.awardedAdvancementCriteria()
)).toList(); )).toList();
} }
return (List<AdvancementRecordDate>) deserialize; return (List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate>) deserialize;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", 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 { public static String getSerializedAdvancements(Player player) throws IOException {
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator(); 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()) { while (serverAdvancements.hasNext()) {
final AdvancementProgress progress = player.getAdvancementProgress(serverAdvancements.next()); final AdvancementProgress progress = player.getAdvancementProgress(serverAdvancements.next());
@@ -259,25 +257,25 @@ public class DataSerializer {
final Map<String, Date> awardedCriteria = new HashMap<>(); final Map<String, Date> awardedCriteria = new HashMap<>();
progress.getAwardedCriteria().forEach(s -> awardedCriteria.put(s, progress.getDateAwarded(s))); 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); 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} * @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 * @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()) { 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 { try {
return (DataSerializer.StatisticData) RedisMessage.deserialize(serializedStatisticData); return (me.william278.husksync.bukkit.data.DataSerializer.StatisticData) RedisMessage.deserialize(serializedStatisticData);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", 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); 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 net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.bukkit.util.PlayerSetter; import net.william278.husksync.bukkit.util.PlayerSetter;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
@@ -58,7 +58,7 @@ public class DataViewer {
// Send a redis message with the updated data after the viewing // Send a redis message with the updated data after the viewing
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE, new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
RedisMessage.serialize(playerData)) RedisMessage.serialize(playerData), Boolean.toString(true))
.send(); .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.entity.Player;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent; 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.entity.Player;
import org.bukkit.event.Cancellable; import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList; 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 net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.bukkit.data.DataViewer; import net.william278.husksync.Settings;
import me.william278.husksync.bukkit.util.PlayerSetter; import net.william278.husksync.bukkit.data.DataViewer;
import net.william278.husksync.bukkit.util.PlayerSetter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; 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.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.*; import org.bukkit.event.player.*;
import org.bukkit.event.world.WorldSaveEvent;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
@@ -37,7 +39,14 @@ public class BukkitEventListener implements Listener {
return; // If the plugin has not been initialized correctly return; // If the plugin has not been initialized correctly
// Update the player's data // 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) @EventHandler(priority = EventPriority.LOWEST)
@@ -50,19 +59,22 @@ public class BukkitEventListener implements Listener {
// Mark the player as awaiting data fetch // Mark the player as awaiting data fetch
HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId()); HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId());
if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) {
return; // If the data handshake has not been completed yet (or MySqlPlayerDataBridge is installed) return; // If the data handshake has not been completed yet (or MySqlPlayerDataBridge is installed)
}
// Send a redis message requesting the player data (if they need to) // Send a redis message requesting the player data (if they need to)
if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) { if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) {
try { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
PlayerSetter.requestPlayerData(player.getUniqueId()); try {
} catch (IOException e) { PlayerSetter.requestPlayerData(player.getUniqueId());
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e); } catch (IOException e) {
} plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData fetch request", e);
}
});
} else { } else {
// If the player's data wasn't set after 10 ticks, ensure it will be // If the player's data wasn't set after the synchronization timeout retry delay ticks, ensure it will be
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> { Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
if (player.isOnline()) { if (player.isOnline()) {
try { try {
if (HuskSyncBukkit.bukkitCache.isAwaitingDataFetch(player.getUniqueId())) { 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); 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 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 de.themoep.minedown.MineDown;
import me.william278.husksync.HuskSyncBukkit; import net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.bukkit.api.HuskSyncAPI; import net.william278.husksync.bukkit.config.ConfigLoader;
import me.william278.husksync.bukkit.config.ConfigLoader; import net.william278.husksync.bukkit.data.DataViewer;
import me.william278.husksync.bukkit.data.DataViewer; import net.william278.husksync.bukkit.migrator.MPDBDeserializer;
import me.william278.husksync.bukkit.migrator.MPDBDeserializer; import net.william278.husksync.bukkit.util.PlayerSetter;
import me.william278.husksync.bukkit.util.PlayerSetter; import net.william278.husksync.migrator.MPDBPlayerData;
import me.william278.husksync.migrator.MPDBPlayerData; import net.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisListener; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.util.MessageManager;
import me.william278.husksync.util.MessageManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level; import java.util.logging.Level;
public class BukkitRedisListener extends RedisListener { public class BukkitRedisListener extends RedisListener {
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance(); private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
public static HashMap<UUID, CompletableFuture<PlayerData>> apiRequests = new HashMap<>();
// Initialize the listener on the bukkit server // Initialize the listener on the bukkit server
public BukkitRedisListener() { public BukkitRedisListener() {
super(); super();
@@ -111,10 +114,10 @@ public class BukkitRedisListener extends RedisListener {
} }
case API_DATA_RETURN -> { case API_DATA_RETURN -> {
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]);
if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) { if (apiRequests.containsKey(requestUUID)) {
try { try {
final PlayerData data = (PlayerData) RedisMessage.deserialize(message.getMessageDataElements()[1]); final PlayerData data = (PlayerData) RedisMessage.deserialize(message.getMessageDataElements()[1]);
HuskSyncAPI.apiRequests.get(requestUUID).complete(data); apiRequests.get(requestUUID).complete(data);
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
log(Level.SEVERE, "Failed to serialize returned API-requested player data"); log(Level.SEVERE, "Failed to serialize returned API-requested player data");
} }
@@ -124,8 +127,8 @@ public class BukkitRedisListener extends RedisListener {
case API_DATA_CANCEL -> { case API_DATA_CANCEL -> {
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[0]);
// Cancel requests if no data could be found on the proxy // Cancel requests if no data could be found on the proxy
if (HuskSyncAPI.apiRequests.containsKey(requestUUID)) { if (apiRequests.containsKey(requestUUID)) {
HuskSyncAPI.apiRequests.get(requestUUID).cancel(true); apiRequests.get(requestUUID).cancel(true);
} }
} }
case RELOAD_CONFIG -> { 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 net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.bukkit.util.PlayerSetter; import net.william278.husksync.bukkit.data.DataSerializer;
import me.william278.husksync.bukkit.data.DataSerializer; import net.william278.husksync.bukkit.util.PlayerSetter;
import me.william278.husksync.migrator.MPDBPlayerData; import net.william278.husksync.migrator.MPDBPlayerData;
import net.craftersland.data.bridge.PD; import net.william278.mpdbconverter.MPDBConverter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level; import java.util.logging.Level;
public class MPDBDeserializer { public class MPDBDeserializer {
@@ -19,10 +19,12 @@ public class MPDBDeserializer {
private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance(); private static final HuskSyncBukkit plugin = HuskSyncBukkit.getInstance();
// Instance of MySqlPlayerDataBridge // Instance of MySqlPlayerDataBridge
private static PD mySqlPlayerDataBridge; private static MPDBConverter mpdbConverter;
public static void setMySqlPlayerDataBridge() { 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 // Set inventory contents
Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER); Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER);
if (!mpdbPlayerData.inventoryData.isEmpty() && !mpdbPlayerData.inventoryData.equalsIgnoreCase("none")) { 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.) // Set armor (if there is data; MPDB stores empty data with literally the word "none". Obviously.)
int armorSlot = 36; int armorSlot = 36;
if (!mpdbPlayerData.armorData.isEmpty() && !mpdbPlayerData.armorData.equalsIgnoreCase("none")) { if (!mpdbPlayerData.armorData.isEmpty() && !mpdbPlayerData.armorData.equalsIgnoreCase("none")) {
ItemStack[] armorItems = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.armorData); ItemStack[] armorItems = mpdbConverter.getItemStackFromSerializedData(mpdbPlayerData.armorData);
for (ItemStack armorPiece : armorItems) { for (ItemStack armorPiece : armorItems) {
if (armorPiece != null) { if (armorPiece != null) {
inventory.setItem(armorSlot, armorPiece); inventory.setItem(armorSlot, armorPiece);
@@ -66,7 +68,7 @@ public class MPDBDeserializer {
// Set ender chest (again, if there is data) // Set ender chest (again, if there is data)
ItemStack[] enderChestData; ItemStack[] enderChestData;
if (!mpdbPlayerData.enderChestData.isEmpty() && !mpdbPlayerData.enderChestData.equalsIgnoreCase("none")) { if (!mpdbPlayerData.enderChestData.isEmpty() && !mpdbPlayerData.enderChestData.equalsIgnoreCase("none")) {
enderChestData = getItemStackArrayFromMPDBBase64String(mpdbPlayerData.enderChestData); enderChestData = mpdbConverter.getItemStackFromSerializedData(mpdbPlayerData.enderChestData);
} else { } else {
enderChestData = new ItemStack[0]; enderChestData = new ItemStack[0];
} }
@@ -82,19 +84,4 @@ public class MPDBDeserializer {
} }
return playerData; 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 net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.util.UpdateChecker; import net.william278.husksync.util.UpdateChecker;
import java.util.logging.Level; 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 net.william278.husksync.HuskSyncBukkit;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.bukkit.api.events.SyncCompleteEvent; import net.william278.husksync.bukkit.events.SyncCompleteEvent;
import me.william278.husksync.bukkit.api.events.SyncEvent; import net.william278.husksync.bukkit.events.SyncEvent;
import me.william278.husksync.bukkit.data.DataSerializer; import net.william278.husksync.bukkit.data.DataSerializer;
import me.william278.husksync.bukkit.util.nms.AdvancementUtils; import net.william278.husksync.bukkit.util.nms.AdvancementUtils;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.advancement.Advancement; import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress; import org.bukkit.advancement.AdvancementProgress;
@@ -95,22 +95,19 @@ public class PlayerSetter {
/** /**
* Update a {@link Player}'s data, sending it to the proxy * 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 // Send a redis message with the player's last updated PlayerData version UUID and their new PlayerData
try { try {
final String serializedPlayerData = getNewSerializedPlayerData(player); final String serializedPlayerData = getNewSerializedPlayerData(player);
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE, new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE,
new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster),
serializedPlayerData).send(); serializedPlayerData, Boolean.toString(bounceBack)).send();
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", 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 // Set the player's data from the PlayerData
try { try {
if (Settings.syncAdvancements) { if (Settings.syncAdvancements) {
List<DataSerializer.AdvancementRecordDate> advancementRecords List<me.william278.husksync.bukkit.data.DataSerializer.AdvancementRecordDate> advancementRecords
= DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements()); = DataSerializer.deserializeAdvancementData(data.getSerializedAdvancements());
if (Settings.useNativeImplementation) { 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); final Object playerAdvancements = AdvancementUtils.getPlayerAdvancements(player);
// Clear // Clear
AdvancementUtils.clearPlayerAdvancements(playerAdvancements); AdvancementUtils.clearPlayerAdvancements(playerAdvancements);
AdvancementUtils.clearVisibleAdvancements(playerAdvancements); AdvancementUtils.clearVisibleAdvancements(playerAdvancements);
advancementRecords.forEach(advancementRecord -> { advancementRecords.forEach(advancementRecord -> {
@@ -315,9 +312,9 @@ public class PlayerSetter {
* Update a player's advancements and progress to match the advancementData * Update a player's advancements and progress to match the advancementData
* *
* @param player The player to set the advancements of * @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 // Temporarily disable advancement announcing if needed
boolean announceAdvancementUpdate = false; boolean announceAdvancementUpdate = false;
if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) { if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) {
@@ -330,12 +327,12 @@ public class PlayerSetter {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Apply the advancements to the player // 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 while (serverAdvancements.hasNext()) { // Iterate through all advancements
boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update
Advancement advancement = serverAdvancements.next(); Advancement advancement = serverAdvancements.next();
AdvancementProgress playerProgress = player.getAdvancementProgress(advancement); 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 the advancement is one on the data
if (record.key().equals(advancement.getKey().getNamespace() + ":" + advancement.getKey().getKey())) { 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) * Set a player's statistics (in the Statistic menu)
* *
* @param player The player to set the statistics of * @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 // Set untyped statistics
for (Statistic statistic : statisticData.untypedStatisticValues().keySet()) { for (Statistic statistic : statisticData.untypedStatisticValues().keySet()) {
player.setStatistic(statistic, statisticData.untypedStatisticValues().get(statistic)); 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 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 // Don't teleport if the location is invalid
if (location == null) { if (location == null) {
return; return;
@@ -463,12 +460,13 @@ public class PlayerSetter {
*/ */
private static void setPlayerHealth(Player player, double health, double maxHealth, double healthScale) { private static void setPlayerHealth(Player player, double health, double maxHealth, double healthScale) {
// Set max health // Set max health
if (maxHealth != 0.0D) { if (maxHealth != 0D) {
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).setBaseValue(maxHealth); Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).setBaseValue(maxHealth);
} }
// Set health // 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 // Set health scaling if needed
if (healthScale != 0D) { 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.advancement.Advancement;
import org.bukkit.entity.Player; 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 org.bukkit.entity.LivingEntity;
import java.lang.reflect.InvocationTargetException; 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 net.william278.husksync.util.ThrowSupplier;
import me.william278.husksync.util.VersionUtils; import net.william278.husksync.util.VersionUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
public class MinecraftVersionUtils { public class MinecraftVersionUtils {

View File

@@ -17,4 +17,6 @@ synchronisation_settings:
flight: false flight: false
cluster_id: 'main' cluster_id: 'main'
check_for_updates: true 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 name: HuskSync
version: ${version} version: ${version}
main: me.william278.husksync.HuskSyncBukkit main: net.william278.husksync.HuskSyncBukkit
api-version: 1.16 api-version: 1.16
author: William278 author: William278
description: 'A modern, cross-server player data synchronization system' description: 'A modern, cross-server player data synchronization system'

View File

@@ -1,5 +1,5 @@
dependencies { dependencies {
implementation project(path: ':common', configuration: 'shadow') implementation project(path: ':common')
implementation 'com.zaxxer:HikariCP:5.0.1' implementation 'com.zaxxer:HikariCP:5.0.1'
implementation 'org.bstats:bstats-bungeecord:3.0.0' implementation 'org.bstats:bstats-bungeecord:3.0.0'
@@ -10,14 +10,11 @@ dependencies {
} }
shadowJar { shadowJar {
relocate 'de.themoep', 'me.william278.husksync.libraries' relocate 'de.themoep', 'net.william278.husksync.libraries'
relocate 'net.byteflux', 'me.william278.husksync.libraries' relocate 'net.byteflux', 'net.william278.husksync.libraries'
relocate 'org.bstats', 'me.william278.husksync.libraries.bstats' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
relocate 'redis.clients', 'net.william278.husksync.libraries'
relocate 'redis.clients', 'me.william278.husksync.libraries' relocate 'org.apache', 'net.william278.husksync.libraries'
relocate 'org.apache', 'me.william278.husksync.libraries'
relocate 'com.zaxxer', 'me.william278.husksync.libraries'
dependencies { dependencies {
//noinspection GroovyAssignabilityCheck //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.BungeeLibraryManager;
import net.byteflux.libby.Library; import net.byteflux.libby.Library;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; 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 org.bstats.bungeecord.Metrics;
import java.io.IOException; import java.io.IOException;
@@ -155,7 +155,7 @@ public final class HuskSyncBungeeCord extends Plugin {
Library mySqlLib = Library.builder() Library mySqlLib = Library.builder()
.groupId("mysql") .groupId("mysql")
.artifactId("mysql-connector-java") .artifactId("mysql-connector-java")
.version("8.0.27") .version("8.0.29")
.build(); .build();
Library sqLiteLib = Library.builder() 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 de.themoep.minedown.MineDown;
import me.william278.husksync.HuskSyncBungeeCord; import net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Server; import net.william278.husksync.Server;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.bungeecord.config.ConfigLoader; import net.william278.husksync.bungeecord.config.ConfigLoader;
import me.william278.husksync.bungeecord.config.ConfigManager; import net.william278.husksync.bungeecord.config.ConfigManager;
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import net.william278.husksync.bungeecord.util.BungeeUpdateChecker;
import me.william278.husksync.migrator.MPDBMigrator; import net.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.command.HuskSyncCommand; import net.william278.husksync.proxy.command.HuskSyncCommand;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -317,7 +317,7 @@ public class BungeeCommand extends Command implements TabExecutor, HuskSyncComma
// View the inventory of a player specified by their name // View the inventory of a player specified by their name
private void openInventory(ProxiedPlayer viewer, String targetPlayerName, String clusterId) { private void openInventory(ProxiedPlayer viewer, String targetPlayerName, String clusterId) {
if (viewer.getName().equalsIgnoreCase(targetPlayerName)) { 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; return;
} }
if (ProxyServer.getInstance().getPlayer(targetPlayerName) != null) { 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 net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import java.util.HashMap; 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.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.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000);
Settings.bounceBackSynchronisation = config.getBoolean("bounce_back_synchronization", true);
// Read cluster data // Read cluster data
Configuration section = config.getSection("clusters"); Configuration section = config.getSection("clusters");
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; 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 net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration; 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 net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent; 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 de.themoep.minedown.MineDown;
import me.william278.husksync.HuskSyncBungeeCord; import net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.Server; import net.william278.husksync.Server;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.migrator.MPDBMigrator; import net.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisListener; import net.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -63,40 +63,37 @@ public class BungeeRedisListener extends RedisListener {
} }
switch (message.getMessageType()) { switch (message.getMessageType()) {
case PLAYER_DATA_REQUEST -> { case PLAYER_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
// Get the UUID of the requesting player // Get the UUID of the requesting player
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData()); final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { try {
try { // Send the reply, serializing the message data
// Send the reply, serializing the message data new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) .send();
.send();
// Send an update to all bukkit servers removing the player from the requester cache // Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send(); .send();
// Send synchronisation complete message // Send synchronisation complete message
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID); ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
if (player != null) { if (player != null) {
if (player.isConnected()) { player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
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();
} }
}); } catch (IOException e) {
} log(Level.SEVERE, "Failed to serialize data when replying to a data request");
case PLAYER_DATA_UPDATE -> { e.printStackTrace();
}
});
case PLAYER_DATA_UPDATE -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
// Deserialize the PlayerData received // Deserialize the PlayerData received
PlayerData playerData; PlayerData playerData;
final String serializedPlayerData = message.getMessageData(); final String serializedPlayerData = message.getMessageDataElements()[0];
final boolean bounceBack = Boolean.parseBoolean(message.getMessageDataElements()[1]);
try { try {
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData); playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
} catch (IOException | ClassNotFoundException e) { } 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) // Reply with the player data if they are still online (switching server)
try { if (Settings.bounceBackSynchronisation && bounceBack) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID()); try {
if (player != null) { ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID());
if (player.isConnected()) { if (player != null) {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
serializedPlayerData) serializedPlayerData)
@@ -126,12 +123,12 @@ public class BungeeRedisListener extends RedisListener {
// Send synchronisation complete message // Send synchronisation complete message
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); 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 -> { case CONNECTION_HANDSHAKE -> {
// Reply to a Bukkit server's connection handshake to complete the process // Reply to a Bukkit server's connection handshake to complete the process
if (HuskSyncBungeeCord.isDisabling) return; // Return if the Proxy is disabling if (HuskSyncBungeeCord.isDisabling) return; // Return if the Proxy is disabling
@@ -198,10 +195,9 @@ public class BungeeRedisListener extends RedisListener {
HuskSyncBungeeCord.dataManager)); 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 playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
try { try {
final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId()); final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId());
@@ -221,7 +217,7 @@ public class BungeeRedisListener extends RedisListener {
} catch (IOException e) { } catch (IOException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); 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; 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 net.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.util.UpdateChecker; import net.william278.husksync.util.UpdateChecker;
import java.util.logging.Level; import java.util.logging.Level;

View File

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

View File

@@ -1,7 +1,11 @@
plugins {
id 'java'
}
dependencies { dependencies {
compileOnly 'com.zaxxer:HikariCP:5.0.1' compileOnly 'com.zaxxer:HikariCP:5.0.1'
} }
shadowJar { 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.io.*;
import java.time.Instant;
import java.util.UUID; import java.util.UUID;
/** /**
@@ -18,6 +19,11 @@ public class PlayerData implements Serializable {
*/ */
private final UUID dataVersionUUID; 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 * 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 serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode,
String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) { String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) {
this.dataVersionUUID = UUID.randomUUID(); this.dataVersionUUID = UUID.randomUUID();
this.timestamp = Instant.now().getEpochSecond();
this.playerUUID = playerUUID; this.playerUUID = playerUUID;
this.serializedInventory = serializedInventory; this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest; this.serializedEnderChest = serializedEnderChest;
@@ -109,16 +116,17 @@ public class PlayerData implements Serializable {
* @param totalExperience Their total experience points ("Score") * @param totalExperience Their total experience points ("Score")
* @param expLevel Their exp level * @param expLevel Their exp level
* @param expProgress Their exp progress to the next 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) * @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, double health, double maxHealth, double healthScale, int hunger, float saturation, float saturationExhaustion,
int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress,
String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements, String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements,
String serializedLocation) { String serializedLocation) {
this.playerUUID = playerUUID; this.playerUUID = playerUUID;
this.dataVersionUUID = dataVersionUUID; this.dataVersionUUID = dataVersionUUID;
this.timestamp = timestamp;
this.serializedInventory = serializedInventory; this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest; this.serializedEnderChest = serializedEnderChest;
this.health = health; this.health = health;
@@ -172,6 +180,15 @@ public class PlayerData implements Serializable {
return dataVersionUUID; 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 * Returns the serialized player {@code ItemStack[]} inventory
* *
@@ -341,6 +358,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedInventory(String serializedInventory) { public void setSerializedInventory(String serializedInventory) {
this.serializedInventory = serializedInventory; this.serializedInventory = serializedInventory;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -350,6 +368,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedEnderChest(String serializedEnderChest) { public void setSerializedEnderChest(String serializedEnderChest) {
this.serializedEnderChest = serializedEnderChest; this.serializedEnderChest = serializedEnderChest;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -359,6 +378,7 @@ public class PlayerData implements Serializable {
*/ */
public void setHealth(double health) { public void setHealth(double health) {
this.health = health; this.health = health;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -368,6 +388,7 @@ public class PlayerData implements Serializable {
*/ */
public void setMaxHealth(double maxHealth) { public void setMaxHealth(double maxHealth) {
this.maxHealth = maxHealth; this.maxHealth = maxHealth;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -377,6 +398,7 @@ public class PlayerData implements Serializable {
*/ */
public void setHealthScale(double healthScale) { public void setHealthScale(double healthScale) {
this.healthScale = healthScale; this.healthScale = healthScale;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -386,6 +408,7 @@ public class PlayerData implements Serializable {
*/ */
public void setHunger(int hunger) { public void setHunger(int hunger) {
this.hunger = hunger; this.hunger = hunger;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -395,6 +418,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSaturation(float saturation) { public void setSaturation(float saturation) {
this.saturation = saturation; this.saturation = saturation;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -404,6 +428,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSaturationExhaustion(float saturationExhaustion) { public void setSaturationExhaustion(float saturationExhaustion) {
this.saturationExhaustion = saturationExhaustion; this.saturationExhaustion = saturationExhaustion;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -413,6 +438,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSelectedSlot(int selectedSlot) { public void setSelectedSlot(int selectedSlot) {
this.selectedSlot = selectedSlot; this.selectedSlot = selectedSlot;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -422,6 +448,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedEffectData(String serializedEffectData) { public void setSerializedEffectData(String serializedEffectData) {
this.serializedEffectData = serializedEffectData; this.serializedEffectData = serializedEffectData;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -431,6 +458,7 @@ public class PlayerData implements Serializable {
*/ */
public void setTotalExperience(int totalExperience) { public void setTotalExperience(int totalExperience) {
this.totalExperience = totalExperience; this.totalExperience = totalExperience;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -440,6 +468,7 @@ public class PlayerData implements Serializable {
*/ */
public void setExpLevel(int expLevel) { public void setExpLevel(int expLevel) {
this.expLevel = expLevel; this.expLevel = expLevel;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -449,6 +478,7 @@ public class PlayerData implements Serializable {
*/ */
public void setExpProgress(float expProgress) { public void setExpProgress(float expProgress) {
this.expProgress = expProgress; this.expProgress = expProgress;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -458,6 +488,7 @@ public class PlayerData implements Serializable {
*/ */
public void setGameMode(String gameMode) { public void setGameMode(String gameMode) {
this.gameMode = gameMode; this.gameMode = gameMode;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -467,6 +498,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedStatistics(String serializedStatistics) { public void setSerializedStatistics(String serializedStatistics) {
this.serializedStatistics = serializedStatistics; this.serializedStatistics = serializedStatistics;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -476,6 +508,7 @@ public class PlayerData implements Serializable {
*/ */
public void setFlying(boolean flying) { public void setFlying(boolean flying) {
isFlying = flying; isFlying = flying;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -485,6 +518,7 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedAdvancements(String serializedAdvancements) { public void setSerializedAdvancements(String serializedAdvancements) {
this.serializedAdvancements = serializedAdvancements; this.serializedAdvancements = serializedAdvancements;
this.timestamp = Instant.now().getEpochSecond();
} }
/** /**
@@ -494,5 +528,6 @@ public class PlayerData implements Serializable {
*/ */
public void setSerializedLocation(String serializedLocation) { public void setSerializedLocation(String serializedLocation) {
this.serializedLocation = 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; import java.util.UUID;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync; package net.william278.husksync;
import java.util.ArrayList; import java.util.ArrayList;
@@ -36,6 +36,9 @@ public class Settings {
// SQL settings // SQL settings
public static DataStorageType dataStorageType; public static DataStorageType dataStorageType;
// Bounce-back synchronisation (default)
public static boolean bounceBackSynchronisation;
// MySQL specific settings // MySQL specific settings
public static String mySQLHost; public static String mySQLHost;
public static String mySQLDatabase; public static String mySQLDatabase;
@@ -67,8 +70,8 @@ public class Settings {
public static boolean syncAdvancements; public static boolean syncAdvancements;
public static boolean syncLocation; public static boolean syncLocation;
public static boolean syncFlight; public static boolean syncFlight;
public static long synchronizationTimeoutRetryDelay;
// Future public static boolean saveOnWorldSave;
public static boolean useNativeImplementation; public static boolean useNativeImplementation;
// This Cluster ID // 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 net.william278.husksync.PlayerData;
import me.william278.husksync.Server; import net.william278.husksync.Server;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.proxy.data.DataManager; import net.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.proxy.data.sql.Database; import net.william278.husksync.proxy.data.sql.Database;
import me.william278.husksync.proxy.data.sql.MySQL; import net.william278.husksync.proxy.data.sql.MySQL;
import me.william278.husksync.redis.RedisListener; import net.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection; 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.io.Serializable;
import java.util.UUID; 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 { 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 net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.proxy.data.sql.Database; import net.william278.husksync.proxy.data.sql.Database;
import me.william278.husksync.proxy.data.sql.MySQL; import net.william278.husksync.proxy.data.sql.MySQL;
import me.william278.husksync.proxy.data.sql.SQLite; import net.william278.husksync.proxy.data.sql.SQLite;
import me.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import java.io.File; import java.io.File;
import java.sql.*; import java.sql.*;
@@ -184,7 +184,7 @@ public class DataManager {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) { if (resultSet.next()) {
final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid")); 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 serializedInventory = resultSet.getString("inventory");
final String serializedEnderChest = resultSet.getString("ender_chest"); final String serializedEnderChest = resultSet.getString("ender_chest");
final double health = resultSet.getDouble("health"); final double health = resultSet.getDouble("health");
@@ -204,10 +204,10 @@ public class DataManager {
final String serializedLocationData = resultSet.getString("location"); final String serializedLocationData = resultSet.getString("location");
final String serializedStatisticData = resultSet.getString("statistics"); final String serializedStatisticData = resultSet.getString("statistics");
data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, dataSaveTimestamp.toInstant().getEpochSecond(),
health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects, serializedInventory, serializedEnderChest, health, maxHealth, healthScale, hunger, saturation,
totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying, saturationExhaustion, selectedSlot, serializedStatusEffects, totalExperience, expLevel, expProgress,
serializedAdvancementData, serializedLocationData)); gameMode, serializedStatisticData, isFlying, serializedAdvancementData, serializedLocationData));
} else { } else {
data.put(cluster, PlayerData.DEFAULT_PLAYER_DATA(playerUUID)); 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 net.william278.husksync.Settings;
import me.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; 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 com.zaxxer.hikari.HikariDataSource;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; 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 com.zaxxer.hikari.HikariDataSource;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import java.io.File; import java.io.File;
import java.io.IOException; 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.*;
import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException; 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 net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import java.io.*; import java.io.*;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util; package net.william278.husksync.util;
import java.util.logging.Level; 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; import java.util.HashMap;
@@ -18,7 +18,7 @@ public class MessageManager {
.append("[%plugin_description%](gray)\n") .append("[%plugin_description%](gray)\n")
.append("[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)\n") .append("[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)\n")
.append("[• Contributors:](white) [HarvelsX](gray show_text=&7Code)\n") .append("[• Contributors:](white) [HarvelsX](gray show_text=&7Code)\n")
.append("[• Translators:](white) [Namiu/うにたろう](gray show_text=&7Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es)\n") .append("[• 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("[• Plugin Info:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/)\n")
.append("[• Report Issues:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)\n") .append("[• Report Issues:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)\n")
.append("[• Support Discord:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)"); .append("[• Support Discord:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)");

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util; package net.william278.husksync.util;
public interface ThrowSupplier<T> { public interface ThrowSupplier<T> {
T get() throws Exception; 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.BufferedReader;
import java.io.IOException; import java.io.IOException;

View File

@@ -1,4 +1,4 @@
package me.william278.husksync.util; package net.william278.husksync.util;
import java.util.Arrays; 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 maximum_lifetime: 1800000
keepalive_time: 0 keepalive_time: 0
connection_timeout: 5000 connection_timeout: 5000
bounce_back_synchronization: true
clusters: clusters:
main: main:
player_table: 'husksync_players' player_table: 'husksync_players'

View File

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

View File

@@ -6,7 +6,8 @@ before_install:
- 'chmod +x gradlew' - 'chmod +x gradlew'
- 'chmod +x ./.jitpack/ensure-java-16' - 'chmod +x ./.jitpack/ensure-java-16'
- 'bash ./.jitpack/ensure-java-16 install' - 'bash ./.jitpack/ensure-java-16 install'
- 'bash ./.jitpack/install-mpdb-converter'
install: install:
- 'if ! ./.jitpack/ensure-java-16 use; then source ~/.sdkman/bin/sdkman-init.sh; fi' - 'if ! ./.jitpack/ensure-java-16 use; then source ~/.sdkman/bin/sdkman-init.sh; fi'
- 'java -version' - 'java -version'
- './gradlew api:clean api:publishToMavenLocal' - './gradlew clean publishToMavenLocal'

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package me.william278.husksync; package net.william278.husksync;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager; 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.PluginContainer;
import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import me.william278.husksync.migrator.MPDBMigrator; import net.william278.husksync.Server;
import me.william278.husksync.proxy.data.DataManager; import net.william278.husksync.Settings;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.velocity.command.VelocityCommand; import net.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.velocity.config.ConfigLoader; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.velocity.config.ConfigManager; import net.william278.husksync.velocity.command.VelocityCommand;
import me.william278.husksync.velocity.listener.VelocityEventListener; import net.william278.husksync.velocity.config.ConfigLoader;
import me.william278.husksync.velocity.listener.VelocityRedisListener; import net.william278.husksync.velocity.config.ConfigManager;
import me.william278.husksync.velocity.util.VelocityLogger; import net.william278.husksync.velocity.listener.VelocityEventListener;
import me.william278.husksync.velocity.util.VelocityUpdateChecker; 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.Library;
import net.byteflux.libby.VelocityLibraryManager; import net.byteflux.libby.VelocityLibraryManager;
import org.bstats.velocity.Metrics; import org.bstats.velocity.Metrics;
@@ -202,7 +204,7 @@ public class HuskSyncVelocity {
Library mySqlLib = Library.builder() Library mySqlLib = Library.builder()
.groupId("mysql") .groupId("mysql")
.artifactId("mysql-connector-java") .artifactId("mysql-connector-java")
.version("8.0.27") .version("8.0.29")
.build(); .build();
Library sqLiteLib = Library.builder() 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.CommandSource;
import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import de.themoep.minedown.adventure.MineDown; import de.themoep.minedown.adventure.MineDown;
import me.william278.husksync.HuskSyncVelocity; import net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Server; import net.william278.husksync.Server;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.migrator.MPDBMigrator; import net.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.command.HuskSyncCommand; import net.william278.husksync.proxy.command.HuskSyncCommand;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import me.william278.husksync.velocity.util.VelocityUpdateChecker; import net.william278.husksync.velocity.util.VelocityUpdateChecker;
import me.william278.husksync.velocity.config.ConfigLoader; import net.william278.husksync.velocity.config.ConfigLoader;
import me.william278.husksync.velocity.config.ConfigManager; import net.william278.husksync.velocity.config.ConfigManager;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -310,7 +310,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand {
// View the inventory of a player specified by their name // View the inventory of a player specified by their name
private void openInventory(Player viewer, String targetPlayerName, String clusterId) { private void openInventory(Player viewer, String targetPlayerName, String clusterId) {
if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) { 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; return;
} }
if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) { 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 net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import java.util.HashMap; 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.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.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout");
Settings.bounceBackSynchronisation = getConfigBoolean(config, true,"bounce_back_synchronization");
// Read cluster data // Read cluster data
ConfigurationNode clusterSection = config.getNode("clusters"); ConfigurationNode clusterSection = config.getNode("clusters");
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; 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 net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import org.yaml.snakeyaml.DumperOptions; 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.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import me.william278.husksync.HuskSyncVelocity; import net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@@ -16,7 +17,7 @@ public class VelocityEventListener {
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
@Subscribe @Subscribe(order = PostOrder.FIRST)
public void onPostLogin(PostLoginEvent event) { public void onPostLogin(PostLoginEvent event) {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { 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 com.velocitypowered.api.proxy.Player;
import de.themoep.minedown.adventure.MineDown; import de.themoep.minedown.adventure.MineDown;
import me.william278.husksync.HuskSyncVelocity; import net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.PlayerData; import net.william278.husksync.PlayerData;
import me.william278.husksync.Server; import net.william278.husksync.Server;
import me.william278.husksync.Settings; import net.william278.husksync.Settings;
import me.william278.husksync.migrator.MPDBMigrator; import net.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisListener; import net.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisMessage; import net.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.MessageManager; import net.william278.husksync.util.MessageManager;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@@ -62,36 +62,35 @@ public class VelocityRedisListener extends RedisListener {
} }
switch (message.getMessageType()) { switch (message.getMessageType()) {
case PLAYER_DATA_REQUEST -> { case PLAYER_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
// Get the UUID of the requesting player // Get the UUID of the requesting player
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData()); final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { try {
try { // Send the reply, serializing the message data
// Send the reply, serializing the message data new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) .send();
.send();
// Send an update to all bukkit servers removing the player from the requester cache // Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send(); .send();
// Send synchronisation complete message // Send synchronisation complete message
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID); Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent())); player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
} catch (IOException e) { } catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request"); log(Level.SEVERE, "Failed to serialize data when replying to a data request");
e.printStackTrace(); e.printStackTrace();
} }
}).schedule(); }).schedule();
} case PLAYER_DATA_UPDATE -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
case PLAYER_DATA_UPDATE -> {
// Deserialize the PlayerData received // Deserialize the PlayerData received
PlayerData playerData; PlayerData playerData;
final String serializedPlayerData = message.getMessageData(); final String serializedPlayerData = message.getMessageDataElements()[0];
final boolean bounceBack = Boolean.parseBoolean(message.getMessageDataElements()[1]);
try { try {
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData); playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
} catch (IOException | ClassNotFoundException e) { } 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) // Reply with the player data if they are still online (switching server)
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID()); if (Settings.bounceBackSynchronisation && bounceBack) {
updatingPlayer.ifPresent(player -> { Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
try { updatingPlayer.ifPresent(player -> {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, try {
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
RedisMessage.serialize(playerData)) new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
.send(); RedisMessage.serialize(playerData))
.send();
// Send synchronisation complete message // Send synchronisation complete message
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
} catch (IOException e) { } catch (IOException e) {
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
e.printStackTrace(); e.printStackTrace();
} }
}); });
}
} }).schedule();
case CONNECTION_HANDSHAKE -> { case CONNECTION_HANDSHAKE -> {
// Reply to a Bukkit server's connection handshake to complete the process // Reply to a Bukkit server's connection handshake to complete the process
if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling 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); 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 playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]); final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
@@ -214,7 +214,7 @@ public class VelocityRedisListener extends RedisListener {
} catch (IOException e) { } catch (IOException e) {
plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API"); 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; 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 net.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.util.UpdateChecker; import net.william278.husksync.util.UpdateChecker;
import java.util.logging.Level; import java.util.logging.Level;

View File

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