/
/*
  SillyMUD Distribution V1.1b             (c) 1993 SillyMUD Developement
 
  See license.doc for distribution terms.   SillyMUD is based on DIKUMUD
*/

#include <stdio.h>

#include "protos.h"

extern char *dirs[];
extern struct char_data *character_list;
extern struct str_app_type str_app[];
extern struct room_data *world;
extern struct dex_app_type dex_app[];
extern struct skill_data skill_info[];
extern struct index_data *obj_index;

struct hunting_data {
  char	*name;
  struct char_data **victim;
};


/*************************************/
/* predicates for find_path function */

int is_target_room_p(int room, void *tgt_room);

int named_object_on_ground(int room, void *c_data);

/* predicates for find_path function */
/*************************************/


/*
**  train:
*/

void do_train(struct char_data *ch, char *argument, int cmd)
{
  /* 
    code to allow high level characters to train low level
    characters.
     */

  
}

void do_inset(struct char_data *ch, char *argument, int cmd)
{
  /* code which allows a character to inset a stone into
     a weapon.  The stone's powers are added to the 
     weapon
     */
  struct obj_data *sword, *gem, *tmp;
  struct char_data *tmp_char;  /* dummy */
  char sp[100], sp2[100], buf[512];
  int ok=FALSE;
  struct extra_descr_data *new_descr;
  int stone_slots, sword_slots, i, j, bits, perc;

  /* get the sword and the gem from the argument */

  if (!ch->skills)
    return;

  argument_interpreter(argument, sp, sp2);

  if (!(*sp) || !(*sp2)) {
    send_to_char("You must supply a weapon and a treasure.\n\r", ch);
    return;
  }

  bits = generic_find(sp, FIND_OBJ_INV,
		      ch, &tmp_char, &sword);

  bits = generic_find(sp2, FIND_OBJ_INV,
		      ch, &tmp_char, &gem);

  /* make sure we have a gem and a sword */

  if (!sword) {
    send_to_char("Inset what?\n\r", ch);
    return;
  }

  if (!gem) {
    send_to_char("Inset what?\n\r", ch);
    return;
  }


  if ((ITEM_TYPE(sword) == ITEM_WEAPON) &&
      (ITEM_TYPE(gem) == ITEM_TREASURE)) {
    ok = TRUE;
  } else if ((ITEM_TYPE(gem) == ITEM_WEAPON) &&
	     (ITEM_TYPE(sword) == ITEM_TREASURE)) {
    tmp = gem;
    gem = sword;
    sword = tmp;
    ok = TRUE;
  }

  if (!ok) {			/* I can't beleive you fucked the act */
				/* parameters. */
    if (GET_ITEM_TYPE(sword) != ITEM_WEAPON) {
      act("$p is not a weapon.", 0, ch, sword, sword, TO_CHAR);
    } 
    if (GET_ITEM_TYPE(gem) != ITEM_TREASURE) {
      act("$p is not a gem.", 0, ch, gem, gem, TO_CHAR);
    }
    return;
  }


  if (sword->obj_flags.weight < gem->obj_flags.weight) {
    send_to_char("That weapon is too small for such a treasure.\n\r",ch);
    /*     act("That weapon is too small for such a treasure",
	   0, ch, 0, 0, TO_CHAR); */
    return;
  }
  
  /* count the effects on the stone */

  for (i=0, stone_slots=0; i < MAX_OBJ_AFFECT; i++) {
    if (gem->affected[i].location != APPLY_NONE) 
      stone_slots++;
  }
  /* count the effects on the sword */
  for (i=0, sword_slots=0; i < MAX_OBJ_AFFECT; i++) {
    if (sword->affected[i].location == APPLY_NONE) 
      sword_slots++;
  }
  /* verify space is available */

  if (stone_slots > sword_slots) {
    act("$p can't hold that many enchantments", 0, ch, sword, sword, TO_CHAR);
    return;
  }
  if (IS_OBJ_STAT(sword, ITEM_INSET)) {
    act("$p has already been inset with a gem", 0, ch, sword, sword, TO_CHAR);
    return;
  }

  /* check skill role.  Failure damages the stone and weapon */
  perc = number(1,101);
  if (perc > ch->skills[SKILL_INSET].learned) {  
    act("$n fumbles with $p and breaks it!", 0, ch, gem, gem, TO_ROOM);
    act("You fumble with $p and break it!", 0, ch, gem, gem, TO_CHAR);
    MakeScrap(ch, gem);
    return;
  }

  /* if success, add stone's effects to sword */


  act("$n insets $p into $P", 0, ch, gem , sword, TO_ROOM);
  act("You inset $p into $P", 0, ch, gem , sword, TO_CHAR);

  SET_BIT(sword->obj_flags.extra_flags, ITEM_INSET);

  /* add an extra description for the stone to the object*/
  CREATE(new_descr, struct extra_descr_data, 1);
  sprintf(buf, "%s hilt",sword->name);
  new_descr->keyword = strdup(buf);
  sprintf(buf, "It is inset with %s", gem->short_description);
  new_descr->description = strdup(buf);
  new_descr->next = sword->ex_description;
  sword->ex_description = new_descr;

  for (i=0; i < MAX_OBJ_AFFECT; i++) {
    if (gem->affected[i].location != APPLY_NONE) {
      j = getFreeAffSlot(sword);
      sword->affected[j].location = gem->affected[i].location;
      sword->affected[j].modifier = gem->affected[i].modifier;
    }
  }

  /* add stone's value to sword's rent (ego)  */
  
  GET_RENT(sword) += GET_VALUE(gem);

  /* delete stone */

  obj_from_char(gem);
  extract_obj(gem);

}

/*
**  Disarm:
*/

void do_disarm(struct char_data *ch, char *argument, int cmd)
{
  char name[30];
  int percent;
  struct char_data *victim;
  struct obj_data *w, *trap;

  if (!ch->skills) return;

  if (check_peaceful(ch,"You feel too peaceful to contemplate violence.\n\r"))
    return;

  if (!IS_PC(ch) && cmd)
    return;
  
  /*
   *   get victim
   */
  only_argument(argument, name);
    if (!(victim = get_char_room_vis(ch, name))) {
    if (ch->specials.fighting) {
      victim = ch->specials.fighting;
    } else {
      if (!ch->skills[SKILL_REMOVE_TRAP].learned) {
	send_to_char("Disarm who?\n\r", ch);
	return;
      } else {

	if (MOUNTED(ch)) {
	  send_to_char("Yeah... right... while mounted\n\r", ch);
	  return;
	}

	if (!(trap = get_obj_in_list_vis(ch, name, 
		    real_roomp(ch->in_room)->contents))) {
	  if (!(trap = get_obj_in_list_vis(ch, name, ch->carrying))) {
	    send_to_char("Disarm what?\n\r", ch);
	    return;
	  }
	}
	if (trap) {
	  remove_trap(ch, trap);
	  return;
	}
      }
    }
  }
  
  
  if (victim == ch) {
    send_to_char("Aren't we funny today...\n\r", ch);
    return;
  }

  if (victim != ch->specials.fighting) {
    send_to_char("but you aren't fighting them!\n\r", ch);
    return;
  }

  if (ch->attackers > 3) {
    send_to_char("There is no room to disarm!\n\r", ch);
    return;
  }

  if (!HasClass(ch, CLASS_WARRIOR) && !HasClass(ch, CLASS_MONK)) {
    send_to_char("You're no warrior!\n\r", ch);
    return;
  }
  
  /*
   *   make roll - modified by dex && level
   */
  percent=number(1,101); /* 101% is a complete failure */
  
  percent -= dex_app[GET_DEX(ch)].reaction*10;
  percent += dex_app[GET_DEX(victim)].reaction*10;
  if (!ch->equipment[WIELD] && !HasClass(ch, CLASS_MONK)) {
    percent += 50;
  }

  percent += GetMaxLevel(victim);
  if (HasClass(victim, CLASS_MONK))
    percent += GetMaxLevel(victim);

  if (HasClass(ch, CLASS_MONK)) {
    percent -= GetMaxLevel(ch);
  } else {
    percent -= GetMaxLevel(ch)>>1;
  }

  if (percent > ch->skills[SKILL_DISARM].learned) {
    /*
     *   failure.
     */
    act("You try to disarm $N, but fail miserably.", 
	TRUE, ch, 0, victim, TO_CHAR);
    act("$n does a nifty fighting move, but then falls on $s butt.",
	TRUE, ch, 0, 0, TO_ROOM);
    GET_POS(ch) = POSITION_SITTING;
    if ((IS_NPC(victim)) && (GET_POS(victim) > POSITION_SLEEPING) &&
        (!victim->specials.fighting)) {
      set_fighting(victim, ch);
    }
    LearnFromMistake(ch, SKILL_DISARM, 0, 95);
    WAIT_STATE(ch, PULSE_VIOLENCE*3);
  } else {
    /*
     *  success
     */
    if (victim->equipment[WIELD]) {
      w = unequip_char(victim, WIELD);
      act("$n makes an impressive fighting move.", 
	  TRUE, ch, 0, 0, TO_ROOM);
      act("You send $p flying from $N's grasp.", TRUE, ch, w, victim, 
	  TO_CHAR);
      act("$p flies from your grasp.", TRUE, ch, w, victim, TO_VICT);
/*
  send the object to a nearby room, instead
*/
      obj_to_room(w, victim->in_room);
    } else {
      act("You try to disarm $N, but $E doesn't have a weapon.", 
	  TRUE, ch, 0, victim, TO_CHAR);
      act("$n makes an impressive fighting move, but does little more.",
	  TRUE, ch, 0, 0, TO_ROOM);
    }
    if ((IS_NPC(victim)) && (GET_POS(victim) > POSITION_SLEEPING) &&
        (!victim->specials.fighting)) {
      set_fighting(victim, ch);
    }
    WAIT_STATE(victim, PULSE_VIOLENCE*2);
    WAIT_STATE(ch, PULSE_VIOLENCE*2);
  }  
}


