Jump to content

Recommended Posts

Posted

OK. I'll go over a simple jython quest. Let's start with the basics (introduction):

All the quests are processed at compile time and tell the engine who are the starter NPCs for that quest. In this manner, when you click on the "Quest" link in a dialog, the engine gets the ID of that NPC and finds all the quests that start from this NPC, plus all the quests that you have accepted and this NPC participates in. So, if you have no quests, it will give you a list of the available quests that start from that NPC. Else, it will check if this NPC participates in the quest you are doing and give you add your quest to the list.

If more than 1 quest is available, it shows you the list and you can choose. If no quests are available, it goes to a default (which is something like "I have no tasks for you"). Else, it goes into the script and starts the interaction.

So, that's where our job starts.

In the beginning of each script you see something like this:

 

 

# Maked by Mr. Have fun! Version 0.2

print "importing quests: 2: What Women Want1"

import sys

from net.sf.l2j.gameserver.model.quest import State

from net.sf.l2j.gameserver.model.quest import QuestState

from net.sf.l2j.gameserver.model.quest.jython import QuestJython as JQuest

 

ARUJIENS_LETTER1_ID = 1092

ARUJIENS_LETTER2_ID = 1093

ARUJIENS_LETTER3_ID = 1094

POETRY_BOOK_ID = 689

GREENIS_LETTER_ID = 693

BEGINNERS_POTION_ID = 1073

ADENA_ID = 57

 

class Quest (JQuest) :

 

def __init__(self,id,name,descr): JQuest.__init__(self,id,name,descr)

 

def onEvent (self,event,st) :

      .........

      .........

      return x

etc etc etc

 

 

In Jython, '#' marks the beginning of a comment.

Jython is capable of making system calls directly. That print commant simply displays the given text in a dos window. You do not need to worry about it. It's used mostly as feedback for the person starting the server, so he can check all the quests that got loaded.

Next, it imports files from Java that it will interact with. Again, this is pretty much the same for all scripts and you don't have to worry about it.

The line:

ARUJIENS_LETTER1_ID = 1092

simply defines a CONSTANT, giving it a name (in this case ARUJIENS_LETTER1_ID) and a value (in this case 1092).

You can define constants in this manner as you please. You can also define constant arrays and such.

The next two lines

 

 

class Quest (JQuest) :

 

def __init__(self,id,name,descr): JQuest.__init__(self,id,name,descr)

 

Just define the beginning of the code for the quest and initialize it. Nothing major here.

 

So...now to the interesting parts. A quest needs to define a method: onEvent. It can also define onTalk and onKill.

Basically, all NPCs (people you talk to and people you kill) get registered with the quest. So each time you kill a quest monster, onKill is called. Each time you talk to a quest NPC, onTalk is called. If a quest doesn't have onTalk or onKill implemented, onEvent is called.

Finally, each time you click on a link in the dialogs, onEvent is called with an event code that is passed via the link you clicked on Wink

let's look at a sample Code:

 

 

def onEvent (self,event,st) :

    htmltext = event

    if event == "2_1" :

          st.takeItems(ARUJIENS_LETTER3_ID,1)

          st.giveItems(POETRY_BOOK_ID,1)

          htmltext = "7223-08.htm"

    elif event == "2_2" and int(st.get("onlyone")) == 0 :

            st.takeItems(ARUJIENS_LETTER3_ID,1)

            st.giveItems(ADENA_ID,450)

            st.set("cond","0")

            st.setState(COMPLETED)

            st.playSound("ItemSound.quest_finish")

            st.set("onlyone","1")

    return htmltext

 

Here, you see part of the implementation of an onEvent function.

the "Event" variable is filled from the engine with some code...in all quests they take that code and store it temporarily in htmltext. This is REALLY not needed, but it's how it's always been done...so no need to change that. In essence, all it does is return the event in case none of the other events fire. In some cases, the link at the htm simply has the name of another page as event, so by doing this, you default to showing the next page only (if no special actions need to take place)

Next, we check what is the value of the event, and do stuff with it.

Some notes:

instead of else if, jython uses elif.

there is no {..} or begin end to notify the beginning and end of the if statement or the loop. Jython is indentation sensitive! You say "if <condition>" in the next line you must put some extra spaces. Jython will assume them all included in this IF, until it sees a line with less spaces!

st is the most important variable. It keeps track of the "state" for the quest, including many variables like the character's race, class, level, items in his inventory, and more. I will go over some of those later.

htmltext is a variable containing info about the next thing to show in a dialog. You can give it either a piece of htmlcode or a link. Both work. When you return htmltext in the end of the above code, the next part of the dialog is shown to the player.

if I said:

 

def onEvent (self,event,st) :

    htmltext = event

    htmltext = "<html><head><body>Hello World.</body></html>"

    return htmltext

 

 

The player would see a popup dialog saying "Hellow World."

If I said:

 

def onEvent (self,event,st) :

    htmltext = event

    htmltext = "1234.htm"

    return htmltext

 

The game engine would look through the server's files for 1234.htm and display a popup dialog with its contents. Simple enough Wink

 

