The task of creating a skill is really not much harder than creating a command. In some cases there is very little difference. The two main differences are a couple extra fields in the define for the skill and the fact that you must make a skill check or some kind of check to see if the person is able to do the skill. In commands you have already had to make checks to see if a character is in the correct position or location now we just add the check to see if the character is strong enough or skilled enough.
As we have done in the chapter on commands we will first have to pick a skill to write. The diagnose skill seems to be a very good example of a non-combat skill so we will use it for our example. It really doesn't matter if you create the dil first or the define but we like to have the define done so that once the dil compiles we can reboot.
Example 4-1. Diagnose Define
index = SKI_DIAGNOSTICS name = diagnostics command = diagnose minpos = POSITION_RESTING turns = PULSE_VIOLENCE/2 func = diagnose@skills type = TYPE_SKILL race human = 0 race elf = 0 race dwarf = 0 race halfling = 0 race gnome = 0 race half-orc = -2 race half-ogre = 0 race half-elf = 0 race brownie = 0 race groll = -3 race darkelf = 0
As you can see from Example 4-1, the define for diagnose, the first line has 'SKI_DIAGNOSE' as the skills index value. This value is defined in the values.h When ever you create a skill you must add a value to the values.h that corresponds to your skill index. Next the name is what you want the skill to be known by for teachers and for the skills command. Finally we have set the diagnose skill to take up a half round of combat that way a person will have to decide wether he wants to fight or diagnose. Notice also we have picked 'Half-Orc' and 'Groll' to be the stupid races that will have a hard time learning diagnose.
Note: If there are any 'Groll' or 'Half-Orc' readers please do not be offended we just picked your race because we needed someone to use as an example
Now you must add the define to the commands.def and compile it. If you haven't been reading this straight through you can find the information on compiling the defines in Chapter 2.
Now with the define written and compiled it is time to move on to designing and coding our command. We will use the same method as we did in commands so we first need to start by asking ourselves some questions about how this command is going to work.
What if the person doesn't have Diagnose skill?
What if argument given is empty?
What if argument is not a character?
Does skill work different in combat?
How will the skill do its check?
What skill and ability will be used for the skill for both success and defense
What happens if the skill fails
Do we show the percentage or a textual representation of the health of the character being diagnosed?
After you have come up with not only the questions but the answers to the questions so that you think you know how you want the command to work you should write out some Pseudo code so that your logic is clear before you start writing the command.
Example 4-2. Diagnose Pseudo Code
if character is not skilled and is npc act you must learn first quit if argument is empty if argument is fighting and target is person you are fighting otherwise if character is not fighting act what would you like to diagnose quit if target has not been found yet and argument is not empty find target and set it if activator of skill can not see target act no one found by that name quit if target found do calculations act calculations if not fighting find weight and size act weight and size quit quit diagnose
There is two things we need to point out in Example 4-2. In the first line it says if person is not skilled and person is a PC fail. The reason for this is currently NPC's skill is based only on their abilities. Thus if a NPC does this skill it should just continue on with out this check. That way if some crazy Admin wants to switch into an NPC and diagnose all day long he or she could do that. Also notice we check to see if the character we are diagnosing is visible. You would be surprised at the amount of players that use commands that don't make this check to find wizinv admins so make sure to always check and see that what you are letting the players do is what you want them to do.
With the pseudo code created it is a simple matter to convert the logic into code. The resulting conversion created the dil in Example 4-3.
Example 4-3. Diagnose DIL
dilbegin diagnose(arg : string); external string sizestring (cm : integer); string weightstring (cm : integer); integer skillresist (aa : integer, ad : integer, sa : integer, sd : integer); var percent : integer; hm : integer; s1 : string; s2 : string; vict : unitptr; skilla : integer; code { if ((self.type == UNIT_ST_PC) and (self.skills[SKI_DIAGNOSTICS] == 0)) { act("You must practice first.", A_ALWAYS, self, null, null, TO_CHAR); quit; } if (arg == "") { vict := self.fighting; if (vict == null) { act("Diagnose who?", A_ALWAYS, self, null, null, TO_CHAR); quit; } } else { vict := findunit(self, arg, FIND_UNIT_SURRO, null); if ((vict == null) or not visible(self, vict)) { act("Nobody here by that name.", A_ALWAYS, self, null, vict, TO_CHAR); quit; } } if (not (vict.type & (UNIT_ST_PC | UNIT_ST_NPC))) { act("It seems to be dead?", A_ALWAYS, self, null, null, TO_CHAR); return; } if (vict.max_hp > 0) percent := (100 * vict.hp) / vict.max_hp; else percent := -1; /* How could MAX_HIT be < 1?? */ if (self.type == UNIT_ST_PC) skilla := self.skills[SKI_DIAGNOSTICS]; else skilla := self.abilities[ABIL_BRA]; hm := skillresist (self.abilities[ABIL_BRA], 20, skilla, 50); if (hm > 0) hm := 0; percent := percent + ((percent * (-hm))/100); if (percent >= 100) act("$3n is in an excellent condition.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 90) act("$3n has a few scratches.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 75) act("$3n has some small wounds and bruises.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 50) act("$3n has quite a few wounds.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 30) act("$3n has some big nasty wounds and scratches.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 15) act("$3n looks pretty hurt.", A_ALWAYS, self, null, vict, TO_CHAR); else if (percent >= 0) act("$3n is in an awful condition.", A_ALWAYS, self, null, vict, TO_CHAR); else act("$3n is bleeding awfully from big wounds.", A_ALWAYS, self, null, vict, TO_CHAR); if (self.fighting == null) { s1 := weightstring (vict.baseweight); s2 := sizestring (vict.height); act("$3e weighs "+s1+" and is "+s2+" tall.", A_SOMEONE, self, null, vict, TO_CHAR); } quit; } dilend
The entire command should be rather easy to figure out if its not let me know and I can add more documentation to this chapter. The only parts that may be difficult about this command a are the external functions called by the command. The sizestring and the weightstring are rather simple hey just return the size and weight of whatever is passed into the function back in a string to be displayed. The code for these two functions can be found in Appendix E.
skillresist on the other hand is a little harder to understand with out knowing what the openroll function does. see Example 4-4 for the listing of the skillresist
Example 4-4. Skill Resist DIL
dilbegin integer skillresist(Abil_self : integer, abil_vict : integer, skill_self : integer, skill_vict : integer); code { return (openroll(100, 5) + abil_self + skill_self - abil_vict - skill_vict - 50); } dilend
The openroll seems to be the thing that stumps writers of skills because of its cryptic name. The reason it is called open roll is because it will reroll till it hits a certain range of numbers. For example in skillresist the following is the openroll call.
openroll(100,5);This means roll a 100 sided dice until it reaches a range from 5 to 95. If the roll hits 5 to 95 first roll it stops and returns the value. If the roll on the first roll is greater than 95 it rolls again adding the next value to the last roll it will continue to roll again till it hits a number between 5 to 95. If however the first roll was 5 to 0 it will roll again subtracting the number that it gets until it hits the range 5 to 95. The function will continue till it hits either the range 5 to 95 or the threshold of about plus or minus 4 billion. Lets take a couple examples in case that explanation just totally messed up your mind.
Example 4-5. Positive Open Roll
first roll = 97 /*Greater than 95 roll again*/ second roll = 98 /* greater than 95 roll again but add to first roll*/ roll value now is 195 third roll = 28 /* third roll in value 5 to 95 stop*/ end roll value = 223
Example 4-6. Negative Open Roll
First roll = 4 /*Less than 5 to 95 so we continue to roll subtracting now*/ second roll = 98 /*outside the range 4 to 95 continue but subtract*/ value is now -94 third roll = 3 /*still outside continue*/ roll is now -97 forth roll = 58 end roll value = -155
Before going on make sure you clearly understand the openroll you will use it a lot.