-
Notifications
You must be signed in to change notification settings - Fork 1
Home
A lot of spigot developers develop their own API for having a simpler interaction with the User or, if they don't have one, they try to "run-away" from it writing complex and not-really-user-friendly commands that are already hard to learn for admins, they couldn't be used with something like a minigame. This API provides the tools to develop powerful Hotbars and GUIs with simple yet concise code.
I want to explain the hotbars first because they are simpler but they still provide a general idea of the code style.
A Hotbar is a List of ItemStacks that is mapped to a list of Links (I'll explain this later). Practically every ItemStack (that is the object you see in the MC Hotbar) has a Link that represents the action to do once the player clicks. Aaand that's it. Here are some examples to see how this translates into code.
Hotbar hotbar = SimpleHotbar.of(HotbarLink.newLink(Link.consoleCommand("kick <player>"), GuiUtils.wool(DyeColor.BLUE, ChatColor.BLUE + "Kick")));
This creates an Hotbar with a single item that is displayed as a Blue wool with "Kick" as the name.
- SimpleHotbar is the default implementation of the Hotbar, you're higly suggested to use this.
- HotbarLink is a class that has both a Link and an ItemStack for the displayed item
- Link is a functional interface that is (simply explained)
void run(Player player)
, so it takes a Player as the input and gets nothing as output. - the Link class has already some implementation like Link.command and Link.consoleCommand. Both generate an Action that runs the command specified in the argument (replacing "" with the player name), the difference between command and consoleCommand is that consoleCommand executes the command with the ConsoleSender as the sender while command uses the player as the sender.
- the GuiUtils class contains a bunch of methods that could help you creating your ItemStack faster
Notes: SimpleHotbar.of() takes an array of HotbarLinks, HotbarLink.newLink (that should be static-imported when possible) takes the Link before the ItemStack (later you'll se why)
newLink(p -> p.sendMessage("pong"), wool(DyeColor.ORANGE, ChatColor.GOLD +"Ping"))
Note: newLink and wool are static-imported
Ok, now you now how to create an Hotbar, but it's quite useless if you don't ever add it to a Player, right?
To do this we use the HotbarManager! particularly the methods add
and set
.
HotbarManager.add(player, hotbar);
Note: the HotbarManager can have multiple hotbars used togheter (if multiple Hotbars are added from the same or even from multiple plugins). This is why there's a distinction between add
and set
. In particular add
"appends" the passed Hotbar to the player while set
overrides any previously set Hotbar with the one passed as argument. So never use HotbarManager.set
unless you know what you're doing
This isn't really that much supported, but for now you can do HotbarManager.remove(player)
to remove every Hotbar that the player has.
This operation should NOT be used unless there are some visual bugs, in that case please don't try to fix it by yourself but report it (with the appropriate description) at the Issue Tracker
Anyway the Hotbar reprinting can be achieved by writing HotbarManager.reprint(player)
There are a lot of ways to interact with a Player's links (intended as visual HotbarLinks, the ItemStack viewed in the Hotbar). The main way to do it is by HotbarManager.getLinks(player)
that returns a Stream of ItemStacks. There are also some helper methods like isItemSimilarToLink
, isItemLink
, anyItemLink
, isInventorySlotLink
and hasLinkInHand
(they are being used from the plugin internal listener)
If you want to do more Hotbar operations on the same player you don't want to use the HotbarManager more times because that would lookup the player more times. The suggested way is to use HotbarManager.get(player)
to get the HotbarData of the player and then do the same methods you would use with the HotbarManager in the HotbarData (removing the player argument)
A GUI is something like a Window in an OS. The only thing different is that a GUI shouldn't change, instead she should open other GUIs. In the GuiManager the GUIs are organized with a stack-like system that helps the developer to make easy Game-like player-system interaction.
a GUI has 4 overridable methods: onClick, onOpen, onClose and print. note that the first 3 methods are all listeners while the last one isn't. it's used to open the GUI to the player. This class is used to give maximum operability to every GUI system (this API shoul even implement Anvil input and Book output GUIs).
In most cases you want to use something simpler and more optimized. even if it isn't as flexible. The BaseGui class is an Abstract implementation of the Gui interface, it opens an Inventory to a player. the Gui stores the printed Inventory so it doesn't have to reprint it every time.
The overridable methods are: onOpen, onClose, render and needsUpdate. onOpen and onClose are the same of the Gui interface, render is a little different, it has no player as input and it must provide a (non-null) Inventory as output (that is the printed Gui), needsUpdate isn't really used but it could be, in some cases, it returns true only if the buffer (that contains the last print) should be reprinted. By default it always returns true only if the buffer is null.
There are other methods you could call like the clear() method that clears the buffer (consequentially) reprints the Gui on the next player print.
Example:
class RainbowGui extends BaseGui {
@Override
public void onClick(InventoryClickEvent event) {
//This should process the click
if(event.getCurrentItem().getType() != Material.AIR)
event.getWhoClicked().sendMessage("Clicked: " + DyeColor.getByWoolData(event.getCurrentItem().getData().getData()));
}
@Override
protected Inventory render() {
DyeColor[] colors = DyeColor.values();
Inventory inv = Bukkit.createInventory(null, GuiSize.min(colors.length));
for(int i = 0; i < colors.length; i++)
inv.setItem(i, wool(colors[i], colors[i].name().toLowerCase()));
return inv;
}
}
Note: the inventory returned can even be a non-chest inventory:
class DispenserGui extends BaseGui {
@Override
public void onClick(InventoryClickEvent event) {}
@Override
protected Inventory render() {
Inventory inv = Bukkit.createInventory(null, InventoryType.DISPENSER);
for(int i = 0; i < 9; i++)
inv.setItem(i, new ItemStack(Material.DIAMOND, 64));
return inv;
}
}
Once you have your beautiful Gui instance you want to open it to someone. The Gui management is a little bit more complex that the Hotbar system because of the stack-like system. There are 4 operations that change the stack:
- Open appends the Gui on top of the stack (clearing the stack before, if needed)
- Close clears the stack
- Back removes the last Gui from the stack, opening the Gui before or closing the inventory (if the stack is empty)
- Change back + open, changes the last Gui with the one passed as argument (the last gui is removed ony if the stack is not empty)
It's suggested not to chain the operations, those methods are optimized in reducing the player's inventory changes, removing, when possible, the flickering.
Note: by default the open method does not clear the stack.
An already implemented Gui is the FolderGui. This provides a Gui that contains a list of Items and Links (like an hotbar) that players can click. It's better to explain with an example:
gui = new FolderGui("Tools")//Gui title
.addLink(GuiAction.close().and(Link.consoleCommand("kick <player>")), wool(DyeColor.YELLOW, "Kick"))
.addLink(close().and(Link.consoleCommand("say <player> is STUPID!")), wool(DyeColor.GREEN, "Poke"))
.addLink(change(new RainbowGui()), wool(DyeColor.LIGHT_BLUE, "Rainbow :)")), //Example of the stack-like Gui system
So, method explanation:
-
addLink
: adds to the FolderGui a link with the respective ItemStack -
GuiAction.operation()
: Tells the system to do the operation, for example GuiAction.change(newgui) creates a Link that once executed changes the gui opened by the player into the one passed as argument -
link.add(otherlink)
: link merging, it could aslo be done withLink.add(link, otherlink)
, it respects the given order. In the example close().and(consoleCommand(...))) it closes the Gui and, after, executes a command
The FolderGui has other things that could be customized: the title and the backButton. the backButton is the button used by the player to go back without selecting anything. it can be removed setting it to null or it can be customized setting it to any other ItemStack
The ConfrimGui is an already implemented Gui that asks the user for confirmation. It has a lot of customizable parameters that we could change like:
-
onConfirm
link executed on player confirmation (default: GuiAction.close()) -
onDecline
link executed on player declination (defaukt: GuiAction.close()) -
onClose
link whenever the player closes the inventory (default: Link.EMPTY or nothing) -
acceptItem
item used as the accept button (default: Green wool) -
declineItem
item used as the decline button (default: Red wool) -
title
the title of the Gui (default: "Confirm") -
descriptionItem
item printed on top of the gui that the user can hover on to see the confirmation details (defailt: null)
Example:
new ConfirmGui()
.title("Sure to ban?")
.descriptionItem(wool(DyeColor.RED, "Ban")) //Optional, but suggesed
.onConfirm(Link.consoleCommand("ban <player>"))
Anvils are also supported (using spigot-anvil-gui), to create them you only need to use the AnvilInputGui
class.
There are sme parameters that you can change in the gui, those are:
- message: the message initially displayed as the item's name
- listener: the listener of that input, it takes two inputs (player and message) and has one output that is the error message (null or "" if no error is found)
The listener can be astracted using filters from
InputFilter
, the filters areInputFilters.plain(BiConsumer<Player, String>)
if any answer is ok and InputFilters.filter(BiConsumer<Player, >)` for any other filter. let's see a simple example:
new AnvilInputGui()
.message("Put your age")
.listener(InputFilters.filterInt((player, age) -> {
GuiManager.close(player);
player.sendMessage(age >= 18 ? "You can watch this" : "Go away!");
}))
NOTE: Remember to put the GuiManager.close or the anvil won't close
More examples can be found in the example file
This API includes tools to create interactive books (using spigot-book-api) while not providing any higher-level Helper for the utils, books are supported.
NOTE: Books are NOT Guis
Thats because of some protocol limitations (server doesn't know when a book is closed).
So there isn't any BookGui but there's the original BookUtil
class. When a book is opened to a player all the player's history (Gui stack) is cleared.
Here's a brief BookUtil's example:
new BookUtil.PageBuilder()
.add(
BookUtil.TextBuilder.of("Here's the drug")
.color(ChatColor.DARK_GREEN)
.style(ChatColor.BOLD)
.onHover(BookUtil.HoverAction.showText("Take the drugs!"))
.onClick(BookUtil.ClickAction.openUrl("https://www.google.it/search?q=drugs"))
.build()
)
.newLine()
.add("Now run! the ")
.add(
BookUtil.TextBuilder.of("POLICE")
.color(ChatColor.BLUE)
.onHover(BookUtil.HoverAction.showText("RUUUN"))
.onClick(BookUtil.ClickAction.runCommand("The police caught me :("))
.build()
)
.add(" is following us")
.build()
If you want a more in-depth explanation you can visit the BookApi's wiki, if you want more examples you can use both the BookApi's examples or the GuiApi's examples
If you see a bug you should do the following operations: 1 check if you have the latest version (check on the spigot page) 2 gather all the possible information about the bug, possible cause, enviromnent.... 3 create an issue in the issue tracker with all the possible information in it
If you want to find an example with all the things in this tutorial you can use the official test that we are using to test all the features
The code is open-source and it isn't all that big so, explore it!