Skip to content
Merged
Show file tree
Hide file tree
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 Jul 9, 2025
55cd262
Merge pull request #609 from xiaohuang2004/main
benwoo1110 Jul 18, 2025
afb9f84
Implement view inventory command for online players only
Woodstop Jul 19, 2025
ecdbc1a
Add logic for offline players and command completion. Enforce read-on…
Woodstop Jul 19, 2025
e844df9
Remove old code
Woodstop Jul 19, 2025
fa50a1f
Added modify inventory functionality
Woodstop Jul 19, 2025
55ca31c
Show live inventory update if the player is online
Woodstop Jul 19, 2025
f16d4c2
Implement InventoryDataProvider for business logic
Woodstop Jul 20, 2025
06f469d
Create new package for ModifiableInventoryHolder and ReadOnlyInventor…
Woodstop Jul 20, 2025
0a95cf7
Minor update, remove unused code
Woodstop Jul 20, 2025
9e78737
Implement helper class to insert filler armor/off-hand items in the i…
Woodstop Jul 20, 2025
f24dd31
Fix bugs associated with the item filler. Remove ability for players …
Woodstop Jul 20, 2025
10a69ff
Simplify scenario 3 and remove import
Woodstop Jul 20, 2025
9ab5c10
Make custom inventory have fewer slots. Empty slots replaced with bar…
Woodstop Jul 20, 2025
a9ae11e
Change public void to void
Woodstop Jul 20, 2025
4d49bcf
Add api version to javadocs and fix minor formating
benwoo1110 Jul 21, 2025
131cd51
Make constructor of services non-public
benwoo1110 Jul 21, 2025
e3d7e54
Refactor InventoryDataProvider and InventoryGUIHelper to improve read…
benwoo1110 Jul 21, 2025
a09f33e
Reduce need for validation checks in view and modify commands
benwoo1110 Jul 21, 2025
6f8e81e
Remove adventure text API
Woodstop Jul 21, 2025
83bb05e
Add "No [Item] Here" for the read-only case
Woodstop Jul 21, 2025
d5be772
Remove inventories field
Woodstop Jul 21, 2025
b03482e
Remove old references to the inventories field
Woodstop Jul 21, 2025
b69e2b4
Change status message to enum type
Woodstop Jul 21, 2025
893ac2f
Move duplicate logic in the view and modify command to populateInvent…
Woodstop Jul 21, 2025
1a05c0e
Change inventories.getLogger() to Logging
Woodstop Jul 21, 2025
a856f34
Make Helper more concise, fix bug where fillers don't always show in …
Woodstop Jul 21, 2025
f0d2daa
Split into smaller private methods
Woodstop Jul 21, 2025
e5b730b
Players can modify their own stored inventory, but not their live inv…
Woodstop Jul 21, 2025
05a0a91
Fix bug where replacing an item in a filler slot makes the item disap…
Woodstop Jul 21, 2025
77928ce
Implement slots for health, hunger, exp
Woodstop Jul 21, 2025
243247c
Add description to javadocs
Woodstop Jul 21, 2025
2d2455d
Begin to add player last location as a slot
Woodstop Jul 21, 2025
7da6de3
Add maxHealth to the health slot
Woodstop Jul 21, 2025
eedd723
Remove unnecessary comments and fix indentation
Woodstop Jul 22, 2025
a23903d
Add private helper to get sharable value. Add check for config option…
Woodstop Jul 22, 2025
b675cb7
Check if sharables are null
Woodstop Jul 22, 2025
8c92f8c
Reduce code nesting within InventoryViewListener
benwoo1110 Jul 28, 2025
9c9cb3a
Refactor InventoryDataProvider and make all the related apis experime…
benwoo1110 Jul 28, 2025
fd117a3
Cleanup some formatting
benwoo1110 Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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.


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);
Comment thread
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));
}
Comment thread
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
});
}
}
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());
Comment thread
Woodstop marked this conversation as resolved.
Outdated
throwable.printStackTrace();
return null; // Must return null for CompletableFuture<Void> in exceptionally
});
}
}
Loading
Loading