Development
Custom Buttons
Creating custom button types for zMenu
Learn how to create and register custom button types in zMenu.
Overview
Custom buttons allow you to extend zMenu with new button behaviors. This involves:
- Creating a
ButtonLoaderto parse YAML configuration - Creating a
Buttonimplementation for the behavior - Registering the loader with the
ButtonManager
ButtonManager
Get the button manager to register custom buttons:
ButtonManager buttonManager = menuPlugin.getButtonManager();Creating a Button Loader
The ButtonLoader interface defines how your button is loaded from YAML:
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
public class MyButtonLoader implements ButtonLoader {
@Override
public String getName() {
return "MY_CUSTOM_BUTTON"; // The type name used in YAML
}
@Override
public Button load(YamlConfiguration configuration, String path, Plugin plugin) {
// Read custom properties from configuration
String customProperty = configuration.getString(path + ".custom-property", "default");
int customNumber = configuration.getInt(path + ".custom-number", 0);
boolean customFlag = configuration.getBoolean(path + ".custom-flag", false);
// Create and return your button instance
return new MyCustomButton(customProperty, customNumber, customFlag);
}
}Creating a Button Implementation
Implement the Button interface (or extend a base class):
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
public class MyCustomButton extends DefaultButtonValue implements Button {
private final String customProperty;
private final int customNumber;
private final boolean customFlag;
public MyCustomButton(String customProperty, int customNumber, boolean customFlag) {
this.customProperty = customProperty;
this.customNumber = customNumber;
this.customFlag = customFlag;
}
@Override
public String getName() {
return "MY_CUSTOM_BUTTON";
}
@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
// Your custom click logic here
player.sendMessage("Custom button clicked!");
player.sendMessage("Property: " + customProperty);
player.sendMessage("Number: " + customNumber);
player.sendMessage("Flag: " + customFlag);
// Example: Execute different actions based on configuration
if (customFlag) {
player.sendMessage("Flag is enabled!");
}
// You can also interact with the inventory
// inventory.refresh(player);
}
}Registering Your Button
Register your button loader when your plugin enables:
@Override
public void onEnable() {
MenuPlugin menuPlugin = (MenuPlugin) Bukkit.getPluginManager().getPlugin("zMenu");
if (menuPlugin != null) {
// Register custom button
menuPlugin.getButtonManager().register(new MyButtonLoader());
getLogger().info("Registered custom button: MY_CUSTOM_BUTTON");
}
}Using Your Custom Button
In your inventory YAML configuration:
items:
my-custom-item:
type: MY_CUSTOM_BUTTON
slot: 13
custom-property: "Hello World"
custom-number: 42
custom-flag: true
item:
material: DIAMOND
name: "&b&lCustom Button"
lore:
- "&7This is a custom button!"Complete Example: Teleport Button
TeleportButtonLoader.java
package com.example.buttons;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
public class TeleportButtonLoader implements ButtonLoader {
@Override
public String getName() {
return "TELEPORT";
}
@Override
public Button load(YamlConfiguration config, String path, Plugin plugin) {
String worldName = config.getString(path + ".world", "world");
double x = config.getDouble(path + ".x", 0);
double y = config.getDouble(path + ".y", 64);
double z = config.getDouble(path + ".z", 0);
float yaw = (float) config.getDouble(path + ".yaw", 0);
float pitch = (float) config.getDouble(path + ".pitch", 0);
String message = config.getString(path + ".message", "&aTeleported!");
World world = Bukkit.getWorld(worldName);
Location location = new Location(world, x, y, z, yaw, pitch);
return new TeleportButton(location, message);
}
}TeleportButton.java
package com.example.buttons;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
public class TeleportButton extends DefaultButtonValue implements Button {
private final Location location;
private final String message;
public TeleportButton(Location location, String message) {
this.location = location;
this.message = message;
}
@Override
public String getName() {
return "TELEPORT";
}
@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
// Close the inventory first
player.closeInventory();
// Teleport the player
player.teleport(location);
// Send message with color codes
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
}
}Usage in YAML
items:
spawn-teleport:
type: TELEPORT
slot: 13
world: "world"
x: 0
y: 100
z: 0
yaw: 90
pitch: 0
message: "&aYou have been teleported to spawn!"
item:
material: ENDER_PEARL
name: "&5&lTeleport to Spawn"
lore:
- "&7Click to teleport"Complete Example: Counter Button
A button that tracks clicks per player:
CounterButtonLoader.java
package com.example.buttons;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class CounterButtonLoader implements ButtonLoader {
// Shared counter storage across all counter buttons
private static final Map<UUID, Integer> counters = new HashMap<>();
@Override
public String getName() {
return "COUNTER";
}
@Override
public Button load(YamlConfiguration config, String path, Plugin plugin) {
int maxCount = config.getInt(path + ".max-count", 10);
String rewardCommand = config.getString(path + ".reward-command", "");
return new CounterButton(counters, maxCount, rewardCommand);
}
}CounterButton.java
package com.example.buttons;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.Map;
import java.util.UUID;
public class CounterButton extends DefaultButtonValue implements Button {
private final Map<UUID, Integer> counters;
private final int maxCount;
private final String rewardCommand;
public CounterButton(Map<UUID, Integer> counters, int maxCount, String rewardCommand) {
this.counters = counters;
this.maxCount = maxCount;
this.rewardCommand = rewardCommand;
}
@Override
public String getName() {
return "COUNTER";
}
@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
UUID uuid = player.getUniqueId();
// Get current count
int currentCount = counters.getOrDefault(uuid, 0);
currentCount++;
// Update counter
counters.put(uuid, currentCount);
player.sendMessage("§aClick count: " + currentCount + "/" + maxCount);
// Check if max reached
if (currentCount >= maxCount) {
player.sendMessage("§6You reached the maximum clicks!");
// Execute reward command
if (!rewardCommand.isEmpty()) {
String command = rewardCommand.replace("%player%", player.getName());
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
// Reset counter
counters.put(uuid, 0);
}
// Refresh the inventory to update display
inventory.refresh(player);
}
}Advanced: Button with Custom Item Display
Override the item display based on player state:
@Override
public ItemStack getCustomItemStack(Player player) {
// Return a custom item based on player state
int count = counters.getOrDefault(player.getUniqueId(), 0);
ItemStack item = new ItemStack(Material.PAPER);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName("§6Counter: " + count + "/" + maxCount);
item.setItemMeta(meta);
return item;
}Best Practices
- Use meaningful type names - Make them unique and descriptive
- Validate configuration - Check for missing or invalid values
- Handle null values - Use defaults for optional properties
- Keep buttons focused - One button type = one purpose
- Document your buttons - Explain required configuration options
- Test edge cases - Empty configs, missing properties, etc.
Next Steps
- Create Custom Actions
- Work with Player Data
- Listen to Events