onTalk and onKill work in a similar manner:

 

def onTalk (Self,npcId,st):

  htmltext = "<html><head><body>I have nothing to say you</body></html>"

# .... .... ....

  if npcId == 7223 :

        if int(st.get("cond"))<15 :    #ignore this line for now.

          if st.getPlayer().getRace().ordinal() != 1 and st.getPlayer().getRace().ordinal() != 0 :

            htmltext = "7223-00.htm"

          elif st.getPlayer().getLevel()>1 :

            htmltext = "7223-02.htm"

            return htmltext

          else:

            htmltext = "7223-01.htm"

        else:

          htmltext = "7223-01.htm"

  return htmltext

 

Here, the id of the NPC that the player talked to is being passed. So you can easily deside which htm to show based on the NPC. In this example, the NPC has quests for elves and humans only. So it also checks the race and if it's not 0 or 1 (human, elf) it shows 7223-01.htm which is a file saying something like "this quest is for humans and elves only".

onKill works in the same manner. You get to check the ID of the killed monster and make decisions like if you want to give a quest item, summon a new quest monster (this happens for few quests where you must kill some regular monsters in order to make the quest monster appear), play certain sounds, etc.

An important note here is that you must actually register each NPC for an onTalk or an onKill event.

Those are the basics of designing a quest. There are more details that I shall go through next.

QUEST      = Quest(2,"2_WhatWomenWant1","What Women Want1")

CREATED    = State('Start', QUEST)

STARTING    = State('Starting', QUEST)

STARTED    = State('Started', QUEST)

COMPLETED  = State('Completed', QUEST)

 

 

QUEST.setInitialState(CREATED)

QUEST.addStartNpc(7223)

 

STARTING.addTalkId(7223)

 

STARTED.addTalkId(7146)

STARTED.addTalkId(7150)

STARTED.addTalkId(7157)

STARTED.addTalkId(7223)

 

 

STARTED.addQuestDrop(7157,GREENIS_LETTER_ID,1)

STARTED.addQuestDrop(7150,ARUJIENS_LETTER3_ID,1)

STARTED.addQuestDrop(7223,ARUJIENS_LETTER1_ID,1)

STARTED.addQuestDrop(7146,ARUJIENS_LETTER2_ID,1)

STARTED.addQuestDrop(7223,POETRY_BOOK_ID,1)

 

 

At the end of each script file, you see something like the above example.

QUEST = Quest(2,"2_WhatWomenWant1","What Women Want1")

Registers that this is quest with quest_id = 2, questname = "2_WhatWomenWant1", and description = "What Women Want1"

Description is only used when an NPC has more than 1 quest and needs to display a list to you...this list comes from the "description

CREATED = State('Start', QUEST)

STARTING = State('Starting', QUEST)

STARTED = State('Started', QUEST)

COMPLETED = State('Completed', QUEST)

This registers the 4 states of the quest. You shouldn't ever have to worry about changing those. A Created state is created immidiately when you click on the "quest" link an NPC who offers quests. Think of it as quest initialization. The onTalk event will immediately check if the quest has been created and if yes, it will make it "Starting" until you accept the quest.

Started = you have accepted the quest and you are working on it.

Completed = you finished the quest.

 

QUEST.setInitialState(CREATED)

this is actually the line telling the engine that when the quest is initialized it should go to "CREATED". You *can* change it to go straight to Starting and skip few steps, but it's risky (in case of crushes etc).

 

QUEST.addStartNpc(7223)

This text is parsed when the server starts and registers which NPCs one can talk to in order to get the quest started.

STARTING.addTalkId(7223)

Registers the NPC with 7223 for onTalk events while the quest is on state "Starting"

 

STARTED.addTalkId(7146)

STARTED.addTalkId(7150)

STARTED.addTalkId(7157)

STARTED.addTalkId(7223)

registers NPCs with the IDs 7146, 7150, etc for onTalk events while the quest is on state "started"

 

STARTED.addKillId(370)

This would add a monster with ID 370 for onKill events at state = started.

 

STARTED.addQuestDrop(7157,GREENIS_LETTER_ID,1)

