FlCodebase3.1/
FlCodebase3.1/bounty/
FlCodebase3.1/challenge/
FlCodebase3.1/clans/
FlCodebase3.1/gods/
FlCodebase3.1/mobprogs/
FlCodebase3.1/player/
FlCodebase3.1/savemud/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Envy Diku Mud, you must comply with   *
 *  the original Diku license in 'license.doc', the Merc license in        *
 *  'license.txt', as well as the Envy license in 'license.nvy'.           *
 *  In particular, you may not remove either of these copyright notices.   *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  * 
 *                                                                         *
 *      ROM 2.4 is copyright 1993-1998 Russ Taylor                         *
 *      ROM has been brought to you by the ROM consortium                  *
 *          Russ Taylor (rtaylor@hypercube.org)                            *
 *          Gabrielle Taylor (gtaylor@hypercube.org)                       *
 *          Brian Moore (zump@rom.org)                                     *
 *      By using this code, you have agreed to follow the terms of the     *
 *      ROM license, in the file Rom24/doc/rom.license                     *
 *                                                                         *
 * Code Adapted and Improved by Abandoned Realms Mud                       *
 * and Aabahran: The Forsaken Lands Mud by Virigoth                        *
 *                                                                         *
 * Continued Production of this code is available at www.flcodebase.com    *
 ***************************************************************************/

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "merc.h"
#include "magic.h"
#include "recycle.h"
#include "interp.h"

