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.