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 141Next, 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.
The Rangers' weapon of choice should be a bow, and it would be nice if they did not have to worry about where to get one. There are two bows described in the object files included with the game, a hunting bow, which says it is not recommended for combat, and a golden bow, which is a special bow which must be earned in area 50. This being the case, we will build a special "Archer Bow" for their use, and code a spell that lets them conjure one. Open the world object file "30.obj", and find the top of object #3030, the torch, at about line 177. Between the torch and the flail above it, insert the following section: #3026 bow archer~ an Archer bow~ A green Archer bow is here.~ ~ 5 dmnopq 8193 0 3 5 11 3 30 20 E bow archer~ This is an Archer bow, custom wrought for the Rangers' guild. An excellent weapon, but useful solely to members of that guild. Every good Ranger should learn to make and use one. ~This will create the Archer bow, and make it a weapon which can be wielded only by Rangers, cannot be sold to any shopkeeper and cannot be "donated". After all, the Rangers are all supposed to be able to make one themselves. At this point, you might also want to assign this weapon to the Huntress guildguard we built in the previous chapter. Close "30.obj" and open "30.zon". At about line 74, there is a pair of lines which places the guildguard and assigns her a sword:
M 0 3029 1 3070 Ranger Guard E 1 3022 100 16 Long SwordReplace these with the following lines: M 0 3029 1 3070 Ranger Guard E 1 3026 999 16 Archer BowFurther down, let's do the same for the Rangers' waitress. The following group of lines, at about line 100, creates the waitress and gives her certain items, including her weapon:
M 0 3030 1 3071 Rangers' Waitress G 1 3003 100 Firebreather G 1 3004 100 Local Bottle E 1 3021 100 16 Small SwordReplace these lines with the following: M 0 3030 1 3071 Rangers' Waitress G 1 3003 100 Firebreather G 1 3004 100 Local Bottle E 1 3026 999 16 Archer BowThese two characters will now be armed with bows instead of swords. This is not actually necessary, but under the circumstances that these characters are part of the Rangers guild it is a nice touch. To allow for players to have the bows and still have them equipped on the two non-player characters, the "maximum existing" values for the weapons have been boosted here from 100 to 999. Close "30.zon" and open "30.mob". We should make one last alteration to their descriptions here, and they will be all set. At about line 258, change the Huntress's short description line, the third line for mobile #3029 to the following: A huntress plays with her bow as she guards the entrance. Now, at about line 274, change the long description for the waitress, mobile #3030, to the following: This 14-year-old girl might fool a member of another guild, but you know that she's one tough cookie. She very friendly, a little flirtatious even, but woe to anyone who angers her. She specializes in punctured egos and Swiss cheese corpses. Once that is done, close "30.mob" and we will start on adding the spell for the Rangers to create this bow for their own use. Open "spells.h" and find the "SPELL_x" definitions, starting at line 39. After the last one, at around line 90, insert the following line: #define SPELL_CREATE_BOW 52Close "spells.h" and open "constants.c". Jump down to about line 701, where you will find the beginning of the spell_wear_off_msg string array constant:
const char *spell_wear_off_msg[] = { "RESERVED DB.C", /* 0 */ "You feel less protected.", /* 1 */ "!Teleport!", "You feel less righteous.", "You feel a cloak of blindness disolve." "!Burning Hands!", /* 5 */ "!Call Lightning", "You feel more self-confident.", "You feel your strength return.", "!Clone!", "!Color Spray!", /* 10 */ "!Control Weather!", "!Create Food!", "!Create Water!", "!Cure Blind!", "!Cure Critic!", /* 15 */ "!Cure Light!", "You feel more optimistic.", "You feel less aware.", "Your eyes stop tingling.", "The detect magic wears off.",/* 20 */ "The detect poison wears off.", "!Dispel Evil!", "!Earthquake!", "!Enchant Weapon!", "!Energy Drain!", /* 25 */ "!Fireball!", "!Harm!", "!Heal!", "You feel yourself exposed.", "!Lightning Bolt!", /* 30 */ "!Locate object!", "!Magic Missile!", "You feel less sick.", "You feel less protected.", "!Remove Curse!", /* 35 */ "The white aura around your body fades.", "!Shocking Grasp!", "You feel less tired.", "You feel weaker.", "!Summon!", /* 40 */ "!Ventriloquate!", "!Word of Recall!", "!Remove Poison!", "You feel less aware of your suroundings.", "!Animate Dead!", /* 45 */ "!Dispel Good!", "!Group Armor!", "!Group Heal!", "!Group Recall!", "Your night vision seems to fade.", /* 50 */ "Your feet seem less boyant.", "!UNUSED!" };While "create bow" isn't the type of spell that needs to wear off, we should nonetheless make a note of our new spell here. At about line 754, between the "Your feet seem less boyant.", line, which is the entry for the Waterwalk spell, and the "!UNUSED!" which follows it, insert the following line: "!Create Bow!",Now close "constants.c" and open "spell_parser.c", and once again you will find spells defined starting on line 37. At about line 94, you will find the start of the "!UNUSED!", entries:
"group recall", "infravision", /* 50 */ "waterwalk", "!UNUSED!", "!UNUSED!", "!UNUSED!", "!UNUSED!",Change the first one to "create bow", as follows:
"group recall", "infravision", /* 50 */ "waterwalk", "create bow", "!UNUSED!", "!UNUSED!", "!UNUSED!",Starting at about line 856, you will find the function mag_assign_spells, which registers spells and skills for player use. As with the "bearhug" skill, we need to add an entry for our "create bow" spell. This time, however, we are going to enter it as one of the magic abilities. Also, there is already a template in place for creating items, a group of functions which already are used by the "create food" spell, and which can be modified to also handle the "create bow" spell. Around line 1000, you will find the spello function calls for the last of the "castable" spells and the beginning of the non-castable spells and the skills:
spello(SPELL_SENSE_LIFE, 20, 10, 2, POS_STANDING, TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS); /* NON-castable spells should appear here */ spello(SPELL_IDENTIFY, 0, 0, 0, 0, TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_ROOM, FALSE, MAG_MANUAL);Between the last castable spell and the first non-castable spell, insert the following lines:
spello(SPELL_CREATE_BOW, 30, 5, 4, POS_STANDING, TAR_IGNORE, FALSE, MAG_CREATIONS);This registers the spell with the spell interpreter, and sets it to use the existing MAG_CREATIONS functions. We now need to modify these functions to handle our new spell. Close "spell_parser.c" and open "magic.c". The function mag_creations starts at about line 1092. About 9 lines down is the following switch statement:
switch (spellnum) { case SPELL_CREATE_FOOD: z = 10; break; default: send_to_char("Spell unimplemented, it would seem.\r\n", ch); return; break; }Right before the default case, we need to add the following lines to implement the "create bow" spell:
case SPELL_CREATE_BOW: z = 3026; break;Now close "magic.c" and open "class.c". We need to enable the new spell for the Rangers to learn and use, so go down once again to the Rangers section of init_spell_levels at about line 562, and add the following line: spell_level(SPELL_CREATE_BOW, CLASS_RANGER, 1);Finally, close "class.c", 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 be able to cast "create bow" at any level.
Now that the Rangers can bearhug their enemies and can create and use their special weapon, let's do something really special. Because of their close relationship with nature, the Rangers have an understanding with the plants. This goes to their advantage in combat outdoors, as they can call upon these plants to entangle their enemies. Or at least, they will be able to, once we add this effect and the spell to use it. The Affect bitvectors are defined in "structs.h", starting at about line 182. After these, at about line 204, insert the following line: #define AFF_TANGLED (1 << 22) /* (R) Char is tangled */Now go up to the mobile bitvector flags, and at about line 153 insert the following line below the MOB_NOBEARHUG entry: #define MOB_NOENTANGLE (1 << 19) /* Mob can't be entangled */Next, close "structs.h" and open "spells.h". As with "create bow", we now need to go through the steps to make this a viable spell. At about line 91 we will again find the end of the spells. Insert the following line after the entry for SPELL_CREATE_BOW that we added before: #define SPELL_ENTANGLE 53Close "spells.h" and open "constants.c". Again, find the end of spell_wear_off_msg. Since this spell really does wear off, it becomes necessary, rather than just good form, to add a wear-off message for this spell. Right below the "!CREATE BOW!", entry we added before, at about line 755, insert the following line. "You are finally free of the vines that bind.",Now move up to around line 186, and find the string array constant affected_bits, which starts around line 186:
/* AFF_x */ const char *affected_bits[] = { "BLIND", "INVIS", "DET-ALIGN", "DET-INVIS", "DET-MAGIC", "SENSE-LIFE", "WATWALK", "SANCT", "GROUP", "CURSE", "INFRA", "POISON", "PROT-EVIL", "PROT-GOOD", "SLEEP", "!TRACK", "UNUSED", "UNUSED", "SNEAK", "HIDE", "UNUSED", "CHARM", };A line needs to be added for entanglement here as well. Between the last listed effect and the carriage return entry ("\n") at about line 210, insert the following line: "TANGLED",Now close "constants.c" and open "spell_parser.c". Once again, find the next "!UNUSED!", entry in spells, which should be about line 95:
"group recall", "infravision", /* 50 */ "waterwalk", "create bow", "!UNUSED!", "!UNUSED!", "!UNUSED!",Change it to "entangle", as follows: "group recall", "infravision", /* 50 */ "waterwalk", "create bow", "entangle", "!UNUSED!", "!UNUSED!",Now go down into mag_assign_spells, and after the entry for "create bow" at about line 1006, insert an entry for "entangle":
spello(SPELL_ENTANGLE, 50, 30, 2, POS_FIGHTING, TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_AFFECTS);Unlike the "create bow", this spell is usable in combat, and takes a target of either whoever the player is fighting or another character in the room. Since there is a template for affect spells, we will use this for the purpose of casting the spell and having it wear off. However, the part of the combat routines which gives the spell its effectiveness will need to be added practically from scratch. In the meantime, we should make the spell castable first. Close "spell_parser.c" and open "magic.c". The function mag_affects starts at about line 441. The switch (spellnum) statement which determines which spell is being used starts at about line 464. Between the last entry, which should be for SPELL_WATERWALK, and the closing brace, at about line 653, insert the following section:
case SPELL_ENTANGLE: if (ROOM_FLAGGED(IN_ROOM(ch),ROOM_INDOORS)) { act("You feel something pounding up into the floor beneath you.", FALSE, victim, 0, 0, TO_CHAR); act("You hear a pounding under the floor below $n.", TRUE, victim, 0, 0, TO_ROOM); return; } if (MOB_FLAGGED(victim,MOB_NOENTANGLE) || mag_savingthrow(victim, savetype)) { act("Vines grow from the ground to entangle you, but you shrug them off.", FALSE, victim, 0, 0, TO_CHAR); act("Vines from the ground try to entangle $n, but can't get a grip.", TRUE, victim, 0, 0, TO_ROOM); return; } af[0].duration = 1 + (GET_LEVEL(ch) >> 3); if (GET_LEVEL(ch) < GET_LEVEL(victim)) af[0].duration -= 1; af[0].bitvector = AFF_TANGLED; to_vict = "Vines suddenly grow up from the ground and entangle you!"; to_room = "Vines grow up from the ground and thoroughly entangle $n."; if (GET_POS(ch) > POS_STUNNED) { if (!(FIGHTING(ch))) set_fighting(ch, victim); if (IS_NPC(ch) && IS_NPC(victim) && victim->master && !number(0, 10) && IS_AFFECTED(victim, AFF_CHARM) && (victim->master->in_room == ch->in_room)) { if (FIGHTING(ch)) stop_fighting(ch); hit(ch, victim->master, TYPE_UNDEFINED); return; } } if (GET_POS(victim) > POS_STUNNED && !FIGHTING(victim)) { set_fighting(victim, ch); if (MOB_FLAGGED(victim, MOB_MEMORY) && !IS_NPC(ch) && (GET_LEVEL(ch) < LVL_IMMORT)) remember(victim, ch); } break; The critical section of this, which is what makes it an aggressive spell, is the section which starts with if (GET_POS(ch) > POS_STUNNED) and ends with the closing brace after remember(victim, ch);. This section is actually copied directly from the damage function, and is responsible for setting the attacker and the victim to fighting each other.Now, hop back up to about line 233, where you will find the function affect_update:
void affect_update(void) { static struct affected_type *af, *next; static struct char_data *i; extern char *spell_wear_off_msg[]; for (i = character_list; i; i = i->next) for (af = i->affected; af; af = next) { next = af->next; if (af->duration >= 1) af->duration--; else if (af->duration == -1) /* No action */ af->duration = -1; /* GODs only! unlimited */ else { if ((af->type > 0) && (af->type <= MAX_SPELLS)) if (!af->next || (af->next->type != af->type) || (af->next->duration > 0)) if (*spell_wear_off_msg[af->type]) { send_to_char(spell_wear_off_msg[af->type], i); send_to_char("\r\n", i); } affect_remove(i, af); } } }Three lines above the call to affect_remove is a two-line block which tells characters who are no longer affected by something that they are no longer affected. We also want to notify others in the room that a character is no longer entangled, so between the second line of this group and the closing brace which follows, insert the following lines: if (af->bitvector == AFF_TANGLED) act("$n is free of the vines that bind.",TRUE,i,0,0,TO_ROOM);Close "magic.c" and open "class.c". Once again, go down to the Rangers section of init_spell_levels, and at about line 574, insert the following line: spell_level(SPELL_ENTANGLE, CLASS_RANGER, 13);It should also be visible to anyone who looks at an entangled character or the room that he or she is in that the character is tangled. Close "class.c" and open "act.informative.c". The function list_one_char, which starts at about line 196, is responsible for providing this information in the list of characters when the room is looked at. At about line 225, you will find the following block of lines which provide visibility for other visible effects:
if (IS_AFFECTED(i, AFF_SANCTUARY)) act("...$e glows with a bright light!", FALSE, i, 0, ch, TO_VICT); if (IS_AFFECTED(i, AFF_BLIND)) act("...$e is groping around blindly!", FALSE, i, 0, ch, TO_VICT);After these lines, insert the following: if (IS_AFFECTED(i, AFF_TANGLED)) act("...$e is entangled in vines!", FALSE, i, 0, ch, TO_VICT);The function look_at_char, which starts about line 155, is responsible for reporting a character's status when that character itself is looked at by a player. This function displays the looked-at character's description, if any, physical condition, equipment, and, to a thief or a God-level player, the character's full inventory. The entanglement aspect would be best displayed after all of these, so right before the function's closing brace at about line 193, insert the following: if (IS_AFFECTED(i, AFF_TANGLED)) act("$e is entangled in vines.", FALSE, i, 0, ch, TO_VICT);Now that the spell exists and can be used, and the effect is visible to other players, we need to implement its effects on the combat and activity routines. After all, what is the purpose of entangling a character if it doesn't hinder it? Close "act.informative.c" and open "fight.c". The first thing we are going to do is to deprive an entangled character of the ability to damage anyone, either through magic or any other means. Go down to the damage function, which starts at about line 602:
void damage(struct char_data * ch, struct char_data * victim, int dam, int attacktype) { int exp; if (GET_POS(victim) <= POS_DEAD) { log("SYSERR: Attempt to damage a corpse."); return; /* -je, 7/7/92 */ } /* peaceful rooms */ if (ch != victim && ROOM_FLAGGED(ch->in_room, ROOM_PEACEFUL)) { send_to_char("This room just has such a peaceful, easy feeling...\r\n", ch); return; } /* shopkeeper protection */ if (!ok_damage_shopkeeper(ch, victim)) return;Between the section on peaceful rooms and the section on shopkeeper protection, at about line 602, insert the following to prevent an entangled character from striking:
/* entangled */ if (IS_AFFECTED(ch, AFF_TANGLED)) { send_to_char("You struggle against your bonds...\r\n", ch); return; }Now we have to prevent entangled characters from fleeing. Close "fight.c" and open "act.offensive.c". You will find the do_flee function, declared with the ACMD macro, starting at around line 239:
ACMD(do_flee) { int i, attempt, loss; for (i = 0; i < 6; i++) { attempt = number(0, NUM_OF_DIRS - 1); /* Select a random direction */ if (CAN_GO(ch, attempt) && !IS_SET(ROOM_FLAGS(EXIT(ch, attempt)->to_room), ROOM_DEATH)) { act("$n panics, and attempts to flee!", TRUE, ch, 0, 0, TO_ROOM); if (do_simple_move(ch, attempt, TRUE)) { send_to_char("You flee head over heels.\r\n", ch); if (FIGHTING(ch)) { if (!IS_NPC(ch)) { loss = GET_MAX_HIT(FIGHTING(ch)) - GET_HIT(FIGHTING(ch)); loss *= GET_LEVEL(FIGHTING(ch)); gain_exp(ch, -loss); } if (FIGHTING(FIGHTING(ch)) == ch) stop_fighting(FIGHTING(ch)); stop_fighting(ch); } } else { act("$n tries to flee, but can't!", TRUE, ch, 0, 0, TO_ROOM); } return; } } send_to_char("PANIC! You couldn't escape!\r\n", ch); }The easiest place to implement the entanglement is at the beginning of the function, before the system even attempts to move the character. Above the start of the for loop, at about line 243, insert the following:
if (IS_AFFECTED(ch, AFF_TANGLED)) { send_to_char("You would flee, but you're all tied up...\r\n", ch); return; }Now, close "act.offensive.c" and reopen "fight.c". Let's place one more restriction on entanglement, that being that when the tangled character is no longer fighting it can no longer be entangled, since by the end of the fight either the entangler or the entangled have disengaged. The first thing we need to do is to write the function which explicitly removes the entanglement effect from a character. Go down to where the function stop_fighting begins at about line 202, and insert the following function above it:
void unentangle(struct char_data *ch) { static struct affected_type *af, *next; extern char *spell_wear_off_msg[]; for (af = ch->affected; af; af = next) { next = af->next; if (af->bitvector == AFF_TANGLED) { send_to_char(spell_wear_off_msg[af->type], ch); send_to_char("\r\n", ch); } affect_remove(ch, af); act("$n is free of the vines that bind.",TRUE,ch,0,0,TO_ROOM); } } Now, go down into stop_fighting itself. At the end of function, insert the following line above the function's closing brace, at about line 233: unentangle(ch);Finally, close "fight.c", 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 be able to cast "entangle" at or above level 13, and the spell should prevent its victim from fighting back, fleeing, or casting spells.
A social is probably the easiest command to add to the game. Only three files are involved: two source code files, and the "socials" file, located in the "misc" directory of the game data library. One of the source code files, "act.social.c", does not even require any modification, unless the social is gender-specific or contains random factors (like "insult", which is both of these). With the exception of these special socials, all social activity is managed by the do_action function which starts at about line 85 of "act.social.c". Also note that all special social functions, such as "insult", must be prototyped like any other command in "interpreter.c", in the same general vicinity of the do_bearhug function prototype we added earlier. Socials cover any and all activity which has nothing directly to do with the playing of the game itself. They basically serve as a means of expression among players, and have no effect on gameplay itself. The basic game comes with a wide variety of socials, from accuse to kiss to yodel. (Yes, that's right, yodel.) Most are affectionate, some are antagonistic, and a number are just downright pointless. Here, we are going to add three socials: caress, which will be an affectionate social which requires a "victim" to make sense; spin, which will not require a "victim" but can have one, and mumble, which takes no "victim" at all. For the sake of not repeating myself, we will add these all at once. Open "interpreter.c", and go down to the definition of the cmd_info string array constant, which should be right where we left it before, in the general neighborhood of line 197. Go down to the C's and insert the following line at around line 237: { "caress" , POS_RESTING , do_action , 0, 0 },Now, down in the M's, insert the following line at around line 357: { "mumble" , POS_SLEEPING, do_action , 0, 0 },Finally, down in the S's, insert the following line at around line 464: { "spin" , POS_STANDING, do_action , 0, 0 },Now close "interpreter.c", and open "socials" in the "misc" directory of the game data library. We can place our new socials pretty much anywhere in the file, but the two best places would be either at the end or in alphabetical order. We are going to use the latter. All socials are either 9 lines followed by a blank line, or three lines, followed by a line containing only a pound sign (#) and then a blank line. After the blank line is either the beginning of the next social or a dollar sign which marks the end of the file. Since "caress" takes an argument, it will have nine lines. The first line of any social has three elements: the command name, the "hide-flag", and a number which represents the minimum position (dead, mortally wounded, incapacitated, stunned, sleeping, resting, sitting, fighting, standing) that any victim must be. The hide-flag is a Boolean value. If it is 0, the social is visible to anyone in the room. If it is 1, the social is only visible to the character who uses it and the character who is its "victim". Since a caress is a somewhat intimate action, we will make this one private. Most such socials are only usable on characters who are awake, meaning that they are either resting or more alert, and so the minimum position value we will use is 5. (The position values can be found in structs.h, starting at about line 103.) The eight message lines contain the statements that will be shown to various characters in certain conditions. The first line is the message sent to the player who uses a social when the social has no argument. The second line contains the message sent to others in the room when the social has no argument. The third message is sent to the player using the social when the player names a target, and that target is present in the room. The fourth message is sent to others in the room under the same circumstances. The fifth message is sent to the target character. The sixth message is sent to the player who uses the social when the player supplies a target, and that target is not available. The seventh message is sent to the player who uses the social when they player supplies his or herself as a target. The eighth message is sent to others in the room under the same circumstances. A pound sign in any message line means that there is no message. There are six different dollar-sign codes which can be inserted into the messages. Each is a dollar-sign followed by a letter. If the letter is lowercase, the player using the social is referenced. If the letter is capital, the target character is referenced. The letter N inserts the character's name, an E inserts a subject pronoun (he, she, it), an S inserts a possessive pronoun (his, hers, its), and an M inserts an object pronoun (him, her, it). At about line 66, you will find the data for the "chuckle" social. We will be placing "caress" above it. Insert the following lines here: caress 1 5 Who do you wish to caress? # You caress $N gently on the cheek. $n caresses $N softly on the cheek. $n caresses you lovingly on the cheek. Your enamored isn't here. You caress your cheek as you think about your loved one. $n rubs $s cheek gently.Now, go down to about line 371, where you will find the beginning of "nibble". We will insert mumble above this one. Since this is intended for others to see, the hide-flag will be 0. Since there is no victim here, our target position flag will be 0. The first message is the one which will be seen by the player using the social. The second will be seen by others in the room. Insert the following: mumble 0 0 You mumble unintelligibly. $n mumbles something under his breath. #Finally, go down to around line 363, and insert the following entry for "spin" above the one for "spit": spin 0 8 You spin joyously in a circle. $n spins around cheerfully. You spin $N around and into your arms. $n twirls $N around and into $s arms. $n twirls you around and into $s warm embrace. You spin joyously in a circle. $n spins around cheerfully.Now, close "socials", 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, all players should now have use of the three new socials. Keep in mind that "messages", "socials", and the world files are not part of the source code, but rather are strictly data files. They are read by the game at runtime and throughout the course of the game. Changes made to them do not require recompiling the executable, they only need for the game to stop running and be restarted. You can change information in these files at any time. At this point, it would probably be a good idea for you or your builders to examine the mob files in the game data library, and apply the MOB_NOBEARHUG and MOB_NOENTANGLE flags to some of the mobiles. After all, some characters are bound to be immune. For example, the ones in the Rangers' guild will probably have the ability to suppress the vines of entanglement, although that may not be needed since they are all in indoor rooms, where the vines cannot penetrate the floor.
|