mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-23 16:49:19 +00:00
Userdata command, API expansion, editor interfaces
This commit is contained in:
@@ -10,32 +10,27 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
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.
|
* The HuskSync API implementation for the Bukkit platform, providing methods to access and modify player {@link UserData} held by {@link User}s.
|
||||||
* </p>
|
* </p>
|
||||||
* Retrieve an instance of the API class via {@link #getInstance()}.
|
* Retrieve an instance of the API class via {@link #getInstance()}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class HuskSyncAPI {
|
public class HuskSyncAPI extends BaseHuskSyncAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>(Internal use only)</b> - Instance of the API class.
|
* <b>(Internal use only)</b> - Instance of the API class
|
||||||
*/
|
*/
|
||||||
private static final HuskSyncAPI INSTANCE = new HuskSyncAPI();
|
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.
|
* <b>(Internal use only)</b> - Constructor, instantiating the API
|
||||||
*/
|
*/
|
||||||
private HuskSyncAPI() {
|
private HuskSyncAPI() {
|
||||||
|
super(BukkitHuskSync.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +46,8 @@ public class HuskSyncAPI {
|
|||||||
* Returns a {@link User} instance for the given bukkit {@link Player}.
|
* Returns a {@link User} instance for the given bukkit {@link Player}.
|
||||||
*
|
*
|
||||||
* @param player the bukkit player to get the {@link User} instance for
|
* @param player the bukkit player to get the {@link User} instance for
|
||||||
* @return the {@link User} instance for the given bukkit player
|
* @return the {@link User} instance for the given bukkit {@link Player}
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public OnlineUser getUser(@NotNull Player player) {
|
public OnlineUser getUser(@NotNull Player player) {
|
||||||
@@ -59,71 +55,49 @@ public class HuskSyncAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link User} by the given player's account {@link UUID}, if they exist.
|
* Set the inventory in the database of the given {@link User} to the given {@link ItemStack} contents
|
||||||
*
|
*
|
||||||
* @param uuid the unique id of the player to get the {@link User} instance for
|
* @param user the {@link User} to set the inventory of
|
||||||
* @return future returning the {@link User} instance for the given player's unique id if they exist, otherwise an empty {@link Optional}
|
* @param inventoryContents the {@link ItemStack} contents to set the inventory to
|
||||||
* @apiNote The player does not have to be online
|
* @return future returning void when complete
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<User>> getUser(@NotNull UUID uuid) {
|
public CompletableFuture<Void> setInventoryData(@NotNull User user, @NotNull ItemStack[] inventoryContents) {
|
||||||
return PLUGIN.getDatabase().getUser(uuid);
|
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
||||||
|
userData.ifPresent(data -> serializeItemStackArray(inventoryContents)
|
||||||
|
.thenAccept(serializedInventory -> {
|
||||||
|
data.getInventoryData().serializedItems = serializedInventory;
|
||||||
|
setUserData(user, data).join();
|
||||||
|
}))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link User} by the given player's username (case-insensitive), if they exist.
|
* Set the inventory in the database of the given {@link User} to the given {@link BukkitInventoryMap} contents
|
||||||
*
|
*
|
||||||
* @param username the username of the {@link User} instance for
|
* @param user the {@link User} to set the inventory of
|
||||||
* @return future returning the {@link User} instance for the given player's username if they exist, otherwise an empty {@link Optional}
|
* @param inventoryMap the {@link BukkitInventoryMap} contents to set the inventory to
|
||||||
* @apiNote The player does not have to be online, though their username has to be the username
|
* @return future returning void when complete
|
||||||
* they had when they last joined the server.
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<User>> getUser(@NotNull String username) {
|
public CompletableFuture<Void> setInventoryData(@NotNull User user, @NotNull BukkitInventoryMap inventoryMap) {
|
||||||
return PLUGIN.getDatabase().getUserByName(username);
|
return setInventoryData(user, inventoryMap.getContents());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link User}'s current {@link UserData}
|
* Set the Ender Chest in the database of the given {@link User} to the given {@link ItemStack} contents
|
||||||
*
|
*
|
||||||
* @param user the {@link User} to get the {@link UserData} for
|
* @param user the {@link User} to set the Ender Chest of
|
||||||
* @return future returning the {@link UserData} for the given {@link User} if they exist, otherwise an empty {@link Optional}
|
* @param enderChestContents the {@link ItemStack} contents to set the Ender Chest to
|
||||||
* @apiNote If the user is not online on the implementing bukkit server,
|
* @return future returning void when complete
|
||||||
* the {@link UserData} returned will be their last database-saved UserData.</p>
|
* @since 2.0
|
||||||
* 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) {
|
public CompletableFuture<Void> setEnderChestData(@NotNull User user, @NotNull ItemStack[] enderChestContents) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
||||||
if (user instanceof OnlineUser) {
|
userData.ifPresent(data -> serializeItemStackArray(enderChestContents)
|
||||||
return Optional.of(((OnlineUser) user).getUserData().join());
|
.thenAccept(serializedInventory -> {
|
||||||
} else {
|
data.getEnderChestData().serializedItems = serializedInventory;
|
||||||
return PLUGIN.getDatabase().getCurrentUserData(user).join().map(VersionedUserData::userData);
|
setUserData(user, data).join();
|
||||||
}
|
}))));
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,10 +106,11 @@ public class HuskSyncAPI {
|
|||||||
* @param user the {@link User} to get the {@link BukkitInventoryMap} for
|
* @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,
|
* @return future returning the {@link BukkitInventoryMap} for the given {@link User} if they exist,
|
||||||
* otherwise an empty {@link Optional}
|
* otherwise an empty {@link Optional}
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<BukkitInventoryMap>> getPlayerInventory(@NotNull User user) {
|
public CompletableFuture<Optional<BukkitInventoryMap>> getPlayerInventory(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||||
.map(userData -> BukkitSerializer.deserializeInventory(userData
|
.map(userData -> deserializeInventory(userData
|
||||||
.getInventoryData().serializedItems).join()));
|
.getInventoryData().serializedItems).join()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +120,11 @@ public class HuskSyncAPI {
|
|||||||
* @param user the {@link User} to get the Ender Chest contents of
|
* @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,
|
* @return future returning the {@link ItemStack} array of Ender Chest items for the user if they exist,
|
||||||
* otherwise an empty {@link Optional}
|
* otherwise an empty {@link Optional}
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<ItemStack[]>> getPlayerEnderChest(@NotNull User user) {
|
public CompletableFuture<Optional<ItemStack[]>> getPlayerEnderChest(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||||
.map(userData -> BukkitSerializer.deserializeItemStackArray(userData
|
.map(userData -> deserializeItemStackArray(userData
|
||||||
.getEnderChestData().serializedItems).join()));
|
.getEnderChestData().serializedItems).join()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,25 +133,71 @@ public class HuskSyncAPI {
|
|||||||
*
|
*
|
||||||
* @param serializedItemStackArray The Base-64 encoded inventory array string.
|
* @param serializedItemStackArray The Base-64 encoded inventory array string.
|
||||||
* @return The deserialized {@link ItemStack} array.
|
* @return The deserialized {@link ItemStack} array.
|
||||||
* @throws DataDeserializationException If an error occurs during deserialization.
|
* @throws DataSerializationException If an error occurs during deserialization.
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<ItemStack[]> deserializeItemStackArray(@NotNull String serializedItemStackArray)
|
public CompletableFuture<ItemStack[]> deserializeItemStackArray(@NotNull String serializedItemStackArray)
|
||||||
throws DataDeserializationException {
|
throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
||||||
.deserializeItemStackArray(serializedItemStackArray).join());
|
.deserializeItemStackArray(serializedItemStackArray).join());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a serialized {@link ItemStack} array of player inventory contents into a {@link BukkitInventoryMap}
|
||||||
|
*
|
||||||
|
* @param serializedInventory The serialized {@link ItemStack} array of player inventory contents.
|
||||||
|
* @return A {@link BukkitInventoryMap} of the deserialized {@link ItemStack} contents array
|
||||||
|
* @throws DataSerializationException If an error occurs during deserialization.
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public CompletableFuture<BukkitInventoryMap> deserializeInventory(@NotNull String serializedInventory)
|
||||||
|
throws DataSerializationException {
|
||||||
|
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
||||||
|
.deserializeInventory(serializedInventory).join());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an {@link ItemStack} array into a Base-64 encoded string.
|
||||||
|
*
|
||||||
|
* @param itemStacks The {@link ItemStack} array to serialize.
|
||||||
|
* @return The serialized Base-64 encoded string.
|
||||||
|
* @throws DataSerializationException If an error occurs during serialization.
|
||||||
|
* @see #deserializeItemStackArray(String)
|
||||||
|
* @see ItemData
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public CompletableFuture<String> serializeItemStackArray(@NotNull ItemStack[] itemStacks)
|
||||||
|
throws DataSerializationException {
|
||||||
|
return CompletableFuture.supplyAsync(() -> BukkitSerializer.serializeItemStackArray(itemStacks).join());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize a Base-64 encoded potion effect array string into a {@link PotionEffect} array.
|
* Deserialize a Base-64 encoded potion effect array string into a {@link PotionEffect} array.
|
||||||
*
|
*
|
||||||
* @param serializedPotionEffectArray The Base-64 encoded potion effect array string.
|
* @param serializedPotionEffectArray The Base-64 encoded potion effect array string.
|
||||||
* @return The deserialized {@link PotionEffect} array.
|
* @return The deserialized {@link PotionEffect} array.
|
||||||
* @throws DataDeserializationException If an error occurs during deserialization.
|
* @throws DataSerializationException If an error occurs during deserialization.
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<PotionEffect[]> deserializePotionEffectArray(@NotNull String serializedPotionEffectArray)
|
public CompletableFuture<PotionEffect[]> deserializePotionEffectArray(@NotNull String serializedPotionEffectArray)
|
||||||
throws DataDeserializationException {
|
throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
return CompletableFuture.supplyAsync(() -> BukkitSerializer
|
||||||
.deserializePotionEffects(serializedPotionEffectArray).join());
|
.deserializePotionEffectArray(serializedPotionEffectArray).join());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a {@link PotionEffect} array into a Base-64 encoded string.
|
||||||
|
*
|
||||||
|
* @param potionEffects The {@link PotionEffect} array to serialize.
|
||||||
|
* @return The serialized Base-64 encoded string.
|
||||||
|
* @throws DataSerializationException If an error occurs during serialization.
|
||||||
|
* @see #deserializePotionEffectArray(String)
|
||||||
|
* @see PotionEffectData
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public CompletableFuture<String> serializePotionEffectArray(@NotNull PotionEffect[] potionEffects)
|
||||||
|
throws DataSerializationException {
|
||||||
|
return CompletableFuture.supplyAsync(() -> BukkitSerializer.serializePotionEffectArray(potionEffects).join());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}).thenApply(succeeded -> {
|
}).thenApply(succeeded -> {
|
||||||
// Prepare data editor
|
// Prepare data editor
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
dataEditor = new DataEditor();
|
dataEditor = new DataEditor(locales);
|
||||||
}
|
}
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}).thenApply(succeeded -> {
|
}).thenApply(succeeded -> {
|
||||||
@@ -149,7 +149,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}).thenApply(succeeded -> {
|
}).thenApply(succeeded -> {
|
||||||
// Establish connection to the Redis server
|
// Establish connection to the Redis server
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
this.redisManager = new RedisManager(settings, dataAdapter, logger);
|
this.redisManager = new RedisManager(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||||
return this.redisManager.initialize().thenApply(initialized -> {
|
return this.redisManager.initialize().thenApply(initialized -> {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
* Commands available on the Bukkit HuskSync implementation
|
* Commands available on the Bukkit HuskSync implementation
|
||||||
*/
|
*/
|
||||||
public enum BukkitCommandType {
|
public enum BukkitCommandType {
|
||||||
|
|
||||||
HUSKSYNC_COMMAND(new HuskSyncCommand(BukkitHuskSync.getInstance())),
|
HUSKSYNC_COMMAND(new HuskSyncCommand(BukkitHuskSync.getInstance())),
|
||||||
HUSKSYNC_INVSEE(new InvseeCommand(BukkitHuskSync.getInstance())),
|
INVENTORY_COMMAND(new InventoryCommand(BukkitHuskSync.getInstance())),
|
||||||
HUSKSYNC_ECHEST(new EchestCommand(BukkitHuskSync.getInstance()));
|
ENDER_CHEST_COMMAND(new EnderChestCommand(BukkitHuskSync.getInstance())),
|
||||||
|
USERDATA_COMMAND(new UserDataCommand(BukkitHuskSync.getInstance()));
|
||||||
|
|
||||||
public final CommandBase commandBase;
|
public final CommandBase commandBase;
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ public class BukkitSerializer {
|
|||||||
* @param inventoryContents The contents of the inventory
|
* @param inventoryContents The contents of the inventory
|
||||||
* @return The serialized inventory contents
|
* @return The serialized inventory contents
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<String> serializeItemStackArray(ItemStack[] inventoryContents)
|
public static CompletableFuture<String> serializeItemStackArray(@NotNull ItemStack[] inventoryContents)
|
||||||
throws DataDeserializationException {
|
throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Return an empty string if there is no inventory item data to serialize
|
// Return an empty string if there is no inventory item data to serialize
|
||||||
if (inventoryContents.length == 0) {
|
if (inventoryContents.length == 0) {
|
||||||
@@ -45,7 +45,7 @@ public class BukkitSerializer {
|
|||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new DataDeserializationException("Failed to serialize item stack data", e);
|
throw new DataSerializationException("Failed to serialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -53,25 +53,25 @@ public class BukkitSerializer {
|
|||||||
/**
|
/**
|
||||||
* Returns a {@link BukkitInventoryMap} from a serialized array of ItemStacks representing the contents of a player's inventory.
|
* Returns a {@link BukkitInventoryMap} from a serialized array of ItemStacks representing the contents of a player's inventory.
|
||||||
*
|
*
|
||||||
* @param serializedPlayerInventory The serialized {@link ItemStack[]} inventory array
|
* @param serializedPlayerInventory The serialized {@link ItemStack} inventory array
|
||||||
* @return The deserialized ItemStacks, mapped for convenience as a {@link BukkitInventoryMap}
|
* @return The deserialized ItemStacks, mapped for convenience as a {@link BukkitInventoryMap}
|
||||||
* @throws DataDeserializationException If the serialized item stack array could not be deserialized
|
* @throws DataSerializationException If the serialized item stack array could not be deserialized
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<BukkitInventoryMap> deserializeInventory(@NotNull String serializedPlayerInventory)
|
public static CompletableFuture<BukkitInventoryMap> deserializeInventory(@NotNull String serializedPlayerInventory)
|
||||||
throws DataDeserializationException {
|
throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> new BukkitInventoryMap(deserializeItemStackArray(serializedPlayerInventory).join()));
|
return CompletableFuture.supplyAsync(() -> new BukkitInventoryMap(deserializeItemStackArray(serializedPlayerInventory).join()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of ItemStacks from serialized inventory data.
|
* Returns an array of ItemStacks from serialized inventory data.
|
||||||
*
|
*
|
||||||
* @param serializeItemStackArray The serialized {@link ItemStack[]} array
|
* @param serializeItemStackArray The serialized {@link ItemStack} array
|
||||||
* @return The deserialized array of {@link ItemStack}s
|
* @return The deserialized array of {@link ItemStack}s
|
||||||
* @throws DataDeserializationException If the serialized item stack array could not be deserialized
|
* @throws DataSerializationException If the serialized item stack array could not be deserialized
|
||||||
* @implNote Empty slots will be represented by {@code null}
|
* @implNote Empty slots will be represented by {@code null}
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<ItemStack[]> deserializeItemStackArray(String serializeItemStackArray)
|
public static CompletableFuture<ItemStack[]> deserializeItemStackArray(@NotNull String serializeItemStackArray)
|
||||||
throws DataDeserializationException {
|
throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Return empty array if there is no inventory data (set the player as having an empty inventory)
|
// Return empty array if there is no inventory data (set the player as having an empty inventory)
|
||||||
if (serializeItemStackArray.isEmpty()) {
|
if (serializeItemStackArray.isEmpty()) {
|
||||||
@@ -95,7 +95,7 @@ public class BukkitSerializer {
|
|||||||
return inventoryContents;
|
return inventoryContents;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
throw new DataDeserializationException("Failed to deserialize item stack data", e);
|
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ public class BukkitSerializer {
|
|||||||
* @return The serialized {@link ItemStack}
|
* @return The serialized {@link ItemStack}
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Map<String, Object> serializeItemStack(ItemStack item) {
|
private static Map<String, Object> serializeItemStack(@Nullable ItemStack item) {
|
||||||
return item != null ? item.serialize() : null;
|
return item != null ? item.serialize() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ public class BukkitSerializer {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked") // Ignore the "Unchecked cast" warning
|
@SuppressWarnings("unchecked") // Ignore the "Unchecked cast" warning
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ItemStack deserializeItemStack(Object serializedItemStack) {
|
private static ItemStack deserializeItemStack(@Nullable Object serializedItemStack) {
|
||||||
return serializedItemStack != null ? ItemStack.deserialize((Map<String, Object>) serializedItemStack) : null;
|
return serializedItemStack != null ? ItemStack.deserialize((Map<String, Object>) serializedItemStack) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ public class BukkitSerializer {
|
|||||||
* @param potionEffects The potion effect array
|
* @param potionEffects The potion effect array
|
||||||
* @return The serialized potion effects
|
* @return The serialized potion effects
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<String> serializePotionEffects(PotionEffect[] potionEffects) throws DataDeserializationException {
|
public static CompletableFuture<String> serializePotionEffectArray(@NotNull PotionEffect[] potionEffects) throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Return an empty string if there are no effects to serialize
|
// Return an empty string if there are no effects to serialize
|
||||||
if (potionEffects.length == 0) {
|
if (potionEffects.length == 0) {
|
||||||
@@ -151,7 +151,7 @@ public class BukkitSerializer {
|
|||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new DataDeserializationException("Failed to serialize potion effect data", e);
|
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -159,10 +159,10 @@ public class BukkitSerializer {
|
|||||||
/**
|
/**
|
||||||
* Returns an array of ItemStacks from serialized potion effect data
|
* Returns an array of ItemStacks from serialized potion effect data
|
||||||
*
|
*
|
||||||
* @param potionEffectData The serialized {@link PotionEffect[]} array
|
* @param potionEffectData The serialized {@link PotionEffect} array
|
||||||
* @return The {@link PotionEffect}s
|
* @return The {@link PotionEffect}s
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<PotionEffect[]> deserializePotionEffects(String potionEffectData) throws DataDeserializationException {
|
public static CompletableFuture<PotionEffect[]> deserializePotionEffectArray(@NotNull String potionEffectData) throws DataSerializationException {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Return empty array if there is no potion effect data (don't apply any effects to the player)
|
// Return empty array if there is no potion effect data (don't apply any effects to the player)
|
||||||
if (potionEffectData.isEmpty()) {
|
if (potionEffectData.isEmpty()) {
|
||||||
@@ -186,7 +186,7 @@ public class BukkitSerializer {
|
|||||||
return potionEffects;
|
return potionEffects;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
throw new DataDeserializationException("Failed to deserialize potion effects", e);
|
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ public class BukkitSerializer {
|
|||||||
* @return The serialized {@link ItemStack}
|
* @return The serialized {@link ItemStack}
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Map<String, Object> serializePotionEffect(PotionEffect potionEffect) {
|
private static Map<String, Object> serializePotionEffect(@Nullable PotionEffect potionEffect) {
|
||||||
return potionEffect != null ? potionEffect.serialize() : null;
|
return potionEffect != null ? potionEffect.serialize() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public class BukkitSerializer {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked") // Ignore the "Unchecked cast" warning
|
@SuppressWarnings("unchecked") // Ignore the "Unchecked cast" warning
|
||||||
@Nullable
|
@Nullable
|
||||||
private static PotionEffect deserializePotionEffect(Object serializedPotionEffect) {
|
private static PotionEffect deserializePotionEffect(@Nullable Object serializedPotionEffect) {
|
||||||
return serializedPotionEffect != null ? new PotionEffect((Map<String, Object>) serializedPotionEffect) : null;
|
return serializedPotionEffect != null ? new PotionEffect((Map<String, Object>) serializedPotionEffect) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import org.bukkit.event.Cancellable;
|
|||||||
import org.bukkit.event.HandlerList;
|
import org.bukkit.event.HandlerList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class BukkitDataSavePlayerEvent extends BukkitEvent implements DataSaveEvent, Cancellable {
|
public class BukkitDataSaveEvent extends BukkitEvent implements DataSaveEvent, Cancellable {
|
||||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||||
private boolean cancelled = false;
|
private boolean cancelled = false;
|
||||||
private UserData userData;
|
private UserData userData;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final DataSaveCause saveCause;
|
private final DataSaveCause saveCause;
|
||||||
|
|
||||||
protected BukkitDataSavePlayerEvent(@NotNull User user, @NotNull UserData userData,
|
protected BukkitDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
||||||
@NotNull DataSaveCause saveCause) {
|
@NotNull DataSaveCause saveCause) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.userData = userData;
|
this.userData = userData;
|
||||||
@@ -6,19 +6,28 @@ import net.william278.husksync.player.OnlineUser;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.player.PlayerEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public abstract class BukkitEvent extends Event implements net.william278.husksync.event.Event {
|
public abstract class BukkitEvent extends Event implements net.william278.husksync.event.Event {
|
||||||
|
|
||||||
|
protected BukkitEvent() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<net.william278.husksync.event.Event> fire() {
|
public CompletableFuture<net.william278.husksync.event.Event> fire() {
|
||||||
final CompletableFuture<net.william278.husksync.event.Event> eventFireFuture = new CompletableFuture<>();
|
final CompletableFuture<net.william278.husksync.event.Event> eventFireFuture = new CompletableFuture<>();
|
||||||
|
// Don't fire events while the server is shutting down
|
||||||
|
if (!BukkitHuskSync.getInstance().isEnabled()) {
|
||||||
|
eventFireFuture.complete(this);
|
||||||
|
} else {
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||||
Bukkit.getServer().getPluginManager().callEvent(this);
|
Bukkit.getServer().getPluginManager().callEvent(this);
|
||||||
eventFireFuture.complete(this);
|
eventFireFuture.complete(this);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return eventFireFuture;
|
return eventFireFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ public class BukkitEventCannon extends EventCannon {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
public CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
||||||
@NotNull DataSaveCause saveCause) {
|
@NotNull DataSaveCause saveCause) {
|
||||||
return new BukkitDataSavePlayerEvent(user, userData, saveCause).fire();
|
return new BukkitDataSaveEvent(user, userData, saveCause).fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fireSyncCompleteEvent(@NotNull OnlineUser user) {
|
public void fireSyncCompleteEvent(@NotNull OnlineUser user) {
|
||||||
new BukkitSyncCompletePlayerEvent(((BukkitPlayer) user).getPlayer()).fire();
|
new BukkitSyncCompleteEvent(((BukkitPlayer) user).getPlayer()).fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public abstract class BukkitPlayerEvent extends org.bukkit.event.player.PlayerEvent implements PlayerEvent {
|
public abstract class BukkitPlayerEvent extends BukkitEvent implements PlayerEvent {
|
||||||
|
|
||||||
|
protected final Player player;
|
||||||
|
|
||||||
public BukkitPlayerEvent(@NotNull Player who) {
|
protected BukkitPlayerEvent(@NotNull Player player) {
|
||||||
super(who);
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ public class BukkitPreSyncEvent extends BukkitPlayerEvent implements PreSyncEven
|
|||||||
this.cancelled = cancelled;
|
this.cancelled = cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnlineUser getUser() {
|
|
||||||
return BukkitPlayer.adapt(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull UserData getUserData() {
|
public @NotNull UserData getUserData() {
|
||||||
return userData;
|
return userData;
|
||||||
|
|||||||
@@ -6,18 +6,13 @@ import org.bukkit.entity.Player;
|
|||||||
import org.bukkit.event.HandlerList;
|
import org.bukkit.event.HandlerList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class BukkitSyncCompletePlayerEvent extends BukkitPlayerEvent implements SyncCompleteEvent {
|
public class BukkitSyncCompleteEvent extends BukkitPlayerEvent implements SyncCompleteEvent {
|
||||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||||
|
|
||||||
protected BukkitSyncCompletePlayerEvent(@NotNull Player player) {
|
protected BukkitSyncCompleteEvent(@NotNull Player player) {
|
||||||
super(player);
|
super(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnlineUser getUser() {
|
|
||||||
return BukkitPlayer.adapt(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public HandlerList getHandlers() {
|
public HandlerList getHandlers() {
|
||||||
@@ -2,7 +2,7 @@ package net.william278.husksync.listener;
|
|||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.data.BukkitSerializer;
|
import net.william278.husksync.data.BukkitSerializer;
|
||||||
import net.william278.husksync.data.DataDeserializationException;
|
import net.william278.husksync.data.DataSerializationException;
|
||||||
import net.william278.husksync.data.ItemData;
|
import net.william278.husksync.data.ItemData;
|
||||||
import net.william278.husksync.player.BukkitPlayer;
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
@@ -57,7 +57,7 @@ public class BukkitEventListener extends EventListener implements Listener {
|
|||||||
try {
|
try {
|
||||||
BukkitSerializer.serializeItemStackArray(event.getInventory().getContents()).thenAccept(
|
BukkitSerializer.serializeItemStackArray(event.getInventory().getContents()).thenAccept(
|
||||||
serializedInventory -> super.handleMenuClose(user, new ItemData(serializedInventory)));
|
serializedInventory -> super.handleMenuClose(user, new ItemData(serializedInventory)));
|
||||||
} catch (DataDeserializationException e) {
|
} catch (DataSerializationException e) {
|
||||||
huskSync.getLoggingAdapter().log(Level.SEVERE,
|
huskSync.getLoggingAdapter().log(Level.SEVERE,
|
||||||
"Failed to serialize inventory data during menu close", e);
|
"Failed to serialize inventory data during menu close", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import net.md_5.bungee.api.ChatMessageType;
|
|||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
import net.william278.husksync.editor.ItemEditorMenu;
|
||||||
import org.apache.commons.lang.ArrayUtils;
|
import org.apache.commons.lang.ArrayUtils;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.advancement.Advancement;
|
import org.bukkit.advancement.Advancement;
|
||||||
@@ -158,13 +158,13 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<PotionEffectData> getPotionEffects() {
|
public CompletableFuture<PotionEffectData> getPotionEffects() {
|
||||||
return BukkitSerializer.serializePotionEffects(player.getActivePotionEffects()
|
return BukkitSerializer.serializePotionEffectArray(player.getActivePotionEffects()
|
||||||
.toArray(new PotionEffect[0])).thenApply(PotionEffectData::new);
|
.toArray(new PotionEffect[0])).thenApply(PotionEffectData::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setPotionEffects(@NotNull PotionEffectData potionEffectData) {
|
public CompletableFuture<Void> setPotionEffects(@NotNull PotionEffectData potionEffectData) {
|
||||||
return BukkitSerializer.deserializePotionEffects(potionEffectData.serializedPotionEffects)
|
return BukkitSerializer.deserializePotionEffectArray(potionEffectData.serializedPotionEffects)
|
||||||
.thenApplyAsync(effects -> {
|
.thenApplyAsync(effects -> {
|
||||||
final CompletableFuture<Void> potionEffectsSetFuture = new CompletableFuture<>();
|
final CompletableFuture<Void> potionEffectsSetFuture = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||||
@@ -328,8 +328,8 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
// Set untyped statistics
|
// Set untyped statistics
|
||||||
for (String statistic : statisticsData.untypedStatistic.keySet()) {
|
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistic.get(statistic));
|
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set block statistics
|
// Set block statistics
|
||||||
@@ -440,7 +440,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showMenu(@NotNull InventoryEditorMenu menu) {
|
public void showMenu(@NotNull ItemEditorMenu menu) {
|
||||||
BukkitSerializer.deserializeItemStackArray(menu.itemData.serializedItems).thenAccept(inventoryContents -> {
|
BukkitSerializer.deserializeItemStackArray(menu.itemData.serializedItems).thenAccept(inventoryContents -> {
|
||||||
final Inventory inventory = Bukkit.createInventory(player, menu.slotCount,
|
final Inventory inventory = Bukkit.createInventory(player, menu.slotCount,
|
||||||
BaseComponent.toLegacyText(menu.menuTitle.toComponent()));
|
BaseComponent.toLegacyText(menu.menuTitle.toComponent()));
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ libraries:
|
|||||||
- 'mysql:mysql-connector-java:8.0.29'
|
- 'mysql:mysql-connector-java:8.0.29'
|
||||||
commands:
|
commands:
|
||||||
husksync:
|
husksync:
|
||||||
usage: '/husksync <update|info|reload>'
|
usage: '/husksync <update|info|reload|migrate>'
|
||||||
invsee:
|
description: 'Manage the HuskSync plugin'
|
||||||
usage: '/invsee <player>'
|
inventory:
|
||||||
echest:
|
usage: '/inventory <username> [version_uuid]'
|
||||||
usage: '/echest <player>'
|
description: 'View & edit a player''s inventory'
|
||||||
|
enderchest:
|
||||||
|
usage: '/enderchest <username> [version_uuid]'
|
||||||
|
description: 'View & edit a player''s Ender Chest'
|
||||||
|
userdata:
|
||||||
|
usage: '/userdata <view|list|delete|restore> <username> [version_uuid]'
|
||||||
|
description: 'View, manage & restore player userdata'
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package net.william278.husksync.api;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
import net.william278.husksync.data.UserData;
|
||||||
|
import net.william278.husksync.data.VersionedUserData;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base implementation of the HuskSync API, containing cross-platform API calls.
|
||||||
|
* </p>
|
||||||
|
* This class should not be used directly, but rather through platform-specific extending API classes.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class BaseHuskSyncAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>(Internal use only)</b> - Instance of the implementing plugin.
|
||||||
|
*/
|
||||||
|
protected final HuskSync plugin;
|
||||||
|
|
||||||
|
protected BaseHuskSyncAPI(@NotNull HuskSync plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final 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.
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final 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>
|
||||||
|
* Because of this, if the user is online on another server on the network,
|
||||||
|
* then the {@link UserData} returned by this method will <i>not necessarily reflective of
|
||||||
|
* their current state</i>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link UserData} to the database for the given {@link User}.
|
||||||
|
* </p>
|
||||||
|
* If the user is online and on the same cluster, their data will be updated in game.
|
||||||
|
*
|
||||||
|
* @param user the {@link User} to set the {@link UserData} for
|
||||||
|
* @param userData the {@link UserData} to set for the given {@link User}
|
||||||
|
* @return future returning void when complete
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData) {
|
||||||
|
return CompletableFuture.runAsync(() ->
|
||||||
|
plugin.getDatabase().setUserData(user, userData, DataSaveCause.API)
|
||||||
|
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(user, userData).join()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the {@link UserData} of an {@link OnlineUser} to the database
|
||||||
|
*
|
||||||
|
* @param user the {@link OnlineUser} to save the {@link UserData} of
|
||||||
|
* @return future returning void when complete
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
||||||
|
return CompletableFuture.runAsync(() -> user.getUserData().thenAccept(userData ->
|
||||||
|
plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final 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}
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public final String getUserDataJson(@NotNull UserData userData, boolean prettyPrint) {
|
||||||
|
return plugin.getDataAdapter().toJson(userData, prettyPrint);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package net.william278.husksync.command;
|
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
|
||||||
import net.william278.husksync.data.UserData;
|
|
||||||
import net.william278.husksync.data.VersionedUserData;
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
|
||||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class EchestCommand extends CommandBase {
|
|
||||||
|
|
||||||
public EchestCommand(@NotNull HuskSync implementor) {
|
|
||||||
super("echest", Permission.COMMAND_VIEW_INVENTORIES, implementor, "openechest");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
|
||||||
if (args.length == 0 || args.length > 2) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax", "/echest <player>")
|
|
||||||
.ifPresent(player::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAcceptAsync(optionalUser ->
|
|
||||||
optionalUser.ifPresentOrElse(user -> {
|
|
||||||
List<VersionedUserData> userData = plugin.getDatabase().getUserData(user).join();
|
|
||||||
Optional<VersionedUserData> dataToView;
|
|
||||||
if (args.length == 2) {
|
|
||||||
try {
|
|
||||||
final UUID version = UUID.fromString(args[1]);
|
|
||||||
dataToView = userData.stream().filter(data -> data.versionUUID().equals(version)).findFirst();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/echest <player> [version_uuid]").ifPresent(player::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dataToView = userData.stream().sorted().findFirst();
|
|
||||||
}
|
|
||||||
dataToView.ifPresentOrElse(versionedUserData -> {
|
|
||||||
final UserData data = versionedUserData.userData();
|
|
||||||
final InventoryEditorMenu menu = InventoryEditorMenu.createEnderChestMenu(
|
|
||||||
data.getEnderChestData(), user, player);
|
|
||||||
plugin.getLocales().getLocale("viewing_ender_chest_of", user.username)
|
|
||||||
.ifPresent(player::sendMessage);
|
|
||||||
plugin.getDataEditor().openInventoryMenu(player, menu).thenAcceptAsync(inventoryDataOnClose -> {
|
|
||||||
if (!menu.canEdit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final UserData updatedUserData = new UserData(data.getStatusData(),
|
|
||||||
data.getInventoryData(), inventoryDataOnClose,
|
|
||||||
data.getPotionEffectsData(), data.getAdvancementData(),
|
|
||||||
data.getStatisticsData(), data.getLocationData(),
|
|
||||||
data.getPersistentDataContainerData());
|
|
||||||
plugin.getDatabase().setUserData(user, updatedUserData, DataSaveCause.ECHEST_COMMAND_EDIT).join();
|
|
||||||
});
|
|
||||||
}, () -> plugin.getLocales().getLocale(args.length == 2 ? "error_invalid_version_uuid"
|
|
||||||
: "error_no_data_to_display").ifPresent(player::sendMessage));
|
|
||||||
}, () -> plugin.getLocales().getLocale("error_invalid_player").ifPresent(player::sendMessage)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.ItemData;
|
||||||
|
import net.william278.husksync.data.UserData;
|
||||||
|
import net.william278.husksync.data.VersionedUserData;
|
||||||
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
import net.william278.husksync.editor.ItemEditorMenu;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class EnderChestCommand extends CommandBase {
|
||||||
|
|
||||||
|
public EnderChestCommand(@NotNull HuskSync implementor) {
|
||||||
|
super("enderchest", Permission.COMMAND_ENDER_CHEST, implementor, "echest", "openechest");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
|
if (args.length == 0 || args.length > 2) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax", "/enderchest <player>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
||||||
|
optionalUser.ifPresentOrElse(user -> {
|
||||||
|
if (args.length == 2) {
|
||||||
|
// View user data by specified UUID
|
||||||
|
try {
|
||||||
|
final UUID versionUuid = UUID.fromString(args[1]);
|
||||||
|
plugin.getDatabase().getUserData(user).thenAccept(userDataList -> userDataList.stream()
|
||||||
|
.filter(userData -> userData.versionUUID().equals(versionUuid)).findFirst().ifPresentOrElse(
|
||||||
|
userData -> showEnderChestMenu(player, userData, user, userDataList.stream().sorted().findFirst()
|
||||||
|
.map(VersionedUserData::versionUUID).orElse(UUID.randomUUID()).equals(versionUuid)),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// View latest user data
|
||||||
|
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||||
|
versionedUserData -> showEnderChestMenu(player, versionedUserData, user, true),
|
||||||
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
}
|
||||||
|
}, () -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showEnderChestMenu(@NotNull OnlineUser player, @NotNull VersionedUserData versionedUserData,
|
||||||
|
@NotNull User dataOwner, final boolean allowEdit) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
final UserData data = versionedUserData.userData();
|
||||||
|
final ItemEditorMenu menu = ItemEditorMenu.createEnderChestMenu(data.getEnderChestData(),
|
||||||
|
dataOwner, player, plugin.getLocales(), allowEdit);
|
||||||
|
plugin.getLocales().getLocale("viewing_ender_chest_of", dataOwner.username,
|
||||||
|
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
||||||
|
.format(versionedUserData.versionTimestamp()))
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
final ItemData enderChestDataOnClose = plugin.getDataEditor().openItemEditorMenu(player, menu).join();
|
||||||
|
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.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.ENDER_CHEST_COMMAND_EDIT).join();
|
||||||
|
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -50,10 +50,11 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.reload();
|
plugin.reload();
|
||||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Reloaded config & message files.]((#00fb9a)"));
|
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Reloaded config & message files.](#00fb9a)"));
|
||||||
}
|
}
|
||||||
default ->
|
default -> plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax", "/husksync <update/info/reload>").ifPresent(player::sendMessage);
|
"/husksync <update/info/reload>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +76,8 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
case "migrate" -> {
|
case "migrate" -> {
|
||||||
//todo - MPDB migrator
|
//todo - MPDB migrator
|
||||||
}
|
}
|
||||||
default ->
|
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid syntax. Console usage: \"husksync <update/info/reload/migrate>\"");
|
"Invalid syntax. Console usage: \"husksync <update/info/reload/migrate>\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
import net.william278.husksync.data.ItemData;
|
||||||
|
import net.william278.husksync.data.UserData;
|
||||||
|
import net.william278.husksync.data.VersionedUserData;
|
||||||
|
import net.william278.husksync.editor.ItemEditorMenu;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class InventoryCommand extends CommandBase {
|
||||||
|
|
||||||
|
public InventoryCommand(@NotNull HuskSync implementor) {
|
||||||
|
super("inventory", Permission.COMMAND_INVENTORY, implementor, "invsee", "openinv");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
|
if (args.length == 0 || args.length > 2) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax", "/inventory <player>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
||||||
|
optionalUser.ifPresentOrElse(user -> {
|
||||||
|
if (args.length == 2) {
|
||||||
|
// View user data by specified UUID
|
||||||
|
try {
|
||||||
|
final UUID versionUuid = UUID.fromString(args[1]);
|
||||||
|
plugin.getDatabase().getUserData(user).thenAccept(userDataList -> userDataList.stream()
|
||||||
|
.filter(userData -> userData.versionUUID().equals(versionUuid)).findFirst().ifPresentOrElse(
|
||||||
|
userData -> showInventoryMenu(player, userData, user, userDataList.stream().sorted().findFirst()
|
||||||
|
.map(VersionedUserData::versionUUID).orElse(UUID.randomUUID()).equals(versionUuid)),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// View latest user data
|
||||||
|
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||||
|
versionedUserData -> showInventoryMenu(player, versionedUserData, user, true),
|
||||||
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
}
|
||||||
|
}, () -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showInventoryMenu(@NotNull OnlineUser player, @NotNull VersionedUserData versionedUserData,
|
||||||
|
@NotNull User dataOwner, boolean allowEdit) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
final UserData data = versionedUserData.userData();
|
||||||
|
final ItemEditorMenu menu = ItemEditorMenu.createInventoryMenu(data.getInventoryData(),
|
||||||
|
dataOwner, player, plugin.getLocales(), allowEdit);
|
||||||
|
plugin.getLocales().getLocale("viewing_inventory_of", dataOwner.username,
|
||||||
|
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
||||||
|
.format(versionedUserData.versionTimestamp()))
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
final ItemData inventoryDataOnClose = plugin.getDataEditor().openItemEditorMenu(player, menu).join();
|
||||||
|
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.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND_EDIT).join();
|
||||||
|
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package net.william278.husksync.command;
|
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
|
||||||
import net.william278.husksync.data.UserData;
|
|
||||||
import net.william278.husksync.data.VersionedUserData;
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
|
||||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class InvseeCommand extends CommandBase {
|
|
||||||
|
|
||||||
public InvseeCommand(@NotNull HuskSync implementor) {
|
|
||||||
super("invsee", Permission.COMMAND_VIEW_INVENTORIES, implementor, "openinv");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
|
||||||
if (args.length == 0 || args.length > 2) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax", "/invsee <player>")
|
|
||||||
.ifPresent(player::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAcceptAsync(optionalUser ->
|
|
||||||
optionalUser.ifPresentOrElse(user -> {
|
|
||||||
List<VersionedUserData> userData = plugin.getDatabase().getUserData(user).join();
|
|
||||||
Optional<VersionedUserData> dataToView;
|
|
||||||
if (args.length == 2) {
|
|
||||||
try {
|
|
||||||
final UUID version = UUID.fromString(args[1]);
|
|
||||||
dataToView = userData.stream().filter(data -> data.versionUUID().equals(version)).findFirst();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/invsee <player> [version_uuid]").ifPresent(player::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dataToView = userData.stream().sorted().findFirst();
|
|
||||||
}
|
|
||||||
dataToView.ifPresentOrElse(versionedUserData -> {
|
|
||||||
final UserData data = versionedUserData.userData();
|
|
||||||
final InventoryEditorMenu menu = InventoryEditorMenu.createInventoryMenu(
|
|
||||||
data.getInventoryData(), user, player);
|
|
||||||
plugin.getLocales().getLocale("viewing_inventory_of", user.username)
|
|
||||||
.ifPresent(player::sendMessage);
|
|
||||||
plugin.getDataEditor().openInventoryMenu(player, menu).thenAcceptAsync(inventoryDataOnClose -> {
|
|
||||||
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.getDatabase().setUserData(user, updatedUserData, DataSaveCause.INVSEE_COMMAND_EDIT).join();
|
|
||||||
});
|
|
||||||
}, () -> plugin.getLocales().getLocale(args.length == 2 ? "error_invalid_version_uuid"
|
|
||||||
: "error_no_data_to_display").ifPresent(player::sendMessage));
|
|
||||||
}, () -> plugin.getLocales().getLocale("error_invalid_player").ifPresent(player::sendMessage)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -30,45 +30,51 @@ public enum Permission {
|
|||||||
/**
|
/**
|
||||||
* Lets the user save a player's data {@code /husksync save (player)}
|
* Lets the user save a player's data {@code /husksync save (player)}
|
||||||
*/
|
*/
|
||||||
COMMAND_HUSKSYNC_SAVE("husksync.command.husksync.save", DefaultAccess.OPERATORS),
|
COMMAND_HUSKSYNC_SAVE("husksync.command.husksync.save", DefaultAccess.OPERATORS), // todo
|
||||||
/**
|
/**
|
||||||
* Lets the user save all online player data {@code /husksync saveall}
|
* Lets the user save all online player data {@code /husksync saveall}
|
||||||
*/
|
*/
|
||||||
COMMAND_HUSKSYNC_SAVE_ALL("husksync.command.husksync.saveall", DefaultAccess.OPERATORS),
|
COMMAND_HUSKSYNC_SAVE_ALL("husksync.command.husksync.saveall", DefaultAccess.OPERATORS), //todo
|
||||||
/**
|
|
||||||
* Lets the user view a player's backup data {@code /husksync backup (player)}
|
|
||||||
*/
|
|
||||||
COMMAND_HUSKSYNC_BACKUPS("husksync.command.husksync.backups", DefaultAccess.OPERATORS),
|
|
||||||
/**
|
|
||||||
* Lets the user restore a player's backup data {@code /husksync backup (player) restore (backup_uuid)}
|
|
||||||
*/
|
|
||||||
COMMAND_HUSKSYNC_BACKUPS_RESTORE("husksync.command.husksync.backups.restore", DefaultAccess.OPERATORS),
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* /invsee command permissions
|
* /inventory command permissions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lets the user use the {@code /invsee (player)} command and view offline players' inventories
|
* Lets the user use the {@code /inventory (player)} command and view offline players' inventories
|
||||||
*/
|
*/
|
||||||
COMMAND_VIEW_INVENTORIES("husksync.command.invsee", DefaultAccess.OPERATORS),
|
COMMAND_INVENTORY("husksync.command.inventory", DefaultAccess.OPERATORS),
|
||||||
/**
|
/**
|
||||||
* Lets the user edit the contents of offline players' inventories
|
* Lets the user edit the contents of offline players' inventories
|
||||||
*/
|
*/
|
||||||
COMMAND_EDIT_INVENTORIES("husksync.command.invsee.edit", DefaultAccess.OPERATORS),
|
COMMAND_INVENTORY_EDIT("husksync.command.inventory.edit", DefaultAccess.OPERATORS),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* /echest command permissions
|
* /enderchest command permissions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lets the user use the {@code /echest (player)} command and view offline players' ender chests
|
* Lets the user use the {@code /enderchest (player)} command and view offline players' ender chests
|
||||||
*/
|
*/
|
||||||
COMMAND_VIEW_ENDER_CHESTS("husksync.command.echest", DefaultAccess.OPERATORS),
|
COMMAND_ENDER_CHEST("husksync.command.enderchest", DefaultAccess.OPERATORS),
|
||||||
/**
|
/**
|
||||||
* Lets the user edit the contents of offline players' ender chests
|
* Lets the user edit the contents of offline players' ender chests
|
||||||
*/
|
*/
|
||||||
COMMAND_EDIT_ENDER_CHESTS("husksync.command.echest.edit", DefaultAccess.OPERATORS);
|
COMMAND_ENDER_CHEST_EDIT("husksync.command.enderchest.edit", DefaultAccess.OPERATORS),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* /userdata command permissions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets the user view user data {@code /userdata view/list (player) (version_uuid)}
|
||||||
|
*/
|
||||||
|
COMMAND_USER_DATA("husksync.command.userdata", DefaultAccess.OPERATORS),
|
||||||
|
/**
|
||||||
|
* Lets the user restore and delete user data {@code /userdata restore/delete (player) (version_uuid)}
|
||||||
|
*/
|
||||||
|
COMMAND_USER_DATA_MANAGE("husksync.command.userdata.manage", DefaultAccess.OPERATORS);
|
||||||
|
|
||||||
|
|
||||||
public final String node;
|
public final String node;
|
||||||
public final DefaultAccess defaultAccess;
|
public final DefaultAccess defaultAccess;
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||||
|
|
||||||
|
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore"};
|
||||||
|
|
||||||
|
public UserDataCommand(@NotNull HuskSync implementor) {
|
||||||
|
super("userdata", Permission.COMMAND_USER_DATA, implementor, "playerdata");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata <view|list|delete|restore> <username> [version_uuid]")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "view" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata view <username> [version_uuid]")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String username = args[1];
|
||||||
|
if (args.length >= 3) {
|
||||||
|
try {
|
||||||
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
|
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
||||||
|
optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
|
user -> plugin.getDatabase().getUserData(user).thenAccept(
|
||||||
|
userDataList -> userDataList.stream().filter(versionedUserData -> versionedUserData
|
||||||
|
.versionUUID().equals(versionUuid))
|
||||||
|
.findFirst().ifPresentOrElse(userData ->
|
||||||
|
plugin.getDataEditor()
|
||||||
|
.displayDataOverview(player, userData, user),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
|
.ifPresent(player::sendMessage))),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage))));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata view <username> [version_uuid]")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
||||||
|
optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
|
user -> plugin.getDatabase().getCurrentUserData(user).thenAccept(
|
||||||
|
latestData -> latestData.ifPresentOrElse(
|
||||||
|
userData -> plugin.getDataEditor()
|
||||||
|
.displayDataOverview(player, userData, user),
|
||||||
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
|
.ifPresent(player::sendMessage))),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "list" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata list <username>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String username = args[1];
|
||||||
|
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
||||||
|
optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
|
user -> plugin.getDatabase().getUserData(user).thenAccept(dataList -> {
|
||||||
|
if (dataList.isEmpty()) {
|
||||||
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getDataEditor().displayDataList(player, dataList, user);
|
||||||
|
}),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage))));
|
||||||
|
}
|
||||||
|
case "delete" -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
case "restore" -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
|
return Arrays.stream(COMMAND_ARGUMENTS)
|
||||||
|
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
||||||
|
.sorted().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package net.william278.husksync.data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates an error occurred during base-64 serialization and deserialization of data.
|
|
||||||
* </p>
|
|
||||||
* For example, an exception deserializing {@link ItemData} item stack or {@link PotionEffectData} potion effect arrays
|
|
||||||
*/
|
|
||||||
public class DataDeserializationException extends RuntimeException {
|
|
||||||
protected DataDeserializationException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +1,81 @@
|
|||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.api.BaseHuskSyncAPI;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the cause of a player data save.
|
* 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.
|
* @implNote This enum is saved in the database.
|
||||||
|
* </p>
|
||||||
|
* Cause names have a max length of 32 characters.
|
||||||
*/
|
*/
|
||||||
public enum DataSaveCause {
|
public enum DataSaveCause {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when a player disconnected from the server (either to change servers, or to log off)
|
* Indicates data saved when a player disconnected from the server (either to change servers, or to log off)
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
DISCONNECT,
|
DISCONNECT,
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the world saved
|
* Indicates data saved when the world saved
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
WORLD_SAVE,
|
WORLD_SAVE,
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the server shut down
|
* Indicates data saved when the server shut down
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
SERVER_SHUTDOWN,
|
SERVER_SHUTDOWN,
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by editing inventory contents via the {@code /invsee} command
|
* Indicates data was saved by editing inventory contents via the {@code /inventory} command
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
INVSEE_COMMAND_EDIT,
|
INVENTORY_COMMAND_EDIT,
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by editing Ender Chest contents via the {@code /echest} command
|
* Indicates data was saved by editing Ender Chest contents via the {@code /enderchest} command
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
ECHEST_COMMAND_EDIT,
|
ENDER_CHEST_COMMAND_EDIT,
|
||||||
|
/**
|
||||||
|
* Indicates data was saved by restoring it from a previous version
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
BACKUP_RESTORE,
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by an API call
|
* Indicates data was saved by an API call
|
||||||
|
*
|
||||||
|
* @see BaseHuskSyncAPI#saveUserData(OnlineUser)
|
||||||
|
* @see BaseHuskSyncAPI#setUserData(User, UserData)
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
API,
|
API,
|
||||||
|
|
||||||
|
MPDB_IMPORT,
|
||||||
|
LEGACY_IMPORT,
|
||||||
|
MANUAL_IMPORT,
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by an unknown cause.
|
* Indicates data was saved by an unknown cause.
|
||||||
* </p>
|
* </p>
|
||||||
* This should not be used and is only used for error handling purposes.
|
* This should not be used and is only used for error handling purposes.
|
||||||
|
*
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
UNKNOWN;
|
UNKNOWN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link DataSaveCause} by name.
|
||||||
|
*
|
||||||
|
* @return the {@link DataSaveCause} or {@link #UNKNOWN} if the name is not valid.
|
||||||
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public static DataSaveCause getCauseByName(@NotNull String name) {
|
public static DataSaveCause getCauseByName(@NotNull String name) {
|
||||||
for (DataSaveCause cause : values()) {
|
for (DataSaveCause cause : values()) {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates an error occurred during Base-64 serialization and deserialization of data.
|
||||||
|
* </p>
|
||||||
|
* For example, an exception deserializing {@link ItemData} item stack or {@link PotionEffectData} potion effect arrays
|
||||||
|
*/
|
||||||
|
public class DataSerializationException extends RuntimeException {
|
||||||
|
protected DataSerializationException(@NotNull String message, @NotNull Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package net.william278.husksync.data;
|
|||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +14,7 @@ public class StatisticsData {
|
|||||||
* Map of untyped statistic names to their values
|
* Map of untyped statistic names to their values
|
||||||
*/
|
*/
|
||||||
@SerializedName("untyped_statistics")
|
@SerializedName("untyped_statistics")
|
||||||
public Map<String, Integer> untypedStatistic;
|
public Map<String, Integer> untypedStatistics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of block type statistics to a map of material types to values
|
* Map of block type statistics to a map of material types to values
|
||||||
@@ -35,11 +34,11 @@ public class StatisticsData {
|
|||||||
@SerializedName("entity_statistics")
|
@SerializedName("entity_statistics")
|
||||||
public Map<String, Map<String, Integer>> entityStatistics;
|
public Map<String, Map<String, Integer>> entityStatistics;
|
||||||
|
|
||||||
public StatisticsData(@NotNull Map<String, Integer> untypedStatistic,
|
public StatisticsData(@NotNull Map<String, Integer> untypedStatistics,
|
||||||
@NotNull Map<String, Map<String, Integer>> blockStatistics,
|
@NotNull Map<String, Map<String, Integer>> blockStatistics,
|
||||||
@NotNull Map<String, Map<String, Integer>> itemStatistics,
|
@NotNull Map<String, Map<String, Integer>> itemStatistics,
|
||||||
@NotNull Map<String, Map<String, Integer>> entityStatistics) {
|
@NotNull Map<String, Map<String, Integer>> entityStatistics) {
|
||||||
this.untypedStatistic = untypedStatistic;
|
this.untypedStatistics = untypedStatistics;
|
||||||
this.blockStatistics = blockStatistics;
|
this.blockStatistics = blockStatistics;
|
||||||
this.itemStatistics = itemStatistics;
|
this.itemStatistics = itemStatistics;
|
||||||
this.entityStatistics = entityStatistics;
|
this.entityStatistics = entityStatistics;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class MySqlDatabase extends Database {
|
|||||||
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) {
|
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) {
|
||||||
super(settings.getStringValue(Settings.ConfigOption.DATABASE_PLAYERS_TABLE_NAME),
|
super(settings.getStringValue(Settings.ConfigOption.DATABASE_PLAYERS_TABLE_NAME),
|
||||||
settings.getStringValue(Settings.ConfigOption.DATABASE_DATA_TABLE_NAME),
|
settings.getStringValue(Settings.ConfigOption.DATABASE_DATA_TABLE_NAME),
|
||||||
settings.getIntegerValue(Settings.ConfigOption.SYNCHRONIZATION_MAX_USER_DATA_RECORDS),
|
Math.max(1, Math.min(20, settings.getIntegerValue(Settings.ConfigOption.SYNCHRONIZATION_MAX_USER_DATA_RECORDS))),
|
||||||
resourceReader, dataAdapter, eventCannon, logger);
|
resourceReader, dataAdapter, eventCannon, logger);
|
||||||
this.mySqlHost = settings.getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
this.mySqlHost = settings.getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
||||||
this.mySqlPort = settings.getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
this.mySqlPort = settings.getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package net.william278.husksync.editor;
|
package net.william278.husksync.editor;
|
||||||
|
|
||||||
|
import net.william278.husksync.command.Permission;
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.data.AdvancementData;
|
||||||
import net.william278.husksync.data.ItemData;
|
import net.william278.husksync.data.ItemData;
|
||||||
import net.william278.husksync.data.VersionedUserData;
|
import net.william278.husksync.data.VersionedUserData;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.text.DateFormat;
|
||||||
import java.util.UUID;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,25 +23,27 @@ public class DataEditor {
|
|||||||
* Map of currently open inventory and ender chest data editors
|
* Map of currently open inventory and ender chest data editors
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
protected final HashMap<UUID, InventoryEditorMenu> openInventoryMenus;
|
protected final HashMap<UUID, ItemEditorMenu> openInventoryMenus;
|
||||||
|
|
||||||
public DataEditor() {
|
private final Locales locales;
|
||||||
|
|
||||||
|
public DataEditor(@NotNull Locales locales) {
|
||||||
this.openInventoryMenus = new HashMap<>();
|
this.openInventoryMenus = new HashMap<>();
|
||||||
|
this.locales = locales;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open an inventory or ender chest editor menu
|
* Open an inventory or ender chest editor menu
|
||||||
*
|
*
|
||||||
* @param user The online user to open the editor for
|
* @param user The online user to open the editor for
|
||||||
* @param inventoryEditorMenu The {@link InventoryEditorMenu} to open
|
* @param itemEditorMenu The {@link ItemEditorMenu} to open
|
||||||
* @return The inventory editor menu
|
* @see ItemEditorMenu#createInventoryMenu(ItemData, User, OnlineUser, Locales, boolean)
|
||||||
* @see InventoryEditorMenu#createInventoryMenu(ItemData, User, OnlineUser)
|
* @see ItemEditorMenu#createEnderChestMenu(ItemData, User, OnlineUser, Locales, boolean)
|
||||||
* @see InventoryEditorMenu#createEnderChestMenu(ItemData, User, OnlineUser)
|
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<ItemData> openInventoryMenu(@NotNull OnlineUser user,
|
public CompletableFuture<ItemData> openItemEditorMenu(@NotNull OnlineUser user,
|
||||||
@NotNull InventoryEditorMenu inventoryEditorMenu) {
|
@NotNull ItemEditorMenu itemEditorMenu) {
|
||||||
this.openInventoryMenus.put(user.uuid, inventoryEditorMenu);
|
this.openInventoryMenus.put(user.uuid, itemEditorMenu);
|
||||||
return inventoryEditorMenu.showInventory(user);
|
return itemEditorMenu.showInventory(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +73,7 @@ public class DataEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a chat message detailing information about {@link VersionedUserData}
|
* Display a chat menu detailing information about {@link VersionedUserData}
|
||||||
*
|
*
|
||||||
* @param user The online user to display the message to
|
* @param user The online user to display the message to
|
||||||
* @param userData The {@link VersionedUserData} to display information about
|
* @param userData The {@link VersionedUserData} to display information about
|
||||||
@@ -75,11 +81,89 @@ public class DataEditor {
|
|||||||
*/
|
*/
|
||||||
public void displayDataOverview(@NotNull OnlineUser user, @NotNull VersionedUserData userData,
|
public void displayDataOverview(@NotNull OnlineUser user, @NotNull VersionedUserData userData,
|
||||||
@NotNull User dataOwner) {
|
@NotNull User dataOwner) {
|
||||||
//todo
|
locales.getLocale("data_manager_title",
|
||||||
|
dataOwner.username, dataOwner.uuid.toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
locales.getLocale("data_manager_versioning",
|
||||||
|
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss").format(userData.versionTimestamp()),
|
||||||
|
userData.versionUUID().toString().split("-")[0],
|
||||||
|
userData.versionUUID().toString(),
|
||||||
|
userData.cause().name().toLowerCase().replaceAll("_", " "))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
locales.getLocale("data_manager_status",
|
||||||
|
Double.toString(userData.userData().getStatusData().health),
|
||||||
|
Double.toString(userData.userData().getStatusData().maxHealth),
|
||||||
|
Double.toString(userData.userData().getStatusData().hunger),
|
||||||
|
Integer.toString(userData.userData().getStatusData().expLevel),
|
||||||
|
userData.userData().getStatusData().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))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
if (user.hasPermission(Permission.COMMAND_INVENTORY.node)
|
||||||
|
&& user.hasPermission(Permission.COMMAND_ENDER_CHEST.node)) {
|
||||||
|
locales.getLocale("data_manager_item_buttons",
|
||||||
|
dataOwner.username, userData.versionUUID().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
if (user.hasPermission(Permission.COMMAND_USER_DATA_MANAGE.node)) {
|
||||||
|
locales.getLocale("data_manager_management_buttons",
|
||||||
|
dataOwner.username, userData.versionUUID().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull String generateAdvancementPreview(@NotNull List<AdvancementData> advancementData) {
|
||||||
|
final StringJoiner joiner = new StringJoiner("\n");
|
||||||
|
final int PREVIEW_SIZE = 8;
|
||||||
|
for (int i = 0; i < advancementData.size(); i++) {
|
||||||
|
joiner.add(advancementData.get(i).key);
|
||||||
|
if (i >= PREVIEW_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int remainingAdvancements = advancementData.size() - PREVIEW_SIZE;
|
||||||
|
if (remainingAdvancements > 0) {
|
||||||
|
joiner.add(locales.getRawLocale("data_manager_advancement_preview_remaining",
|
||||||
|
Integer.toString(remainingAdvancements)).orElse("+" + remainingAdvancements + "…"));
|
||||||
|
}
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a chat list detailing a player's saved list of {@link VersionedUserData}
|
||||||
|
*
|
||||||
|
* @param user The online user to display the message to
|
||||||
|
* @param userDataList The list of {@link VersionedUserData} to display
|
||||||
|
* @param dataOwner The {@link User} who owns the {@link VersionedUserData}
|
||||||
|
*/
|
||||||
|
public void displayDataList(@NotNull OnlineUser user, @NotNull List<VersionedUserData> userDataList,
|
||||||
|
@NotNull User dataOwner) {
|
||||||
|
locales.getLocale("data_list_title",
|
||||||
|
dataOwner.username, dataOwner.uuid.toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
|
final String[] numberedIcons = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳" .split("");
|
||||||
|
for (int i = 0; i < Math.min(20, userDataList.size()); i++) {
|
||||||
|
final VersionedUserData userData = userDataList.get(i);
|
||||||
|
locales.getLocale("data_list_item",
|
||||||
|
numberedIcons[i],
|
||||||
|
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
||||||
|
.format(userData.versionTimestamp()),
|
||||||
|
userData.versionUUID().toString().split("-")[0],
|
||||||
|
userData.versionUUID().toString(),
|
||||||
|
userData.cause().name().toLowerCase().replaceAll("_", " "),
|
||||||
|
dataOwner.username)
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the user has an inventory editor menu open
|
* Returns whether the user has an inventory editor menu open
|
||||||
|
*
|
||||||
* @param user {@link OnlineUser} to check
|
* @param user {@link OnlineUser} to check
|
||||||
* @return {@code true} if the user has an inventory editor open; {@code false} otherwise
|
* @return {@code true} if the user has an inventory editor open; {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package net.william278.husksync.editor;
|
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
|
||||||
import net.william278.husksync.command.Permission;
|
|
||||||
import net.william278.husksync.data.ItemData;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import net.william278.husksync.player.User;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public class InventoryEditorMenu {
|
|
||||||
|
|
||||||
public final ItemData itemData;
|
|
||||||
public final int slotCount;
|
|
||||||
public final MineDown menuTitle;
|
|
||||||
public final boolean canEdit;
|
|
||||||
|
|
||||||
private CompletableFuture<ItemData> inventoryDataCompletableFuture;
|
|
||||||
|
|
||||||
private InventoryEditorMenu(@NotNull ItemData itemData, int slotCount,
|
|
||||||
@NotNull MineDown menuTitle, boolean canEdit) {
|
|
||||||
this.itemData = itemData;
|
|
||||||
this.menuTitle = menuTitle;
|
|
||||||
this.slotCount = slotCount;
|
|
||||||
this.canEdit = canEdit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<ItemData> showInventory(@NotNull OnlineUser user) {
|
|
||||||
inventoryDataCompletableFuture = new CompletableFuture<>();
|
|
||||||
user.showMenu(this);
|
|
||||||
return inventoryDataCompletableFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeInventory(@NotNull ItemData itemData) {
|
|
||||||
inventoryDataCompletableFuture.completeAsync(() -> itemData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InventoryEditorMenu createInventoryMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
|
||||||
@NotNull OnlineUser viewer) {
|
|
||||||
return new InventoryEditorMenu(itemData, 45,
|
|
||||||
new MineDown(dataOwner.username + "'s Inventory"),
|
|
||||||
viewer.hasPermission(Permission.COMMAND_EDIT_INVENTORIES.node));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InventoryEditorMenu createEnderChestMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
|
||||||
@NotNull OnlineUser viewer) {
|
|
||||||
return new InventoryEditorMenu(itemData, 27,
|
|
||||||
new MineDown(dataOwner.username + "'s Ender Chest"),
|
|
||||||
viewer.hasPermission(Permission.COMMAND_EDIT_ENDER_CHESTS.node));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package net.william278.husksync.editor;
|
||||||
|
|
||||||
|
import de.themoep.minedown.MineDown;
|
||||||
|
import net.william278.husksync.command.Permission;
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.data.ItemData;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class ItemEditorMenu {
|
||||||
|
|
||||||
|
public final ItemData itemData;
|
||||||
|
public final int slotCount;
|
||||||
|
public final MineDown menuTitle;
|
||||||
|
public boolean canEdit;
|
||||||
|
|
||||||
|
private CompletableFuture<ItemData> inventoryDataCompletableFuture;
|
||||||
|
|
||||||
|
private ItemEditorMenu(@NotNull ItemData itemData, int slotCount,
|
||||||
|
@NotNull MineDown menuTitle, boolean canEdit) {
|
||||||
|
this.itemData = itemData;
|
||||||
|
this.menuTitle = menuTitle;
|
||||||
|
this.slotCount = slotCount;
|
||||||
|
this.canEdit = canEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<ItemData> showInventory(@NotNull OnlineUser user) {
|
||||||
|
inventoryDataCompletableFuture = new CompletableFuture<>();
|
||||||
|
user.showMenu(this);
|
||||||
|
return inventoryDataCompletableFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeInventory(@NotNull ItemData itemData) {
|
||||||
|
inventoryDataCompletableFuture.complete(itemData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemEditorMenu createInventoryMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
||||||
|
@NotNull OnlineUser viewer, @NotNull Locales locales,
|
||||||
|
boolean canEdit) {
|
||||||
|
return new ItemEditorMenu(itemData, 45,
|
||||||
|
locales.getLocale("inventory_viewer_menu_title", dataOwner.username).orElse(new MineDown("")),
|
||||||
|
viewer.hasPermission(Permission.COMMAND_INVENTORY_EDIT.node) && canEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemEditorMenu createEnderChestMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
||||||
|
@NotNull OnlineUser viewer, @NotNull Locales locales,
|
||||||
|
boolean canEdit) {
|
||||||
|
return new ItemEditorMenu(itemData, 27,
|
||||||
|
locales.getLocale("ender_chest_viewer_menu_title", dataOwner.username).orElse(new MineDown("")),
|
||||||
|
viewer.hasPermission(Permission.COMMAND_ENDER_CHEST_EDIT.node) && canEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,16 +8,39 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to fire plugin {@link Event}s
|
||||||
|
*/
|
||||||
public abstract class EventCannon {
|
public abstract class EventCannon {
|
||||||
|
|
||||||
protected EventCannon() {
|
protected EventCannon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a {@link PreSyncEvent}
|
||||||
|
*
|
||||||
|
* @param user The user to fire the event for
|
||||||
|
* @param userData The user data to fire the event with
|
||||||
|
* @return A future that will be completed when the event is fired
|
||||||
|
*/
|
||||||
public abstract CompletableFuture<Event> firePreSyncEvent(@NotNull OnlineUser user, @NotNull UserData userData);
|
public abstract CompletableFuture<Event> firePreSyncEvent(@NotNull OnlineUser user, @NotNull UserData userData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a {@link DataSaveEvent}
|
||||||
|
*
|
||||||
|
* @param user The user to fire the event for
|
||||||
|
* @param userData The user data to fire the event with
|
||||||
|
* @return A future that will be completed when the event is fired
|
||||||
|
*/
|
||||||
public abstract CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
public abstract CompletableFuture<Event> fireDataSaveEvent(@NotNull User user, @NotNull UserData userData,
|
||||||
|
|
||||||
@NotNull DataSaveCause saveCause);
|
@NotNull DataSaveCause saveCause);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a {@link SyncCompleteEvent}
|
||||||
|
*
|
||||||
|
* @param user The user to fire the event for
|
||||||
|
*/
|
||||||
public abstract void fireSyncCompleteEvent(@NotNull OnlineUser user);
|
public abstract void fireSyncCompleteEvent(@NotNull OnlineUser user);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package net.william278.husksync.player;
|
|||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.MineDown;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
import net.william278.husksync.editor.ItemEditorMenu;
|
||||||
import net.william278.husksync.event.EventCannon;
|
import net.william278.husksync.event.EventCannon;
|
||||||
import net.william278.husksync.event.PreSyncEvent;
|
import net.william278.husksync.event.PreSyncEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -225,11 +225,11 @@ public abstract class OnlineUser extends User {
|
|||||||
public abstract boolean hasPermission(@NotNull String node);
|
public abstract boolean hasPermission(@NotNull String node);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the player a {@link InventoryEditorMenu} GUI
|
* Show the player a {@link ItemEditorMenu} GUI
|
||||||
*
|
*
|
||||||
* @param menu The {@link InventoryEditorMenu} interface to show
|
* @param menu The {@link ItemEditorMenu} interface to show
|
||||||
*/
|
*/
|
||||||
public abstract void showMenu(@NotNull InventoryEditorMenu menu);
|
public abstract void showMenu(@NotNull ItemEditorMenu menu);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the player's current {@link UserData}
|
* Get the player's current {@link UserData}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public enum RedisKeyType {
|
||||||
|
CACHE(60 * 60 * 24),
|
||||||
|
DATA_UPDATE(10),
|
||||||
|
SERVER_SWITCH(10);
|
||||||
|
|
||||||
|
public final int timeToLive;
|
||||||
|
|
||||||
|
RedisKeyType(int timeToLive) {
|
||||||
|
this.timeToLive = timeToLive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getKeyPrefix() {
|
||||||
|
return RedisManager.KEY_NAMESPACE.toLowerCase() + ":" + RedisManager.clusterId.toLowerCase() + ":" + name().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,16 @@
|
|||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.DataAdapter;
|
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.xerial.snappy.Snappy;
|
import redis.clients.jedis.*;
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
import redis.clients.jedis.JedisPool;
|
|
||||||
import redis.clients.jedis.JedisPoolConfig;
|
|
||||||
import redis.clients.jedis.exceptions.JedisException;
|
import redis.clients.jedis.exceptions.JedisException;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -25,30 +21,25 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
*/
|
*/
|
||||||
public class RedisManager {
|
public class RedisManager {
|
||||||
|
|
||||||
private static final String KEY_NAMESPACE = "husksync:";
|
protected static final String KEY_NAMESPACE = "husksync:";
|
||||||
private static String clusterId = "";
|
protected static String clusterId = "";
|
||||||
|
private final HuskSync plugin;
|
||||||
private final JedisPoolConfig jedisPoolConfig;
|
private final JedisPoolConfig jedisPoolConfig;
|
||||||
private final DataAdapter dataAdapter;
|
|
||||||
|
|
||||||
private final Logger logger;
|
|
||||||
private final String redisHost;
|
private final String redisHost;
|
||||||
private final int redisPort;
|
private final int redisPort;
|
||||||
private final String redisPassword;
|
private final String redisPassword;
|
||||||
private final boolean redisUseSsl;
|
private final boolean redisUseSsl;
|
||||||
|
|
||||||
private JedisPool jedisPool;
|
private JedisPool jedisPool;
|
||||||
|
|
||||||
public RedisManager(@NotNull Settings settings, @NotNull DataAdapter dataAdapter, @NotNull Logger logger) {
|
public RedisManager(@NotNull HuskSync plugin) {
|
||||||
clusterId = settings.getStringValue(Settings.ConfigOption.CLUSTER_ID);
|
this.plugin = plugin;
|
||||||
this.dataAdapter = dataAdapter;
|
clusterId = plugin.getSettings().getStringValue(Settings.ConfigOption.CLUSTER_ID);
|
||||||
this.logger = logger;
|
|
||||||
|
|
||||||
// Set redis credentials
|
// Set redis credentials
|
||||||
this.redisHost = settings.getStringValue(Settings.ConfigOption.REDIS_HOST);
|
this.redisHost = plugin.getSettings().getStringValue(Settings.ConfigOption.REDIS_HOST);
|
||||||
this.redisPort = settings.getIntegerValue(Settings.ConfigOption.REDIS_PORT);
|
this.redisPort = plugin.getSettings().getIntegerValue(Settings.ConfigOption.REDIS_PORT);
|
||||||
this.redisPassword = settings.getStringValue(Settings.ConfigOption.REDIS_PASSWORD);
|
this.redisPassword = plugin.getSettings().getStringValue(Settings.ConfigOption.REDIS_PASSWORD);
|
||||||
this.redisUseSsl = settings.getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL);
|
this.redisUseSsl = plugin.getSettings().getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL);
|
||||||
|
|
||||||
// Configure the jedis pool
|
// Configure the jedis pool
|
||||||
this.jedisPoolConfig = new JedisPoolConfig();
|
this.jedisPoolConfig = new JedisPoolConfig();
|
||||||
@@ -74,10 +65,50 @@ public class RedisManager {
|
|||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
CompletableFuture.runAsync(this::subscribe);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void subscribe() {
|
||||||
|
try (final Jedis subscriber = redisPassword.isBlank() ? new Jedis(redisHost, redisPort, 0, redisUseSsl) :
|
||||||
|
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
||||||
|
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
||||||
|
subscriber.connect();
|
||||||
|
subscriber.subscribe(new JedisPubSub() {
|
||||||
|
@Override
|
||||||
|
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||||
|
RedisMessageType.getTypeFromChannel(channel).ifPresent(messageType -> {
|
||||||
|
if (messageType == RedisMessageType.UPDATE_USER_DATA) {
|
||||||
|
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
||||||
|
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
||||||
|
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
||||||
|
user.setData(userData, plugin.getSettings(), plugin.getEventCannon()).thenRun(() -> {
|
||||||
|
plugin.getLocales().getLocale("data_update_complete")
|
||||||
|
.ifPresent(user::sendActionBar);
|
||||||
|
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, Arrays.stream(RedisMessageType.values()).map(RedisMessageType::getMessageChannel).toArray(String[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
||||||
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
jedis.publish(channel, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> sendUserDataUpdate(@NotNull User user, @NotNull UserData userData) {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
final RedisMessage redisMessage = new RedisMessage(user.uuid, plugin.getDataAdapter().toBytes(userData));
|
||||||
|
redisMessage.dispatch(this, RedisMessageType.UPDATE_USER_DATA);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a user's data to the Redis server
|
* Set a user's data to the Redis server
|
||||||
*
|
*
|
||||||
@@ -92,8 +123,9 @@ public class RedisManager {
|
|||||||
// Set the user's data as a compressed byte array of the json using Snappy
|
// Set the user's data as a compressed byte array of the json using Snappy
|
||||||
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid),
|
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid),
|
||||||
RedisKeyType.DATA_UPDATE.timeToLive,
|
RedisKeyType.DATA_UPDATE.timeToLive,
|
||||||
dataAdapter.toBytes(userData));
|
plugin.getDataAdapter().toBytes(userData));
|
||||||
logger.debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name() + " key to redis at: " +
|
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
||||||
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -108,7 +140,8 @@ public class RedisManager {
|
|||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
||||||
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
||||||
logger.debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name() + " key to redis at: " +
|
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
|
||||||
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -126,7 +159,8 @@ public class RedisManager {
|
|||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
||||||
logger.debug("[" + user.username + "] Read " + RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
plugin.getLoggingAdapter().debug("[" + user.username + "] Read " + RedisKeyType.DATA_UPDATE.name()
|
||||||
|
+ " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
final byte[] dataByteArray = jedis.get(key);
|
final byte[] dataByteArray = jedis.get(key);
|
||||||
if (dataByteArray == null) {
|
if (dataByteArray == null) {
|
||||||
@@ -136,7 +170,7 @@ public class RedisManager {
|
|||||||
jedis.del(key);
|
jedis.del(key);
|
||||||
|
|
||||||
// Use Snappy to decompress the json
|
// Use Snappy to decompress the json
|
||||||
return Optional.of(dataAdapter.fromBytes(dataByteArray));
|
return Optional.of(plugin.getDataAdapter().fromBytes(dataByteArray));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -148,7 +182,8 @@ public class RedisManager {
|
|||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
||||||
logger.debug("[" + user.username + "] Read " + RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
plugin.getLoggingAdapter().debug("[" + user.username + "] Read " + RedisKeyType.SERVER_SWITCH.name()
|
||||||
|
+ " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
final byte[] readData = jedis.get(key);
|
final byte[] readData = jedis.get(key);
|
||||||
if (readData == null) {
|
if (readData == null) {
|
||||||
@@ -176,21 +211,4 @@ public class RedisManager {
|
|||||||
return (keyType.getKeyPrefix() + ":" + uuid).getBytes(StandardCharsets.UTF_8);
|
return (keyType.getKeyPrefix() + ":" + uuid).getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RedisKeyType {
|
|
||||||
CACHE(60 * 60 * 24),
|
|
||||||
DATA_UPDATE(10),
|
|
||||||
SERVER_SWITCH(10);
|
|
||||||
|
|
||||||
public final int timeToLive;
|
|
||||||
|
|
||||||
RedisKeyType(int timeToLive) {
|
|
||||||
this.timeToLive = timeToLive;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getKeyPrefix() {
|
|
||||||
return KEY_NAMESPACE.toLowerCase() + ":" + clusterId.toLowerCase() + ":" + name().toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class RedisMessage {
|
||||||
|
|
||||||
|
public UUID targetUserUuid;
|
||||||
|
public byte[] data;
|
||||||
|
|
||||||
|
public RedisMessage(@NotNull UUID targetUserUuid, byte[] message) {
|
||||||
|
this.targetUserUuid = targetUserUuid;
|
||||||
|
this.data = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedisMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatch(@NotNull RedisManager redisManager, @NotNull RedisMessageType type) {
|
||||||
|
CompletableFuture.runAsync(() -> redisManager.sendMessage(type.getMessageChannel(),
|
||||||
|
new GsonBuilder().create().toJson(this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static RedisMessage fromJson(@NotNull String json) throws JsonSyntaxException {
|
||||||
|
return new GsonBuilder().create().fromJson(json, RedisMessage.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public enum RedisMessageType {
|
||||||
|
|
||||||
|
UPDATE_USER_DATA;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getMessageChannel() {
|
||||||
|
return RedisManager.KEY_NAMESPACE.toLowerCase() + ":" + RedisManager.clusterId.toLowerCase()
|
||||||
|
+ ":" + name().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<RedisMessageType> getTypeFromChannel(@NotNull String messageChannel) {
|
||||||
|
return Arrays.stream(values()).filter(messageType -> messageType.getMessageChannel()
|
||||||
|
.equalsIgnoreCase(messageChannel)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[Daten synchronisiert!](#00fb9a)'
|
synchronisation_complete: '[Daten synchronisiert!](#00fb9a)'
|
||||||
viewing_inventory_of: '[Einsicht in das Inventar von](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[Einsicht in die Endertruhe von](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_ender_chest_of: '[Einsicht in die Endertruhe von](#00fb9a) [%1%](#00fb9a bold)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Meldungsdateien wurden aktualisiert.](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Meldungsdateien wurden aktualisiert.](#00fb9a)'
|
||||||
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze: %1%](#ff7e5e)'
|
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[Fehler:](#ff3300) [Du kannst nicht auf dein
|
|||||||
error_console_command_only: '[Fehler:](#ff3300) [Dieser Befehl kann nur über die %1% Konsole ausgeführt werden](#ff7e5e)'
|
error_console_command_only: '[Fehler:](#ff3300) [Dieser Befehl kann nur über die %1% Konsole ausgeführt werden](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[Fehler:](#ff3300) [Vorgang konnte nicht verarbeitet werden; Es sind keine Server online, auf denen HuskSync installiert ist. Bitte stelle sicher, dass HuskSync sowohl auf dem Proxy-Server als auch auf allen Servern installiert ist, zwischen denen du Daten synchronisieren möchtest.](#ff7e5e)'
|
error_no_servers_proxied: '[Fehler:](#ff3300) [Vorgang konnte nicht verarbeitet werden; Es sind keine Server online, auf denen HuskSync installiert ist. Bitte stelle sicher, dass HuskSync sowohl auf dem Proxy-Server als auch auf allen Servern installiert ist, zwischen denen du Daten synchronisieren möchtest.](#ff7e5e)'
|
||||||
error_invalid_cluster: '[Fehler:](#ff3300) [Bitte gib die ID eines gültigen Clusters an.](#ff7e5e)'
|
error_invalid_cluster: '[Fehler:](#ff3300) [Bitte gib die ID eines gültigen Clusters an.](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
synchronisation_complete: '[Data synchronised!](#00fb9a)'
|
synchronisation_complete: '[⏵ Data synchronised!](#00fb9a)'
|
||||||
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold)'
|
|
||||||
viewing_ender_chest_of: '[Viewing the ender chest of](#00fb9a) [%1%](#00fb9a bold)'
|
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)'
|
||||||
error_invalid_syntax: '[Error:](#ff3300) [Incorrect syntax. Usage: %1%](#ff7e5e)'
|
error_invalid_syntax: '[Error:](#ff3300) [Incorrect syntax. Usage: %1%](#ff7e5e)'
|
||||||
error_invalid_player: '[Error:](#ff3300) [Could not find that player.](#ff7e5e)'
|
error_invalid_player: '[Error:](#ff3300) [Could not find that player.](#ff7e5e)'
|
||||||
@@ -15,3 +13,19 @@ error_invalid_cluster: '[Error:](#ff3300) [Please specify the ID of a valid clus
|
|||||||
|
|
||||||
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '&0%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '&0%1%''s Ender Chest'
|
||||||
|
inventory_viewer_opened: '[Viewing snapshot of](#00fb9a) [%1%](#00fb9a bold) [''s inventory as of ⌚ %2%](#00fb9a)'
|
||||||
|
ender_chest_viewer_opened: '[Viewing snapshot of](#00fb9a) [%1%](#00fb9a bold) [''s Ender Chest as of ⌚ %2%](#00fb9a)'
|
||||||
|
data_update_complete: '[🔔 Your data has been updated!](#00fb9a)'
|
||||||
|
|
||||||
|
data_manager_title: '[Viewing user data snapshot for](#00fb9a) [%1%](#00fb9a bold show_text=&7UUID: %2%)[:](#00fb9a)'
|
||||||
|
data_manager_versioning: '[⌚ %1%](gray show_text=&7Version timestamp: &7When the data was saved) [⚡ %2%](gray show_text=&7Version UUID: &7%3%) [⚑ %4%](gray show_text=&7Save cause: &7What caused the data to be saved)'
|
||||||
|
data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
||||||
|
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7⚠ Based on in-game statistics)'
|
||||||
|
data_manager_item_buttons: '[[🪣 Inventory…]](color=#a17b5f-#f5b98c show_text=&7Click to view run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Click to view run_command=/enderchest %1% %2%)'
|
||||||
|
data_manager_management_buttons: '[Manage:](gray) [[❌ Delete…]](#ff3300 show_text=&7Click to delete this user data run_command=/userdata delete %1% %2%) [[⏪ Restore…]](#00fb9a show_text=&7Click to restore this user data. &#ff3300&⚠ Warning: %1%''s current data will be overwritten! run_command=/userdata delete %1% %2%)'
|
||||||
|
data_manager_advancement_preview_remaining: '&7+%1% more…'
|
||||||
|
|
||||||
|
data_list_title: '[List of](#00fb9a) [%1%](#00fb9a bold show_text=&7UUID: %2%)[''s user data snapshots:](#00fb9a)'
|
||||||
|
data_list_item: '[%1%](gray run_command=/userdata view %6% %4%) [⌚ %2%](color=#ffc43b-#f5c962 show_text=&7Version timestamp&7When the data was saved run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Version UUID:&7%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Save cause&7What caused the data to be saved run_command=/userdata view %6% %4%)'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[Datos sincronizados!](#00fb9a)'
|
synchronisation_complete: '[Datos sincronizados!](#00fb9a)'
|
||||||
viewing_inventory_of: '[Viendo el inventario de](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[Viendo el Ender Chest de](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_ender_chest_of: '[Viendo el Ender Chest de](#00fb9a) [%1%](#00fb9a bold)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Se ha reiniciado la configuración y los archivos de los mensajes.](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Se ha reiniciado la configuración y los archivos de los mensajes.](#00fb9a)'
|
||||||
error_invalid_syntax: '[Error:](#ff3300) [Sintaxis incorrecta. Uso: %1%](#ff7e5e)'
|
error_invalid_syntax: '[Error:](#ff3300) [Sintaxis incorrecta. Uso: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[Error:](#ff3300) [No puedes acceder a tu En
|
|||||||
error_console_command_only: '[Error:](#ff3300) [Ese comando solo puede ser ejecutado desde la %1% consola](#ff7e5e)'
|
error_console_command_only: '[Error:](#ff3300) [Ese comando solo puede ser ejecutado desde la %1% consola](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[Error:](#ff3300) [Ha ocurrido un error mientras se procesaba la acción; no hay servidores online con HusckSync instalado. Por favor, asegúrate que HuskSync está instalado tanto en el proxy como en todos los servidores entre los que quieres sincronizar datos.](#ff7e5e)'
|
error_no_servers_proxied: '[Error:](#ff3300) [Ha ocurrido un error mientras se procesaba la acción; no hay servidores online con HusckSync instalado. Por favor, asegúrate que HuskSync está instalado tanto en el proxy como en todos los servidores entre los que quieres sincronizar datos.](#ff7e5e)'
|
||||||
error_invalid_cluster: '[Error:](#ff3300) [Por favor, especifica la ID de un cluster válido.](#ff7e5e)'
|
error_invalid_cluster: '[Error:](#ff3300) [Por favor, especifica la ID de un cluster válido.](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[データが同期されました!](#00fb9a)'
|
synchronisation_complete: '[データが同期されました!](#00fb9a)'
|
||||||
viewing_inventory_of: '[%1%](#00fb9a bold) [のインベントリを表示します](#00fb9a) '
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[%1%](#00fb9a bold) [のエンダーチェストを表示します](#00fb9a) '
|
viewing_ender_chest_of: '[%1%](#00fb9a bold) [のエンダーチェストを表示します](#00fb9a) '
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)'
|
||||||
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法: %1%](#ff7e5e)'
|
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[Error:](#ff3300) [自分のエンダーチ
|
|||||||
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[Error:](#ff3300) [操作の処理に失敗; HuskSyncがインストールされているサーバーがオンラインになっていません。プロキシサーバーとデータを同期させたいすべてのサーバーにHuskSyncがインストールされていることを確認してください。](#ff7e5e)'
|
error_no_servers_proxied: '[Error:](#ff3300) [操作の処理に失敗; HuskSyncがインストールされているサーバーがオンラインになっていません。プロキシサーバーとデータを同期させたいすべてのサーバーにHuskSyncがインストールされていることを確認してください。](#ff7e5e)'
|
||||||
error_invalid_cluster: '[Error:](#ff3300) [有効なクラスターのIDを指定してください。](#ff7e5e)'
|
error_invalid_cluster: '[Error:](#ff3300) [有効なクラスターのIDを指定してください。](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[Дані синхронізовано!](#00fb9a)'
|
synchronisation_complete: '[Дані синхронізовано!](#00fb9a)'
|
||||||
viewing_inventory_of: '[Переглядання інвентару](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[Переглядання скрині енду](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_ender_chest_of: '[Переглядання скрині енду](#00fb9a) [%1%](#00fb9a bold)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)'
|
||||||
error_invalid_syntax: '[Помилка:](#ff3300) [Неправильний синтакс. Використання: %1%](#ff7e5e)'
|
error_invalid_syntax: '[Помилка:](#ff3300) [Неправильний синтакс. Використання: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[Помилка:](#ff3300) [Ви не мож
|
|||||||
error_console_command_only: '[Помилка:](#ff3300) [Ця команда може бути використана лише з допомогою %1% консолі](#ff7e5e)'
|
error_console_command_only: '[Помилка:](#ff3300) [Ця команда може бути використана лише з допомогою %1% консолі](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[Помилка:](#ff3300) [Не вдалося опрацювати операцію; не знайдено жодного сервера із встановленим HuskSync. Запевніться, будьласка, що HuskSync встановлено на Проксі та усіх серверах між якими ви хочете синхроніхувати дані.](#ff7e5e)'
|
error_no_servers_proxied: '[Помилка:](#ff3300) [Не вдалося опрацювати операцію; не знайдено жодного сервера із встановленим HuskSync. Запевніться, будьласка, що HuskSync встановлено на Проксі та усіх серверах між якими ви хочете синхроніхувати дані.](#ff7e5e)'
|
||||||
error_invalid_cluster: '[Помилка:](#ff3300) [Зазнчте будь ласка ID слушного кластеру.](#ff7e5e)'
|
error_invalid_cluster: '[Помилка:](#ff3300) [Зазнчте будь ласка ID слушного кластеру.](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[数据同步完成](#00fb9a)'
|
synchronisation_complete: '[数据同步完成](#00fb9a)'
|
||||||
viewing_inventory_of: '[查看玩家背包:](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[查看玩家末影箱:](#00fb9a) [%1%](#00fb9a bold)'
|
viewing_ender_chest_of: '[查看玩家末影箱:](#00fb9a) [%1%](#00fb9a bold)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 配置与语言文件重载完成.](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 配置与语言文件重载完成.](#00fb9a)'
|
||||||
error_invalid_syntax: '[错误:](#ff3300) [格式错误. 使用方法: %1%](#ff7e5e)'
|
error_invalid_syntax: '[错误:](#ff3300) [格式错误. 使用方法: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[错误:](#ff3300) [你不能查看和编辑
|
|||||||
error_console_command_only: '[错误:](#ff3300) [该命令只能由 %1% 控制台执行](#ff7e5e)'
|
error_console_command_only: '[错误:](#ff3300) [该命令只能由 %1% 控制台执行](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[错误:](#ff3300) [操作处理失败; 没有任何安装了 HuskSync 的后端服务器在线. 请确认 HuskSync 已在 BungeeCord/Velocity 等代理服务器和所有你希望互相同步数据的后端服务器间安装.](#ff7e5e)'
|
error_no_servers_proxied: '[错误:](#ff3300) [操作处理失败; 没有任何安装了 HuskSync 的后端服务器在线. 请确认 HuskSync 已在 BungeeCord/Velocity 等代理服务器和所有你希望互相同步数据的后端服务器间安装.](#ff7e5e)'
|
||||||
error_invalid_cluster: '[错误:](#ff3300) [请指定一个有效的集群(cluster) ID.](#ff7e5e)'
|
error_invalid_cluster: '[错误:](#ff3300) [请指定一个有效的集群(cluster) ID.](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
synchronisation_complete: '[資料已同步!!](#00fb9a)'
|
synchronisation_complete: '[資料已同步!!](#00fb9a)'
|
||||||
viewing_inventory_of: '[查看](#00fb9a) [%1%](#00fb9a bold) [的背包](#00fb9a)'
|
viewing_inventory_of: '[Viewing the inventory of](#00fb9a) [%1%](#00fb9a bold) [as of %2%](#00fb9a)'
|
||||||
viewing_ender_chest_of: '[查看](#00fb9a) [%1%](#00fb9a bold) [的終界箱](#00fb9a)'
|
viewing_ender_chest_of: '[查看](#00fb9a) [%1%](#00fb9a bold) [的終界箱](#00fb9a)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)'
|
||||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法: %1%](#ff7e5e)'
|
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法: %1%](#ff7e5e)'
|
||||||
@@ -12,3 +12,8 @@ error_cannot_view_own_ender_chest: '[錯誤:](#ff3300) [你無法查看自己的
|
|||||||
error_console_command_only: '[錯誤:](#ff3300) [該指令只能通過 %1% 控制台運行](#ff7e5e)'
|
error_console_command_only: '[錯誤:](#ff3300) [該指令只能通過 %1% 控制台運行](#ff7e5e)'
|
||||||
error_no_servers_proxied: '[錯誤:](#ff3300) [處理操作失敗: 沒有安裝 HuskSync 的伺服器在線。 請確保在 Proxy 伺服器和您希望在其他同步數據的所有伺服器上都安裝了 HuskSync。](#ff7e5e)'
|
error_no_servers_proxied: '[錯誤:](#ff3300) [處理操作失敗: 沒有安裝 HuskSync 的伺服器在線。 請確保在 Proxy 伺服器和您希望在其他同步數據的所有伺服器上都安裝了 HuskSync。](#ff7e5e)'
|
||||||
error_invalid_cluster: '[錯誤:](#ff3300) [請提供有效的 Cluster ID](#ff7e5e)'
|
error_invalid_cluster: '[錯誤:](#ff3300) [請提供有效的 Cluster ID](#ff7e5e)'
|
||||||
|
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '�fb9a&%1%''s Inventory'
|
||||||
|
ender_chest_viewer_menu_title: '�fb9a&%1%''s Ender Chest'
|
||||||
@@ -2,7 +2,7 @@ package net.william278.husksync.player;
|
|||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.MineDown;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.editor.InventoryEditorMenu;
|
import net.william278.husksync.editor.ItemEditorMenu;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -149,7 +149,7 @@ public class DummyPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showMenu(@NotNull InventoryEditorMenu menu) {
|
public void showMenu(@NotNull ItemEditorMenu menu) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user