Custom Buttons
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