API: Difference between revisions

From Citizens Wiki

No edit summary
 
(46 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Citizens has an extensive API that can be used for making your plugins work with NPCs or even for adding a brand new character that can be attached to an NPC. Make sure you always are using an up-to-date build of the CitizensAPI to ensure that your plugin works with the latest release of Citizens.
Citizens has an extensive API for working with NPCs. Make sure you always are using an up-to-date build of the CitizensAPI to ensure that your plugin works with the latest release of Citizens.


Javadocs can be found at https://jd.citizensnpcs.co
Javadocs can be found at https://jd.citizensnpcs.co
Line 29: Line 29:
==Linking Properly In Java==
==Linking Properly In Java==


Citizens, like most Java projects, is built using Maven. Your plugin that links to Citizens should be using Maven as well.
Citizens is built using Maven. Your plugin that links to Citizens should be using Maven as well.
 
In your <code>pom.xml</code> (Maven project) file, you can use this repository:


For Maven users: your <code>pom.xml</code> should include this repository:
<pre>
<pre>
     <repository>
     <repository>
         <id>everything</id>
         <id>citizens-repo</id>
         <url>https://repo.citizensnpcs.co/</url>
         <url>https://maven.citizensnpcs.co/repo</url>
     </repository>
     </repository>
</pre>
</pre>
and this dependency:
and this dependency:
<pre>
<pre>
     <dependency>
     <dependency>
         <groupId>net.citizensnpcs</groupId>
         <groupId>net.citizensnpcs</groupId>
         <artifactId>citizens</artifactId>
         <artifactId>citizens-main</artifactId>
         <version>VERSION</version>
         <version>2.0.35-SNAPSHOT</version>
         <type>jar</type>
         <type>jar</type>
         <scope>provided</scope>
         <scope>provided</scope>
        <exclusions>
            <exclusion>
                <groupId>*</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
     </dependency>
     </dependency>
</pre>
</pre>


And replace <code>VERSION</code> with the current version of Citizens (check the filenames at http://ci.citizensnpcs.co/job/Citizens2/ for current version ID), formatted like <code>2.0.27-SNAPSHOT</code> (but with the version numbers changed to the relevant version).
And replace the version with the current version of Citizens (check [https://maven.citizensnpcs.co/#/repo/net/citizensnpcs/citizens-main the repo if unsure]), for example <code>2.0.35-SNAPSHOT</code> (but with the version numbers changed to the relevant version).
 
===Common Mistakes===


Note that for most purposes, it's best to link <code>citizens</code> rather than the API project, as many useful classes and utilities are not in the API. The main Citizens project ''includes'' the API and adds more options.
# '''Using "citizensapi" as the artifact''' - this is missing many important traits and classes that the plugin contains. The '''citizens-main''' module includes the API and plugin classes.
# '''Using "citizens" as the artifact''' - this no longer works
# '''Not updating the version''' - latest version is almost always the best one to go with. Older versions lack new features and classes you might need.
# '''Not keeping literal <code>-SNAPSHOT</code>''' - it looks like a fill in spot, but it's not. Use that word exactly like that.
# '''Forgetting the <code>provided</code> scope''' - the scope is important! If it's unset or set incorrectly, your project will build but not work properly on server.
# '''Using maven shade''' - many users seem to accidentally enable the maven Shade plugin without understanding what it does. If you have the shade plugin and you're not confident exactly why, you probably don't need it. If you are intentionally using shade, please make sure that you do not shade Citizens into your plugin (do not use <code>compile</code> scope).
# '''Leaving off the exclusions''' - this is a new recommendation (Sept 2022), the '''exclusions''' option makes the maven build much faster by informing maven that you don't need to download a separate copy of any of Citizens' upstream artifacts.
 
===Gradle===
 
Some users prefer gradle. We don't use gradle for Citizens, so our references and support for it may be incomplete.
 
In short: same config as with Maven, but reformatted to how the gradle file expects it.
 
You should link the same repo. Something like:
<pre>
    maven {
        name = 'citizens-repo'
        url = 'https://maven.citizensnpcs.co/repo'
    }
</pre>
And add a dependency like this (with the version updated of course):
<pre>
dependencies {
    compileOnly('net.citizensnpcs:citizens-main:2.0.35-SNAPSHOT') {
        exclude group: '*', module: '*'
    }
}
</pre>


==Hooking Into Citizens==
==Hooking Into Citizens==
Hooking into Citizens is as simple as creating a basic plugin and adding the line
Hooking into Citizens is as simple as adding the line
depend: [Citizens] into your plugin.yml. From here, a common basic entry point is the CitizensAPI class. This gives you access to the NPCRegistry for NPC lookup, as well as the TraitFactory which allows trait registration. If Citizens is not loaded, all CitizensAPI.* methods will return null.
 
<pre>
depend: [Citizens]
# or
softdepend: [Citizens]
</pre>
 
into your plugin.yml.  
 
Next, wait for CitizensEnableEvent or CitizensLoadEvent before using Citizens via the CitizensAPI class. This gives you access to the global NPCRegistry for NPCs, as well as the TraitFactory which allows trait registration. If Citizens is not loaded or you didn't wait for CitizensEnableEvent, all CitizensAPI.* methods will return null.
 
==Creating an NPC==


== Creating an NPC ==
The simplest way to create an NPC is to use an NPCRegistry, which manages the storage and creation of NPCs. The default registry is given by CitizensAPI.getNPCRegistry(), and you can create new ones with different storage methods by calling other CitizensAPI methods. A Player NPC with name "fullwall" could then be created like this:
The simplest way to create an NPC is to use an NPCRegistry, which manages the storage and creation of NPCs. The default registry is given by CitizensAPI.getNPCRegistry(), and you can create new ones with different storage methods by calling other CitizensAPI methods. A Player NPC with name "fullwall" could then be created like this:
NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, "fullwall");


== Checking if an entity is a Citizens NPC ==
<code>
Citizens NPCs will have the "NPC" metadata set to true.
NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, "fullwall");
Eg.
npc.spawn(location);
boolean isCitizensNPC = entity.hasMetadata("NPC");
</code>
 
==Checking if an entity is a Citizens NPC==
Citizens NPCs will have the "NPC" Entity metadata set to true. <code>boolean isCitizensNPC = entity.hasMetadata("NPC");</code> This method doesn't even require a dependency on Citizens, as it works entirely through Spigot!
 
==Making an NPC Move==
 
Want to make an NPC pathfind to a location? <code>npc.getNavigator().setTarget(yourLocation);</code>


==Creating a Trait==
==Creating a Trait==
Traits are persistent, attachable objects that are linked to an NPC and provide specific functionality. This can be anything from a full-blown dynamic villager AI to a simple talking trait.
Traits are persistent, attachable objects that are linked to an NPC and provide specific functionality. This can be anything from custom AI to a simple talking trait.
 
If using Maven, Citizens' Maven repo is available at http://repo.citizensnpcs.co


To register a trait, we use the '''TraitFactory''' class. This controls registration for your custom traits.
To register a trait, we use the '''TraitFactory''' class. This controls registration for your custom traits.
{{codebox|height=500px|width=100%|Example registration and simple trait|<pre>
{{codebox|height=500px|width=98%||<pre lang="java">
//This is your trait that will be applied to a npc using the /trait mytraitname command. Each NPC gets its own instance of this class.
//This is your trait that will be applied to a npc using the /trait mytraitname command. Each NPC gets its own instance of this class.
//the Trait class has a reference to the attached NPC class through the protected field 'npc' or getNPC().
//the Trait class has a reference to the attached NPC class through the protected field 'npc' or getNPC().
//The Trait class also implements Listener so you can add EventHandlers directly to your trait.
//The Trait class also implements Listener so you can add EventHandlers directly to your trait.
@TraitName("mytraitname")
public class MyTrait extends Trait {
public class MyTrait extends Trait {
public MyTrait() {
public MyTrait() {
Line 94: Line 143:
         // This does NOT get called when applying the trait for the first time, only loading onto an existing npc at server start.
         // This does NOT get called when applying the trait for the first time, only loading onto an existing npc at server start.
         // This is called AFTER onAttach so you can load defaults in onAttach and they will be overridden here.
         // This is called AFTER onAttach so you can load defaults in onAttach and they will be overridden here.
         // This is called BEFORE onSpawn, npc.getBukkitEntity() will return null.
         // This is called BEFORE onSpawn, npc.getEntity() will return null.
public void load(DataKey key) {
public void load(DataKey key) {
SomeSetting = key.getBoolean("SomeSetting", false);
SomeSetting = key.getBoolean("SomeSetting", false);
Line 104: Line 153:
}
}


         // An example event handler. All traits will be registered automatically as Bukkit Listeners.
         // An example event handler. All traits will be registered automatically as Spigot event Listeners
@EventHandler
@EventHandler
public void click(net.citizensnpcs.api.event.NPCRightClickEvent event){
public void click(net.citizensnpcs.api.event.NPCRightClickEvent event){
Line 118: Line 167:


//Run code when your trait is attached to a NPC.  
//Run code when your trait is attached to a NPC.  
         //This is called BEFORE onSpawn, so npc.getBukkitEntity() will return null
         //This is called BEFORE onSpawn, so npc.getEntity() will return null
         //This would be a good place to load configurable defaults for new NPCs.
         //This would be a good place to load configurable defaults for new NPCs.
@Override
@Override
Line 125: Line 174:
}
}


         // Run code when the NPC is despawned. This is called before the entity actually despawns so npc.getBukkitEntity() is still valid.
         // Run code when the NPC is despawned. This is called before the entity actually despawns so npc.getEntity() is still valid.
@Override
@Override
public void onDespawn() {
public void onDespawn() {
         }
         }


//Run code when the NPC is spawned. Note that npc.getBukkitEntity() will be null until this method is called.
//Run code when the NPC is spawned. Note that npc.getEntity() will be null until this method is called.
         //This is called AFTER onAttach and AFTER Load when the server is started.
         //This is called AFTER onAttach and AFTER Load when the server is started.
@Override
@Override
Line 144: Line 193:
}
}


//This is your bukkit plugin class. Use it to hook your trait into Citizens and handle any commands.
//This is your Spigot plugin class. Use it to hook your trait into Citizens and handle any commands.
public class MyPlugin extends org.bukkit.plugin.java.JavaPlugin {
public class MyPlugin extends org.bukkit.plugin.java.JavaPlugin {


public void onEnable() {
        public void onEnable() {
        // check if Citizens is present and enabled.
//check if Citizens is present and enabled.
        if (getServer().getPluginManager().getPlugin("Citizens") == null
 
                || !getServer().getPluginManager().getPlugin("Citizens").isEnabled()) {
if(getServer().getPluginManager().getPlugin("Citizens") == null || getServer().getPluginManager().getPlugin("Citizens").isEnabled() == false) {
            getLogger().log(Level.SEVERE, "Citizens 2.0 not found or not enabled");
getLogger().log(Level.SEVERE, "Citizens 2.0 not found or not enabled");
        } else {
getServer().getPluginManager().disablePlugin(this);
            // Register your trait with Citizens.
return;
            net.citizensnpcs.api.CitizensAPI.getTraitFactory()
}
                    .registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(MyTrait.class));
 
            Bukkit.getServer().getPluginManager().registerEvents(new Listener() {
//Register your trait with Citizens.      
                @EventHandler
net.citizensnpcs.api.CitizensAPI.getTraitFactory().registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(MyTrait.class).withName("mytraitname"));
                public void onCitizensEnable(CitizensEnableEvent ev) {
}
                    NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, "Dinnerbone");
 
                    npc.spawn(yourlocationhere);
@Override
                }
public boolean onCommand(CommandSender sender, Command cmd, String cmdLabel, String[] inargs) {
            }, this);
//handle commands for /myplugin
        }
}
    }
}
}


Line 171: Line 220:
===Dos and Don'ts===
===Dos and Don'ts===
'''{{color|green|white|DO}}'''
'''{{color|green|white|DO}}'''
* Check npc.isSpawned() before using npc.getBukkitEntity()
* Use Spigot Entity API with npc.getEntity()
* Check npc.isSpawned() before using npc.getNavigator()
* Create a separate event Listener class if you expect there to be many trait instances. This may help performance with frequently called events.
* Create a separate singleton Listener class if you expect there to be many instances of this trait running. This may help performance with frequently called events.
* Honor npc.isProtected() If this is true the NPC should be 'invulnerable' to normal damaging effects.
* Honor npc.data().get(NPC.DEFAULT_PROTECTED_METADATA) If this is true the NPC should be 'invulnerable' to normal damaging effects.
* Use CitizensAPI.getNPCRegistry().isNPC() to check if an entity is a NPC. Real players and player-type NPCs will both return true for instanceof Player.
* use CitizensAPI.getNPCRegistry().isNPC() to check if an entity is a NPC. Real players and player-type NPCs will both return true for instanceof Player.
'''{{color|red|white|DON'T}}'''
'''{{color|red|white|DON'T}}'''
* Attempt to access npc.getBukkitEntity() from within traits until onSpawn() has been called or npc.isSpawned() returns true.
* Attempt to access npc.getEntity() from within traits until onSpawn() has been called or npc.isSpawned() returns true.
* Change anything in npc.getNavigator.getDefaultParams() unless you're sure you want a global change. Use the localParams() instead ''after'' setting a navigation target.
* Change anything in npc.getNavigator().getDefaultParams() unless you're sure you want a global change. Use the localParams() instead ''after'' setting a navigation target.
* Assume a NPC is a player-type. Mob types have some important differences.
* Assume a NPC is a Player. Mob types have some important differences.


==Default Traits==
==Default Traits==
Citizens comes by default with a number of Traits that can be interacted with. You can see these Traits in the JavaDocs http://jd.citizensnpcs.co/net/citizensnpcs/api/trait/trait/package-frame.html and also in the Citizens2 GitHub https://github.com/CitizensDev/Citizens2/tree/master/main/src/main/java/net/citizensnpcs/trait.
Citizens comes by default with Traits for its built-in commands. You can see these Traits in the JavaDocs https://jd.citizensnpcs.co/net/citizensnpcs/api/trait/trait/package-summary.html and also in the Citizens2 GitHub https://github.com/CitizensDev/Citizens2/tree/master/main/src/main/java/net/citizensnpcs/trait.


For example, you might add a piece of armor to an NPC using
For example, you might add a piece of armor to an NPC using
  npc.getTrait(Equipment.class).set(EquipmentSlot.BOOTS, new ItemStack(Material.LEATHER_BOOTS, 1));
  npc.getOrAddTrait(Equipment.class).set(EquipmentSlot.BOOTS, new ItemStack(Material.LEATHER_BOOTS, 1));


==NPC Events==
==NPC Events==
Citizens implements its own Listeners and will call new NPC-specific versions of many common events. This saves Trait developers the trouble of finding their npcs from the normal event entities. The event object these events provide are just like their Bukkit counterparts with the addition of the getNPC() method.
Citizens implements its own Listeners and will call new NPC-specific versions of many common events. This saves Trait developers the trouble of finding their npcs from the normal event entities. The event object these events provide are just like their Spigot counterparts with the addition of the getNPC() method.
Citizens currently provides the following:
*EntityTargetNPCEvent
*NPCChatEvent
*NPCClickEvent
*NPCCollisionEvent
*NPCCombustByBlockEvent
*NPCCombustByEntityEvent
*NPCCombustEvent
*NPCDamageByBlockEvent
*NPCDamageByEntityEvent
*NPCDamageEvent
*NPCDeathEvent
*NPCDespawnEvent
*NPCEvent
*NPCLeftClickEvent
*NPCPushEvent
*NPCRemoveEvent
*NPCRightClickEvent
*NPCSelectEvent
*NPCSpawnEvent
*PlayerCreateNPCEvent


See the [[http://jd.citizensnpcs.co/ Javadocs]] for details.
See the [[http://jd.citizensnpcs.co/ Javadocs]] for a full list.


==Using the AI API==
==Using the AI API==
Line 220: Line 247:
The GoalSelector allows a great deal of flexibility within goal implementations. It allows firstly the dynamic selection of sub-goals and the concurrent execution of many sub-goals, and can stop execution at any time.
The GoalSelector allows a great deal of flexibility within goal implementations. It allows firstly the dynamic selection of sub-goals and the concurrent execution of many sub-goals, and can stop execution at any time.


In recent versions of CitizensAPI the Behavior class is introduced which allows a behavior tree-based AI approach that is backwards compatible with Goals and GoalControllers. More info about Behavior trees is available here: https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)
Modern plugins should use the Behavior interface which allows a behavior tree-based AI approach that is backwards compatible with Goals and GoalControllers. More info about Behavior is available here: https://jd.citizensnpcs.co/net/citizensnpcs/api/ai/tree/Behavior.html


{{codebox|height=500px|width=100%|Example|<pre lang="java5">
{{codebox|height=500px|width=98%||<pre lang="java">
public class MyBehavior extends BehaviorGoalAdapter { // enable direct GoalController compatibility
    private Object state;
    public void reset() {
        state = null;
        // this method can be called at any time if another goal is selected
    }
    public BehaviorStatus run() {
        if(!npcIsCool()) {
            return BehaviorStatus.FAILURE;
        } else if (npcIsAwesome()) {
            return BehaviorStatus.SUCCESS;
        } else if (npcNeedsCool()) {
            new AccumulateCoolBehavior().run(); // easily run other behavior inline
            return BehaviorStatus.RUNNING;
        }
    }
    public boolean shouldExecute() {
        if (npcIsCool()) {
            return true;
        }
        return false;
    }
}
</pre>
}}
 
You can easily create trees of behaviors such as in the following example:
{{codebox|height=300px|width=98%||<pre lang="java">
public void setupTree(NPC npc) {
    npc.getGoalController().addGoal(Sequence.createSequence(new MyBehavior(), new MyAccumulateBehavior(), new MyParallelBehavior()));
 
    // A more complicated example
    npc.getGoalController().addGoal(Sequence.createSequence(
              new IfElse(() -> npc.isCool(),
                  TimerDecorator.tickLimiter(new MyLongRunningBehavior(), 100),
                  new MyElseBehavior()),
              new MyParallelBehavior()
    ));
 
    // You can implement nested loops and other behavior sequences using the API provided in net.citizensnpcs.api.ai.tree
}
</pre>
}}
 
{{codebox|height=500px|width=98%||<pre lang="java">
  public class MyGoal implements Goal {
  public class MyGoal implements Goal {
     private Object state;
     private Object state;  
    private GoalSelector selector; // the current selector
     public void reset() {
     public void reset() {
         state = null;
         state = null;
         // this method can be called at any time - tear down any state
         // this method can be called at any time if another goal is selected
     }
     }
     public void run() {
     public void run(GoalSelector selector) {
         if(!npcIsCool()) {
         if(!npcIsCool()) {
             selector.finish(); // stops execution
             selector.finish(); // stops execution
Line 240: Line 311:
     }
     }
     public boolean shouldExecute(GoalSelector selector) {
     public boolean shouldExecute(GoalSelector selector) {
         if (npcIsCool()) {
         if (npcIsCool()) {  
            this.selector = selector;
             return true;
             return true;
         }
         }
         return false;
         return false;
     }
     }  
  }
  }
</pre>
</pre>
Line 258: Line 328:
*NavigationReplaceEvent
*NavigationReplaceEvent


You can use the NavigatorParameters class to control various aspects of pathfinding. The default parameters are copied to create the "local" parameters whenever a new path is started - modify local parameters after setting your path target!
The pathfinding range of the Navigator is the maximum range it will search when attempting to find a path to the target. This is usually set by the server admin. The speed modifier of the Navigator is the % modified movement speed of the NPC while moving to the target.
The pathfinding range of the Navigator is the maximum range it will search when attempting to find a path to the target. This is usually set by the server admin. The speed modifier of the Navigator is the % modified movement speed of the NPC while moving to the target.


Line 263: Line 334:
Sometimes, traits can store a lot of simple variables such as primitives, Strings, Locations, and others. Saving/loading them via the trait API can be a little bit of overkill.
Sometimes, traits can store a lot of simple variables such as primitives, Strings, Locations, and others. Saving/loading them via the trait API can be a little bit of overkill.


Citizens 2.0.4+ provides a simple Persistence API to automatically save and load these variables using DataKeys. The key to this API is the @Persist annotation. Sample code is provided below.
Citizens provides a simple Persistence API to automatically save and load these variables using DataKeys. The key to this API is the @Persist annotation. Sample code is provided below.
{{codebox|height=300px|width=100%|Example|<pre lang="java5">
{{codebox|height=300px|width=100%|Example|<pre lang="java">
  public class MyTrait extends Trait {
  public class MyTrait extends Trait {
     // logic omitted.
     // logic omitted.
Line 278: Line 349:
More advanced use of the API can be found in the @DelegatePersistence annotation. This allows complex types such as Locations to be saved and loaded with finer grained control. These types can be given default delegates by calling PersistenceLoader#registerPersistDelegate(Persister) - Location has a built in Persister for convenience.
More advanced use of the API can be found in the @DelegatePersistence annotation. This allows complex types such as Locations to be saved and loaded with finer grained control. These types can be given default delegates by calling PersistenceLoader#registerPersistDelegate(Persister) - Location has a built in Persister for convenience.


{{codebox|height=500px|width=100%|Example|<pre lang="java5">
{{codebox|height=500px|width=98%||<pre lang="java">
  public class MyTrait extends Trait {
  public class MyTrait extends Trait {
     // logic omitted.
     // logic omitted.

Latest revision as of 02:49, 28 August 2024

Citizens has an extensive API for working with NPCs. Make sure you always are using an up-to-date build of the CitizensAPI to ensure that your plugin works with the latest release of Citizens.

Javadocs can be found at https://jd.citizensnpcs.co

IF YOU HAVE TROUBLE USING THE API please speak to us on GitHub at https://github.com/CitizensDev/Citizens2/issues OR on Discord at https://discord.gg/Q6pZGSR

Current Release Download: Jenkins

Developmental Builds: Jenkins

Documentation: JavaDocs

Source: Github

Linking Properly In Java

Citizens is built using Maven. Your plugin that links to Citizens should be using Maven as well.

For Maven users: your pom.xml should include this repository:

    <repository>
        <id>citizens-repo</id>
        <url>https://maven.citizensnpcs.co/repo</url>
    </repository>

and this dependency:

    <dependency>
        <groupId>net.citizensnpcs</groupId>
        <artifactId>citizens-main</artifactId>
        <version>2.0.35-SNAPSHOT</version>
        <type>jar</type>
        <scope>provided</scope>
        <exclusions>
            <exclusion>
                <groupId>*</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

And replace the version with the current version of Citizens (check the repo if unsure), for example 2.0.35-SNAPSHOT (but with the version numbers changed to the relevant version).

Common Mistakes

  1. Using "citizensapi" as the artifact - this is missing many important traits and classes that the plugin contains. The citizens-main module includes the API and plugin classes.
  2. Using "citizens" as the artifact - this no longer works
  3. Not updating the version - latest version is almost always the best one to go with. Older versions lack new features and classes you might need.
  4. Not keeping literal -SNAPSHOT - it looks like a fill in spot, but it's not. Use that word exactly like that.
  5. Forgetting the provided scope - the scope is important! If it's unset or set incorrectly, your project will build but not work properly on server.
  6. Using maven shade - many users seem to accidentally enable the maven Shade plugin without understanding what it does. If you have the shade plugin and you're not confident exactly why, you probably don't need it. If you are intentionally using shade, please make sure that you do not shade Citizens into your plugin (do not use compile scope).
  7. Leaving off the exclusions - this is a new recommendation (Sept 2022), the exclusions option makes the maven build much faster by informing maven that you don't need to download a separate copy of any of Citizens' upstream artifacts.

Gradle

Some users prefer gradle. We don't use gradle for Citizens, so our references and support for it may be incomplete.

In short: same config as with Maven, but reformatted to how the gradle file expects it.

You should link the same repo. Something like:

    maven {
        name = 'citizens-repo'
        url = 'https://maven.citizensnpcs.co/repo'
    }

And add a dependency like this (with the version updated of course):

dependencies {
    compileOnly('net.citizensnpcs:citizens-main:2.0.35-SNAPSHOT') {
        exclude group: '*', module: '*'
    }
}

Hooking Into Citizens

Hooking into Citizens is as simple as adding the line

depend: [Citizens]
# or 
softdepend: [Citizens]

into your plugin.yml.

Next, wait for CitizensEnableEvent or CitizensLoadEvent before using Citizens via the CitizensAPI class. This gives you access to the global NPCRegistry for NPCs, as well as the TraitFactory which allows trait registration. If Citizens is not loaded or you didn't wait for CitizensEnableEvent, all CitizensAPI.* methods will return null.

Creating an NPC

The simplest way to create an NPC is to use an NPCRegistry, which manages the storage and creation of NPCs. The default registry is given by CitizensAPI.getNPCRegistry(), and you can create new ones with different storage methods by calling other CitizensAPI methods. A Player NPC with name "fullwall" could then be created like this:

NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, "fullwall"); npc.spawn(location);

Checking if an entity is a Citizens NPC

Citizens NPCs will have the "NPC" Entity metadata set to true. boolean isCitizensNPC = entity.hasMetadata("NPC"); This method doesn't even require a dependency on Citizens, as it works entirely through Spigot!

Making an NPC Move

Want to make an NPC pathfind to a location? npc.getNavigator().setTarget(yourLocation);

Creating a Trait

Traits are persistent, attachable objects that are linked to an NPC and provide specific functionality. This can be anything from custom AI to a simple talking trait.

To register a trait, we use the TraitFactory class. This controls registration for your custom traits.

Code:
//This is your trait that will be applied to a npc using the /trait mytraitname command. Each NPC gets its own instance of this class.
//the Trait class has a reference to the attached NPC class through the protected field 'npc' or getNPC().
//The Trait class also implements Listener so you can add EventHandlers directly to your trait.
@TraitName("mytraitname")
public class MyTrait extends Trait {
	public MyTrait() {
		super("mytraitname");
		plugin = JavaPlugin.getPlugin(MyPlugin.class);
	}

	MyPlugin plugin = null;

	boolean SomeSetting = false;
        
        // see the 'Persistence API' section
        @Persist("mysettingname") boolean automaticallyPersistedSetting = false;

	// Here you should load up any values you have previously saved (optional). 
        // This does NOT get called when applying the trait for the first time, only loading onto an existing npc at server start.
        // This is called AFTER onAttach so you can load defaults in onAttach and they will be overridden here.
        // This is called BEFORE onSpawn, npc.getEntity() will return null.
	public void load(DataKey key) {
		SomeSetting = key.getBoolean("SomeSetting", false);
	}

	// Save settings for this NPC (optional). These values will be persisted to the Citizens saves file
	public void save(DataKey key) {
		key.setBoolean("SomeSetting",SomeSetting);
	}

        // An example event handler. All traits will be registered automatically as Spigot event Listeners
	@EventHandler
	public void click(net.citizensnpcs.api.event.NPCRightClickEvent event){
		//Handle a click on a NPC. The event has a getNPC() method. 
		//Be sure to check event.getNPC() == this.getNPC() so you only handle clicks on this NPC!
		
	}
      
        // Called every tick
        @Override
        public void run() {
        }

	//Run code when your trait is attached to a NPC. 
        //This is called BEFORE onSpawn, so npc.getEntity() will return null
        //This would be a good place to load configurable defaults for new NPCs.
	@Override
	public void onAttach() {
		plugin.getServer().getLogger().info(npc.getName() + "has been assigned MyTrait!");
	}

        // Run code when the NPC is despawned. This is called before the entity actually despawns so npc.getEntity() is still valid.
	@Override
	public void onDespawn() {
        }

	//Run code when the NPC is spawned. Note that npc.getEntity() will be null until this method is called.
        //This is called AFTER onAttach and AFTER Load when the server is started.
	@Override
	public void onSpawn() {

	}

        //run code when the NPC is removed. Use this to tear down any repeating tasks.
	@Override
	public void onRemove() {
        }

}

//This is your Spigot plugin class. Use it to hook your trait into Citizens and handle any commands.
public class MyPlugin extends org.bukkit.plugin.java.JavaPlugin {

        public void onEnable() {
        // check if Citizens is present and enabled.
        if (getServer().getPluginManager().getPlugin("Citizens") == null
                || !getServer().getPluginManager().getPlugin("Citizens").isEnabled()) {
            getLogger().log(Level.SEVERE, "Citizens 2.0 not found or not enabled");
        } else {
            // Register your trait with Citizens.
            net.citizensnpcs.api.CitizensAPI.getTraitFactory()
                    .registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(MyTrait.class));
            Bukkit.getServer().getPluginManager().registerEvents(new Listener() {
                @EventHandler
                public void onCitizensEnable(CitizensEnableEvent ev) {
                    NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.PLAYER, "Dinnerbone");
                    npc.spawn(yourlocationhere);
                }
            }, this);
        }
    }
}

Dos and Don'ts

DO

  • Use Spigot Entity API with npc.getEntity()
  • Create a separate event Listener class if you expect there to be many trait instances. This may help performance with frequently called events.
  • Honor npc.isProtected() If this is true the NPC should be 'invulnerable' to normal damaging effects.
  • Use CitizensAPI.getNPCRegistry().isNPC() to check if an entity is a NPC. Real players and player-type NPCs will both return true for instanceof Player.

DON'T

  • Attempt to access npc.getEntity() from within traits until onSpawn() has been called or npc.isSpawned() returns true.
  • Change anything in npc.getNavigator().getDefaultParams() unless you're sure you want a global change. Use the localParams() instead after setting a navigation target.
  • Assume a NPC is a Player. Mob types have some important differences.

Default Traits

Citizens comes by default with Traits for its built-in commands. You can see these Traits in the JavaDocs https://jd.citizensnpcs.co/net/citizensnpcs/api/trait/trait/package-summary.html and also in the Citizens2 GitHub https://github.com/CitizensDev/Citizens2/tree/master/main/src/main/java/net/citizensnpcs/trait.

For example, you might add a piece of armor to an NPC using

npc.getOrAddTrait(Equipment.class).set(EquipmentSlot.BOOTS, new ItemStack(Material.LEATHER_BOOTS, 1));

NPC Events

Citizens implements its own Listeners and will call new NPC-specific versions of many common events. This saves Trait developers the trouble of finding their npcs from the normal event entities. The event object these events provide are just like their Spigot counterparts with the addition of the getNPC() method.

See the [Javadocs] for a full list.

Using the AI API

The AI API of Citizens can be broken down into two parts - GoalController and Navigator.

A Goal is a repeatable, abstract unit of work that can be performed by an NPC. For example, moving to a different location or attacking an enemy until it dies. It can be registered with a GoalController with a priority (higher is more important). The highest priority goal which can be executed will be prioritised. NPC contains getDefaultGoalController() for this purpose.

The GoalSelector allows a great deal of flexibility within goal implementations. It allows firstly the dynamic selection of sub-goals and the concurrent execution of many sub-goals, and can stop execution at any time.

Modern plugins should use the Behavior interface which allows a behavior tree-based AI approach that is backwards compatible with Goals and GoalControllers. More info about Behavior is available here: https://jd.citizensnpcs.co/net/citizensnpcs/api/ai/tree/Behavior.html


Code:
 public class MyBehavior extends BehaviorGoalAdapter { // enable direct GoalController compatibility
     private Object state; 
     public void reset() {
         state = null;
         // this method can be called at any time if another goal is selected
     }
     public BehaviorStatus run() {
         if(!npcIsCool()) {
             return BehaviorStatus.FAILURE;
         } else if (npcIsAwesome()) {
             return BehaviorStatus.SUCCESS; 
         } else if (npcNeedsCool()) {
             new AccumulateCoolBehavior().run(); // easily run other behavior inline
             return BehaviorStatus.RUNNING;
         }
     }
     public boolean shouldExecute() {
         if (npcIsCool()) { 
             return true;
         }
         return false;
     } 
 }

You can easily create trees of behaviors such as in the following example:

Code:
 public void setupTree(NPC npc) {
     npc.getGoalController().addGoal(Sequence.createSequence(new MyBehavior(), new MyAccumulateBehavior(), new MyParallelBehavior()));

     // A more complicated example
     npc.getGoalController().addGoal(Sequence.createSequence(
              new IfElse(() -> npc.isCool(), 
                  TimerDecorator.tickLimiter(new MyLongRunningBehavior(), 100), 
                  new MyElseBehavior()),
              new MyParallelBehavior()
     ));

     // You can implement nested loops and other behavior sequences using the API provided in net.citizensnpcs.api.ai.tree
 }


Code:
 public class MyGoal implements Goal {
     private Object state; 
     public void reset() {
         state = null;
         // this method can be called at any time if another goal is selected
     }
     public void run(GoalSelector selector) {
         if(!npcIsCool()) {
             selector.finish(); // stops execution
         } else if (npcIsAwesome()){
             selector.select(new AwesomeGoal()); // this switches execution to AwesomeGoal and stops execution of this goal.
         } else if (npcNeedsCool()) {
             selector.selectAdditional(new AccumulateCoolGoal()); // AccumulateCoolGoal executes concurrently to this goal.
         }
     }
     public boolean shouldExecute(GoalSelector selector) {
         if (npcIsCool()) { 
             return true;
         }
         return false;
     } 
 }

The second concept is the Navigator. This controls the pathfinding aspects of the NPC. The Navigator can have one target at a time, and will call events to notify of completion/cancellation:

  • NavigationBeginEvent
  • NavigationCancelEvent
  • NavigationCompleteEvent
  • NavigationEvent
  • NavigationReplaceEvent

You can use the NavigatorParameters class to control various aspects of pathfinding. The default parameters are copied to create the "local" parameters whenever a new path is started - modify local parameters after setting your path target! The pathfinding range of the Navigator is the maximum range it will search when attempting to find a path to the target. This is usually set by the server admin. The speed modifier of the Navigator is the % modified movement speed of the NPC while moving to the target.

Using the Persistence API

Sometimes, traits can store a lot of simple variables such as primitives, Strings, Locations, and others. Saving/loading them via the trait API can be a little bit of overkill.

Citizens provides a simple Persistence API to automatically save and load these variables using DataKeys. The key to this API is the @Persist annotation. Sample code is provided below.

Code: Example
 public class MyTrait extends Trait {
     // logic omitted.

    @Persist boolean myVariable = false; // the default value of @Persist saves the value under the field name (in this case, 'myVariable').
    
    @Persist("newkey") int intVariable = 11; // this saves the value under 'newkey'. The default value of the variable has been set to 11 - this will be used when loading if the key doesn't exist.

    @Persist(value="newkey", required=true) String required; // if the value under 'newkey' doesn't exist, then the trait will fail to load.
 }

More advanced use of the API can be found in the @DelegatePersistence annotation. This allows complex types such as Locations to be saved and loaded with finer grained control. These types can be given default delegates by calling PersistenceLoader#registerPersistDelegate(Persister) - Location has a built in Persister for convenience.


Code:
 public class MyTrait extends Trait {
     // logic omitted.

    @Persist 
    @DelegatePersistence(ExplicitComplexTypePersister.class) // explicit delegation
    ComplexType myComplexType;

    @Persist ComplexType implicitComplexType; // implicit delegation
    static {
        PersistenceLoader.registerPersistDelegate(ImplicitComplexTypePersister.class);
    }
 }
 
 public class ExplicitComplexTypePersister implements Persister {
     public Object create(DataKey root) {
         return new ComplexType(root.getInteger("complexstructures"));
     }
     public void save(Object instance, DataKey root) {
         ComplexType real = (ComplexType) instance; // guaranteed cast - will always succeed.
         root.setInteger("complexstructures", real.getComplexStructure());
     }
 }

See Also

Characters