Adding Affection Spells (chapter 3, part 3)
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
53
Close "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", "\n" };
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.