mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-21 15:49:20 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e98bac844a | ||
|
|
d6d9a55f72 | ||
|
|
e3070a65ab | ||
|
|
a8b4696604 | ||
|
|
7f5ca6206b | ||
|
|
ad885a9a15 | ||
|
|
fe89e7b770 | ||
|
|
17ea62ed0b | ||
|
|
94717637ba | ||
|
|
f6663f0c09 | ||
|
|
33588c2345 | ||
|
|
c2c5a424fb | ||
|
|
ce41053e87 | ||
|
|
5817de83e5 | ||
|
|
30dd48ce88 | ||
|
|
cf7912a89e | ||
|
|
9900b44858 | ||
|
|
9019181208 | ||
|
|
99483387f1 | ||
|
|
42177f2582 | ||
|
|
e4e0743205 | ||
|
|
105927a57f | ||
|
|
71706bf9ae | ||
|
|
101e0c11d7 | ||
|
|
70323fb2e2 | ||
|
|
9dc5577175 | ||
|
|
117d5edea2 | ||
|
|
3f0f518037 | ||
|
|
2017ecc20f | ||
|
|
ded89ad343 | ||
|
|
c4b194f8d6 | ||
|
|
d682e6e6c6 | ||
|
|
6fef9c4eae | ||
|
|
16eee05065 | ||
|
|
b664e2586d | ||
|
|
d594c9c257 | ||
|
|
532a65eca8 | ||
|
|
5af8ae0da5 | ||
|
|
c0709f82bd | ||
|
|
945b65e1bc | ||
|
|
efcb36d345 | ||
|
|
30cd89c578 | ||
|
|
bb3753b8e4 | ||
|
|
d5569ad3ed | ||
|
|
d8386fd2a2 | ||
|
|
3bfea58f35 | ||
|
|
51cf7beeb8 | ||
|
|
df247b41f4 | ||
|
|
bac760165e | ||
|
|
dd39482ed1 | ||
|
|
c05f165278 | ||
|
|
c888759d33 | ||
|
|
089ea5b63a | ||
|
|
9020e9d906 | ||
|
|
7584ea0070 | ||
|
|
9c243c2893 | ||
|
|
8ba90fadc4 |
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright © William278 2022. All rights reserved
|
Copyright © William278 2023. 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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# [](https://github.com/WiIIiam278/HuskSync)
|
# [](https://github.com/WiIIiam278/HuskSync)
|
||||||
[](https://github.com/WiIIiam278/HuskSync/actions/workflows/java_ci.yml)
|
[](https://github.com/WiIIiam278/HuskSync/actions/workflows/java_ci.yml)
|
||||||
[](https://jitpack.io/#net.william278/HuskSync)
|
[](https://jitpack.io/#net.william278/HuskSync)
|
||||||
[](https://discord.gg/tVYhJfyDWG)
|
[](https://discord.gg/tVYhJfyDWG)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
## Requirements
|
## Requirements
|
||||||
* A MySQL Database (v8.0+).
|
* A MySQL Database (v8.0+).
|
||||||
* A Redis Database (v5.0+)
|
* A Redis Database (v5.0+)
|
||||||
* Any number of proxied Spigot servers (Minecraft v1.16.5+)
|
* Any number of proxied Spigot servers (Minecraft v1.16.5+, Java 16+)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
1. Place the plugin jar file in the `/plugins/` directory of each Spigot server. You do not need to install HuskSync as a proxy plugin.
|
1. Place the plugin jar file in the `/plugins/` directory of each Spigot server. You do not need to install HuskSync as a proxy plugin.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ dependencies {
|
|||||||
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
||||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||||
implementation 'net.william278:hsldataconverter:1.0'
|
implementation 'net.william278:hsldataconverter:1.0'
|
||||||
|
implementation 'net.william278:MapDataAPI:1.0.2'
|
||||||
|
implementation 'net.william278:AndJam:1.0.2'
|
||||||
implementation 'me.lucko:commodore:2.2'
|
implementation 'me.lucko:commodore:2.2'
|
||||||
implementation 'net.kyori:adventure-platform-bukkit:4.1.2'
|
implementation 'net.kyori:adventure-platform-bukkit:4.1.2'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.3'
|
implementation 'dev.triumphteam:triumph-gui:3.1.3'
|
||||||
@@ -12,8 +14,10 @@ dependencies {
|
|||||||
compileOnly 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
compileOnly 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||||
compileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
compileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||||
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
||||||
|
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||||
compileOnly 'net.william278:DesertWell:1.1'
|
compileOnly 'net.william278:DesertWell:1.1'
|
||||||
compileOnly 'net.william278:Annotaml:2.0'
|
compileOnly 'net.william278:Annotaml:2.0'
|
||||||
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -31,6 +35,10 @@ shadowJar {
|
|||||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
|
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
||||||
|
relocate 'net.querz', 'net.william278.husksync.libraries.nbt'
|
||||||
|
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
||||||
|
|
||||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ import net.william278.husksync.migrator.MpdbMigrator;
|
|||||||
import net.william278.husksync.player.BukkitPlayer;
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
import net.william278.husksync.util.BukkitLogger;
|
|
||||||
import net.william278.husksync.util.BukkitResourceReader;
|
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import org.bstats.bukkit.Metrics;
|
import org.bstats.bukkit.Metrics;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.PluginCommand;
|
import org.bukkit.command.PluginCommand;
|
||||||
@@ -54,8 +50,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
private static final int METRICS_ID = 13140;
|
private static final int METRICS_ID = 13140;
|
||||||
private Database database;
|
private Database database;
|
||||||
private RedisManager redisManager;
|
private RedisManager redisManager;
|
||||||
private Logger logger;
|
|
||||||
private ResourceReader resourceReader;
|
|
||||||
private EventListener eventListener;
|
private EventListener eventListener;
|
||||||
private DataAdapter dataAdapter;
|
private DataAdapter dataAdapter;
|
||||||
private EventCannon eventCannon;
|
private EventCannon eventCannon;
|
||||||
@@ -85,19 +79,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
// Initialize HuskSync
|
// Initialize HuskSync
|
||||||
final AtomicBoolean initialized = new AtomicBoolean(true);
|
final AtomicBoolean initialized = new AtomicBoolean(true);
|
||||||
try {
|
try {
|
||||||
// Set the logging adapter and resource reader
|
|
||||||
this.logger = new BukkitLogger(this.getLogger());
|
|
||||||
this.resourceReader = new BukkitResourceReader(this);
|
|
||||||
|
|
||||||
// Create adventure audience
|
// Create adventure audience
|
||||||
this.audiences = BukkitAudiences.create(this);
|
this.audiences = BukkitAudiences.create(this);
|
||||||
|
|
||||||
// Load settings and locales
|
// Load settings and locales
|
||||||
getLoggingAdapter().log(Level.INFO, "Loading plugin configuration settings & locales...");
|
log(Level.INFO, "Loading plugin configuration settings & locales...");
|
||||||
initialized.set(reload().join());
|
initialized.set(reload().join());
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
logger.showDebugLogs(settings.debugLogging);
|
log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
|
||||||
} else {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
|
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
|
||||||
}
|
}
|
||||||
@@ -121,11 +110,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare database connection
|
// Prepare database connection
|
||||||
this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter, eventCannon);
|
this.database = new MySqlDatabase(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database...");
|
log(Level.INFO, "Attempting to establish connection to the database...");
|
||||||
initialized.set(this.database.initialize());
|
initialized.set(this.database.initialize());
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the database");
|
log(Level.INFO, "Successfully established a connection to the database");
|
||||||
} else {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to establish a connection to the database. " +
|
throw new HuskSyncInitializationException("Failed to establish a connection to the database. " +
|
||||||
"Please check the supplied database credentials in the config file");
|
"Please check the supplied database credentials in the config file");
|
||||||
@@ -133,22 +122,22 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
|
|
||||||
// Prepare redis connection
|
// Prepare redis connection
|
||||||
this.redisManager = new RedisManager(this);
|
this.redisManager = new RedisManager(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||||
initialized.set(this.redisManager.initialize().join());
|
initialized.set(this.redisManager.initialize());
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the Redis server");
|
log(Level.INFO, "Successfully established a connection to the Redis server");
|
||||||
} else {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " +
|
throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " +
|
||||||
"Please check the supplied Redis credentials in the config file");
|
"Please check the supplied Redis credentials in the config file");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register events
|
// Register events
|
||||||
getLoggingAdapter().log(Level.INFO, "Registering events...");
|
log(Level.INFO, "Registering events...");
|
||||||
this.eventListener = new BukkitEventListener(this);
|
this.eventListener = new BukkitEventListener(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully registered events listener");
|
log(Level.INFO, "Successfully registered events listener");
|
||||||
|
|
||||||
// Register permissions
|
// Register permissions
|
||||||
getLoggingAdapter().log(Level.INFO, "Registering permissions & commands...");
|
log(Level.INFO, "Registering permissions & commands...");
|
||||||
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
|
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
|
||||||
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
|
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
|
||||||
case EVERYONE -> PermissionDefault.TRUE;
|
case EVERYONE -> PermissionDefault.TRUE;
|
||||||
@@ -163,32 +152,32 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand);
|
new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands");
|
log(Level.INFO, "Successfully registered permissions & commands");
|
||||||
|
|
||||||
// Hook into plan
|
// Hook into plan
|
||||||
if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
|
if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Enabling Plan integration...");
|
log(Level.INFO, "Enabling Plan integration...");
|
||||||
new PlanHook(database, logger).hookIntoPlan();
|
new PlanHook(this).hookIntoPlan();
|
||||||
getLoggingAdapter().log(Level.INFO, "Plan integration enabled!");
|
log(Level.INFO, "Plan integration enabled!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook into bStats metrics
|
// Hook into bStats metrics
|
||||||
try {
|
try {
|
||||||
new Metrics(this, METRICS_ID);
|
new Metrics(this, METRICS_ID);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
getLoggingAdapter().log(Level.WARNING, "Skipped bStats metrics initialization due to an exception.");
|
log(Level.WARNING, "Skipped bStats metrics initialization due to an exception.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
if (settings.checkForUpdates) {
|
if (settings.checkForUpdates) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Checking for updates...");
|
log(Level.INFO, "Checking for updates...");
|
||||||
getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||||
newestVersion.ifPresent(newVersion -> getLoggingAdapter().log(Level.WARNING,
|
newestVersion.ifPresent(newVersion -> log(Level.WARNING,
|
||||||
"An update is available for HuskSync, v" + newVersion
|
"An update is available for HuskSync, v" + newVersion
|
||||||
+ " (Currently running v" + getPluginVersion() + ")")));
|
+ " (Currently running v" + getPluginVersion() + ")")));
|
||||||
}
|
}
|
||||||
} catch (HuskSyncInitializationException exception) {
|
} catch (HuskSyncInitializationException exception) {
|
||||||
getLoggingAdapter().log(Level.SEVERE, """
|
log(Level.SEVERE, """
|
||||||
***************************************************
|
***************************************************
|
||||||
|
|
||||||
Failed to initialize HuskSync!
|
Failed to initialize HuskSync!
|
||||||
@@ -203,14 +192,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
.replaceAll("%error_message%", exception.getMessage()));
|
.replaceAll("%error_message%", exception.getMessage()));
|
||||||
initialized.set(false);
|
initialized.set(false);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
getLoggingAdapter().log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
|
log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
|
||||||
initialized.set(false);
|
initialized.set(false);
|
||||||
} finally {
|
} finally {
|
||||||
// Validate initialization
|
// Validate initialization
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
||||||
} else {
|
} else {
|
||||||
getLoggingAdapter().log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled");
|
log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled");
|
||||||
getServer().getPluginManager().disablePlugin(this);
|
getServer().getPluginManager().disablePlugin(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +210,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
if (this.eventListener != null) {
|
if (this.eventListener != null) {
|
||||||
this.eventListener.handlePluginDisable();
|
this.eventListener.handlePluginDisable();
|
||||||
}
|
}
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -275,14 +264,8 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Logger getLoggingAdapter() {
|
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||||
return logger;
|
getLogger().log(level, message, throwable);
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public ResourceReader getResourceReader() {
|
|
||||||
return resourceReader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -307,6 +290,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
return audiences;
|
return audiences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<UUID> getLockedPlayers() {
|
||||||
|
return this.eventListener.getLockedPlayers();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> reload() {
|
public CompletableFuture<Boolean> reload() {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
@@ -322,7 +310,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
return true;
|
return true;
|
||||||
} catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException |
|
} catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException |
|
||||||
InstantiationException e) {
|
InstantiationException e) {
|
||||||
getLoggingAdapter().log(Level.SEVERE, "Failed to load data from the config", e);
|
log(Level.SEVERE, "Failed to load data from the config", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ public class BrigadierUtil {
|
|||||||
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
||||||
@NotNull CommandBase command) {
|
@NotNull CommandBase command) {
|
||||||
// Register command descriptions via commodore (brigadier wrapper)
|
// Register command descriptions via commodore (brigadier wrapper)
|
||||||
try (InputStream pluginFile = plugin.getResourceReader()
|
try (InputStream pluginFile = plugin.getResource("commodore/" + command.command + ".commodore")) {
|
||||||
.getResource("commodore/" + command.command + ".commodore")) {
|
|
||||||
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
||||||
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
||||||
player -> player.hasPermission(command.permission));
|
player -> player.hasPermission(command.permission));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
plugin.log(Level.SEVERE,
|
||||||
"Failed to load " + command.command + ".commodore command definitions", e);
|
"Failed to load " + command.command + ".commodore command definitions", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
import net.william278.mapdataapi.MapData;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.MapMeta;
|
||||||
|
import org.bukkit.map.*;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the persistence of {@link MapData} into {@link ItemStack}s.
|
||||||
|
*/
|
||||||
|
public class BukkitMapHandler {
|
||||||
|
|
||||||
|
private static final BukkitHuskSync plugin = BukkitHuskSync.getInstance();
|
||||||
|
private static final NamespacedKey MAP_DATA_KEY = new NamespacedKey(plugin, "map_data");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link MapData} from the given {@link ItemStack} and persist it in its' data container
|
||||||
|
*
|
||||||
|
* @param itemStack the {@link ItemStack} to get the {@link MapData} from
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public static void persistMapData(@Nullable ItemStack itemStack) {
|
||||||
|
if (itemStack == null || itemStack.getType() != Material.FILLED_MAP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
|
||||||
|
if (mapMeta == null || !mapMeta.hasMapView()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the map view from the map
|
||||||
|
final MapView mapView = mapMeta.getMapView();
|
||||||
|
if (mapView == null || !mapView.isLocked() || mapView.isVirtual()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the map data
|
||||||
|
plugin.debug("Rendering map view onto canvas for locked map");
|
||||||
|
final LockedMapCanvas canvas = new LockedMapCanvas(mapView);
|
||||||
|
for (MapRenderer renderer : mapView.getRenderers()) {
|
||||||
|
renderer.render(mapView, canvas, Bukkit.getServer()
|
||||||
|
.getOnlinePlayers().stream()
|
||||||
|
.findAny()
|
||||||
|
.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the extracted rendered map data
|
||||||
|
plugin.debug("Saving pixel canvas data for locked map");
|
||||||
|
if (!mapMeta.getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) {
|
||||||
|
mapMeta.getPersistentDataContainer().set(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY,
|
||||||
|
canvas.extractMapData().toBytes());
|
||||||
|
itemStack.setItemMeta(mapMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the map data of the given {@link ItemStack} to the given {@link MapData}, applying a map view to the item stack
|
||||||
|
*
|
||||||
|
* @param itemStack the {@link ItemStack} to set the map data of
|
||||||
|
*/
|
||||||
|
public static void setMapRenderer(@Nullable ItemStack itemStack) {
|
||||||
|
if (itemStack == null || itemStack.getType() != Material.FILLED_MAP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
|
||||||
|
if (mapMeta == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemStack.getItemMeta().getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final byte[] serializedData = itemStack.getItemMeta().getPersistentDataContainer()
|
||||||
|
.get(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY);
|
||||||
|
final MapData mapData = MapData.fromByteArray(Objects.requireNonNull(serializedData));
|
||||||
|
plugin.debug("Setting deserialized map data for an item stack");
|
||||||
|
|
||||||
|
// Create a new map view renderer with the map data color at each pixel
|
||||||
|
final MapView view = Bukkit.createMap(Bukkit.getWorlds().get(0));
|
||||||
|
view.getRenderers().clear();
|
||||||
|
view.addRenderer(new PersistentMapRenderer(mapData));
|
||||||
|
view.setLocked(true);
|
||||||
|
view.setScale(MapView.Scale.NORMAL);
|
||||||
|
view.setTrackingPosition(false);
|
||||||
|
view.setUnlimitedTracking(false);
|
||||||
|
mapMeta.setMapView(view);
|
||||||
|
itemStack.setItemMeta(mapMeta);
|
||||||
|
plugin.debug("Successfully applied renderer to map item stack");
|
||||||
|
} catch (IOException | NullPointerException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING, "Failed to deserialize map data for a player", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MapRenderer} that can be used to render persistently serialized {@link MapData} to a {@link MapView}
|
||||||
|
*/
|
||||||
|
public static class PersistentMapRenderer extends MapRenderer {
|
||||||
|
|
||||||
|
private final MapData mapData;
|
||||||
|
|
||||||
|
private PersistentMapRenderer(@NotNull MapData mapData) {
|
||||||
|
super(false);
|
||||||
|
this.mapData = mapData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player) {
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
// We set the pixels in this order to avoid the map being rendered upside down
|
||||||
|
canvas.setPixel(j, i, (byte) mapData.getColorAt(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
|
||||||
|
*/
|
||||||
|
public static class LockedMapCanvas implements MapCanvas {
|
||||||
|
|
||||||
|
private final MapView mapView;
|
||||||
|
private final int[][] pixels = new int[128][128];
|
||||||
|
private MapCursorCollection cursors;
|
||||||
|
|
||||||
|
private LockedMapCanvas(@NotNull MapView mapView) {
|
||||||
|
this.mapView = mapView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MapView getMapView() {
|
||||||
|
return mapView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MapCursorCollection getCursors() {
|
||||||
|
return cursors == null ? (cursors = new MapCursorCollection()) : cursors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCursors(@NotNull MapCursorCollection cursors) {
|
||||||
|
this.cursors = cursors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPixel(int x, int y, byte color) {
|
||||||
|
pixels[x][y] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getPixel(int x, int y) {
|
||||||
|
return (byte) pixels[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getBasePixel(int x, int y) {
|
||||||
|
return getPixel(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawImage(int x, int y, @NotNull Image image) {
|
||||||
|
// Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawText(int x, int y, @NotNull MapFont font, @NotNull String text) {
|
||||||
|
// Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String getDimension() {
|
||||||
|
return mapView.getWorld() == null ? "minecraft:overworld"
|
||||||
|
: switch (mapView.getWorld().getEnvironment()) {
|
||||||
|
case NETHER -> "minecraft:the_nether";
|
||||||
|
case THE_END -> "minecraft:the_end";
|
||||||
|
default -> "minecraft:overworld";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the map data from the canvas. Must be rendered first
|
||||||
|
* @return the extracted map data
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private MapData extractMapData() {
|
||||||
|
return MapData.fromPixels(pixels, getDimension(), (byte) 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||||
@@ -40,14 +41,18 @@ public class BukkitSerializer {
|
|||||||
bukkitOutputStream.writeInt(inventoryContents.length);
|
bukkitOutputStream.writeInt(inventoryContents.length);
|
||||||
|
|
||||||
// Write each serialize each ItemStack to the output stream
|
// Write each serialize each ItemStack to the output stream
|
||||||
|
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||||
for (ItemStack inventoryItem : inventoryContents) {
|
for (ItemStack inventoryItem : inventoryContents) {
|
||||||
|
if (persistLockedMaps) {
|
||||||
|
BukkitMapHandler.persistMapData(inventoryItem);
|
||||||
|
}
|
||||||
bukkitOutputStream.writeObject(serializeItemStack(inventoryItem));
|
bukkitOutputStream.writeObject(serializeItemStack(inventoryItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize item stack data", e);
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize item stack data", e);
|
||||||
throw new DataSerializationException("Failed to serialize item stack data", e);
|
throw new DataSerializationException("Failed to serialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -89,8 +94,13 @@ public class BukkitSerializer {
|
|||||||
|
|
||||||
// Set the ItemStacks in the array from deserialized ItemStack data
|
// Set the ItemStacks in the array from deserialized ItemStack data
|
||||||
int slotIndex = 0;
|
int slotIndex = 0;
|
||||||
|
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||||
for (ItemStack ignored : inventoryContents) {
|
for (ItemStack ignored : inventoryContents) {
|
||||||
inventoryContents[slotIndex] = deserializeItemStack(bukkitInputStream.readObject());
|
final ItemStack deserialized = deserializeItemStack(bukkitInputStream.readObject());
|
||||||
|
if (persistLockedMaps) {
|
||||||
|
BukkitMapHandler.setMapRenderer(deserialized);
|
||||||
|
}
|
||||||
|
inventoryContents[slotIndex] = deserialized;
|
||||||
slotIndex++;
|
slotIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +108,7 @@ public class BukkitSerializer {
|
|||||||
return inventoryContents;
|
return inventoryContents;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize item stack data", e);
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize item stack data", e);
|
||||||
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -155,7 +165,7 @@ public class BukkitSerializer {
|
|||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize potion effect data", e);
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize potion effect data", e);
|
||||||
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -191,7 +201,7 @@ public class BukkitSerializer {
|
|||||||
return potionEffects;
|
return potionEffects;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
|
||||||
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface BukkitDeathEventListener extends Listener {
|
||||||
|
|
||||||
|
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerDeathHighest(@NotNull PlayerDeathEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||||
|
handlePlayerDeath(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
default void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||||
|
handlePlayerDeath(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerDeathLowest(@NotNull PlayerDeathEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||||
|
handlePlayerDeath(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePlayerDeath(@NotNull PlayerDeathEvent player);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package net.william278.husksync.listener;
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.BukkitInventoryMap;
|
import net.william278.husksync.data.BukkitInventoryMap;
|
||||||
import net.william278.husksync.data.BukkitSerializer;
|
import net.william278.husksync.data.BukkitSerializer;
|
||||||
import net.william278.husksync.data.ItemData;
|
import net.william278.husksync.data.ItemData;
|
||||||
import net.william278.husksync.player.BukkitPlayer;
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Projectile;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -16,11 +19,11 @@ import org.bukkit.event.block.BlockPlaceEvent;
|
|||||||
import org.bukkit.event.entity.EntityDamageEvent;
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
import org.bukkit.event.world.WorldSaveEvent;
|
import org.bukkit.event.world.WorldSaveEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -28,39 +31,40 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BukkitEventListener extends EventListener implements Listener {
|
public class BukkitEventListener extends EventListener implements BukkitJoinEventListener, BukkitQuitEventListener,
|
||||||
|
BukkitDeathEventListener, Listener {
|
||||||
|
|
||||||
public BukkitEventListener(@NotNull BukkitHuskSync huskSync) {
|
public BukkitEventListener(@NotNull BukkitHuskSync huskSync) {
|
||||||
super(huskSync);
|
super(huskSync);
|
||||||
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
|
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@Override
|
||||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
public boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority) {
|
||||||
super.handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
return plugin.getSettings().getEventPriority(type).equals(priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@Override
|
||||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
public void handlePlayerQuit(@NotNull BukkitPlayer bukkitPlayer) {
|
||||||
super.handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
final Player player = bukkitPlayer.getPlayer();
|
||||||
|
if (!bukkitPlayer.isLocked() && !player.getItemOnCursor().getType().isAir()) {
|
||||||
|
player.getWorld().dropItem(player.getLocation(), player.getItemOnCursor());
|
||||||
|
player.setItemOnCursor(null);
|
||||||
|
}
|
||||||
|
super.handlePlayerQuit(bukkitPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
@Override
|
||||||
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
public void handlePlayerJoin(@NotNull BukkitPlayer bukkitPlayer) {
|
||||||
// Handle saving player data snapshots when the world saves
|
super.handlePlayerJoin(bukkitPlayer);
|
||||||
if (!plugin.getSettings().saveOnWorldSave) return;
|
|
||||||
|
|
||||||
CompletableFuture.runAsync(() -> super.saveOnWorldSave(event.getWorld().getPlayers()
|
|
||||||
.stream().map(BukkitPlayer::adapt)
|
|
||||||
.collect(Collectors.toList())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
@Override
|
||||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||||
final OnlineUser user = BukkitPlayer.adapt(event.getEntity());
|
final OnlineUser user = BukkitPlayer.adapt(event.getEntity());
|
||||||
|
|
||||||
// If the player is locked or the plugin disabling, clear their drops
|
// If the player is locked or the plugin disabling, clear their drops
|
||||||
if (cancelPlayerEvent(user)) {
|
if (cancelPlayerEvent(user.uuid)) {
|
||||||
event.getDrops().clear();
|
event.getDrops().clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,50 +81,72 @@ public class BukkitEventListener extends EventListener implements Listener {
|
|||||||
.thenAccept(serializedDrops -> super.saveOnPlayerDeath(user, new ItemData(serializedDrops)));
|
.thenAccept(serializedDrops -> super.saveOnPlayerDeath(user, new ItemData(serializedDrops)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(ignoreCancelled = true)
|
||||||
|
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
||||||
|
// Handle saving player data snapshots when the world saves
|
||||||
|
if (!plugin.getSettings().saveOnWorldSave) return;
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> super.saveOnWorldSave(event.getWorld().getPlayers()
|
||||||
|
.stream().map(BukkitPlayer::adapt)
|
||||||
|
.collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Events to cancel if the player has not been set yet
|
* Events to cancel if the player has not been set yet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
|
||||||
|
final Projectile projectile = event.getEntity();
|
||||||
|
if (projectile.getShooter() instanceof Player player && projectile.getType() == EntityType.TRIDENT) {
|
||||||
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
||||||
if (event.getPlayer() instanceof Player player) {
|
if (event.getPlayer() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onInventoryClick(@NotNull InventoryClickEvent event) {
|
||||||
|
event.setCancelled(cancelPlayerEvent(event.getWhoClicked().getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface BukkitJoinEventListener extends Listener {
|
||||||
|
|
||||||
|
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerJoinHighest(@NotNull PlayerJoinEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||||
|
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
default void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||||
|
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerJoinLowest(@NotNull PlayerJoinEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||||
|
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePlayerJoin(@NotNull BukkitPlayer player);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface BukkitQuitEventListener extends Listener {
|
||||||
|
|
||||||
|
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerQuitHighest(@NotNull PlayerQuitEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||||
|
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
default void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||||
|
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
default void onPlayerQuitLowest(@NotNull PlayerQuitEvent event) {
|
||||||
|
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||||
|
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePlayerQuit(@NotNull BukkitPlayer player);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,26 +49,26 @@ public class LegacyMigrator extends Migrator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> start() {
|
public CompletableFuture<Boolean> start() {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration of legacy HuskSync v1.x data...");
|
plugin.log(Level.INFO, "Starting migration of legacy HuskSync v1.x data...");
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Wipe the existing database, preparing it for data import
|
// Wipe the existing database, preparing it for data import
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)...");
|
plugin.log(Level.INFO, "Preparing existing database (wiping)...");
|
||||||
plugin.getDatabase().wipeDatabase().join();
|
plugin.getDatabase().wipeDatabase().join();
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||||
|
|
||||||
// Create jdbc driver connection url
|
// Create jdbc driver connection url
|
||||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||||
|
|
||||||
// Create a new data source for the mpdb converter
|
// Create a new data source for the mpdb converter
|
||||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to legacy database...");
|
plugin.log(Level.INFO, "Establishing connection to legacy database...");
|
||||||
connectionPool.setJdbcUrl(jdbcUrl);
|
connectionPool.setJdbcUrl(jdbcUrl);
|
||||||
connectionPool.setUsername(sourceUsername);
|
connectionPool.setUsername(sourceUsername);
|
||||||
connectionPool.setPassword(sourcePassword);
|
connectionPool.setPassword(sourcePassword);
|
||||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||||
|
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
|
plugin.log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
|
||||||
final List<LegacyData> dataToMigrate = new ArrayList<>();
|
final List<LegacyData> dataToMigrate = new ArrayList<>();
|
||||||
try (final Connection connection = connectionPool.getConnection()) {
|
try (final Connection connection = connectionPool.getConnection()) {
|
||||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||||
@@ -106,33 +106,33 @@ public class LegacyMigrator extends Migrator {
|
|||||||
));
|
));
|
||||||
playersMigrated++;
|
playersMigrated++;
|
||||||
if (playersMigrated % 50 == 0) {
|
if (playersMigrated % 50 == 0) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
|
plugin.log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!");
|
plugin.log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!");
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
plugin.log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
||||||
|
|
||||||
final AtomicInteger playersConverted = new AtomicInteger();
|
final AtomicInteger playersConverted = new AtomicInteger();
|
||||||
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
||||||
.exceptionally(exception -> {
|
.exceptionally(exception -> {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
|
plugin.log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
|
||||||
return null;
|
return null;
|
||||||
})).join();
|
})).join();
|
||||||
|
|
||||||
playersConverted.getAndIncrement();
|
playersConverted.getAndIncrement();
|
||||||
if (playersConverted.get() % 50 == 0) {
|
if (playersConverted.get() % 50 == 0) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
|
plugin.log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
|
||||||
}
|
}
|
||||||
}).join());
|
}).join());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?");
|
plugin.log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -176,15 +176,15 @@ public class LegacyMigrator extends Migrator {
|
|||||||
}
|
}
|
||||||
default -> false;
|
default -> false;
|
||||||
}) {
|
}) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,26 +56,26 @@ public class MpdbMigrator extends Migrator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> start() {
|
public CompletableFuture<Boolean> start() {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
|
plugin.log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Wipe the existing database, preparing it for data import
|
// Wipe the existing database, preparing it for data import
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)...");
|
plugin.log(Level.INFO, "Preparing existing database (wiping)...");
|
||||||
plugin.getDatabase().wipeDatabase().join();
|
plugin.getDatabase().wipeDatabase().join();
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||||
|
|
||||||
// Create jdbc driver connection url
|
// Create jdbc driver connection url
|
||||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||||
|
|
||||||
// Create a new data source for the mpdb converter
|
// Create a new data source for the mpdb converter
|
||||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
|
plugin.log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
|
||||||
connectionPool.setJdbcUrl(jdbcUrl);
|
connectionPool.setJdbcUrl(jdbcUrl);
|
||||||
connectionPool.setUsername(sourceUsername);
|
connectionPool.setUsername(sourceUsername);
|
||||||
connectionPool.setPassword(sourcePassword);
|
connectionPool.setPassword(sourcePassword);
|
||||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||||
|
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
|
plugin.log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
|
||||||
final List<MpdbData> dataToMigrate = new ArrayList<>();
|
final List<MpdbData> dataToMigrate = new ArrayList<>();
|
||||||
try (final Connection connection = connectionPool.getConnection()) {
|
try (final Connection connection = connectionPool.getConnection()) {
|
||||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||||
@@ -103,32 +103,32 @@ public class MpdbMigrator extends Migrator {
|
|||||||
));
|
));
|
||||||
playersMigrated++;
|
playersMigrated++;
|
||||||
if (playersMigrated % 25 == 0) {
|
if (playersMigrated % 25 == 0) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players...");
|
plugin.log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the MySQLPlayerDataBridge database!");
|
plugin.log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the MySQLPlayerDataBridge database!");
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
plugin.log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
||||||
|
|
||||||
final AtomicInteger playersConverted = new AtomicInteger();
|
final AtomicInteger playersConverted = new AtomicInteger();
|
||||||
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
||||||
.exceptionally(exception -> {
|
.exceptionally(exception -> {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
|
plugin.log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}).join();
|
}).join();
|
||||||
playersConverted.getAndIncrement();
|
playersConverted.getAndIncrement();
|
||||||
if (playersConverted.get() % 50 == 0) {
|
if (playersConverted.get() % 50 == 0) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
|
plugin.log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
|
||||||
}
|
}
|
||||||
}).join());
|
}).join());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
|
plugin.log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -176,15 +176,15 @@ public class MpdbMigrator extends Migrator {
|
|||||||
}
|
}
|
||||||
default -> false;
|
default -> false;
|
||||||
}) {
|
}) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
|||||||
import dev.triumphteam.gui.guis.Gui;
|
import dev.triumphteam.gui.guis.Gui;
|
||||||
import dev.triumphteam.gui.guis.StorageGui;
|
import dev.triumphteam.gui.guis.StorageGui;
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
import net.roxeez.advancement.display.FrameType;
|
||||||
|
import net.william278.andjam.Toast;
|
||||||
import net.william278.desertwell.Version;
|
import net.william278.desertwell.Version;
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
@@ -58,6 +60,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
this.audience = BukkitHuskSync.getInstance().getAudiences().player(player);
|
this.audience = BukkitHuskSync.getInstance().getAudiences().player(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public static BukkitPlayer adapt(@NotNull Player player) {
|
public static BukkitPlayer adapt(@NotNull Player player) {
|
||||||
return new BukkitPlayer(player);
|
return new BukkitPlayer(player);
|
||||||
}
|
}
|
||||||
@@ -88,8 +91,8 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull Settings settings) {
|
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull Settings settings) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
// Set max health
|
||||||
.getBaseValue();
|
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue();
|
||||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.MAX_HEALTH)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.MAX_HEALTH)) {
|
||||||
if (statusData.maxHealth != 0d) {
|
if (statusData.maxHealth != 0d) {
|
||||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
||||||
@@ -98,22 +101,33 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HEALTH)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HEALTH)) {
|
||||||
|
// Set health
|
||||||
final double currentHealth = player.getHealth();
|
final double currentHealth = player.getHealth();
|
||||||
if (statusData.health != currentHealth) {
|
if (statusData.health != currentHealth) {
|
||||||
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
||||||
if (healthToSet < 1) {
|
final double maxHealth = currentMaxHealth;
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> player.setHealth(healthToSet));
|
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||||
} else {
|
try {
|
||||||
player.setHealth(healthToSet);
|
player.setHealth(Math.min(healthToSet, maxHealth));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set health of player " + player.getName() + " to " + healthToSet);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set health scale
|
||||||
|
try {
|
||||||
if (statusData.healthScale != 0d) {
|
if (statusData.healthScale != 0d) {
|
||||||
player.setHealthScale(statusData.healthScale);
|
player.setHealthScale(statusData.healthScale);
|
||||||
} else {
|
} else {
|
||||||
player.setHealthScale(statusData.maxHealth);
|
player.setHealthScale(statusData.maxHealth);
|
||||||
}
|
}
|
||||||
player.setHealthScaled(statusData.healthScale != 0D);
|
player.setHealthScaled(statusData.healthScale != 0D);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set health scale of player " + player.getName() + " to " + statusData.healthScale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HUNGER)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HUNGER)) {
|
||||||
player.setFoodLevel(statusData.hunger);
|
player.setFoodLevel(statusData.hunger);
|
||||||
@@ -155,7 +169,9 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
return BukkitSerializer.deserializeInventory(itemData.serializedItems).thenApplyAsync(contents -> {
|
return BukkitSerializer.deserializeInventory(itemData.serializedItems).thenApplyAsync(contents -> {
|
||||||
final CompletableFuture<Void> inventorySetFuture = new CompletableFuture<>();
|
final CompletableFuture<Void> inventorySetFuture = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||||
|
player.setItemOnCursor(null);
|
||||||
player.getInventory().setContents(contents.getContents());
|
player.getInventory().setContents(contents.getContents());
|
||||||
|
player.updateInventory();
|
||||||
inventorySetFuture.complete(null);
|
inventorySetFuture.complete(null);
|
||||||
});
|
});
|
||||||
return inventorySetFuture.join();
|
return inventorySetFuture.join();
|
||||||
@@ -351,32 +367,52 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
// Set untyped statistics
|
// Set generic statistics
|
||||||
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set generic statistic " + statistic + " for " + username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set block statistics
|
// Set block statistics
|
||||||
for (String statistic : statisticsData.blockStatistics.keySet()) {
|
for (String statistic : statisticsData.blockStatistics.keySet()) {
|
||||||
for (String blockMaterial : statisticsData.blockStatistics.get(statistic).keySet()) {
|
for (String blockMaterial : statisticsData.blockStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
||||||
statisticsData.blockStatistics.get(statistic).get(blockMaterial));
|
statisticsData.blockStatistics.get(statistic).get(blockMaterial));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set " + blockMaterial + " statistic " + statistic + " for " + username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set item statistics
|
// Set item statistics
|
||||||
for (String statistic : statisticsData.itemStatistics.keySet()) {
|
for (String statistic : statisticsData.itemStatistics.keySet()) {
|
||||||
for (String itemMaterial : statisticsData.itemStatistics.get(statistic).keySet()) {
|
for (String itemMaterial : statisticsData.itemStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
||||||
statisticsData.itemStatistics.get(statistic).get(itemMaterial));
|
statisticsData.itemStatistics.get(statistic).get(itemMaterial));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set " + itemMaterial + " statistic " + statistic + " for " + username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set entity statistics
|
// Set entity statistics
|
||||||
for (String statistic : statisticsData.entityStatistics.keySet()) {
|
for (String statistic : statisticsData.entityStatistics.keySet()) {
|
||||||
for (String entityType : statisticsData.entityStatistics.get(statistic).keySet()) {
|
for (String entityType : statisticsData.entityStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
||||||
statisticsData.entityStatistics.get(statistic).get(entityType));
|
statisticsData.entityStatistics.get(statistic).get(entityType));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
BukkitHuskSync.getInstance().getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set " + entityType + " statistic " + statistic + " for " + username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -472,7 +508,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
return new PersistentDataContainerData(persistentDataMap);
|
return new PersistentDataContainerData(persistentDataMap);
|
||||||
}).exceptionally(throwable -> {
|
}).exceptionally(throwable -> {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
BukkitHuskSync.getInstance().log(Level.WARNING,
|
||||||
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return new PersistentDataContainerData(new HashMap<>());
|
return new PersistentDataContainerData(new HashMap<>());
|
||||||
@@ -480,65 +516,62 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData) {
|
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData container) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
||||||
player.getPersistentDataContainer().remove(namespacedKey));
|
player.getPersistentDataContainer().remove(namespacedKey));
|
||||||
persistentDataContainerData.getTags().forEach(keyString -> {
|
container.getTags().forEach(keyString -> {
|
||||||
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
// Set a tag with the given key and value. This is crying out for a refactor.
|
// Set a tag with the given key and value. This is crying out for a refactor.
|
||||||
persistentDataContainerData.getTagType(keyString).ifPresentOrElse(dataType -> {
|
container.getTagType(keyString).ifPresentOrElse(dataType -> {
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case BYTE -> persistentDataContainerData.getTagValue(keyString, byte.class).ifPresent(
|
case BYTE -> container.getTagValue(keyString, byte.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.BYTE, value));
|
PersistentDataType.BYTE, value));
|
||||||
case SHORT -> persistentDataContainerData.getTagValue(keyString, short.class).ifPresent(
|
case SHORT -> container.getTagValue(keyString, short.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.SHORT, value));
|
PersistentDataType.SHORT, value));
|
||||||
case INTEGER -> persistentDataContainerData.getTagValue(keyString, int.class).ifPresent(
|
case INTEGER -> container.getTagValue(keyString, int.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.INTEGER, value));
|
PersistentDataType.INTEGER, value));
|
||||||
case LONG -> persistentDataContainerData.getTagValue(keyString, long.class).ifPresent(
|
case LONG -> container.getTagValue(keyString, long.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.LONG, value));
|
PersistentDataType.LONG, value));
|
||||||
case FLOAT -> persistentDataContainerData.getTagValue(keyString, float.class).ifPresent(
|
case FLOAT -> container.getTagValue(keyString, float.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.FLOAT, value));
|
PersistentDataType.FLOAT, value));
|
||||||
case DOUBLE -> persistentDataContainerData.getTagValue(keyString, double.class).ifPresent(
|
case DOUBLE -> container.getTagValue(keyString, double.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.DOUBLE, value));
|
PersistentDataType.DOUBLE, value));
|
||||||
case STRING -> persistentDataContainerData.getTagValue(keyString, String.class).ifPresent(
|
case STRING -> container.getTagValue(keyString, String.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.STRING, value));
|
PersistentDataType.STRING, value));
|
||||||
case BYTE_ARRAY ->
|
case BYTE_ARRAY -> container.getTagValue(keyString, byte[].class).ifPresent(
|
||||||
persistentDataContainerData.getTagValue(keyString, byte[].class).ifPresent(
|
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.BYTE_ARRAY, value));
|
PersistentDataType.BYTE_ARRAY, value));
|
||||||
case INTEGER_ARRAY ->
|
case INTEGER_ARRAY -> container.getTagValue(keyString, int[].class).ifPresent(
|
||||||
persistentDataContainerData.getTagValue(keyString, int[].class).ifPresent(
|
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.INTEGER_ARRAY, value));
|
PersistentDataType.INTEGER_ARRAY, value));
|
||||||
case LONG_ARRAY ->
|
case LONG_ARRAY -> container.getTagValue(keyString, long[].class).ifPresent(
|
||||||
persistentDataContainerData.getTagValue(keyString, long[].class).ifPresent(
|
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.LONG_ARRAY, value));
|
PersistentDataType.LONG_ARRAY, value));
|
||||||
case TAG_CONTAINER ->
|
case TAG_CONTAINER ->
|
||||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer.class).ifPresent(
|
container.getTagValue(keyString, PersistentDataContainer.class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.TAG_CONTAINER, value));
|
PersistentDataType.TAG_CONTAINER, value));
|
||||||
case TAG_CONTAINER_ARRAY ->
|
case TAG_CONTAINER_ARRAY ->
|
||||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer[].class).ifPresent(
|
container.getTagValue(keyString, PersistentDataContainer[].class).ifPresent(
|
||||||
value -> player.getPersistentDataContainer().set(key,
|
value -> player.getPersistentDataContainer().set(key,
|
||||||
PersistentDataType.TAG_CONTAINER_ARRAY, value));
|
PersistentDataType.TAG_CONTAINER_ARRAY, value));
|
||||||
}
|
}
|
||||||
}, () -> BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
}, () -> BukkitHuskSync.getInstance().log(Level.WARNING,
|
||||||
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
||||||
" as it has an invalid type. Skipping!"));
|
" as it has an invalid type. Skipping!"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).exceptionally(throwable -> {
|
}).exceptionally(throwable -> {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
BukkitHuskSync.getInstance().log(Level.WARNING,
|
||||||
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@@ -573,7 +606,6 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
// Deserialize the item data to be shown and show it in a triumph GUI
|
// Deserialize the item data to be shown and show it in a triumph GUI
|
||||||
BukkitSerializer.deserializeItemStackArray(itemData.serializedItems).thenAccept(items -> {
|
BukkitSerializer.deserializeItemStackArray(itemData.serializedItems).thenAccept(items -> {
|
||||||
try {
|
|
||||||
// Build the GUI and populate with items
|
// Build the GUI and populate with items
|
||||||
final int itemCount = items.length;
|
final int itemCount = items.length;
|
||||||
final StorageBuilder guiBuilder = Gui.storage()
|
final StorageBuilder guiBuilder = Gui.storage()
|
||||||
@@ -609,16 +641,17 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
// Display the GUI (synchronously; on the main server thread)
|
// Display the GUI (synchronously; on the main server thread)
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> gui.open(player));
|
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> gui.open(player));
|
||||||
} catch (Exception e) {
|
}).exceptionally(throwable -> {
|
||||||
e.printStackTrace();
|
// Handle exceptions
|
||||||
}
|
updatedData.completeExceptionally(throwable);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
return updatedData;
|
return updatedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDead() {
|
public boolean isDead() {
|
||||||
return player.getHealth() < 1;
|
return player.getHealth() <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -628,6 +661,23 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
.replace().toComponent());
|
.replace().toComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
|
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||||
|
try {
|
||||||
|
final Material material = Material.matchMaterial(iconMaterial);
|
||||||
|
Toast.builder(BukkitHuskSync.getInstance())
|
||||||
|
.setTitle(title.toComponent())
|
||||||
|
.setDescription(description.toComponent())
|
||||||
|
.setIcon(material != null ? material : Material.BARRIER)
|
||||||
|
.setFrameType(FrameType.valueOf(backgroundType))
|
||||||
|
.build()
|
||||||
|
.show(player);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@NotNull MineDown mineDown) {
|
public void sendMessage(@NotNull MineDown mineDown) {
|
||||||
audience.sendMessage(mineDown
|
audience.sendMessage(mineDown
|
||||||
@@ -654,4 +704,14 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
return maxHealth;
|
return maxHealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return BukkitHuskSync.getInstance().getLockedPlayers().contains(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNpc() {
|
||||||
|
return player.hasMetadata("NPC");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
public class BukkitLogger extends Logger {
|
|
||||||
|
|
||||||
private final java.util.logging.Logger logger;
|
|
||||||
|
|
||||||
public BukkitLogger(@NotNull java.util.logging.Logger logger) {
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Exception e) {
|
|
||||||
logger.log(level, message, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(@NotNull Level level, @NotNull String message) {
|
|
||||||
logger.log(level, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void info(@NotNull String message) {
|
|
||||||
logger.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void severe(@NotNull String message) {
|
|
||||||
logger.severe(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void config(@NotNull String message) {
|
|
||||||
logger.config(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class BukkitResourceReader implements ResourceReader {
|
|
||||||
|
|
||||||
private final BukkitHuskSync plugin;
|
|
||||||
|
|
||||||
public BukkitResourceReader(BukkitHuskSync plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull InputStream getResource(String fileName) {
|
|
||||||
return Objects.requireNonNull(plugin.getResource(fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ dependencies {
|
|||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.11.0'
|
||||||
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||||
implementation 'net.kyori:adventure-api:4.11.0'
|
implementation 'net.kyori:adventure-api:4.11.0'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.10'
|
||||||
implementation 'dev.dejvokep:boosted-yaml:1.3'
|
implementation 'dev.dejvokep:boosted-yaml:1.3'
|
||||||
implementation 'net.william278:Annotaml:2.0'
|
implementation 'net.william278:Annotaml:2.0'
|
||||||
implementation 'net.william278:DesertWell:1.1'
|
implementation 'net.william278:DesertWell:1.1'
|
||||||
@@ -19,6 +19,9 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
|
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
|
||||||
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
|
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
|
||||||
|
testImplementation 'redis.clients:jedis:' + jedis_version
|
||||||
|
testImplementation 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||||
|
testImplementation 'org.apache.commons:commons-text:' + commons_text_version
|
||||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ import net.william278.husksync.event.EventCannon;
|
|||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import net.william278.desertwell.Version;
|
import net.william278.desertwell.Version;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract implementation of the HuskSync plugin.
|
* Abstract implementation of the HuskSync plugin.
|
||||||
@@ -103,20 +103,33 @@ public interface HuskSync {
|
|||||||
Locales getLocales();
|
Locales getLocales();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the plugin {@link Logger}
|
* Get a resource as an {@link InputStream} from the plugin jar
|
||||||
*
|
*
|
||||||
* @return the {@link Logger}
|
* @param name the path to the resource
|
||||||
|
* @return the {@link InputStream} of the resource
|
||||||
*/
|
*/
|
||||||
@NotNull
|
InputStream getResource(@NotNull String name);
|
||||||
Logger getLoggingAdapter();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the plugin resource file reader
|
* Log a message to the console
|
||||||
*
|
*
|
||||||
* @return the {@link ResourceReader}
|
* @param level the level of the message
|
||||||
|
* @param message the message to log
|
||||||
|
* @param throwable a throwable to log
|
||||||
*/
|
*/
|
||||||
@NotNull
|
void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable);
|
||||||
ResourceReader getResourceReader();
|
|
||||||
|
/**
|
||||||
|
* Send a debug message to the console, if debug logging is enabled
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
* @param throwable a throwable to log
|
||||||
|
*/
|
||||||
|
default void debug(@NotNull String message, @NotNull Throwable... throwable) {
|
||||||
|
if (getSettings().debugLogging) {
|
||||||
|
log(Level.INFO, "[DEBUG] " + message, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the plugin version
|
* Returns the plugin version
|
||||||
@@ -165,4 +178,6 @@ public interface HuskSync {
|
|||||||
*/
|
*/
|
||||||
CompletableFuture<Boolean> reload();
|
CompletableFuture<Boolean> reload();
|
||||||
|
|
||||||
|
Set<UUID> getLockedPlayers();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public abstract class BaseHuskSyncAPI {
|
|||||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
if (user instanceof OnlineUser) {
|
if (user instanceof OnlineUser) {
|
||||||
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join();
|
return ((OnlineUser) user).getUserData(plugin).join();
|
||||||
} else {
|
} else {
|
||||||
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ public abstract class BaseHuskSyncAPI {
|
|||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
||||||
return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings())
|
return CompletableFuture.runAsync(() -> user.getUserData(plugin)
|
||||||
.thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
.thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class EnderChestCommand extends CommandBase implements TabCompletable {
|
public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||||
@@ -44,9 +46,10 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// View latest user data
|
// View (and edit) the latest user data
|
||||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||||
versionedUserData -> showEnderChestMenu(player, versionedUserData, user, true),
|
versionedUserData -> showEnderChestMenu(player, versionedUserData, user,
|
||||||
|
player.hasPermission(Permission.COMMAND_ENDER_CHEST_EDIT.node)),
|
||||||
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage)));
|
.ifPresent(player::sendMessage)));
|
||||||
}
|
}
|
||||||
@@ -68,7 +71,12 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
// Show inventory menu
|
// Show inventory menu
|
||||||
player.showMenu(itemData, allowEdit, 3, plugin.getLocales()
|
player.showMenu(itemData, allowEdit, 3, plugin.getLocales()
|
||||||
.getLocale("ender_chest_viewer_menu_title", dataOwner.username)
|
.getLocale("ender_chest_viewer_menu_title", dataOwner.username)
|
||||||
.orElse(new MineDown("Ender Chest Viewer"))).thenAccept(dataOnClose -> {
|
.orElse(new MineDown("Ender Chest Viewer")))
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
|
||||||
|
return Optional.empty();
|
||||||
|
})
|
||||||
|
.thenAccept(dataOnClose -> {
|
||||||
if (dataOnClose.isEmpty() || !allowEdit) {
|
if (dataOnClose.isEmpty() || !allowEdit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,8 +94,9 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
|
|
||||||
// Set the updated data
|
// Set the updated data
|
||||||
final UserData updatedUserData = builder.build();
|
final UserData updatedUserData = builder.build();
|
||||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
|
plugin.getDatabase()
|
||||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
.setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND)
|
||||||
|
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,25 +88,25 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
@Override
|
@Override
|
||||||
public void onConsoleExecute(@NotNull String[] args) {
|
public void onConsoleExecute(@NotNull String[] args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\"");
|
plugin.log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[0].toLowerCase()) {
|
switch (args[0].toLowerCase()) {
|
||||||
case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||||
newestVersion.ifPresentOrElse(newVersion -> plugin.getLoggingAdapter().log(Level.WARNING,
|
newestVersion.ifPresentOrElse(newVersion -> plugin.log(Level.WARNING,
|
||||||
"An update is available for HuskSync, v" + newVersion
|
"An update is available for HuskSync, v" + newVersion
|
||||||
+ " (Running v" + plugin.getPluginVersion() + ")"),
|
+ " (Running v" + plugin.getPluginVersion() + ")"),
|
||||||
() -> plugin.getLoggingAdapter().log(Level.INFO,
|
() -> plugin.log(Level.INFO,
|
||||||
"HuskSync is up to date" +
|
"HuskSync is up to date" +
|
||||||
" (Running v" + plugin.getPluginVersion() + ")")));
|
" (Running v" + plugin.getPluginVersion() + ")")));
|
||||||
case "about", "info" -> aboutMenu.toString().lines().forEach(plugin.getLoggingAdapter()::info);
|
case "about", "info" -> aboutMenu.toString().lines().forEach(line -> plugin.log(Level.INFO, line));
|
||||||
case "reload" -> {
|
case "reload" -> {
|
||||||
plugin.reload();
|
plugin.reload();
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files.");
|
plugin.log(Level.INFO, "Reloaded config & message files.");
|
||||||
}
|
}
|
||||||
case "migrate" -> {
|
case "migrate" -> {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO,
|
plugin.log(Level.INFO,
|
||||||
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
||||||
logMigratorsList();
|
logMigratorsList();
|
||||||
return;
|
return;
|
||||||
@@ -115,35 +115,35 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
|
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
|
||||||
selectedMigrator.ifPresentOrElse(migrator -> {
|
selectedMigrator.ifPresentOrElse(migrator -> {
|
||||||
if (args.length < 3) {
|
if (args.length < 3) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, migrator.getHelpMenu());
|
plugin.log(Level.INFO, migrator.getHelpMenu());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[2]) {
|
switch (args[2]) {
|
||||||
case "start" -> migrator.start().thenAccept(succeeded -> {
|
case "start" -> migrator.start().thenAccept(succeeded -> {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration completed successfully!");
|
plugin.log(Level.INFO, "Migration completed successfully!");
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.WARNING, "Migration failed!");
|
plugin.log(Level.WARNING, "Migration failed!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
||||||
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
default -> plugin.log(Level.INFO,
|
||||||
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + " <start/set>");
|
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + " <start/set>");
|
||||||
}
|
}
|
||||||
}, () -> {
|
}, () -> {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO,
|
plugin.log(Level.INFO,
|
||||||
"Please specify a valid migrator.\n" +
|
"Please specify a valid migrator.\n" +
|
||||||
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
|
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
|
||||||
logMigratorsList();
|
logMigratorsList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
default -> plugin.log(Level.INFO,
|
||||||
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
|
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logMigratorsList() {
|
private void logMigratorsList() {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO,
|
plugin.log(Level.INFO,
|
||||||
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
|
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
|
||||||
plugin.getAvailableMigrators().stream()
|
plugin.getAvailableMigrators().stream()
|
||||||
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class InventoryCommand extends CommandBase implements TabCompletable {
|
public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||||
@@ -44,9 +46,10 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// View latest user data
|
// View (and edit) the latest user data
|
||||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||||
versionedUserData -> showInventoryMenu(player, versionedUserData, user, true),
|
versionedUserData -> showInventoryMenu(player, versionedUserData, user,
|
||||||
|
player.hasPermission(Permission.COMMAND_INVENTORY_EDIT.node)),
|
||||||
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage)));
|
.ifPresent(player::sendMessage)));
|
||||||
}
|
}
|
||||||
@@ -69,13 +72,15 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
player.showMenu(itemData, allowEdit, 5, plugin.getLocales()
|
player.showMenu(itemData, allowEdit, 5, plugin.getLocales()
|
||||||
.getLocale("inventory_viewer_menu_title", dataOwner.username)
|
.getLocale("inventory_viewer_menu_title", dataOwner.username)
|
||||||
.orElse(new MineDown("Inventory Viewer")))
|
.orElse(new MineDown("Inventory Viewer")))
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
|
||||||
|
return Optional.empty();
|
||||||
|
})
|
||||||
.thenAccept(dataOnClose -> {
|
.thenAccept(dataOnClose -> {
|
||||||
if (dataOnClose.isEmpty() || !allowEdit) {
|
if (dataOnClose.isEmpty() || !allowEdit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.getLoggingAdapter().debug("Inventory data changed, updating user, etc!");
|
|
||||||
|
|
||||||
// Create the updated data
|
// Create the updated data
|
||||||
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
||||||
data.getStatus().ifPresent(builder::setStatus);
|
data.getStatus().ifPresent(builder::setStatus);
|
||||||
@@ -89,8 +94,9 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
|
|
||||||
// Set the updated data
|
// Set the updated data
|
||||||
final UserData updatedUserData = builder.build();
|
final UserData updatedUserData = builder.build();
|
||||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
|
plugin.getDatabase()
|
||||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
.setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND)
|
||||||
|
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
.split("-")[0], user.username, result)
|
.split("-")[0], user.username, result)
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to dump user data", e);
|
plugin.log(Level.SEVERE, "Failed to dump user data", e);
|
||||||
}
|
}
|
||||||
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
.ifPresent(player::sendMessage))),
|
.ifPresent(player::sendMessage))),
|
||||||
|
|||||||
@@ -121,6 +121,21 @@ public class Locales {
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates a String to a specified length, and appends an ellipsis if it is longer than the specified length
|
||||||
|
*
|
||||||
|
* @param string The string to truncate
|
||||||
|
* @param length The maximum length of the string
|
||||||
|
* @return The truncated string
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static String truncate(@NotNull String string, int length) {
|
||||||
|
if (string.length() > length) {
|
||||||
|
return string.substring(0, length) + "…";
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the base list options to use for a paginated chat list
|
* Returns the base list options to use for a paginated chat list
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin settings, read from config.yml
|
* Plugin settings, read from config.yml
|
||||||
@@ -19,8 +18,7 @@ import java.util.Optional;
|
|||||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
┣╸ Information: https://william278.net/project/husksync
|
┣╸ Information: https://william278.net/project/husksync
|
||||||
┗╸ Documentation: https://william278.net/docs/husksync""",
|
┗╸ Documentation: https://william278.net/docs/husksync""",
|
||||||
|
versionField = "config_version", versionNumber = 3)
|
||||||
versionField = "config_version", versionNumber = 2)
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
// Top-level settings
|
// Top-level settings
|
||||||
@@ -47,7 +45,7 @@ public class Settings {
|
|||||||
@YamlKey("database.credentials.database")
|
@YamlKey("database.credentials.database")
|
||||||
public String mySqlDatabase = "HuskSync";
|
public String mySqlDatabase = "HuskSync";
|
||||||
|
|
||||||
@YamlKey("database.mysql.credentials.username")
|
@YamlKey("database.credentials.username")
|
||||||
public String mySqlUsername = "root";
|
public String mySqlUsername = "root";
|
||||||
|
|
||||||
@YamlKey("database.credentials.password")
|
@YamlKey("database.credentials.password")
|
||||||
@@ -77,8 +75,7 @@ public class Settings {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getTableName(@NotNull TableName tableName) {
|
public String getTableName(@NotNull TableName tableName) {
|
||||||
return Optional.ofNullable(tableNames.get(tableName.name().toLowerCase()))
|
return tableNames.getOrDefault(tableName.name().toLowerCase(), tableName.defaultName);
|
||||||
.orElse(tableName.defaultName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -111,6 +108,9 @@ public class Settings {
|
|||||||
@YamlKey("synchronization.compress_data")
|
@YamlKey("synchronization.compress_data")
|
||||||
public boolean compressData = true;
|
public boolean compressData = true;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.notification_display_slot")
|
||||||
|
public NotificationDisplaySlot notificationDisplaySlot = NotificationDisplaySlot.ACTION_BAR;
|
||||||
|
|
||||||
@YamlKey("synchronization.save_dead_player_inventories")
|
@YamlKey("synchronization.save_dead_player_inventories")
|
||||||
public boolean saveDeadPlayerInventories = true;
|
public boolean saveDeadPlayerInventories = true;
|
||||||
|
|
||||||
@@ -121,8 +121,20 @@ public class Settings {
|
|||||||
public Map<String, Boolean> synchronizationFeatures = SynchronizationFeature.getDefaults();
|
public Map<String, Boolean> synchronizationFeatures = SynchronizationFeature.getDefaults();
|
||||||
|
|
||||||
public boolean getSynchronizationFeature(@NotNull SynchronizationFeature feature) {
|
public boolean getSynchronizationFeature(@NotNull SynchronizationFeature feature) {
|
||||||
return Optional.ofNullable(synchronizationFeatures.get(feature.name().toLowerCase()))
|
return synchronizationFeatures.getOrDefault(feature.name().toLowerCase(), feature.enabledByDefault);
|
||||||
.orElse(feature.enabledByDefault);
|
}
|
||||||
|
|
||||||
|
@YamlKey("synchronization.event_priorities")
|
||||||
|
public Map<String, String> synchronizationEventPriorities = EventType.getDefaults();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public EventPriority getEventPriority(@NotNull Settings.EventType eventType) {
|
||||||
|
try {
|
||||||
|
return EventPriority.valueOf(synchronizationEventPriorities.get(eventType.name().toLowerCase()));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return EventPriority.NORMAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -139,11 +151,13 @@ public class Settings {
|
|||||||
this.defaultName = defaultName;
|
this.defaultName = defaultName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private Map.Entry<String, String> toEntry() {
|
private Map.Entry<String, String> toEntry() {
|
||||||
return Map.entry(name().toLowerCase(), defaultName);
|
return Map.entry(name().toLowerCase(), defaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@NotNull
|
||||||
private static Map<String, String> getDefaults() {
|
private static Map<String, String> getDefaults() {
|
||||||
return Map.ofEntries(Arrays.stream(values())
|
return Map.ofEntries(Arrays.stream(values())
|
||||||
.map(TableName::toEntry)
|
.map(TableName::toEntry)
|
||||||
@@ -151,11 +165,32 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the slot a system notification should be displayed in
|
||||||
|
*/
|
||||||
|
public enum NotificationDisplaySlot {
|
||||||
|
/**
|
||||||
|
* Displays the notification in the action bar
|
||||||
|
*/
|
||||||
|
ACTION_BAR,
|
||||||
|
/**
|
||||||
|
* Displays the notification in the chat
|
||||||
|
*/
|
||||||
|
CHAT,
|
||||||
|
/**
|
||||||
|
* Displays the notification in an advancement toast
|
||||||
|
*/
|
||||||
|
TOAST,
|
||||||
|
/**
|
||||||
|
* Does not display the notification
|
||||||
|
*/
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents enabled synchronisation features
|
* Represents enabled synchronisation features
|
||||||
*/
|
*/
|
||||||
public enum SynchronizationFeature {
|
public enum SynchronizationFeature {
|
||||||
|
|
||||||
INVENTORIES(true),
|
INVENTORIES(true),
|
||||||
ENDER_CHESTS(true),
|
ENDER_CHESTS(true),
|
||||||
HEALTH(true),
|
HEALTH(true),
|
||||||
@@ -167,6 +202,7 @@ public class Settings {
|
|||||||
GAME_MODE(true),
|
GAME_MODE(true),
|
||||||
STATISTICS(true),
|
STATISTICS(true),
|
||||||
PERSISTENT_DATA_CONTAINER(false),
|
PERSISTENT_DATA_CONTAINER(false),
|
||||||
|
LOCKED_MAPS(false),
|
||||||
LOCATION(false);
|
LOCATION(false);
|
||||||
|
|
||||||
private final boolean enabledByDefault;
|
private final boolean enabledByDefault;
|
||||||
@@ -175,11 +211,13 @@ public class Settings {
|
|||||||
this.enabledByDefault = enabledByDefault;
|
this.enabledByDefault = enabledByDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private Map.Entry<String, Boolean> toEntry() {
|
private Map.Entry<String, Boolean> toEntry() {
|
||||||
return Map.entry(name().toLowerCase(), enabledByDefault);
|
return Map.entry(name().toLowerCase(), enabledByDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@NotNull
|
||||||
private static Map<String, Boolean> getDefaults() {
|
private static Map<String, Boolean> getDefaults() {
|
||||||
return Map.ofEntries(Arrays.stream(values())
|
return Map.ofEntries(Arrays.stream(values())
|
||||||
.map(SynchronizationFeature::toEntry)
|
.map(SynchronizationFeature::toEntry)
|
||||||
@@ -187,4 +225,51 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents events that HuskSync listens to, with a configurable priority listener
|
||||||
|
*/
|
||||||
|
public enum EventType {
|
||||||
|
JOIN_LISTENER(EventPriority.LOWEST),
|
||||||
|
QUIT_LISTENER(EventPriority.LOWEST),
|
||||||
|
DEATH_LISTENER(EventPriority.NORMAL);
|
||||||
|
|
||||||
|
private final EventPriority defaultPriority;
|
||||||
|
|
||||||
|
EventType(@NotNull EventPriority defaultPriority) {
|
||||||
|
this.defaultPriority = defaultPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Map.Entry<String, String> toEntry() {
|
||||||
|
return Map.entry(name().toLowerCase(), defaultPriority.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NotNull
|
||||||
|
private static Map<String, String> getDefaults() {
|
||||||
|
return Map.ofEntries(Arrays.stream(values())
|
||||||
|
.map(EventType::toEntry)
|
||||||
|
.toArray(Map.Entry[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents priorities for events that HuskSync listens to
|
||||||
|
*/
|
||||||
|
public enum EventPriority {
|
||||||
|
/**
|
||||||
|
* Listens and processes the event execution last
|
||||||
|
*/
|
||||||
|
HIGHEST,
|
||||||
|
/**
|
||||||
|
* Listens in between {@link #HIGHEST} and {@link #LOWEST} priority marked
|
||||||
|
*/
|
||||||
|
NORMAL,
|
||||||
|
/**
|
||||||
|
* Listens and processes the event execution first
|
||||||
|
*/
|
||||||
|
LOWEST
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.api.BaseHuskSyncAPI;
|
import net.william278.husksync.api.BaseHuskSyncAPI;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
@@ -100,4 +101,9 @@ public enum DataSaveCause {
|
|||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Locales.truncate(name().toLowerCase(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
|||||||
public class StatisticsData {
|
public class StatisticsData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of untyped statistic names to their values
|
* Map of generic statistic names to their values
|
||||||
*/
|
*/
|
||||||
@SerializedName("untyped_statistics")
|
@SerializedName("untyped_statistics")
|
||||||
public Map<String, Integer> untypedStatistics;
|
public Map<String, Integer> untypedStatistics;
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package net.william278.husksync.database;
|
package net.william278.husksync.database;
|
||||||
|
|
||||||
import net.william278.husksync.data.DataAdapter;
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,78 +24,10 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
*/
|
*/
|
||||||
public abstract class Database {
|
public abstract class Database {
|
||||||
|
|
||||||
/**
|
protected final HuskSync plugin;
|
||||||
* Name of the table that stores player information
|
|
||||||
*/
|
|
||||||
protected final String playerTableName;
|
|
||||||
|
|
||||||
/**
|
protected Database(@NotNull HuskSync plugin) {
|
||||||
* Name of the table that stores data
|
this.plugin = plugin;
|
||||||
*/
|
|
||||||
protected final String dataTableName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of user records to store in the database at once per user
|
|
||||||
*/
|
|
||||||
protected final int maxUserDataRecords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link DataAdapter} implementation used for adapting {@link UserData} to and from JSON
|
|
||||||
*/
|
|
||||||
private final DataAdapter dataAdapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link DataAdapter} used to adapt {@link UserData} to and from JSON
|
|
||||||
*
|
|
||||||
* @return instance of the {@link DataAdapter} implementation
|
|
||||||
*/
|
|
||||||
protected DataAdapter getDataAdapter() {
|
|
||||||
return dataAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link EventCannon} implementation used for firing events
|
|
||||||
*/
|
|
||||||
private final EventCannon eventCannon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link EventCannon} used to fire events
|
|
||||||
*
|
|
||||||
* @return instance of the {@link EventCannon} implementation
|
|
||||||
*/
|
|
||||||
protected EventCannon getEventCannon() {
|
|
||||||
return eventCannon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger instance used for database error logging
|
|
||||||
*/
|
|
||||||
private final Logger logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Logger} used to log database errors
|
|
||||||
*
|
|
||||||
* @return the {@link Logger} instance
|
|
||||||
*/
|
|
||||||
protected Logger getLogger() {
|
|
||||||
return logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link ResourceReader} used to read internal resource files by name
|
|
||||||
*/
|
|
||||||
private final ResourceReader resourceReader;
|
|
||||||
|
|
||||||
protected Database(@NotNull String playerTableName, @NotNull String dataTableName, final int maxUserDataRecords,
|
|
||||||
@NotNull ResourceReader resourceReader, @NotNull DataAdapter dataAdapter,
|
|
||||||
@NotNull EventCannon eventCannon, @NotNull Logger logger) {
|
|
||||||
this.playerTableName = playerTableName;
|
|
||||||
this.dataTableName = dataTableName;
|
|
||||||
this.maxUserDataRecords = maxUserDataRecords;
|
|
||||||
this.resourceReader = resourceReader;
|
|
||||||
this.dataAdapter = dataAdapter;
|
|
||||||
this.eventCannon = eventCannon;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,7 +39,7 @@ public abstract class Database {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
||||||
return formatStatementTables(new String(Objects.requireNonNull(resourceReader.getResource(schemaFileName))
|
return formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName))
|
||||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +50,8 @@ public abstract class Database {
|
|||||||
* @return the formatted statement, with table placeholders replaced with the correct names
|
* @return the formatted statement, with table placeholders replaced with the correct names
|
||||||
*/
|
*/
|
||||||
protected final String formatStatementTables(@NotNull String sql) {
|
protected final String formatStatementTables(@NotNull String sql) {
|
||||||
return sql.replaceAll("%users_table%", playerTableName)
|
return sql.replaceAll("%users_table%", plugin.getSettings().getTableName(Settings.TableName.USERS))
|
||||||
.replaceAll("%user_data_table%", dataTableName);
|
.replaceAll("%user_data_table%", plugin.getSettings().getTableName(Settings.TableName.USER_DATA));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package net.william278.husksync.database;
|
package net.william278.husksync.database;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.event.DataSaveEvent;
|
import net.william278.husksync.event.DataSaveEvent;
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -51,12 +49,9 @@ public class MySqlDatabase extends Database {
|
|||||||
*/
|
*/
|
||||||
private HikariDataSource connectionPool;
|
private HikariDataSource connectionPool;
|
||||||
|
|
||||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger,
|
public MySqlDatabase(@NotNull HuskSync plugin) {
|
||||||
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) {
|
super(plugin);
|
||||||
super(settings.getTableName(Settings.TableName.USERS),
|
final Settings settings = plugin.getSettings();
|
||||||
settings.getTableName(Settings.TableName.USER_DATA),
|
|
||||||
Math.max(1, Math.min(20, settings.maxUserDataSnapshots)),
|
|
||||||
resourceReader, dataAdapter, eventCannon, logger);
|
|
||||||
this.mySqlHost = settings.mySqlHost;
|
this.mySqlHost = settings.mySqlHost;
|
||||||
this.mySqlPort = settings.mySqlPort;
|
this.mySqlPort = settings.mySqlPort;
|
||||||
this.mySqlDatabaseName = settings.mySqlDatabase;
|
this.mySqlDatabaseName = settings.mySqlDatabase;
|
||||||
@@ -111,10 +106,10 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
plugin.log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
getLogger().log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
|
plugin.log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -135,9 +130,9 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.setString(2, existingUser.uuid.toString());
|
statement.setString(2, existingUser.uuid.toString());
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
getLogger().log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")");
|
plugin.log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
plugin.log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -153,7 +148,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to insert a user into the database", e);
|
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -176,7 +171,7 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e);
|
plugin.log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e);
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -199,7 +194,7 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -226,11 +221,11 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray)));
|
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} catch (SQLException | DataAdaptionException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -257,13 +252,13 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray));
|
plugin.getDataAdapter().fromBytes(dataByteArray));
|
||||||
retrievedData.add(data);
|
retrievedData.add(data);
|
||||||
}
|
}
|
||||||
return retrievedData;
|
return retrievedData;
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} catch (SQLException | DataAdaptionException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||||
}
|
}
|
||||||
return retrievedData;
|
return retrievedData;
|
||||||
});
|
});
|
||||||
@@ -291,11 +286,11 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray)));
|
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} catch (SQLException | DataAdaptionException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e);
|
plugin.log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e);
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -306,7 +301,7 @@ public class MySqlDatabase extends Database {
|
|||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
||||||
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
||||||
if (unpinnedUserData.size() > maxUserDataRecords) {
|
if (unpinnedUserData.size() > plugin.getSettings().maxUserDataSnapshots) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
DELETE FROM `%user_data_table%`
|
DELETE FROM `%user_data_table%`
|
||||||
@@ -314,12 +309,12 @@ public class MySqlDatabase extends Database {
|
|||||||
AND `pinned` IS FALSE
|
AND `pinned` IS FALSE
|
||||||
ORDER BY `timestamp` ASC
|
ORDER BY `timestamp` ASC
|
||||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
LIMIT %entry_count%;""".replace("%entry_count%",
|
||||||
Integer.toString(unpinnedUserData.size() - maxUserDataRecords))))) {
|
Integer.toString(unpinnedUserData.size() - plugin.getSettings().maxUserDataSnapshots))))) {
|
||||||
statement.setString(1, user.uuid.toString());
|
statement.setString(1, user.uuid.toString());
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
|
plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -338,7 +333,7 @@ public class MySqlDatabase extends Database {
|
|||||||
return statement.executeUpdate() > 0;
|
return statement.executeUpdate() > 0;
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to delete specific user data from the database", e);
|
plugin.log(Level.SEVERE, "Failed to delete specific user data from the database", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -348,7 +343,7 @@ public class MySqlDatabase extends Database {
|
|||||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
||||||
@NotNull DataSaveCause saveCause) {
|
@NotNull DataSaveCause saveCause) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
final DataSaveEvent dataSaveEvent = (DataSaveEvent) getEventCannon().fireDataSaveEvent(user,
|
final DataSaveEvent dataSaveEvent = (DataSaveEvent) plugin.getEventCannon().fireDataSaveEvent(user,
|
||||||
userData, saveCause).join();
|
userData, saveCause).join();
|
||||||
if (!dataSaveEvent.isCancelled()) {
|
if (!dataSaveEvent.isCancelled()) {
|
||||||
final UserData finalData = dataSaveEvent.getUserData();
|
final UserData finalData = dataSaveEvent.getUserData();
|
||||||
@@ -360,11 +355,11 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.setString(1, user.uuid.toString());
|
statement.setString(1, user.uuid.toString());
|
||||||
statement.setString(2, saveCause.name());
|
statement.setString(2, saveCause.name());
|
||||||
statement.setBlob(3, new ByteArrayInputStream(
|
statement.setBlob(3, new ByteArrayInputStream(
|
||||||
getDataAdapter().toBytes(finalData)));
|
plugin.getDataAdapter().toBytes(finalData)));
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} catch (SQLException | DataAdaptionException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
|
plugin.log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).thenRun(() -> rotateUserData(user).join());
|
}).thenRun(() -> rotateUserData(user).join());
|
||||||
@@ -384,7 +379,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to pin user data in the database", e);
|
plugin.log(Level.SEVERE, "Failed to pin user data in the database", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -403,7 +398,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to unpin user data in the database", e);
|
plugin.log(Level.SEVERE, "Failed to unpin user data in the database", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -416,7 +411,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
|
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to wipe the database", e);
|
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import com.djrapitops.plan.extension.icon.Family;
|
|||||||
import com.djrapitops.plan.extension.icon.Icon;
|
import com.djrapitops.plan.extension.icon.Icon;
|
||||||
import com.djrapitops.plan.extension.table.Table;
|
import com.djrapitops.plan.extension.table.Table;
|
||||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.database.Database;
|
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ import java.util.regex.Pattern;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlanDataExtension implements DataExtension {
|
public class PlanDataExtension implements DataExtension {
|
||||||
|
|
||||||
private Database database;
|
private HuskSync plugin;
|
||||||
|
|
||||||
private static final String UNKNOWN_STRING = "N/A";
|
private static final String UNKNOWN_STRING = "N/A";
|
||||||
|
|
||||||
private static final String PINNED_HTML_STRING = "📍 ";
|
private static final String PINNED_HTML_STRING = "📍 ";
|
||||||
|
|
||||||
protected PlanDataExtension(@NotNull Database database) {
|
protected PlanDataExtension(@NotNull HuskSync plugin) {
|
||||||
this.database = database;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PlanDataExtension() {
|
protected PlanDataExtension() {
|
||||||
@@ -66,9 +66,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
|
|
||||||
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
|
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
final Optional<User> optionalUser = database.getUser(uuid).join();
|
final Optional<User> optionalUser = plugin.getDatabase().getUser(uuid).join();
|
||||||
if (optionalUser.isPresent()) {
|
if (optionalUser.isPresent()) {
|
||||||
return database.getCurrentUserData(optionalUser.get()).join();
|
return plugin.getDatabase().getCurrentUserData(optionalUser.get()).join();
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -208,8 +208,8 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
|
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
|
||||||
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
|
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
|
||||||
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
|
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
|
||||||
database.getUser(playerUUID).join().ifPresent(user ->
|
plugin.getDatabase().getUser(playerUUID).join().ifPresent(user ->
|
||||||
database.getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
plugin.getDatabase().getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
||||||
versionedUserData.versionTimestamp().getTime(),
|
versionedUserData.versionTimestamp().getTime(),
|
||||||
versionedUserData.versionUUID().toString().split("-")[0],
|
versionedUserData.versionUUID().toString().split("-")[0],
|
||||||
versionedUserData.cause().name().toLowerCase().replaceAll("_", " "),
|
versionedUserData.cause().name().toLowerCase().replaceAll("_", " "),
|
||||||
|
|||||||
@@ -2,20 +2,17 @@ package net.william278.husksync.hook;
|
|||||||
|
|
||||||
import com.djrapitops.plan.capability.CapabilityService;
|
import com.djrapitops.plan.capability.CapabilityService;
|
||||||
import com.djrapitops.plan.extension.ExtensionService;
|
import com.djrapitops.plan.extension.ExtensionService;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class PlanHook {
|
public class PlanHook {
|
||||||
|
|
||||||
private final Database database;
|
private final HuskSync plugin;
|
||||||
private final Logger logger;
|
|
||||||
|
|
||||||
public PlanHook(@NotNull Database database, @NotNull Logger logger) {
|
public PlanHook(@NotNull HuskSync plugin) {
|
||||||
this.database = database;
|
this.plugin = plugin;
|
||||||
this.logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hookIntoPlan() {
|
public void hookIntoPlan() {
|
||||||
@@ -33,13 +30,9 @@ public class PlanHook {
|
|||||||
|
|
||||||
private void registerDataExtension() {
|
private void registerDataExtension() {
|
||||||
try {
|
try {
|
||||||
ExtensionService.getInstance().register(new PlanDataExtension(database));
|
ExtensionService.getInstance().register(new PlanDataExtension(plugin));
|
||||||
} catch (IllegalStateException planIsNotEnabled) {
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
logger.log(Level.SEVERE, "Plan extension hook failed to register. Plan is not enabled.", planIsNotEnabled);
|
plugin.log(Level.WARNING, "Failed to register Plan data extension: " + e.getMessage(), e);
|
||||||
// Plan is not enabled, handle exception
|
|
||||||
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
|
|
||||||
logger.log(Level.SEVERE, "Plan extension hook failed to register. Data hook implementation is invalid.", dataExtensionImplementationIsInvalid);
|
|
||||||
// The DataExtension implementation has an implementation error, handle exception
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.william278.husksync.listener;
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
import net.william278.husksync.data.ItemData;
|
import net.william278.husksync.data.ItemData;
|
||||||
@@ -51,13 +52,17 @@ public abstract class EventListener {
|
|||||||
* @param user The {@link OnlineUser} to handle
|
* @param user The {@link OnlineUser} to handle
|
||||||
*/
|
*/
|
||||||
protected final void handlePlayerJoin(@NotNull OnlineUser user) {
|
protected final void handlePlayerJoin(@NotNull OnlineUser user) {
|
||||||
|
if (user.isNpc()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lockedPlayers.add(user.uuid);
|
lockedPlayers.add(user.uuid);
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
// Hold reading data for the network latency threshold, to ensure the source server has set the redis key
|
// Hold reading data for the network latency threshold, to ensure the source server has set the redis key
|
||||||
Thread.sleep(Math.max(0, plugin.getSettings().networkLatencyMilliseconds));
|
Thread.sleep(Math.max(0, plugin.getSettings().networkLatencyMilliseconds));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "An exception occurred handling a player join", e);
|
plugin.log(Level.SEVERE, "An exception occurred handling a player join", e);
|
||||||
} finally {
|
} finally {
|
||||||
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
|
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
|
||||||
if (!changingServers) {
|
if (!changingServers) {
|
||||||
@@ -83,8 +88,7 @@ public abstract class EventListener {
|
|||||||
}
|
}
|
||||||
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
|
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
|
||||||
redisUserData.ifPresent(redisData -> {
|
redisUserData.ifPresent(redisData -> {
|
||||||
user.setData(redisData, plugin.getSettings(), plugin.getEventCannon(),
|
user.setData(redisData, plugin)
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion())
|
|
||||||
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
|
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
})).join();
|
})).join();
|
||||||
@@ -106,8 +110,7 @@ public abstract class EventListener {
|
|||||||
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
|
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
|
||||||
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
|
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
|
||||||
if (databaseUserData.isPresent()) {
|
if (databaseUserData.isPresent()) {
|
||||||
return user.setData(databaseUserData.get().userData(), plugin.getSettings(), plugin.getEventCannon(),
|
return user.setData(databaseUserData.get().userData(), plugin).join();
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).join();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -121,9 +124,17 @@ public abstract class EventListener {
|
|||||||
*/
|
*/
|
||||||
private void handleSynchronisationCompletion(@NotNull OnlineUser user, boolean succeeded) {
|
private void handleSynchronisationCompletion(@NotNull OnlineUser user, boolean succeeded) {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar);
|
switch (plugin.getSettings().notificationDisplaySlot) {
|
||||||
lockedPlayers.remove(user.uuid);
|
case CHAT -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
case ACTION_BAR -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||||
|
.ifPresent(user::sendActionBar);
|
||||||
|
case TOAST -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||||
|
.ifPresent(locale -> user.sendToast(locale, new MineDown(""),
|
||||||
|
"minecraft:bell", "TASK"));
|
||||||
|
}
|
||||||
plugin.getDatabase().ensureUser(user).join();
|
plugin.getDatabase().ensureUser(user).join();
|
||||||
|
lockedPlayers.remove(user.uuid);
|
||||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLocales().getLocale("synchronisation_failed")
|
plugin.getLocales().getLocale("synchronisation_failed")
|
||||||
@@ -143,19 +154,19 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Don't sync players awaiting synchronization
|
// Don't sync players awaiting synchronization
|
||||||
if (lockedPlayers.contains(user.uuid)) {
|
if (lockedPlayers.contains(user.uuid) || user.isNpc()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle asynchronous disconnection
|
// Handle asynchronous disconnection
|
||||||
lockedPlayers.add(user.uuid);
|
lockedPlayers.add(user.uuid);
|
||||||
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
|
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
|
||||||
.thenRun(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).thenAccept(
|
.thenRun(() -> user.getUserData(plugin).thenAccept(
|
||||||
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
|
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
|
||||||
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
||||||
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
||||||
.thenRun(() -> lockedPlayers.remove(user.uuid)).exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
plugin.log(Level.SEVERE,
|
||||||
"An exception occurred handling a player disconnection");
|
"An exception occurred handling a player disconnection");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@@ -171,8 +182,11 @@ public abstract class EventListener {
|
|||||||
if (disabling || !plugin.getSettings().saveOnWorldSave) {
|
if (disabling || !plugin.getSettings().saveOnWorldSave) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
usersInWorld.stream()
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.WORLD_SAVE).join()));
|
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
|
||||||
|
.forEach(user -> user.getUserData(plugin)
|
||||||
|
.thenAccept(data -> data.ifPresent(userData -> plugin.getDatabase()
|
||||||
|
.setUserData(user, userData, DataSaveCause.WORLD_SAVE))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,11 +196,11 @@ public abstract class EventListener {
|
|||||||
* @param drops The items that this user would have dropped
|
* @param drops The items that this user would have dropped
|
||||||
*/
|
*/
|
||||||
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull ItemData drops) {
|
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull ItemData drops) {
|
||||||
if (disabling || !plugin.getSettings().saveOnDeath) {
|
if (disabling || !plugin.getSettings().saveOnDeath || lockedPlayers.contains(user.uuid) || user.isNpc()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings())
|
user.getUserData(plugin)
|
||||||
.thenAccept(data -> data.ifPresent(userData -> {
|
.thenAccept(data -> data.ifPresent(userData -> {
|
||||||
userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems;
|
userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems;
|
||||||
plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH);
|
plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH);
|
||||||
@@ -196,11 +210,11 @@ public abstract class EventListener {
|
|||||||
/**
|
/**
|
||||||
* Determine whether a player event should be cancelled
|
* Determine whether a player event should be cancelled
|
||||||
*
|
*
|
||||||
* @param user {@link OnlineUser} performing the event
|
* @param userUuid The UUID of the user to check
|
||||||
* @return Whether the event should be cancelled
|
* @return Whether the event should be cancelled
|
||||||
*/
|
*/
|
||||||
protected final boolean cancelPlayerEvent(@NotNull OnlineUser user) {
|
protected final boolean cancelPlayerEvent(@NotNull UUID userUuid) {
|
||||||
return disabling || lockedPlayers.contains(user.uuid);
|
return disabling || lockedPlayers.contains(userUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,12 +223,23 @@ public abstract class EventListener {
|
|||||||
public final void handlePluginDisable() {
|
public final void handlePluginDisable() {
|
||||||
disabling = true;
|
disabling = true;
|
||||||
|
|
||||||
plugin.getOnlineUsers().stream().filter(user -> !lockedPlayers.contains(user.uuid)).forEach(
|
// Save data for all online users
|
||||||
user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
plugin.getOnlineUsers().stream()
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()));
|
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
|
||||||
|
.forEach(user -> {
|
||||||
|
lockedPlayers.add(user.uuid);
|
||||||
|
user.getUserData(plugin).join()
|
||||||
|
.ifPresent(userData -> plugin.getDatabase()
|
||||||
|
.setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close outstanding connections
|
||||||
plugin.getDatabase().close();
|
plugin.getDatabase().close();
|
||||||
plugin.getRedisManager().close();
|
plugin.getRedisManager().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final Set<UUID> getLockedPlayers() {
|
||||||
|
return this.lockedPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package net.william278.husksync.player;
|
|||||||
|
|
||||||
import de.themoep.minedown.adventure.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.desertwell.Version;
|
import net.william278.desertwell.Version;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.event.PreSyncEvent;
|
import net.william278.husksync.event.PreSyncEvent;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -198,6 +197,17 @@ public abstract class OnlineUser extends User {
|
|||||||
*/
|
*/
|
||||||
public abstract void sendActionBar(@NotNull MineDown mineDown);
|
public abstract void sendActionBar(@NotNull MineDown mineDown);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch a toast message to this player
|
||||||
|
*
|
||||||
|
* @param title the title of the toast
|
||||||
|
* @param description the description of the toast
|
||||||
|
* @param iconMaterial the namespace-keyed material to use as an icon of the toast
|
||||||
|
* @param backgroundType the background ("ToastType") of the toast
|
||||||
|
*/
|
||||||
|
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
|
@NotNull String iconMaterial, @NotNull String backgroundType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the player has the permission node
|
* Returns if the player has the permission node
|
||||||
*
|
*
|
||||||
@@ -232,37 +242,32 @@ public abstract class OnlineUser extends User {
|
|||||||
* This will only set data that is enabled as per the enabled settings in the config file.
|
* This will only set data that is enabled as per the enabled settings in the config file.
|
||||||
* Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored.
|
* Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored.
|
||||||
*
|
*
|
||||||
* @param data The {@link UserData} to set to the player
|
* @param plugin The plugin instance
|
||||||
* @param settings The plugin {@link Settings} to determine which data to set
|
|
||||||
* @param eventCannon The {@link EventCannon} to fire the synchronisation events
|
|
||||||
* @param logger The {@link Logger} for debug and error logging
|
|
||||||
* @param serverMinecraftVersion The server's Minecraft version, for validating the format of the {@link UserData}
|
|
||||||
* @return a future returning a boolean when complete; if the sync was successful, the future will return {@code true}.
|
* @return a future returning a boolean when complete; if the sync was successful, the future will return {@code true}.
|
||||||
*/
|
*/
|
||||||
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull Settings settings,
|
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull HuskSync plugin) {
|
||||||
@NotNull EventCannon eventCannon, @NotNull Logger logger,
|
|
||||||
@NotNull Version serverMinecraftVersion) {
|
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Prevent synchronising user data from newer versions of Minecraft
|
// Prevent synchronising user data from newer versions of Minecraft
|
||||||
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) {
|
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(plugin.getMinecraftVersion()) > 0) {
|
||||||
logger.log(Level.SEVERE, "Cannot set data for " + username +
|
plugin.log(Level.SEVERE, "Cannot set data for " + username +
|
||||||
" because the Minecraft version of their user data (" + data.getMinecraftVersion() +
|
" because the Minecraft version of their user data (" + data.getMinecraftVersion() +
|
||||||
") is newer than the server's Minecraft version (" + serverMinecraftVersion + ").");
|
") is newer than the server's Minecraft version (" + plugin.getMinecraftVersion() + ").");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Prevent synchronising user data from newer versions of the plugin
|
// Prevent synchronising user data from newer versions of the plugin
|
||||||
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
|
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
|
||||||
logger.log(Level.SEVERE, "Cannot set data for " + username +
|
plugin.log(Level.SEVERE, "Cannot set data for " + username +
|
||||||
" because the format version of their user data (v" + data.getFormatVersion() +
|
" because the format version of their user data (v" + data.getFormatVersion() +
|
||||||
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
|
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the PreSyncEvent
|
// Fire the PreSyncEvent
|
||||||
final PreSyncEvent preSyncEvent = (PreSyncEvent) eventCannon.firePreSyncEvent(this, data).join();
|
final PreSyncEvent preSyncEvent = (PreSyncEvent) plugin.getEventCannon().firePreSyncEvent(this, data).join();
|
||||||
final UserData finalData = preSyncEvent.getUserData();
|
final UserData finalData = preSyncEvent.getUserData();
|
||||||
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
|
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
|
||||||
if (!isOffline() && !preSyncEvent.isCancelled()) {
|
if (!isOffline() && !preSyncEvent.isCancelled()) {
|
||||||
|
final Settings settings = plugin.getSettings();
|
||||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||||
finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData)));
|
finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData)));
|
||||||
}
|
}
|
||||||
@@ -292,7 +297,7 @@ public abstract class OnlineUser extends User {
|
|||||||
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
|
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
|
||||||
.exceptionally(exception -> {
|
.exceptionally(exception -> {
|
||||||
// Handle synchronisation exceptions
|
// Handle synchronisation exceptions
|
||||||
logger.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
|
plugin.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}).join();
|
}).join();
|
||||||
@@ -311,16 +316,17 @@ public abstract class OnlineUser extends User {
|
|||||||
* <p>
|
* <p>
|
||||||
* If the user data could not be returned due to an exception, the optional will return empty
|
* If the user data could not be returned due to an exception, the optional will return empty
|
||||||
*
|
*
|
||||||
* @param logger The logger to use for handling exceptions
|
* @param plugin The plugin instance
|
||||||
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
|
|
||||||
*/
|
*/
|
||||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger, @NotNull Settings settings) {
|
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull HuskSync plugin) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
|
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
|
||||||
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
|
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
|
||||||
if (!isOffline()) {
|
if (!isOffline()) {
|
||||||
|
final Settings settings = plugin.getSettings();
|
||||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||||
if (isDead() && settings.saveDeadPlayerInventories) {
|
if (isDead() && settings.saveDeadPlayerInventories) {
|
||||||
|
plugin.debug("Player " + username + " is dead, so their inventory will be set to empty.");
|
||||||
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
|
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
|
||||||
} else {
|
} else {
|
||||||
add(getInventory().thenAccept(builder::setInventory));
|
add(getInventory().thenAccept(builder::setInventory));
|
||||||
@@ -351,12 +357,24 @@ public abstract class OnlineUser extends User {
|
|||||||
// Apply operations in parallel, join when complete
|
// Apply operations in parallel, join when complete
|
||||||
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
|
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
|
||||||
return Optional.of(builder.build());
|
return Optional.of(builder.build());
|
||||||
})
|
}).exceptionally(exception -> {
|
||||||
.exceptionally(exception -> {
|
plugin.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
||||||
logger.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if the player is locked
|
||||||
|
*
|
||||||
|
* @return the player's locked status
|
||||||
|
*/
|
||||||
|
public abstract boolean isLocked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if the player is a NPC
|
||||||
|
*
|
||||||
|
* @return if the player is a NPC with metadata
|
||||||
|
*/
|
||||||
|
public abstract boolean isNpc();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
@@ -18,7 +19,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
/**
|
/**
|
||||||
* Manages the connection to the Redis server, handling the caching of user data
|
* Manages the connection to the Redis server, handling the caching of user data
|
||||||
*/
|
*/
|
||||||
public class RedisManager {
|
public class RedisManager extends JedisPubSub {
|
||||||
|
|
||||||
protected static final String KEY_NAMESPACE = "husksync:";
|
protected static final String KEY_NAMESPACE = "husksync:";
|
||||||
protected static String clusterId = "";
|
protected static String clusterId = "";
|
||||||
@@ -52,8 +53,7 @@ public class RedisManager {
|
|||||||
*
|
*
|
||||||
* @return a future returning void when complete
|
* @return a future returning void when complete
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Boolean> initialize() {
|
public boolean initialize() {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
|
||||||
if (redisPassword.isBlank()) {
|
if (redisPassword.isBlank()) {
|
||||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
||||||
} else {
|
} else {
|
||||||
@@ -66,7 +66,6 @@ public class RedisManager {
|
|||||||
}
|
}
|
||||||
CompletableFuture.runAsync(this::subscribe);
|
CompletableFuture.runAsync(this::subscribe);
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subscribe() {
|
private void subscribe() {
|
||||||
@@ -74,19 +73,33 @@ public class RedisManager {
|
|||||||
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
||||||
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
||||||
subscriber.connect();
|
subscriber.connect();
|
||||||
subscriber.subscribe(new JedisPubSub() {
|
subscriber.subscribe(this, Arrays.stream(RedisMessageType.values())
|
||||||
|
.map(RedisMessageType::getMessageChannel)
|
||||||
|
.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(@NotNull String channel, @NotNull String message) {
|
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||||
RedisMessageType.getTypeFromChannel(channel).ifPresent(messageType -> {
|
final RedisMessageType messageType = RedisMessageType.getTypeFromChannel(channel).orElse(null);
|
||||||
if (messageType == RedisMessageType.UPDATE_USER_DATA) {
|
if (messageType != RedisMessageType.UPDATE_USER_DATA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
||||||
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
||||||
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
||||||
user.setData(userData, plugin.getSettings(), plugin.getEventCannon(),
|
user.setData(userData, plugin).thenAccept(succeeded -> {
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).thenAccept(succeeded -> {
|
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLocales().getLocale("data_update_complete")
|
switch (plugin.getSettings().notificationDisplaySlot) {
|
||||||
|
case CHAT -> plugin.getLocales().getLocale("data_update_complete")
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
case ACTION_BAR -> plugin.getLocales().getLocale("data_update_complete")
|
||||||
.ifPresent(user::sendActionBar);
|
.ifPresent(user::sendActionBar);
|
||||||
|
case TOAST -> plugin.getLocales().getLocale("data_update_complete")
|
||||||
|
.ifPresent(locale -> user.sendToast(locale, new MineDown(""),
|
||||||
|
"minecraft:bell", "TASK"));
|
||||||
|
}
|
||||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLocales().getLocale("data_update_failed")
|
plugin.getLocales().getLocale("data_update_failed")
|
||||||
@@ -95,11 +108,6 @@ public class RedisManager {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}, Arrays.stream(RedisMessageType.values()).map(RedisMessageType::getMessageChannel).toArray(String[]::new));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
@@ -131,7 +139,7 @@ public class RedisManager {
|
|||||||
plugin.getDataAdapter().toBytes(userData));
|
plugin.getDataAdapter().toBytes(userData));
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
plugin.debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
||||||
+ " key to redis at: " +
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
}
|
}
|
||||||
@@ -147,7 +155,7 @@ public class RedisManager {
|
|||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
||||||
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
|
plugin.debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
|
||||||
+ " key to redis at: " +
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -168,12 +176,12 @@ public class RedisManager {
|
|||||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
||||||
final byte[] dataByteArray = jedis.get(key);
|
final byte[] dataByteArray = jedis.get(key);
|
||||||
if (dataByteArray == null) {
|
if (dataByteArray == null) {
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
|
plugin.debug("[" + user.username + "] Could not read " +
|
||||||
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
plugin.debug("[" + user.username + "] Successfully read "
|
||||||
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
|
||||||
@@ -195,12 +203,12 @@ public class RedisManager {
|
|||||||
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
||||||
final byte[] readData = jedis.get(key);
|
final byte[] readData = jedis.get(key);
|
||||||
if (readData == null) {
|
if (readData == null) {
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
|
plugin.debug("[" + user.username + "] Could not read " +
|
||||||
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
plugin.debug("[" + user.username + "] Successfully read "
|
||||||
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class DataDumper {
|
|||||||
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to upload data to logs site", e);
|
plugin.log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||||
}
|
}
|
||||||
return "(Failed to upload to logs site)";
|
return "(Failed to upload to logs site)";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class DataSnapshotList {
|
|||||||
.format(snapshot.versionTimestamp()),
|
.format(snapshot.versionTimestamp()),
|
||||||
snapshot.versionUUID().toString().split("-")[0],
|
snapshot.versionUUID().toString().split("-")[0],
|
||||||
snapshot.versionUUID().toString(),
|
snapshot.versionUUID().toString(),
|
||||||
snapshot.cause().name().toLowerCase().replaceAll("_", " "),
|
snapshot.cause().getDisplayName(),
|
||||||
dataOwner.username,
|
dataOwner.username,
|
||||||
snapshot.pinned() ? "※" : " ")
|
snapshot.pinned() ? "※" : " ")
|
||||||
.orElse("• " + snapshot.versionUUID())).toList(),
|
.orElse("• " + snapshot.versionUUID())).toList(),
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract, cross-platform representation of a logger
|
|
||||||
*/
|
|
||||||
public abstract class Logger {
|
|
||||||
|
|
||||||
private boolean debug;
|
|
||||||
|
|
||||||
public abstract void log(@NotNull Level level, @NotNull String message, @NotNull Exception e);
|
|
||||||
|
|
||||||
public abstract void log(@NotNull Level level, @NotNull String message);
|
|
||||||
|
|
||||||
public abstract void info(@NotNull String message);
|
|
||||||
|
|
||||||
public abstract void severe(@NotNull String message);
|
|
||||||
|
|
||||||
public final void debug(@NotNull String message) {
|
|
||||||
if (debug) {
|
|
||||||
log(Level.INFO, "[DEBUG] " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void config(@NotNull String message);
|
|
||||||
|
|
||||||
public final void showDebugLogs(boolean debug) {
|
|
||||||
this.debug = debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract representation of a reader that reads internal resource files by name
|
|
||||||
*/
|
|
||||||
public interface ResourceReader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the resource with given filename and reads it as an {@link InputStream}
|
|
||||||
*
|
|
||||||
* @param fileName Name of the resource file to read
|
|
||||||
* @return The resource, read as an {@link InputStream}; or {@code null} if the resource was not found
|
|
||||||
*/
|
|
||||||
@Nullable InputStream getResource(String fileName);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,20 +22,20 @@ data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text
|
|||||||
data_manager_advancements_statistics: '[⭐ Erfolge: %1%](color=#ffc43b-#f5c962 show_text=&7Erfolge in denen du Fortschritt gemacht hast:\n&8%2%) [⌛ Spielzeit: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Deine verbrachte Zeit im Spiel\n&8⚠ Basierend auf Spielstatistiken)\n'
|
data_manager_advancements_statistics: '[⭐ Erfolge: %1%](color=#ffc43b-#f5c962 show_text=&7Erfolge in denen du Fortschritt gemacht hast:\n&8%2%) [⌛ Spielzeit: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Deine verbrachte Zeit im Spiel\n&8⚠ Basierend auf Spielstatistiken)\n'
|
||||||
data_manager_item_buttons: '[View:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
data_manager_item_buttons: '[View:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
||||||
data_manager_management_buttons: '[Verwalte:](gray) [[❌ Löschen…]](#ff3300 show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss zu löschen.\n&8Dies betrifft nicht die aktuellen Nutzerdaten.\n&#ff3300&⚠ Dieser Schritt kann nicht rückgängig gemacht werden! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Wiederherstellen…]](#00fb9a show_text=&7Klicke, um die Nutzerdaten wiederherzustellen.\n&8Dies wird die Nutzerdaten auf den Stand des Schnappschusses setzen.\n&#ff3300&⚠ Die aktuellen Nutzerdaten von %1% werden überschrieben! suggest_command=/husksync:userdata restore %1% %2%) [[※ Anheften/Loslösen…]](#d8ff2b show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss anzuheften oder loszulösen\n&8Angeheftete Nutzerdaten-Schnappschüsse werden nicht automatisch rotiert run_command=/userdata pin %1% %2%)'
|
data_manager_management_buttons: '[Verwalte:](gray) [[❌ Löschen…]](#ff3300 show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss zu löschen.\n&8Dies betrifft nicht die aktuellen Nutzerdaten.\n&#ff3300&⚠ Dieser Schritt kann nicht rückgängig gemacht werden! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Wiederherstellen…]](#00fb9a show_text=&7Klicke, um die Nutzerdaten wiederherzustellen.\n&8Dies wird die Nutzerdaten auf den Stand des Schnappschusses setzen.\n&#ff3300&⚠ Die aktuellen Nutzerdaten von %1% werden überschrieben! suggest_command=/husksync:userdata restore %1% %2%) [[※ Anheften/Loslösen…]](#d8ff2b show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss anzuheften oder loszulösen\n&8Angeheftete Nutzerdaten-Schnappschüsse werden nicht automatisch rotiert run_command=/userdata pin %1% %2%)'
|
||||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_system_buttons: '[System:](gray) [[⏷ Daten-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss in eine Datei zu speichern.\n&8Daten-Dumps können unter ~/plugins/HuskSync/dumps/ gefunden werden. run_command=/husksync:userdata dump %1% %2% file) [[☂ Web-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss auf den mc-logs Service hochzuladen.\n&8Du erhältst dann eine URL, die die Daten enthält. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_manager_advancements_preview_remaining: '&7und %1% weitere…'
|
data_manager_advancements_preview_remaining: '&7und %1% weitere…'
|
||||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[Nutzerdaten-Schnappschüsse von %1%:](#00fb9a) [(%2%-%3% von](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7Daten-Schnappschuss %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Angeheftet:\n&8Angeheftete Schnappschüsse werden nicht automatisch rotiert. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:&7\n&8Zeitpunkt der Speicherung der Daten run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versions-UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten run_command=/userdata view %6% %4%)'
|
data_list_item: '[%1%](gray show_text=&7Daten-Schnappschuss %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Angeheftet:\n&8Angeheftete Schnappschüsse werden nicht automatisch rotiert. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:&7\n&8Zeitpunkt der Speicherung der Daten run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versions-UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten run_command=/userdata view %6% %4%)'
|
||||||
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||||
data_restored: '[⏪ Erfgreich wiederhergestellt](#00fb9a) [Aktuelle Nutzerdaten des Schnappschusses von %1%](#00fb9a show_text=&7Spieler-UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versions-UUID:\n&8%4%)'
|
data_restored: '[⏪ Erfgreich wiederhergestellt](#00fb9a) [Aktuelle Nutzerdaten des Schnappschusses von %1%](#00fb9a show_text=&7Spieler-UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versions-UUID:\n&8%4%)'
|
||||||
data_pinned: '[※ Nutzerdaten-Schnappschuss erfolgreich angepinnt](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
data_pinned: '[※ Nutzerdaten-Schnappschuss erfolgreich angepinnt](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
||||||
data_unpinned: '[※ Nutzerdaten-Schnappschuss erfolgreich losgelöst](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
data_unpinned: '[※ Nutzerdaten-Schnappschuss erfolgreich losgelöst](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
||||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
data_dumped: '[☂ Nutzerdaten-Schnappschuss %1% für %2% erfolgreich gedumpt nach:](#00fb9a) &7%3%'
|
||||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
list_footer: '\n%1%[Seite](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
list_previous_page_button: '[◀](white show_text=&7Siehe vorherige Seite run_command=%2% %1%) '
|
||||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
list_next_page_button: ' [▶](white show_text=&7Siehe nächste Seite run_command=%2% %1%)'
|
||||||
list_page_jumpers: '(%1%)'
|
list_page_jumpers: '(%1%)'
|
||||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
list_page_jumper_button: '[%1%](show_text=&7Springe zu Seite %1% run_command=%2% %1%)'
|
||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
@@ -22,20 +22,20 @@ data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text
|
|||||||
data_manager_advancements_statistics: '[⭐ Progressi: %1%](color=#ffc43b-#f5c962 show_text=&7Progressi compiuti in:\n&8%2%) [⌛ Tempo di gioco: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo di gioco\n&8⚠ Basato sulle statistiche di gioco)\n'
|
data_manager_advancements_statistics: '[⭐ Progressi: %1%](color=#ffc43b-#f5c962 show_text=&7Progressi compiuti in:\n&8%2%) [⌛ Tempo di gioco: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo di gioco\n&8⚠ Basato sulle statistiche di gioco)\n'
|
||||||
data_manager_item_buttons: '[View:](gray) [[🪣 Inventario…]](color=#a17b5f-#f5b98c show_text=&7Clicca per visualizzare run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Clicca per visualizzare run_command=/enderchest %1% %2%)'
|
data_manager_item_buttons: '[View:](gray) [[🪣 Inventario…]](color=#a17b5f-#f5b98c show_text=&7Clicca per visualizzare run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Clicca per visualizzare run_command=/enderchest %1% %2%)'
|
||||||
data_manager_management_buttons: '[Gestisci:](gray) [[❌ Cancella…]](#ff3300 show_text=&7Fare clic per eliminare questa istantanea.\n&8Questo non influisce sui dati attuali dell''utente.\n&#ff3300&⚠ Questo non può essere annullato! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Ripristina…]](#00fb9a show_text=&7Clicca per ripristinare i dati dell''utente.\n&8I dati dell''utente saranno ripristinati a quest''istantanea.\n&#ff3300&⚠ I dati di %1% saranno sovrascritti! suggest_command=/husksync:userdata restore %1% %2%) [[※ fissa/sblocca...]](#d8ff2b show_text=&7Clicca per fissare o sbloccare quest''istantanea\n&8Le istantanee fissate non saranno cancellate automaticamente run_command=/userdata pin %1% %2%)'
|
data_manager_management_buttons: '[Gestisci:](gray) [[❌ Cancella…]](#ff3300 show_text=&7Fare clic per eliminare questa istantanea.\n&8Questo non influisce sui dati attuali dell''utente.\n&#ff3300&⚠ Questo non può essere annullato! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Ripristina…]](#00fb9a show_text=&7Clicca per ripristinare i dati dell''utente.\n&8I dati dell''utente saranno ripristinati a quest''istantanea.\n&#ff3300&⚠ I dati di %1% saranno sovrascritti! suggest_command=/husksync:userdata restore %1% %2%) [[※ fissa/sblocca...]](#d8ff2b show_text=&7Clicca per fissare o sbloccare quest''istantanea\n&8Le istantanee fissate non saranno cancellate automaticamente run_command=/userdata pin %1% %2%)'
|
||||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_system_buttons: '[Sistema:](gray) [[⏷ Dump del File…]](dark_gray show_text=&7Clicca per ottenere il dump dei dati del giocatore.\n&8I dati salvati sono posizioanti nella cartella ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Dump su Web…]](dark_gray show_text=&7Clicca per ottenere il dump del file su mc-logs\n&8 Ti verrà consegnato l''url per visionare il dump. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_manager_advancements_preview_remaining: '&7e %1% altro…'
|
data_manager_advancements_preview_remaining: '&7e %1% altro…'
|
||||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[Lista delle istantanee di %1%:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7Istantanea dei dati %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Fissato:\n&8Le istantanee fissate non saranno cancellate automaticamente. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Timestamp della versione:&7\n&8Quando i dati sono stati salvati run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versione di UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Causa del salvataggio:\n&8Cosa ha causato il salvataggio dei dati run_command=/userdata view %6% %4%)'
|
data_list_item: '[%1%](gray show_text=&7Istantanea dei dati %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Fissato:\n&8Le istantanee fissate non saranno cancellate automaticamente. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Timestamp della versione:&7\n&8Quando i dati sono stati salvati run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versione di UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Causa del salvataggio:\n&8Cosa ha causato il salvataggio dei dati run_command=/userdata view %6% %4%)'
|
||||||
data_deleted: '[❌ Istantanea eliminata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_deleted: '[❌ Istantanea eliminata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||||
data_restored: '[⏪ Ripristato con successo](#00fb9a) [Dati dall''istantanea di](#00fb9a)[%1%](#00fb9a show_text=&7Player UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versione di UUID:\n&8%4%)'
|
data_restored: '[⏪ Ripristato con successo](#00fb9a) [Dati dall''istantanea di](#00fb9a)[%1%](#00fb9a show_text=&7Player UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versione di UUID:\n&8%4%)'
|
||||||
data_pinned: '[※ Instantanea fissata](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_pinned: '[※ Instantanea fissata](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||||
data_unpinned: '[※ L''istantanea dei dati utente è stata sbloccata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_unpinned: '[※ L''istantanea dei dati utente è stata sbloccata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
data_dumped: '[☂ Hai ottenuto il dump dell''istantanea %1% di %2% nel formato:](#00fb9a) &7%3%'
|
||||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
list_footer: '\n%1%[Pagina](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
list_previous_page_button: '[◀](white show_text=&7Visualizza pagina precedente run_command=%2% %1%) '
|
||||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
list_next_page_button: ' [▶](white show_text=&7Visualizza pagina successiva run_command=%2% %1%)'
|
||||||
list_page_jumpers: '(%1%)'
|
list_page_jumpers: '(%1%)'
|
||||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
list_page_jumper_button: '[%1%](show_text=&7Vai alla pagina %1% run_command=%2% %1%)'
|
||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
115
common/src/test/java/net/william278/husksync/DummyHuskSync.java
Normal file
115
common/src/test/java/net/william278/husksync/DummyHuskSync.java
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package net.william278.husksync;
|
||||||
|
|
||||||
|
import net.william278.desertwell.Version;
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import net.william278.husksync.data.DataAdapter;
|
||||||
|
import net.william278.husksync.database.Database;
|
||||||
|
import net.william278.husksync.event.EventCannon;
|
||||||
|
import net.william278.husksync.migrator.Migrator;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.redis.RedisManager;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class DummyHuskSync implements HuskSync {
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Set<OnlineUser> getOnlineUsers() {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Database getDatabase() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public RedisManager getRedisManager() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public DataAdapter getDataAdapter() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public EventCannon getEventCannon() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<Migrator> getAvailableMigrators() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Settings getSettings() {
|
||||||
|
return new Settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Locales getLocales() {
|
||||||
|
return new Locales();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getResource(@NotNull String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Version getPluginVersion() {
|
||||||
|
return Version.fromString("1.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public File getDataFolder() {
|
||||||
|
return new File(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Version getMinecraftVersion() {
|
||||||
|
return Version.fromString("1.16.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> reload() {
|
||||||
|
return CompletableFuture.supplyAsync(() -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<UUID> getLockedPlayers() {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.DummyHuskSync;
|
||||||
import net.william278.husksync.logger.DummyLogger;
|
|
||||||
import net.william278.husksync.player.DummyPlayer;
|
import net.william278.husksync.player.DummyPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
@@ -19,7 +18,7 @@ public class DataAdaptionTests {
|
|||||||
@Test
|
@Test
|
||||||
public void testJsonDataAdapter() {
|
public void testJsonDataAdapter() {
|
||||||
final OnlineUser dummyUser = DummyPlayer.create();
|
final OnlineUser dummyUser = DummyPlayer.create();
|
||||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||||
@@ -37,7 +36,7 @@ public class DataAdaptionTests {
|
|||||||
final OnlineUser dummyUser = DummyPlayer.create();
|
final OnlineUser dummyUser = DummyPlayer.create();
|
||||||
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"minecraft_version\":\"1.19\",\"format_version\":3}";
|
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"minecraft_version\":\"1.19\",\"format_version\":3}";
|
||||||
AtomicReference<String> json = new AtomicReference<>();
|
AtomicReference<String> json = new AtomicReference<>();
|
||||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||||
json.set(new String(data, StandardCharsets.UTF_8));
|
json.set(new String(data, StandardCharsets.UTF_8));
|
||||||
@@ -48,7 +47,7 @@ public class DataAdaptionTests {
|
|||||||
@Test
|
@Test
|
||||||
public void testCompressedDataAdapter() {
|
public void testCompressedDataAdapter() {
|
||||||
final OnlineUser dummyUser = DummyPlayer.create();
|
final OnlineUser dummyUser = DummyPlayer.create();
|
||||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||||
final DataAdapter dataAdapter = new CompressedDataAdapter();
|
final DataAdapter dataAdapter = new CompressedDataAdapter();
|
||||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package net.william278.husksync.logger;
|
|
||||||
|
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
public class DummyLogger extends Logger {
|
|
||||||
|
|
||||||
public DummyLogger() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Exception e) {
|
|
||||||
System.out.println(level.getName() + ": " + message);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void log(@NotNull Level level, @NotNull String message) {
|
|
||||||
System.out.println(level.getName() + ": " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void info(@NotNull String message) {
|
|
||||||
System.out.println(Level.INFO.getName() + ": " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void severe(@NotNull String message) {
|
|
||||||
System.out.println(Level.SEVERE.getName() + ": " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void config(@NotNull String message) {
|
|
||||||
System.out.println(Level.CONFIG.getName() + ": " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -142,6 +142,12 @@ public class DummyPlayer extends OnlineUser {
|
|||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
|
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(@NotNull String node) {
|
public boolean hasPermission(@NotNull String node) {
|
||||||
return true;
|
return true;
|
||||||
@@ -160,4 +166,14 @@ public class DummyPlayer extends OnlineUser {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNpc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
javaVersion=16
|
javaVersion=16
|
||||||
|
|
||||||
plugin_version=2.1
|
plugin_version=2.2.2
|
||||||
plugin_archive=husksync
|
plugin_archive=husksync
|
||||||
|
|
||||||
jedis_version=4.2.3
|
jedis_version=4.3.1
|
||||||
mysql_driver_version=8.0.30
|
mysql_driver_version=8.0.31
|
||||||
snappy_version=1.1.8.4
|
snappy_version=1.1.8.4
|
||||||
commons_text_version=1.9
|
commons_text_version=1.10.0
|
||||||
Reference in New Issue
Block a user