/*
**   Track:
*/

int named_mobile_in_room(int room, struct hunting_data *c_data)
{
  struct char_data	*scan;

  for (scan = real_roomp(room)->people; scan; scan = scan->next_in_room)
    if (isname(c_data->name, scan->player.name)) {
         *(c_data->victim) = scan;
         return 1;
  }
  return 0;
}

void do_track(struct char_data *ch, char *argument, int cmd)
{
  char name[256], buf[256], found=FALSE;
  int dist, code;
  struct hunting_data	huntd;
  struct char_data *scan;
  extern struct char_data  *character_list;
  
#if NOTRACK
  send_to_char("Sorry, tracking is disabled. Try again after reboot.\n\r",ch);
  return;
#endif

  only_argument(argument, name);
  
  found = FALSE;
  for (scan = character_list; scan && !found ; scan = scan->next)
    if (isname(name, scan->player.name)) {
      found = TRUE;
    }
  
  if (!found) {
    send_to_char("You are unable to find traces of one.\n\r", ch);
    return;
  }
  
  if (!ch->skills) 
    dist = 10;
  else
    dist = ch->skills[SKILL_HUNT].learned;
  
  if (IS_SET(ch->player.class, CLASS_THIEF)) {
    dist *= 3;
  }
  
  switch(GET_RACE(ch)){
  case RACE_ELVEN:
    dist *= 2;               /* even better */
    break;
  case RACE_DEVIL:
  case RACE_DEMON:
    dist = MAX_ROOMS;   /* as good as can be */
    break;
  default:
    break;
  }
  
  if (GetMaxLevel(ch) >= IMMORTAL)
    dist = MAX_ROOMS;
  
  
  if (affected_by_spell(ch, SPELL_MINOR_TRACK)) {
    dist = GetMaxLevel(ch) * 10;
  } else if (affected_by_spell(ch, SPELL_MAJOR_TRACK)){
    dist = GetMaxLevel(ch) * 20;
  }
  
  if (dist == 0)
    return;
  
  ch->hunt_dist = dist;
  
  ch->specials.hunting = 0;
  huntd.name = name;
  huntd.victim = &ch->specials.hunting;

  if ((GetMaxLevel(ch) < MIN_GLOB_TRACK_LEV) ||
      (affected_by_spell(ch, SPELL_MINOR_TRACK)) || 
      (affected_by_spell(ch, SPELL_MAJOR_TRACK))) {
    code = find_path( ch->in_room, named_mobile_in_room, &huntd, -dist, 1);
  } else {
    code = find_path( ch->in_room, named_mobile_in_room, &huntd, -dist, 0);
  }
  
  WAIT_STATE(ch, PULSE_VIOLENCE*1);
  
  if(ch->specials.hunting)  {

    if(GET_RACE(ch->specials.hunting) == RACE_VEGMAN &&
       real_roomp(ch->specials.hunting->in_room)->sector_type != SECT_FOREST) {
      send_to_char("You are unable to find traces of one.\n\r", ch);
      ch->specials.hunting = 0;
      return;
    }
    
    if(IS_SET(ch->specials.act, ACT_SENTINEL) || 
       IS_AFFECTED(ch->specials.hunting, AFF_FLYING)) {
      send_to_char("You are unable to find traces of one.\n\r", ch);
      ch->specials.hunting = 0;
      return;
    }
  }

  if (code == -1) {
    send_to_char("You are unable to find traces of one.\n\r", ch);
    return;
  } else if (code == -69)  {
    send_to_char("Look in front of your nose, dolt!\n\r",ch);
    return;
  } else {
    if (IS_LIGHT(ch->in_room) || IS_AFFECTED(ch, AFF_TRUE_SIGHT) ) {
      SET_BIT(ch->specials.act, PLR_HUNTING);
      sprintf(buf, "You see traces of your quarry to the %s.\n\r", dirs[code]);
      send_to_char(buf,ch);
    } else {
      ch->specials.hunting = 0;
      send_to_char("It's too dark in here to track...\n\r",ch);
      return;
    }
  }
}

int track( struct char_data *ch, struct char_data *vict)
{

  char buf[256];
  int code;

  if ((!ch) || (!vict))
    return(-1);

  if ((GetMaxLevel(ch) < MIN_GLOB_TRACK_LEV) || 
      (affected_by_spell(ch, SPELL_MINOR_TRACK)) || 
      (affected_by_spell(ch, SPELL_MAJOR_TRACK))) {
    code = choose_exit_in_zone(ch->in_room, vict->in_room, ch->hunt_dist);
  } else {
    code = choose_exit_global(ch->in_room, vict->in_room, ch->hunt_dist);
  }
  if ((!ch) || (!vict))
    return(-1);
  

  if (ch->in_room == vict->in_room) {
    send_to_char("##You have found your target!\n\r",ch);
    return(FALSE);  /* false to continue the hunt */
  }
  if (code == -1) {
    send_to_char("##You have lost the trail.\n\r",ch);
    return(FALSE);
  } else {
    sprintf(buf, "##You see a faint trail to the %s.\n\r", dirs[code]);
    send_to_char(buf, ch);
    return(TRUE);
  }

}

int dir_track( struct char_data *ch, struct char_data *vict)
{

  char buf[256];
  int code;

  if ((!ch) || (!vict))
    return(-1);

  
  if ((GetMaxLevel(ch) >= MIN_GLOB_TRACK_LEV) ||
      (affected_by_spell(ch, SPELL_MINOR_TRACK)) || 
      (affected_by_spell(ch, SPELL_MAJOR_TRACK))) {
    code = choose_exit_global(ch->in_room, vict->in_room, ch->hunt_dist);
  } else {
    code = choose_exit_in_zone(ch->in_room, vict->in_room, ch->hunt_dist);
  }
  
  if ((!ch) || (!vict))
    return(-1);

  if (code == -1) {
    if (ch->in_room == vict->in_room) {
      send_to_char("##You have found your target!\n\r",ch);
    } else {
      send_to_char("##You have lost the trail.\n\r",ch);
    }
    return(-1);  /* false to continue the hunt */
  } else {
    sprintf(buf, "##You see a faint trail to the %s.\n\r", dirs[code]);
    send_to_char(buf, ch);
    return(code);
  }

}