extern void cast_new( CHAR_DATA *ch, char *argument, bool iscommune, bool fIgnorePos );
void do_tune( CHAR_DATA *ch, char *argument)
{
    char arg[MIL], arg2[MIL];
    OBJ_DATA *obj;
    AFFECT_DATA *paf;
    int chance, loc_var, result, fail = 25, bonus = 0, stat_chg, added, level = ch->level, sn = skill_lookup("tune");
    bool stop = FALSE, old_found = FALSE;
    argument = one_argument( argument, arg);
    argument = one_argument( argument, arg2);
    if (ch->fighting != NULL)
    {
        send_to_char("Shouldn't you be concentrating on the battle instead?\n\r",ch);
        return;
    }
    if (arg[0] == '\0' || (chance = get_skill(ch,sn)) == 0)
    {
        send_to_char("You put your lips together and whistle a tune.\n\r",ch);
        return;
    }
    if (ch->mana < 100)
    {
        send_to_char("You feel too weak to forge right now.\n\r", ch);
        return;
    }
    if ((obj = get_obj_list(ch,arg,ch->carrying)) == NULL || CAN_WEAR(obj,ITEM_WEAR_TATTOO))
    {
        send_to_char("You don't have that instrument.\n\r",ch);
        return;
    }
    if (obj->wear_loc != -1)
    {
        send_to_char("You must carry the instrument you wish to tune.\n\r",ch);
        return;
    }
    if (obj->item_type != ITEM_INSTRUMENT)
    {
        send_to_char("How do you expect to tune that?\n\r",ch);
	  return;
    }
    WAIT_STATE(ch, skill_table[skill_lookup("tune")].beats);
    if (!IS_IMMORTAL(ch))
    	ch->mana -= 100;
    if (IS_IMMORTAL(ch) && arg2[0] != '\0')
    {
	  if (!is_number(arg2))
	  {
	      send_to_char("Must be numeric.\n\r",ch);
	      return;
	  }	
        stat_chg = atoi(arg2);
        if( (stat_chg <= 0) || (stat_chg > 5) )     
        {
            send_to_char("Must be numeric 1 to 5.\n\r",ch);
            return;
        }	
    }
    else     //checks what stat it'll change
        stat_chg = number_range(1, 5);
    if (!obj->enchanted)
    {
        for ( paf = obj->pIndexData->affected; paf != NULL; paf = paf->next )
        {
            if ( paf->location == APPLY_HIT || paf->location == APPLY_MANA )
            {
                bonus = paf->modifier;
		if (bonus > 24)
		    stop = TRUE;
                fail += 2 * (bonus * bonus)/25;
            }
            else if (paf->location == APPLY_DAMROLL || paf->location == APPLY_HITROLL)
            {
                bonus = paf->modifier;
		if (bonus > 4)
		    stop = TRUE;
                fail += 3 * (bonus * bonus);
            }
            else if (paf->location > APPLY_STR && paf->location < APPLY_CON )
            {
                bonus = paf->modifier;
		if (bonus > 4)
		    stop = TRUE;
                fail += 2 * (bonus * bonus);
            }
            else
                fail += 25;
        }
    }
    for ( paf = obj->affected; paf != NULL; paf = paf->next )
    {
        if ( paf->location == APPLY_HIT || paf->location == APPLY_MANA )
        {
            bonus = paf->modifier;
	    if (bonus > 24)
		stop = TRUE;
            fail += 2 * (bonus * bonus)/25;
        }
        else if (paf->location == APPLY_DAMROLL || paf->location == APPLY_HITROLL)
        {
            bonus = paf->modifier;
	    if (bonus > 4)
		stop = TRUE;
            fail += 3 * (bonus * bonus);
        }
        else if (paf->location > APPLY_STR && paf->location < APPLY_CON )
        {
            bonus = paf->modifier;
	    if (bonus > 4)
	    	stop = TRUE;
            fail += 2 * (bonus * bonus);
        }
        else
            fail += 25;
    }
    fail -= level;
    if (obj->level > level)
	fail += obj->level - level;
    if (IS_OBJ_STAT(obj,ITEM_BLESS))
        fail -= 15;
    if (IS_OBJ_STAT(obj,ITEM_GLOW))
        fail += 5;
    fail = URANGE(5,fail,95);
    result = number_percent();
    if (result < (fail / 5) || chance < fail/4)
    {
        act("$p is ruined from excessive tuning!",ch,obj,NULL,TO_ALL);
        extract_obj(obj);
        return;
    }
    if (result < (fail / 2) || chance < fail/2 || stop)
    {
        AFFECT_DATA *paf_next;
        act("$p goes completely out of tune.",ch,obj,NULL,TO_ALL);
        obj->enchanted = TRUE;
        for (paf = obj->affected; paf != NULL; paf = paf_next)
        {
            paf_next = paf->next;
            free_affect(paf);
        }
        obj->affected = NULL;
        REMOVE_BIT(obj->extra_flags,ITEM_GLOW);
        REMOVE_BIT(obj->extra_flags,ITEM_HUM);
        REMOVE_BIT(obj->extra_flags,ITEM_MAGIC);
        return;
    }
    if ( result <= fail || result > chance)
    {
        send_to_char("Nothing seemed to happen.\n\r",ch);
        return;
    }
    act("$p is more finely tuned.",ch,obj,NULL,TO_ALL);
    SET_BIT(obj->extra_flags, ITEM_MAGIC);   
    if (obj->level < LEVEL_HERO - 1)
        obj->level = UMIN(LEVEL_HERO - 1,obj->level + 1);
    if (!obj->enchanted)
    {
	AFFECT_DATA *af_new;
	obj->enchanted = TRUE;
	for (paf = obj->pIndexData->affected; paf != NULL; paf = paf->next) 
	{
	    af_new = new_affect();
	    af_new->next = obj->affected;
	    obj->affected = af_new;
	    af_new->where	= paf->where;
	    af_new->type 	= UMAX(0,paf->type);
	    af_new->level	= paf->level;
	    af_new->duration	= paf->duration;
	    af_new->location	= paf->location;
	    af_new->modifier	= paf->modifier;
	    af_new->bitvector	= paf->bitvector;
	}
    }
//*************Blah blah blah
    switch (stat_chg)
    {
            // 1 = int, 2 = wis, 3 = dex, 4 = hp, 5 = mana
            case (1):        
 	        loc_var		= APPLY_INT;
    		added 		= 1;
               	break;
            case (2):        
	        loc_var		= APPLY_WIS;
    		added 		= 1;
                break;     
            case (3):        
	        loc_var		= APPLY_DEX;
    		added 		= 1;
                break;        
            case (4):        
	        loc_var		= APPLY_HIT;
    		added 		= 5;
                break;        
            case (5):        
	        loc_var		= APPLY_MANA;
    		added 		= 5;
                break;
            default:
	       send_to_char("Something is admist, notify an IMP!\n\r",ch);
	       return;
    }
    for ( paf = obj->affected; paf != NULL; paf = paf->next )
    {
	if (paf->location == loc_var)
	{
	    paf->modifier += added;
	    old_found = TRUE;
	}
    }
//adding stats in 
    if (!old_found)
    {
	paf = new_affect();
	paf->where	= TO_OBJECT;
	paf->type	= sn;
	paf->level	= level;
	paf->duration	= -1;
	paf->location	= loc_var;
	paf->modifier	= added;
	paf->bitvector  = 0;
    	paf->next	= obj->affected;
    	obj->affected	= paf;
    }
    check_improve(ch,sn,TRUE,1);
}

