FancyInnovations
FancyNpcsAPIActions

Custom Actions

Creating custom action types for NPCs

You can extend the FancyNPCs actions system by creating custom action types. This allows you to add unique behaviors specific to your plugin.

Creating a custom action

To create a custom action, extend the NpcAction abstract class:

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;

public class MyCustomAction extends NpcAction {

    public MyCustomAction() {
        super(
            "my_custom_action",  // Action name
            true                  // Whether this action requires a value
        );
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        // Get context information
        Player player = context.getPlayer();
        Npc npc = context.getNpc();
        Location location = context.getLocation();

        // Your custom logic here
        player.sendMessage("Custom action executed with value: " + value);
    }
}

Registering a custom action

Register your custom action with the ActionManager:

import de.oliver.fancynpcs.api.FancyNpcsPlugin;
import de.oliver.fancynpcs.api.actions.ActionManager;

public class MyPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Wait for FancyNPCs to load
        if (!getServer().getPluginManager().isPluginEnabled("FancyNpcs")) {
            getLogger().severe("FancyNPCs not found!");
            getServer().getPluginManager().disablePlugin(this);
            return;
        }

        // Register custom action
        ActionManager actionManager = FancyNpcsPlugin.get().getActionManager();
        actionManager.registerAction(new MyCustomAction());

        getLogger().info("Registered custom NPC action!");
    }

    @Override
    public void onDisable() {
        // Unregister when plugin disables
        ActionManager actionManager = FancyNpcsPlugin.get().getActionManager();
        NpcAction action = actionManager.getActionByName("my_custom_action");
        if (action != null) {
            actionManager.unregisterAction(action);
        }
    }
}

Example: Teleport action

Here's a complete example of a teleport action:

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.Location;
import org.bukkit.entity.Player;

public class TeleportAction extends NpcAction {

    public TeleportAction() {
        super("teleport", true);
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        // Parse location from value: "world,x,y,z"
        String[] parts = value.split(",");
        if (parts.length != 4) {
            player.sendMessage("§cInvalid teleport location format!");
            return;
        }

        try {
            String worldName = parts[0];
            double x = Double.parseDouble(parts[1]);
            double y = Double.parseDouble(parts[2]);
            double z = Double.parseDouble(parts[3]);

            World world = player.getServer().getWorld(worldName);
            if (world == null) {
                player.sendMessage("§cWorld not found: " + worldName);
                return;
            }

            Location destination = new Location(world, x, y, z);
            player.teleport(destination);
            player.sendMessage("§aTeleported!");

        } catch (NumberFormatException e) {
            player.sendMessage("§cInvalid coordinates!");
        }
    }
}

Usage:

/npc action my_npc right_click add teleport world,100,64,200

Example: Give item action

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

public class GiveItemAction extends NpcAction {

    public GiveItemAction() {
        super("give_item", true);
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        // Parse value: "MATERIAL:AMOUNT"
        String[] parts = value.split(":");
        if (parts.length != 2) {
            player.sendMessage("§cFormat: MATERIAL:AMOUNT");
            return;
        }

        try {
            Material material = Material.valueOf(parts[0].toUpperCase());
            int amount = Integer.parseInt(parts[1]);

            ItemStack item = new ItemStack(material, amount);
            player.getInventory().addItem(item);
            player.sendMessage("§aReceived " + amount + " " + material.name());

        } catch (IllegalArgumentException e) {
            player.sendMessage("§cInvalid material or amount!");
        }
    }
}

Usage:

/npc action my_npc right_click add give_item DIAMOND:5

Example: Economy action

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;

public class GiveMoneyAction extends NpcAction {

    private final Economy economy;

    public GiveMoneyAction(JavaPlugin plugin) {
        super("give_money", true);

        // Get Vault economy
        RegisteredServiceProvider<Economy> rsp =
            plugin.getServer().getServicesManager()
                .getRegistration(Economy.class);

        this.economy = rsp != null ? rsp.getProvider() : null;
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        if (economy == null) {
            player.sendMessage("§cEconomy system not available!");
            return;
        }

        try {
            double amount = Double.parseDouble(value);
            economy.depositPlayer(player, amount);
            player.sendMessage("§aReceived $" + amount + "!");

        } catch (NumberFormatException e) {
            player.sendMessage("§cInvalid amount!");
        }
    }
}