/** Perform breadth first search on rooms from start (in_room) **/
/** until end (tgt_room) is reached. Then return the correct   **/
/** direction to take from start to reach end.                 **/

/* thoth@manatee.cis.ufl.edu
   if dvar<0 then search THROUGH closed but not locked doors,
   for mobiles that know how to open doors.
 */

#define IS_DIR    (real_roomp(q_head->room_nr)->dir_option[i])
#define GO_OK  (!IS_SET(IS_DIR->exit_info,EX_CLOSED)\
		 && (IS_DIR->to_room != NOWHERE))
#define GO_OK_SMARTER  (!IS_SET(IS_DIR->exit_info,EX_LOCKED)\
		 && (IS_DIR->to_room != NOWHERE))

void donothing()
{
  return;
}

int find_path(int in_room, int (*predicate)(), void *c_data, 
	      int depth, int in_zone)
{
   struct room_q *tmp_q, *q_head, *q_tail;
#if 1
  struct hash_header	x_room;
/*  static struct hash_header	x_room; */
#else
  struct nodes x_room[MAX_ROOMS];
#endif
   int i, tmp_room, count=0, thru_doors;
  struct room_data	*herep, *therep;
  struct room_data      *startp;
  struct room_direction_data	*exitp;

	/* If start = destination we are done */
   if ((predicate)(in_room, c_data))
     return(-69);		/* <grin> couldn't return a direction */

#if 0
   if (top_of_world > MAX_ROOMS) {
     log("TRACK Is disabled, too many rooms.\n\rContact Loki soon.\n\r");
    return -1;
   }
#endif

   if (depth<0) {
     thru_doors = TRUE;
     depth = - depth;
   } else {
     thru_doors = FALSE;
   }

  startp = real_roomp(in_room);

  init_hash_table(&x_room, sizeof(int), 2048);
  hash_enter(&x_room, in_room, (void*)-1);

	   /* initialize queue */
   q_head = (struct room_q *) malloc(sizeof(struct room_q));
   q_tail = q_head;
   q_tail->room_nr = in_room;
   q_tail->next_q = 0;

  while(q_head) {
    herep = real_roomp(q_head->room_nr);
		/* for each room test all directions */
    if (herep->zone == startp->zone || !in_zone) {  
                                           /* only look in this zone.. 
					      saves cpu time.  makes world
					      safer for players
					      */
      for(i = 0; i <= 5; i++) {
        exitp = herep->dir_option[i];
        if (exit_ok(exitp, &therep) && (thru_doors ? GO_OK_SMARTER : GO_OK)) {
	  /* next room */
	  tmp_room = herep->dir_option[i]->to_room;
	  if(!((predicate)(tmp_room, c_data))) {
	    /* shall we add room to queue ? */
	    /* count determines total breadth and depth */
	    if(!hash_find(&x_room,tmp_room) && (count < depth)
	       && !IS_SET(RM_FLAGS(tmp_room),DEATH)) {
	      count++;
	      /* mark room as visted and put on queue */
	      
	      tmp_q = (struct room_q *) malloc(sizeof(struct room_q));
	      tmp_q->room_nr = tmp_room;
	      tmp_q->next_q = 0;
	      q_tail->next_q = tmp_q;
	      q_tail = tmp_q;
	      
	      /* ancestor for first layer is the direction */
	      hash_enter(&x_room, tmp_room,
			 ((int)hash_find(&x_room,q_head->room_nr) == -1) ?
			 (void*)(i+1) : hash_find(&x_room,q_head->room_nr));
	    }
	  } else {
	    /* have reached our goal so free queue */
	    tmp_room = q_head->room_nr;
	    for(;q_head;q_head = tmp_q)   {
	      tmp_q = q_head->next_q;
	      free(q_head);
	    }
	    /* return direction if first layer */
	    if ((int)hash_find(&x_room,tmp_room)==-1) {
              if (x_room.buckets) { /* junk left over from a previous track */
		destroy_hash_table(&x_room, donothing);
              }
	      return(i);
	    } else {  /* else return the ancestor */
	      int i;
	      
              i = (int)hash_find(&x_room,tmp_room);
              if (x_room.buckets) { /* junk left over from a previous track */
		destroy_hash_table(&x_room, donothing);
              }
	      return( -1+i);
	    }
	  }
	}
      }
    }
  
      /* free queue head and point to next entry */
      tmp_q = q_head->next_q;
      free(q_head);
      q_head = tmp_q;
   }
   /* couldn't find path */
   if (x_room.buckets) { /* junk left over from a previous track */
      destroy_hash_table(&x_room, donothing);
   } 
   return(-1);

}


int choose_exit_global(int in_room, int tgt_room, int depth)
{
  return find_path(in_room, is_target_room_p, (void*)tgt_room, depth, 0);
}

int choose_exit_in_zone(int in_room, int tgt_room, int depth)
{
  return find_path(in_room, is_target_room_p, (void*)tgt_room, depth, 1);
}

int go_direction(struct char_data *ch, int dir)     
{
  if (ch->specials.fighting)
    return 0;
  
  if (!IS_SET(EXIT(ch,dir)->exit_info, EX_CLOSED)) {
    do_move(ch, "", dir+1);
  } else if ( IsHumanoid(ch) && !IS_SET(EXIT(ch,dir)->exit_info, EX_LOCKED) ) {
    open_door(ch, dir);
    return 0;
  }
}


void slam_into_wall( struct char_data *ch, struct room_direction_data *exitp)
{
  char doorname[128];
  char buf[256];
  
  if (exitp->keyword && *exitp->keyword) {
    if ((strcmp(fname(exitp->keyword), "secret")==0) ||
	(IS_SET(exitp->exit_info, EX_SECRET))) {
      strcpy(doorname, "wall");
    } else {
      strcpy(doorname, fname(exitp->keyword));
    }
  } else {
    strcpy(doorname, "barrier");
  }
  sprintf(buf, "You slam against the %s with no effect.\n\r", doorname);
  send_to_char(buf, ch);
  send_to_char("OUCH!  That REALLY Hurt!\n\r", ch);
  sprintf(buf, "$n crashes against the %s with no effect.\n\r", doorname);
  act(buf, FALSE, ch, 0, 0, TO_ROOM);
  GET_HIT(ch) -= number(1, 10)*2;
  if (GET_HIT(ch) < 0)
    GET_HIT(ch) = 0;
  GET_POS(ch) = POSITION_STUNNED;
  return;
}


