diff --git a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java index d95d0b20..376c2aa6 100644 --- a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java +++ b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java @@ -6,10 +6,7 @@ import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; -import net.william278.husksync.command.BukkitCommand; -import net.william278.husksync.command.CommandBase; -import net.william278.husksync.command.HuskSyncCommand; -import net.william278.husksync.command.Permission; +import net.william278.husksync.command.*; import net.william278.husksync.config.Locales; import net.william278.husksync.config.Settings; import net.william278.husksync.data.CompressedDataAdapter; @@ -17,6 +14,7 @@ import net.william278.husksync.data.DataAdapter; import net.william278.husksync.data.JsonDataAdapter; import net.william278.husksync.database.Database; import net.william278.husksync.database.MySqlDatabase; +import net.william278.husksync.editor.DataEditor; import net.william278.husksync.listener.BukkitEventListener; import net.william278.husksync.listener.EventListener; import net.william278.husksync.player.BukkitPlayer; @@ -51,6 +49,8 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync { private DataAdapter dataAdapter; + private DataEditor dataEditor; + private Settings settings; private Locales locales; @@ -97,6 +97,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync { return loadedSettings; }).join(); }).thenApply(succeeded -> { + // Prepare data adapter if (succeeded) { if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_COMPRESS_DATA)) { dataAdapter = new CompressedDataAdapter(); @@ -105,6 +106,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync { } } return succeeded; + }).thenApply(succeeded -> { + // Prepare data editor + if (succeeded) { + dataEditor = new DataEditor(); + } + return succeeded; }).thenApply(succeeded -> { // Establish connection to the database this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter); @@ -160,11 +167,10 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync { }))); // Register commands - final CommandBase[] commands = new CommandBase[]{new HuskSyncCommand(this)}; - for (CommandBase commandBase : commands) { - final PluginCommand pluginCommand = getCommand(commandBase.command); + for (final BukkitCommandType bukkitCommandType : BukkitCommandType.values()) { + final PluginCommand pluginCommand = getCommand(bukkitCommandType.commandBase.command); if (pluginCommand != null) { - new BukkitCommand(commandBase, this).register(pluginCommand); + new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand); } } getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands"); @@ -226,6 +232,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync { return dataAdapter; } + @Override + public @NotNull DataEditor getDataEditor() { + return dataEditor; + } + @Override public @NotNull Settings getSettings() { return settings; diff --git a/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java b/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java index 2785f8a3..0a6962d2 100644 --- a/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java +++ b/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java @@ -67,4 +67,5 @@ public class BukkitCommand implements CommandExecutor, TabExecutor { } return Collections.emptyList(); } + } diff --git a/bukkit/src/main/java/net/william278/husksync/command/BukkitCommandType.java b/bukkit/src/main/java/net/william278/husksync/command/BukkitCommandType.java new file mode 100644 index 00000000..b8bd22b1 --- /dev/null +++ b/bukkit/src/main/java/net/william278/husksync/command/BukkitCommandType.java @@ -0,0 +1,19 @@ +package net.william278.husksync.command; + +import net.william278.husksync.BukkitHuskSync; +import org.jetbrains.annotations.NotNull; + +/** + * Commands available on the Bukkit HuskSync implementation + */ +public enum BukkitCommandType { + HUSKSYNC_COMMAND(new HuskSyncCommand(BukkitHuskSync.getInstance())), + HUSKSYNC_INVSEE(new InvseeCommand(BukkitHuskSync.getInstance())), + HUSKSYNC_ECHEST(new EchestCommand(BukkitHuskSync.getInstance())); + + public final CommandBase commandBase; + + BukkitCommandType(@NotNull CommandBase commandBase) { + this.commandBase = commandBase; + } +} diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java index 0419fe56..6dcd4856 100644 --- a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java +++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java @@ -21,7 +21,7 @@ public class BukkitSerializer { * @param inventoryContents The contents of the inventory * @return The serialized inventory contents */ - public static CompletableFuture serializeInventory(ItemStack[] inventoryContents) { + public static CompletableFuture serializeInventory(ItemStack[] inventoryContents) throws DataDeserializationException { return CompletableFuture.supplyAsync(() -> { // Return an empty string if there is no inventory item data to serialize if (inventoryContents.length == 0) { @@ -54,7 +54,7 @@ public class BukkitSerializer { * @param inventoryData The serialized {@link ItemStack[]} array * @return The inventory contents as an array of {@link ItemStack}s */ - public static CompletableFuture deserializeInventory(String inventoryData) { + public static CompletableFuture deserializeInventory(String inventoryData) throws DataDeserializationException { return CompletableFuture.supplyAsync(() -> { // Return empty array if there is no inventory data (set the player as having an empty inventory) if (inventoryData.isEmpty()) { @@ -89,6 +89,7 @@ public class BukkitSerializer { * @param item The {@link ItemStack} to serialize * @return The serialized {@link ItemStack} */ + @Nullable private static Map serializeItemStack(ItemStack item) { return item != null ? item.serialize() : null; } @@ -100,6 +101,7 @@ public class BukkitSerializer { * @return The deserialized {@link ItemStack} */ @SuppressWarnings("unchecked") // Ignore the "Unchecked cast" warning + @Nullable private static ItemStack deserializeItemStack(Object serializedItemStack) { return serializedItemStack != null ? ItemStack.deserialize((Map) serializedItemStack) : null; } @@ -110,7 +112,7 @@ public class BukkitSerializer { * @param potionEffects The potion effect array * @return The serialized potion effects */ - public static CompletableFuture serializePotionEffects(PotionEffect[] potionEffects) { + public static CompletableFuture serializePotionEffects(PotionEffect[] potionEffects) throws DataDeserializationException { return CompletableFuture.supplyAsync(() -> { // Return an empty string if there are no effects to serialize if (potionEffects.length == 0) { @@ -143,7 +145,7 @@ public class BukkitSerializer { * @param potionEffectData The serialized {@link PotionEffect[]} array * @return The {@link PotionEffect}s */ - public static CompletableFuture deserializePotionEffects(String potionEffectData) { + public static CompletableFuture deserializePotionEffects(String potionEffectData) throws DataDeserializationException { return CompletableFuture.supplyAsync(() -> { // Return empty array if there is no potion effect data (don't apply any effects to the player) if (potionEffectData.isEmpty()) { diff --git a/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java b/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java index 96eb42ae..83e48a85 100644 --- a/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java +++ b/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java @@ -1,15 +1,29 @@ package net.william278.husksync.listener; import net.william278.husksync.BukkitHuskSync; +import net.william278.husksync.data.BukkitSerializer; +import net.william278.husksync.data.DataDeserializationException; +import net.william278.husksync.data.InventoryData; import net.william278.husksync.player.BukkitPlayer; +import net.william278.husksync.player.OnlineUser; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldSaveEvent; import org.jetbrains.annotations.NotNull; +import java.util.logging.Level; import java.util.stream.Collectors; public class BukkitEventListener extends EventListener implements Listener { @@ -36,11 +50,60 @@ public class BukkitEventListener extends EventListener implements Listener { .collect(Collectors.toList())); } - /*@EventHandler(ignoreCancelled = true) - public void onGenericPlayerEvent(@NotNull PlayerEvent event) { - if (event instanceof Cancellable) { - ((Cancellable) event).setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer()))); + @EventHandler(ignoreCancelled = true) + public void onInventoryClose(@NotNull InventoryCloseEvent event) { + if (event.getPlayer() instanceof Player player) { + final OnlineUser user = BukkitPlayer.adapt(player); + if (huskSync.getDataEditor().isEditingInventoryData(user)) { + try { + BukkitSerializer.serializeInventory(event.getInventory().getContents()).thenAccept( + serializedInventory -> super.handleMenuClose(user, new InventoryData(serializedInventory))); + } catch (DataDeserializationException e) { + huskSync.getLoggingAdapter().log(Level.SEVERE, + "Failed to serialize inventory data during menu close", e); + } + } + } + } + + /* + * Events to cancel if the player has not been set yet + */ + + @EventHandler(priority = EventPriority.HIGHEST) + public void onDropItem(@NotNull PlayerDropItemEvent event) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer()))); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPickupItem(@NotNull EntityPickupItemEvent event) { + if (event.getEntity() instanceof Player player) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player))); } - }*/ + + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerInteract(@NotNull PlayerInteractEvent event) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer()))); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockPlace(@NotNull BlockPlaceEvent event) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer()))); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(@NotNull BlockBreakEvent event) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer()))); + + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryOpen(@NotNull InventoryOpenEvent event) { + if (event.getPlayer() instanceof Player player) { + event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player))); + } + } } diff --git a/bukkit/src/main/java/net/william278/husksync/player/BukkitPlayer.java b/bukkit/src/main/java/net/william278/husksync/player/BukkitPlayer.java index 6982b716..23e3486a 100644 --- a/bukkit/src/main/java/net/william278/husksync/player/BukkitPlayer.java +++ b/bukkit/src/main/java/net/william278/husksync/player/BukkitPlayer.java @@ -2,8 +2,10 @@ package net.william278.husksync.player; import de.themoep.minedown.MineDown; import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.data.*; +import net.william278.husksync.editor.InventoryEditorMenu; import org.apache.commons.lang.ArrayUtils; import org.bukkit.*; import org.bukkit.advancement.Advancement; @@ -12,6 +14,7 @@ import org.bukkit.attribute.Attribute; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.bukkit.potion.PotionEffect; @@ -423,6 +426,20 @@ public class BukkitPlayer extends OnlineUser { return player.hasPermission(node); } + @Override + public void showMenu(@NotNull InventoryEditorMenu menu) { + BukkitSerializer.deserializeInventory(menu.inventoryData.serializedInventory).thenAccept(inventoryContents -> { + final Inventory inventory = Bukkit.createInventory(player, menu.slotCount, + BaseComponent.toLegacyText(menu.menuTitle.toComponent())); + inventory.setContents(inventoryContents); + Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> { + player.closeInventory(); + player.openInventory(inventory); + }); + }); + + } + @Override public void sendActionBar(@NotNull MineDown mineDown) { player.spigot().sendMessage(ChatMessageType.ACTION_BAR, mineDown.toComponent()); diff --git a/common/src/main/java/net/william278/husksync/HuskSync.java b/common/src/main/java/net/william278/husksync/HuskSync.java index a689a749..56020775 100644 --- a/common/src/main/java/net/william278/husksync/HuskSync.java +++ b/common/src/main/java/net/william278/husksync/HuskSync.java @@ -3,6 +3,7 @@ package net.william278.husksync; import net.william278.husksync.config.Locales; import net.william278.husksync.config.Settings; import net.william278.husksync.data.DataAdapter; +import net.william278.husksync.editor.DataEditor; import net.william278.husksync.database.Database; import net.william278.husksync.player.OnlineUser; import net.william278.husksync.redis.RedisManager; @@ -26,6 +27,8 @@ public interface HuskSync { @NotNull DataAdapter getDataAdapter(); + @NotNull DataEditor getDataEditor(); + @NotNull Settings getSettings(); @NotNull Locales getLocales(); diff --git a/common/src/main/java/net/william278/husksync/command/EchestCommand.java b/common/src/main/java/net/william278/husksync/command/EchestCommand.java new file mode 100644 index 00000000..f5212245 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/command/EchestCommand.java @@ -0,0 +1,63 @@ +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.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 ") + .ifPresent(player::sendMessage); + return; + } + plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAcceptAsync(optionalUser -> { + optionalUser.ifPresentOrElse(user -> { + List userData = plugin.getDatabase().getUserData(user).join(); + Optional 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 [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 -> { + final UserData updatedUserData = new UserData(data.getStatusData(), + data.getInventoryData(), menu.canEdit ? inventoryDataOnClose : data.getEnderChestData(), + data.getPotionEffectData(), data.getAdvancementData(), + data.getStatisticData(), data.getLocationData(), + data.getPersistentDataContainerData()); + plugin.getDatabase().setUserData(user, updatedUserData).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)); + }); + } + +} diff --git a/common/src/main/java/net/william278/husksync/command/InvseeCommand.java b/common/src/main/java/net/william278/husksync/command/InvseeCommand.java new file mode 100644 index 00000000..e94a0a82 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/command/InvseeCommand.java @@ -0,0 +1,63 @@ +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.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 ") + .ifPresent(player::sendMessage); + return; + } + plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAcceptAsync(optionalUser -> { + optionalUser.ifPresentOrElse(user -> { + List userData = plugin.getDatabase().getUserData(user).join(); + Optional 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 [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 -> { + final UserData updatedUserData = new UserData(data.getStatusData(), + menu.canEdit ? inventoryDataOnClose : data.getInventoryData(), + data.getEnderChestData(), data.getPotionEffectData(), data.getAdvancementData(), + data.getStatisticData(), data.getLocationData(), + data.getPersistentDataContainerData()); + plugin.getDatabase().setUserData(user, updatedUserData).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)); + }); + } + +} diff --git a/common/src/main/java/net/william278/husksync/editor/DataEditor.java b/common/src/main/java/net/william278/husksync/editor/DataEditor.java new file mode 100644 index 00000000..c0cf35ae --- /dev/null +++ b/common/src/main/java/net/william278/husksync/editor/DataEditor.java @@ -0,0 +1,90 @@ +package net.william278.husksync.editor; + +import net.william278.husksync.config.Locales; +import net.william278.husksync.data.InventoryData; +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.HashMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Provides methods for displaying and editing user data + */ +public class DataEditor { + + /** + * Map of currently open inventory and ender chest data editors + */ + @NotNull + protected final HashMap openInventoryMenus; + + public DataEditor() { + this.openInventoryMenus = new HashMap<>(); + } + + /** + * Open an inventory or ender chest editor menu + * + * @param user The online user to open the editor for + * @param inventoryEditorMenu The {@link InventoryEditorMenu} to open + * @return The inventory editor menu + * @see InventoryEditorMenu#createInventoryMenu(InventoryData, User, OnlineUser) + * @see InventoryEditorMenu#createEnderChestMenu(InventoryData, User, OnlineUser) + */ + public CompletableFuture openInventoryMenu(@NotNull OnlineUser user, + @NotNull InventoryEditorMenu inventoryEditorMenu) { + this.openInventoryMenus.put(user.uuid, inventoryEditorMenu); + return inventoryEditorMenu.showInventory(user); + } + + /** + * Close an inventory or ender chest editor menu + * + * @param user The online user to close the editor for + * @param inventoryData the {@link InventoryData} contained within the menu at the time of closing + */ + public void closeInventoryMenu(@NotNull OnlineUser user, @NotNull InventoryData inventoryData) { + if (this.openInventoryMenus.containsKey(user.uuid)) { + this.openInventoryMenus.get(user.uuid).closeInventory(inventoryData); + } + this.openInventoryMenus.remove(user.uuid); + } + + /** + * Returns whether edits to the inventory or ender chest menu are allowed + * + * @param user The online user with an inventory open to check + * @return {@code true} if edits to the inventory or ender chest menu are allowed; {@code false} otherwise, including if they don't have an inventory open + */ + public boolean cancelInventoryEdit(@NotNull OnlineUser user) { + if (this.openInventoryMenus.containsKey(user.uuid)) { + return !this.openInventoryMenus.get(user.uuid).canEdit; + } + return false; + } + + /** + * Display a chat message detailing information about {@link VersionedUserData} + * + * @param user The online user to display the message to + * @param userData The {@link VersionedUserData} to display information about + * @param dataOwner The {@link User} who owns the {@link VersionedUserData} + */ + public void displayDataOverview(@NotNull OnlineUser user, @NotNull VersionedUserData userData, + @NotNull User dataOwner) { + //todo + } + + /** + * Returns whether the user has an inventory editor menu open + * @param user {@link OnlineUser} to check + * @return {@code true} if the user has an inventory editor open; {@code false} otherwise + */ + public boolean isEditingInventoryData(@NotNull OnlineUser user) { + return this.openInventoryMenus.containsKey(user.uuid); + } +} diff --git a/common/src/main/java/net/william278/husksync/editor/InventoryEditorMenu.java b/common/src/main/java/net/william278/husksync/editor/InventoryEditorMenu.java new file mode 100644 index 00000000..3a66ca3e --- /dev/null +++ b/common/src/main/java/net/william278/husksync/editor/InventoryEditorMenu.java @@ -0,0 +1,53 @@ +package net.william278.husksync.editor; + +import de.themoep.minedown.MineDown; +import net.william278.husksync.command.Permission; +import net.william278.husksync.data.InventoryData; +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 InventoryData inventoryData; + public final int slotCount; + public final MineDown menuTitle; + public final boolean canEdit; + + private CompletableFuture inventoryDataCompletableFuture; + + private InventoryEditorMenu(@NotNull InventoryData inventoryData, int slotCount, + @NotNull MineDown menuTitle, boolean canEdit) { + this.inventoryData = inventoryData; + this.menuTitle = menuTitle; + this.slotCount = slotCount; + this.canEdit = canEdit; + } + + public CompletableFuture showInventory(@NotNull OnlineUser user) { + inventoryDataCompletableFuture = new CompletableFuture<>(); + user.showMenu(this); + return inventoryDataCompletableFuture; + } + + public void closeInventory(@NotNull InventoryData inventoryData) { + inventoryDataCompletableFuture.completeAsync(() -> inventoryData); + } + + public static InventoryEditorMenu createInventoryMenu(@NotNull InventoryData inventoryData, @NotNull User dataOwner, + @NotNull OnlineUser viewer) { + return new InventoryEditorMenu(inventoryData, 45, + new MineDown(dataOwner.username + "'s Inventory"), + viewer.hasPermission(Permission.COMMAND_EDIT_INVENTORIES.node)); + } + + public static InventoryEditorMenu createEnderChestMenu(@NotNull InventoryData inventoryData, @NotNull User dataOwner, + @NotNull OnlineUser viewer) { + return new InventoryEditorMenu(inventoryData, 27, + new MineDown(dataOwner.username + "'s Ender Chest"), + viewer.hasPermission(Permission.COMMAND_EDIT_ENDER_CHESTS.node)); + } + +} diff --git a/common/src/main/java/net/william278/husksync/listener/EventListener.java b/common/src/main/java/net/william278/husksync/listener/EventListener.java index 21e4268f..b314ae14 100644 --- a/common/src/main/java/net/william278/husksync/listener/EventListener.java +++ b/common/src/main/java/net/william278/husksync/listener/EventListener.java @@ -2,6 +2,7 @@ package net.william278.husksync.listener; import net.william278.husksync.HuskSync; import net.william278.husksync.config.Settings; +import net.william278.husksync.data.InventoryData; import net.william278.husksync.player.OnlineUser; import org.jetbrains.annotations.NotNull; @@ -19,7 +20,7 @@ public abstract class EventListener { /** * The plugin instance */ - private final HuskSync huskSync; + protected final HuskSync huskSync; /** * Set of UUIDs current awaiting item synchronization. Events will be cancelled for these users @@ -84,6 +85,7 @@ public abstract class EventListener { } public final void handlePlayerQuit(@NotNull OnlineUser user) { + // Players quitting have their data manually saved by the plugin disable hook if (disabling) { return; } @@ -110,8 +112,22 @@ public abstract class EventListener { huskSync.getRedisManager().close(); } + public final void handleMenuClose(@NotNull OnlineUser user, @NotNull InventoryData menuInventory) { + if (disabling) { + return; + } + huskSync.getDataEditor().closeInventoryMenu(user, menuInventory); + } + + public final boolean cancelMenuClick(@NotNull OnlineUser user) { + if (disabling) { + return true; + } + return huskSync.getDataEditor().cancelInventoryEdit(user); + } + public final boolean cancelPlayerEvent(@NotNull OnlineUser user) { - return usersAwaitingSync.contains(user.uuid); + return disabling || usersAwaitingSync.contains(user.uuid); } } diff --git a/common/src/main/java/net/william278/husksync/player/OnlineUser.java b/common/src/main/java/net/william278/husksync/player/OnlineUser.java index 509e86c8..7d1aa845 100644 --- a/common/src/main/java/net/william278/husksync/player/OnlineUser.java +++ b/common/src/main/java/net/william278/husksync/player/OnlineUser.java @@ -3,6 +3,7 @@ package net.william278.husksync.player; import de.themoep.minedown.MineDown; import net.william278.husksync.config.Settings; import net.william278.husksync.data.*; +import net.william278.husksync.editor.InventoryEditorMenu; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -236,6 +237,13 @@ public abstract class OnlineUser extends User { */ public abstract boolean hasPermission(@NotNull String node); + /** + * Show the player a {@link InventoryEditorMenu} GUI + * + * @param menu The {@link InventoryEditorMenu} interface to show + */ + public abstract void showMenu(@NotNull InventoryEditorMenu menu); + /** * Get the player's current {@link UserData} * diff --git a/common/src/main/resources/locales/en-gb.yml b/common/src/main/resources/locales/en-gb.yml index cedb844b..3d04172b 100644 --- a/common/src/main/resources/locales/en-gb.yml +++ b/common/src/main/resources/locales/en-gb.yml @@ -3,7 +3,7 @@ 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)' 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)' error_no_permission: '[Error:](#ff3300) [You do not have permission to execute this command](#ff7e5e)' error_cannot_view_inventory_online: '[Error:](#ff3300) [You can''t access the inventory of an online player through HuskSync](#ff7e5e)' error_cannot_view_ender_chest_online: '[Error:](#ff3300) [You can''t access the ender chest of an online player through HuskSync](#ff7e5e)' @@ -11,4 +11,7 @@ error_cannot_view_own_inventory: '[Error:](#ff3300) [You can''t access your own error_cannot_view_own_ender_chest: '[Error:](#ff3300) [You can''t access your own ender chest!](#ff7e5e)' error_console_command_only: '[Error:](#ff3300) [That command can only be run through the %1% console](#ff7e5e)' error_no_servers_proxied: '[Error:](#ff3300) [Failed to process operation; no servers are online that have HuskSync installed. Please ensure HuskSync is installed on both the Proxy server and all servers you wish to synchronise data between.](#ff7e5e)' -error_invalid_cluster: '[Error:](#ff3300) [Please specify the ID of a valid cluster.](#ff7e5e)' \ No newline at end of file +error_invalid_cluster: '[Error:](#ff3300) [Please specify the ID of a valid cluster.](#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)' \ No newline at end of file