void do_ventriloquate( CHAR_DATA *ch, char *argument)
{
    char buf1[MSL], buf2[MSL], speaker[MIL];
    CHAR_DATA *vch;
    int chance, sn = skill_lookup("ventriloquate");
    argument = one_argument( argument, speaker );
    if ( (chance = get_skill(ch,sn)) == 0)
    {
        send_to_char("You don't know how to throw your voice.\n\r",ch);
        return;
    }
    if (ch->mana < 5)   
    {
        send_to_char("Rest up first.\n\r", ch);
        return;     
    }
    if (speaker[0] == '\0' || argument[0] == '\0')
    {
        send_to_char("Syntax: ventriloquate <speaker> <message>\n\r",ch);
        return;
    }
    ch->mana -= 5;
    WAIT_STATE(ch, skill_table[sn].beats);
    if (chance < number_percent())
    {
	send_to_char("You failed.\n\r",ch);
	return;
    }
    sprintf( buf1, "%s says '`#%s``'\n\r", speaker, argument );
    sprintf( buf2, "Someone makes %s say '`#%s``'\n\r", speaker, argument );
    buf1[0] = UPPER(buf1[0]);
    for ( vch = ch->in_room->people; vch != NULL; vch = vch->next_in_room )
        if (!is_exact_name( speaker, vch->name) && IS_AWAKE(vch))
            send_to_char( saves_spell(ch->level,vch,DAM_OTHER,skill_table[sn].spell_type) ? buf2 : buf1, vch );
}

void do_feed( CHAR_DATA *ch, char *argument)
{
    CHAR_DATA *victim;
    char arg[MIL], arg2[MIL];
    OBJ_DATA *obj;
    argument = one_argument( argument, arg);
    argument = one_argument( argument, arg2);
    if (arg[0] == '\0' || arg2[0] == '\0')
    {
        send_to_char("Feed who what?\n\r",ch);
        return;
    }
    if ((victim = get_char_room(ch, NULL, arg)) == NULL)
    {
        send_to_char("They aren't here.\n\r",ch);
        return;
    }
    if ((obj = get_obj_list(ch,arg2,ch->carrying)) == NULL )
    {
        send_to_char("You don't have that item.\n\r",ch);
        return;
    }
    if (obj->wear_loc != -1)
    {
        send_to_char("You must carry the item you wish to feed.\n\r",ch);
        return;
    }
    if (obj->item_type != ITEM_FOOD
	&& obj->item_type != ITEM_DRINK_CON)
    {
        send_to_char("You can't feed someone with that.\n\r",ch);
	  return;
    }
    if (victim->master != ch && !is_same_group(ch,victim))
    {
	act("$N	isn't following you.",ch,NULL,victim,TO_CHAR);
	return;
    }
    if (!IS_AWAKE(victim))
    {
	act("$N is sleeping at the moment.",ch,NULL,victim,TO_CHAR);
	return;
    }
    if (!can_see_obj( victim, obj ) )
    {
        sendf(ch, "%s can't see it.\n\r", PERS(victim,ch));
        return;
    }
    if ( obj->value[3] != 0 ){
      send_to_char("You cannot feed someone with dangerous goods.\n\r", ch);
      return;
    }
/*    if (!IS_NPC(victim))
    {
	act("$N can do that $Mself.",ch,NULL,victim,TO_CHAR);
	return;
    } */
    if (IS_NPC(victim) && victim->pIndexData->vnum == MOB_VNUM_PHOENIX && victim->practice > 20)
    {
	act("$N isn't hungry right now.",ch,NULL,victim,TO_CHAR);
	return;
    }
    WAIT_STATE2(ch, PULSE_VIOLENCE / 2);
    act("$n feeds $N with $p.",ch,obj,victim,TO_NOTVICT);
    act("$n feeds you with $p.",ch,obj,victim,TO_VICT);
    act("You feed $N with $p.",ch,obj,victim,TO_CHAR);
    /* obj_to_ch unlike obj_to_char omits can_Carry checks so the item will always get moved */
    obj_from_char(obj);
    obj_to_ch(obj,victim);

    if (obj->item_type == ITEM_DRINK_CON){
      do_drink(victim, obj->name);

      /* obj_to_ch omits can_Carry checks so the item will always get moved */
      obj_from_char(obj);
      obj_to_ch(obj,ch);
    }
    else if (obj->item_type == ITEM_FOOD)
      do_eat(victim,obj->name);
}

