-
-
Notifications
You must be signed in to change notification settings - Fork 86
Add /mvinv view Command for player inventory inspection across worlds #614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 19 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
24b408c
Add Chinese translation file to the project
xiaohuang2004 55cd262
Merge pull request #609 from xiaohuang2004/main
benwoo1110 afb9f84
Implement view inventory command for online players only
Woodstop ecdbc1a
Add logic for offline players and command completion. Enforce read-on…
Woodstop e844df9
Remove old code
Woodstop fa50a1f
Added modify inventory functionality
Woodstop 55ca31c
Show live inventory update if the player is online
Woodstop f16d4c2
Implement InventoryDataProvider for business logic
Woodstop 06f469d
Create new package for ModifiableInventoryHolder and ReadOnlyInventor…
Woodstop 0a95cf7
Minor update, remove unused code
Woodstop 9e78737
Implement helper class to insert filler armor/off-hand items in the i…
Woodstop f24dd31
Fix bugs associated with the item filler. Remove ability for players …
Woodstop 10a69ff
Simplify scenario 3 and remove import
Woodstop 9ab5c10
Make custom inventory have fewer slots. Empty slots replaced with bar…
Woodstop a9ae11e
Change public void to void
Woodstop 4d49bcf
Add api version to javadocs and fix minor formating
benwoo1110 131cd51
Make constructor of services non-public
benwoo1110 e3d7e54
Refactor InventoryDataProvider and InventoryGUIHelper to improve read…
benwoo1110 a09f33e
Reduce need for validation checks in view and modify commands
benwoo1110 6f8e81e
Remove adventure text API
Woodstop 83bb05e
Add "No [Item] Here" for the read-only case
Woodstop d5be772
Remove inventories field
Woodstop b03482e
Remove old references to the inventories field
Woodstop b69e2b4
Change status message to enum type
Woodstop 893ac2f
Move duplicate logic in the view and modify command to populateInvent…
Woodstop 1a05c0e
Change inventories.getLogger() to Logging
Woodstop a856f34
Make Helper more concise, fix bug where fillers don't always show in …
Woodstop f0d2daa
Split into smaller private methods
Woodstop e5b730b
Players can modify their own stored inventory, but not their live inv…
Woodstop 05a0a91
Fix bug where replacing an item in a filler slot makes the item disap…
Woodstop 77928ce
Implement slots for health, hunger, exp
Woodstop 243247c
Add description to javadocs
Woodstop 2d2455d
Begin to add player last location as a slot
Woodstop 7da6de3
Add maxHealth to the health slot
Woodstop eedd723
Remove unnecessary comments and fix indentation
Woodstop a23903d
Add private helper to get sharable value. Add check for config option…
Woodstop b675cb7
Check if sharables are null
Woodstop 8c92f8c
Reduce code nesting within InventoryViewListener
benwoo1110 9c9cb3a
Refactor InventoryDataProvider and make all the related apis experime…
benwoo1110 fd117a3
Cleanup some formatting
benwoo1110 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
146 changes: 146 additions & 0 deletions
146
src/main/java/org/mvplugins/multiverse/inventories/commands/InventoryModifyCommand.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| package org.mvplugins.multiverse.inventories.commands; | ||
|
|
||
| import net.kyori.adventure.text.Component; | ||
| import org.bukkit.Bukkit; | ||
| import org.bukkit.ChatColor; | ||
| import org.bukkit.Material; | ||
| import org.bukkit.OfflinePlayer; | ||
| import org.bukkit.entity.Player; | ||
| import org.bukkit.inventory.Inventory; | ||
| import org.jvnet.hk2.annotations.Service; | ||
| import org.mvplugins.multiverse.core.command.MVCommandIssuer; | ||
| import org.mvplugins.multiverse.core.world.MultiverseWorld; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Description; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; | ||
| import org.mvplugins.multiverse.external.jakarta.inject.Inject; | ||
| import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; | ||
| import org.mvplugins.multiverse.inventories.MultiverseInventories; | ||
| import org.mvplugins.multiverse.inventories.profile.InventoryDataProvider; | ||
| import org.mvplugins.multiverse.inventories.view.InventoryGUIHelper; | ||
| import org.mvplugins.multiverse.inventories.view.ModifiableInventoryHolder; | ||
|
|
||
| @Service | ||
| final class InventoryModifyCommand extends InventoriesCommand { | ||
|
|
||
| private final InventoryDataProvider inventoryDataProvider; | ||
| private final MultiverseInventories inventories; | ||
| private final InventoryGUIHelper inventoryGUIHelper; | ||
|
|
||
| @Inject | ||
| InventoryModifyCommand( | ||
| @NotNull InventoryDataProvider inventoryDataProvider, | ||
| @NotNull MultiverseInventories inventories, | ||
| @NotNull InventoryGUIHelper inventoryGUIHelper | ||
| ) { | ||
| this.inventoryDataProvider = inventoryDataProvider; | ||
| this.inventories = inventories; | ||
| this.inventoryGUIHelper = inventoryGUIHelper; | ||
| } | ||
|
|
||
| // This method contains the logic for the /mvinv modify command | ||
| @Subcommand("modify") | ||
| @CommandPermission("multiverse.inventories.view.modify") // Specific permission for modification | ||
| @CommandCompletion("@mvinvplayernames @mvworlds") | ||
| @Syntax("<player> <world>") | ||
| @Description("Modify a player's inventory in a specific world.") | ||
| void onInventoryModifyCommand( | ||
| @NotNull MVCommandIssuer issuer, | ||
|
|
||
| // to make sure the command is only run by players | ||
| @Flags("resolve=issuerOnly") | ||
| @NotNull Player player, | ||
|
|
||
| @Syntax("<player>") | ||
| @Description("Online or offline player") | ||
| OfflinePlayer targetPlayer, | ||
|
|
||
| @Syntax("<world>") | ||
| @Description("The world the player's inventory is in") | ||
| MultiverseWorld world | ||
| ) { | ||
| if (player.getUniqueId().equals(targetPlayer.getUniqueId())) { | ||
| issuer.sendError(ChatColor.RED + "You cannot modify your own inventory using this command. Use your regular inventory."); | ||
| return; | ||
| } | ||
|
|
||
| String worldName = world.getName(); | ||
| // Asynchronously load data using InventoryDataProvider | ||
| issuer.sendInfo(ChatColor.YELLOW + "Loading inventory data for " + targetPlayer.getName() + "..."); | ||
|
|
||
| inventoryDataProvider.loadPlayerInventoryData(targetPlayer, worldName) | ||
| .thenAccept(playerInventoryData -> { | ||
| // Ensure GUI operations run on the main thread | ||
| Bukkit.getScheduler().runTask(inventories, () -> { | ||
| // Create inventory with ModifiableInventoryHolder | ||
| // Pass all necessary context to the holder for saving on close. | ||
| Component title = Component.text("Modify " + targetPlayer.getName() + " @ " + worldName); | ||
|
Woodstop marked this conversation as resolved.
Outdated
|
||
| Inventory inv = Bukkit.createInventory( | ||
| new ModifiableInventoryHolder( | ||
| targetPlayer, | ||
| worldName, | ||
| playerInventoryData.profileTypeUsed, // Use the determined profile type | ||
| inventories | ||
| ), | ||
| 45, | ||
| title | ||
| ); | ||
|
|
||
| // Fill inventory | ||
| if (playerInventoryData.contents != null) { | ||
| for (int i = 0; i < Math.min(playerInventoryData.contents.length, 36); i++) { | ||
| inv.setItem(i, playerInventoryData.contents[i]); | ||
| } | ||
| } | ||
| // Armor slot mapping for display in the GUI and add fillers if empty | ||
| // Slot 36: Helmet | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[3] == null) { | ||
| inv.setItem(36, inventoryGUIHelper.createFillerItemForSlot(36)); // Use helper | ||
| } else { | ||
| inv.setItem(36, playerInventoryData.armor[3]); | ||
| } | ||
| // Slot 37: Chestplate | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[2] == null) { | ||
| inv.setItem(37, inventoryGUIHelper.createFillerItemForSlot(37)); // Use helper | ||
| } else { | ||
| inv.setItem(37, playerInventoryData.armor[2]); | ||
| } | ||
| // Slot 38: Leggings | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[1] == null) { | ||
| inv.setItem(38, inventoryGUIHelper.createFillerItemForSlot(38)); // Use helper | ||
| } else { | ||
| inv.setItem(38, playerInventoryData.armor[1]); | ||
| } | ||
| // Slot 39: Boots | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[0] == null) { | ||
| inv.setItem(39, inventoryGUIHelper.createFillerItemForSlot(39)); // Use helper | ||
| } else { | ||
| inv.setItem(39, playerInventoryData.armor[0]); | ||
| } | ||
| // Off-hand slot (40) and add filler if empty | ||
| if (playerInventoryData.offHand == null || playerInventoryData.offHand.getType() == Material.AIR) { | ||
| inv.setItem(40, inventoryGUIHelper.createFillerItemForSlot(40)); // Use helper | ||
| } else { | ||
| inv.setItem(40, playerInventoryData.offHand); | ||
| } | ||
| // Add the remaining slots as non-interactable filler items | ||
| for (int i = 41; i <= 44; i++) { | ||
| inv.setItem(i, inventoryGUIHelper.createFillerItemForSlot(i)); | ||
| } | ||
|
Woodstop marked this conversation as resolved.
Outdated
|
||
|
|
||
| player.openInventory(inv); | ||
| issuer.sendInfo(ChatColor.GREEN + "Opened editable inventory for " + targetPlayer.getName() + " in world " + worldName + ". Changes will save on close."); | ||
| }); // End of Bukkit.getScheduler().runTask() | ||
| }) | ||
| .exceptionally(throwable -> { | ||
| // This block runs if an exception occurs during data loading | ||
| issuer.sendError(ChatColor.RED + "Failed to load inventory data: " + throwable.getMessage()); | ||
| inventories.getLogger().severe("Error loading inventory for " + targetPlayer.getName() + ": " + throwable.getMessage()); | ||
| throwable.printStackTrace(); | ||
| return null; // Must return null for CompletableFuture<Void> in exceptionally | ||
| }); | ||
| } | ||
| } | ||
132 changes: 132 additions & 0 deletions
132
src/main/java/org/mvplugins/multiverse/inventories/commands/InventoryViewCommand.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| package org.mvplugins.multiverse.inventories.commands; | ||
|
|
||
| import net.kyori.adventure.text.Component; | ||
| import org.bukkit.Bukkit; | ||
| import org.bukkit.ChatColor; | ||
| import org.bukkit.Material; | ||
| import org.bukkit.OfflinePlayer; | ||
| import org.bukkit.entity.Player; | ||
| import org.bukkit.inventory.Inventory; | ||
| import org.jvnet.hk2.annotations.Service; | ||
| import org.mvplugins.multiverse.core.command.MVCommandIssuer; | ||
| import org.mvplugins.multiverse.core.world.MultiverseWorld; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Description; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; | ||
| import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; | ||
| import org.mvplugins.multiverse.external.jakarta.inject.Inject; | ||
| import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; | ||
| import org.mvplugins.multiverse.inventories.MultiverseInventories; | ||
| import org.mvplugins.multiverse.inventories.view.InventoryGUIHelper; | ||
| import org.mvplugins.multiverse.inventories.view.ReadOnlyInventoryHolder; | ||
| import org.mvplugins.multiverse.inventories.profile.InventoryDataProvider; | ||
|
|
||
| @Service | ||
| final class InventoryViewCommand extends InventoriesCommand { | ||
|
|
||
| private final InventoryDataProvider inventoryDataProvider; | ||
| private final MultiverseInventories inventories; | ||
| private final InventoryGUIHelper inventoryGUIHelper; | ||
|
|
||
| @Inject | ||
| InventoryViewCommand( | ||
| @NotNull InventoryDataProvider inventoryDataProvider, | ||
| @NotNull MultiverseInventories inventories, | ||
| @NotNull InventoryGUIHelper inventoryGUIHelper | ||
| ) { | ||
| this.inventories = inventories; | ||
| this.inventoryDataProvider = inventoryDataProvider; | ||
| this.inventoryGUIHelper = inventoryGUIHelper; | ||
| } | ||
|
|
||
| @Subcommand("view") | ||
| @CommandPermission("multiverse.inventories.view") | ||
| @CommandCompletion("@mvinvplayernames @mvworlds") | ||
| @Syntax("<player> <world>") | ||
| @Description("View a player's inventory in a specific world.") | ||
| void onInventoryViewCommand( | ||
| @NotNull MVCommandIssuer issuer, | ||
|
|
||
| // Ensure the command is run by a player | ||
| @Flags("resolve=issuerOnly") | ||
| @NotNull Player player, | ||
|
|
||
| @Syntax("<player>") | ||
| @Description("Online or offline player") | ||
| OfflinePlayer targetPlayer, | ||
|
|
||
| @Syntax("<world>") | ||
| @Description("The world the player's inventory is in") | ||
| MultiverseWorld world | ||
| ) { | ||
| String worldName = world.getName(); | ||
|
|
||
| // Asynchronously load data using InventoryDataProvider | ||
| issuer.sendInfo(ChatColor.YELLOW + "Loading inventory data for " + targetPlayer.getName() + "..."); | ||
|
|
||
| inventoryDataProvider.loadPlayerInventoryData(targetPlayer, worldName) | ||
| .thenAccept(playerInventoryData -> { | ||
| // Ensure GUI operations run on the main thread | ||
| Bukkit.getScheduler().runTask(inventories, () -> { | ||
| // Create an inventory for viewing. | ||
| Component title = Component.text(targetPlayer.getName() + " @ " + worldName); | ||
| Inventory inv = Bukkit.createInventory(new ReadOnlyInventoryHolder(), 45, title); | ||
|
|
||
| // Fill in main inventory slots (0–35) | ||
| if (playerInventoryData.contents != null) { | ||
| for (int i = 0; i < Math.min(playerInventoryData.contents.length, 36); i++) { | ||
| inv.setItem(i, playerInventoryData.contents[i]); | ||
| } | ||
| } | ||
| // Armor slot mapping for display in the GUI and add fillers if empty | ||
| // Slot 36: Helmet | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[3] == null) { | ||
| inv.setItem(36, inventoryGUIHelper.createFillerItemForSlot(36)); // Use helper | ||
| } else { | ||
| inv.setItem(36, playerInventoryData.armor[3]); | ||
| } | ||
| // Slot 37: Chestplate | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[2] == null) { | ||
| inv.setItem(37, inventoryGUIHelper.createFillerItemForSlot(37)); // Use helper | ||
| } else { | ||
| inv.setItem(37, playerInventoryData.armor[2]); | ||
| } | ||
| // Slot 38: Leggings | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[1] == null) { | ||
| inv.setItem(38, inventoryGUIHelper.createFillerItemForSlot(38)); // Use helper | ||
| } else { | ||
| inv.setItem(38, playerInventoryData.armor[1]); | ||
| } | ||
| // Slot 39: Boots | ||
| if (playerInventoryData.armor == null || playerInventoryData.armor[0] == null) { | ||
| inv.setItem(39, inventoryGUIHelper.createFillerItemForSlot(39)); // Use helper | ||
| } else { | ||
| inv.setItem(39, playerInventoryData.armor[0]); | ||
| } | ||
|
|
||
| // Off-hand slot (40) and add filler if empty | ||
| if (playerInventoryData.offHand == null || playerInventoryData.offHand.getType() == Material.AIR) { | ||
| inv.setItem(40, inventoryGUIHelper.createFillerItemForSlot(40)); // Use helper | ||
| } else { | ||
| inv.setItem(40, playerInventoryData.offHand); | ||
| } | ||
| // Add the remaining slots as non-interactable filler items | ||
| for (int i = 41; i <= 44; i++) { | ||
| inv.setItem(i, inventoryGUIHelper.createFillerItemForSlot(i)); | ||
| } | ||
|
|
||
| player.openInventory(inv); | ||
| issuer.sendInfo(ChatColor.GREEN + playerInventoryData.statusMessage); | ||
| }); // End of Bukkit.getScheduler().runTask() | ||
| }) | ||
| .exceptionally(throwable -> { | ||
| // This block runs if an exception occurs during data loading | ||
| issuer.sendError(ChatColor.RED + "Failed to load inventory data: " + throwable.getMessage()); | ||
| inventories.getLogger().severe("Error loading inventory for " + targetPlayer.getName() + ": " + throwable.getMessage()); | ||
|
Woodstop marked this conversation as resolved.
Outdated
|
||
| throwable.printStackTrace(); | ||
| return null; // Must return null for CompletableFuture<Void> in exceptionally | ||
| }); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason for this check? This command seems to be 99% a command for admins, so I dont see a use case where they can edit every player's inventories except their own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's currently a bug where if I were to modify my own live inventory, adding/removing from my GUI inventory will either duplicate or remove the item entirely. In other words, if I take an item out of the GUI inventory and put it into my actual inventory, it will make the item disappear. If I add an item from my actual inventory to the GUI inventory, it will duplicate the item.
I put this check here to prevent that from happening, but perhaps a better way would be to allow players to modify their own inventories in other worlds, just not their current world. If players can update their stored inventory instead of their live inventory, there shouldn't be any issues. Players can change their inventory normally without this command, so a better use seems to be letting players modify their inventories in other worlds.