> Valhalla MUD - The Dil Tutorial
Dil Quest Tutorial

Original Author: Andrew Cowan (Icculus)

Please use this tutorial only as a guide to writing your first Dil quest, it hopefully explains enough so that your next will not need it. If you have any comments please email wrh2@po.cwru.edu.

If you have been playing Valhalla for a while you may recognize the following definition as the eagle who was located in Asgaard near the cloud stairs. Here is the full code for this quest (Asgaard Quest), which you'll notice is part of the eagle mobile definition.

Comments will be done in C style ( /* to indicate the beginning of a multiline comment and */ to indicate the end ).

Start of Example Code

We start with a normal mobile definition, which you can see explained in the builder's tutorial part I. We tack on the actual Dil Programs at the end of the definition, but preceding the 'end' statement which will officially end the eagle definition. Of course it does not matter where you put the Dil code, just as long as it is between the symbolic name reference and 'end' statement. I generally follow a specific format though and have my Dil programs come last.

                                eagle

names {"eagle", "giant", "giant eagle"}
title "a giant eagle"
descr "A giant eagle is here."
extra {}
"One of the few remaining in Asgaard, Odin enjoys watching them enjoy
their freedom. "
alignment -250
level 95
money 2 GOLD_PIECE
sex SEX_NEUTRALS
weight 150
height 210
race RACE_EAGLE
NATURAL_DEF(WPN_CLAW, ARM_HLEATHER)
MSET_ABILITY(35,30,20,10,5,0,0,0)
MSET_WEAPON(2,2,2,2,35,2)
MSET_SPELL(4,4,4,5,5,5,5,5,6,6,6)
position POSITION_STANDING
default POSITION_STANDING

/* Dil to accept new members to the quest, and check if all
of the items were given to the eagle. If they were, the eagle
gives the char the proper quest reward. -Icc */

/* These two C pre-processor directives define two quest
flags for use with the Dil program. */

