-
Notifications
You must be signed in to change notification settings - Fork 20
Menu DSL
Without a doubt, the Menu DSL is one of the best things that KotlinBukkitAPI can help you. In this part of the Wiki we will look at it.
fun Plugin.menu(
displayName: String,
lines: Int,
cancelOnClick: Boolean = true,
block: MenuDSL.() -> Unit
): MenuDSL
-
displayName
is the name in the Menu, this can be changed when the Menu renders to the Player. -
cancelOnClick
if should cancel on player click on an item (commonly this will be always true).
val myMenu = menu("Servers", 4, true) {
...
}
fun MenuDSL.slot(
line: Int,
slot: Int,
item: ItemStack?,
block: SlotDSL.() -> Unit = {}
): SlotDSL
-
line
is the inventory line of the slot -
slot
is the slot of the line starting in 1 to 9 -
item
the item to be displayed at this slot
val myMenu = menu("Servers", 4, true) {
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
...
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
...
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
...
}
val coinsItem = item(Material.EMERALD) {
displayName = "§2Network coins"
lore = listOf(
" ",
" §2Global coins: §b{global_coins}"
" §2Prision coins: §b{prision_coins}
)
}
slot(4, 5, coinsItem) {
...
}
}
RESULT
Let's ignore the coinsItem for a second just for the code be small in the examples.
typealias MenuPlayerSlotInteractEvent = MenuPlayerSlotInteract.() -> Unit
// class SlotDSL:
fun onClick(click: MenuPlayerSlotInteractEvent)
class MenuPlayerSlotInteract(
menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
player: Player,
inventory: Inventory,
canceled: Boolean,
val click: ClickType,
val action: InventoryAction,
val clicked: ItemStack?,
val cursor: ItemStack?,
val hotbarKey: Int
) : MenuPlayerInteract
-
slotPos
is the current slot post in the Menu -
player
that is clicking in the Item -
var canceled
if should cancel the Player pick the item (if you put in the Menu buildcancelOnClick
you don't need to change this value) -
clicked
the current ItemStack that is in the Inventory.
val myMenu = menu("Servers", 4, true) {
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
onClick {
player.msg("§eTeleporting to Factions server.")
player.bungeecord.send("factions")
close() // close the Menu
}
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
onClick {
player.msg("§eTeleporting to Creative server.")
player.bungeecord.send("creative")
close() // close the Menu
}
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
onClick {
player.msg("§eTeleporting to Prision server.")
player.bungeecord.send("prision")
close() // close the Menu
}
}
}
RESULT
NOTE that is not teleporting to Other server because I'm not using BungeeCord. If you want to know more about KotlinBukkitAPI BungeeCord extesions, check the documentation.
Note that in your coins menu item, he has variables that is not Global, it is values for the Player that is opening the menu too.
val coinsItem = item(Material.EMERALD) {
displayName = "§2Network coins"
lore = listOf(
" ",
" §2Global coins: §b{global_coins}"
" §2Prision coins: §b{prision_coins}
)
}
slot(4, 5, coinsItem) {
...
}
-
{global_coins}
,{prision_coins}
We can use onRender
to listen when the Slot is rendering to the Player (on open menu) and update the Item.
typealias MenuPlayerSlotRenderEvent = MenuPlayerSlotRender.() -> Unit
fun onRender(render: MenuPlayerSlotRenderEvent)
class MenuPlayerSlotRender(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory
) : MenuPlayerInventorySlot
interface MenuPlayerInventorySlot {
var showingItem: ItemStack?
...
}
-
slotPos
is the current slot post in the Menu -
player
that is clicking in the Item -
showingItem
is the current ItemStack that is in the Player inventory.
val coinsItem = item(Material.EMERALD) {
displayName = "§2Network coins"
lore = listOf(
" ",
" §2Global coins: §b{global_coins}"
" §2Prision coins: §b{prision_coins}"
)
}
slot(4, 5, coinsItem) {
onRender {
showingItem?.meta<ItemMeta> {
lore = lore.map {
it.replace("{global_coins}", CoinsManager.getGlobalCoinsFormated(player))
it.replace("{prision_coins}", CoinsManager.getPresionCoinsFormated(player))
}
}
}
}
RESULT
** Note ** that slot(...)
support null items, this means that if you want to set a item only in onRender, this is possible!
slot(4, 5, null) {
onRender {
showingItem = item(Material.DIAMOND)
}
}
Let's change a little bit your Servers "buttons" to add the players online count of the server. The goal is that even with Menu open, the online player count keep updating with the current value of the server.
Again, without the coinsItem
to make the sample small.
val myMenu = menu("Servers", 4, true) {
val playersOnlineLore = listOf(" ", " §2Players online: {players_online} ")
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
onClick {
player.msg("§eTeleporting to Factions server.")
player.bungeecord.send("factions")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("factions"))
}
}
}
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
onClick {
player.msg("§eTeleporting to Creative server.")
player.bungeecord.send("creative")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("factions"))
}
}
}
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
onClick {
player.msg("§eTeleporting to Prision server.")
player.bungeecord.send("prision")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("factions"))
}
}
}
}
}
This is getting big, later we will fix it, but now, lets make your Slot auto update.
To achieve that goal, we will use a property of the Menu called updateDelay
that define the update time of the menu in ticks.
And we will use onUpdate
block from SlotDSL.
typealias MenuPlayerSlotUpdateEvent = MenuPlayerSlotUpdate.() -> Unit
// SlotDSL function
fun onUpdate(update: MenuPlayerSlotUpdateEvent)
class MenuPlayerSlotUpdate(
override val menu: Menu<*>,
override val slotPos: Int,
override val slot: Slot,
override val player: Player,
override val inventory: Inventory
) : MenuPlayerInventorySlot
-
slotPos
is the current slot post in the Menu -
player
that is clicking in the Item
We have the same structure from MenuPlayerSlotRender
.
interface MenuPlayerInventorySlot {
var showingItem: ItemStack?
fun updateSlotToPlayer()
fun updateSlot()
}
-
showingItem
is the current ItemStack that is in the Player inventory. -
fun updateSlotToPlayer()
: updates the slot, callingonUpdate
block to the current player viewing Menu. -
fun updateSlot()
: updates the slot, callingonUpdate
block to all players viewing the Menu.
val myMenu = menu("Servers", 4, true) {
updateDelay = 20 // 1 second
val playersOnlineLore = listOf(" ", " §2Players online: {players_online} ")
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
onClick {
player.msg("§eTeleporting to Factions server.")
player.bungeecord.send("factions")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("factions"))
}
}
}
onUpdate {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("factions"))
}
}
}
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
onClick {
player.msg("§eTeleporting to Creative server.")
player.bungeecord.send("creative")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("creative"))
}
}
}
onUpdate {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("creative"))
}
}
}
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
onClick {
player.msg("§eTeleporting to Prision server.")
player.bungeecord.send("prision")
close() // close the Menu
}
onRender {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("prision"))
}
}
}
onUpdate {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers("prision"))
}
}
}
}
}
Uff... Is getting bigger and... repetitive...
The onUpdate
and onRender
is the same, we can use Kotlin extension function to solve this problem because the MenuPlayerSlotRender
and MenuPlayerSlotUpdate
implements MenuPlayerInventorySlot
.
val myMenu = menu("Servers", 4, true) {
updateDelay = 20 // 1 second
val playersOnlineLore = listOf(" ", " §2Players online: {players_online} ")
fun MenuPlayerInventorySlot.updateCurrentPlayersCount(
server: String
) {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers(server))
}
}
}
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
onClick {
player.msg("§eTeleporting to Factions server.")
player.bungeecord.send("factions")
close() // close the Menu
}
onRender { updateCurrentPlayersCount("factions") }
onUpdate { updateCurrentPlayersCount("factions") }
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
onClick {
player.msg("§eTeleporting to Creative server.")
player.bungeecord.send("creative")
close() // close the Menu
}
onRender { updateCurrentPlayersCount("creative") }
onUpdate { updateCurrentPlayersCount("creative") }
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
onClick {
player.msg("§eTeleporting to Prision server.")
player.bungeecord.send("presion")
close() // close the Menu
}
onRender { updateCurrentPlayersCount("prision") }
onUpdate { updateCurrentPlayersCount("prision") }
}
}
Much better... But we can do that with onClick
to, is basic the same...
val myMenu = menu("Servers", 4, true) {
updateDelay = 20 // 1 second
val playersOnlineLore = listOf(" ", " §2Players online: {players_online} ")
fun MenuPlayerInventorySlot.updateCurrentPlayersCount(
server: String
) {
showingItem?.meta<ItemMeta> {
lore = playersOnlineLore.map {
it.replace("{players_online}", ServerManager.getOnlinePlayers(server))
}
}
}
fun MenuPlayerSlotInteract.teleportPlayerToServerAndNotify(
bungeeServer: String, displayName: String
) {
player.msg("§eTeleporting to $displayName server.")
player.bungeecord.send(bungeeServer)
close() // close the Menu
}
slot(2, 3, item(Material.TNT).displayName("§cFactions")) {
onClick { teleportPlayerToServerAndNotify("faction", "Factions") }
onRender { updateCurrentPlayersCount("factions") }
onUpdate { updateCurrentPlayersCount("factions") }
}
slot(2, 5, item(Material.GRASS).displayName("§aCreative")) {
onClick { teleportPlayerToServerAndNotify("creative", "Creative") }
onRender { updateCurrentPlayersCount("creative") }
onUpdate { updateCurrentPlayersCount("creative") }
}
slot(2, 7, item(Material.IRON_PICKAXE).displayName("§cPrision")) {
onClick { teleportPlayerToServerAndNotify("prision", "Prision") }
onRender { updateCurrentPlayersCount("prision") }
onUpdate { updateCurrentPlayersCount("prision") }
}
val coinsItem = item(Material.EMERALD) {
displayName = "§2Network coins"
lore = listOf(
" ",
" §2Global coins: §b{global_coins}"
" §2Prision coins: §b{prision_coins}"
)
}
slot(4, 5, coinsItem) {
onRender {
showingItem?.meta<ItemMeta> {
lore = lore.map {
it.replace("{global_coins}", CoinsManager.getGlobalCoinsFormated(player))
it.replace("{prision_coins}", CoinsManager.getPresionCoinsFormated(player))
}
}
}
}
}
RESULT
This is the basics of the Menu DSL, to keep learning, check out the Menu DSL: Advanced documentation.
If you want to navigate into the code, you can check this two folder that contains all Menu structure.