> For the complete documentation index, see [llms.txt](https://vulkan-technologies.gitbook.io/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://vulkan-technologies.gitbook.io/documentation/vulkan-menu/api/actions.md).

# Actions

Creating custom actions allows you to extend VulkanMenu's functionality beyond the built-in actions. This guide will walk you through creating, registering, and using custom actions.

## Understanding Actions

Actions are commands that execute when players interact with menu items. They implement the `Action` interface and can perform any task from sending messages to complex game mechanics.

## Creating a Custom Action

### Basic Action Structure

Every action must implement the `Action` interface:

```java
import com.vulkantechnologies.menu.model.action.Action;
import com.vulkantechnologies.menu.annotation.ComponentName;
import com.vulkantechnologies.menu.model.menu.Menu;
import org.bukkit.entity.Player;

@ComponentName("my-action")
public class MyCustomAction implements Action {
    
    private final String parameter;
    
    public MyCustomAction(String parameter) {
        this.parameter = parameter;
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        // Your action logic here
        player.sendMessage("Executing custom action with: " + parameter);
    }
}
```

### Important Annotations

#### @ComponentName

This annotation is **required** and defines how the action is referenced in configuration files:

```java
@ComponentName("give-item")
public class GiveItemAction implements Action {
    // ...
}
```

Usage in config:

```yaml
actions:
  - "[give-item] diamond 5"
```

#### @Single

Use this annotation for parameters that should be treated as single words (no spaces):

```java
import com.vulkantechnologies.menu.annotation.Single;

@ComponentName("set-score")
public record SetScoreAction(@Single String scoreboard, int value) implements Action {
    
    @Override
    public void execute(Player player, Menu menu) {
        // Set scoreboard value
    }
}
```

## Parameter Handling

### Simple Parameters

Actions can accept various parameter types:

```java
@ComponentName("teleport-relative")
public record TeleportRelativeAction(double x, double y, double z) implements Action {
    
    @Override
    public void execute(Player player, Menu menu) {
        Location loc = player.getLocation();
        loc.add(x, y, z);
        player.teleport(loc);
    }
}
```

### Complex Parameters

For more complex parameter handling:

```java
@ComponentName("multi-command")
public class MultiCommandAction implements Action {
    
    private final List<String> commands;
    
    public MultiCommandAction(String rawCommands) {
        // Parse semicolon-separated commands
        this.commands = Arrays.asList(rawCommands.split(";"));
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        for (String command : commands) {
            Bukkit.dispatchCommand(player, command);
        }
    }
}
```

## Action Examples

### Economy Action

```java
@ComponentName("buy-item")
public class BuyItemAction implements Action {
    
    private final Material item;
    private final int amount;
    private final double price;
    
    public BuyItemAction(String item, int amount, double price) {
        this.item = Material.valueOf(item.toUpperCase());
        this.amount = amount;
        this.price = price;
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        // Check if player has enough money (using Vault)
        if (!VaultHook.hasBalance(player, price)) {
            player.sendMessage("§cInsufficient funds!");
            return;
        }
        
        // Withdraw money
        VaultHook.withdrawPlayer(player, price);
        
        // Give items
        ItemStack itemStack = new ItemStack(item, amount);
        player.getInventory().addItem(itemStack);
        
        player.sendMessage("§aSuccessfully purchased " + amount + "x " + item.name());
    }
}
```

### Conditional Action

```java
@ComponentName("conditional-action")
public class ConditionalAction implements Action {
    
    private final String condition;
    private final String trueAction;
    private final String falseAction;
    
    public ConditionalAction(String condition, String trueAction, String falseAction) {
        this.condition = condition;
        this.trueAction = trueAction;
        this.falseAction = falseAction;
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        // Evaluate condition using menu variables
        boolean result = evaluateCondition(condition, menu);
        
        String actionToExecute = result ? trueAction : falseAction;
        
        // Parse and execute the chosen action
        Action action = ActionParser.parse(actionToExecute);
        if (action != null) {
            action.execute(player, menu);
        }
    }
    
    private boolean evaluateCondition(String condition, Menu menu) {
        // Implementation for condition evaluation
        return false;
    }
}
```

### Hook Action

For actions that integrate with other plugins:

```java
@ComponentName("faction-promote")
public class FactionPromoteAction extends HookAction {
    
    public FactionPromoteAction() {
        super("Factions");  // Required plugin name
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        if (!isHookEnabled()) {
            player.sendMessage("§cFactions plugin is not installed!");
            return;
        }
        
        // Factions API usage
        FPlayer fPlayer = FPlayers.getInstance().getByPlayer(player);
        if (fPlayer.hasFaction()) {
            fPlayer.setRole(Role.MODERATOR);
            player.sendMessage("§aYou have been promoted!");
        }
    }
}
```

## Registration

### Registering Your Action

Register your custom action during your plugin's onEnable:

```java
public class MyPlugin extends JavaPlugin {
    
    @Override
    public void onEnable() {
        // Register custom action
        VMenuAPI.registerAction(MyCustomAction.class);
        
        // Register multiple actions
        VMenuAPI.registerAction(BuyItemAction.class);
        VMenuAPI.registerAction(FactionPromoteAction.class);
    }
}
```

### Registration with Dependencies

If your action depends on another plugin:

```java
@Override
public void onEnable() {
    // Only register if dependency is present
    if (getServer().getPluginManager().getPlugin("Factions") != null) {
        VMenuAPI.registerAction(FactionPromoteAction.class);
    }
}
```

## Advanced Features

### Accessing Menu Variables

```java
@ComponentName("variable-math")
public class VariableMathAction implements Action {
    
    private final String variable;
    private final String operation;
    private final double value;
    
    public VariableMathAction(String variable, String operation, double value) {
        this.variable = variable;
        this.operation = operation;
        this.value = value;
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        double current = menu.getVariable(player, variable)
            .map(Double::parseDouble)
            .orElse(0.0);
        
        double result = switch (operation) {
            case "+" -> current + value;
            case "-" -> current - value;
            case "*" -> current * value;
            case "/" -> current / value;
            default -> current;
        };
        
        menu.setVariable(player, variable, String.valueOf(result));
        menu.refresh(player);  // Refresh to show updated value
    }
}
```

### Async Actions

For actions that might cause lag:

```java
@ComponentName("database-save")
public class DatabaseSaveAction implements Action {
    
    private final String data;
    
    public DatabaseSaveAction(String data) {
        this.data = data;
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        // Run async to avoid blocking main thread
        Bukkit.getScheduler().runTaskAsynchronously(VulkanMenu.getInstance(), () -> {
            // Database operation
            saveToDatabase(player.getUniqueId(), data);
            
            // Return to main thread for Bukkit API calls
            Bukkit.getScheduler().runTask(VulkanMenu.getInstance(), () -> {
                player.sendMessage("§aData saved successfully!");
            });
        });
    }
    
    private void saveToDatabase(UUID uuid, String data) {
        // Database implementation
    }
}
```

### Chainable Actions

Create actions that can be chained together:

```java
@ComponentName("chain")
public class ChainAction implements Action {
    
    private final List<Action> actions;
    
    public ChainAction(String actionsString) {
        this.actions = parseActions(actionsString);
    }
    
    @Override
    public void execute(Player player, Menu menu) {
        for (Action action : actions) {
            action.execute(player, menu);
        }
    }
    
    private List<Action> parseActions(String actionsString) {
        // Parse multiple actions separated by semicolons
        return Arrays.stream(actionsString.split(";"))
            .map(ActionParser::parse)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
}
```

## Best Practices

### 1. Error Handling

Always handle potential errors gracefully:

```java
@Override
public void execute(Player player, Menu menu) {
    try {
        // Your action logic
    } catch (Exception e) {
        player.sendMessage("§cAn error occurred while executing the action!");
        VulkanMenu.getInstance().getLogger().severe("Error in custom action: " + e.getMessage());
        e.printStackTrace();
    }
}
```

### 2. Null Checks

Always validate parameters:

```java
public MyAction(String param) {
    this.param = Objects.requireNonNull(param, "Parameter cannot be null");
}
```

### 3. Performance Considerations

* Use async operations for heavy tasks
* Cache frequently used data
* Avoid creating unnecessary objects

### 4. Documentation

Document your action's usage:

```java
/**
 * Gives the player a custom item with enchantments.
 * Usage: [give-custom] <material> <amount> <enchantment:level>
 * Example: [give-custom] diamond_sword 1 sharpness:5
 */
@ComponentName("give-custom")
public class GiveCustomItemAction implements Action {
    // ...
}
```

## Configuration Usage

Once registered, use your custom actions in menu configurations:

```yaml
items:
  custom_item:
    slot: 10
    material: emerald
    name: "<green>Custom Action Test"
    actions:
      - "[my-action] parameter1"
      - "[give-item] diamond 5"
      - "[database-save] player_data"
```

## Troubleshooting

### Action Not Recognized

If your action isn't recognized:

1. Ensure the @ComponentName annotation is present
2. Verify the action is registered during onEnable
3. Check that the constructor parameters match the configuration usage
4. Look for errors in the console during registration

### Parameter Parsing Issues

If parameters aren't parsing correctly:

1. Use @Single annotation for single-word parameters
2. Ensure numeric parameters are valid numbers
3. Check parameter order matches constructor order

### Action Not Executing

If the action doesn't execute:

1. Add debug messages to verify the execute method is called
2. Check for exceptions in the console
3. Ensure the action isn't blocked by requirements

## Complete Example

Here's a complete example of a custom action plugin:

```java
package com.example.vmenuactions;

import com.vulkantechnologies.menu.VMenuAPI;
import com.vulkantechnologies.menu.model.action.Action;
import com.vulkantechnologies.menu.annotation.ComponentName;
import com.vulkantechnologies.menu.model.menu.Menu;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;

public class CustomActionsPlugin extends JavaPlugin {
    
    @Override
    public void onEnable() {
        // Register all custom actions
        VMenuAPI.registerAction(PotionEffectAction.class);
        VMenuAPI.registerAction(RandomTeleportAction.class);
        
        getLogger().info("Custom VulkanMenu actions registered!");
    }
    
    @ComponentName("potion-effect")
    public static class PotionEffectAction implements Action {
        
        private final PotionEffectType type;
        private final int duration;
        private final int amplifier;
        
        public PotionEffectAction(String effect, int duration, int amplifier) {
            this.type = PotionEffectType.getByName(effect.toUpperCase());
            this.duration = duration * 20; // Convert seconds to ticks
            this.amplifier = amplifier;
        }
        
        @Override
        public void execute(Player player, Menu menu) {
            if (type == null) {
                player.sendMessage("§cInvalid potion effect!");
                return;
            }
            
            player.addPotionEffect(new PotionEffect(type, duration, amplifier));
            player.sendMessage("§aPotion effect applied!");
        }
    }
    
    @ComponentName("random-teleport")
    public static class RandomTeleportAction implements Action {
        
        private final int radius;
        
        public RandomTeleportAction(int radius) {
            this.radius = radius;
        }
        
        @Override
        public void execute(Player player, Menu menu) {
            Location center = player.getLocation();
            Random random = new Random();
            
            double x = center.getX() + (random.nextDouble() * radius * 2) - radius;
            double z = center.getZ() + (random.nextDouble() * radius * 2) - radius;
            double y = player.getWorld().getHighestBlockYAt((int) x, (int) z) + 1;
            
            Location destination = new Location(player.getWorld(), x, y, z);
            player.teleport(destination);
            player.sendMessage("§aTeleported to random location!");
        }
    }
}
```

Usage in menu configuration:

```yaml
actions:
  - "[potion-effect] speed 30 2"  # Speed II for 30 seconds
  - "[random-teleport] 100"        # Teleport within 100 block radius
```
