GroupeZ Docs
Development

MenuItemStack

Creating and manipulating dynamic items with MenuItemStack

Cette page est affichée en anglais car sa traduction française n’est pas encore disponible.

Learn how to use MenuItemStack to create dynamic, placeholder-aware items in zMenu.

Overview

MenuItemStack is an abstraction over Bukkit's ItemStack that provides:

  • Placeholder support in names, lores, and other properties
  • Dynamic item building based on player context
  • Easy manipulation of item properties
  • Support for custom model data, enchantments, and more

Getting MenuItemStack

From Button Configuration

In custom buttons, retrieve the configured item:

public class MyButton extends Button {

    @Override
    public ItemStack getCustomItemStack(Player player) {
        // Get the MenuItemStack from YAML configuration
        MenuItemStack menuItemStack = this.getItemStack();

        // Build and return the ItemStack
        return menuItemStack.build(player);
    }
}

From InventoryManager

Load a MenuItemStack from configuration:

InventoryManager manager = menuPlugin.getInventoryManager();
MenuItemStack itemStack = manager.loadItemStack(configuration, "path.to.item");

Building ItemStacks

Basic Build

MenuItemStack menuItemStack = getItemStack();

// Simple build with placeholder parsing
ItemStack item = menuItemStack.build(player);

Build with Cache Control

// Build without caching (always fresh)
ItemStack item = menuItemStack.build(player, false);

// Build with caching (reuses cached result when possible)
ItemStack itemCached = menuItemStack.build(player, true);

Build with Custom Placeholders

import fr.maxlego08.menu.api.utils.Placeholders;

MenuItemStack menuItemStack = getItemStack();
Placeholders placeholders = new Placeholders();

// Register custom placeholders
placeholders.register("price", "1000");
placeholders.register("currency", "coins");
placeholders.register("stock", "42");

// Build with custom placeholders
ItemStack item = menuItemStack.build(player, false, placeholders);

Custom Placeholders

Creating Placeholders

Placeholders placeholders = new Placeholders();

// String values
placeholders.register("player_name", player.getName());
placeholders.register("server_name", "MySever");

// Numeric values (converted to string)
placeholders.register("balance", String.valueOf(economy.getBalance(player)));
placeholders.register("level", String.valueOf(player.getLevel()));

// Boolean states
placeholders.register("is_premium", player.hasPermission("premium") ? "Yes" : "No");

// Formatted values
placeholders.register("health", String.format("%.1f", player.getHealth()));

Using in YAML

item:
  material: DIAMOND
  name: "&6Purchase for %price% %currency%"
  lore:
    - "&7Stock remaining: &e%stock%"
    - "&7Seller: &b%seller_name%"
    - ""
    - "&aClick to buy!"

Complete Example: Dynamic Shop Item

SellBuyButton.java

package com.example.shop.buttons;

import fr.maxlego08.menu.api.MenuItemStack;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.utils.Placeholders;
import com.example.shop.ShopPlugin;
import com.example.shop.ShopItem;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;

import java.math.BigDecimal;

public class SellBuyButton extends Button {

    private final ShopPlugin plugin;

    public SellBuyButton(Plugin plugin) {
        this.plugin = (ShopPlugin) plugin;
    }

    @Override
    public ItemStack getCustomItemStack(Player player) {
        // Get the MenuItemStack from configuration
        MenuItemStack menuItemStack = this.getItemStack();

        // Get data from player cache
        var cache = plugin.getCacheManager().getCache(player);
        ShopItem shopItem = cache.get("current_item");
        BigDecimal price = cache.get("price", BigDecimal.ZERO);

        // Create placeholders
        Placeholders placeholders = new Placeholders();
        placeholders.register("price", plugin.getEconomyManager().format(price));
        placeholders.register("item_name", shopItem.getName());
        placeholders.register("quantity", String.valueOf(shopItem.getQuantity()));

        // Check affordability and add status
        boolean canAfford = plugin.getEconomyManager().has(player, price);
        placeholders.register("status", canAfford ? "&aYou can afford this!" : "&cNot enough money!");

        // Build and return
        return menuItemStack.build(player, false, placeholders);
    }

    @Override
    public void onClick(Player player, InventoryClickEvent event,
                       InventoryEngine inventory, int slot, Placeholders placeholders) {
        super.onClick(player, event, inventory, slot, placeholders);

        var cache = plugin.getCacheManager().getCache(player);
        ShopItem shopItem = cache.get("current_item");
        BigDecimal price = cache.get("price", BigDecimal.ZERO);

        if (plugin.getEconomyManager().withdraw(player, price)) {
            shopItem.give(player);
            player.sendMessage("§aPurchase successful!");
            player.closeInventory();
        } else {
            player.sendMessage("§cYou don't have enough money!");
        }
    }
}

YAML Configuration

items:
  buy-button:
    type: MY_SELL_BUY
    slot: 22
    item:
      material: EMERALD
      name: "&a&lConfirm Purchase"
      lore:
        - "&7Item: &f%item_name%"
        - "&7Quantity: &e%quantity%"
        - "&7Price: &6%price%"
        - ""
        - "%status%"
        - ""
        - "&eClick to purchase!"

Complete Example: Sort Button with States

SortButton.java

package com.example.sort.buttons;

import fr.maxlego08.menu.api.MenuItemStack;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.utils.Placeholders;
import com.example.sort.SortPlugin;
import com.example.sort.SortType;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;

import java.util.List;

public class SortButton extends Button {

    private final SortPlugin plugin;
    private final String enableText;
    private final String disableText;
    private final MenuItemStack loadingItemStack;
    private final List<SortType> sortTypes;

    public SortButton(Plugin plugin, String enableText, String disableText,
                     MenuItemStack loadingItemStack, List<SortType> sortTypes) {
        this.plugin = (SortPlugin) plugin;
        this.enableText = enableText;
        this.disableText = disableText;
        this.loadingItemStack = loadingItemStack;
        this.sortTypes = sortTypes;
    }

    @Override
    public boolean isPermanent() {
        return true;
    }

    @Override
    public ItemStack getCustomItemStack(Player player) {
        var cache = plugin.getCacheManager().getCache(player);
        SortType currentSort = cache.get("current_sort", SortType.DATE_DESC);

        MenuItemStack itemStack = this.getItemStack();
        Placeholders placeholders = new Placeholders();

        // Register state for each sort type
        for (SortType sortType : sortTypes) {
            String state = (sortType == currentSort) ? enableText : disableText;
            String displayName = state.replace("%sorting%", sortType.getDisplayName());
            placeholders.register(sortType.name(), displayName);
        }

        return itemStack.build(player, false, placeholders);
    }

    @Override
    public void onClick(Player player, InventoryClickEvent event,
                       InventoryEngine inventory, int slot, Placeholders placeholders) {
        super.onClick(player, event, inventory, slot, placeholders);

        var cache = plugin.getCacheManager().getCache(player);
        SortType currentSort = cache.get("current_sort", SortType.DATE_DESC);

        // Show loading state
        for (Integer buttonSlot : getSlots()) {
            inventory.getSpigotInventory().setItem(buttonSlot, loadingItemStack.build(player));
        }

        // Calculate next sort type
        int currentIndex = sortTypes.indexOf(currentSort);
        int direction = event.isRightClick() ? -1 : 1;
        int nextIndex = (currentIndex + direction + sortTypes.size()) % sortTypes.size();
        SortType nextSort = sortTypes.get(nextIndex);

        // Update cache and refresh
        cache.set("current_sort", nextSort);

        plugin.getScheduler().runAsync(task -> {
            // Perform async sorting
            plugin.getSortManager().sortItems(player, nextSort);

            // Update inventory on main thread
            plugin.getScheduler().runNextTick(w -> {
                inventory.updateInventory();
            });
        });
    }
}

YAML Configuration

items:
  sort-button:
    type: MY_SORT_BUTTON
    slot: 49
    is-permanent: true
    item:
      material: HOPPER
      name: "&e&lSort Items"
      lore:
        - "&7Current sorting options:"
        - ""
        - " %DATE_DESC%"
        - " %DATE_ASC%"
        - " %PRICE_DESC%"
        - " %PRICE_ASC%"
        - " %NAME_ASC%"
        - ""
        - "&eLeft-click &7for next"
        - "&eRight-click &7for previous"

Complete Example: Toggle Button

ToggleButton.java

package com.example.settings.buttons;

import fr.maxlego08.menu.api.MenuItemStack;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.engine.InventoryEngine;
import fr.maxlego08.menu.api.utils.Placeholders;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;

public class ToggleButton extends Button {

    private final Plugin plugin;
    private final String settingKey;
    private final MenuItemStack enabledItem;
    private final MenuItemStack disabledItem;

    public ToggleButton(Plugin plugin, String settingKey,
                       MenuItemStack enabledItem, MenuItemStack disabledItem) {
        this.plugin = plugin;
        this.settingKey = settingKey;
        this.enabledItem = enabledItem;
        this.disabledItem = disabledItem;
    }

    @Override
    public ItemStack getCustomItemStack(Player player) {
        boolean isEnabled = getSettingValue(player);

        // Use different MenuItemStack based on state
        MenuItemStack itemStack = isEnabled ? enabledItem : disabledItem;

        Placeholders placeholders = new Placeholders();
        placeholders.register("status", isEnabled ? "&aEnabled" : "&cDisabled");
        placeholders.register("action", isEnabled ? "disable" : "enable");

        return itemStack.build(player, false, placeholders);
    }

    @Override
    public void onClick(Player player, InventoryClickEvent event,
                       InventoryEngine inventory, int slot, Placeholders placeholders) {
        super.onClick(player, event, inventory, slot, placeholders);

        // Toggle the setting
        boolean currentValue = getSettingValue(player);
        setSettingValue(player, !currentValue);

        // Refresh inventory
        inventory.updateInventory();

        // Notify player
        String newStatus = !currentValue ? "enabled" : "disabled";
        player.sendMessage("§aSetting " + settingKey + " " + newStatus + "!");
    }

    private boolean getSettingValue(Player player) {
        // Implementation depends on your storage system
        return plugin.getConfig().getBoolean("players." + player.getUniqueId() + "." + settingKey, false);
    }

    private void setSettingValue(Player player, boolean value) {
        plugin.getConfig().set("players." + player.getUniqueId() + "." + settingKey, value);
        plugin.saveConfig();
    }
}

Loading MenuItemStack from Configuration

Custom ButtonLoader

package com.example.loader;

import fr.maxlego08.menu.api.MenuItemStack;
import fr.maxlego08.menu.api.InventoryManager;
import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import com.example.buttons.ToggleButton;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;

public class ToggleButtonLoader implements ButtonLoader {

    private final Plugin plugin;
    private final InventoryManager inventoryManager;

    public ToggleButtonLoader(Plugin plugin, InventoryManager inventoryManager) {
        this.plugin = plugin;
        this.inventoryManager = inventoryManager;
    }

    @Override
    public String getName() {
        return "MY_TOGGLE";
    }

    @Override
    public Button load(YamlConfiguration config, String path, Plugin plugin) {
        // Load basic properties
        String settingKey = config.getString(path + ".setting-key", "default");

        // Load MenuItemStack for enabled state
        MenuItemStack enabledItem = inventoryManager.loadItemStack(config, path + ".enabled-item");

        // Load MenuItemStack for disabled state
        MenuItemStack disabledItem = inventoryManager.loadItemStack(config, path + ".disabled-item");

        return new ToggleButton(this.plugin, settingKey, enabledItem, disabledItem);
    }
}

YAML Configuration

items:
  notifications-toggle:
    type: MY_TOGGLE
    slot: 11
    setting-key: "notifications"
    enabled-item:
      material: LIME_DYE
      name: "&a&lNotifications: %status%"
      lore:
        - "&7You will receive notifications"
        - ""
        - "&eClick to %action%"
    disabled-item:
      material: GRAY_DYE
      name: "&c&lNotifications: %status%"
      lore:
        - "&7Notifications are turned off"
        - ""
        - "&eClick to %action%"

Properties available in YAML configuration:

item:
  # Material
  material: DIAMOND_SWORD

  # Display name
  name: "&6&lLegendary Sword"

  # Lore lines
  lore:
    - "&7A powerful weapon"
    - "&7Damage: &c+50"

  # Amount
  amount: 1

  # Custom model data (1.14+)
  modelId: 12345

  # Durability/damage
  durability: 0

  # Enchantments
  enchants:
    - SHARPNESS,5
    - UNBREAKING,3

  # Item flags
  flags:
    - HIDE_ENCHANTS
    - HIDE_ATTRIBUTES

  # Glow effect (without enchant text)
  glow: true

  # Player head
  playerHead: "%player%"
  # Or custom URL
  url: "eyJ0ZXh0dXJlcyI6..."

  # Potion
  potion: SPEED
  potionExtended: true
  potionUpgraded: false

  # Leather armor color
  color: "#FF5555"

  # Banner patterns
  banner:
    - RED,STRIPE_TOP
    - BLUE,CROSS

Best Practices

  1. Use placeholders - Leverage the placeholder system for dynamic content
  2. Cache when possible - Use build(player, true) for static items
  3. Create reusable placeholders - Build a helper class for common placeholders
  4. Handle null values - Always provide defaults for placeholder values
  5. Keep lore concise - Long lores can cause display issues
  6. Test with PlaceholderAPI - Ensure PAPI placeholders work correctly

Next Steps

Sur cette page

GroupeZ MCP

La documentation dans ton IA

Le MCP permet à ton IA de chercher dans la documentation GroupeZ et de lire les exemples à jour pour t’aider à configurer tes plugins.

Lecture seule · Sans clé API côté documentation

1. Copie l’adresse du serveur

Transport : HTTP (Streamable HTTP). Authentification : aucune. Ton outil utilise son propre modèle et ses propres limites d’abonnement.

  1. Ouvre Plugins dans ChatGPT sur le web, puis + → Ajouter un serveur MCP personnalisé.
  2. Nomme-le « GroupeZ Documentation », colle l’URL ci-dessus et choisis « Aucune authentification ». Crée puis installe le plugin.
  3. Dans une conversation, tape @ et sélectionne ce plugin avant d’envoyer ta question. Si nécessaire, active search, get_page et list_pages dans ses paramètres.
Guide officiel de cet outil

3. Pose une question

Utilise le MCP GroupeZ pour m’aider à créer un inventaire zMenu. Recherche les pages pertinentes, lis les exemples et cite tes sources.
Autres clients et dépannage

Pour les clients utilisant mcpServers, adapte cet exemple à leur format :

{
  "mcpServers": {
    "groupez-docs": {
      "url": ""
    }
  }
}

Les outils disponibles sont search, get_page et list_pages. Après une mise à jour, actualise les outils du connecteur. Si ton IA n’y accède pas, vérifie que le connecteur est activé dans la conversation et que ton compte autorise les MCP personnalisés. Ouvrir l’URL dans un navigateur n’est pas un test MCP.