forked from public-mirrors/HuskSync
Events & API work, save DataSaveCauses as part of versioning
parent
fd08a3e7d0
commit
1c9d74f925
@ -1,4 +1,181 @@
|
||||
package net.william278.husksync.api;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.data.*;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* The HuskSync API for the Bukkit platform, providing methods to access and modify player {@link UserData} held by {@link User}s.
|
||||
* </p>
|
||||
* Retrieve an instance of the API class via {@link #getInstance()}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class HuskSyncAPI {
|
||||
|
||||
/**
|
||||
* <b>(Internal use only)</b> - Instance of the API class.
|
||||
*/
|
||||
private static final HuskSyncAPI INSTANCE = new HuskSyncAPI();
|
||||
/**
|
||||
* <b>(Internal use only)</b> - Instance of the implementing plugin.
|
||||
*/
|
||||
private static final BukkitHuskSync PLUGIN = BukkitHuskSync.getInstance();
|
||||
|
||||
/**
|
||||
* <b>(Internal use only)</b> - Constructor.
|
||||
*/
|
||||
private HuskSyncAPI() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint to the HuskSync API - returns an instance of the API
|
||||
*
|
||||
* @return instance of the HuskSync API
|
||||
*/
|
||||
public static @NotNull HuskSyncAPI getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link User} instance for the given bukkit {@link Player}.
|
||||
*
|
||||
* @param player the bukkit player to get the {@link User} instance for
|
||||
* @return the {@link User} instance for the given bukkit player
|
||||
*/
|
||||
@NotNull
|
||||
public OnlineUser getUser(@NotNull Player player) {
|
||||
return BukkitPlayer.adapt(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link User} by the given player's account {@link UUID}, if they exist.
|
||||
*
|
||||
* @param uuid the unique id of the player to get the {@link User} instance for
|
||||
* @return future returning the {@link User} instance for the given player's unique id if they exist, otherwise an empty {@link Optional}
|
||||
* @apiNote The player does not have to be online
|
||||
*/
|
||||
public CompletableFuture<Optional<User>> getUser(@NotNull UUID uuid) {
|
||||
return PLUGIN.getDatabase().getUser(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link User} by the given player's username (case-insensitive), if they exist.
|
||||
*
|
||||
* @param username the username of the {@link User} instance for
|
||||
* @return future returning the {@link User} instance for the given player's username if they exist, otherwise an empty {@link Optional}
|
||||
* @apiNote The player does not have to be online, though their username has to be the username
|
||||
* they had when they last joined the server.
|
||||
*/
|
||||
public CompletableFuture<Optional<User>> getUser(@NotNull String username) {
|
||||
return PLUGIN.getDatabase().getUserByName(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link User}'s current {@link UserData}
|
||||
*
|
||||
* @param user the {@link User} to get the {@link UserData} for
|
||||
* @return future returning the {@link UserData} for the given {@link User} if they exist, otherwise an empty {@link Optional}
|
||||
* @apiNote If the user is not online on the implementing bukkit server,
|
||||
* the {@link UserData} returned will be their last database-saved UserData.</p>
|
||||
* If the user happens to be online on another server on the network,
|
||||
* then the {@link UserData} returned here may not be reflective of their actual current UserData.
|
||||
*/
|
||||
public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (user instanceof OnlineUser) {
|
||||
return Optional.of(((OnlineUser) user).getUserData().join());
|
||||
} else {
|
||||
return PLUGIN.getDatabase().getCurrentUserData(user).join().map(VersionedUserData::userData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the saved {@link VersionedUserData} records for the given {@link User}
|
||||
*
|
||||
* @param user the {@link User} to get the {@link VersionedUserData} for
|
||||
* @return future returning a list {@link VersionedUserData} for the given {@link User} if they exist,
|
||||
* otherwise an empty {@link Optional}
|
||||
* @apiNote The length of the list of VersionedUserData will correspond to the configured
|
||||
* {@code max_user_data_records} config option
|
||||
*/
|
||||
public CompletableFuture<List<VersionedUserData>> getSavedUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> PLUGIN.getDatabase().getUserData(user).join());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON string representation of the given {@link UserData}
|
||||
*
|
||||
* @param userData the {@link UserData} to get the JSON string representation of
|
||||
* @param prettyPrint whether to pretty print the JSON string
|
||||
* @return the JSON string representation of the given {@link UserData}
|
||||
*/
|
||||
@NotNull
|
||||
public String getUserDataJson(@NotNull UserData userData, boolean prettyPrint) {
|
||||
return PLUGIN.getDataAdapter().toJson(userData, prettyPrint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link BukkitInventoryMap} for the given {@link User}, containing their current inventory item data
|
||||
*
|
||||
* @param user the {@link User} to get the {@link BukkitInventoryMap} for
|
||||
* @return future returning the {@link BukkitInventoryMap} for the given {@link User} if they exist,
|
||||
* otherwise an empty {@link Optional}
|
||||
*/
|
||||
public CompletableFuture<Optional<BukkitInventoryMap>> getPlayerInventory(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||
.map(userData -> BukkitSerializer.deserializeInventory(userData
|
||||
.getInventoryData().serializedItems).join()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ItemStack}s array contents of the given {@link User}'s Ender Chest data
|
||||
*
|
||||
* @param user the {@link User} to get the Ender Chest contents of
|
||||
* @return future returning the {@link ItemStack} array of Ender Chest items for the user if they exist,
|
||||
* otherwise an empty {@link Optional}
|
||||
*/
|
||||
public CompletableFuture<Optional<ItemStack[]>> getPlayerEnderChest(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||
.map(userData -> BukkitSerializer.deserializeItemStackArray(userData
|
||||
.getEnderChestData().serializedItems).join()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Base-64 encoded inventory array string into a {@link ItemStack} array.
|
||||
*
|
||||
* @param serializedItemStackArray The Base-64 encoded inventory array string.
|
||||
* @return The deserialized {@link ItemStack} array.
|
||||
* @throws DataDeserializationException If an error occurs during deserialization.
|
||||
*/
|
||||
public CompletableFuture<ItemStack[]> deserializeItemStackArray(@NotNull String serializedItemStackArray)
|
||||
throws DataDeserializationException {
|
||||
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
||||
.deserializeItemStackArray(serializedItemStackArray).join());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Base-64 encoded potion effect array string into a {@link PotionEffect} array.
|
||||
*
|
||||
* @param serializedPotionEffectArray The Base-64 encoded potion effect array string.
|
||||
* @return The deserialized {@link PotionEffect} array.
|
||||
* @throws DataDeserializationException If an error occurs during deserialization.
|
||||
*/
|
||||
public CompletableFuture<PotionEffect[]> deserializePotionEffectArray(@NotNull String serializedPotionEffectArray)
|
||||
throws DataDeserializationException {
|
||||
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
||||
.deserializePotionEffects(serializedPotionEffectArray).join());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A mapped player inventory, providing methods to easily access a player's inventory.
|
||||
*/
|
||||
public class BukkitInventoryMap {
|
||||
|
||||
private ItemStack[] contents;
|
||||
|
||||
/**
|
||||
* Creates a new mapped inventory from the given contents.
|
||||
*
|
||||
* @param contents the contents of the inventory
|
||||
*/
|
||||
protected BukkitInventoryMap(ItemStack[] contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the inventory.
|
||||
*
|
||||
* @return the contents of the inventory
|
||||
*/
|
||||
public ItemStack[] getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the contents of the inventory.
|
||||
*
|
||||
* @param contents the contents of the inventory
|
||||
*/
|
||||
public void setContents(ItemStack[] contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the inventory.
|
||||
*
|
||||
* @return the size of the inventory
|
||||
*/
|
||||
public int getSize() {
|
||||
return contents.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item at the given index.
|
||||
*
|
||||
* @param index the index of the item to get
|
||||
* @return the item at the given index
|
||||
*/
|
||||
public Optional<ItemStack> getItemAt(int index) {
|
||||
if (contents.length >= index) {
|
||||
if (contents[index] == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(contents[index]);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item at the given index.
|
||||
*
|
||||
* @param itemStack the item to set at the given index
|
||||
* @param index the index of the item to set
|
||||
* @throws IllegalArgumentException if the index is out of bounds
|
||||
*/
|
||||
public void setItemAt(@NotNull ItemStack itemStack, int index) throws IllegalArgumentException {
|
||||
contents[index] = itemStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main inventory contents.
|
||||
*
|
||||
* @return the main inventory contents
|
||||
*/
|
||||
public ItemStack[] getInventory() {
|
||||
final ItemStack[] inventory = new ItemStack[36];
|
||||
System.arraycopy(contents, 0, inventory, 0, Math.min(contents.length, inventory.length));
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public ItemStack[] getHotbar() {
|
||||
final ItemStack[] armor = new ItemStack[9];
|
||||
for (int i = 0; i <= 9; i++) {
|
||||
armor[i] = getItemAt(i).orElse(null);
|
||||
}
|
||||
return armor;
|
||||
}
|
||||
|
||||
public Optional<ItemStack> getOffHand() {
|
||||
return getItemAt(40);
|
||||
}
|
||||
|
||||
public Optional<ItemStack> getHelmet() {
|
||||
return getItemAt(39);
|
||||
}
|
||||
|
||||
public Optional<ItemStack> getChestplate() {
|
||||
return getItemAt(38);
|
||||
}
|
||||
|
||||
public Optional<ItemStack> getLeggings() {
|
||||
return getItemAt(37);
|
||||
}
|
||||
|
||||
public Optional<ItemStack> getBoots() {
|
||||
return getItemAt(36);
|
||||
}
|
||||
|
||||
public ItemStack[] getArmor() {
|
||||
final ItemStack[] armor = new ItemStack[4];
|
||||
for (int i = 36; i < 40; i++) {
|
||||
armor[i - 36] = getItemAt(i).orElse(null);
|
||||
}
|
||||
return armor;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitDataSavePlayerEvent extends BukkitEvent implements DataSaveEvent, Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
private UserData userData;
|
||||
private final User user;
|
||||
private final DataSaveCause saveCause;
|
||||
|
||||
protected BukkitDataSavePlayerEvent(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull DataSaveCause saveCause) {
|
||||
this.user = user;
|
||||
this.userData = userData;
|
||||
this.saveCause = saveCause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UserData getUserData() {
|
||||
return userData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserData(@NotNull UserData userData) {
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DataSaveCause getSaveCause() {
|
||||
return saveCause;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
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.Event;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class BukkitEvent extends Event implements net.william278.husksync.event.Event {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<net.william278.husksync.event.Event> fire() {
|
||||
final CompletableFuture<net.william278.husksync.event.Event> eventFireFuture = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getServer().getPluginManager().callEvent(this);
|
||||
eventFireFuture.complete(this);
|
||||
});
|
||||
return eventFireFuture;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitEventCannon extends EventCannon {
|
||||
|
||||
public BukkitEventCannon() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Event> firePreSyncEvent(@NotNull OnlineUser user, @NotNull UserData userData) {
|
||||
return new BukkitPreSyncEvent(((BukkitPlayer) user).getPlayer(), userData).fire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull DataSaveCause saveCause) {
|
||||
return new BukkitDataSavePlayerEvent(user, userData, saveCause).fire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireSyncCompleteEvent(@NotNull OnlineUser user) {
|
||||
new BukkitSyncCompletePlayerEvent(((BukkitPlayer) user).getPlayer()).fire();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class BukkitPlayerEvent extends org.bukkit.event.player.PlayerEvent implements PlayerEvent {
|
||||
|
||||
|
||||
public BukkitPlayerEvent(@NotNull Player who) {
|
||||
super(who);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OnlineUser getUser() {
|
||||
return BukkitPlayer.adapt(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Event> fire() {
|
||||
final CompletableFuture<Event> eventFireFuture = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getServer().getPluginManager().callEvent(this);
|
||||
eventFireFuture.complete(this);
|
||||
});
|
||||
return eventFireFuture;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitPreSyncEvent extends BukkitPlayerEvent implements PreSyncEvent, Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
private UserData userData;
|
||||
|
||||
protected BukkitPreSyncEvent(@NotNull Player player, @NotNull UserData userData) {
|
||||
super(player);
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OnlineUser getUser() {
|
||||
return BukkitPlayer.adapt(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UserData getUserData() {
|
||||
return userData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserData(@NotNull UserData userData) {
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitSyncCompletePlayerEvent extends BukkitPlayerEvent implements SyncCompleteEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
protected BukkitSyncCompletePlayerEvent(@NotNull Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OnlineUser getUser() {
|
||||
return BukkitPlayer.adapt(player);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Identifies the cause of a player data save.
|
||||
*
|
||||
* @implNote This enum is saved in the database. Cause names have a max length of 32 characters.
|
||||
*/
|
||||
public enum DataSaveCause {
|
||||
|
||||
/**
|
||||
* Indicates data saved when a player disconnected from the server (either to change servers, or to log off)
|
||||
*/
|
||||
DISCONNECT,
|
||||
/**
|
||||
* Indicates data saved when the world saved
|
||||
*/
|
||||
WORLD_SAVE,
|
||||
/**
|
||||
* Indicates data saved when the server shut down
|
||||
*/
|
||||
SERVER_SHUTDOWN,
|
||||
/**
|
||||
* Indicates data was saved by editing inventory contents via the {@code /invsee} command
|
||||
*/
|
||||
INVSEE_COMMAND_EDIT,
|
||||
/**
|
||||
* Indicates data was saved by editing Ender Chest contents via the {@code /echest} command
|
||||
*/
|
||||
ECHEST_COMMAND_EDIT,
|
||||
/**
|
||||
* Indicates data was saved by an API call
|
||||
*/
|
||||
API,
|
||||
/**
|
||||
* Indicates data was saved by an unknown cause.
|
||||
* </p>
|
||||
* This should not be used and is only used for error handling purposes.
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
@NotNull
|
||||
public static DataSaveCause getCauseByName(@NotNull String name) {
|
||||
for (DataSaveCause cause : values()) {
|
||||
if (cause.name().equalsIgnoreCase(name)) {
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Stores information about a player's inventory or ender chest
|
||||
*/
|
||||
public class InventoryData {
|
||||
|
||||
/**
|
||||
* A base64 string of platform-serialized inventory data
|
||||
*/
|
||||
@SerializedName("serialized_inventory")
|
||||
public String serializedInventory;
|
||||
|
||||
public InventoryData(@NotNull final String serializedInventory) {
|
||||
this.serializedInventory = serializedInventory;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected InventoryData() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Stores information about the contents of a player's inventory or Ender Chest.
|
||||
*/
|
||||
public class ItemData {
|
||||
|
||||
/**
|
||||
* A Base-64 string of platform-serialized items
|
||||
*/
|
||||
@SerializedName("serialized_items")
|
||||
public String serializedItems;
|
||||
|
||||
public ItemData(@NotNull final String serializedItems) {
|
||||
this.serializedItems = serializedItems;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected ItemData() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Flags for setting {@link StatusData}, indicating which elements should be synced
|
||||
*/
|
||||
public enum StatusDataFlag {
|
||||
|
||||
SET_HEALTH(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HEALTH),
|
||||
SET_MAX_HEALTH(Settings.ConfigOption.SYNCHRONIZATION_SYNC_MAX_HEALTH),
|
||||
SET_HUNGER(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HUNGER),
|
||||
SET_EXPERIENCE(Settings.ConfigOption.SYNCHRONIZATION_SYNC_EXPERIENCE),
|
||||
SET_GAME_MODE(Settings.ConfigOption.SYNCHRONIZATION_SYNC_GAME_MODE),
|
||||
SET_FLYING(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION),
|
||||
SET_SELECTED_ITEM_SLOT(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES);
|
||||
|
||||
private final Settings.ConfigOption configOption;
|
||||
|
||||
StatusDataFlag(@NotNull Settings.ConfigOption configOption) {
|
||||
this.configOption = configOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all status data flags
|
||||
*
|
||||
* @return all status data flags as a list
|
||||
*/
|
||||
@NotNull
|
||||
@SuppressWarnings("unused")
|
||||
public static List<StatusDataFlag> getAll() {
|
||||
return Arrays.stream(StatusDataFlag.values()).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all status data flags that are enabled for setting as per the {@link Settings}
|
||||
*
|
||||
* @param settings the settings to use for determining which flags are enabled
|
||||
* @return all status data flags that are enabled for setting
|
||||
*/
|
||||
@NotNull
|
||||
public static List<StatusDataFlag> getFromSettings(@NotNull Settings settings) {
|
||||
return Arrays.stream(StatusDataFlag.values()).filter(
|
||||
flag -> settings.getBooleanValue(flag.configOption)).toList();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
public interface CancellableEvent extends Event {
|
||||
|
||||
boolean isCancelled();
|
||||
|
||||
void setCancelled(boolean cancelled);
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface DataSaveEvent extends CancellableEvent {
|
||||
|
||||
@NotNull
|
||||
UserData getUserData();
|
||||
|
||||
void setUserData(@NotNull UserData userData);
|
||||
|
||||
@NotNull User getUser();
|
||||
|
||||
@NotNull
|
||||
DataSaveCause getSaveCause();
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface Event {
|
||||
|
||||
CompletableFuture<Event> fire();
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class EventCannon {
|
||||
|
||||
protected EventCannon() {
|
||||
}
|
||||
|
||||
public abstract CompletableFuture<Event> firePreSyncEvent(@NotNull OnlineUser user, @NotNull UserData userData);
|
||||
|
||||
public abstract CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull DataSaveCause saveCause);
|
||||
|
||||
public abstract void fireSyncCompleteEvent(@NotNull OnlineUser user);
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
|
||||
public interface PlayerEvent extends Event {
|
||||
|
||||
OnlineUser getUser();
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.UserData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface PreSyncEvent extends CancellableEvent {
|
||||
|
||||
@NotNull
|
||||
UserData getUserData();
|
||||
|
||||
void setUserData(@NotNull UserData userData);
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package net.william278.husksync.event;
|
||||
|
||||
public interface SyncCompleteEvent extends PlayerEvent {
|
||||
|
||||
}
|
@ -1,20 +1,34 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* An abstract, cross-platform representation of a logger
|
||||
*/
|
||||
public interface Logger {
|
||||
public abstract class Logger {
|
||||
|
||||
private boolean debug;
|
||||
|
||||
public abstract void log(@NotNull Level level, @NotNull String message, @NotNull Exception e);
|
||||
|
||||
public abstract void log(@NotNull Level level, @NotNull String message);
|
||||
|
||||
void log(Level level, String message, Exception e);
|
||||
public abstract void info(@NotNull String message);
|
||||
|
||||
void log(Level level, String message);
|
||||
public abstract void severe(@NotNull String message);
|
||||
|
||||
void info(String message);
|
||||
public final void debug(@NotNull String message) {
|
||||
if (debug) {
|
||||
log(Level.INFO, "[DEBUG] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
void severe(String message);
|
||||
public abstract void config(@NotNull String message);
|
||||
|
||||
void config(String message);
|
||||
public final void showDebugLogs(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.player.DummyPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class DataAdaptionTests {
|
||||
|
||||
@Test
|
||||
public void testJsonDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
final UserData dummyUserData = dummyUser.getUserData().join();
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
|
||||
boolean isEquals = deserializedUserData.getInventoryData().serializedItems
|
||||
.equals(dummyUserData.getInventoryData().serializedItems)
|
||||
&& deserializedUserData.getEnderChestData().serializedItems
|
||||
.equals(dummyUserData.getEnderChestData().serializedItems)
|
||||
&& deserializedUserData.getPotionEffectsData().serializedPotionEffects
|
||||
.equals(dummyUserData.getPotionEffectsData().serializedPotionEffects)
|
||||
&& deserializedUserData.getStatusData().health == dummyUserData.getStatusData().health
|
||||
&& deserializedUserData.getStatusData().hunger == dummyUserData.getStatusData().hunger
|
||||
&& deserializedUserData.getStatusData().saturation == dummyUserData.getStatusData().saturation
|
||||
&& deserializedUserData.getStatusData().saturationExhaustion == dummyUserData.getStatusData().saturationExhaustion
|
||||
&& deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot
|
||||
&& deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience
|
||||
&& deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth
|
||||
&& deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale;
|
||||
|
||||
Assertions.assertTrue(isEquals);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonFormat() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
final UserData dummyUserData = dummyUser.getUserData().join();
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final String json = new String(data, StandardCharsets.UTF_8);
|
||||
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_inventory\":\"\"},\"ender_chest\":{\"serialized_inventory\":\"\"},\"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\":{}},\"format_version\":1}";
|
||||
Assertions.assertEquals(expectedJson, json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompressedDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
final UserData dummyUserData = dummyUser.getUserData().join();
|
||||
final DataAdapter dataAdapter = new CompressedDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
|
||||
boolean isEquals = deserializedUserData.getInventoryData().serializedItems
|
||||
.equals(dummyUserData.getInventoryData().serializedItems)
|
||||
&& deserializedUserData.getEnderChestData().serializedItems
|
||||
.equals(dummyUserData.getEnderChestData().serializedItems)
|
||||
&& deserializedUserData.getPotionEffectsData().serializedPotionEffects
|
||||
.equals(dummyUserData.getPotionEffectsData().serializedPotionEffects)
|
||||
&& deserializedUserData.getStatusData().health == dummyUserData.getStatusData().health
|
||||
&& deserializedUserData.getStatusData().hunger == dummyUserData.getStatusData().hunger
|
||||
&& deserializedUserData.getStatusData().saturation == dummyUserData.getStatusData().saturation
|
||||
&& deserializedUserData.getStatusData().saturationExhaustion == dummyUserData.getStatusData().saturationExhaustion
|
||||
&& deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot
|
||||
&& deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience
|
||||
&& deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth
|
||||
&& deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale;
|
||||
|
||||
Assertions.assertTrue(isEquals);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package net.william278.husksync.player;
|
||||
|
||||
import de.themoep.minedown.MineDown;
|
||||
import net.william278.husksync.data.*;
|
||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DummyPlayer extends OnlineUser {
|
||||
|
||||
private DummyPlayer(@NotNull UUID uuid, @NotNull String username) {
|
||||
super(uuid, username);
|
||||
}
|
||||
|
||||
public static DummyPlayer create() {
|
||||
return new DummyPlayer(UUID.fromString("00000000-0000-0000-0000-000000000000"),
|
||||
"DummyPlayer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<StatusData> getStatus() {
|
||||
return CompletableFuture.supplyAsync(() -> new StatusData(20, 20, 0,
|
||||
20, 5, 5, 1,
|
||||
100, 1, 1f, "SURVIVAL", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull List<StatusDataFlag> statusDataFlags) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ItemData> getInventory() {
|
||||
return CompletableFuture.supplyAsync(() -> new ItemData(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setInventory(@NotNull ItemData itemData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ItemData> getEnderChest() {
|
||||
return CompletableFuture.supplyAsync(() -> new ItemData(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setEnderChest(@NotNull ItemData enderChestData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<PotionEffectData> getPotionEffects() {
|
||||
return CompletableFuture.supplyAsync(() -> new PotionEffectData(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setPotionEffects(@NotNull PotionEffectData potionEffectData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<AdvancementData>> getAdvancements() {
|
||||
return CompletableFuture.supplyAsync(ArrayList::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<StatisticsData> getStatistics() {
|
||||
return CompletableFuture.supplyAsync(() -> new StatisticsData(new HashMap<>(),
|
||||
new HashMap<>(), new HashMap<>(), new HashMap<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LocationData> getLocation() {
|
||||
return CompletableFuture.supplyAsync(() -> new LocationData("dummy_world",
|
||||
UUID.fromString("00000000-0000-0000-0000-000000000000"),
|
||||
"NORMAL", 0, 64, 0, 90f, 180f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setLocation(@NotNull LocationData locationData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<PersistentDataContainerData> getPersistentDataContainer() {
|
||||
return CompletableFuture.supplyAsync(() -> new PersistentDataContainerData(new HashMap<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDead() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOffline() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull MineDown mineDown) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(@NotNull MineDown mineDown) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMenu(@NotNull InventoryEditorMenu menu) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue