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,200Example: 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:5Example: 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:20Actions 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
}