-
Notifications
You must be signed in to change notification settings - Fork 18
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-
displayNameis the name in the Menu, this can be changed when the Menu renders to the Player. -
cancelOnClickif 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 -
lineis the inventory line of the slot -
slotis the slot of the line starting in 1 to 9 -
itemthe 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-
slotPosis the current slot post in the Menu -
playerthat is clicking in the Item -
var canceledif should cancel the Player pick the item (if you put in the Menu buildcancelOnClickyou don't need to change this value) -
clickedthe 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?
...
}-
slotPosis the current slot post in the Menu -
playerthat is clicking in the Item -
showingItemis 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-
slotPosis the current slot post in the Menu -
playerthat is clicking in the Item
We have the same structure from MenuPlayerSlotRender.
interface MenuPlayerInventorySlot {
var showingItem: ItemStack?
fun updateSlotToPlayer()
fun updateSlot()
}-
showingItemis the current ItemStack that is in the Player inventory. -
fun updateSlotToPlayer(): updates the slot, callingonUpdateblock to the current player viewing Menu. -
fun updateSlot(): updates the slot, callingonUpdateblock 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.