/*
  skill to allow fighters to break down doors
*/
void do_doorbash( struct char_data *ch, char *arg, int cmd)
{
  extern char *dirs[];
  int dir;
  int ok;
  struct room_direction_data *exitp;
  int was_in, roll;

  char buf[256], type[128], direction[128];

  if (GET_MOVE(ch) < 10) {
    send_to_char("You're too tired to do that\n\r", ch);
    return;
  }

  if (MOUNTED(ch)) {
    send_to_char("Yeah... right... while mounted\n\r", ch);
    return;
  }

  /*
    make sure that the argument is a direction, or a keyword.
  */

  for (;*arg == ' '; arg++);

  argument_interpreter(arg, type, direction);

  if ((dir = find_door(ch, type, direction)) >= 0) {
    ok = TRUE;
  } else {
    act("$n looks around, bewildered.", FALSE, ch, 0, 0, TO_ROOM);
    return;
  }

  if (!ok) {
    send_to_char("Hmm, you shouldn't have gotten this far\n\r", ch);
    return;
  }

  exitp = EXIT(ch, dir);
  if (!exitp) {
    send_to_char("you shouldn't have gotten here.\n\r", ch);
    return;
  }

  if (dir == UP) {
    if (real_roomp(exitp->to_room)->sector_type == SECT_AIR &&
	!IS_AFFECTED(ch, AFF_FLYING)) {
      send_to_char("You have no way of getting there!\n\r", ch);
      return;
    }
  }
  
  sprintf(buf, "$n charges %swards", dirs[dir]);
  act(buf, FALSE, ch, 0, 0, TO_ROOM);
  sprintf(buf, "You charge %swards\n\r", dirs[dir]);
  send_to_char(buf, ch);

  if (!IS_SET(exitp->exit_info, EX_CLOSED)) {
    was_in = ch->in_room;
    char_from_room(ch);
    char_to_room(ch, exitp->to_room);
    do_look(ch, "", 0);

    DisplayMove(ch, dir, was_in, 1);
    if (!check_falling(ch)) {
      if (IS_SET(RM_FLAGS(ch->in_room), DEATH) && 
	  GetMaxLevel(ch) < LOW_IMMORTAL) {
	NailThisSucker(ch);
	return;
      } else {
	WAIT_STATE(ch, PULSE_VIOLENCE*3);
	GET_MOVE(ch) -= 10;
      }
    } else {
      return;
    }
    WAIT_STATE(ch, PULSE_VIOLENCE*3);
    GET_MOVE(ch) -= 10;
    return;
  }

  GET_MOVE(ch) -= 10;

  if (IS_SET(exitp->exit_info, EX_LOCKED) &&
      IS_SET(exitp->exit_info, EX_PICKPROOF)) {
    slam_into_wall(ch, exitp);
    return;
  }

  /*
    now we've checked for failures, time to check for success;
    */
  if (ch->skills) {
    if (ch->skills[SKILL_DOORBASH].learned) {
      roll = number(1, 100);
      if (roll > ch->skills[SKILL_DOORBASH].learned) {
	slam_into_wall(ch, exitp);
	LearnFromMistake(ch, SKILL_DOORBASH, 0, 95);
      } else {
	/*
	  unlock and open the door
	  */
	sprintf(buf, "$n slams into the %s, and it bursts open!", 
		fname(exitp->keyword));
	act(buf, FALSE, ch, 0, 0, TO_ROOM);
	sprintf(buf, "You slam into the %s, and it bursts open!\n\r", 
		fname(exitp->keyword));
	send_to_char(buf, ch);
	raw_unlock_door(ch, exitp, dir);
	raw_open_door(ch, dir);
	GET_HIT(ch) -= number(1,5);
	/*
	  Now a dex check to keep from flying into the next room
	  */
	roll = number(1, 20);
	if (roll > GET_DEX(ch)) {
	  was_in = ch->in_room;

	  char_from_room(ch);
	  char_to_room(ch, exitp->to_room);
	  do_look(ch, "", 0);
	  DisplayMove(ch, dir, was_in, 1);
	  if (!check_falling(ch)) {
	    if (IS_SET(RM_FLAGS(ch->in_room), DEATH) && 
		GetMaxLevel(ch) < LOW_IMMORTAL) {
	      NailThisSucker(ch);
	      return;
	    }
	  } else {
	    return;
	  }
	  WAIT_STATE(ch, PULSE_VIOLENCE*3);
	  GET_MOVE(ch) -= 10;
	  return;	  
	} else {
	  WAIT_STATE(ch, PULSE_VIOLENCE*1);
	  GET_MOVE(ch) -= 5;
	  return;
	}
      }
    } else {
      send_to_char("You just don't know the nuances of door-bashing.\n\r", ch);
      slam_into_wall(ch, exitp);
      return;
    }
  } else {
    send_to_char("You're just a goofy mob.\n\r", ch);
    return;
  }
}

/*
  skill to allow anyone to move through rivers and underwater
*/

void do_swim( struct char_data *ch, char *arg, int cmd)
{

  struct affected_type af;
  byte percent;
  

  send_to_char("Ok, you'll try to swim for a while.\n\r", ch);

  if (IS_AFFECTED(ch, AFF_WATERBREATH)) {
    /* kinda pointless if they don't need to...*/
    return;
  }
  
  if (affected_by_spell(ch, SKILL_SWIM)) {
    send_to_char("You're too exhausted to swim right now\n", ch);
    return;
  }

  percent=number(1,101); /* 101% is a complete failure */

  if (!ch->skills)
    return;
  
  if (percent > ch->skills[SKILL_SWIM].learned) {
    send_to_char("You're too afraid to enter the water.\n\r",ch);
    if (ch->skills[SKILL_SWIM].learned < 95 &&
	ch->skills[SKILL_SWIM].learned > 0) {
      if (number(1,101) > ch->skills[SKILL_SWIM].learned) {
	send_to_char("You feel a bit braver, though.\n\r", ch);
	ch->skills[SKILL_SWIM].learned++;
      }
    }
    return;
  }
 
  af.type = SKILL_SWIM;
  af.duration = (ch->skills[SKILL_SWIM].learned/10)+1;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = AFF_WATERBREATH;
  affect_to_char(ch, &af);

  af.type = SKILL_SWIM;
  af.duration = 13;
  af.modifier = -10;
  af.location = APPLY_MOVE;
  af.bitvector = 0;
  affect_to_char(ch, &af);

}


int SpyCheck(struct char_data *ch)
{

  if (!ch->skills) return(FALSE);

  if (number(1,101) > ch->skills[SKILL_SPY].learned)
    return(FALSE);

  return(TRUE);

}

void do_spy( struct char_data *ch, char *arg, int cmd)
{

  struct affected_type af;

  send_to_char("Ok, you'll try to act like a secret agent.\n\r", ch);

  if (IS_AFFECTED(ch, AFF_SCRYING)) {
    return;
  }
  
  if (affected_by_spell(ch, SKILL_SPY)) {
    send_to_char("You're already acting like a secret agent.\n", ch);
    return;
  }

  if (!ch->skills)
    return;

  if (number(1,101) > ch->skills[SKILL_SPY].learned) {
    LearnFromMistake(ch, SKILL_SPY, 0, 95);

    af.type = SKILL_SPY;
    af.duration = (ch->skills[SKILL_SPY].learned/10)+1;
    af.modifier = 0;
    af.location = APPLY_NONE;
    af.bitvector = 0;
    affect_to_char(ch, &af);    return;
  }
 
  af.type = SKILL_SPY;
  af.duration = (ch->skills[SKILL_SPY].learned/10)+1;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = AFF_SCRYING;
  affect_to_char(ch, &af);    return;
}

int remove_trap( struct char_data *ch, struct obj_data *trap)
{
  int num;

  if (ITEM_TYPE(trap) != ITEM_TRAP) {
    send_to_char("That's no trap!\n\r", ch);
    return(FALSE);
  }
  if (GET_TRAP_CHARGES(trap) <= 0) {
    send_to_char("That trap is already sprung!\n\r", ch);
    return(FALSE);
  }
  num = number(1,101);
  if (num < ch->skills[SKILL_REMOVE_TRAP].learned) {
    send_to_char("<Click>\n\r", ch);
    act("$n disarms $p", FALSE, ch, trap, 0, TO_ROOM);
    GET_TRAP_CHARGES(trap) = 0;
    return(TRUE);
  } else {
    send_to_char("<Click>\n\r(uh oh)\n\r", ch);
    act("$n attempts to disarm $p, ack!", FALSE, ch, trap, 0, TO_ROOM);
    TriggerTrap(ch, trap);
    return(TRUE);
  }
}

void do_feign_death( struct char_data *ch, char *arg, int cmd)
{
  struct room_data *rp;
  struct char_data *t;

  if (!ch->skills)
    return;

  if (!ch->specials.fighting) {
    send_to_char("But you are not fighting anything...\n\r", ch);
    return;
  }
  
  if (!HasClass(ch, CLASS_MONK)) {
    send_to_char("You're no monk!\n\r", ch);
    return;
  }

  if (MOUNTED(ch)) {
    send_to_char("Yeah... right... while mounted\n\r", ch);
    return;
  }

  rp = real_roomp(ch->in_room);
  if (!rp)
    return;

  send_to_char("You try to fake your own demise\n\r", ch);

  death_cry(ch);
  act("$n is dead! R.I.P.", FALSE, ch, 0, 0, TO_ROOM);

  if (number(1,101) < ch->skills[SKILL_FEIGN_DEATH].learned) {
    stop_fighting(ch);
    for (t = rp->people;t;t=t->next_in_room) {
      if (t->specials.fighting == ch) {
	stop_fighting(t);
	if (number(1,101) < ch->skills[SKILL_FEIGN_DEATH].learned/2)
	  SET_BIT(ch->specials.affected_by, AFF_HIDE);
	GET_POS(ch) = POSITION_SLEEPING;
      }
    }
    WAIT_STATE(ch, PULSE_VIOLENCE*2);
    return;
  } else {
    GET_POS(ch) = POSITION_SLEEPING;
    WAIT_STATE(ch, PULSE_VIOLENCE*3);
    LearnFromMistake(ch, SKILL_FEIGN_DEATH, 0, 95);
  }
}


