Skip to content

Menu DSL Advanced

Gabriel Souza edited this page Jul 19, 2020 · 6 revisions

Menu DSL in one hand can be simple to use but in the other, super powerful, here you will learn what Menu DSL can do to help you.

Base Slot

Base slot works on providing you access to all slots that you don't declare.

menu("test", 3, true) {
    baseSlot.apply {
        onRender {
            showingItem = item(Material.STAINED_GLASS_PANE)
        }
    }

    slot(2, 5, item(Material.DIAMOND))
}

result

Menu events

Currently, you only learn the slot's events, but the Menu has there on Events that you can listen.

  • We can use onOpen to listen when the menu Opens
typealias MenuPlayerOpenEvent = MenuPlayerOpen.() -> Unit

fun onOpen(open: MenuPlayerOpenEvent)

class MenuPlayerOpen(
        override val menu: Menu<*>,
        override val player: Player,
        override val inventory: Inventory
) : MenuPlayerInventory
menu("test", 3, false) {
    slot(2, 5, item(Material.DIAMOND))

    onOpen {
        log.info("The player ${player.name} open the reward menu")
    }

}

Here we log a message whe a player opens the Menu.

  • preOpen: When you listen for onOpen you can't do anything about, onOpen means that the menu already opens to the Player. Differently, preOpen you can cancel the open of a Menu.
typealias MenuPlayerPreOpenEvent = MenuPlayerPreOpen.() -> Unit

fun preOpen(preOpen: MenuPlayerPreOpenEvent)