void do_ignite( CHAR_DATA *ch, char *argument)
{
    CHAR_DATA *victim;
    int value;
    if (!IS_NPC(ch))
    {
	send_to_char("Huh?\n\r",ch);
	return;
    }
    if (ch->pIndexData->vnum != MOB_VNUM_PHOENIX)
	return;
    if ((victim = ch->master) == NULL)
	return;
    if (victim->level < ch->level/2)
    {
	act("$n tries to ignite but is too inexperienced.",ch,NULL,NULL,TO_ROOM);
	return;
    }
    if (ch->hit < ch->max_hit/2)
    {
	act("$n tries to ignite but looks too hurt.",ch,NULL,NULL,TO_ROOM);
	return;
    }
    act("$n ignites into a ball of energy, and combines with $N!",ch,NULL,victim,TO_NOTVICT);
    act("$n ignites into a ball of energy, and combines with you!",ch,NULL,victim,TO_VICT);
    value = ((ch->level*ch->hit)/4)/victim->level;
    victim->hit = UMIN(victim->max_hit, victim->hit + value);
    update_pos(victim);
    char_from_room(ch);
    extract_char(ch,TRUE);
}

/* checks for dreamprobe link between the two chars */
bool dprobe_check(CHAR_DATA* ch, CHAR_DATA* victim){
  AFFECT_DATA* dprobe;

  if (ch == NULL || victim == NULL)
    return FALSE;
  else if (ch->in_room == victim->in_room)
    return FALSE;
  else if ( (dprobe = affect_find(ch->affected, gsn_dreamprobe)) != NULL
	    && dprobe->has_string
	    && !IS_NULLSTR(dprobe->string)
	    && !str_cmp(dprobe->string, victim->name))
    return TRUE;
  else
    return FALSE;
}
  
/* psi dreamwalk ability */
void do_dreamwalk( CHAR_DATA *ch, char *argument){
  CHAR_DATA* victim;
  AFFECT_DATA* paf;
  char buf[MIL];
  const int cost = 15;
  int sn = 0;

  const int gsn_omen		= skill_lookup("omen");
  const int gsn_deathmare	= skill_lookup("deathmare");
  const int gsn_dreamprobe	= skill_lookup("dreamprobe");
  const int gsn_mindmelt	= skill_lookup("mindmelt");

  if (get_skill(ch, gsn_telepathy) < 2){
    send_to_char("Huh?\n\r", ch);
    return;
  }
  else if (IS_NULLSTR(argument)){
    send_to_char("Dreamwalk in who's dream?\n\r", ch);
    return;
  }

/* VICTIM CHOICE */
  if ( (victim = get_char_world(ch, argument)) != NULL
       && (ch->in_room == victim->in_room || dprobe_check(ch, victim))
       ){
    AFFECT_DATA af;
    if (is_safe(ch, victim)){
      return;
    }
    else if (IS_AWAKE(victim)){
      send_to_char("They must be sleeping first.\n\r", ch);
      return;
    }
    else if (IS_NPC(victim) || IS_UNDEAD(victim) || IS_DEMON(victim)){
      send_to_char("Try as you might you do not sense any dreams.\n\r", ch);
      return;
    }
    else if (ch->in_room != victim->in_room
	     && victim
	     && !dprobe_check(ch, victim)){
      send_to_char("They aren't here.\n\r", ch );
      return;
    }
    affect_strip(ch, gen_dreamwalk );
    act("You tune your senses into $N's dream.", ch, NULL, victim, TO_CHAR);
    act("$n touches $N's head and chants softly.", ch, NULL, victim, TO_ROOM);

    af.type	= gen_dreamwalk;
    af.level	= 0; //used to keep track of the messages
    af.duration	= 4;
    af.where	= TO_AFFECTS;
    af.bitvector = 0;
    af.location	= APPLY_NONE;
    af.modifier = 0;//sn of the spell to cast
    paf = affect_to_char(ch, &af);
    string_to_affect(paf, victim->name );
    return;
  }
  /* SPELL CHOICE */
  sn = find_spell(ch,argument);
  if (sn != gsn_omen 
      && sn != gsn_deathmare 
      && sn != gsn_mindmelt
      && sn != gsn_dreamprobe){
    send_to_char("You can only dreamwalk omen, deathmare, dreamprobe and mind melt.\n\r", ch);
    return;
  }
  else if ( (paf = affect_find(ch->affected, gen_dreamwalk)) == NULL
	    || !paf->has_string
	    || IS_NULLSTR(paf->string)){
    send_to_char("You must enter someone's dream first.\n\r", ch);
    return;
  }
  else if (ch->mana < cost){
    send_to_char("You cannot concentrate sufficiently.\n\r", ch);
    return;
  }
  WAIT_STATE(ch, 2 * PULSE_VIOLENCE );
  if (number_percent() < get_skill(ch, gsn_telepathy)){
    ch->mana -= cost;
    check_improve(ch, gsn_telepathy, TRUE, 1);
  }
  else{
    ch->mana -= cost / 2;
    check_improve(ch, gsn_telepathy, FALSE, 1);
    send_to_char("You failed to enter their dreams.\n\r", ch );
    return;
  }

  /* compose the spell target */
  sprintf(buf, "\'%s\' %s", skill_table[sn].name, paf->string);

  /* cast the spell */
  a_yell( ch, victim );
  cast_new( ch, buf, FALSE, TRUE );
}

