What is L2UI? Well L2UI (Short for L2's User Interface) is a spigot tool for creating User Interfaces. It is an API designed around making any sort of UI you need without dealing with the nuances of inventories.
Let's face it, a majority of plugins need some sort of graphical user interface. If you are thinking yours doesn't well, you are wrong! GUI's in minecraft help by adding a great overall user experience to the player.
So, how does L2UI help you in this aspect?
Easy. I provide a pretty robust API (that I actually use in almost all of my plugins) for creating said graphical user interfaces. I can provide all the robust-ness without limiting what you can do, you still have access to all the normal inventory features.
What does it support:
Multiple users per inventory
Individual Screens per person (great for games)
Actions! (It will handle all events for you, and give them to you whenever you need them)
Animated GUI's! (that's right, you can have some pretty fancy animations if you want)
Auto-Pagination (this one I am especially proud of. It will auto paginate, for displaying lists of items. It will handle all the logic, all you do is tell it what slots you want it to appear in, and provide the list!)
Presistance (Want to create a crafting menu? sure! This will allow your UI to not accidentally ovewrite items in the UI that the player may have placed in there)
Callbacks (Have a lambda function you want to run after the user has done something easy! A great way to have interlinking UI's to pass data around)
Auto-Handles (player joins another UI? we got you covered. Player leaves? Leave it to me. Want to kick a player? Yup! I handle a lot of the player management)
Okay okay, enough about the features, lets dive into the code!
Here are some code examples. Unfortunately I haven't gotten around to doing java-docs or anything of that nature. I'll eventally add pictures and what-not.
Enjoy!
Code (Java):
publicclass ExampleStatic
extends UserInteface
{ //This is the base structure of a class. //In this example, one screen for all players. private Screen screen
; public ExampleStatic
(){ //Lets setup the screen this.
screen=new DefaultScreen
().
setName("Example!").
setSize(9); } //This is called whenever a screen updates and/or when a player joins. @Override
public Screen getScreen
(Player player
){ //lets return the screen to the player. return screen
; }
//Called before the screen is opened. @Override
publicboolean onJoin
(Player player
){ //Make sure this is true! returntrue; } //Called when the ui is officially dead. @Override
publicvoid onClose
(){
} //Calls when a player leaves the UI @Override
publicvoid onLeave
(OfflinePlayer player
){
} //Called when a player joins for the first time, before the onJoin. This is a great place to setup a screen @Override
publicvoid onInitialize
(){ //First we want to make sure all actions are cancelled by default. getL2UI
().
setCanceledByDefault(true); //lets add an icon.. screen.
addIcon(0, getInfoIcon
()); } //Helper methods for this example
privateIcon getInfoIcon
(){ Icon icon
=new DefaultIcon
(getInfoItemStack
()); //Lets send a message when the user selects this icon... icon.
setActionHandler((action
->{ //For this example, we want to know what type of action happened... if(action.
getEvent()instanceof InventoryClickEvent
){ //great it's a click! InventoryClickEvent event
=(InventoryClickEvent
) action.
getEvent(); if(event.
isLeftClick()){ //Grab the player that clicked via the action action.
getPlayer().
sendMessage("You left clicked me!"); }elseif(event.
isRightClick()){ action.
getPlayer().
sendMessage("You right clicked me!"); } } })); return icon
; }
private ItemStack getInfoItemStack
(){ //L2UI has a built in itembuilder class.. ItemBuilder itemBuilder
=new ItemBuilder
(Material.
ENCHANTED_BOOK); itemBuilder.
setDisplayName("&aClick me to see some information!"); itemBuilder.
setLore("&7Left click for message",
"&cRight click for different message"); return itemBuilder.
build(); } }
public CallableInterface
(String message
){ this.
screen=new DefaultScreen
().
setName("Confirm your action").
setSize(9); this.
message= message
; } //New function to support the callback. @Override
public CallableInterface setCallback
(Consumer
<Boolean
> consumer
){ this.
result= consumer
; returnthis; }
@Override
public Screen getScreen
(Player player
){ return screen
; }
@Override
publicboolean onJoin
(Player player
){ returntrue; }
@Override
publicvoid onClose
(){
}
@Override
publicvoid onLeave
(OfflinePlayer offlinePlayer
){ //You may want to add some logic if they leave without answering. }
@Override
publicvoid onInitialize
(){ //Cancel by default getL2UI
().
setCanceledByDefault(true); screen.
addIcon(0,
new DefaultIcon
(new ItemBuilder
(Material.
WRITTEN_BOOK).
setDisplayName(ChatColor.
GRAY+ message
).
build())); //[0][1][2][][4][][6][7][8] //Just to make it pretty, lets fill in the void. for(Integer i
: wall
){ screen.
addIcon(i,
new DefaultIcon
(new ItemStack
(Material.
BLACK_STAINED_GLASS_PANE))); } //Add the first yes screen.
addIcon(3,
new DefaultIcon
(new ItemBuilder
(Material.
GREEN_STAINED_GLASS_PANE).
setDisplayName("&aYes").
build()).
setActionHandler((action
->{ //the callback function will be true result.
accept(true); }))); //add the no icon screen.
addIcon(5,
new DefaultIcon
(new ItemBuilder
(Material.
RED_STAINED_GLASS_PANE).
setDisplayName("&cNo").
build()).
setActionHandler((action
->{ //the callback function will be false. result.
accept(false); }))); } }
Code (Java):
publicclass CraftingMenu
extends UserInteface
implements PersistantInterface
{ //In an effort to help save memory, I make these static & final as they will be used on all Crafting Menu instances. privatestaticfinalint[] border
={0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
15,
16,
17,
18,
19,
20,
24,
25,
26,
27,
28,
29,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42}; privatestaticfinalint[] craftingArea
={12,
13,
14,
21,
22,
23,
30,
31,
32}; privatestaticfinalint cancelSlot
=43; privatestaticfinalint saveSlot
=44; privatefinal Screen screen
; //We should keep track of the items. privatefinal HashMap
<Integer, ItemStack
> items
; public CraftingMenu
(){ screen
=new DefaultScreen
(45).
setName("&bRecipe Builder"); this.
items=new HashMap
<>(); }
@Override
public Screen getScreen
(Player player
){ return screen
; }
@Override
publicboolean onJoin
(Player player
){ returntrue; }
@Override
publicvoid onClose
(){
}
@Override
publicvoid onLeave
(OfflinePlayer offlinePlayer
){ //If the player leaves without crafting lets give the items back. for(int i
: craftingArea
){ if(screen.
getIcon(i
)==null){ //If they are offline, well.. I'll let you figure out what to do :) if(offlinePlayer.
isOnline()){ //TODO: This doesn't give the item back because there is no icon Inventory inventory
= Objects.
requireNonNull(offlinePlayer.
getPlayer()).
getInventory(); for(ItemStack itemStack
: items.
values()){ inventory.
addItem(itemStack
); } items.
clear(); } } } }
privatevoid setupButtons
(){ //If they cancel, we will kick them from the UI screen.
addIcon(cancelSlot,
new DefaultIcon
(new ItemBuilder
(Material.
REDSTONE_BLOCK).
setDisplayName("&cCancel").
build()).
setActionHandler((action
->{ //We need to actually cancel the action if(action.
getEvent()instanceof Cancellable
){ ((Cancellable
) action.
getEvent()).
setCancelled(true); } getL2UI
().
kick(action.
getPlayer()); }))); //This can be whatever you choose. but for this button, this will build the item screen.
addIcon(saveSlot,
new DefaultIcon
(new ItemBuilder
(Material.
EMERALD_BLOCK).
setDisplayName("&aBuild").
build()).
setActionHandler((action
->{ //Cancel the action because we cannot use cancelByDefault if(action.
getEvent()instanceof Cancellable
){ ((Cancellable
) action.
getEvent()).
setCancelled(true); } //Using action we can grab the raw inventory. We want the top item since this is where our crafting menu is. Inventory inventory
= action.
getRawInventory().
getTopInventory(); //Default setup array ItemStack
[] itemStacks
=new ItemStack
[9]; //Iterate over the crafting slots for(int i
=0; i
< craftingArea.
length; i
++){ //Is it null? if(inventory.
getItem(craftingArea
[i
])!=null){ itemStacks
[i
]= inventory.
getItem(craftingArea
[i
]); }else{ itemStacks
[i
]=new ItemStack
(Material.
AIR); } } //You now have the items in the crafting area you can figure out what you want to do. //Remove one of each? and give item? sure!
}))); }
privatevoid setupBorder
(){ //Border to make it pretty. for(int i
: border
){ screen.
addIcon(i,
new DefaultIcon
(new ItemStack
(Material.
BLACK_STAINED_GLASS_PANE))); } }
privatevoid setupCraftingMenu
(){ //Iterate over the crafting areas for(int i
: craftingArea
){ //We want to set a raw action handler for the slot, without the icon! Super important!!!! //The reason we set a tick delay is because we need to make sure the item is actually placed. screen.
setSlotActionHandler(i,
(a
)-> Bukkit.
getScheduler().
scheduleSyncDelayedTask(<YourPlugin
>,
()->{ //Lets grab the item. ItemStack itemToAdd
= a.
getRawInventory().
getTopInventory().
getItem(a.
getSlot()); //Are they placing or adding? if(itemToAdd
==null){ //if they are removing, then remove from items. items.
remove(a.
getSlot()); }else{ //if they are adding, add it. items.
put(a.
getSlot(), itemToAdd
); } }, 5L
)); } } //Last but not least, we need to make sure they can't shift click or drag this could mess up the UI privatevoid setupBottomSlots
(){ //Iterate over their inventory slots. for(int i
=45; i
<81; i
++){ //Set a raw action handler screen.
setSlotActionHandler(i,
(a
)->{ //Cancel those annoying events. if(a.
getEvent()instanceof InventoryDragEvent
){ ((InventoryDragEvent
) a.
getEvent()).
setCancelled(true); } if(a.
getEvent()instanceof InventoryClickEvent
){ if(((InventoryClickEvent
) a.
getEvent()).
isShiftClick()){ ((InventoryClickEvent
) a.
getEvent()).
setCancelled(true); } } }); } }
} }
Code (Java):
publicclass PaginatedUI
extends PaginatedMenu
{ privatefinal List
<String
> strings
; //We need the items that we want to display. public PaginatedUI
(List
<String
> strings
){ //The menu title, the reason we declare it in super is the paginatedMenu class will do a lot of the setup for us. super("Paginated String Menu"); // Assign this.
strings= strings
; //Lets sort it, to make the users life easier strings.
sort(String::compareTo
); } @Override
publicboolean onJoin
(Player player
){ //As you may notice, there is no getScreen Method, the paginated menu only supports 1 player at a time!!! //I like to do loading here so that we know what player is viewing (in case you don't want the user to see everything ///Or if you just need permission checks. load
(player
); returntrue; }
@Override
publicvoid onClose
(){
}
@Override
publicvoid onLeave
(OfflinePlayer player
){
}
publicvoid load
(Player player
){ //Due to loading with each player, we may need to update the list. //In order to do this, we must remove everything (aka clear) and re-add. getScreen
().
clearPages(); //Lets iterate over everything we want to add for(String s
: strings
){ //Just as an example, if you only want to display the icon if they have permission if(player.
hasPermission("yourplugin."+s
)){ //Add to list will allow us to add an item.. getScreen
().
addToList(generateIcon
(s
)); } }
} //Helper method to clean up code.. privateIcon generateIcon
(String s
){ returnnew DefaultIcon
(new ItemBuilder
(Material.
PAPER).
setDisplayName(ChatColor.
GRAY+ s
).
build()).
setActionHandler((a
)->{ //Do something when they select it, if you want. }); }
@Override
publicvoid onInitialize
(){ //Lets cancel all events by default. getL2UI
().
setCanceledByDefault(true); //Now lets setup our paginated menu. //The update Indexes method will be the UI slots that we want to populate with our items (don't worry about making it exact) getScreen
().
updateIndexes(newint[]{1,
2,
3,
4,
5,
6,
7}); //The menu size, must be divisable by 9! getScreen
().
setSize(9); //The paginated menu also needs the slots for the 'next page' and 'previous page' icon. must be within the bounds //In our case, at the beginning and end of the menu. getScreen
().
setPreviousSlot(0); getScreen
().
setNextSlot(8); } }
Code (Java):
publicclass MysteryBox
extends UserInteface
implements Callback
<ItemStack
>{ //Slots in which to get the item. privatestaticfinalint[] randoms
={10,
11,
12,
13,
14,
15,
16}; //Actionalble slot privatestaticfinalint middle
=13; //Beacons privatestaticfinalint top
=4; privatestaticfinalint bottom
=22; privatefinal Queue
<Material
> queue
; privatefinal Screen screen
; private Consumer
<ItemStack
> itemStackConsumer
; privateboolean finished
=false;
public MysteryBox
(){ //Set it up this.
screen=new DefaultScreen
().
setSize(27).
setName(ChatColor.
BLUE+"Mystery box"); this.
queue=new LinkedList
<>(); }
@Override
publicvoid onInitialize
(){ //Cancel by default getL2UI
().
setCanceledByDefault(true); //set refresh rate ((AnimatedL2UI
) getL2UI
()).
setRefreshRate(2); //Add starting icons for(int i
=0; i
< randoms.
length; i
++){ Material m
= getRandomMaterial
(); this.
queue.
add(m
); this.
screen.
addIcon(randoms
[i
],
new DefaultIcon
(new ItemStack
(m
))); } //Actionable slot this.
screen.
setSlotActionHandler(middle,
(action
->{ //If it is finished, we don't want the action to happen anymore if(finished
)return; //Grab the item, make sure it's final final ItemStack itemStack
= action.
getIcon().
getRepresentation(); //Stop the refresh ((AnimatedL2UI
) getL2UI
()).
setRefreshRate(0); finished
=true; //Clear everything but the middle slot (in this case, the slot that was selected for(int slot
: randoms
){ if(slot
!= action.
getSlot()){ this.
screen.
getIcon(slot
).
update(new ItemStack
(Material.
AIR)); } } //force one more refresh. getL2UI
().
simpleRefresh(); //Set a delay to make the transition smoother Bukkit.
getScheduler().
scheduleSyncDelayedTask(Mystic.
getInstance(),
()->{ itemStackConsumer.
accept(itemStack
); }, 30L
); })); //setup beacons this.
screen.
addIcon(top,
new DefaultIcon
(new ItemStack
(Material.
BEACON))); this.
screen.
addIcon(bottom,
new DefaultIcon
(new ItemStack
(Material.
BEACON))); }
privatevoid updateScreen
(){ //If it is finished we don't need to do this anymore if(!finished
){ //remove first queue.
poll(); //add new item queue.
add(getRandomMaterial
()); //Iterate over the queue Iterator
<Material
> materialIterator
= queue.
iterator(); int index
=0; while(materialIterator.
hasNext()){ Material m
= materialIterator.
next(); //update each slot. this.
screen.
getIcon(randoms
[index
]).
update(new ItemStack
(m
)); index
++; } } }
private Material getRandomMaterial
(){ Material
[] materialArr
= Material.
values(); Material m
=null; do{ m
= materialArr
[getRandomNumberUsingNextInt
(0, materialArr.
length)]; }while(m.
isAir()&&!m.
isItem()); return m
; }
publicint getRandomNumberUsingNextInt
(int min,
int max
){ Random random
=newRandom(); return random.
nextInt(max
- min
)+ min
; } }
Code (Java):
publicvoid showUI
(Player player
){ //First lets instantiate the UI ExampleStatic exampleStatic
=new ExampleStatic
(); //We want this to be a static. Although animated is available with .getAnimatedL2UserInterface(<ui>); L2UserInterface l2UI
=new L2UI
().
getRegistrar().
getStaticL2UserInterface(exampleStatic
); //all they need to do now is join it. l2UI.
join(player
); }
publicvoid callableUI
(Player player
){ CallableInterface callableInterface
=new CallableInterface
("Are you having fun?"); //Set the callback callableInterface.
setCallback((bol
)->{ if(bol
){ System.
out.
println(player.
getName()+" is having fun!"); }else{ System.
out.
pr[SPOILER
="Example"][/SPOILER
]intln
(player.
getName()+" is not having fun :("); } }); //We want this to be a static. Although animated is available with .getAnimatedL2UserInterface(<ui>); L2UserInterface l2UI
=new L2UI
().
getRegistrar().
getStaticL2UserInterface(callableInterface
); //all they need to do now is join it. l2UI.
join(player
); }
publicvoid animatedUI
(Player player
){ ExampleStatic exampleStatic
=new ExampleStatic
(); AnimatedL2UI animatedL2UI
=(AnimatedL2UI
)new L2UI
().
getRegistrar().
getAnimatedL2UserInterface(exampleStatic
); //refresh every second. (20 ticks in a second) animatedL2UI.
setRefreshRate(20); animatedL2UI.
join(player
); }
publicvoid paginatedUI
(Player player, List
<String
> stringList
){ PaginatedUI paginatedUI
=new PaginatedUI
(stringList
); //We want this to be a static. Although animated is available with .getAnimatedL2UserInterface(<ui>); L2UserInterface l2UI
=new L2UI
().
getRegistrar().
getStaticL2UserInterface(paginatedUI
); //all they need to do now is join it. l2UI.
join(player
); }