This registers a quest drop: NPC with id = 7157, gives item with id = GREENIS_LETTER_ID (one of the predefined constants), with a drop rate of 1%. The rate actually never worked right. So instead of using that rate and expecting the engine to give the drop, we use onKill events and generate random numbers to get the probabilities manually. We use an 1% when registering the drops because they HAVE to be registered with more than 0% and we do not want to give them a high value (just to be sure that the engine doesn't have a bug that will mess things up).

 

Finally, there are some variables that are used (and added in the database) in order to track the quest's progress. Variables used are cond, id, and onlyone. You can access these variables by:

st.set("cond","1") <-- sets the variable cond to have the value 1

st.get("cond") <--- returns the value that was set to "cond"

the st variable has enough information to know where to find the value (which db table to look in and what character & quest to filter by).

variable "onlyone" is set to 1 if it was a non-repeatable quest that just got completed. If it's a non-repeatable quest that's still in progress or a repeatable quest (regardless if it's completed or not) onlyone is set to 0.

Values for id and cond are free for us to set as we find best to track progress.

Now...I would like to talk about the interaction of the player with all these events. How does a quest get started, how does an event get called, etc.

So suppose a new character name "Tester" goes to an NPC. When he presses the "Quest" link, the code gets activated and pulls a list of all quests for the NPC, or if there is no quests it shows a default message, or if there is only 1 quest it goes into the script.

The first thing from the script that will be ran is quest initialization (making it get state CREATED) and IMMEDIATELY after that, the onTalk event for that NPC will start.

normally, the onTalk is like this:

 

def onTalk (Self,npcId,st):

  htmltext = "<html><head><body>I have nothing to say you</body></html>"

  id = st.getState()

  if id == CREATED :

    st.setState(STARTING)

    st.set("cond","0")

    st.set("onlyone","0")

    st.set("id","0")

  if npcId == 7534 and int(st.get("cond"))==0 :

      if int(st.get("cond")) < 15 :

        if st.getPlayer().getRace().ordinal() != 4 :

          htmltext = "7534-00.htm"

        elif st.getPlayer().getLevel() >= 10 :

          htmltext = "7534-02.htm"

          return htmltext

        else:

          htmltext = "7534-01.htm"

      else:

        htmltext = "7534-01.htm"

    .....  ....  ...

  return htmltext

 

 

(I had skipped this text in the above example)

So, when the quest starts, it defaults the html that it will return to "I have nothing to say to you". Then it checks if the quest has been initialized properly and if yes, then it changes its state, sets the cond, id, onlyone variables, and then starts checking which NPC you talked to, and what to do next. The user has NOT accepted a quest, yet. In essence, one of the things that the script will do now, is find which page to show to the player (presumably, a page with the description of the quest and a link "Say you will do the task" or if the user doesn't qualify for this quest, it will display the proper page explaining why "Tester" cannot take this quest). It will change the htm appropriately, and it will return it so the user will see it.

When the user presses on "Say you will do the task" (or a similar link) the code behind this link looks like this:

 

 

<a action="bypass -h Quest 294_CovertBusiness 1">Say you will take the task</a>

So that tells the engine to open the script for quest named "294_CovertBusiness" with event code "1"

Oftentimes, you see things like

<a action="bypass -h Quest 294_CovertBusiness 1234_01.htm">Say you will take the task</a>

Here, "1234_01.htm" is NOT an actual link to another page. It's just an event code. However, you can use this event code in your script (the onEvent function) and do with it as you please...diplay another htm if you so wish. Event code "1" is usually used for accepting the quests. This is only by convention, but it's good to use it this way.
So...now the user pressed the link, our onEvent code will run and somewhere in there, we should have code like this:

def onEvent (self,event,st) :
    htmltext = event
    if event == "1" :
      st.set("id","0")
      st.set("cond","1")
      st.setState(STARTED)
      st.playSound("ItemSound.quest_accept")
      htmltext = "7534-03.htm"
    return htmltext 


So here, we check what event code came in. If the code is "1" it means that the player pressed on the link saying "Say you will do this task" which means he wants to accept the quest. So we set the state to STARTED, play the sound indicating a quest was accepted, and set our variables to the values we need.
Now that the state changed, all the NPCs and mobs we registered for that state are also active...the player can talk to them and activate new dialogs...
Of course, we must be careful to check that the user is at the right part of the quest, which we often do based on quest items that the user should have on him, or cond and id variables that we set to track the progress of the quest Smile

In summary, here are the important elements of jython:
- it is case sensitive
- it has no brackets or begin/end statements. ALWAYS make sure you indent properly.
- comment lines start with #
- it can access many predefined functions of java to retrieve or add info about the quest, including counting questitems the user has, giving items, taking items, checking information about the character participating in that quest, and much more.
- onEvent is the generic function taking care of everything, given a code that's passed from the engine (and it may come from a link on an htm, or it may be related to a kill / talk event). onEvent is NOT called if an onTalk/onKill is implemented for that npc/mob
- onTalk and onKill events give you easy access to the id of the npc/mob and you can use them for immediate action after talking to an npc or killing a mob.

I know this may be too much info at one time. Well...don't worry. Once you start seeing scripts for yourself and study them a little, it'll get better.
Also, I had to find out most of this info on my own...it wasn't too hard. Few details I didn't know were filled in by a guy yellowperil got me to talk to. There is very little documentation and most of it is just not helpful.
Also, the engine is changing...few things have bugs that they are trying to fix still. Occasionally, they fix something and make something else worthless...in general they do not destroy anything, but you see some redundant checks (like they check if cond = 0 and then check if cond < 15) which come from things they had to do in the past and are no longer necessary now.

Made by Fulmirus for l2j
I made the guide o stop ask how to create a quests and ect

C# Geodata editor:[url=http://rapidshare.com/files/51363334/L2j-GE_v0.8.rar.html]http://rapidshare.com/files/51363334/L2j-GE_v0.8.rar.html[/url]

  • 1 year later...
Guest
This topic is now closed to further replies.
×
×
  • Create New...