9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-26 01:59:20 +00:00

Implement variable-sized user data; only save needed data

This commit is contained in:
William
2022-10-07 17:02:26 +01:00
parent b9e474d946
commit cbf5d9c24e
13 changed files with 424 additions and 188 deletions

View File

@@ -1,9 +1,7 @@
package net.william278.husksync.command;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.DataSaveCause;
import net.william278.husksync.data.UserData;
import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.data.*;
import net.william278.husksync.editor.ItemEditorMenu;
import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.player.User;
@@ -58,7 +56,8 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
@NotNull User dataOwner, final boolean allowEdit) {
CompletableFuture.runAsync(() -> {
final UserData data = userDataSnapshot.userData();
final ItemEditorMenu menu = ItemEditorMenu.createEnderChestMenu(data.getEnderChestData(),
final ItemEditorMenu menu = ItemEditorMenu.createEnderChestMenu(
data.getEnderChest().orElse(ItemData.empty()),
dataOwner, player, plugin.getLocales(), allowEdit);
plugin.getLocales().getLocale("viewing_ender_chest_of", dataOwner.username,
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
@@ -68,11 +67,18 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
if (!menu.canEdit) {
return;
}
final UserData updatedUserData = new UserData(data.getStatusData(), data.getInventoryData(),
enderChestDataOnClose, data.getPotionEffectsData(), data.getAdvancementData(),
data.getStatisticsData(), data.getLocationData(),
data.getPersistentDataContainerData(),
plugin.getMinecraftVersion().toString());
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
data.getStatus().ifPresent(builder::setStatus);
data.getInventory().ifPresent(builder::setInventory);
data.getAdvancements().ifPresent(builder::setAdvancements);
data.getLocation().ifPresent(builder::setLocation);
data.getPersistentDataContainer().ifPresent(builder::setPersistentDataContainer);
data.getStatistics().ifPresent(builder::setStatistics);
data.getPotionEffects().ifPresent(builder::setPotionEffects);
builder.setEnderChest(enderChestDataOnClose);
final UserData updatedUserData = builder.build();
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.ENDERCHEST_COMMAND).join();
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
});

View File

@@ -1,9 +1,7 @@
package net.william278.husksync.command;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.DataSaveCause;
import net.william278.husksync.data.UserData;
import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.data.*;
import net.william278.husksync.editor.ItemEditorMenu;
import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.player.User;
@@ -58,7 +56,8 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
@NotNull User dataOwner, boolean allowEdit) {
CompletableFuture.runAsync(() -> {
final UserData data = userDataSnapshot.userData();
final ItemEditorMenu menu = ItemEditorMenu.createInventoryMenu(data.getInventoryData(),
final ItemEditorMenu menu = ItemEditorMenu.createInventoryMenu(
data.getInventory().orElse(ItemData.empty()),
dataOwner, player, plugin.getLocales(), allowEdit);
plugin.getLocales().getLocale("viewing_inventory_of", dataOwner.username,
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
@@ -68,11 +67,18 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
if (!menu.canEdit) {
return;
}
final UserData updatedUserData = new UserData(data.getStatusData(), inventoryDataOnClose,
data.getEnderChestData(), data.getPotionEffectsData(), data.getAdvancementData(),
data.getStatisticsData(), data.getLocationData(),
data.getPersistentDataContainerData(),
plugin.getMinecraftVersion().toString());
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
data.getStatus().ifPresent(builder::setStatus);
data.getEnderChest().ifPresent(builder::setEnderChest);
data.getAdvancements().ifPresent(builder::setAdvancements);
data.getLocation().ifPresent(builder::setLocation);
data.getPersistentDataContainer().ifPresent(builder::setPersistentDataContainer);
data.getStatistics().ifPresent(builder::setStatistics);
data.getPotionEffects().ifPresent(builder::setPotionEffects);
builder.setEnderChest(inventoryDataOnClose);
final UserData updatedUserData = builder.build();
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
});

View File

@@ -14,6 +14,16 @@ public class ItemData {
@SerializedName("serialized_items")
public String serializedItems;
/**
* Get an empty item data object, representing an empty inventory or Ender Chest
*
* @return an empty item data object
*/
@NotNull
public static ItemData empty() {
return new ItemData("");
}
public ItemData(@NotNull final String serializedItems) {
this.serializedItems = serializedItems;
}

View File

@@ -1,6 +1,7 @@
package net.william278.husksync.data;
import com.google.gson.annotations.SerializedName;
import net.william278.desertwell.Version;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -86,12 +87,28 @@ public class UserData {
* Stores the version of the data format being used
*/
@SerializedName("format_version")
protected int formatVersion;
protected int formatVersion = CURRENT_FORMAT_VERSION;
public UserData(@NotNull StatusData statusData, @NotNull ItemData inventoryData,
@NotNull ItemData enderChestData, @NotNull PotionEffectData potionEffectData,
@NotNull List<AdvancementData> advancementData, @NotNull StatisticsData statisticData,
@NotNull LocationData locationData, @NotNull PersistentDataContainerData persistentDataContainerData,
/**
* Create a new {@link UserData} object with the provided data
*
* @param statusData the user's status data ({@link StatusData})
* @param inventoryData the user's inventory data ({@link ItemData})
* @param enderChestData the user's ender chest data ({@link ItemData})
* @param potionEffectData the user's potion effect data ({@link PotionEffectData})
* @param advancementData the user's advancement data ({@link AdvancementData})
* @param statisticData the user's statistic data ({@link StatisticsData})
* @param locationData the user's location data ({@link LocationData})
* @param persistentDataContainerData the user's persistent data container data ({@link PersistentDataContainerData})
* @param minecraftVersion the version of Minecraft this data was generated in (e.g. {@code "1.19.2"})
* @deprecated see {@link #builder(String)} or {@link #builder(Version)} to create a {@link UserDataBuilder}, which
* you can use to {@link UserDataBuilder#build()} a {@link UserData} instance with
*/
@Deprecated(since = "2.1")
public UserData(@Nullable StatusData statusData, @Nullable ItemData inventoryData,
@Nullable ItemData enderChestData, @Nullable PotionEffectData potionEffectData,
@Nullable List<AdvancementData> advancementData, @Nullable StatisticsData statisticData,
@Nullable LocationData locationData, @Nullable PersistentDataContainerData persistentDataContainerData,
@NotNull String minecraftVersion) {
this.statusData = statusData;
this.inventoryData = inventoryData;
@@ -102,7 +119,6 @@ public class UserData {
this.locationData = locationData;
this.persistentDataContainerData = persistentDataContainerData;
this.minecraftVersion = minecraftVersion;
this.formatVersion = CURRENT_FORMAT_VERSION;
}
// Empty constructor to facilitate json serialization
@@ -313,4 +329,29 @@ public class UserData {
return formatVersion;
}
/**
* Get a new {@link UserDataBuilder} for creating {@link UserData}
*
* @param minecraftVersion the version of Minecraft this data was generated in (e.g. {@code "1.19.2"})
* @return a UserData {@link UserDataBuilder} instance
* @since 2.1
*/
@NotNull
public static UserDataBuilder builder(@NotNull String minecraftVersion) {
return new UserDataBuilder(minecraftVersion);
}
/**
* Get a new {@link UserDataBuilder} for creating {@link UserData}
*
* @param minecraftVersion a {@link Version} object, representing the Minecraft version this data was generated in
* @return a UserData {@link UserDataBuilder} instance
* @since 2.1
*/
@NotNull
public static UserDataBuilder builder(@NotNull Version minecraftVersion) {
return builder(minecraftVersion.toStringWithoutMetadata());
}
}

View File

@@ -0,0 +1,140 @@
package net.william278.husksync.data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* A builder utility for creating {@link UserData} instances
*
* @since 2.1
*/
@SuppressWarnings("UnusedReturnValue")
public class UserDataBuilder {
@NotNull
private final UserData userData;
protected UserDataBuilder(@NotNull String minecraftVersion) {
this.userData = new UserData();
this.userData.minecraftVersion = minecraftVersion;
}
/**
* Set the {@link StatusData} to this {@link UserData}
*
* @param status the {@link StatusData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setStatus(@NotNull StatusData status) {
this.userData.statusData = status;
return this;
}
/**
* Set the inventory {@link ItemData} to this {@link UserData}
*
* @param inventoryData the inventory {@link ItemData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setInventory(@Nullable ItemData inventoryData) {
this.userData.inventoryData = inventoryData;
return this;
}
/**
* Set the ender chest {@link ItemData} to this {@link UserData}
*
* @param enderChestData the ender chest {@link ItemData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setEnderChest(@Nullable ItemData enderChestData) {
this.userData.enderChestData = enderChestData;
return this;
}
/**
* Set the {@link List} of {@link ItemData} to this {@link UserData}
*
* @param potionEffectData the {@link List} of {@link ItemData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setPotionEffects(@Nullable PotionEffectData potionEffectData) {
this.userData.potionEffectData = potionEffectData;
return this;
}
/**
* Set the {@link List} of {@link ItemData} to this {@link UserData}
*
* @param advancementData the {@link List} of {@link ItemData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setAdvancements(@Nullable List<AdvancementData> advancementData) {
this.userData.advancementData = advancementData;
return this;
}
/**
* Set the {@link StatisticsData} to this {@link UserData}
*
* @param statisticData the {@link StatisticsData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setStatistics(@Nullable StatisticsData statisticData) {
this.userData.statisticData = statisticData;
return this;
}
/**
* Set the {@link LocationData} to this {@link UserData}
*
* @param locationData the {@link LocationData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setLocation(@Nullable LocationData locationData) {
this.userData.locationData = locationData;
return this;
}
/**
* Set the {@link PersistentDataContainerData} to this {@link UserData}
*
* @param persistentDataContainerData the {@link PersistentDataContainerData} to set
* @return this {@link UserDataBuilder}
* @since 2.1
*/
@NotNull
public UserDataBuilder setPersistentDataContainer(@Nullable PersistentDataContainerData persistentDataContainerData) {
this.userData.persistentDataContainerData = persistentDataContainerData;
return this;
}
/**
* Build and get the {@link UserData} instance
*
* @return the {@link UserData} instance
* @since 2.1
*/
@NotNull
public UserData build() {
return this.userData;
}
}

View File

@@ -80,6 +80,7 @@ public class DataEditor {
*/
public void displayDataOverview(@NotNull OnlineUser user, @NotNull UserDataSnapshot userData,
@NotNull User dataOwner) {
// Title message, timestamp, owner and cause.
locales.getLocale("data_manager_title",
userData.versionUUID().toString().split("-")[0],
userData.versionUUID().toString(),
@@ -95,19 +96,27 @@ public class DataEditor {
locales.getLocale("data_manager_cause",
userData.cause().name().toLowerCase().replaceAll("_", " "))
.ifPresent(user::sendMessage);
locales.getLocale("data_manager_status",
Integer.toString((int) userData.userData().getStatusData().health),
Integer.toString((int) userData.userData().getStatusData().maxHealth),
Integer.toString(userData.userData().getStatusData().hunger),
Integer.toString(userData.userData().getStatusData().expLevel),
userData.userData().getStatusData().gameMode.toLowerCase())
// User status data, if present in the snapshot
userData.userData().getStatus()
.flatMap(statusData -> locales.getLocale("data_manager_status",
Integer.toString((int) statusData.health),
Integer.toString((int) statusData.maxHealth),
Integer.toString(statusData.hunger),
Integer.toString(statusData.expLevel),
statusData.gameMode.toLowerCase()))
.ifPresent(user::sendMessage);
locales.getLocale("data_manager_advancements_statistics",
Integer.toString(userData.userData().getAdvancementData().size()),
generateAdvancementPreview(userData.userData().getAdvancementData()),
String.format("%.2f", (((userData.userData().getStatisticsData().untypedStatistics.getOrDefault(
"PLAY_ONE_MINUTE", 0)) / 20d) / 60d) / 60d))
// Advancement and statistic data, if both are present in the snapshot
userData.userData().getAdvancements()
.flatMap(advancementData -> userData.userData().getStatistics()
.flatMap(statisticsData -> locales.getLocale("data_manager_advancements_statistics",
Integer.toString(advancementData.size()),
generateAdvancementPreview(advancementData),
String.format("%.2f", (((statisticsData.untypedStatistics.getOrDefault(
"PLAY_ONE_MINUTE", 0)) / 20d) / 60d) / 60d))))
.ifPresent(user::sendMessage);
if (user.hasPermission(Permission.COMMAND_INVENTORY.node)
&& user.hasPermission(Permission.COMMAND_ENDER_CHEST.node)) {
locales.getLocale("data_manager_item_buttons",

View File

@@ -10,7 +10,6 @@ import com.djrapitops.plan.extension.icon.Family;
import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.extension.table.TableColumnFormat;
import net.william278.husksync.data.StatusData;
import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.database.Database;
import net.william278.husksync.player.User;
@@ -114,9 +113,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public String getCurrentDataId(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> versionedUserData.versionUUID().toString()
.split(Pattern.quote("-"))[0])
return getCurrentUserData(uuid).join()
.map(versionedUserData -> versionedUserData.versionUUID().toString()
.split(Pattern.quote("-"))[0])
.orElse(UNKNOWN_STRING);
}
@@ -130,11 +129,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public String getHealth(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> {
final StatusData statusData = versionedUserData.userData().getStatusData();
return (int) statusData.health + "/" + (int) statusData.maxHealth;
})
return getCurrentUserData(uuid).join()
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
.map(statusData -> (int) statusData.health + "/" + (int) statusData.maxHealth)
.orElse(UNKNOWN_STRING);
}
@@ -148,8 +145,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public long getHunger(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> (long) versionedUserData.userData().getStatusData().hunger)
return getCurrentUserData(uuid).join()
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
.map(statusData -> (long) statusData.hunger)
.orElse(0L);
}
@@ -163,8 +161,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public long getExperienceLevel(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> (long) versionedUserData.userData().getStatusData().expLevel)
return getCurrentUserData(uuid).join()
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
.map(statusData -> (long) statusData.expLevel)
.orElse(0L);
}
@@ -178,8 +177,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public String getGameMode(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> versionedUserData.userData().getStatusData().gameMode.toLowerCase())
return getCurrentUserData(uuid).join()
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
.map(status -> status.gameMode)
.orElse(UNKNOWN_STRING);
}
@@ -192,8 +192,9 @@ public class PlanDataExtension implements DataExtension {
)
@Tab("Current Status")
public long getAdvancementsCompleted(@NotNull UUID playerUUID) {
return getCurrentUserData(playerUUID).join().map(
versionedUserData -> (long) versionedUserData.userData().getAdvancementData().size())
return getCurrentUserData(playerUUID).join()
.flatMap(versionedUserData -> versionedUserData.userData().getAdvancements())
.map(advancementsData -> (long) advancementsData.size())
.orElse(0L);
}
@@ -201,7 +202,7 @@ public class PlanDataExtension implements DataExtension {
@TableProvider(tableColor = Color.LIGHT_BLUE)
@Tab("Data Snapshots")
public Table getDataSnapshots(@NotNull UUID playerUUID) {
Table.Factory dataSnapshotsTable = Table.builder()
final Table.Factory dataSnapshotsTable = Table.builder()
.columnOne("Time", new Icon(Family.SOLID, "clock", Color.NONE))
.columnOneFormat(TableColumnFormat.DATE_SECOND)
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))

View File

@@ -164,73 +164,6 @@ public abstract class OnlineUser extends User {
@NotNull
public abstract Version getMinecraftVersion();
/**
* Set {@link UserData} to a player
*
* @param data The data to set
* @param settings Plugin settings, for determining what needs setting
* @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,
@NotNull EventCannon eventCannon, @NotNull Logger logger,
@NotNull Version serverMinecraftVersion) {
return CompletableFuture.supplyAsync(() -> {
// Prevent synchronising user data from newer versions of Minecraft
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) {
logger.log(Level.SEVERE, "Cannot set data for " + username +
" because the Minecraft version of their user data (" + data.getMinecraftVersion() +
") is newer than the server's Minecraft version (" + serverMinecraftVersion + ").");
return false;
}
// Prevent synchronising user data from newer versions of the plugin
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
logger.log(Level.SEVERE, "Cannot set data for " + username +
" because the format version of their user data (v" + data.getFormatVersion() +
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
return false;
}
// Fire the PreSyncEvent
final PreSyncEvent preSyncEvent = (PreSyncEvent) eventCannon.firePreSyncEvent(this, data).join();
final UserData finalData = preSyncEvent.getUserData();
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
if (!isOffline() && !preSyncEvent.isCancelled()) {
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
add(setInventory(finalData.getInventoryData()));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
add(setEnderChest(finalData.getEnderChestData()));
}
add(setStatus(finalData.getStatusData(), StatusDataFlag.getFromSettings(settings)));
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
add(setPotionEffects(finalData.getPotionEffectsData()));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
add(setAdvancements(finalData.getAdvancementData()));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
add(setStatistics(finalData.getStatisticsData()));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
add(setLocation(finalData.getLocationData()));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
add(setPersistentDataContainer(finalData.getPersistentDataContainerData()));
}
}
}};
// Apply operations in parallel, join when complete
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
.exceptionally(exception -> {
// Handle synchronisation exceptions
logger.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace();
return false;
}).join();
});
}
/**
* Dispatch a MineDown-formatted message to this player
*
@@ -268,10 +201,88 @@ public abstract class OnlineUser extends User {
public abstract boolean isDead();
/**
* Get the player's current {@link UserData} in an {@link Optional}
* Apply {@link UserData} to a player, updating their inventory, status, statistics, etc. as per the config.
* <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
* 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.
*
* @param data The {@link UserData} to set to the player
* @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}.
*/
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull Settings settings,
@NotNull EventCannon eventCannon, @NotNull Logger logger,
@NotNull Version serverMinecraftVersion) {
return CompletableFuture.supplyAsync(() -> {
// Prevent synchronising user data from newer versions of Minecraft
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) {
logger.log(Level.SEVERE, "Cannot set data for " + username +
" because the Minecraft version of their user data (" + data.getMinecraftVersion() +
") is newer than the server's Minecraft version (" + serverMinecraftVersion + ").");
return false;
}
// Prevent synchronising user data from newer versions of the plugin
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
logger.log(Level.SEVERE, "Cannot set data for " + username +
" because the format version of their user data (v" + data.getFormatVersion() +
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
return false;
}
// Fire the PreSyncEvent
final PreSyncEvent preSyncEvent = (PreSyncEvent) eventCannon.firePreSyncEvent(this, data).join();
final UserData finalData = preSyncEvent.getUserData();
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
if (!isOffline() && !preSyncEvent.isCancelled()) {
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData)));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
finalData.getEnderChest().ifPresent(itemData -> add(setEnderChest(itemData)));
}
finalData.getStatus().ifPresent(statusData -> add(setStatus(statusData,
StatusDataFlag.getFromSettings(settings))));
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
finalData.getPotionEffects().ifPresent(potionEffectData -> add(setPotionEffects(potionEffectData)));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
finalData.getAdvancements().ifPresent(advancementData -> add(setAdvancements(advancementData)));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
finalData.getStatistics().ifPresent(statisticData -> add(setStatistics(statisticData)));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
finalData.getLocation().ifPresent(locationData -> add(setLocation(locationData)));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
finalData.getPersistentDataContainer().ifPresent(persistentDataContainerData ->
add(setPersistentDataContainer(persistentDataContainerData)));
}
}
}};
// Apply operations in parallel, join when complete
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
.exceptionally(exception -> {
// Handle synchronisation exceptions
logger.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace();
return false;
}).join();
});
}
/**
* Get the player's current {@link UserData} in an {@link Optional}.
* <p>
* Since v2.1, this method will respect the data synchronisation settings; user data will only be as big as the
* enabled synchronisation values set in the config file
* <p>
* Also note that if the {@code SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES} ConfigOption has been set,
* the user's inventory will only be returned if the player is alive.
* <p>
* If the user data could not be returned due to an exception, the optional will return empty
*
@@ -279,12 +290,43 @@ public abstract class OnlineUser extends User {
* @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) {
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())))
return CompletableFuture.supplyAsync(() -> {
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
if (!isOffline()) {
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
if (isDead() && settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES)) {
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
} else {
add(getInventory().thenAccept(builder::setInventory));
}
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
add(getEnderChest().thenAccept(builder::setEnderChest));
}
add(getStatus().thenAccept(builder::setStatus));
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
add(getPotionEffects().thenAccept(builder::setPotionEffects));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
add(getAdvancements().thenAccept(builder::setAdvancements));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
add(getStatistics().thenAccept(builder::setStatistics));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
add(getLocation().thenAccept(builder::setLocation));
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
add(getPersistentDataContainer().thenAccept(builder::setPersistentDataContainer));
}
}
}};
// Apply operations in parallel, join when complete
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
return Optional.of(builder.build());
})
.exceptionally(exception -> {
logger.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace();