Stendhal NPC Coding
Stendhal NPCs
Usually we add NPCs (non-player characters) to make world more alive and to use them in Quests.
This is how NPCs are added to the world.
A java file should define the path they walk on, basic dialog and how they look. It can also define the selling, buying, healing or producing behaviour of an NPC. The NPC is only loaded into a zone if you also edit the zone's xml file, to configure the zone using that java file.
More complicated dialog for NPCs, such as you'd find in a quest, is covered in Stendhal Quest Coding. But the basics for any NPC used in a quest should still be written as below.
Before you start
This page describes how to code an NPC. You don't need to know a lot about Java. You should, however, already have setup an IDE and be able to compile and start a local Stendhal server.
Define NPC with Java
First you need to decide what region your NPC will be in, so that we can create the Java file in the correct place. The location for the file will be:
src/games/stendhal/server/maps/region/subregion
You should name the NPC file after the function of the NPC. Examples are GreeterNPC, HealerNPC, BuyerNPC, ChefNPC, SellerNPC, LifeguardNPC
In this example we'll make a wizard in a magician's hut.
So we will create a file src/games/stendhal/server/maps/ados/magician_house/WizardNPC.java
The start of the file will need to specify some standard things to make it work, don't worry about these just copy them for now. Notice that the class is the same as our filename and the package is related to where we put the file.
package games.stendhal.server.maps.ados.magician_house;
import games.stendhal.server.core.config.ZoneConfigurator;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.core.pathfinder.FixedPath;
import games.stendhal.server.core.pathfinder.Node;
// this one is just because our NPC is a seller
import games.stendhal.server.entity.npc.ShopList;
import games.stendhal.server.entity.npc.SpeakerNPC;
// this one is just because our NPC is a seller
import games.stendhal.server.entity.npc.behaviour.adder.SellerAdder;
// this one is just because our NPC is a seller
import games.stendhal.server.entity.npc.behaviour.impl.SellerBehaviour;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class WizardNPC implements ZoneConfigurator {
// this is just because he has a 'shop' to sell potions
private final ShopList shops = SingletonRepository.getShopList();
/**
* Configure a zone.
*
* @param zone The zone to be configured.
* @param attributes Configuration attributes.
*/
public void configureZone(final StendhalRPZone zone, final Map<String, String> attributes) {
buildNPC(zone);
}
Now we have set up this standard stuff we can build the actual NPC. We'll call the method which builds the NPC, buildNPC.
private void buildNPC(final StendhalRPZone zone) {
final SpeakerNPC npc = new SpeakerNPC("Mr Healer") {
protected void createPath() {
List<Node> nodes=new LinkedList<Node>();
nodes.add(new Path.Node(9,5));
nodes.add(new Path.Node(14,5));
setPath(nodes,true);
}
protected void createDialog() {
// Lets the NPC reply with "Hallo" when a player greets him. But we could have set a custom greeting inside the ()
addGreeting();
// Lets the NPC reply when a player says "job"
addJob("I have healing abilities and I heal wounded players. I also sell #potions and antidotes.");
// Lets the NPC reply when a player asks for help
addHelp("Ask me to #heal you and I will help you or ask me #offer and I will show my shop's stuff.");
// Makes the NPC sell potions and antidote
addSeller(new SellerBehaviour(shops.get("healing")));
// Lets the NPC heal players for free
addHealer(0);
// respond about a special trigger word
addReply("potions","Please ask for my #offer.");
// use standard goodbye, but you can also set one inside the ()
addGoodbye();
}
});
// This determines how the NPC will look like. welcomernpc.png is a picture in data/sprites/npc/
npc.setEntityClass("welcomernpc");
// set a description for when a player does 'Look'
npc.setDescription("You see Mr Healer, he looks a a bit busy at the moment but perhaps he can help you anyway.");
// Set the initial position to be the first node on the Path you defined above.
npc.setPosition(9, 5);
npc.initHP(100);
zone.add(npc);
}
}
The first thing defined is the name. The NPC is added to a list of NPCs so you can later fetch it for adding more dialogues for quests. So, it is very important to make sure the name you give to your NPC is unique.
Next define a path that the NPC will follow on that area. Just used your tiled map or walk around in game and check coordinates to choose your nodes. Each node is where the NPC turns. Make sure you right down every turning point and don't miss one (don't go from one corner, diagonally to another corner.)
You can also make the NPC stand still by using this instead:
protected void createPath() {
// NPC does not move
setPath(null);
}
In that case your NPC will stand still at whatever point you set as the initial position with npc.setPosition(x, y);
.
And then create a dialog. Dialogs and such are explained in Quest sections, but you should find the example above covers most options and the options like addHelp are quite self-explanatory. There are:
- addGreeting
- addOffer
- addHelp
- addJob
- addQuest
- addGoodbye
- addReply specify a trigger and a reply
We set its outfit either by:
- setting its class to a PNG image that exists at data/sprites/npc, e.g.
npc.setEntityClass("welcomernpc");
- setting its outfit with setOutfit method e.g.
npc.setOutfit(new Outfit(0, 05, 01, 06, 01));
- see Outfit javadoc
It is nice to set a description for when the player does Look. If you don't set one, it will just say: You see [NPC Name].
Finally set its initial position and its HP. Don't worry for your NPC. It can't be attacked nor killed.
Once that is done add the NPC to zone using zone.add() method.
Configure zone xml
We need to tell a zone to load this configuration class file, so the NPC is added to the world somewhere. The NPC in the example lives in the ados area, so the information is stored in data/conf/zones/ados.xml. The map she's on is called int_ados_magician_house, so the information is inside the <zone name="int_ados_magician_house" file="interiors/ados/magician_house.tmx"> </zone> section. To load the java class file you created just add the line
<configurator class-name="games.stendhal.server.maps.ados.magician_house.WizardNPC" />
right after the <zone ... tmx"> Check the location of the file matches the path set in the class name!