Create own types
Note
In many examples we do not adhere to some Java conventions and common code style to avoid boilerplate code. We want to show how to use API, and not how to write the proper code in Java.
AbstractMenus gives access to create own Action, Rule, Item property, and Catalog.
Create Action
Each action class must implement the Action interface.
public class MyAction implements Action {
@Override
public void activate(Player player, Menu menu, Item clickedItem) {
player.sendMessage("Hello! This is my action!");
}
}
This is the simplest example. Note that clickedItem
can be null
. It depends on what triggered the action call - a click on an item, or something else.
As written on Types deserializing page, each action must have its own serializer. Let’s add it.
public class MyAction implements Action {
private final String text;
private MyAction(String text) {
this.text = text;
}
@Override
public void activate(Player player, Menu menu, Item clickedItem) {
player.sendMessage(text);
}
public static class Serializer implements NodeSerializer<MyAction> {
@Override
public MyAction deserialize(Class<MyAction> type, ConfigNode node) {
return new MyAction(node.getString());
}
}
}
Now our action can take a string parameter and send it in a message to the player. To register our action, we need a Types manager. To register action you need to call registerAction
method which requires some arguments:
Action name. This name must be unique and will be used in menu configuration.
Action class. In our case this is
MyAction.class
.Serializer instance.
Types.registerAction("myAction", MyAction.class, new MyAction.Serializer());
Here myAction
is the key of our action. This is how it might be used in a menu file:
items: [
{
slot: 1
material: STONE
name: "My item"
click {
myAction: "Hello! This is my action!"
}
}
]
Create Rule
Each rule implements the Rule interface. It contains one method, which, depending on the check, can return true
(the player matches the rule) or false
(the player doesn’t matches the rule).
public class MyRule implements Rule {
@Override
public boolean check(Player player, Menu menu, Item clickedItem) {
return player.getName().equals("Bob");
}
}
In this case, the rule will return false
if player’s name is not Bob
. The creation of a serializer is exactly the same as for Action.
public class MyRule implements Rule {
@Override
public boolean check(Player player, Menu menu, Item clickedItem) {
return player.getName().equals("Bob");
}
public static class Serializer implements NodeSerializer<MyRule> {
@Override
public MyRule deserialize(Class<MyRule> type, ConfigNode node) {
return new MyRule();
}
}
}
Here we decided not to avoid parameters. Our rule will just check the player’s username.
Registration is similar to an Action, but calling registerRule
method.
Types.registerRule("myRule", MyRule.class, new MyRule.Serializer());
Since our rule does not accept any parameters, when specifying it in the menu file, we can simply specify true
.
rules {
myRule: true
}
This applies to all registered actions, rules, etc., which have no parameters.
Create Value Extractor
Value extractors used by Activators and Catalogs, so before learn how to make them, you need to know how to make extractor.
Value extractor is an object that takes some object and placeholder. By provided placeholder extractor returns some values from provided object.
Each value extractor should implement ValueExtractor interface. This interface has single method with two arguments:
Object context
Placeholder string
Lets create simple value extractor. For example, we have some class:
public class User {
public String name;
public int age;
public int friends;
}
To get some value from object of this class, we create extractor like this:
public class UserExtractor implements ValueExtractor {
@Override
public String extract(Object obj, String placeholder) {
if (obj instanceof User) {
User user = (User) obj;
switch (placeholder) {
case "user_name": return user.name;
case "user_age": return String.valueOf(user.age);
case "user_friends": return String.valueOf(user.friends);
}
}
return null;
}
}
First, we cast object to User
. Then depends on placeholder we returned requested value from this object.
This API similar to PlaceholderAPI, but it can accept any type, not only Player
.
Value exractors are not registered, like actions or rules. Each menu element whoch uses it, should provide extractor instance. You will understood how it works after reading next parts of this page.
Create Activator
Each activator extends from the abstract class Activator. It has no abstract methods to implement. This class is implemented from the Bukkit’s Listener
interface, so inside you can listen for events, the calling of which will open the menu.
Important
Do not register activator as listener manually. The plugin will do it automatically.
public class MyActivator extends Activator {
@EventHandler
public void onSneak(PlayerToggleSneakEvent event) {
if (event.isSneaking()) {
openMenu(null, event.getPlayer());
}
}
public static class Serializer implements NodeSerializer<MyActivator> {
@Override
public MyActivator deserialize(Class<MyActivator> type, ConfigNode node) {
return new MyActivator();
}
}
}
The openMenu
method is a part of the Activator
class. It opens the menu in which this activator is located. In this case, we open the menu if the player toggles sneak on. This method takes two arguments:
- ctx:
The context of activator. Can be null if there no any context value.
- player:
Player for who we menu will be opened
How to add context to activator, you can read in section below.
Registration of activator similar to other menu types, but using registerActivator
method.
Types.registerActivator("myActivator", MyActivator.class, new MyActivator.Serializer());
Activator with context
Activator’s context is any object that saves before opening menu. After menu opened, you can get access to some context value through value extractor’s placeholders.
To create activator with context, you need to specify required object when you call openMenu
method.
public class MyActivator extends Activator {
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
openMenu(event.getRespawnLocation(), event.getPlayer());
}
}
Here, we specified location in which player respawned as context. To provide access to context values (in our case this is Location
object), we need to create Value Extractor. This process described here.
We will create simple extractor for Location
object.
public class LocationExtractor implements ValueExtractor {
@Override
public String extract(Object obj, String placeholder) {
if (obj instanceof Location) {
Location loc = (Location) obj;
switch (placeholder) {
case "loc_x": return String.valueOf(loc.getX());
case "loc_y": return String.valueOf(loc.getY());
case "loc_z": return String.valueOf(loc.getZ());
}
}
return null;
}
}
Now we need to return our extractor in overrided getValueExtractor
method.
public class MyActivator extends Activator {
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
openMenu(event.getRespawnLocation(), event.getPlayer());
}
@Override
public ValueExtractor getValueExtractor() {
return new LocationExtractor();
}
}
Now, we need to register activator.
Types.registerActivator("myActivator", MyActivator.class, new MyActivator.Serializer());
After this, we can use this activator in menu.
title: "Test"
size: 1
activators {
myActivator: true
}
items: [
{
slot: 4
material: CAKE
name: "Test item"
lore: [
"Loc x: %activator_loc_x%",
"Loc y: %activator_loc_y%"
"Loc z: %activator_loc_z%"
]
}
]
Create Item Property
An item property is an object that modify item appearance.
To create new item property, you need to implement ItemProperty interface. There is several methods to implement:
- canReplaceMaterial:
Does this property modify item type (material installer). If
true
, then plugin will apply this property first, to generate valid item meta.- isApplyMeta:
If return
false
, plugin won’t apply saved meta after exiting fromapply
method. Returntrue
if you don’t set own meta to provided ItemStack.- apply:
Apply properties to ItemStack or ItemMeta.
Example:
public class MyProperty implements ItemProperty {
private final String name;
private MyProperty (String name) {
this.name = name;
}
@Override
public boolean canReplaceMaterial() {
return false;
}
@Override
public boolean isApplyMeta() {
return true;
}
@Override
public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) {
meta.setDisplayName(name);
}
public static class Serializer implements NodeSerializer<MyProperty> {
@Override
public MyProperty deserialize(Class<MyProperty> type, ConfigNode node) {
return new MyProperty(node.getString());
}
}
}
Another example with a material replacer. Here we give out the creeper head. This property has no parameters, so we can simply specify true
instead of any parameters.
public class MyMaterialProperty implements ItemProperty {
@Override
public boolean canReplaceMaterial() {
return true;
}
@Override
public boolean isApplyMeta() {
return false;
}
@Override
public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) {
item.setType(Material.CREEPER_HEAD);
}
public static class Serializer implements NodeSerializer<MyMaterialProperty> {
@Override
public MyMaterialProperty deserialize(Class<MyMaterialProperty> type, ConfigNode value) {
return new MyMaterialProperty();
}
}
}
After we created item property, we eed to register it.
Types.registerItemProperty("myProperty", MyMaterialProperty.class, new MyMaterialProperty.Serializer());
Create Catalog
When menu opens, catalog provides collection of objects. Also catalog must return Value Extractor to provide access to object properties through placeholders.
To create catalog you need:
The class, objects of which you will provide.
Value extractor for this type.
Implement catalog which returns collection of your objects and provides.
Make serializer for catalog.
For example, we have such type:
public class User {
public String name;
public int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
Then we create simple extractor for this type.
public class UserExtractor implements ValueExtractor {
@Override
public String extract(Object obj, String placeholder) {
if (obj instanceof User) {
User user = (User) obj;
switch (placeholder) {
case "user_name": return user.name;
case "user_age": return String.valueOf(user.age);
}
}
return null;
}
}
When we have type and extractor for this type, we can create catalog. Each catalog implements Catalog interface.
public class UserCatalog implements Catalog<User> {
@Override
public Collection<User> snapshot(Player player, Menu menu) {
return Arrays.asList(
new User("User 1", 17),
new User("User 2", 18),
new User("User 3", 19),
new User("User 4", 20),
new User("User 5", 21),
new User("User 6", 22)
);
}
@Override
public ValueExtractor extractor() {
return new UserExtractor();
}
public static class Serializer implements NodeSerializer<UserCatalog> {
@Override
public UserCatalog deserialize(Class<UserCatalog> type, ConfigNode node) throws NodeSerializeException {
return new UserCatalog();
}
}
}
The snapshot
method should return collection of your objects. This collection can be empty, but shouldn’t be null
.
Now we can register it.
Types.registerCatalog("my_catalog", MyCatalog.class, new MyCatalog.Serializer());
After this, we can use our catalog in menu.
title: "Test menu"
size: 4
catalog {
type: my_catalog
}
matrix {
cells: [
"_x_x_x_x_",
"_x_x_x_x_",
"_x_x_x_x_"
]
templates {
"x" {
material: CAKE
name: "%activator_user_name%"
lore: "&7Age: &e%activator_user_age%"
}
}
}
If you need to add additional parameters to catalog, you can parse it from catalog
block. This block is provided ConfigNode
in serializer.