mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-21 07:49:13 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
390a77b407 | ||
|
|
04ab9d14f8 | ||
|
|
3a32d481c4 | ||
|
|
8edbc029f8 | ||
|
|
258356e45d | ||
|
|
e1628b6448 | ||
|
|
fa32e97564 | ||
|
|
8080d57645 | ||
|
|
0a2f7b6cd4 | ||
|
|
26a2366876 | ||
|
|
2690ab3144 | ||
|
|
18b96944e9 | ||
|
|
3282f5739c | ||
|
|
50e66be0c0 | ||
|
|
593c88c8ba | ||
|
|
2f700b2d93 | ||
|
|
d1c95030f0 | ||
|
|
1ed2414241 | ||
|
|
8847483ff8 | ||
|
|
31552f85e4 | ||
|
|
125f142cf5 | ||
|
|
dc3882e47e | ||
|
|
dafbcad10e | ||
|
|
d1085ca7bd |
@@ -15,7 +15,7 @@
|
||||
- Supports segregating synchronisation across multiple distinct clusters on one network.
|
||||
|
||||
## Requirements
|
||||
* A MySQL Database (v8.0+)
|
||||
* A MySQL Database (v8.0+).
|
||||
* A Redis Database (v5.0+)
|
||||
* Any number of proxied Spigot servers (Minecraft v1.16.5+)
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ dependencies {
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
exclude(dependency('com.mojang:brigadier'))
|
||||
}
|
||||
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||
@@ -16,6 +20,7 @@ shadowJar {
|
||||
relocate 'com.google', 'net.william278.husksync.libraries'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||
|
||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
|
||||
@@ -10,6 +10,9 @@ version "$ext.plugin_version+${versionMetadata()}"
|
||||
|
||||
ext {
|
||||
set 'version', version.toString()
|
||||
set 'jedis_version', jedis_version.toString()
|
||||
set 'mysql_driver_version', mysql_driver_version.toString()
|
||||
set 'snappy_version', snappy_version.toString()
|
||||
}
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
@@ -31,11 +34,12 @@ allprojects {
|
||||
maven { url 'https://repo.minebench.de/' }
|
||||
maven { url 'https://repo.alessiodp.com/releases/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://libraries.minecraft.net/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -3,25 +3,31 @@ dependencies {
|
||||
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||
implementation 'net.william278:hsldataconverter:1.0'
|
||||
implementation 'me.lucko:commodore:2.2'
|
||||
|
||||
compileOnly 'redis.clients:jedis:4.2.3'
|
||||
compileOnly 'commons-io:commons-io:2.11.0'
|
||||
compileOnly 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||
compileOnly 'dev.dejvokep:boosted-yaml:1.2'
|
||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||
compileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
||||
|
||||
testImplementation 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
dependencies {
|
||||
exclude(dependency('com.mojang:brigadier'))
|
||||
}
|
||||
|
||||
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||
relocate 'com.google', 'net.william278.husksync.libraries'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
||||
|
||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||
|
||||
@@ -184,7 +184,19 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
CompletableFuture.runAsync(() -> new UpdateChecker(getPluginVersion(), getLoggingAdapter()).logToConsole());
|
||||
}
|
||||
} catch (HuskSyncInitializationException exception) {
|
||||
getLoggingAdapter().log(Level.SEVERE, exception.getMessage());
|
||||
getLoggingAdapter().log(Level.SEVERE, """
|
||||
***************************************************
|
||||
|
||||
Failed to initialize HuskSync!
|
||||
|
||||
***************************************************
|
||||
The plugin was disabled due to an error. Please check
|
||||
the logs below for details.
|
||||
No user data will be synchronised.
|
||||
***************************************************
|
||||
Caused by: %error_message%
|
||||
"""
|
||||
.replaceAll("%error_message%", exception.getMessage()));
|
||||
initialized.set(false);
|
||||
} catch (Exception exception) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
|
||||
@@ -268,6 +280,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ResourceReader getResourceReader() {
|
||||
return resourceReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Version getPluginVersion() {
|
||||
return Version.pluginVersion(getDescription().getVersion());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
import me.lucko.commodore.file.CommodoreFileReader;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Used for registering Brigadier hooks on platforms that support commodore for rich command syntax
|
||||
*/
|
||||
public class BrigadierUtil {
|
||||
|
||||
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
||||
@NotNull CommandBase command) {
|
||||
// Register command descriptions via commodore (brigadier wrapper)
|
||||
try (InputStream pluginFile = plugin.getResourceReader()
|
||||
.getResource("commodore/" + command.command + ".commodore")) {
|
||||
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
||||
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
||||
player -> player.hasPermission(command.permission));
|
||||
} catch (IOException e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
||||
"Failed to load " + command.command + ".commodore command definitions", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -18,14 +19,14 @@ public class BukkitCommand implements CommandExecutor, TabExecutor {
|
||||
/**
|
||||
* The {@link CommandBase} that will be executed
|
||||
*/
|
||||
private final CommandBase command;
|
||||
protected final CommandBase command;
|
||||
|
||||
/**
|
||||
* The implementing plugin
|
||||
*/
|
||||
private final HuskSync plugin;
|
||||
private final BukkitHuskSync plugin;
|
||||
|
||||
public BukkitCommand(@NotNull CommandBase command, @NotNull HuskSync implementor) {
|
||||
public BukkitCommand(@NotNull CommandBase command, @NotNull BukkitHuskSync implementor) {
|
||||
this.command = command;
|
||||
this.plugin = implementor;
|
||||
}
|
||||
@@ -40,6 +41,9 @@ public class BukkitCommand implements CommandExecutor, TabExecutor {
|
||||
pluginCommand.setTabCompleter(this);
|
||||
pluginCommand.setPermission(command.permission);
|
||||
pluginCommand.setDescription(command.getDescription());
|
||||
if (CommodoreProvider.isSupported()) {
|
||||
BrigadierUtil.registerCommodore(plugin, pluginCommand, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BukkitDataSaveEvent extends BukkitEvent implements DataSaveEvent, Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
@@ -57,4 +58,8 @@ public class BukkitDataSaveEvent extends BukkitEvent implements DataSaveEvent, C
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ package net.william278.husksync.event;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class BukkitEvent extends Event implements net.william278.husksync.event.Event {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
protected BukkitEvent() {
|
||||
}
|
||||
|
||||
@@ -26,4 +31,14 @@ public abstract class BukkitEvent extends Event implements net.william278.husksy
|
||||
return eventFireFuture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,16 @@ import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class BukkitPlayerEvent extends BukkitEvent implements PlayerEvent {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
protected final Player player;
|
||||
|
||||
protected BukkitPlayerEvent(@NotNull Player player) {
|
||||
@@ -32,4 +36,15 @@ public abstract class BukkitPlayerEvent extends BukkitEvent implements PlayerEve
|
||||
return eventFireFuture;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BukkitPreSyncEvent extends BukkitPlayerEvent implements PreSyncEvent, Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
@@ -41,4 +42,8 @@ public class BukkitPreSyncEvent extends BukkitPlayerEvent implements PreSyncEven
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BukkitSyncCompleteEvent extends BukkitPlayerEvent implements SyncCompleteEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
@@ -16,4 +17,8 @@ public class BukkitSyncCompleteEvent extends BukkitPlayerEvent implements SyncCo
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
@@ -121,6 +122,13 @@ public class BukkitEventListener extends EventListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
if (cancelPlayerEvent(BukkitPlayer.adapt(event.getEntity()))) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -68,14 +69,15 @@ public class LegacyMigrator extends Migrator {
|
||||
connectionPool.setPassword(sourcePassword);
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the legacy database...");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
|
||||
final List<LegacyData> dataToMigrate = new ArrayList<>();
|
||||
try (final Connection connection = connectionPool.getConnection()) {
|
||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||
SELECT `uuid`, `username`, `inventory`, `ender_chest`, `health`, `max_health`, `health_scale`, `hunger`, `saturation`, `saturation_exhaustion`, `selected_slot`, `status_effects`, `total_experience`, `exp_level`, `exp_progress`, `game_mode`, `statistics`, `is_flying`, `advancements`, `location`
|
||||
FROM `%source_players_table%`
|
||||
INNER JOIN `%source_data_table%`
|
||||
ON `%source_players_table%`.`id` = `%source_data_table%`.`player_id`;
|
||||
ON `%source_players_table%`.`id` = `%source_data_table%`.`player_id`
|
||||
WHERE `username` IS NOT NULL;
|
||||
""".replaceAll(Pattern.quote("%source_players_table%"), sourcePlayersTable)
|
||||
.replaceAll(Pattern.quote("%source_data_table%"), sourceDataTable))) {
|
||||
try (final ResultSet resultSet = statement.executeQuery()) {
|
||||
@@ -104,7 +106,7 @@ public class LegacyMigrator extends Migrator {
|
||||
resultSet.getString("location")
|
||||
));
|
||||
playersMigrated++;
|
||||
if (playersMigrated % 25 == 0) {
|
||||
if (playersMigrated % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
|
||||
}
|
||||
}
|
||||
@@ -112,14 +114,22 @@ public class LegacyMigrator extends Migrator {
|
||||
}
|
||||
}
|
||||
plugin.getLoggingAdapter().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 latest HuskSync user data format...");
|
||||
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData ->
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
||||
|
||||
final AtomicInteger playersConverted = new AtomicInteger();
|
||||
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
||||
.exceptionally(exception -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
|
||||
return null;
|
||||
}))));
|
||||
})).join();
|
||||
|
||||
playersConverted.getAndIncrement();
|
||||
if (playersConverted.get() % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
|
||||
}
|
||||
}).join());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -73,7 +74,7 @@ public class MpdbMigrator extends Migrator {
|
||||
connectionPool.setPassword(sourcePassword);
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database...");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
|
||||
final List<MpdbData> dataToMigrate = new ArrayList<>();
|
||||
try (final Connection connection = connectionPool.getConnection()) {
|
||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||
@@ -108,14 +109,21 @@ public class MpdbMigrator extends Migrator {
|
||||
}
|
||||
}
|
||||
plugin.getLoggingAdapter().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...");
|
||||
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData ->
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
||||
|
||||
final AtomicInteger playersConverted = new AtomicInteger();
|
||||
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
||||
.exceptionally(exception -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
|
||||
return null;
|
||||
})));
|
||||
}).join();
|
||||
playersConverted.getAndIncrement();
|
||||
if (playersConverted.get() % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
|
||||
}
|
||||
}).join());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.data.*;
|
||||
import net.william278.husksync.editor.ItemEditorMenu;
|
||||
import net.william278.husksync.util.Version;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
@@ -33,6 +32,20 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
private static final PersistentDataType<?, ?>[] PRIMITIVE_PERSISTENT_DATA_TYPES = new PersistentDataType<?, ?>[]{
|
||||
PersistentDataType.BYTE,
|
||||
PersistentDataType.SHORT,
|
||||
PersistentDataType.INTEGER,
|
||||
PersistentDataType.LONG,
|
||||
PersistentDataType.FLOAT,
|
||||
PersistentDataType.DOUBLE,
|
||||
PersistentDataType.STRING,
|
||||
PersistentDataType.BYTE_ARRAY,
|
||||
PersistentDataType.INTEGER_ARRAY,
|
||||
PersistentDataType.LONG_ARRAY,
|
||||
PersistentDataType.TAG_CONTAINER_ARRAY,
|
||||
PersistentDataType.TAG_CONTAINER};
|
||||
|
||||
private final Player player;
|
||||
|
||||
private BukkitPlayer(@NotNull Player player) {
|
||||
@@ -84,7 +97,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
final double currentHealth = player.getHealth();
|
||||
if (statusData.health != currentHealth) {
|
||||
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
||||
if (healthToSet <= 0) {
|
||||
if (healthToSet < 1) {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> player.setHealth(healthToSet));
|
||||
} else {
|
||||
player.setHealth(healthToSet);
|
||||
@@ -402,17 +415,61 @@ public class BukkitPlayer extends OnlineUser {
|
||||
if (container.isEmpty()) {
|
||||
return new PersistentDataContainerData(new HashMap<>());
|
||||
}
|
||||
final HashMap<String, Byte[]> persistentDataMap = new HashMap<>();
|
||||
// Set persistent data keys; ignore keys that we cannot synchronise as byte arrays
|
||||
final HashMap<String, PersistentDataTag<?>> persistentDataMap = new HashMap<>();
|
||||
for (final NamespacedKey key : container.getKeys()) {
|
||||
try {
|
||||
persistentDataMap.put(key.toString(), ArrayUtils.toObject(container.get(key, PersistentDataType.BYTE_ARRAY)));
|
||||
} catch (IllegalArgumentException | NullPointerException ignored) {
|
||||
PersistentDataType<?, ?> type = null;
|
||||
for (PersistentDataType<?, ?> dataType : PRIMITIVE_PERSISTENT_DATA_TYPES) {
|
||||
if (container.has(key, dataType)) {
|
||||
type = dataType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type != null) {
|
||||
// This is absolutely disgusting code and needs to be swiftly put out of its misery with a refactor
|
||||
final Class<?> primitiveType = type.getPrimitiveType();
|
||||
if (String.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.STRING,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.STRING))));
|
||||
} else if (int.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.INTEGER))));
|
||||
} else if (double.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.DOUBLE,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.DOUBLE))));
|
||||
} else if (float.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.FLOAT,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.FLOAT))));
|
||||
} else if (long.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.LONG,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.LONG))));
|
||||
} else if (short.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.SHORT,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.SHORT))));
|
||||
} else if (byte.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.BYTE))));
|
||||
} else if (byte[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.BYTE_ARRAY))));
|
||||
} else if (int[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.INTEGER_ARRAY))));
|
||||
} else if (long[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.LONG_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.LONG_ARRAY))));
|
||||
} else if (PersistentDataContainer.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.TAG_CONTAINER,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.TAG_CONTAINER))));
|
||||
} else if (PersistentDataContainer[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.TAG_CONTAINER_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.TAG_CONTAINER_ARRAY))));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new PersistentDataContainerData(persistentDataMap);
|
||||
}).exceptionally(throwable -> {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING, "Could not read " + player.getName() + "'s persistent data map, skipping!");
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
||||
throwable.printStackTrace();
|
||||
return new PersistentDataContainerData(new HashMap<>());
|
||||
});
|
||||
@@ -423,14 +480,64 @@ public class BukkitPlayer extends OnlineUser {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
||||
player.getPersistentDataContainer().remove(namespacedKey));
|
||||
persistentDataContainerData.persistentDataMap.keySet().forEach(keyString -> {
|
||||
persistentDataContainerData.getTags().forEach(keyString -> {
|
||||
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
||||
if (key != null) {
|
||||
final byte[] data = ArrayUtils.toPrimitive(persistentDataContainerData
|
||||
.persistentDataMap.get(keyString));
|
||||
player.getPersistentDataContainer().set(key, PersistentDataType.BYTE_ARRAY, data);
|
||||
// Set a tag with the given key and value. This is crying out for a refactor.
|
||||
persistentDataContainerData.getTagType(keyString).ifPresentOrElse(dataType -> {
|
||||
switch (dataType) {
|
||||
case BYTE -> persistentDataContainerData.getTagValue(keyString, byte.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.BYTE, value));
|
||||
case SHORT -> persistentDataContainerData.getTagValue(keyString, short.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.SHORT, value));
|
||||
case INTEGER -> persistentDataContainerData.getTagValue(keyString, int.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.INTEGER, value));
|
||||
case LONG -> persistentDataContainerData.getTagValue(keyString, long.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.LONG, value));
|
||||
case FLOAT -> persistentDataContainerData.getTagValue(keyString, float.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.FLOAT, value));
|
||||
case DOUBLE -> persistentDataContainerData.getTagValue(keyString, double.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.DOUBLE, value));
|
||||
case STRING -> persistentDataContainerData.getTagValue(keyString, String.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.STRING, value));
|
||||
case BYTE_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, byte[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.BYTE_ARRAY, value));
|
||||
case INTEGER_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, int[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.INTEGER_ARRAY, value));
|
||||
case LONG_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, long[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.LONG_ARRAY, value));
|
||||
case TAG_CONTAINER ->
|
||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.TAG_CONTAINER, value));
|
||||
case TAG_CONTAINER_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.TAG_CONTAINER_ARRAY, value));
|
||||
}
|
||||
}, () -> BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
||||
" as it has an invalid type. Skipping!"));
|
||||
}
|
||||
});
|
||||
}).exceptionally(throwable -> {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
||||
throwable.printStackTrace();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -465,6 +572,11 @@ public class BukkitPlayer extends OnlineUser {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDead() {
|
||||
return player.getHealth() < 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(@NotNull MineDown mineDown) {
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, mineDown.replace().toComponent());
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.william278.husksync.util;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -20,9 +19,4 @@ public class BukkitResourceReader implements ResourceReader {
|
||||
return Objects.requireNonNull(plugin.getResource(fileName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
3
bukkit/src/main/resources/commodore/enderchest.commodore
Normal file
3
bukkit/src/main/resources/commodore/enderchest.commodore
Normal file
@@ -0,0 +1,3 @@
|
||||
inventory {
|
||||
name brigadier:string single_word;
|
||||
}
|
||||
5
bukkit/src/main/resources/commodore/husksync.commodore
Normal file
5
bukkit/src/main/resources/commodore/husksync.commodore
Normal file
@@ -0,0 +1,5 @@
|
||||
husksync {
|
||||
update;
|
||||
about;
|
||||
reload;
|
||||
}
|
||||
3
bukkit/src/main/resources/commodore/inventory.commodore
Normal file
3
bukkit/src/main/resources/commodore/inventory.commodore
Normal file
@@ -0,0 +1,3 @@
|
||||
enderchest {
|
||||
name brigadier:string single_word;
|
||||
}
|
||||
25
bukkit/src/main/resources/commodore/userdata.commodore
Normal file
25
bukkit/src/main/resources/commodore/userdata.commodore
Normal file
@@ -0,0 +1,25 @@
|
||||
userdata {
|
||||
view {
|
||||
name brigadier:string single_word {
|
||||
version brigadier:string single_word;
|
||||
}
|
||||
}
|
||||
list {
|
||||
name brigadier:string single_word;
|
||||
}
|
||||
delete {
|
||||
name brigadier:string single_word {
|
||||
version brigadier:string single_word;
|
||||
}
|
||||
}
|
||||
restore {
|
||||
name brigadier:string single_word {
|
||||
version brigadier:string single_word;
|
||||
}
|
||||
}
|
||||
pin {
|
||||
name brigadier:string single_word {
|
||||
version brigadier:string single_word;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,10 @@ softdepend:
|
||||
- MysqlPlayerDataBridge
|
||||
- Plan
|
||||
libraries:
|
||||
- 'mysql:mysql-connector-java:8.0.29'
|
||||
- 'org.xerial.snappy:snappy-java:1.1.8.4'
|
||||
- 'dev.dejvokep:boosted-yaml:1.2'
|
||||
- 'redis.clients:jedis:${jedis_version}'
|
||||
- 'mysql:mysql-connector-java:${mysql_driver_version}'
|
||||
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||
|
||||
commands:
|
||||
husksync:
|
||||
usage: '/husksync <update/info/reload/migrate>'
|
||||
|
||||
@@ -2,30 +2,28 @@ dependencies {
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation('redis.clients:jedis:4.2.3') {
|
||||
exclude module: 'slf4j-api'
|
||||
}
|
||||
implementation 'dev.dejvokep:boosted-yaml:1.3'
|
||||
implementation ('com.zaxxer:HikariCP:5.0.1') {
|
||||
exclude module: 'slf4j-api'
|
||||
}
|
||||
|
||||
compileOnly 'dev.dejvokep:boosted-yaml:1.2'
|
||||
compileOnly 'org.xerial.snappy:snappy-java:1.1.8.4'
|
||||
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||
compileOnly 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.4.1690'
|
||||
|
||||
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
|
||||
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
|
||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
||||
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||
relocate 'com.google', 'net.william278.husksync.libraries'
|
||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import net.william278.husksync.util.Version;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -114,6 +115,14 @@ public interface HuskSync {
|
||||
@NotNull
|
||||
Logger getLoggingAdapter();
|
||||
|
||||
/**
|
||||
* Returns the plugin resource file reader
|
||||
*
|
||||
* @return the {@link ResourceReader}
|
||||
*/
|
||||
@NotNull
|
||||
ResourceReader getResourceReader();
|
||||
|
||||
/**
|
||||
* Returns the plugin version
|
||||
*
|
||||
|
||||
@@ -72,7 +72,7 @@ public abstract class BaseHuskSyncAPI {
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (user instanceof OnlineUser) {
|
||||
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter()).join();
|
||||
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join();
|
||||
} else {
|
||||
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
||||
}
|
||||
@@ -103,7 +103,8 @@ public abstract class BaseHuskSyncAPI {
|
||||
* @since 2.0
|
||||
*/
|
||||
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
||||
return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter()).thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
||||
return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings())
|
||||
.thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.william278.husksync.util.UpdateChecker;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
@@ -126,10 +127,13 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull String[] args) {
|
||||
if (args.length <= 1) {
|
||||
return Arrays.stream(COMMAND_ARGUMENTS)
|
||||
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
||||
.sorted().collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void displayPluginInformation(@NotNull OnlineUser player) {
|
||||
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_INFO.node)) {
|
||||
|
||||
@@ -225,7 +225,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
switch (args.length) {
|
||||
case 0, 1 -> {
|
||||
return Arrays.stream(COMMAND_ARGUMENTS)
|
||||
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
||||
.filter(argument -> argument.startsWith(args.length == 1 ? args[0] : ""))
|
||||
.sorted().collect(Collectors.toList());
|
||||
}
|
||||
case 2 -> {
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Locales {
|
||||
[A modern, cross-server player data synchronization system](gray)
|
||||
[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)
|
||||
[• Contributors:](white) [HarvelsX](gray show_text=&7Code), [HookWoods](gray show_text=&7Code)
|
||||
[• Translators:](white) [Namiu](gray show_text=&7\\(うにたろう\\) - Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es), [Melonzio](gray show_text=&7Spanish, es-es), [Ceddix](gray show_text=&7German, de-de), [mateusneresrb](gray show_text=&7Brazilian Portuguese, pt-br], [小蔡](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [DJelly4K](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua), [xF3d3](gray show_text=&7Italian, it-it)
|
||||
[• Translators:](white) [Namiu](gray show_text=&7\\(うにたろう\\) - Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es), [Melonzio](gray show_text=&7Spanish, es-es), [Ceddix](gray show_text=&7German, de-de), [Pukejoy_1](gray show_text=&7Bulgarian, bg-bg), [mateusneresrb](gray show_text=&7Brazilian Portuguese, pt-br], [小蔡](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [DJelly4K](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua), [xF3d3](gray show_text=&7Italian, it-it)
|
||||
[• Documentation:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://william278.net/docs/husksync/Home/)
|
||||
[• Bug reporting:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)
|
||||
[• Discord support:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)""";
|
||||
|
||||
@@ -4,10 +4,7 @@ import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Settings used for the plugin, as read from the config file
|
||||
@@ -17,7 +14,7 @@ public class Settings {
|
||||
/**
|
||||
* Map of {@link ConfigOption}s read from the config file
|
||||
*/
|
||||
private final HashMap<ConfigOption, Object> configOptions;
|
||||
private final Map<ConfigOption, Object> configOptions;
|
||||
|
||||
// Load the settings from the document
|
||||
private Settings(@NotNull YamlDocument config) {
|
||||
@@ -33,6 +30,11 @@ public class Settings {
|
||||
}));
|
||||
}
|
||||
|
||||
// Default constructor for empty settings
|
||||
protected Settings(@NotNull Map<ConfigOption, Object> configOptions) {
|
||||
this.configOptions = configOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the specified {@link ConfigOption}
|
||||
*
|
||||
@@ -144,6 +146,7 @@ public class Settings {
|
||||
SYNCHRONIZATION_SAVE_ON_WORLD_SAVE("synchronization.save_on_world_save", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_COMPRESS_DATA("synchronization.compress_data", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_NETWORK_LATENCY_MILLISECONDS("synchronization.network_latency_milliseconds", OptionType.INTEGER, 500),
|
||||
SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES("synchronization.save_dead_player_inventories", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_INVENTORIES("synchronization.features.inventories", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_ENDER_CHESTS("synchronization.features.ender_chests", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_HEALTH("synchronization.features.health", OptionType.BOOLEAN, true),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the type of a {@link PersistentDataTag}
|
||||
*/
|
||||
public enum BukkitPersistentDataTagType {
|
||||
|
||||
BYTE,
|
||||
SHORT,
|
||||
INTEGER,
|
||||
LONG,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
STRING,
|
||||
BYTE_ARRAY,
|
||||
INTEGER_ARRAY,
|
||||
LONG_ARRAY,
|
||||
TAG_CONTAINER_ARRAY,
|
||||
TAG_CONTAINER;
|
||||
|
||||
|
||||
public static Optional<BukkitPersistentDataTagType> getDataType(@NotNull String typeName) {
|
||||
for (BukkitPersistentDataTagType type : values()) {
|
||||
if (type.name().equalsIgnoreCase(typeName)) {
|
||||
return Optional.of(type);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Store's a user's persistent data container, holding a map of plugin-set persistent values
|
||||
@@ -14,9 +16,9 @@ public class PersistentDataContainerData {
|
||||
* Map of namespaced key strings to a byte array representing the persistent data
|
||||
*/
|
||||
@SerializedName("persistent_data_map")
|
||||
public Map<String, Byte[]> persistentDataMap;
|
||||
protected Map<String, PersistentDataTag<?>> persistentDataMap;
|
||||
|
||||
public PersistentDataContainerData(@NotNull final Map<String, Byte[]> persistentDataMap) {
|
||||
public PersistentDataContainerData(@NotNull final Map<String, PersistentDataTag<?>> persistentDataMap) {
|
||||
this.persistentDataMap = persistentDataMap;
|
||||
}
|
||||
|
||||
@@ -24,4 +26,23 @@ public class PersistentDataContainerData {
|
||||
protected PersistentDataContainerData() {
|
||||
}
|
||||
|
||||
|
||||
public <T> Optional<T> getTagValue(@NotNull final String tagName, @NotNull Class<T> tagClass) {
|
||||
if (persistentDataMap.containsKey(tagName)) {
|
||||
return Optional.of(tagClass.cast(persistentDataMap.get(tagName).value));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<BukkitPersistentDataTagType> getTagType(@NotNull final String tagType) {
|
||||
if (persistentDataMap.containsKey(tagType)) {
|
||||
return BukkitPersistentDataTagType.getDataType(persistentDataMap.get(tagType).type);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Set<String> getTags() {
|
||||
return persistentDataMap.keySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a persistent data tag set by a plugin.
|
||||
*/
|
||||
public class PersistentDataTag<T> {
|
||||
|
||||
/**
|
||||
* The enumerated primitive data type name value of the tag
|
||||
*/
|
||||
protected String type;
|
||||
|
||||
/**
|
||||
* The value of the tag
|
||||
*/
|
||||
public T value;
|
||||
|
||||
public PersistentDataTag(@NotNull BukkitPersistentDataTagType type, @NotNull T value) {
|
||||
this.type = type.name();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private PersistentDataTag() {
|
||||
}
|
||||
|
||||
public Optional<BukkitPersistentDataTagType> getType() {
|
||||
return BukkitPersistentDataTagType.getDataType(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class UserData {
|
||||
* </p>
|
||||
* This value is to be incremented whenever the format changes.
|
||||
*/
|
||||
public static final int CURRENT_FORMAT_VERSION = 1;
|
||||
public static final int CURRENT_FORMAT_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Stores the user's status data, including health, food, etc.
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -108,7 +109,7 @@ public abstract class Database {
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
||||
return formatStatementTables(new String(resourceReader.getResource(schemaFileName)
|
||||
return formatStatementTables(new String(Objects.requireNonNull(resourceReader.getResource(schemaFileName))
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public class MySqlDatabase extends Database {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> ensureUser(@NotNull User user) {
|
||||
return CompletableFuture.runAsync(() -> getUser(user.uuid).thenAccept(optionalUser ->
|
||||
return getUser(user.uuid).thenAccept(optionalUser ->
|
||||
optionalUser.ifPresentOrElse(existingUser -> {
|
||||
if (!existingUser.username.equals(user.username)) {
|
||||
// Update a user's name if it has changed in the database
|
||||
@@ -155,7 +155,7 @@ public class MySqlDatabase extends Database {
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||
}
|
||||
})));
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -152,9 +152,10 @@ public abstract class EventListener {
|
||||
// Handle asynchronous disconnection
|
||||
lockedPlayers.add(user.uuid);
|
||||
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
|
||||
.thenRun(() -> user.getUserData(plugin.getLoggingAdapter()).thenAccept(optionalUserData ->
|
||||
optionalUserData.ifPresent(userData -> plugin.getRedisManager().setUserData(user, userData)
|
||||
.thenRun(() -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
||||
.thenRun(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).thenAccept(
|
||||
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
|
||||
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
||||
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
||||
.thenRun(() -> lockedPlayers.remove(user.uuid)).exceptionally(throwable -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
||||
"An exception occurred handling a player disconnection");
|
||||
@@ -172,7 +173,7 @@ public abstract class EventListener {
|
||||
if (disabling || !plugin.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) {
|
||||
return;
|
||||
}
|
||||
usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter()).join().ifPresent(
|
||||
usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.WORLD_SAVE).join()));
|
||||
}
|
||||
|
||||
@@ -217,7 +218,7 @@ public abstract class EventListener {
|
||||
disabling = true;
|
||||
|
||||
plugin.getOnlineUsers().stream().filter(user -> !lockedPlayers.contains(user.uuid)).forEach(
|
||||
user -> user.getUserData(plugin.getLoggingAdapter()).join().ifPresent(
|
||||
user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()));
|
||||
|
||||
plugin.getDatabase().close();
|
||||
|
||||
@@ -260,16 +260,28 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract void showMenu(@NotNull ItemEditorMenu menu);
|
||||
|
||||
/**
|
||||
* Returns true if the player is dead
|
||||
*
|
||||
* @return true if the player is dead
|
||||
*/
|
||||
public abstract boolean isDead();
|
||||
|
||||
/**
|
||||
* Get the player's current {@link UserData} in an {@link Optional}
|
||||
* </p>
|
||||
* <p>
|
||||
* If the {@code SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES} ConfigOption has been set,
|
||||
* the user's inventory will only be returned if they are alive
|
||||
* <p>
|
||||
* 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
|
||||
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
|
||||
*/
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger) {
|
||||
return CompletableFuture.supplyAsync(() -> Optional.of(new UserData(getStatus().join(), getInventory().join(),
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger, @NotNull Settings settings) {
|
||||
return CompletableFuture.supplyAsync(() -> Optional.of(new UserData(getStatus().join(),
|
||||
(settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES)
|
||||
? getInventory().join() : (isDead() ? new ItemData("") : getInventory().join())),
|
||||
getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(),
|
||||
getStatistics().join(), getLocation().join(), getPersistentDataContainer().join(),
|
||||
getMinecraftVersion().toString())))
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -14,15 +13,8 @@ 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}
|
||||
* @return The resource, read as an {@link InputStream}; or {@code null} if the resource was not found
|
||||
*/
|
||||
@NotNull InputStream getResource(String fileName);
|
||||
|
||||
/**
|
||||
* Gets the plugin data folder where plugin configuration and data are kept
|
||||
*
|
||||
* @return the plugin data directory
|
||||
*/
|
||||
@NotNull File getDataFolder();
|
||||
@Nullable InputStream getResource(String fileName);
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ synchronization:
|
||||
max_user_data_snapshots: 5
|
||||
save_on_world_save: true
|
||||
compress_data: true
|
||||
save_dead_player_inventories: true
|
||||
network_latency_milliseconds: 500
|
||||
features:
|
||||
inventories: true
|
||||
|
||||
31
common/src/main/resources/locales/bg-bg.yml
Normal file
31
common/src/main/resources/locales/bg-bg.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
synchronisation_complete: '[⏵ Данните синхронизирани!](#00fb9a)'
|
||||
synchronisation_failed: '[⏵ Провалихме се да синхронизираме Вашите данни! Моля свържете се с администратор.](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Презаредихме конфигурацията и файловете със съобщения.](#00fb9a)'
|
||||
error_invalid_syntax: '[Грешка:](#ff3300) [Неправилен синтаксис. Използвайте: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Грешка:](#ff3300) [Не можахме да открием играч с това име.](#ff7e5e)'
|
||||
error_no_permission: '[Грешка:](#ff3300) [Нямате право да използвате тази команда](#ff7e5e)'
|
||||
error_console_command_only: '[Грешка:](#ff3300) [Тази команда може да бъде използвана единствено през конзолата](#ff7e5e)'
|
||||
error_in_game_command_only: 'Грешка: Тази команда може да бъде използвана само от играта.'
|
||||
error_no_data_to_display: '[Грешка:](#ff3300) [Не можахме да открием никакви данни за потребителя, които да покажем.](#ff7e5e)'
|
||||
error_invalid_version_uuid: '[Грешка:](#ff3300) [Не можахме да открием никакви потребителски данни за тази версия на това UUID.](#ff7e5e)'
|
||||
inventory_viewer_menu_title: '&0Инвентара на %1%'
|
||||
ender_chest_viewer_menu_title: '&0Ендър Сандъка на %1%'
|
||||
inventory_viewer_opened: '[Преглеждане снапшота на](#00fb9a) [%1%](#00fb9a bold) [ инвентар от ⌚ %2%](#00fb9a)'
|
||||
ender_chest_viewer_opened: '[Преглеждане снапшота на](#00fb9a) [%1%](#00fb9a bold) [ Ендър Сандък от ⌚ %2%](#00fb9a)'
|
||||
data_update_complete: '[🔔 Вашите данни бяха обновени!](#00fb9a)'
|
||||
data_update_failed: '[🔔 Провалихме се да обновим Вашите данни! Моля свържете се с администратор.](#ff7e5e)'
|
||||
data_manager_title: '[Преглеждане потребителският снапшот](#00fb9a) [%1%](#00fb9a show_text=&7Версия на UUID:\\n&8%2%) [за](#00fb9a) [%3%](#00fb9a bold show_text=&7UUID на Играча:\\n&8%4%)[:](#00fb9a)'
|
||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Клеймо на Версията:\\n&8Когато данните са били запазени)'
|
||||
data_manager_pinned: '[※ Закачен снапшот](#d8ff2b show_text=&7Закачен:\\n&8Снапшота на този потребител няма да бъде автоматично завъртан.)'
|
||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Причина на Запазване:\\n&8Какво е накарало данните да бъдат запазени)\\n'
|
||||
data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Точки кръв) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Точки глад) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Ниво опит) [🏹 %5%](dark_aqua show_text=&7Режим на игра)'
|
||||
data_manager_advancements_statistics: '[⭐ Напредъци: %1%](color=#ffc43b-#f5c962 show_text=&7Напредъци, в които имате прогрес:\\n&8%2%) [⌛ Изиграно Време: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Изиграно време в играта\\n&8⚠ Базирано на статистики от играта)'
|
||||
data_manager_item_buttons: '[[🪣 Инвентар…]](color=#a17b5f-#f5b98c show_text=&7Натиснете, за да прегледате run_command=/inventory %1% %2%) [[⌀ Ендър Сандък…]](#b649c4-#d254ff show_text=&7Натиснете, за да проверите run_command=/enderchest %1% %2%)\\n'
|
||||
data_manager_management_buttons: '[Управление:](gray) [[❌ Изтрий…]](#ff3300 show_text=&7Натиснете, за да изтриете този снапшот от потребителски данни.\\n&8Това няма да засегне текущите данни на потребителя.\\n&#ff3300&⚠ Това не може да бъде отменено! run_command=/userdata delete %1% %2%) [[⏪ Възстанови…]](#00fb9a show_text=&7Натиснете, за да възстановите тези потребителски данни.\\n&8Това ще зададе данните на потребителя на този снапшот.\\n&#ff3300&⚠ Текущите данни на %1% ще бъдат пренаписани! run_command=/userdata restore %1% %2%) [[※ Закачи/Откачи…]](#d8ff2b show_text=&7Натиснете, за да закачите или откачите този снапшот с потребителски данни\\n&8Закачените снапшоти няма да бъдат автоматично завъртани run_command=/userdata pin %1% %2%)\\n'
|
||||
data_manager_advancements_preview_remaining: '&7и още %1%…'
|
||||
data_list_title: '[Лист от](#00fb9a) [снапшоти на данните на потребителя](#00fb9a) [%1%](#00fb9a bold show_text=&7UUID: %2%)\\n'
|
||||
data_list_item: '[%1%](gray show_text=&7Снапшот с данни %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Закачен:\\n&8Закачените снапшоти няма да бъдат автоматично завъртани. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Клеймо на Версията:&7\\n&8Когато данните са били запазени run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Версия на UUID:&7\\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Причина на Запазване:\\n&8Какво е накарало данните да бъдат запазени run_command=/userdata view %6% %4%)'
|
||||
data_deleted: '[❌ Успешно изтрихме снапшота с потребителски данни](#00fb9a) [%1%](#00fb9a show_text=&7Версия на UUID:\\n&8%2%) [за](#00fb9a) [%3%.](#00fb9a show_text=&7UUID на Играча:\\n&8%4%)'
|
||||
data_restored: '[⏪ Успешно възстановихме](#00fb9a) [текущите потребителски данни за](#00fb9a) [%1%](#00fb9a show_text=&7UUID на Играча:\\n&8%2%) [от снапшот](#00fb9a) [%3%.](#00fb9a show_text=&7Версия на UUID:\\n&8%4%)'
|
||||
data_pinned: '[※ Успешно закачихме снапшота с потребителски данни](#00fb9a) [%1%](#00fb9a show_text=&7Версия на UUID:\\n&8%2%) [за](#00fb9a) [%3%.](#00fb9a show_text=&7UUID на Играча:\\n&8%4%)'
|
||||
data_unpinned: '[※ Успешно откачихме снапшота с потребителски данни](#00fb9a) [%1%](#00fb9a show_text=&7Версия на UUID:\\n&8%2%) [за](#00fb9a) [%3%.](#00fb9a show_text=&7UUID на Играча:\\n&8%4%)'
|
||||
@@ -1,12 +1,16 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.logger.DummyLogger;
|
||||
import net.william278.husksync.player.DummyPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -19,7 +23,7 @@ public class DataAdaptionTests {
|
||||
public void testJsonDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
final AtomicBoolean isEquals = new AtomicBoolean(false);
|
||||
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyLogger(), DummySettings.get()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
@@ -45,9 +49,9 @@ public class DataAdaptionTests {
|
||||
@Test
|
||||
public void testJsonFormat() {
|
||||
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\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"minecraft_version\":\"1.19-beta123456\",\"format_version\":1}";
|
||||
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\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"minecraft_version\":\"1.19-beta123456\",\"format_version\":2}";
|
||||
AtomicReference<String> json = new AtomicReference<>();
|
||||
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyLogger(), DummySettings.get()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
json.set(new String(data, StandardCharsets.UTF_8));
|
||||
@@ -59,7 +63,7 @@ public class DataAdaptionTests {
|
||||
public void testCompressedDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
AtomicBoolean isEquals = new AtomicBoolean(false);
|
||||
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyLogger(), DummySettings.get()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new CompressedDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
@@ -82,4 +86,40 @@ public class DataAdaptionTests {
|
||||
Assertions.assertTrue(isEquals.get());
|
||||
}
|
||||
|
||||
private String getTestSerializedPersistentDataContainer() {
|
||||
final HashMap<String, PersistentDataTag<?>> persistentDataTest = new HashMap<>();
|
||||
persistentDataTest.put("husksync:byte_test", new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE, 0x01));
|
||||
persistentDataTest.put("husksync:double_test", new PersistentDataTag<>(BukkitPersistentDataTagType.DOUBLE, 2d));
|
||||
persistentDataTest.put("husksync:string_test", new PersistentDataTag<>(BukkitPersistentDataTagType.STRING, "test"));
|
||||
persistentDataTest.put("husksync:int_test", new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER, 3));
|
||||
persistentDataTest.put("husksync:long_test", new PersistentDataTag<>(BukkitPersistentDataTagType.LONG, 4L));
|
||||
persistentDataTest.put("husksync:float_test", new PersistentDataTag<>(BukkitPersistentDataTagType.FLOAT, 5f));
|
||||
persistentDataTest.put("husksync:short_test", new PersistentDataTag<>(BukkitPersistentDataTagType.SHORT, 6));
|
||||
final PersistentDataContainerData persistentDataContainerData = new PersistentDataContainerData(persistentDataTest);
|
||||
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
UserData userData = new UserData();
|
||||
userData.persistentDataContainerData = persistentDataContainerData;
|
||||
return dataAdapter.toJson(userData, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistentDataContainerSerialization() {
|
||||
Assertions.assertEquals(getTestSerializedPersistentDataContainer(), "{\"persistent_data_container\":{\"persistent_data_map\":{\"husksync:int_test\":{\"type\":\"INTEGER\",\"value\":3},\"husksync:string_test\":{\"type\":\"STRING\",\"value\":\"test\"},\"husksync:long_test\":{\"type\":\"LONG\",\"value\":4},\"husksync:byte_test\":{\"type\":\"BYTE\",\"value\":1},\"husksync:short_test\":{\"type\":\"SHORT\",\"value\":6},\"husksync:double_test\":{\"type\":\"DOUBLE\",\"value\":2.0},\"husksync:float_test\":{\"type\":\"FLOAT\",\"value\":5.0}}},\"format_version\":0}");
|
||||
}
|
||||
|
||||
// For testing settings
|
||||
private static class DummySettings extends Settings {
|
||||
private DummySettings(@NotNull Map<ConfigOption, Object> settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
public static DummySettings get() {
|
||||
return new DummySettings(Map.of(
|
||||
ConfigOption.SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES, true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -155,4 +155,9 @@ public class DummyPlayer extends OnlineUser {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDead() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,5 +3,9 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.daemon=true
|
||||
javaVersion=16
|
||||
|
||||
plugin_version=2.0.1
|
||||
plugin_version=2.0.2
|
||||
plugin_archive=husksync
|
||||
|
||||
jedis_version=4.2.3
|
||||
mysql_driver_version=8.0.30
|
||||
snappy_version=1.1.8.4
|
||||
BIN
images/rich-syntax-highlighting.png
Normal file
BIN
images/rich-syntax-highlighting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
Reference in New Issue
Block a user