void do_first_aid( struct char_data *ch, char *arg, int cmd)
{
  struct affected_type af;
    
  send_to_char("You attempt to render first aid unto yourself.\n\r", ch);

  if (affected_by_spell(ch, SKILL_FIRST_AID)) {
    send_to_char("You can only do this once per day.\n\r", ch);
    return;
  }

  if (number(1,101) < ch->skills[SKILL_FIRST_AID].learned) {
    GET_HIT(ch)+= number(1,20) + (GetMaxLevel(ch)/2);
    if(GET_HIT(ch) > GET_MAX_HIT(ch))
       GET_HIT(ch) = GET_MAX_HIT(ch);

    af.duration = 24;
  } else {
    af.duration = 6;
    LearnFromMistake(ch, SKILL_FEIGN_DEATH, TRUE, 95);
  }

  af.type = SKILL_FIRST_AID;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = 0;
  affect_to_char(ch, &af);    
  return;  
}


void do_disguise(struct char_data *ch, char *argument, int cmd)
{
  struct affected_type af;
    
  send_to_char("You attempt to disguise yourself\n\r", ch);

  if (affected_by_spell(ch, SKILL_DISGUISE)) {
    send_to_char("You can only do this once per day\n\r", ch);
    return;
  }

  if (number(1,101) < ch->skills[SKILL_DISGUISE].learned) {
    struct char_data *k;

    for (k=character_list; k; k=k->next) {
      if (k->specials.hunting == ch) {
	k->specials.hunting = 0;
      }
      if (number(1,101) < ch->skills[SKILL_DISGUISE].learned) {
	if (Hates(k, ch)) {
	  ZeroHatred(k, ch);
	}
	if (Fears(k, ch)) {
	  ZeroFeared(k, ch);
	}
      }
    }
  } else {
    LearnFromMistake(ch, SKILL_DISGUISE, 0, 95);
  }

  af.type = SKILL_DISGUISE;
  af.duration = 24;
  af.modifier = 0;
  af.location = APPLY_NONE;
  af.bitvector = 0;
  affect_to_char(ch, &af);    
  return;  
}

/* Skill for climbing walls and the like -DM */
void do_climb( struct char_data *ch, char *arg, int cmd)
{
  extern char *dirs[];
  int dir;
  struct room_direction_data *exitp;
  int was_in, roll;
  extern char *dirs[];

  char buf[256], type[128], direction[128];

  if (GET_MOVE(ch) < 10) {
    send_to_char("You're too tired to do that\n\r", ch);
    return;
  }

  if (MOUNTED(ch)) {
    send_to_char("Yeah... right... while mounted\n\r", ch);
    return;
  }

  /*
    make sure that the argument is a direction, or a keyword.
  */

  for (;*arg == ' '; arg++);

  only_argument(arg,direction);

  if ((dir = search_block(direction, dirs, FALSE)) < 0) {
    send_to_char("You can't climb that way.\n\r", ch);
    return;
  }

  exitp = EXIT(ch, dir);
  if (!exitp) {
    send_to_char("You can't climb that way.\n\r", ch);
    return;
  }

  if(!IS_SET(exitp->exit_info, EX_CLIMB)) {
    send_to_char("You can't climb that way.\n\r", ch);
    return;
  }

  if (dir == UP) {
    if (real_roomp(exitp->to_room)->sector_type == SECT_AIR &&
	!IS_AFFECTED(ch, AFF_FLYING)) {
      send_to_char("You have no way of getting there!\n\r", ch);
      return;
    }
  }

  if (IS_SET(exitp->exit_info, EX_ISDOOR) &&
      IS_SET(exitp->exit_info, EX_CLOSED)) {
    send_to_char("You can't climb that way.\n\r", ch);
    return;
  }

  sprintf(buf, "$n attempts to climb %swards", dirs[dir]);
  act(buf, FALSE, ch, 0, 0, TO_ROOM);
  sprintf(buf, "You attempt to climb %swards\n\r", dirs[dir]);
  send_to_char(buf, ch);

  GET_MOVE(ch) -= 10;

  /*
    now we've checked for failures, time to check for success;
    */
  if (ch->skills) {
    if (ch->skills[SKILL_CLIMB].learned) {
      roll = number(1, 100);
      if (roll > ch->skills[SKILL_CLIMB].learned) {
	slip_in_climb(ch, dir, exitp->to_room);
	LearnFromMistake(ch, SKILL_CLIMB, 0, 95);
      } else {

	  was_in = ch->in_room;

	  char_from_room(ch);
	  char_to_room(ch, exitp->to_room);
	  do_look(ch, "", 0);
	  DisplayMove(ch, dir, was_in, 1);
	  if (!check_falling(ch)) {
	    if (IS_SET(RM_FLAGS(ch->in_room), DEATH) && 
		GetMaxLevel(ch) < LOW_IMMORTAL) {
	      NailThisSucker(ch);
	      return;
	    }

	  }
	  WAIT_STATE(ch, PULSE_VIOLENCE*3);
	  GET_MOVE(ch) -= 10;
	  return;	  
	}
      }
     else {
      send_to_char("You just don't know the nuances of climbing.\n\r", ch);
      slip_in_climb(ch, dir, exitp->to_room);
      return;
    }
  } else {
    send_to_char("You're just a goofy mob.\n\r", ch);
    return;
  }
}


void slip_in_climb(struct char_data *ch, int dir, int room)
{
 int i;

 i = number(1, 6);

 if(dir != DOWN) {
   act("$n falls down and goes splut.", FALSE, ch, 0, 0, TO_ROOM);
   send_to_char("You fall.\n\r", ch);
 }

 else {
   act("$n loses $s grip and falls further down.", FALSE, ch, 0, 0, TO_ROOM);
   send_to_char("You slip and start to fall.\n\r", ch);
   i += number(1, 6);
   char_from_room(ch);
   char_to_room(ch, room);
   do_look(ch, "", 0);
 }

 GET_POS(ch) = POSITION_SITTING;
 if(i > GET_HIT(ch))
   GET_HIT(ch) = 1;
 else
   GET_HIT(ch) -= i;
}

