Stendhal Quest Coding: Difference between revisions

Jump to navigation Jump to search
Content deleted Content added
imported>MiguelAngelBlanchLardin
No edit summary
imported>Kribbel
m replace old link
 
(333 intermediate revisions by 16 users not shown)
Line 1: Line 1:
{{Navigation for Stendhal Top|Contributing}}
Adding quests is perhaps the most complex step on any game.
{{Navigation for Stendhal Contributors}}


{{ TODO | Update page for changes in quest coding }}
So please before even trying, read these docs and make sure you understand the basis:
* [[HowToAddItemsStendhal|How to add items]]
* [[HowToAddMapsServerStendhal|How to add new maps to server]]
* [[HowToAddMapsServerStendhal#Adding_NPC|How to create NPC]]


It would be even better if you try yourself and play a bit with it.


{{Stendhal Quests}}
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
__TOC__
<pre>
package games.stendhal.server.maps.quests;


If you have ideas for new quests or are interested in helping to refine quest ideas, please have a look at the [[Stendhal Quest Contribution|Quest Contributor's Guide]] or the [[Stendhal Quest Ideas]].
public class LookBookforCeryl implements IQuest
{
public LookBookforCeryl(StendhalRPWorld w, StendhalRPRuleProcessor rules)
{
}
}
</pre>


The constructor '''ALWAYS''' has two parameters: World object and Rules object.


== Before you start ==
Then let see how to add a quest by example.


This page describes how to code a quest. You don't need to know a lot about Java. You should, however, already have [[Configure a development environment (IDE)|setup an IDE]] and be able to compile and start a local Stendhal server.
<pre>
package games.stendhal.server.maps.quests;


This tutorial assumes that the new quest only uses NPCs and items that already exist in Stendhal. To add a new NPC, see [[Stendhal NPC Coding]].
import games.stendhal.server.*;

import games.stendhal.server.maps.*;
== Creating a quest skeleton ==
import games.stendhal.server.entity.Player;

import games.stendhal.server.entity.item.Item;
This tutorial is based on the quest "Beer For Hayunn". As this quest already exists, you may want to delete the java file locally in order to follow this tutorial.
import games.stendhal.server.entity.item.StackableItem;

import games.stendhal.server.entity.creature.Sheep;
Quest files are put into the package games.stendhal.server.maps.quests. (If you are new to Java: this refers to the folder stendhal/src/games/stendhal/server/maps/quests).
import games.stendhal.server.entity.npc.Behaviours;

import games.stendhal.server.entity.npc.NPC;
Please create a new file in that folder called BeerForHayunn.java. (Note: The upper / lower case spelling is important, even on Microsoft Windows):
import games.stendhal.server.entity.npc.SpeakerNPC;

import games.stendhal.server.pathfinder.Path;
<source lang="java">
package games.stendhal.server.maps.quests;


import games.stendhal.server.entity.npc.*;
import games.stendhal.server.entity.npc.action.*;
import games.stendhal.server.entity.npc.condition.*;
import games.stendhal.server.entity.player.*;
import java.util.*;
import java.util.*;


public class BeerForHayunn extends AbstractQuest {
import marauroa.common.game.IRPZone;
</pre>


public static final String QUEST_SLOT = "beer_hayunn";
Just java wording. Import whatever you need.
It is an accept good practique to import only what you really need.


@Override
<pre>
public void addToWorld() {
/**
super.addToWorld();
* 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.
*/
</pre>


@Override
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.
public String getSlotName() {
return QUEST_SLOT;
}


@Override
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 String getName() {
return "BeerForHayunn";
}


public List<String> getHistory(final Player player) {
<pre>
final List<String> res = new ArrayList<String>();
public class LookBookforCeryl implements IQuest
return res;
{
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();
}
}
}
</source>


Don't worry, if you don't understand a word of this; just copy it. We will explain the important parts and extend this skeleton in the following sections.
</pre>


In order for the Stendhal server to pick up this file, it has to be registered in the file StendhalQuestSystem.java in the package games.stendhal.server.core.rp by adding two lines at the appropriate places:
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.


<source lang="java">
Think of your quests as a set of steps that need to be done in order for it to be completed.
import games.stendhal.server.maps.quests.BeerForHayunn;


// [...]
Let's see the first step: ''Talk with Ceryl to activate the quest.''


loadQuest(new BeerForHayunn());
<pre>
</source>
private void step_1()
{
StendhalRPZone zone=(StendhalRPZone)world.getRPZone(new IRPZone.ID("int_semos_library"));
</pre>


Of course in the case of this tutorial the two lines for BeerForHayunn are already there.
We get the zone we are going to work in.


== Teaching the NPC to talk ==
Ceryl lives at Library so we get library.


Okay, we have now completed the preparation. Our first task is to get Hayunn to reply to the word "quest".
<pre>
SpeakerNPC npc=npcs.get("Ceryl");
</pre>


Therefore we add a new method called prepareQuestStep() at the end of the file BeerForHayunn, just above the last closing "}". This method makes Hayunn reply to the word "quest" with the answer "My mouth is dry, but I can't be seen to abandon this teaching room!"
This works because in games/stendhal/server/maps/Semos.java we defined Ceryl NPC.


<source lang="java">
Now we simply get it to start adding dialogues to it.
public void prepareQuestStep() {


// get a reference to the Hayunn npc
<pre>
SpeakerNPC npc = npcs.get("Hayunn Naratha");
Behaviours.addQuest(npc,"I am looking for a very special #book");
</pre>


// add a reply on the trigger phrase "quest" to Hayunn
npc.addReply("quest", "My mouth is dry, but I can't be seen to abandon this teaching room!");
}
</source>


<pre>
/** 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");
}
});


There is one little step left before we can test it: We need to tell the server to execute our new method. There is already a method called "addToWorld" which will be executed on server start. So we add a call to our method in "addToWorld":
npc.add(60,"no",null,1,"Oh! Ok :(",null);


<source lang="java">
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);
@Override
public void addToWorld() {
super.addToWorld();
prepareQuestStep();
}
</source>


Okay, all done? Please start the server (depending on whether you are using an IDE or not you might have to compile or build first). Go to Hayunn and say "quest", after starting the conversation with "hi". He should now respond with the sentence "My mouth is dry, but I can't be seen to abandon this teaching room!".
/** 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"));


== Commonly used conversation phrases ==
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.");


Good, Hayunn now replies to the trigger "quest". He does not, however, reply to "task". All other NPCs accept both words as synonym. A simple solution would be to add a second ''npc.addReply'' line. But there is a better way which makes it very easy to add additional synonyms later. We predefined lists of commonly used [https://github.com/arianne/stendhal/blob/master/src/games/stendhal/server/entity/npc/ConversationPhrases.java ConversationPhrases]. If there are already conversation phrases defined for the triggers that you would like to add, you should use the phrases.
Item book=world.getRuleManager().getEntityManager().getItem("book_black");
player.equip(book);
}
});


Let's adjust the above sample by using ''ConversationPhrases.QUEST_MESSAGES'' instead of the hard coded word "quest":
/** 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);


<source lang="java">
npc.add(1,"ceryl",null,1,"Ceryl is the book keeper at Semos's library",null);
public void prepareQuestStep() {


// get a reference to the Hayunn npc
/** Finally if player didn't started the quest, just ignore him/her */
SpeakerNPC npc = npcs.get("Hayunn Naratha");
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"));


// add a reply on quest related trigger phrases to Hayunn
SpeakerNPC npc=npcs.get("Ceryl");
npc.addReply(ConversationPhrases.QUEST_MESSAGES,
"My mouth is dry, but I can't be seen to abandon this teaching room!");
/** Complete the quest */
}
npc.add(1,"book",new SpeakerNPC.ChatCondition()
</source>
{
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");


Please compile and restart your server. Hayunn should now respond to "quest", "task", and "favor", after you started the talk by saying "hi".
money.setQuantity(50);
player.addXP(100);


== Blue trigger words ==
world.modify(player);


As you probably know NPCs can say words in blue, words that they expect to be repeated by the player. We want to add such words for "beer" and "tavern". As we have done before, we will add npc.addReply lines for those words.
player.setQuest("ceryl_book","done");

}
So, how do we get the words colored blue? Simple, add a "#" in front of them. If you actually want to include a #-character, you need to repeat it.
else

{
<source lang="java">
engine.say("Where did you put #Jynath's #book?. You need to start again the search.");
public void prepareQuestStep() {
player.removeQuest("ceryl_book");

}
// get a reference to the Hayunn npc
}
SpeakerNPC npc = npcs.get("Hayunn Naratha");
});

}
// ask for a beer and explain it
}
npc.addReply(ConversationPhrases.QUEST_MESSAGES,
</pre>
"Please bring me a #beer.");

// explain blue words
npc.addReply("beer", "Margaret sells beers in the #tavern.");
npc.addReply("tavern", "If you don't know where the inn is, you could ask old Monogenes.");

// an example for escaping #
npc.addReply("trading", "http://stendhal.game-host.org/wiki/index.php/StendhalFAQ##Trading");
}
</source>

You know the drill: Compile, restart and try it out.

== Second Part of this Tutorial ==

Congratulations if you made it this far. You are now able to code basic dialogs with NPCs. The next section of this tutorial will describe advanced techniques. Please make sure the steps on this page work before you continue to [[Stendhal Quest Coding - Part 2|the second part of this tutorial]].


[[Category:Stendhal]]