/* wrap around telepathic tell */
void do_telepathy( CHAR_DATA* ch, char* argument ){
  bool fTele = IS_TELEPATH( ch );

  if (get_skill( ch, gsn_telepathy) < 2){
    send_to_char("Huh?\n\r", ch );
    return;
  }

  if (!fTele)
    SET_BIT(ch->comm, COMM_TELEPATH);
  do_tell( ch, argument );
  if (!fTele)
    REMOVE_BIT(ch->comm, COMM_TELEPATH);
}


//shows tracks in the room
void show_tracks( CHAR_DATA* ch, ROOM_INDEX_DATA* in_room ){
  AFFECT_DATA* paf;
  char* age_str;
  long age;

  if (ch->class != gcn_ranger)
    return;
  else if ( (paf = affect_find(ch->affected, gsn_tracker)) == NULL)
    return;
  else if (paf->modifier < 1 || paf->modifier >= MAX_ANATOMY)
    return;

  age = (mud_data.current_time - in_room->tracks[paf->modifier]) / 30;

  switch( age ){
  case -1:
  case 0:	age_str = "new";	break;
  case 1:	age_str = "fresh";	break;
  case 2:	age_str = "recent";	break;
  case 3:
  case 4:	age_str = "old";	break;
  default:
    return;
  }
  sendf(ch, "You spot some %s spur.\n\r", age_str );
}

