Skip to content

Commit 8412664

Browse files
committed
✨ feat(inventory): add offline inventory editing commands for players
- implement commands to set item slots and equipment for offline players - add summary and clear commands for offline inventory management - introduce EquipmentSlotArgument for equipment slot validation
1 parent 528f878 commit 8412664

3 files changed

Lines changed: 165 additions & 1 deletion

File tree

surf-api-paper/surf-api-paper-plugin-test/src/main/java/dev/slne/surf/api/paper/test/command/SurfApiTestCommand.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public SurfApiTestCommand() {
3131
new ShowItemCommand("showitem"),
3232
new SortInvCommand("sortInv"),
3333
new SignedMessageArgumentTest("signedmessage"),
34-
new BlockPdcContainerTest("blockpdc")
34+
new BlockPdcContainerTest("blockpdc"),
35+
new OfflineInventoryEditTest("editOfflineInventory")
3536
);
3637
}
3738
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dev.slne.surf.surfapi.bukkit.test.command.args
2+
3+
import dev.jorel.commandapi.arguments.ArgumentSuggestions
4+
import dev.jorel.commandapi.arguments.CustomArgument
5+
import dev.jorel.commandapi.arguments.StringArgument
6+
import org.bukkit.inventory.EquipmentSlot
7+
8+
class EquipmentSlotArgument(name: String) :
9+
CustomArgument<EquipmentSlot, String>(StringArgument(name), { info ->
10+
try {
11+
EquipmentSlot.valueOf(info.input)
12+
} catch (e: IllegalArgumentException) {
13+
throw CustomArgumentException.fromMessageBuilder(
14+
MessageBuilder()
15+
.append("Invalid equipment slot: ")
16+
.appendArgInput()
17+
.append(" (valid: ")
18+
.append(EquipmentSlot.entries.joinToString(", ") { it.name })
19+
.append(")")
20+
)
21+
}
22+
}) {
23+
init {
24+
replaceSuggestions(ArgumentSuggestions.strings(EquipmentSlot.entries.map { it.name }))
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@file:OptIn(NmsUseWithCaution::class)
2+
3+
package dev.slne.surf.surfapi.bukkit.test.command.subcommands
4+
5+
import dev.jorel.commandapi.CommandAPICommand
6+
import dev.jorel.commandapi.arguments.AsyncPlayerProfileArgument
7+
import dev.jorel.commandapi.kotlindsl.argument
8+
import dev.jorel.commandapi.kotlindsl.getValue
9+
import dev.jorel.commandapi.kotlindsl.integerArgument
10+
import dev.jorel.commandapi.kotlindsl.subcommand
11+
import dev.slne.surf.api.paper.command.executors.anyExecutorSuspend
12+
import dev.slne.surf.api.paper.command.executors.playerExecutorSuspend
13+
import dev.slne.surf.api.paper.command.util.awaitAsyncPlayerProfile
14+
import dev.slne.surf.api.paper.command.util.idOrThrow
15+
import dev.slne.surf.api.paper.extensions.server
16+
import dev.slne.surf.api.paper.nms.NmsUseWithCaution
17+
import dev.slne.surf.api.paper.nms.bridges.SurfPaperNmsPlayerBridge
18+
import dev.slne.surf.surfapi.bukkit.test.command.args.EquipmentSlotArgument
19+
import org.bukkit.command.CommandSender
20+
import org.bukkit.inventory.EquipmentSlot
21+
import java.util.*
22+
23+
class OfflineInventoryEditTest(name: String) : CommandAPICommand(name) {
24+
init {
25+
setSlotCommand()
26+
setEquipmentCommand()
27+
summaryCommand()
28+
clearCommand()
29+
}
30+
31+
private fun setSlotCommand() = subcommand("set-slot") {
32+
argument(AsyncPlayerProfileArgument("player"))
33+
integerArgument("slot", 0, 35)
34+
35+
playerExecutorSuspend { sender, args ->
36+
val target = args.awaitAsyncPlayerProfile("player").idOrThrow()
37+
val slot: Int by args
38+
val item = sender.inventory.itemInMainHand.clone()
39+
40+
if (item.isEmpty) {
41+
sender.sendMessage("Hold an item in your main hand to copy it into the offline inventory.")
42+
return@playerExecutorSuspend
43+
}
44+
45+
if (!runEdit(sender, target) { edit ->
46+
edit.items[slot] = item
47+
}) return@playerExecutorSuspend
48+
49+
sender.sendMessage("Set slot $slot of $target to ${item.type.key.asString()} x${item.amount}.")
50+
}
51+
}
52+
53+
private fun setEquipmentCommand() = subcommand("set-equipment") {
54+
argument(AsyncPlayerProfileArgument("player"))
55+
argument(EquipmentSlotArgument("slot"))
56+
57+
playerExecutorSuspend { sender, args ->
58+
val target = args.awaitAsyncPlayerProfile("player").idOrThrow()
59+
val slot: EquipmentSlot by args
60+
val item = sender.inventory.itemInMainHand.clone()
61+
62+
if (item.isEmpty) {
63+
sender.sendMessage("Hold an item in your main hand to copy it into the offline equipment.")
64+
return@playerExecutorSuspend
65+
}
66+
67+
if (!runEdit(sender, target) { edit ->
68+
edit.equipment.setItem(slot, item)
69+
}) return@playerExecutorSuspend
70+
71+
sender.sendMessage("Set equipment slot ${slot.name} of $target to ${item.type.key.asString()} x${item.amount}.")
72+
}
73+
}
74+
75+
private fun summaryCommand() = subcommand("summary") {
76+
argument(AsyncPlayerProfileArgument("player"))
77+
78+
anyExecutorSuspend { sender, args ->
79+
val target = args.awaitAsyncPlayerProfile("player").idOrThrow()
80+
var inventoryItems = emptyList<String>()
81+
var equipmentItems = emptyList<String>()
82+
83+
if (!runEdit(sender, target) { edit ->
84+
inventoryItems = edit.items.withIndex()
85+
.filter { (_, item) -> !item.isEmpty }
86+
.map { (slot, item) -> "slot $slot=${item.type.key.asString()} x${item.amount}" }
87+
88+
equipmentItems = EDITABLE_EQUIPMENT_SLOTS
89+
.map { slot -> slot to edit.equipment.getItem(slot) }
90+
.filter { (_, item) -> !item.isEmpty }
91+
.map { (slot, item) -> "${slot.name}=${item.type.key.asString()} x${item.amount}" }
92+
}) return@anyExecutorSuspend
93+
94+
sender.sendMessage("Offline inventory summary for ${target}:")
95+
sender.sendMessage("Inventory: ${inventoryItems.ifEmpty { listOf("empty") }.joinToString()}")
96+
sender.sendMessage("Equipment: ${equipmentItems.ifEmpty { listOf("empty") }.joinToString()}")
97+
}
98+
}
99+
100+
private fun clearCommand() = subcommand("clear") {
101+
argument(AsyncPlayerProfileArgument("player"))
102+
103+
anyExecutorSuspend { sender, args ->
104+
val target = args.awaitAsyncPlayerProfile("player").idOrThrow()
105+
106+
if (!runEdit(sender, target) { edit ->
107+
edit.items.clear()
108+
edit.equipment.clear()
109+
}) return@anyExecutorSuspend
110+
111+
sender.sendMessage("Cleared offline inventory and equipment of ${target}.")
112+
}
113+
}
114+
115+
private suspend fun runEdit(
116+
sender: CommandSender,
117+
target: UUID,
118+
edit: (SurfPaperNmsPlayerBridge.PlayerInventoryEdit) -> Unit
119+
): Boolean {
120+
return runCatching {
121+
SurfPaperNmsPlayerBridge.editOfflineInventory(server.getOfflinePlayer(target), edit)
122+
}.onFailure { error ->
123+
sender.sendMessage("editOfflineInventory failed: ${error::class.simpleName}: ${error.message}")
124+
}.isSuccess
125+
}
126+
127+
companion object {
128+
private val EDITABLE_EQUIPMENT_SLOTS = listOf(
129+
EquipmentSlot.HAND,
130+
EquipmentSlot.OFF_HAND,
131+
EquipmentSlot.HEAD,
132+
EquipmentSlot.CHEST,
133+
EquipmentSlot.LEGS,
134+
EquipmentSlot.FEET
135+
)
136+
}
137+
}

0 commit comments

Comments
 (0)