void do_palm( struct char_data *ch, char *arg, int cmd)
{
  char arg1[MAX_STRING_LENGTH], arg2[MAX_STRING_LENGTH], 
  buffer[MAX_STRING_LENGTH];
  struct obj_data *sub_object;
  struct obj_data *obj_object;
  bool has=FALSE;

  if(!ch->desc || !ch->skills) {
    send_to_char("You are unable to use this command right now.\n\r", ch);
    return;
  }

  if(number(1,101) > ch->skills[SKILL_PALM].learned || 
     !(HasClass(ch, CLASS_THIEF) && !IsIntrinsic(ch, SKILL_SPY))) {

    do_get(ch, arg, cmd);
    return;
  }
  
  argument_interpreter(arg, arg1, arg2);
  
  if(!*arg1) {
    send_to_char("Palm what doofus?\n\r",ch);
  } else if(*arg1 && !*arg2) {
    
    if(!str_cmp(arg1,"all")) {
      send_to_char("Palm everything?  Are you mad?!?!\n\r", ch);
      return;
    }
    
    obj_object = get_obj_in_list_vis(ch, arg1,
				     real_roomp(ch->in_room)->contents);

    if(obj_object) {
      if ((IS_CARRYING_N(ch) + 1 < CAN_CARRY_N(ch))) {
	if ((IS_CARRYING_W(ch) + obj_object->obj_flags.weight) <
	    CAN_CARRY_W(ch)) {
	  if (CAN_WEAR(obj_object,ITEM_TAKE)) {
	    if (obj_object->in_room == NOWHERE) {
	      obj_object->in_room = ch->in_room;
	    }
	    obj_from_room(obj_object);
	    obj_to_char(obj_object, ch);
	    act("You get $p.", 0, ch, obj_object, 0, TO_CHAR);
	    if((obj_object->obj_flags.type_flag == ITEM_MONEY)) {
	      if (obj_object->obj_flags.value[0]<1)
		obj_object->obj_flags.value[0] = 1;
	      obj_from_char(obj_object);
	      sprintf(buffer,"There %s %d coins.\n\r",
		      obj_object->obj_flags.value[0] > 1 ? "were" : "was",
		      obj_object->obj_flags.value[0]);
	      send_to_char(buffer,ch);
	      GET_GOLD(ch) += obj_object->obj_flags.value[0];
	      if (GET_GOLD(ch) > 100000 && 
		  obj_object->obj_flags.value[0] > 10000) {
		char buf[MAX_INPUT_LENGTH];
		sprintf(buf,"%s just got %d coins!",
			GET_NAME(ch),obj_object->obj_flags.value[0]);
		log(buf);
	      }
	      extract_obj(obj_object);
	    }
	  } else {
	    send_to_char("You can't take that.\n\r", ch);
	    return;
	  }
	} else {
	  sprintf(buffer,"%s : You can't carry that much weight.\n\r",
		  obj_object->short_description);
	  send_to_char(buffer, ch);
	  return;
	}
      } else {
	sprintf(buffer,"%s : You can't carry that many items.\n\r",
		obj_object->short_description);
	send_to_char(buffer, ch);
	return;
      }
    } else {
      sprintf(buffer,"You do not see a %s here.\n\r", arg1);
      send_to_char(buffer, ch);
      return;
    }
  } else {			/* arg1 && arg2 */
    if(!str_cmp(arg1,"all")) {
      send_to_char("Palm everything?  Are you mad?!?!\n\r", ch);
      return;
    }
    sub_object = (struct obj_data *)get_obj_vis_accessible(ch, arg2);
    if (sub_object) {
      if(get_obj_in_list_vis(ch,arg2, ch->carrying)) has=TRUE;
      if (GET_ITEM_TYPE(sub_object) == ITEM_CONTAINER) {
	obj_object = get_obj_in_list_vis(ch, arg1,
					 sub_object->contains);
	if (obj_object) {
	  if (CheckForInsideTrap(ch, sub_object))
	    return;
	  if ((IS_CARRYING_N(ch) + 1 < CAN_CARRY_N(ch))) {
	    if (has || (IS_CARRYING_W(ch) + obj_object->obj_flags.weight) <
		CAN_CARRY_W(ch)) {
	      if (CAN_WEAR(obj_object,ITEM_TAKE)) {
		if (!IS_SET(sub_object->obj_flags.value[1], CONT_CLOSED)) {
		  obj_from_obj(obj_object);
		  obj_to_char(obj_object, ch);
	  act("You get $p from $P.",0,ch,obj_object,sub_object,TO_CHAR);
		  if((obj_object->obj_flags.type_flag == ITEM_MONEY)) {
		    if (obj_object->obj_flags.value[0]<1)
		      obj_object->obj_flags.value[0] = 1;
		    obj_from_char(obj_object);
		    sprintf(buffer,"There %s %d coins.\n\r",
			   obj_object->obj_flags.value[0] > 1 ? "were" : "was",
			    obj_object->obj_flags.value[0]);
		    send_to_char(buffer,ch);
		    GET_GOLD(ch) += obj_object->obj_flags.value[0];
		    if (GET_GOLD(ch) > 100000 &&
			obj_object->obj_flags.value[0] > 10000) {
		      char buf[MAX_INPUT_LENGTH];
		      sprintf(buf,"%s just got %d coins!",
			      GET_NAME(ch),obj_object->obj_flags.value[0]);
		      log(buf);
		    }
		    extract_obj(obj_object);
		  }
		} else {
		  act("$P must be opened first.",1,ch,0,sub_object,TO_CHAR);
		}
	      } else {
		send_to_char("You can't take that.\n\r", ch);
	      }
	    } else {
	      sprintf(buffer,"%s : You can't carry that much weight.\n\r",
		      obj_object->short_description);
	      send_to_char(buffer, ch);
	    }
	  } else {
	    sprintf(buffer,"%s : You can't carry that many items.\n\r",
		    obj_object->short_description);
	    send_to_char(buffer, ch);
	  }
	} else {
	  sprintf(buffer,"%s does not contain the %s.\n\r",
		  sub_object->short_description, arg1);
	  send_to_char(buffer, ch);
	}
      } else {
	sprintf(buffer,"%s is not a container.\n\r", 
		sub_object->short_description);
      }
    } else {
      sprintf(buffer,"You do not see or have the %s.\n\r", arg2);
      send_to_char(buffer, ch);
    }
  }
}


void do_peek( struct char_data *ch, char *arg, int cmd)
{
  char *argument;
  struct char_data *peeked;
  struct  obj_data *dummy;

  if(!ch->desc || !ch->skills) {
    send_to_char("You are unable to use this command right now.\n\r", ch);
    return;
  }

  if ( IS_AFFECTED(ch, AFF_BLIND) ) {
    send_to_char("You can't see a damn thing, you're blinded!\n\r", ch);
    return;
  }

  if ( IS_DARK(ch->in_room) ) {
    if(!SUNPROBLEM(ch) && IS_AFFECTED(ch, AFF_INFRAVISION) && 
       !IS_AFFECTED(ch, AFF_TRUE_SIGHT)) {
      send_to_char("Your infravision can't discern the items!\n\r",ch);
      return;
    }
  }

  if(!IS_IMMORTAL(ch)) {
    if( number(1,101) > ch->skills[SKILL_PEEK].learned || 
       !(HasClass(ch, CLASS_THIEF) && !IsIntrinsic(ch, SKILL_SPY))) {
      do_look(ch, arg, cmd);
      LearnFromMistake(ch, SKILL_PEEK, 0, 95);
      return;
    }
  }

  argument = arg;

  if(!strn_cmp(arg,"at",2))
    argument = arg+3;

  if(generic_find(argument, FIND_CHAR_ROOM, ch, &peeked, &dummy)) {
    show_char_to_char(peeked, ch, 1);
  } else {
    send_to_char("Heh heh, peek at whom?\n\r", ch);
  }
}

void do_berserk( struct char_data *ch, char *arg, int cmd)
{

  struct affected_type af;
  
  if(!ch->skills)
    return;

  if (IS_NPC(ch)) {
    send_to_char("Funny, you don't feel like a real player ogre.\n\r",ch);
    return;
  }

  if (affected_by_spell(ch, SKILL_BERSERK)) {
    send_to_char("You have not recovered completely from the last time!\n\r",
		 ch);
    return;
  }

  if(IS_AFFECTED2(ch, AFF2_BERSERK)) {
    send_to_char("But you allready are!\n\r",ch);
    return;
  }

  if (!ch->specials.fighting) {
    act("$n gets worked up into a lather and runs madly about!",TRUE, ch, 0, 
	0, TO_ROOM);
    send_to_char("You get all worked up over nothing and run madly about.\n\r",
		 ch);
    send_to_char("You get the feeling that this works better in combat.\n\r",
		 ch);
    return;
  }

  if(GET_RACE(ch) != RACE_OGRE) {
    send_to_char("Hey, you aint no ogre, go home wuss.\n\r",ch);
    return;
  }

  /* ok, we assume we have a fighting ogre */

  if(number(1,101) > ch->skills[SKILL_BERSERK].learned) {
    send_to_char("You fail to go totally ape and kill everyone!\n\r",ch);
    act("$n starts snorting and huffing, but stops.",TRUE,ch,0,0,TO_ROOM);
    WAIT_STATE(ch, PULSE_VIOLENCE);
  } else {
    if (GET_POS(ch) >= POSITION_FIGHTING) {
      SET_BIT(ch->specials.affected_by2, AFF2_BERSERK);
      af.type = SKILL_BERSERK;
      af.duration = 24;
      af.modifier = 30;
      af.location = APPLY_AC;
      af.bitvector =0;

      affect_to_char(ch, &af);

      af.modifier = 1;
      af.location = APPLY_HASTE;
      affect_to_char(ch, &af);

      af.modifier = 3;
      af.location = APPLY_DAMROLL;
      affect_to_char(ch, &af);
      
      send_to_char("You fly into a furious rage!\n\r",ch);
      act("$n roars furiously and starts squashing $s opponents!\n\r",TRUE,
	  ch,0, 0, TO_ROOM);
      
    } else {
      send_to_char("Maybe you should get on your feet first?\n\r",ch);
    }
  }
}

