Skip to content

Commit 5893315

Browse files
authored
Merge pull request #7 from Trilleo/copilot/add-itemstack-builder-dsl
Feature: Add ItemStack Builder DSL with MiniMessage support
2 parents d07a6eb + 1580442 commit 5893315

2 files changed

Lines changed: 244 additions & 0 deletions

File tree

docs/DEVELOPER_GUIDE.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,75 @@ class RewardsCommand : PluginCommand(
455455
}
456456
}
457457
```
458+
459+
---
460+
461+
## ItemStack Builder DSL
462+
463+
Building `ItemStack` instances with custom names, lore, enchantments, and flags normally requires verbose
464+
boilerplate. The `itemStack` DSL in `com.example.exampleplugin.utils` lets you create fully configured items in a
465+
single expression. All text is parsed through
466+
[MiniMessage](https://docs.advntr.dev/minimessage/index.html), so rich formatting tags like `<bold>`, `<red>`,
467+
and `<gradient>` work out of the box.
468+
469+
### Before (vanilla API)
470+
471+
```kotlin
472+
val item = ItemStack(Material.DIAMOND_SWORD)
473+
val meta = item.itemMeta
474+
meta.displayName(MiniMessage.miniMessage().deserialize("<bold><gradient:gold:yellow>Excalibur</gradient></bold>"))
475+
meta.lore(listOf(
476+
MiniMessage.miniMessage().deserialize("<gray>A legendary blade"),
477+
MiniMessage.miniMessage().deserialize("<gray>Damage: <red>+20")
478+
))
479+
meta.addEnchant(Enchantment.SHARPNESS, 5, true)
480+
meta.isUnbreakable = true
481+
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS)
482+
item.itemMeta = meta
483+
```
484+
485+
### After (using the DSL)
486+
487+
```kotlin
488+
import com.example.exampleplugin.utils.itemStack
489+
490+
val item = itemStack(Material.DIAMOND_SWORD) {
491+
name("<bold><gradient:gold:yellow>Excalibur</gradient></bold>")
492+
lore("<gray>A legendary blade", "<gray>Damage: <red>+20")
493+
enchant(Enchantment.SHARPNESS, 5)
494+
unbreakable(true)
495+
flag(ItemFlag.HIDE_ENCHANTS)
496+
}
497+
```
498+
499+
### Builder Methods
500+
501+
| Method | Signature | Description |
502+
|:------------------|:-----------------------------------|:-----------------------------------------------------|
503+
| `name` | `name(String)` | Set the display name (MiniMessage) |
504+
| `lore` | `lore(vararg String)` | Set lore lines (each parsed with MiniMessage) |
505+
| `enchant` | `enchant(Enchantment, Int)` | Add an enchantment at the given level |
506+
| `unbreakable` | `unbreakable(Boolean)` | Make the item unbreakable |
507+
| `amount` | `amount(Int)` | Set the stack size |
508+
| `flag` | `flag(vararg ItemFlag)` | Add one or more item flags |
509+
| `customModelData` | `customModelData(Int)` | Set the custom model data value |
510+
| `meta` | `meta(ItemMeta.() -> Unit)` | Escape hatch for direct `ItemMeta` manipulation |
511+
512+
### Escape Hatch Example
513+
514+
For advanced use-cases not covered by the builder methods, the `meta` block gives you direct access to the
515+
`ItemMeta`. Any changes made inside `meta` are applied **after** all other builder properties, so they take
516+
precedence:
517+
518+
```kotlin
519+
import com.example.exampleplugin.utils.itemStack
520+
521+
val head = itemStack(Material.PLAYER_HEAD) {
522+
name("<yellow>Custom Head")
523+
meta {
524+
// 'this' is the ItemMeta — cast and use any Paper API method
525+
(this as org.bukkit.inventory.meta.SkullMeta)
526+
.owningPlayer = org.bukkit.Bukkit.getOfflinePlayer("Notch")
527+
}
528+
}
529+
```
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.example.exampleplugin.utils
2+
3+
import net.kyori.adventure.text.minimessage.MiniMessage
4+
import org.bukkit.Material
5+
import org.bukkit.enchantments.Enchantment
6+
import org.bukkit.inventory.ItemFlag
7+
import org.bukkit.inventory.ItemStack
8+
import org.bukkit.inventory.meta.ItemMeta
9+
10+
/**
11+
* DSL builder for creating [ItemStack] instances in a concise, readable way.
12+
*
13+
* All text (display name and lore lines) is parsed through
14+
* [MiniMessage](https://docs.advntr.dev/minimessage/index.html), so you can
15+
* use MiniMessage tags such as `<bold>`, `<red>`, `<gradient>`, etc.
16+
*
17+
* Use the top-level [itemStack] function as the entry point:
18+
* ```kotlin
19+
* val sword = itemStack(Material.DIAMOND_SWORD) {
20+
* name("<bold><gradient:gold:yellow>Excalibur</gradient></bold>")
21+
* lore("<gray>A legendary blade", "<gray>Damage: <red>+20")
22+
* enchant(Enchantment.SHARPNESS, 5)
23+
* unbreakable(true)
24+
* flag(ItemFlag.HIDE_ENCHANTS)
25+
* }
26+
* ```
27+
*
28+
* For advanced use-cases not covered by the builder API, use the [meta]
29+
* escape hatch to modify the [ItemMeta] directly:
30+
* ```kotlin
31+
* val head = itemStack(Material.PLAYER_HEAD) {
32+
* name("<yellow>Custom Head")
33+
* meta {
34+
* // 'this' is the ItemMeta — call any Paper API method
35+
* (this as org.bukkit.inventory.meta.SkullMeta)
36+
* .owningPlayer = org.bukkit.Bukkit.getOfflinePlayer("Notch")
37+
* }
38+
* }
39+
* ```
40+
*/
41+
class ItemStackBuilder(@PublishedApi internal val material: Material) {
42+
43+
private val miniMessage = MiniMessage.miniMessage()
44+
45+
@PublishedApi internal var itemAmount: Int = 1
46+
@PublishedApi internal var displayName: String? = null
47+
@PublishedApi internal var loreLines: List<String>? = null
48+
@PublishedApi internal var enchantments: MutableMap<Enchantment, Int> = mutableMapOf()
49+
@PublishedApi internal var isUnbreakable: Boolean = false
50+
@PublishedApi internal var itemFlags: MutableList<ItemFlag> = mutableListOf()
51+
@PublishedApi internal var modelData: Int? = null
52+
@PublishedApi internal var metaBlock: (ItemMeta.() -> Unit)? = null
53+
54+
/**
55+
* Sets the display name of the item.
56+
* The string is parsed with MiniMessage.
57+
*
58+
* @param name the display name (supports MiniMessage tags)
59+
*/
60+
fun name(name: String) {
61+
this.displayName = name
62+
}
63+
64+
/**
65+
* Sets the lore lines of the item.
66+
* Each line is parsed with MiniMessage independently.
67+
*
68+
* @param lines one or more lore lines (each supports MiniMessage tags)
69+
*/
70+
fun lore(vararg lines: String) {
71+
this.loreLines = lines.toList()
72+
}
73+
74+
/**
75+
* Adds an enchantment to the item.
76+
*
77+
* @param enchantment the enchantment to apply
78+
* @param level the enchantment level
79+
*/
80+
fun enchant(enchantment: Enchantment, level: Int) {
81+
this.enchantments[enchantment] = level
82+
}
83+
84+
/**
85+
* Sets whether the item is unbreakable.
86+
*
87+
* @param value `true` to make the item unbreakable
88+
*/
89+
fun unbreakable(value: Boolean) {
90+
this.isUnbreakable = value
91+
}
92+
93+
/**
94+
* Sets the stack size of the item.
95+
*
96+
* @param amount the number of items in the stack (1–64)
97+
*/
98+
fun amount(amount: Int) {
99+
this.itemAmount = amount
100+
}
101+
102+
/**
103+
* Adds one or more [ItemFlag]s to the item.
104+
*
105+
* @param flags the flags to add (e.g. [ItemFlag.HIDE_ENCHANTS])
106+
*/
107+
fun flag(vararg flags: ItemFlag) {
108+
this.itemFlags.addAll(flags)
109+
}
110+
111+
/**
112+
* Sets the custom model data value for the item.
113+
*
114+
* @param data the custom model data integer
115+
*/
116+
fun customModelData(data: Int) {
117+
this.modelData = data
118+
}
119+
120+
/**
121+
* Escape hatch for direct [ItemMeta] manipulation.
122+
*
123+
* The receiver block is applied to the item's meta **after** all other
124+
* builder properties have been set, so any changes made here take
125+
* precedence.
126+
*
127+
* @param block a lambda with [ItemMeta] as the receiver
128+
*/
129+
fun meta(block: ItemMeta.() -> Unit) {
130+
this.metaBlock = block
131+
}
132+
133+
/**
134+
* Builds and returns the configured [ItemStack].
135+
*
136+
* @return the fully constructed item stack
137+
*/
138+
fun build(): ItemStack {
139+
val item = ItemStack(material, itemAmount)
140+
val meta = item.itemMeta ?: return item
141+
142+
displayName?.let { meta.displayName(miniMessage.deserialize(it)) }
143+
loreLines?.let { lines -> meta.lore(lines.map { miniMessage.deserialize(it) }) }
144+
enchantments.forEach { (enchant, level) -> meta.addEnchant(enchant, level, true) }
145+
meta.isUnbreakable = isUnbreakable
146+
if (itemFlags.isNotEmpty()) meta.addItemFlags(*itemFlags.toTypedArray())
147+
modelData?.let { meta.setCustomModelData(it) }
148+
metaBlock?.invoke(meta)
149+
150+
item.itemMeta = meta
151+
return item
152+
}
153+
}
154+
155+
/**
156+
* Creates an [ItemStack] of the given [material] using a builder DSL.
157+
*
158+
* Example:
159+
* ```kotlin
160+
* val item = itemStack(Material.GOLDEN_APPLE) {
161+
* name("<gold>Enchanted Apple")
162+
* amount(3)
163+
* }
164+
* ```
165+
*
166+
* @param material the material type for the item
167+
* @param block the builder configuration block
168+
* @return the fully constructed [ItemStack]
169+
*/
170+
fun itemStack(material: Material, block: ItemStackBuilder.() -> Unit): ItemStack {
171+
return ItemStackBuilder(material).apply(block).build()
172+
}

0 commit comments

Comments
 (0)