Example: Permission check action

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.entity.Player;

public class RequirePermissionAction extends NpcAction {

    public RequirePermissionAction() {
        super("require_permission", true);
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        // Check if player has the permission
        if (!player.hasPermission(value)) {
            player.sendMessage("§cYou don't have permission to do that!");
            // You might want to cancel further action execution here
            // This would require modifying the action executor
        }
    }
}

Example: Play particle effect

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.entity.Player;

public class ParticleAction extends NpcAction {

    public ParticleAction() {
        super("play_particle", true);
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();
        Location npcLoc = context.getLocation();

        // Parse value: "PARTICLE:COUNT"
        String[] parts = value.split(":");
        if (parts.length != 2) {
            return;
        }

        try {
            Particle particle = Particle.valueOf(parts[0].toUpperCase());
            int count = Integer.parseInt(parts[1]);

            // Spawn particles at NPC location
            player.spawnParticle(particle, npcLoc.clone().add(0, 1, 0),
                count, 0.5, 0.5, 0.5, 0.1);

        } catch (IllegalArgumentException ignored) {
        }
    }
}

Usage:

/npc action my_npc right_click add play_particle HEART:20

Actions without values

Some actions don't need a value parameter:

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.entity.Player;

public class HealPlayerAction extends NpcAction {

    public HealPlayerAction() {
        super(
            "heal_player",
            false  // No value required
        );
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        // Heal player to full health
        player.setHealth(player.getMaxHealth());
        player.setFoodLevel(20);
        player.setSaturation(20f);

        player.sendMessage("§aYou have been healed!");
    }
}

Async actions

For actions that perform heavy operations, use async execution:

import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.actions.executor.ActionExecutionContext;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

public class DatabaseAction extends NpcAction {

    private final Plugin plugin;

    public DatabaseAction(Plugin plugin) {
        super("database_query", true);
        this.plugin = plugin;
    }

    @Override
    public void execute(ActionExecutionContext context, String value) {
        Player player = context.getPlayer();

        // Perform database query async
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            // Simulate database query
            String result = queryDatabase(value);

            // Return to main thread for Bukkit API calls
            Bukkit.getScheduler().runTask(plugin, () -> {
                player.sendMessage("Query result: " + result);
            });
        });
    }

    private String queryDatabase(String query) {
        // Your database logic here
        return "result";
    }
}

Best practices

1. Validate input

Always validate the value parameter:

@Override
public void execute(ActionExecutionContext context, String value) {
    if (value == null || value.isEmpty()) {
        context.getPlayer().sendMessage("§cMissing value!");
        return;
    }

    // Continue with validated value
}

2. Handle errors gracefully

Catch exceptions and provide user feedback:

@Override
public void execute(ActionExecutionContext context, String value) {
    try {
        // Your logic here
    } catch (Exception e) {
        context.getPlayer().sendMessage("§cAction failed: " + e.getMessage());
        plugin.getLogger().warning("Action error: " + e.getMessage());
    }
}

3. Use placeholders

Replace placeholders in your value:

@Override
public void execute(ActionExecutionContext context, String value) {
    Player player = context.getPlayer();

    // Replace basic placeholders
    value = value.replace("{player}", player.getName());
    value = value.replace("{npc}", context.getNpc().getData().getName());

    // Use the replaced value
    player.sendMessage(value);
}

4. Document your action

Add clear javadoc comments:

/**
 * Teleports the player to a specific location.
 *
 * Value format: "world,x,y,z"
 * Example: "world,100,64,200"
 *
 * @author YourName
 */
public class TeleportAction extends NpcAction {
    // ...
}

Debugging custom actions

Enable debug logging to troubleshoot:

@Override
public void execute(ActionExecutionContext context, String value) {
    plugin.getLogger().info("Executing " + getName() + " with value: " + value);
    plugin.getLogger().info("Player: " + context.getPlayer().getName());
    plugin.getLogger().info("NPC: " + context.getNpc().getData().getName());

    // Your logic here
}

On this page