void do_makepotion(struct char_data *ch, char *argument, int cmd)
{
  int i, ingredients=0 ,which_potion=0, match=0, j, max;
  bool object[5];
  struct obj_data *o, *in_o, *next, *potion;
  struct room_data *rp;
  char buf[80];

  extern struct index_data *obj_index;
  extern struct BrewMeister BrewList[MAX_POTIONS];

  for(i=0;i<5;i++)		/* very important indeed */
    object[i] = 0;
  
  for(i=0; i<MAX_POTIONS; i++) {
    if(!strcmp(argument, BrewList[i].keyword)) {
      which_potion = i;
      for(j=0;j<5;j++)
	if(BrewList[which_potion].object[j] > 0) {
	  ingredients++;	/* number of valid ingredients */
	}
      break;
    }
  }
 
  /* well, these (and only these) ingredients must be found in a */
  /* cauldron that is sitting in the room.  One communal one in the */
  /* druid's tree, another sold by elvira in her shop :) and one in the*/
  /* mages tower. */

  if(!ingredients) {
    send_to_char("Eh, what?  Have you got a gummi bear up your nose?\n\r", ch);
    send_to_char("You've never heard of such a potion!\n\r", ch);
    return;
  }


#define CAULDRON 1531

  rp = real_roomp(ch->in_room);
  if (!rp) return;
  for (o = rp->contents; o; o = o->next_content) {
    if( obj_index[o->item_number].virtual == CAULDRON ) {
      match = TRUE;		/* o now points at our cauldron */
      break;		
    }
  }
  
  if(!match) {
    send_to_char("Egads, go find Sherlock, your cauldron is gone!\n\r", ch);
    return;
  }
  potion = read_object(BrewList[which_potion].object[5], VIRTUAL); 

  /* is the caster high enough level to cast this? */

  for(max=0,j=1;j<4;j++) {
    if(potion->obj_flags.value[j] >= 1) {
      for(i=0;i<MIN_LEVEL_NUM;i++)
	if(skill_info[potion->obj_flags.value[j]].min_level[i] < LOW_IMMORTAL)
	  max=MAX(max,
		  skill_info[potion->obj_flags.value[j]].min_level[i]);
    }
  }

  {
    char buf[80];
    sprintf(buf,"Min brew level is: %d", max);
    log(buf);
  }

  extract_obj(potion);

  if(which_potion) 
    if(!max || GetMaxLevel(ch) < max) {
      send_to_char("This brew is beyond your powers.\n\r", ch);
      return;
    }
  else {
    if(GetMaxLevel(ch) < 40) {
      send_to_char("This brew is beyond your powers.\n\r", ch);
      return;
    }
  }
  
  if(GET_ITEM_TYPE(o) == ITEM_CONTAINER) { /* it better be */
    /* our cauldron must contain ONLY the right ingredients */
    for(in_o=o->contains,i=0,match=ingredients; in_o; in_o = next) {
      next = in_o->next_content;
      i++;
      for(j=0;j<5;j++) {
	if (obj_index[in_o->item_number].virtual == 
	    BrewList[which_potion].object[j]) {
	  if(!object[j]) {
	    object[j] = TRUE;
	    match--;
	  }
	}
      }
    }
  }

  if(!i) {
    send_to_char("But the pot is empty?\n\r", ch);
    return;
  }

  if(ingredients != i || match != 0) {
    send_to_char("You don't have all the correct ingredients!\n\r", ch);
    send_to_char("Damn, you lost this batch.\n\r", ch);
  } else if (number(1,101) > ch->skills[SKILL_BREWING].learned) {  
    act("$n accidentally lets a drop of sweat fall into the brew...",
	FALSE, ch, 0, 0, TO_ROOM);
    act("You stood too close to the cauldron.  A drop of your sweat just fell in...", FALSE, ch, 0, 0, TO_CHAR);
    act("The cauldron overflows!  Everything is ruined!", 
	FALSE, ch, 0, 0, TO_ROOM);
    act("Next time pay more attention to your master... You just ruined this batch!", FALSE, ch, 0, 0, TO_CHAR);
  } else {
    potion = read_object(BrewList[which_potion].object[5], VIRTUAL);
    obj_to_room(potion, ch->in_room);
    
    act("$n carefully mixes some secret ingredients in a cauldron.",
	FALSE, ch, 0, 0, TO_ROOM);
    act("You carefully mix the ingredients of an age-old recipe.", 
	FALSE, ch, 0, 0, TO_CHAR);
    act("Suddenly... Rainbows shoot out of the pot, and sparks fly!",
	FALSE, ch, 0, 0, TO_ROOM);
    act("Then in a burst of light, nothing remains but a bright flask!", 
	FALSE, ch, 0, 0, TO_ROOM);
    act("Suddenly... Rainbows shoot out of the pot, and sparks fly!",
	FALSE, ch, 0, 0, TO_CHAR);
    act("Then in a burst of light, nothing remains but a bright flask!",
	  FALSE, ch, 0, 0, TO_CHAR);
  }

  /* we always clean the pot, brew or fail. */

  for(in_o = o->contains; in_o; in_o = next) {
    next = in_o->next_content;
    obj_from_obj(in_o);
    act("$p has vanished in a flash of bright light.", 
	FALSE, ch, in_o, 0, TO_CHAR);
    act("$p has vanished in a flash of bright light.", 
	FALSE, ch, in_o, 0, TO_ROOM);
    extract_obj(in_o);
  }
}

/* skill code pieces contributed by Gecko */

void add_skill(int nr, int taught_by, int class_use, int percent)
{
  int i;

  skill_info[nr].spell_pointer = NULL;
  skill_info[nr].minimum_position = POSITION_STANDING;
  skill_info[nr].min_usesmana = 200;
  skill_info[nr].beats = 0;
  skill_info[nr].min_level[MIN_LEVEL_CLERIC] = LOKI+1; /* changed by Kiku */
  skill_info[nr].min_level[MIN_LEVEL_MAGIC] = LOKI+1;  /* New data structure */
  skill_info[nr].min_level[MIN_LEVEL_DRUID] = LOKI+1;  /* implementation. */
  skill_info[nr].targets = TAR_IGNORE;
  skill_info[nr].spellfail = 0;
  skill_info[nr].percent = percent;                    /* added by Kiku   */

  for (i = 0; i < MAX_RACE_DENY; i++)
    skill_info[nr].race_deny[i] = 0;
  
  for (i = 0; i < MAX_RACE_INTRINSIC; i++)
    skill_info[nr].race_intrinsic[i] = 0;

  skill_info[nr].taught_by = taught_by;
  skill_info[nr].class_use = class_use;
}