#define ASGAARD_ONGOING  "Asgaard Quest Ongoing"
#define ASGAARD_COMPLETE "Asgaard Quest Complete" /* During runtime, the ASGAARD_ values are substituted in for the appropriate defined strings. This is used mainly to aid the dil coder in a more stable way of referring to these values. C and C++ coders will recognize the benefit of such definitions! */ /* We start every Dil pogram with the 'dilbegin' label, and end every Dil program with a 'dilend' label. Though variables are not always necessary, there are for every quest you can think up, so be sure to have this section. To make the variable declaration section, just have the 'var' label and follow it by a list of variables and their type. This is explained in dil.doc so we shall move on. */ dilbegin var pcname : string; item : unitptr; pc : unitptr; i : integer; exdp : extraptr; /* The actual Dil program always follows the 'code' and '{' lines. The heartbeat sets up the delay for such statements that use SFB_TICK, i.e. wait(SFB_TICK, whatever might be here) and walkto statements. If you want a mobile to 'walkto' a specific target you can use this command and each step will be timed based on the heartbeat value. */ code { :init: heartbeat := PULSE_SEC*5; on_activation((self.position < POSITION_SITTING) or (self.position == POSITION_FIGHTING), skip); /* The :init: and :start: labels are just used for references so they can actually be named anything. What is contained in them is quite important however. The on_activation() call is a way to make sure our eagle is actually in a position to help before we begin the quest. If he is not then the dil is skipped via the 'skip' command. There is a bit more on this towards the end of the tutorial. */ /* The contents of the start label is very important. We use this label as a reference so when we want to come back to the beginning of the program we can just use goto start. We generally provide a means for the program to return here on completion of the dil or if something went wrong during execution. The wait() statement is probably the single most important aspect of dil there is! The dil does nothing until the conditions specified in the wait statement are met. The SFB_CMD is just one option, it tells the dil to wait for any command given, then you can specify more like what command was given, and by who! */ /* The activator is usually either an npc or pc which activates the dil. It is treated as a structure (unitptr) and you can reference many things about it, like activator.hp (hitpoints) or activator.quests (what quests it has done) or activator.type which is either UNIT_ST_PC or UNIT_ST_NPC for these types. (NOTE) For advanced dil coding using SFB_MSG, the activator can also be a room or object. */ /* Not sure I have to clear this up but for anyone wondering what 'mobile' means, it is just another name for the monsters (non- playing characters) on the MUD. The term mobile comes specifically from those that wander around in a zone. */ /* Ok, this statement following waits for any command, it makes sure the command was given by a pc (playing character) and it will accept and proceed with the rest of the program if the command is not the 'give' command*/ :start: wait(SFB_CMD,(activator.type == UNIT_ST_PC) and (not command(CMD_GIVE))); /* Ok, so we have a pc activator which has given some command and it is not the 'give' command. Now we want to make sure the activator has not completed this quest already. */ if (ASGAARD_COMPLETE in activator.quests) goto waitawhile; /* Great! So the activator has not, lets continue. We want to store activator in a memory locaion so if we modify it in any way, it gets remembered later! We assign the unitptr variable 'pc' to the activator structure and secure it. This means it has protected memory and we can now modify it however we want. We no longer refer to the activator, we now use pc instead, it is the same person! */ /* Note added by Papi - What secure does is: Whenever the DIL program is activated (tick, command, combat, etc) or the DIL program executes a command that could mean the possible destruction of another unit, it scans through all of it's secured variables.If any of the secured unitptr's are removed from the 'local environment' during this search, the variable is set to NULL and execution continues at 'label' specified in the secure. The local environment is the environment which the unit is currently in ( transparency counts too ). */ pc := activator; secure(pc, lostpc); /* Ok, here is what the eagle will do. It will explain to pc what it wants now! The structure 'self' is how we refer to the thing the Dil is defined on! For us, it is the eagle. If we were defining this dil on an object or room it would mean that instead. We can make the eagle do what we want now, and try to get the pc to join our quest. */ if (not (ASGAARD_ONGOING in pc.quests)) { pause; exec("say You seek the Sky Father Odin do you?", self); pause; exec("say The only way in requires my aid, I'm afraid!", self); pause; exec("say You see, you seek the key, and only I can give it to " + "thee.", self); pause; exec("laugh", self); pause; exec("say and don't think about my death, a crazy idea like" + " that is as foul as your breath!", self); pause; exec("say Now, this is what you must bring me. Upon success " + "I will get you the key.", self); pause; exec("say To get the key, bring these things three.", self); pause; exec("say In the run down village of Ratswarf is an object " + "crafted in the image of a skull.", self); pause; exec("say This you shall bring me. But that's not all!", self); pause; exec("say In a small room near the gates of hell, a blood red" + "trident can be found. This you shall also bring..", self); pause; exec("say Nearby where the ninjas practice, a sword made for " + "guards can be found. Bring this to me as well...", self); pause; exec("say When I have all three, you'll have the key.",self); pause; exec("say Is such a key worth all of the bloodshed to " + "surely follow? Please nod if you think so.", self); /* The loop will wait for the pc to enter a command either a shake of the head to say 'no quest for me' or nod to say 'ok', sign me up and give me the quest flag that says I am ready to try! The loop will prompt for a decision 5 times, then quit if not met */ i := 0; while (i < 5){ wait(SFB_CMD, activator #= pc); if (command(CMD_NOD)) { goto quest_accepted; /* Goto the label if accepted */ } else if (command(CMD_SAY)) { /* This specifies what happens if the pc says something */ pause; exec("say Nod if you accept my challenge, shake your head" + "otherwise.", self); } else if (command(CMD_SHAKE)) /* Ok,the pc didn't want the quest, we tried and failed */ goto no_quest; i := i+1; } /* Here is what happens when the pc shakes its head, we basically say ok, sorry, and want to undo our variable secure. Always remember to unsecure the secured variable before returning to :start:. Otherwise, if the player leaves the room, quits or gets killed while the dil is still executing, the execution will continue where told in the secure statement. */ :no_quest: pause; exec("say So be it! It is most unlikely you will ever meet the " + "Sky Father.", self); unsecure(pc); goto waitawhile; /* Ok, quest accepted, what do we do? Well, we add the quest flag to the pc and unsecure it, then return to star. This part of the dil is done. */ :quest_accepted: pause; exec("say Begone then, do not return until you have all you " + "need!", self); addextra(pc.quests, {ASGAARD_ONGOING}, ""); unsecure(pc); goto start; } /* Here is what happens if the pc has already joined the quest. We will check the extra flags on the quest to see if the other Dil (the one coming up after this) has received the items. These names you see (skull key,...) were defined in the next dil, as a way of alerting this one that the items were given. If all 3 items were given, we replace the ongoing flag with the complete flag, this pc has completed it and shall now receive the reward. */ else /* ASGAARD_ONGOING in pc.quests! */ { exdp := ASGAARD_ONGOING in pc.quests; if (("skull key" in exdp.names) and ("trident" in exdp.names) and ("imperial sword" in exdp.names)) { pause; subextra(pc.quests, ASGAARD_ONGOING); addextra(pc.quests, {ASGAARD_COMPLETE}, ""); exec("emote sets the skull key down before him.", self); pause; exec("emote magically transforms the skull key into a " + "cloud-gray key.", self); pause; exec("emote gives you the cloud-gray key.", self); item := load("sky_key@asgaard"); /* Loads the item */ link(item, pc); /* Gives the item to the pc. NOTE- The link will not work unless pc is secured! */ experience(90000, pc); /* what can I say, I am extra nice! */ /* ^^^^^^^^^^^^^^^^^^^^^ Note added by Papi - Be very careful when using this command. It can easily raise a 3rd level to 200th level if in a loop. Always remember to test these constructs carefully BEFORE they become available to players. */ exec("say This will gain you entrance into the planes " + "of sky.", self); pause; exec("pat "+pc.name, self); pause; exec("say I also have for you a gift!", self); pause; exec("emote gives you a Stick of Bridging.", self); item := load("bridge_stick@asgaard"); link(item, pc); pause; exec("say That will help in your efforts to get around " + "in this world.", self); pause; exec("wave", self); } } /* Ok, we are done, lets unsecure the pc and return to start so that we may get a new quest member. */ unsecure(pc); goto start; /* Just a label to go to if we want to skip the main body of code, like if the char has completed this quest already! If we didn't do this, the eagle would immediately check for a new activator to tell the whole story to. */ :waitawhile: heartbeat := PULSE_SEC*60; pause; goto init; /* Ok, you noticed that we used secure(pc, lost_pc) at the top of the progam. What this means is this: We secure our pc variable, and at any time if the eagle and the pc get seperated, this is what the Dil jumps to. So if the pc leaves in the middle, we come here, and then return to start to wait for another pc. */ :lostpc: exec("say Too much to handle I suppose.", self); pause; exec("shrug", self); goto start; /* This does not get used at all! I used to use it if the pc attacked the eagle during the dil, but removed the call to it. Pay no heed, although I am anal retentive and leave it in anyway. If I were going to use it, this is how. All the way at the top of the dil, right after the :init: label I would put this call. on_combat(self.poisiton == POSITION_FIGHTING, ohno_combat); Then when the quest host (self) is attacked, the :ohno_combat: routine is executed. I could also use 'skip' here as I did at the top of the program for our on_activation() call. If I also want to prevent the dil from operating when the host is asleep, I can do this in :init: also: (You'll also notice the one we actually used in :init: checked for fighting as well.) (NOTE) This on_activation() call not only checks the eagle's condition at the point it is called, but also at any given time while the program is executed! This means if the eagle were to be hit by a sleep spell during his talk, the on_activation() would sense his condition and abort the program execution then and there. Here is the call: on_activation(self.position < POSITION_SITTING, skip); where 'skip' is a special statement used to stop the Dil program from activating. So if the eagle is in a position of sleeping, or worse (dead or incap) then the dil will not execute! Otherwise execution continues. (Thanks to Papi for this explanation) */ :ohno_combat: heartbeat := PULSE_SEC*5; exec("say Ok then, so it shall be this way with you...", self); pause; exec("say Die nerf herder.", self); pause; goto start; } dilend /* Ok, the last Dil script necessary. This one will activate on any command given by a pc. The purpose of this one is to accept the quest items and for everyone accepted, put an entry on the quest flag (exdp entry) that says 'I got that itme!' */ dilbegin var pc : unitptr; item : unitptr; exdp : extraptr; code { heartbeat := PULSE_SEC*5; on_activation((self.position < POSITION_SITTING) or (self.position == POSITION_FIGHTING), skip); /* Ok, so again we set up a heartbeat to use and check that the eagle is in a position to help. I.E. That he is not sleeping, incap or dead, and not fighting as well. */ :start: wait(SFB_CMD, command(CMD_GIVE) and (activator.type == UNIT_ST_PC)); /* This label (give) is referred to below. Note - The label will be ignored now, we will just go to the next line, but later if we do a 'goto give', this is where we come! */ :give: if (not (ASGAARD_ONGOING in activator.quests)) goto start; /* The pc stuff we discussed. The item works as follows. We assign item to whatever is 'inside' the self (eagle in our case). So if I just gave the eagle the skull key, that is inside the eagle, which means in its inventory. And we want to secure the item just as we did before, we use the label labsecure where we used lost_pc earlier! */ pc := activator; item := self.inside; secure(pc, labsecure); secure(item, labsecure); /* Basically we wait for a command now! */ wait(SFB_CMD or SFB_TICK, TRUE); /* Ok, now we compare what is inside the eagle's inventory. */ if (not (item #= self.inside)) { unsecure(item); exdp := ASGAARD_ONGOING in pc.quests; /* Very Important! The "Skull key" is exactly the FIRST name on the namelist for the item skull key. If you use any other name on the list, this will not work, so always use the first! Ok, so if the skull key is an item in the eagle's inventory, we add the extra description (exdp.names) "skull key" to the quest flag. This name "skull key" can be specified by you, use what ever you like here, but remember to use the same one in the first dil wich checks for it. Go back now and look to see if we checked for "skull key" in the first dil! What purpose does this serve you ask? Well, if the pc only gives one item at a time and has to leave, when he returns, the item given is still considered given since we put it on his quest flag! It is a way of recording data on the pc itself! Finally we have the eagle say something, and then destroy the key in its inventory! Lastly we repeat this process for each of the other two items! */ /* (NOTE) An alternative to our check on the item below is to use a symbolic comparison. We could check the same skull key like this: if (("skull_key" $= self.inside.nameidx) and ("ratswarf" $= self.inside.zoneidx)) This call above verifies both the symbolic name of the item and the zone it is defined in. For those of you C coders you'll be happy to note that these string comparisons are NOT case- sensitive. */ if ("Skull key" in self.inside.name) { addstring(exdp.names, "skull key"); exec("say Heh, the old slaver suffered I reckon *grin*...", self); destroy(self.inside); } else if ("Trident" in self.inside.name) { addstring(exdp.names, "trident"); exec("say I gather the Guardian Devil didn't take to your " + "insolence?", self); destroy(self.inside); } else if ("Imperial sword" in self.inside.name) { addstring(exdp.names, "imperial sword"); exec("say Ahh yes, a fine imperial sword, " + "a most devastating weapon indeed!", self); destroy(self.inside); } else { exec("say Did I ask for this, "+pc.name +"?", self); pause; exec("say I think not.", self); /* Return the item to the player */ link(self.inside, pc); } } unsecure(pc); /* If the 'give' command is given, at any time, go back to the :give: label and start from there to check what was given! Also, a drawback to this whole procedure is the fact that if two items were given quickly enough, one of them would not be recorded as being given, the give label would be referenced before the first item was done triggering its code sequence. You'll also notice that like in C, if our if() statement only has one line to execute, the braces {, } are not necessary, I use them generally out of habit and anal-coding style. */ if ((command(CMD_GIVE)) and (activator.type == UNIT_ST_PC)) { goto give; } else goto start; /* This is equiv. to our :lost_pc: of the first dil script. The dil comes here when there is a seperation of self and either pc, or item. */ :labsecure: exec("say They won't get far!", self); goto start; } dilend /* Well, that is all there is to it. I must give credit where it is due. I used the Mary's quest format, I think written by Papi but I am not sure. I just modified it to suit my needs for my quest. This is what you can do also for your quest, but feel free to play and explore...You can think of many things to add here if you find the need. An important thing to realize about Dil is this: It works in parallel with other dil scripts. That is, the two we defined here on the eagle will both be running at the same time, and that is why we use conditions like command("give"), to specify which dil we want to use at the time. I hope this tutorial helps you, please feel free to send any questions/comments to me (Icculus, Andrew Cowan) at cowana@magicnet.net I would highly recommend also checking out haon-dor.zon which is available through the same ftp site. This file contains much more advanced dil code, and illustrates the use of sending messages to/from dil programs. Enjoy the wonderful world of Dil! -Icculus */ end /* end of eagle deinition */ /* *************** End of Tutorial *************** */