Chapter 3
Adding Player Abilities and System Effects
Having additional classes in your MUD is terrific, but if that class is just
a composite of elements from other classes, it is not really all that much
to brag about. For a new class to be interesting, it has to have something
about it which is unique; special skills or abilities that no other class
in the game has. Also, if you take a look at the abilities list toward the
end of "class.c", there is a significant imbalance in how special powers
were distributed, with the mages and clerics having the lion's share.
To this end, we should be able to add abilities. The places where modifications
need to be made are, again, distributed over a few files, but this is not
as complicated as adding a class was.
There are a couple of different types of spells and of skills we could create.
Some spells are aggressive, and will automatically start a fight, or
automatically target someone that the person or non-player character using
the spell is already fighting. Some create or destroy objects. Some affect
attributes of a character, either improving or diminishing the character's
ability to move or to fight, and some of these have a lasting affect that
eventually wears off (if it doesn't kill the target character first). Some
only work on characters who are under the influence of these other affects.
Some affect a character's ability to see other characters, some affect a
character's ability to be seen.
The spells and skills being defined here are intended for the ranger class
which was created in the previous chapter. Let's start with a couple of easy
ones, and then maybe move onto some more intricate ones. First we'll create
a generic attack skill. After that, we'll create a non-combat spell which
adds to the player's inventory. Then we'll create a spell which applies a
system effect to a character, and finish up with a social.
The first step in adding any command, with the exception of spells which
must be cast rather than simply done, is to add the command to the list in
"interpreter.c". At about line 197 you should find the cmd_info array, which
contains a list of every command that can be typed by any player in "normal"
play mode (CON_PLAYING). After the "reserved" entry and direction commands,
the other entries are listed for the most part in alphabetical order. Each
entry is in the array is a structure of type command_info which contains
the components needed to make the command accessible through the game's normal
command interpreter sequences. The first of these is a string containing
the actual command as the player would type it. The second is the minimum
position (dead, mortally wounded, incapacitated, stunned, sleeping, resting,
sitting, fighting or standing) that a player must be in to use this command.
The third is the name of the function, as it exists in the source code, which
executes the command. What is actually stored here is a pointer to the function's
location in memory, and the ability of C to do this allows for the routines
which actually interpret the commands to be much simpler than they would
otherwise need to be. The fourth element is the minimum level at which a
player can type this command. Setting this LVL_IMMORT, for example, would
restrict this ability to players at the Immortal level and above. The fifth
and last element is the subcommand feature, which is set to a subcommand
value, if more than one command is handled by the same function. For example,
there are several different commands for communicating on one of the public
channels, such as the "shout" and "gossip", all of which are handled by the
do_gen_com function, but are done with different commands, which send do_gen_com
different subcommand values. Subcommand values are defined in "interpreter.h"
starting at about line 67, although we will not be using any of them.
The order the commands appear in actually serves a purpose. The command
interpreter searches through the list from top to bottom until it finds a
command which is either the same as or starts with the command that a player
has typed. Consequently, if "cat" and "catch" are both on the list, and "cat"
comes first, a player typing "catch" will in fact execute "cat". As a result,
commands which are part of other commands should be placed earlier in the
list. Also, if you want "bea" to be an abbreviation for "beat", and "bearhug"
is also on the list, "beat" must be ahead of "bearhug".
Our new attack skill is going to be called "Bearhug", so we'll go down to
the commands starting with "b", at about line 218:
{ "bounce" , POS_STANDING, do_action , 0, 0 }, { "backstab" , POS_STANDING, do_backstab , 1, 0 }, { "ban" , POS_DEAD , do_ban , LVL_GRGOD, 0 }, { "balance" , POS_STANDING, do_not_here , 1, 0 }, { "bash" , POS_FIGHTING, do_bash , 1, 0 }, { "beg" , POS_RESTING , do_action , 0, 0 }, { "bleed" , POS_RESTING , do_action , 0, 0 }, { "blush" , POS_RESTING , do_action , 0, 0 }, { "bow" , POS_STANDING, do_action , 0, 0 }, { "brb" , POS_RESTING , do_action , 0, 0 }, { "brief" , POS_DEAD , do_gen_tog , 0, SCMD_BRIEF }, { "burp" , POS_RESTING , do_action , 0, 0 }, { "buy" , POS_STANDING, do_not_here , 0, 0 }, { "bug" , POS_DEAD , do_gen_write, 0, SCMD_BUG },
Somewhere in there, add the following line:
{ "bearhug" , POS_FIGHTING, do_bearhug , 1, 0 },
This having been done, we need to move up to about line 55 or so, and find
the "do_x" function prototypes. Right below the line for do_bash, at about
line 64, insert the following line:
ACMD(do_bearhug);
This will put a prototype in for the do_bearhug function, using the ACMD macro defined in "interpreter.h", which is used for all commands to insure that they interface properly with whatever functions call them.
Close "interpreter.c" and open "spells.h". Here, we need to define certain
values so that the skill can be learned. Go down to about line 93 and find
the following section:
/* PLAYER SKILLS - Numbered from MAX_SPELLS+1 to MAX_SKILLS */ #define SKILL_BACKSTAB 131 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_BASH 132 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_HIDE 133 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_KICK 134 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_PICK_LOCK 135 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_PUNCH 136 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_RESCUE 137 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_SNEAK 138 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_STEAL 139 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_TRACK 140 /* Reserved Skill[] DO NOT CHANGE */
At the end of these, insert the following line:
#define SKILL_BEARHUG 141
Next, close "spells.h" and open "structs.h". Go down to about line 134 and
find the bitvector flags for mobiles:
/* Mobile flags: used by char_data.char_specials.act */ #define MOB_SPEC (1 << 0) /* Mob has a callable spec-proc */ #define MOB_SENTINEL (1 << 1) /* Mob should not move */ #define MOB_SCAVENGER (1 << 2) /* Mob picks up stuff on the ground */ #define MOB_ISNPC (1 << 3) /* (R) Automatically set on all Mobs */ #define MOB_AWARE (1 << 4) /* Mob can't be backstabbed */ #define MOB_AGGRESSIVE (1 << 5) /* Mob hits players in the room */ #define MOB_STAY_ZONE (1 << 6) /* Mob shouldn't wander out of zone */ #define MOB_WIMPY (1 << 7) /* Mob flees if severely injured */ #define MOB_AGGR_EVIL (1 << 8) /* auto attack evil PC's */ #define MOB_AGGR_GOOD (1 << 9) /* auto attack good PC's */ #define MOB_AGGR_NEUTRAL (1 << 10) /* auto attack neutral PC's */ #define MOB_MEMORY (1 << 11) /* remember attackers if attacked */ #define MOB_HELPER (1 << 12) /* attack PCs fighting other NPCs */ #define MOB_NOCHARM (1 << 13) /* Mob can't be charmed */ #define MOB_NOSUMMON (1 << 14) /* Mob can't be summoned */ #define MOB_NOSLEEP (1 << 15) /* Mob can't be slept */ #define MOB_NOBASH (1 << 16) /* Mob can't be bashed (e.g. trees) */ #define MOB_NOBLIND (1 << 17) /* Mob can't be blinded */
Add the following line to the end of the list, enabling a bitvector entry
for mobiles so that some can be made non-bearhug-able:
#define MOB_NOBEARHUG (1 << 18) /* Mob can't be bearhugged */
Now, since this is a combat function, close "structs.h" and open
"act.offensive.c" so that we can add do_bearhug in with the other functions
for combat abilities. Since we are adding a whole function here, this can
actually go anywhere in the file after the #include statements and file-wide
declarations, which end at about line 31, but for the sake of being able
to find it later, we will place it at the end of the file.
ACMD(do_bearhug) { struct char_data *vict; int percent, prob; one_argument(argument, arg); if (!(vict = get_char_room_vis(ch, arg))) { if (FIGHTING(ch)) { vict = FIGHTING(ch); } else { send_to_char("Bearhug who?\r\n", ch); return; } } if (vict == ch) { send_to_char("Aren't we funny today...\r\n", ch); return; } percent = ((10 - (GET_AC(vict) / 10)) << 1) + number(1, 101); prob = GET_SKILL(ch, SKILL_BEARHUG); if (MOB_FLAGGED(vict, MOB_NOBEARHUG)) { percent = 101; } if (percent > prob) { damage(ch, vict, 0, SKILL_BEARHUG); } else damage(ch, vict, GET_LEVEL(ch) << 1, SKILL_BEARHUG); WAIT_STATE(ch, PULSE_VIOLENCE * 3); }
This function was actually copied from do_kick, with all references to kicks
changed so that they refer to bearhugs. The third-to-last line was modified
so that it inflicts four times the damage that the kick would, and a clause
was inserted so that mobs who shouldn't be bearhugged cannot be bearhugged.
Close "act.offensive.c" and open "spell_parser.c". Go down to about line
37 and find the definition for something called spells, which contains the
spell and skill names. Go down to about line 130, where the unused skills
start, find the following line:
"!UNUSED!", "!UNUSED!", "!UNUSED!", "!UNUSED!", "!UNUSED!", /* 145 */
Change the first element in this line to "bearhug", as follows:
"bearhug", "!UNUSED!", "!UNUSED!", "!UNUSED!", "!UNUSED!", /* 145 */
Now, go down to the declaration of skills at the end of the function
mag_assign_spells, at about line 1015:
skillo(SKILL_BACKSTAB); skillo(SKILL_BASH); skillo(SKILL_HIDE); skillo(SKILL_KICK); skillo(SKILL_PICK_LOCK); skillo(SKILL_PUNCH); skillo(SKILL_RESCUE); skillo(SKILL_SNEAK); skillo(SKILL_STEAL); skillo(SKILL_TRACK);
At the end of this list, insert the following line: skillo(SKILL_BEARHUG);
Now, open "class.c", and go down to the end of the function init_spell_levels,
and find the section we added for the Rangers' spells and skills, about lines
562 through 574. Insert the following line to make the Bearhug skill available
to Rangers level 12 or above: spell_level(SKILL_BEARHUG, CLASS_RANGER, 12);
Finally, we need to add the messages sent to players when this skill is
attempted. Close "class.c", and go into the "lib" directory containing the
game data library, and from the "misc" directory contained there open the
file "messages". Note that the entries here apply not just to skill and spell
attacks, but to regular attacks and non-player-character attacks as well.
Also note that a given attack can have more than one block of messages associated
with it.
Each block of messages is arranged in a specific order. It starts with the
letter "M" alone on a line. The next line has a space followed by the skill
number, in this case 141, and then twelve message lines. These twelve lines
are divided into four groups of three messages. The first group is used if
the attack kills the victim. The second group is used when the attack misses.
The third group is used when the attack connects but is not a fatal strike.
The fourth group is used when the attacker is foolish enough to try using
these feeble skills against a character at the God levels. Within each group,
the first message is seen by the victim, the second by the attacker, and
the third by anyone else in the room. Each block is followed by a blank line,
and the file is ended by a line with a dollar sign on it. Lines which start
with an asterisk are comments.
For our bearhug, we will only use one block of messages. At about line 302,
you will find comments marking the start of the regular attacks. The last
group of messages above this point is the second block of messages for the
kick. Between the end of the kick messages and the comment lines, insert
the following block:
*Bearhug M 141 Your bearhug crushes $N to death! You feel your ribs break as $n bearhugs you to death! You hear the sound of bones snapping as $n crushes $N to death with a bearhug! You try to bearhug $N, but topple over as $E slips out of the way. You sidestep a bearhug from $n, who falls on $s face. $N dodges a bearhug from $n, who ends up flat on the floor. You squeeze the breath out of $N with your bearhug! $n's bearhug squeezes your lungs empty! $n squeezes the wind out of $N with a powerful bearhug! You try to bearhug $N but $s body is like solid rock! You grin as $n tries to bearhug you, but can't seem to apply any pressure. $n tries to bearhug $N, but $N doesn't seem to feel it.
Now close "message", delete any compiler object files you might have in your
"src" directory from previous compiles, and make or re-make the program.
If everything is in order, your Rangers should now have full use of the Bearhug
at and above level 12.
That was painless enough. Now let's see about adding a spell.