Stendhal Quest Coding
Adding quests is perhaps the most complex step on any game.
So please before even trying, read these docs and make sure you understand the basis:
It would be even better if you try yourself and play a bit with it.
Ok, now lets create a new Java file at games/stendhal/server/maps/quest. This will will be name like the quest you are going to do, for example: LookBookforCeryl
package games.stendhal.server.maps.quests;
public class LookBookforCeryl implements IQuest
{
public LookBookforCeryl(StendhalRPWorld w, StendhalRPRuleProcessor rules)
{
}
}
The constructor ALWAYS has two parameters: World object and Rules object.
Then let see how to add a quest by example.
package games.stendhal.server.maps.quests; import games.stendhal.server.*; import games.stendhal.server.maps.*; import games.stendhal.server.entity.Player; import games.stendhal.server.entity.item.Item; import games.stendhal.server.entity.item.StackableItem; import games.stendhal.server.entity.creature.Sheep; import games.stendhal.server.entity.npc.Behaviours; import games.stendhal.server.entity.npc.NPC; import games.stendhal.server.entity.npc.SpeakerNPC; import games.stendhal.server.pathfinder.Path; import java.util.*; import marauroa.common.game.IRPZone;
Just java wording. Import whatever you need. It is an accept good practique to import only what you really need.
/** * QUEST: Look book for Ceryl * PARTICIPANTS: * - Ceryl * - Jynath * * STEPS: * - Talk with Ceryl to activate the quest. * - Talk with Jynath for the book. * - Return the book to Ceryl * * REWARD: * - 100 XP * - 50 gold coins * * REPETITIONS: * - As much as wanted. */
This is mandatory!. Describe the quest to your best at the top of the class so others can read it and spot bugs or test it completly. This way you ease the development of the whole game.
Many of you think content shouldn't be open source because it removed the fun of discovering. Well, we, on Arianne, don't agree with that statement, and we think that Stendhal is as fun as any other closed source code.
public class LookBookforCeryl implements IQuest
{
private StendhalRPWorld world;
private NPCList npcs;
public LookBookforCeryl(StendhalRPWorld w, StendhalRPRuleProcessor rules)
{
this.npcs=NPCList.get();
this.world=w;
step_1();
step_2();
step_3();
}
The constructor is called by the Quest system to create the quest. So instead of writting the quest inside the constructor we have split it in steps.
Think of your quests as a set of steps that need to be done in order for it to be completed.
Let's see the first step: Talk with Ceryl to activate the quest.
private void step_1()
{
StendhalRPZone zone=(StendhalRPZone)world.getRPZone(new IRPZone.ID("int_semos_library"));
We get the zone we are going to work in.
Ceryl lives at Library so we get library.
SpeakerNPC npc=npcs.get("Ceryl");
This works because in games/stendhal/server/maps/Semos.java we defined Ceryl NPC.
Now we simply get it to start adding dialogues to it.
Behaviours.addQuest(npc,"I am looking for a very special #book");
There two ways of adding chat to a NPC. The first and simpler one is using the Behaviour class.
This class has a set of predefined triggers that you can use:
- addGreeting(npc, text)
Replies to anyone that greet this NPC with the given text.
The trigger condition is hi, hello, hola. To start any conversation with a NPC the player MUST first greet the NPC: - addReply(npc, trigger, text)
Reply the attended player with text when NPC listen the keyword trigger or a word that contains the keyword. - addQuest(npc, text)
Show text to player when NPC listen the keyword quest or task - addJob(npc, text)
Show NPC job explained in text when listen the keyword job or work - addHelp(npc, text)
When NPC listen to help or ayuda it says text. - addSeller
- addBuyer
- addHealer
These three tasks are so common among ours NPC that we havew promoted the code to a method. Just add the items it sell or it buy or how much does it charge for healing.
We use a very simple method to denote special keywords by placing a # before it. The client renders the next word in a bold color.
The other way of creating a NPC dialog is by adding states to the FSM.
/** In case Quest is completed */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return player.isQuestCompleted("ceryl_book");
}
},
1,"I already got the book. Thank you!",null);
/** If quest is not started yet, start it. */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return !player.hasQuest("ceryl_book");
}
},
60,"Could you ask #Jynath for a #book that I am looking?",null);
npc.add(60,"yes",null,
1,null,new SpeakerNPC.ChatAction()
{
public void fire(Player player, String text, SpeakerNPC engine)
{
engine.say("Great!. Start the quest now!");
player.setQuest("ceryl_book","start");
}
});
npc.add(60,"no",null,1,"Oh! Ok :(",null);
npc.add(60,"jynath",null,60,"Jynath is a witch that lives at south of Or'ril castle. So will you get me the #book?",null);
/** Remind player about the quest */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return player.hasQuest("ceryl_book") && player.getQuest("ceryl_book").equals("start");
}
},
1,"I really need that #book now!. Go to talk with #Jynath.",null);
npc.add(1,"jynath",null,1,"Jynath is a witch that lives at south of Or'ril castle. So will you get me the #book?",null);
}
private void step_2()
{
StendhalRPZone zone=(StendhalRPZone)world.getRPZone(new IRPZone.ID("int_orril_jynath_house"));
SpeakerNPC npc=npcs.get("Jynath");
/** If player has quest and is in the correct state, just give him the book. */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return player.hasQuest("ceryl_book") && player.getQuest("ceryl_book").equals("start");
}
},
1,null,new SpeakerNPC.ChatAction()
{
public void fire(Player player, String text, SpeakerNPC engine)
{
player.setQuest("ceryl_book","jynath");
engine.say("Here you have the book Ceryl is looking for.");
Item book=world.getRuleManager().getEntityManager().getItem("book_black");
player.equip(book);
}
});
/** If player keep asking for book, just tell him to hurry up */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return player.hasQuest("ceryl_book") && player.getQuest("ceryl_book").equals("jynath");
}
},
1,"Hurry up! Grab the book to #Ceryl.", null);
npc.add(1,"ceryl",null,1,"Ceryl is the book keeper at Semos's library",null);
/** Finally if player didn't started the quest, just ignore him/her */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return !player.hasQuest("ceryl_book");
}
},
1,"Shhhh!!! I am working on a new potion!.", null);
}
private void step_3()
{
StendhalRPZone zone=(StendhalRPZone)world.getRPZone(new IRPZone.ID("int_semos_library"));
SpeakerNPC npc=npcs.get("Ceryl");
/** Complete the quest */
npc.add(1,"book",new SpeakerNPC.ChatCondition()
{
public boolean fire(Player player, SpeakerNPC npc)
{
return player.hasQuest("ceryl_book") && player.getQuest("ceryl_book").equals("jynath");
}
},
1,null,new SpeakerNPC.ChatAction()
{
public void fire(Player player, String text, SpeakerNPC engine)
{
Item item=player.drop("book_black");
if(item!=null)
{
engine.say("Thanks!");
StackableItem money=(StackableItem)world.getRuleManager().getEntityManager().getItem("money");
money.setQuantity(50);
player.addXP(100);
world.modify(player);
player.setQuest("ceryl_book","done");
}
else
{
engine.say("Where did you put #Jynath's #book?. You need to start again the search.");
player.removeQuest("ceryl_book");
}
}
});
}
}