Citizens is the most popular NPC plugin for Minecraft. Foundation’s integration allows you to detect NPCs and retrieve their targeting information.
Check if loaded
if (HookManager.isCitizensLoaded()) {
// Citizens is available
}
Detect NPCs
Check if an entity is a Citizens NPC:
@EventHandler
public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity damaged = event.getEntity();
if (HookManager.isNPC(damaged)) {
// This is an NPC, handle differently
event.setCancelled(true);
return;
}
// Normal entity logic
}
Get NPC target
Retrieve what entity an NPC is targeting:
public void checkNPCTarget(Entity npc) {
if (!HookManager.isNPC(npc))
return;
Entity target = HookManager.getNPCTarget(npc);
if (target instanceof Player) {
Player targetPlayer = (Player) target;
Common.log("NPC is targeting: " + targetPlayer.getName());
}
}
Practical examples
Filter NPCs from player events
@EventHandler
public void onPlayerInteract(PlayerInteractEntityEvent event) {
Player player = event.getPlayer();
Entity entity = event.getRightClicked();
// Ignore NPC interactions
if (HookManager.isNPC(entity))
return;
// Handle normal entity interactions
handleEntityInteraction(player, entity);
}
Exclude NPCs from player counts
public int getRealPlayerCount() {
int count = 0;
for (Player player : Bukkit.getOnlinePlayers()) {
// Skip NPCs that appear as players
if (HookManager.isNPC(player))
continue;
count++;
}
return count;
}
Prevent NPC damage
@EventHandler
public void onEntityDamage(EntityDamageEvent event) {
Entity entity = event.getEntity();
// Protect all NPCs from damage
if (HookManager.isNPC(entity)) {
event.setCancelled(true);
return;
}
}
NPC interaction logging
@EventHandler
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
Entity entity = event.getRightClicked();
if (!HookManager.isNPC(entity))
return;
Player player = event.getPlayer();
// Log NPC interactions
Common.log(player.getName() + " interacted with NPC: " +
entity.getName());
}
Safe nickname handling
Avoid calling nickname methods on NPCs:
public String getDisplayName(CommandSender sender) {
if (!(sender instanceof Player))
return sender.getName();
Player player = (Player) sender;
// Don't get nicknames for NPCs
if (HookManager.isNPC(player)) {
return player.getName();
}
// Get nickname from EssentialsX or CMI
return HookManager.getNickColored(player);
}
Chat filter
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
// Prevent NPCs from chatting
if (HookManager.isNPC(player)) {
event.setCancelled(true);
return;
}
// Process normal chat
}
Implementation details
Foundation’s Citizens integration is simple but effective:
boolean isNPC(final Entity entity) {
try {
final NPCRegistry reg = CitizensAPI.getNPCRegistry();
return reg != null ? reg.isNPC(entity) : false;
} catch (final NoClassDefFoundError err) {
Common.logTimed(60 * 30,
"Unable to check if " + entity + " is Citizens NPC, got " + err +
". This error only shows once per 30min.");
return false;
}
}
Entity getNPCTarget(Entity entity) {
final NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);
if (npc != null) {
final EntityTarget target = npc.getNavigator().getEntityTarget();
if (target != null)
return target.getTarget();
}
return null;
}
Foundation includes error handling for class loading issues and rate-limits error messages to prevent log spam.
Safe defaults
When Citizens isn’t loaded:
public static boolean isNPC(final Entity entity) {
return isCitizensLoaded() ? citizensHook.isNPC(entity) : false;
}
public static Entity getNPCTarget(final Entity entity) {
return isCitizensLoaded() ? citizensHook.getNPCTarget(entity) : null;
}
Common use cases
Exclude from commands
public class TeleportCommand extends SimpleCommand {
public TeleportCommand() {
super("tp");
}
@Override
protected void onCommand() {
Player player = findPlayer(args[0]);
checkNotNull(player, "Player not found!");
checkBoolean(!HookManager.isNPC(player),
"Cannot teleport to NPCs!");
getPlayer().teleport(player);
tellSuccess("Teleported to " + player.getName());
}
}
Economy protection
public void giveReward(Player player, double amount) {
// Don't give money to NPCs
if (HookManager.isNPC(player))
return;
if (HookManager.isVaultLoaded()) {
HookManager.deposit(player, amount);
Common.tell(player, "Received $" + amount);
}
}
Stats tracking
@EventHandler
public void onPlayerKill(EntityDeathEvent event) {
if (!(event.getEntity() instanceof Player))
return;
Player killed = (Player) event.getEntity();
Player killer = killed.getKiller();
// Don't count NPC kills
if (HookManager.isNPC(killed) || HookManager.isNPC(killer))
return;
// Record kill stats
statsManager.addKill(killer);
statsManager.addDeath(killed);
}
Best practices
Always check before player operations - NPCs can appear as players:
if (sender instanceof Player) {
Player player = (Player) sender;
if (HookManager.isNPC(player))
return; // Skip NPCs
// Safe to use player methods
}
Check early in event handlers - Return early for NPCs:
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if (HookManager.isNPC(event.getPlayer()))
return;
// Normal join logic
}
Use null checks with getNPCTarget() - Target may be null:
Entity target = HookManager.getNPCTarget(npc);
if (target != null) {
// Use target
}
Why check for NPCs?
Citizens NPCs can:
- Trigger player join/quit events
- Appear in player lists
- Receive chat messages
- Have inventories opened
- Trigger interaction events
Always filter them out to prevent:
- Economy exploits
- Stats corruption
- Chat spam
- Data corruption
- Unwanted side effects
NPCs can sometimes masquerade as real players. Always use HookManager.isNPC() before performing player-specific operations like economy transactions, stats tracking, or permission checks.