void assign_skills()
{
  add_skill(SKILL_SNEAK,       TAUGHT_BY_THIEF | TAUGHT_BY_MONK,
                               CLASS_THIEF     | CLASS_MONK, 45);
  add_skill(SKILL_HIDE,        TAUGHT_BY_THIEF | TAUGHT_BY_MONK,
                               CLASS_THIEF     | CLASS_MONK, 45);
  add_skill(SKILL_STEAL,       TAUGHT_BY_THIEF, CLASS_THIEF, 45);
  add_skill(SKILL_BACKSTAB,    TAUGHT_BY_THIEF, CLASS_THIEF, 45);
  add_skill(SKILL_PICK_LOCK,   TAUGHT_BY_THIEF | TAUGHT_BY_MONK,
                               CLASS_THIEF     | CLASS_MONK, 45);

  add_skill(SKILL_KICK,        TAUGHT_BY_WARRIOR | TAUGHT_BY_MONK,
                               CLASS_WARRIOR     | CLASS_MONK, 45);
  add_skill(SKILL_BASH,        TAUGHT_BY_WARRIOR, CLASS_WARRIOR, 45);
  add_skill(SKILL_RESCUE,      TAUGHT_BY_WARRIOR, CLASS_WARRIOR, 45);

  add_skill(SKILL_DUAL_WIELD,  TAUGHT_BY_NINJA,
                               CLASS_THIEF | CLASS_MONK | CLASS_WARRIOR, 95);
  add_skill(SKILL_FIRST_AID,   TAUGHT_BY_HUNTER, CLASS_ALL, 95);

  add_skill(SKILL_SIGN,        TAUGHT_BY_LORE,   CLASS_ALL, 95);
  add_skill(SKILL_RIDE,        TAUGHT_BY_NINJA,  CLASS_ALL, 95);
  add_skill(SKILL_SWITCH_OPP,  TAUGHT_BY_MONK  | TAUGHT_BY_NINJA,
                               CLASS_MONK      | CLASS_WARRIOR, 45);
  add_skill(SKILL_DODGE,       TAUGHT_BY_MONK,   CLASS_WARRIOR | CLASS_MONK, 95);
  add_skill(SKILL_REMOVE_TRAP, TAUGHT_BY_HUNTER, CLASS_THIEF, 95);

  add_skill(SKILL_RETREAT,     TAUGHT_BY_NINJA | TAUGHT_BY_MONK,
                               CLASS_THIEF | CLASS_WARRIOR | CLASS_MONK, 45);
  add_skill(SKILL_QUIV_PALM,   TAUGHT_BY_MONK,   CLASS_MONK, 45);
  add_skill(SKILL_SAFE_FALL,   TAUGHT_BY_MONK,   CLASS_MONK, 95);
  add_skill(SKILL_FEIGN_DEATH, TAUGHT_BY_MONK,   CLASS_MONK, 45);
  add_skill(SKILL_HUNT,        TAUGHT_BY_HUNTER, CLASS_THIEF, 95);

  add_skill(SKILL_LOCATE_TRAP,   TAUGHT_BY_HUNTER, CLASS_THIEF, 95);
  add_skill(SKILL_SPRING_LEAP, TAUGHT_BY_MONK,   CLASS_MONK, 45);
  add_skill(SKILL_DISARM,      TAUGHT_BY_NINJA | TAUGHT_BY_MONK,
	                       CLASS_WARRIOR   | CLASS_MONK, 45);
  add_skill(SKILL_READ_MAGIC,  TAUGHT_BY_LORE,   CLASS_ALL, 95);
  add_skill(SKILL_EVALUATE,    TAUGHT_BY_HUNTER, CLASS_THIEF, 95);
  
  add_skill(SKILL_SPY,         TAUGHT_BY_NINJA,  CLASS_THIEF, 45);
  add_skill(SKILL_DOORBASH,    TAUGHT_BY_NINJA,  CLASS_WARRIOR, 45);
  add_skill(SKILL_SWIM,        TAUGHT_BY_SAILOR, CLASS_ALL, 60);
  add_skill(SKILL_CONS_UNDEAD, TAUGHT_BY_LORE,   CLASS_ALL, 95);
  add_skill(SKILL_CONS_VEGGIE, TAUGHT_BY_LORE,   CLASS_ALL, 95);
  
  add_skill(SKILL_CONS_DEMON,   TAUGHT_BY_LORE, CLASS_ALL, 95);
  add_skill(SKILL_CONS_ANIMAL,  TAUGHT_BY_LORE, CLASS_ALL, 95);
  add_skill(SKILL_CONS_REPTILE, TAUGHT_BY_LORE, CLASS_ALL, 95);
  add_skill(SKILL_CONS_PEOPLE,  TAUGHT_BY_LORE, CLASS_ALL, 95);
  add_skill(SKILL_CONS_GIANT,   TAUGHT_BY_LORE, CLASS_ALL, 95);
  
  add_skill(SKILL_CONS_OTHER, TAUGHT_BY_LORE,  CLASS_ALL, 95);
  add_skill(SKILL_DISGUISE,   TAUGHT_BY_NINJA, CLASS_THIEF, 45);
  add_skill(SKILL_CLIMB,      TAUGHT_BY_NINJA, CLASS_THIEF, 45);
  add_skill(SKILL_INSET,      TAUGHT_BY_LORE,  CLASS_ALL, 95);
  add_skill(SKILL_BREWING,    TAUGHT_BY_LORE,  CLASS_DRUID, 95);
  add_skill(SKILL_BERSERK,     TAUGHT_BY_ETTIN, 0, 80);
  add_skill(SKILL_PALM,       TAUGHT_BY_THIEF, CLASS_THIEF, 45);
  add_skill(SKILL_PEEK,       TAUGHT_BY_THIEF, CLASS_THIEF, 45);
  add_skill(SKILL_CONS_AVIAN, TAUGHT_BY_LORE, CLASS_ALL, 95);
  add_skill(SKILL_CONS_INSECT, TAUGHT_BY_LORE, CLASS_ALL, 95);
  
  /* Racial access and forbiddance goes below.  Must ensure that */
  /* skills (spells will) allow these via IsIntrinsic() checks.  */

  skill_info[SKILL_SWIM].race_deny[0] = RACE_DWARF;

  skill_info[SPELL_FAERIE_FIRE].race_intrinsic[0] = RACE_ELVEN;
  skill_info[SPELL_FAERIE_FIRE].race_intrinsic[1] = RACE_FAERIE;
  skill_info[SPELL_FAERIE_FIRE].race_intrinsic[2] = RACE_DROW;

  skill_info[SPELL_FAERIE_FOG].race_intrinsic[0] = RACE_ELVEN;
  skill_info[SPELL_FAERIE_FOG].race_intrinsic[1] = RACE_DROW;

  skill_info[SPELL_ENCHANT_WEAPON].race_intrinsic[0] = RACE_DWARF;
  skill_info[SPELL_ENCHANT_ARMOR].race_intrinsic[0] = RACE_DWARF;

  skill_info[SPELL_CURE_LIGHT].race_intrinsic[0] = RACE_FAERIE;
  skill_info[SPELL_WEB].race_intrinsic[0] = RACE_DROW;

  skill_info[SPELL_CHARM_PERSON].race_intrinsic[0] = RACE_VAMPIRE;
  skill_info[SPELL_FEAR].race_intrinsic[0] = RACE_VAMPIRE;

  skill_info[SKILL_BERSERK].race_intrinsic[0] = RACE_OGRE;
  skill_info[SKILL_HUNT].race_intrinsic[0] = RACE_ELVEN;

  skill_info[SKILL_PEEK].race_intrinsic[0] = RACE_FAERIE;

  skill_info[SKILL_RETREAT].race_intrinsic[0] = RACE_HALFLING;
  skill_info[SKILL_RETREAT].race_intrinsic[1] = RACE_DRAAGDIM;

  skill_info[SKILL_SPY].race_intrinsic[0] = RACE_MFLAYER;

  skill_info[SKILL_PALM].race_intrinsic[0] = RACE_HALFLING;
  skill_info[SKILL_PALM].race_intrinsic[1] = RACE_DRAAGDIM;

  skill_info[SKILL_EVALUATE].race_intrinsic[1] = RACE_DWARF;
  skill_info[SKILL_EVALUATE].race_intrinsic[0] = RACE_HALFLING;

  skill_info[SKILL_SAFE_FALL].race_intrinsic[0] = RACE_VAMPIRE;

  skill_info[SKILL_PICK_LOCK].race_intrinsic[0] = RACE_GNOME;
  skill_info[SKILL_REMOVE_TRAP].race_intrinsic[0] = RACE_GNOME;

}