class MenuPlayerPreOpen(
        override val menu: Menu<*>,
        override val player: Player,
        override var canceled: Boolean = false
) : MenuPlayerCancellable
menu("test", 3, false) {
    slot(2, 5, item(Material.DIAMOND))

    onOpen {
        log.info("The player ${player.name} open the reward menu")
    }

    preOpen {
        if(!player.hasPermission("rank.that.was.reward")
            canceled = true
    }
}

Here we only enable to open the Menu for the players that have the permission rank.that.was.reward.

  • onUpdate works the same way that works on slot, is triggered when the menu updates with the configurable updateDelay.
typealias MenuPlayerUpdateEvent = MenuPlayerUpdate.() -> Unit

fun onUpdate(update: MenuPlayerUpdateEvent)

class MenuPlayerUpdate(
        override val menu: Menu<*>,
        override val player: Player,
        override val inventory: Inventory,
        var title: String
) : MenuPlayerInventory
menu("test", 3, false) {
    updateDelay = 20 // 1 second

    slot(2, 5, item(Material.DIAMOND)) {
        onClick {
            Console.command("rank-reward ${player.name}")
            close()
        }
    }
    
    preOpen {
        if(!player.hasPermission("rank.that.was.reward")
            canceled = true
    }

    onUpdate {
        getItem(2, 5)?.amount?.inc()
    }
}

Here we update very single second we increment one in the amount of the DIAMOND.

  • onClose: Called when the player closes the Menu
typealias MenuPlayerCloseEvent = MenuPlayerClose.() -> Unit

fun onClose(close: MenuPlayerCloseEvent)

class MenuPlayerClose(
        override val menu: Menu<*>,
        override val player: Player
) : MenuPlayer
menu("test", 3, false) {
    slot(2, 5, item(Material.DIAMOND))

    onClose {
        player.inventory.addItem(item(Material.EMERALD, amount = 5))
    }
}

Data

One of the most powerful features of the Menu is the Data. The Menu holds data values in a Map/Dictionary that you can easily insert and access.

The Menu has four types of data cache.

  • data - Global data that is shared with all viewers/players.
  • playerData - Holds only the Player data and its is cleared when the Player closes the Menu.
  • slotData - Global slot data that is shared with all viewers/players and you can only access within a Slot.
  • playerSlotData - Holds only the Player slot data, that you can only access within as slot (such as onClick) and its is cleared when the Player closes the Menu.

data and playerData can easily be access when it's not inside that menu, this means that you can insert some value before open the Menu to a Player, and use this data inside the Menu to different things.

Okay, let's go to an example.

CASE: We have a RankUP plugin, and we will create a Menu that show information about a Rank.

For that, we will need a small data class that holds a Rank and their information: data class Rank(val name: String, val upgradePrice: Double, val description: String)

val menuRankInfo = menu("Rank Information", 3, true) {
  // first we will use preOpen to check if has a player data key with the name `rank`
  // if does not, we will cancel the open of the menu because none Rank was insert.
  preOpen {
    if(getPlayerData("rank") == null)
      canceled = true
  }

  slot(2, 4, item(Material.PAPER)) {
    onRender {
      val rank = getPlayerData("rank") as Rank
      
      showingItem?.lore(rank.description.split("\n"))?.displayName(rank.name)
    }
  }

  slot(2, 6, item(Material.EMERALD).displayName("Upgrade: {{price}}")) {
    onRender {
      val rank = getPlayerData("rank") as Rank

      showingItem?.displayName(showingItem?.itemMeta?.displayName?.replace("{{price}}", rank.upgradePrice))
    }
  }
}

getPlayerData that has only key/value is a function that is provided in MenuPlayer to make an easy way to extract it.

interface MenuPlayer {
    val menu: Menu<*>
    val player: Player

    fun putPlayerData(key: String, value: Any)

    fun getPlayerData(key: String): Any?
}

We will need to create a simple command that will open the menu and use playerData to insert the Rank.

command("rankinfo") {
  executorPlayer {
    val rankName = string(0)
    val rank = findRankByName(rankName) ?: fail("Rank not found!")

    menuRankInfo.putPlayerData(sender, "rank", rank)
    menuRankInfo.openToPlayer(sender)
  }
}

With this menu, you will have one menu that can be used to show all your Ranks information.

This can be used to add filters to a Shop or Auction Menu and much more.

Mastering Menus

One of the most important things that you need to know is the events inheritance, because this can help you make your code less repeatable.

Let's look into the onClick structure that is an extension of MenuPlayerSlotInteract.

open 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(menu, player, inventory, canceled), MenuPlayerInventorySlot

Here we can see that we have a lot of properties that we have access and the inheritance from MenuPlayerInteract and MenuPlayerInventorySlot.

Let's look into the MenuPlayerInteract first.

open class MenuPlayerInteract(
        override val menu: Menu<*>,
        override val player: Player,
        override val inventory: Inventory,
        override var canceled: Boolean
) : MenuPlayerInventory, MenuPlayerCancellable

Here we can see that we have more properties that we have access to it and more inheritance.

MenuPlayerInventory is a big one that we can manage the inventory that is open to a Player.

interface MenuPlayerInventory : MenuPlayer {
    val inventory: Inventory

    // closes the menu to the player
    fun close()

    fun getItem(line: Int, slot: Int): ItemStack?

    fun getItem(slot: Int): ItemStack?

    fun setItem(line: Int, slot: Int, item: ItemStack?)

    fun setItem(slot: Int, item: ItemStack?)

    fun getPlayerItem(line: Int, slot: Int): ItemStack?

    fun getPlayerItem(slot: Int): ItemStack?

    fun setPlayerItem(line: Int, slot: Int, item: ItemStack?)

    fun setPlayerItem(slot: Int, item: ItemStack?)

    fun getLine(line: Int): List<ItemStack?>

    fun getPlayerLine(line: Int): List<ItemStack?>

    fun updateToPlayer()
}

MenuPlayer is the final, and it holds the menu and the Player that this event is from.

interface MenuPlayer {
    val menu: Menu<*>
    val player: Player

    fun putPlayerData(key: String, value: Any)

    fun getPlayerData(key: String): Any?
}

Okay, let's go up to the MenuPlayerInteract and continue into the MenuPlayerCancellable

MenuPlayerCancellable to cancel the events.

interface MenuPlayerCancellable : MenuPlayer {
    var canceled: Boolean
}

Okay, we already see the MenuPlayer, now let's go up again into the MenuPlayerSlotInteract and go to MenuPlayerInventorySlot

interface MenuPlayerInventorySlot : MenuPlayerSlot, MenuPlayerInventory {

    // update the slot item that is beeing showed to the player in the Inventory.
    var showingItem: ItemStack?

    fun updateSlotToPlayer()

    fun updateSlot()
}

MenuPlayerInventory we already see, note that this has a pattern from the Menu events, we can have the same Interfaces. Okay, let's go into the MenuPlayerSlot

interface MenuPlayerSlot : MenuPlayer {
    val slotPos: Int
    val slot: Slot

    fun putPlayerSlotData(key: String, value: Any)

    fun getPlayerSlotData(key: String): Any?
}

After all this code, and all this interface, we a lot of inheritance and it's important to know all of it, because with this knowledge, work with the Menu DSL in a much simpler and much powerful.

We can check all the Events and their inheritance here:

Pagination and more.

With all this API is possible to to do more complex things like an easy pagination that you can see more about in the Menu DSL Pagination Docs.

Clone this wiki locally