/* new ranger skills */
void do_track( CHAR_DATA* ch, char* argument ){
  AFFECT_DATA af, *paf;

  const int lag = skill_table[gsn_tracker].beats;
  const int cost = skill_table[gsn_tracker].min_mana;

  int skill = get_skill(ch, gsn_tracker );
  int anat;

  if (skill < 2){
    send_to_char("Huh?\n\r", ch);
    return;
  }
  else if (IS_NULLSTR(argument)){
    send_to_char("Track footprints of which type?\n\r", ch);
    return;
  }
  else if ( (anat = anatomy_lookup( argument )) < 1){
    int i;
    send_to_char("You may track:\n\r", ch );

    for (i = 1; i < MAX_ANATOMY && !IS_NULLSTR(anatomy_table[i].name); i++){
      sendf(ch, "%s\n\r", anatomy_table[i].name );
    }
    return;
  }
  else if (ch->mana < cost){
    send_to_char("You cannot remember the spur.\n\r", ch);
    return;
  }

  ch->mana -= cost;
  WAIT_STATE(ch, lag );

  if (number_percent() > skill){
    send_to_char("You failed to recall the shape of the footprint.\n\r", ch);
    check_improve(ch, gsn_tracker, FALSE, 5);
    return;
  }
  check_improve(ch, gsn_tracker, TRUE, 1);

  if ( (paf = affect_find( ch->affected, gsn_tracker )) != NULL){
    paf->modifier = anat;
    paf->duration = number_fuzzy( ch->level / 6 );
  }
  else{
    af.type	= gsn_tracker;
    af.level	= ch->level;
    af.duration = number_fuzzy( ch->level / 6 );
    af.where	= TO_AFFECTS;
    af.bitvector= 0;
    af.location = APPLY_NONE;
    af.modifier = anat;
    affect_to_char(ch, &af);
  }
  sendf( ch, "You begin to look for %s spur.\n\r", anatomy_table[anat].name );
}

  
/* causes any ranger pets to return to him */
void do_pack_call( CHAR_DATA* ch, char* argument ){
  CHAR_DATA* pet;
  AFFECT_DATA af;
  const int gsn_pack_call = skill_lookup("pack call");
  int skill = get_skill(ch, gsn_pack_call );

  const int lag = skill_table[gsn_pack_call].beats;
  const int cost = skill_table[gsn_pack_call].min_mana;
  bool fSet = FALSE;

  if (skill < 2){
    send_to_char("Huh?\n\r", ch);
    return;
  }
  else if (is_affected(ch, gsn_pack_call)){
    send_to_char("You're not ready to call on your pack yet.\n\r", ch);
    return;
  }
  else if (ch->mana < cost){
    send_to_char("You can't manage to concentrate.\n\r", ch);
    return;
  }
  else
    ch->mana -= cost;
  WAIT_STATE2(ch, lag );

  if (number_percent() < 4 * skill / 5){
    send_to_char("You failed to call on your pack.\n\r", ch);
    check_improve(ch, gsn_pack_call, FALSE, 5);
    return;
  }
  else
    check_improve(ch, gsn_pack_call, TRUE, 5);
  send_to_char("You call on your pack.\n\r", ch);
  act("$n calls on $s pack.", ch, NULL, NULL, TO_ROOM);

  //we generate paths for each creature found.
  for (pet = char_list; pet; pet = pet->next){
    if (!IS_NPC(pet) || !IS_AFFECTED(pet, AFF_CHARM) || pet->master == NULL)
      continue;
    else if (pet->pIndexData->vnum != MOB_VNUM_RAVEN
	     && pet->pIndexData->vnum != MOB_VNUM_LEOPARD
	     && pet->pIndexData->vnum != MOB_VNUM_WYVERN
	     && pet->pIndexData->vnum != MOB_VNUM_DISPLACER
	     && pet->pIndexData->vnum != MOB_VNUM_MAMMOTH)
      continue;
    else if (pet->in_room == ch->in_room)
      continue;
    if (set_path( pet, pet->in_room, ch->in_room, 256, NULL) < 1){
      sendf(ch, "%s could not find its way here.\n\r", PERS2(pet));
    }
    else
      fSet = TRUE;
  }
  if (fSet){
    af.type = gsn_pack_call;
    af.level = ch->level;
    af.duration = 12;
    af.where = TO_AFFECTS;
    af.bitvector = 0;
    af.location = APPLY_NONE;
    af.modifier = 0;
    affect_to_char(ch, &af);
  }
}

/* faster reload in combat at 3/4 damage */
void do_rapid_fire( CHAR_DATA* ch, char* argument ){
  AFFECT_DATA af;
  int skill = get_skill(ch, gsn_rapid_fire );

  const int lag = skill_table[gsn_rapid_fire].beats;
  const int cost = skill_table[gsn_rapid_fire].min_mana;


  if (skill < 2){
    send_to_char("Huh?\n\r", ch);
    return;
  }
  else if (is_affected(ch, gsn_rapid_fire)){
    send_to_char("You're already prepared to quicky fire in combat.\n\r", ch);
    return;
  }
  else if (ch->mana < cost){
    send_to_char("You can't manage to concentrate.\n\r", ch);
    return;
  }
  else
    ch->mana -= cost;
  WAIT_STATE2(ch, lag );

  af.type	= gsn_rapid_fire;
  af.duration	= number_fuzzy(ch->level / 10 );
  af.level	= ch->level;
  af.where	= TO_AFFECTS;
  af.bitvector	= 0;
  af.modifier	= -1;
  af.location	= APPLY_DEX;
  affect_to_char( ch, &af);
  send_to_char("You will now forsake aim for speed in ranged combat.\n\r", ch);
}