dbna/clans/
dbna/councils/
dbna/deity/
dbna/gods/
dbna/houses/
dbna/space/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |   \\._.//   *
 * -----------------------------------------------------------|   (0...0)   *
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998  by Derek Snider      |    ).:.(    *
 * -----------------------------------------------------------|    {o o}    *
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |   / ' ' \   *
 * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek,      |~'~.VxvxV.~'~*
 * Tricops and Fireblade                                      |             *
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.     *
 * ------------------------------------------------------------------------ *
 *	    Misc module for general commands: not skills or spells	    *
 ****************************************************************************
 * Note: Most of the stuff in here would go in act_obj.c, but act_obj was   *
 * getting big.								    *
 ****************************************************************************/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "mud.h"


extern int top_exit;
void remove_tail args( ( CHAR_DATA * ch ) );
/*
 * Fill a container
 * Many enhancements added by Thoric (ie: filling non-drink containers)
 */
void do_fill( CHAR_DATA * ch, char *argument )
{
  char arg1[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];
  OBJ_DATA *obj;
  OBJ_DATA *source;
  sh_int dest_item, src_item1, src_item2, src_item3;
  int diff = 0;
  bool all = FALSE;

  argument = one_argument( argument, arg1 );
  argument = one_argument( argument, arg2 );

  /*
   * munch optional words 
   */
  if( ( !str_cmp( arg2, "from" ) || !str_cmp( arg2, "with" ) ) && argument[0] != '\0' )
    argument = one_argument( argument, arg2 );

  if( arg1[0] == '\0' )
  {
    send_to_char( "Fill what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( obj = get_obj_carry( ch, arg1 ) ) == NULL )
  {
    send_to_char( "You do not have that item.\n\r", ch );
    return;
  }
  else
    dest_item = obj->item_type;

  src_item1 = src_item2 = src_item3 = -1;
  switch ( dest_item )
  {
    default:
      act( AT_ACTION, "$n tries to fill $p... (Don't ask me how)", ch, obj, NULL, TO_ROOM );
      send_to_char( "You cannot fill that.\n\r", ch );
      return;
      /*
       * place all fillable item types here 
       */
    case ITEM_DRINK_CON:
      src_item1 = ITEM_FOUNTAIN;
      src_item2 = ITEM_BLOOD;
      break;
    case ITEM_HERB_CON:
      src_item1 = ITEM_HERB;
      src_item2 = ITEM_HERB_CON;
      break;
    case ITEM_PIPE:
      src_item1 = ITEM_HERB;
      src_item2 = ITEM_HERB_CON;
      break;
    case ITEM_CONTAINER:
      src_item1 = ITEM_CONTAINER;
      src_item2 = ITEM_CORPSE_NPC;
      src_item3 = ITEM_CORPSE_PC;
      break;
  }

  if( dest_item == ITEM_CONTAINER )
  {
    if( IS_SET( obj->value[1], CONT_CLOSED ) )
    {
      act( AT_PLAIN, "The $d is closed.", ch, NULL, obj->name, TO_CHAR );
      return;
    }
    if( get_real_obj_weight( obj ) / obj->count >= obj->value[0] )
    {
      send_to_char( "It's already full as it can be.\n\r", ch );
      return;
    }
  }
  else
  {
    diff = obj->value[0] - obj->value[1];
    if( diff < 1 || obj->value[1] >= obj->value[0] )
    {
      send_to_char( "It's already full as it can be.\n\r", ch );
      return;
    }
  }

  if( dest_item == ITEM_PIPE && IS_SET( obj->value[3], PIPE_FULLOFASH ) )
  {
    send_to_char( "It's full of ashes, and needs to be emptied first.\n\r", ch );
    return;
  }

  if( arg2[0] != '\0' )
  {
    if( dest_item == ITEM_CONTAINER && ( !str_cmp( arg2, "all" ) || !str_prefix( "all.", arg2 ) ) )
    {
      all = TRUE;
      source = NULL;
    }
    else
      /*
       * This used to let you fill a pipe from an object on the ground.  Seems
       * to me you should be holding whatever you want to fill a pipe with.
       * It's nitpicking, but I needed to change it to get a mobprog to work
       * right.  Check out Lord Fitzgibbon if you're curious.  -Narn 
       */
    if( dest_item == ITEM_PIPE )
    {
      if( ( source = get_obj_carry( ch, arg2 ) ) == NULL )
      {
        send_to_char( "You don't have that item.\n\r", ch );
        return;
      }
      if( source->item_type != src_item1 && source->item_type != src_item2 && source->item_type != src_item3 )
      {
        act( AT_PLAIN, "You cannot fill $p with $P!", ch, obj, source, TO_CHAR );
        return;
      }
    }
    else
    {
      if( ( source = get_obj_here( ch, arg2 ) ) == NULL )
      {
        send_to_char( "You cannot find that item.\n\r", ch );
        return;
      }
    }
  }
  else
    source = NULL;

  if( !source && dest_item == ITEM_PIPE )
  {
    send_to_char( "Fill it with what?\n\r", ch );
    return;
  }

  if( !source )
  {
    bool found = FALSE;
    OBJ_DATA *src_next;

    found = FALSE;
    separate_obj( obj );
    for( source = ch->in_room->first_content; source; source = src_next )
    {
      src_next = source->next_content;
      if( dest_item == ITEM_CONTAINER )
      {
        if( !CAN_WEAR( source, ITEM_TAKE )
            || IS_OBJ_STAT( source, ITEM_BURIED )
            || ( IS_OBJ_STAT( source, ITEM_PROTOTYPE ) && !can_take_proto( ch ) )
            || ch->carry_weight + get_obj_weight( source ) > can_carry_w( ch )
            || ( get_real_obj_weight( source ) + get_real_obj_weight( obj ) / obj->count ) > obj->value[0] )
          continue;
        if( all && arg2[3] == '.' && !nifty_is_name( &arg2[4], source->name ) )
          continue;
        obj_from_room( source );
        if( source->item_type == ITEM_MONEY )
        {
          ch->gold += source->value[0];
          extract_obj( source );
        }
        else
          obj_to_obj( source, obj );
        found = TRUE;
      }
      else if( source->item_type == src_item1 || source->item_type == src_item2 || source->item_type == src_item3 )
      {
        found = TRUE;
        break;
      }
    }
    if( !found )
    {
      switch ( src_item1 )
      {
        default:
          send_to_char( "There is nothing appropriate here!\n\r", ch );
          return;
        case ITEM_FOUNTAIN:
          send_to_char( "There is no fountain or pool here!\n\r", ch );
          return;
        case ITEM_BLOOD:
          send_to_char( "There is no blood pool here!\n\r", ch );
          return;
        case ITEM_HERB_CON:
          send_to_char( "There are no herbs here!\n\r", ch );
          return;
        case ITEM_HERB:
          send_to_char( "You cannot find any smoking herbs.\n\r", ch );
          return;
      }
    }
    if( dest_item == ITEM_CONTAINER )
    {
      act( AT_ACTION, "You fill $p.", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n fills $p.", ch, obj, NULL, TO_ROOM );
      return;
    }
  }

  if( dest_item == ITEM_CONTAINER )
  {
    OBJ_DATA *otmp, *otmp_next;
    char name[MAX_INPUT_LENGTH];
    CHAR_DATA *gch;
    char *pd;
    bool found = FALSE;

    if( source == obj )
    {
      send_to_char( "You can't fill something with itself!\n\r", ch );
      return;
    }

    switch ( source->item_type )
    {
      default: /* put something in container */
        if( !source->in_room  /* disallow inventory items */
            || !CAN_WEAR( source, ITEM_TAKE )
            || ( IS_OBJ_STAT( source, ITEM_PROTOTYPE ) && !can_take_proto( ch ) )
            || ch->carry_weight + get_obj_weight( source ) > can_carry_w( ch )
            || ( get_real_obj_weight( source ) + get_real_obj_weight( obj ) / obj->count ) > obj->value[0] )
        {
          send_to_char( "You can't do that.\n\r", ch );
          return;
        }
        separate_obj( obj );
        act( AT_ACTION, "You take $P and put it inside $p.", ch, obj, source, TO_CHAR );
        act( AT_ACTION, "$n takes $P and puts it inside $p.", ch, obj, source, TO_ROOM );
        obj_from_room( source );
        obj_to_obj( source, obj );
        break;
      case ITEM_MONEY:
        send_to_char( "You can't do that... yet.\n\r", ch );
        break;
      case ITEM_CORPSE_PC:
        if( IS_NPC( ch ) )
        {
          send_to_char( "You can't do that.\n\r", ch );
          return;
        }
        if( IS_OBJ_STAT( source, ITEM_CLANCORPSE ) && !IS_IMMORTAL( ch ) )
        {
          send_to_char( "Your hands fumble.  Maybe you better loot a different way.\n\r", ch );
          return;
        }
        if( !IS_OBJ_STAT( source, ITEM_CLANCORPSE ) || !IS_SET( ch->pcdata->flags, PCFLAG_DEADLY ) )
        {
          pd = source->short_descr;
          pd = one_argument( pd, name );
          pd = one_argument( pd, name );
          pd = one_argument( pd, name );
          pd = one_argument( pd, name );

          if( str_cmp( name, ch->name ) && !IS_IMMORTAL( ch ) )
          {
            bool fGroup;

            fGroup = FALSE;
            for( gch = first_char; gch; gch = gch->next )
            {
              if( !IS_NPC( gch ) && is_same_group( ch, gch ) && !str_cmp( name, gch->name ) )
              {
                fGroup = TRUE;
                break;
              }
            }
            if( !fGroup )
            {
              send_to_char( "That's someone else's corpse.\n\r", ch );
              return;
            }
          }
        }
      case ITEM_CONTAINER:
        if( source->item_type == ITEM_CONTAINER /* don't remove */
            && IS_SET( source->value[1], CONT_CLOSED ) )
        {
          act( AT_PLAIN, "The $d is closed.", ch, NULL, source->name, TO_CHAR );
          return;
        }
      case ITEM_CORPSE_NPC:
        if( ( otmp = source->first_content ) == NULL )
        {
          send_to_char( "It's empty.\n\r", ch );
          return;
        }
        separate_obj( obj );
        for( ; otmp; otmp = otmp_next )
        {
          otmp_next = otmp->next_content;

          if( !CAN_WEAR( otmp, ITEM_TAKE )
              || ( IS_OBJ_STAT( otmp, ITEM_PROTOTYPE ) && !can_take_proto( ch ) )
              || ch->carry_number + otmp->count > can_carry_n( ch )
              || ch->carry_weight + get_obj_weight( otmp ) > can_carry_w( ch )
              || ( get_real_obj_weight( source ) + get_real_obj_weight( obj ) / obj->count ) > obj->value[0] )
            continue;
          obj_from_obj( otmp );
          obj_to_obj( otmp, obj );
          found = TRUE;
        }
        if( found )
        {
          act( AT_ACTION, "You fill $p from $P.", ch, obj, source, TO_CHAR );
          act( AT_ACTION, "$n fills $p from $P.", ch, obj, source, TO_ROOM );
        }
        else
          send_to_char( "There is nothing appropriate in there.\n\r", ch );
        break;
    }
    return;
  }

  if( source->value[1] < 1 )
  {
    send_to_char( "There's none left!\n\r", ch );
    return;
  }
  if( source->count > 1 && source->item_type != ITEM_FOUNTAIN )
    separate_obj( source );
  separate_obj( obj );

  switch ( source->item_type )
  {
    default:
      bug( "do_fill: got bad item type: %d", source->item_type );
      send_to_char( "Something went wrong...\n\r", ch );
      return;
    case ITEM_FOUNTAIN:
      if( obj->value[1] != 0 && obj->value[2] != 0 )
      {
        send_to_char( "There is already another liquid in it.\n\r", ch );
        return;
      }
      obj->value[2] = 0;
      obj->value[1] = obj->value[0];
      act( AT_ACTION, "You fill $p from $P.", ch, obj, source, TO_CHAR );
      act( AT_ACTION, "$n fills $p from $P.", ch, obj, source, TO_ROOM );
      return;
    case ITEM_BLOOD:
      if( obj->value[1] != 0 && obj->value[2] != 13 )
      {
        send_to_char( "There is already another liquid in it.\n\r", ch );
        return;
      }
      obj->value[2] = 13;
      if( source->value[1] < diff )
        diff = source->value[1];
      obj->value[1] += diff;
      act( AT_ACTION, "You fill $p from $P.", ch, obj, source, TO_CHAR );
      act( AT_ACTION, "$n fills $p from $P.", ch, obj, source, TO_ROOM );
      if( ( source->value[1] -= diff ) < 1 )
      {
        extract_obj( source );
        make_bloodstain( ch );
      }
      return;
    case ITEM_HERB:
      if( obj->value[1] != 0 && obj->value[2] != source->value[2] )
      {
        send_to_char( "There is already another type of herb in it.\n\r", ch );
        return;
      }
      obj->value[2] = source->value[2];
      if( source->value[1] < diff )
        diff = source->value[1];
      obj->value[1] += diff;
      act( AT_ACTION, "You fill $p with $P.", ch, obj, source, TO_CHAR );
      act( AT_ACTION, "$n fills $p with $P.", ch, obj, source, TO_ROOM );
      if( ( source->value[1] -= diff ) < 1 )
        extract_obj( source );
      return;
    case ITEM_HERB_CON:
      if( obj->value[1] != 0 && obj->value[2] != source->value[2] )
      {
        send_to_char( "There is already another type of herb in it.\n\r", ch );
        return;
      }
      obj->value[2] = source->value[2];
      if( source->value[1] < diff )
        diff = source->value[1];
      obj->value[1] += diff;
      source->value[1] -= diff;
      act( AT_ACTION, "You fill $p from $P.", ch, obj, source, TO_CHAR );
      act( AT_ACTION, "$n fills $p from $P.", ch, obj, source, TO_ROOM );
      return;
    case ITEM_DRINK_CON:
      if( obj->value[1] != 0 && obj->value[2] != source->value[2] )
      {
        send_to_char( "There is already another liquid in it.\n\r", ch );
        return;
      }
      obj->value[2] = source->value[2];
      if( source->value[1] < diff )
        diff = source->value[1];
      obj->value[1] += diff;
      source->value[1] -= diff;
      act( AT_ACTION, "You fill $p from $P.", ch, obj, source, TO_CHAR );
      act( AT_ACTION, "$n fills $p from $P.", ch, obj, source, TO_ROOM );
      return;
  }
}

void do_drink( CHAR_DATA * ch, char *argument )
{
  char arg[MAX_INPUT_LENGTH];
  OBJ_DATA *obj;
  int amount;
  int liquid;

  argument = one_argument( argument, arg );
  /*
   * munch optional words 
   */
  if( !str_cmp( arg, "from" ) && argument[0] != '\0' )
    argument = one_argument( argument, arg );

  if( arg[0] == '\0' )
  {
    for( obj = ch->in_room->first_content; obj; obj = obj->next_content )
      if( ( obj->item_type == ITEM_FOUNTAIN ) || ( obj->item_type == ITEM_BLOOD ) )
        break;

    if( !obj )
    {
      send_to_char( "Drink what?\n\r", ch );
      return;
    }
  }
  else
  {
    if( ( obj = get_obj_here( ch, arg ) ) == NULL )
    {
      send_to_char( "You can't find it.\n\r", ch );
      return;
    }
  }

  if( obj->count > 1 && obj->item_type != ITEM_FOUNTAIN )
    separate_obj( obj );

  if( !IS_NPC( ch ) && ch->pcdata->condition[COND_DRUNK] > 40 )
  {
    send_to_char( "You fail to reach your mouth.  *Hic*\n\r", ch );
    return;
  }

  switch ( obj->item_type )
  {
    default:
      if( obj->carried_by == ch )
      {
        act( AT_ACTION, "$n lifts $p up to $s mouth and tries to drink from it...", ch, obj, NULL, TO_ROOM );
        act( AT_ACTION, "You bring $p up to your mouth and try to drink from it...", ch, obj, NULL, TO_CHAR );
      }
      else
      {
        act( AT_ACTION, "$n gets down and tries to drink from $p... (Is $e feeling ok?)", ch, obj, NULL, TO_ROOM );
        act( AT_ACTION, "You get down on the ground and try to drink from $p...", ch, obj, NULL, TO_CHAR );
      }
      break;

    case ITEM_POTION:
      if( obj->carried_by == ch )
        do_quaff( ch, obj->name );
      else
        send_to_char( "You're not carrying that.\n\r", ch );
      break;

    case ITEM_BLOOD:
      send_to_char( "It is not in your nature to do such things.\n\r", ch );
      break;

    case ITEM_FOUNTAIN:
      if( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
      {
        act( AT_ACTION, "$n drinks from the fountain.", ch, NULL, NULL, TO_ROOM );
        send_to_char( "You take a long thirst quenching drink.\n\r", ch );
      }

      if( !IS_NPC( ch ) )
        ch->pcdata->condition[COND_THIRST] = 40;
      break;

    case ITEM_DRINK_CON:
      if( obj->value[1] <= 0 )
      {
        send_to_char( "It is already empty.\n\r", ch );
        return;
      }

      if( ( liquid = obj->value[2] ) >= LIQ_MAX )
      {
        bug( "Do_drink: bad liquid number %d.", liquid );
        liquid = obj->value[2] = 0;
      }

      if( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
      {
        act( AT_ACTION, "$n drinks $T from $p.", ch, obj, liq_table[liquid].liq_name, TO_ROOM );
        act( AT_ACTION, "You drink $T from $p.", ch, obj, liq_table[liquid].liq_name, TO_CHAR );
      }

      amount = 1; /* UMIN(amount, obj->value[1]); */
      /*
       * what was this? concentrated drinks?  concentrated water
       * too I suppose... sheesh! 
       */

      gain_condition( ch, COND_DRUNK, amount * liq_table[liquid].liq_affect[COND_DRUNK] );
      gain_condition( ch, COND_FULL, amount * liq_table[liquid].liq_affect[COND_FULL] );
      gain_condition( ch, COND_THIRST, amount * liq_table[liquid].liq_affect[COND_THIRST] );

      if( !IS_NPC( ch ) )
      {
        if( ch->pcdata->condition[COND_DRUNK] > 24 )
          send_to_char( "You feel quite sloshed.\n\r", ch );
        else if( ch->pcdata->condition[COND_DRUNK] > 18 )
          send_to_char( "You feel very drunk.\n\r", ch );
        else if( ch->pcdata->condition[COND_DRUNK] > 12 )
          send_to_char( "You feel drunk.\n\r", ch );
        else if( ch->pcdata->condition[COND_DRUNK] > 8 )
          send_to_char( "You feel a little drunk.\n\r", ch );
        else if( ch->pcdata->condition[COND_DRUNK] > 5 )
          send_to_char( "You feel light headed.\n\r", ch );

        if( ch->pcdata->condition[COND_FULL] > 40 )
          send_to_char( "You are full.\n\r", ch );

        if( ch->pcdata->condition[COND_THIRST] > 40 )
          send_to_char( "You feel bloated.\n\r", ch );
        else if( ch->pcdata->condition[COND_THIRST] > 36 )
          send_to_char( "Your stomach is sloshing around.\n\r", ch );
        else if( ch->pcdata->condition[COND_THIRST] > 30 )
          send_to_char( "You do not feel thirsty.\n\r", ch );
      }

      if( obj->value[3] )
      {
        /*
         * The drink was poisoned! 
         */
        AFFECT_DATA af;

        act( AT_POISON, "$n sputters and gags.", ch, NULL, NULL, TO_ROOM );
        act( AT_POISON, "You sputter and gag.", ch, NULL, NULL, TO_CHAR );
        ch->mental_state = URANGE( 20, ch->mental_state + 5, 100 );
        af.type = gsn_poison;
        af.duration = 3 * obj->value[3];
        af.location = APPLY_NONE;
        af.modifier = 0;
        af.bitvector = meb( AFF_POISON );
        affect_join( ch, &af );
      }

      obj->value[1] -= amount;
      if( obj->value[1] <= 0 )
      {
        send_to_char( "The empty container vanishes.\n\r", ch );
        if( cur_obj == obj->serial )
          global_objcode = rOBJ_DRUNK;
        extract_obj( obj );
      }
      break;
  }
  if( who_fighting( ch ) && IS_PKILL( ch ) )
    WAIT_STATE( ch, PULSE_PER_SECOND / 3 );
  else
    WAIT_STATE( ch, PULSE_PER_SECOND );
  return;
}

void do_eat( CHAR_DATA * ch, char *argument )
{
  char buf[MAX_STRING_LENGTH];
  OBJ_DATA *obj;
  ch_ret retcode;
  int foodcond;
  bool hgflag = TRUE;

  if( argument[0] == '\0' )
  {
    send_to_char( "Eat what?\n\r", ch );
    return;
  }

  if( IS_NPC( ch ) || ch->pcdata->condition[COND_FULL] > 5 )
    if( ms_find_obj( ch ) )
      return;

  if( ( obj = find_obj( ch, argument, TRUE ) ) == NULL )
    return;

  if( !IS_IMMORTAL( ch ) )
  {
    if( ch->position == POS_FIGHTING
        || ch->position == POS_EVASIVE
        || ch->position == POS_DEFENSIVE || ch->position == POS_AGGRESSIVE || ch->position == POS_BERSERK )
    {
      act( AT_ACTION, "$n fumbles with $p, but can't concentrate.", ch, obj, NULL, TO_ROOM );
      act( AT_ACTION, "You fumble with $p but are too preoccupied to do any more.", ch, obj, NULL, TO_CHAR );
      return;
    }

    if( obj->item_type != ITEM_FOOD && obj->item_type != ITEM_PILL && obj->item_type != ITEM_COOK )
    {
      act( AT_ACTION, "$n starts to nibble on $p... ($e must really be hungry)", ch, obj, NULL, TO_ROOM );
      act( AT_ACTION, "You try to nibble on $p...", ch, obj, NULL, TO_CHAR );
      return;
    }

    if( !IS_NPC( ch ) && ch->pcdata->condition[COND_FULL] > 40 && obj->pIndexData->vnum != 2001 )
    {
      send_to_char( "You are too full to eat more.\n\r", ch );
      return;
    }
  }

  if( !IS_NPC( ch ) && ( !IS_PKILL( ch ) || ( IS_PKILL( ch ) && !IS_SET( ch->pcdata->flags, PCFLAG_HIGHGAG ) ) ) )
    hgflag = FALSE;

  /*
   * required due to object grouping 
   */
  separate_obj( obj );
  if( obj->in_obj )
  {
    if( !hgflag )
      act( AT_PLAIN, "You take $p from $P.", ch, obj, obj->in_obj, TO_CHAR );
    act( AT_PLAIN, "$n takes $p from $P.", ch, obj, obj->in_obj, TO_ROOM );
  }
  if( ch->fighting && number_percent(  ) > ( get_curr_dex( ch ) * 2 + 47 ) )
  {
    sprintf( buf, "%s",
             ( ch->in_room->sector_type == SECT_UNDERWATER ||
               ch->in_room->sector_type == SECT_WATER_SWIM ||
               ch->in_room->sector_type == SECT_WATER_NOSWIM ) ? "dissolves in the water" :
             ( ch->in_room->sector_type == SECT_AIR ||
               xIS_SET( ch->in_room->room_flags, ROOM_NOFLOOR ) ) ? "falls far below" : "is trampled underfoot" );
    act( AT_MAGIC, "$n drops $p, and it $T.", ch, obj, buf, TO_ROOM );
    if( !hgflag )
      act( AT_MAGIC, "Oops, $p slips from your hand and $T!", ch, obj, buf, TO_CHAR );
  }
  else
  {
    if( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
    {
      if( !obj->action_desc || obj->action_desc[0] == '\0' )
      {
        act( AT_ACTION, "$n eats $p.", ch, obj, NULL, TO_ROOM );
        if( !hgflag )
          act( AT_ACTION, "You eat $p.", ch, obj, NULL, TO_CHAR );
      }
      else
        actiondesc( ch, obj, NULL );
    }

    switch ( obj->item_type )
    {

      case ITEM_COOK:
      case ITEM_FOOD:
        WAIT_STATE( ch, PULSE_PER_SECOND / 3 );
        if( obj->timer > 0 && obj->value[1] > 0 )
          foodcond = ( obj->timer * 10 ) / obj->value[1];
        else
          foodcond = 10;

        if( !IS_NPC( ch ) )
        {
          int condition;

          condition = ch->pcdata->condition[COND_FULL];
          gain_condition( ch, COND_FULL, ( obj->value[0] * foodcond ) / 10 );
          if( condition <= 1 && ch->pcdata->condition[COND_FULL] > 1 )
            send_to_char( "You are no longer hungry.\n\r", ch );
          else if( ch->pcdata->condition[COND_FULL] > 40 )
            send_to_char( "You are full.\n\r", ch );
        }

        if( obj->value[2] != 0 )
        {
          ch->mental_state += obj->value[2];
          if( ch->mental_state > 100 )
            ch->mental_state = 100;
        }

        if( obj->value[3] != 0
            || ( foodcond < 4 && number_range( 0, foodcond + 1 ) == 0 )
            || ( obj->item_type == ITEM_COOK && obj->value[2] == 0 ) )
        {
          /*
           * The food was poisoned! 
           */
          AFFECT_DATA af;

          if( obj->value[3] != 0 )
          {
            act( AT_POISON, "$n chokes and gags.", ch, NULL, NULL, TO_ROOM );
            act( AT_POISON, "You choke and gag.", ch, NULL, NULL, TO_CHAR );
            ch->mental_state = URANGE( 20, ch->mental_state + 5, 100 );
          }
          else
          {
            act( AT_POISON, "$n gags on $p.", ch, obj, NULL, TO_ROOM );
            act( AT_POISON, "You gag on $p.", ch, obj, NULL, TO_CHAR );
            ch->mental_state = URANGE( 15, ch->mental_state + 5, 100 );
          }

          af.type = gsn_poison;
          af.duration = 2 * obj->value[0] * ( obj->value[3] > 0 ? obj->value[3] : 1 );
          af.location = APPLY_NONE;
          af.modifier = 0;
          af.bitvector = meb( AFF_POISON );
          affect_join( ch, &af );
        }
        break;

      case ITEM_PILL:
        sysdata.upill_val += obj->cost / 100;
        if( who_fighting( ch ) && IS_PKILL( ch ) )
          WAIT_STATE( ch, PULSE_PER_SECOND / 4 );
        else
          WAIT_STATE( ch, PULSE_PER_SECOND / 3 );
        /*
         * allow pills to fill you, if so desired 
         */
        if( !IS_NPC( ch ) && obj->value[4] )
        {
          int condition;

          condition = ch->pcdata->condition[COND_FULL];
          gain_condition( ch, COND_FULL, obj->value[4] );
          if( condition <= 1 && ch->pcdata->condition[COND_FULL] > 1 )
            send_to_char( "You are no longer hungry.\n\r", ch );
          else if( ch->pcdata->condition[COND_FULL] > 40 )
            send_to_char( "You are full.\n\r", ch );
        }
        retcode = obj_cast_spell( obj->value[1], obj->value[0], ch, ch, NULL );
        if( retcode == rNONE )
          retcode = obj_cast_spell( obj->value[2], obj->value[0], ch, ch, NULL );
        if( retcode == rNONE )
          retcode = obj_cast_spell( obj->value[3], obj->value[0], ch, ch, NULL );
        break;
    }

  }
  if( obj->serial == cur_obj )
    global_objcode = rOBJ_EATEN;
  extract_obj( obj );
  return;
}

void do_quaff( CHAR_DATA * ch, char *argument )
{
  OBJ_DATA *obj;
  ch_ret retcode;
  bool hgflag = TRUE;

  if( argument[0] == '\0' || !str_cmp( argument, "" ) )
  {
    send_to_char( "Quaff what?\n\r", ch );
    return;
  }

  if( ( obj = find_obj( ch, argument, TRUE ) ) == NULL )
    return;

  if( !IS_NPC( ch ) && IS_AFFECTED( ch, AFF_CHARM ) )
    return;

  if( obj->item_type != ITEM_POTION )
  {
    if( obj->item_type == ITEM_DRINK_CON )
      do_drink( ch, obj->name );
    else
    {
      act( AT_ACTION, "$n lifts $p up to $s mouth and tries to drink from it...", ch, obj, NULL, TO_ROOM );
      act( AT_ACTION, "You bring $p up to your mouth and try to drink from it...", ch, obj, NULL, TO_CHAR );
    }
    return;
  }

  /*
   * Empty container check          -Shaddai
   */
  if( obj->value[1] == -1 && obj->value[2] == -1 && obj->value[3] == -1 )
  {
    send_to_char( "You suck in nothing but air.\n\r", ch );
    return;
  }
  /*
   * Fullness checking          -Thoric
   */
  if( !IS_NPC( ch ) && ( ch->pcdata->condition[COND_FULL] >= 48 || ch->pcdata->condition[COND_THIRST] >= 48 ) )
  {
    send_to_char( "Your stomach cannot contain any more.\n\r", ch );
    return;
  }

  /*
   * People with nuisance flag feels up quicker. -- Shaddai 
   */
  /*
   * Yeah so I can't spell I'm a coder :P --Shaddai 
   */
  /*
   * You are now adept at feeling up quickly! -- Blod 
   */
  if( !IS_NPC( ch ) && ch->pcdata->nuisance &&
      ch->pcdata->nuisance->flags > 3
      && ( ch->pcdata->condition[COND_FULL] >= ( 48 - ( 3 * ch->pcdata->nuisance->flags ) +
                                                 ch->pcdata->nuisance->power )
           || ch->pcdata->condition[COND_THIRST] >= ( 48 - ( ch->pcdata->nuisance->flags ) +
                                                      ch->pcdata->nuisance->power ) ) )
  {
    send_to_char( "Your stomach cannot contain any more.\n\r", ch );
    return;
  }

  if( !IS_NPC( ch ) && ( !IS_PKILL( ch ) || ( IS_PKILL( ch ) && !IS_SET( ch->pcdata->flags, PCFLAG_HIGHGAG ) ) ) )
    hgflag = FALSE;

  separate_obj( obj );
  if( obj->in_obj )
  {
    if( !CAN_PKILL( ch ) )
    {
      act( AT_PLAIN, "You take $p from $P.", ch, obj, obj->in_obj, TO_CHAR );
      act( AT_PLAIN, "$n takes $p from $P.", ch, obj, obj->in_obj, TO_ROOM );
    }
  }

  /*
   * If fighting, chance of dropping potion     -Thoric
   */
  if( ch->fighting && number_percent(  ) > ( get_curr_dex( ch ) * 2 + 48 ) )
  {
    act( AT_MAGIC, "$n fumbles $p and shatters it into fragments.", ch, obj, NULL, TO_ROOM );
    if( !hgflag )
      act( AT_MAGIC, "Oops... $p is knocked from your hand and shatters!", ch, obj, NULL, TO_CHAR );
  }
  else
  {
    if( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
    {
      if( !CAN_PKILL( ch ) || !obj->in_obj )
      {
        act( AT_ACTION, "$n quaffs $p.", ch, obj, NULL, TO_ROOM );
        if( !hgflag )
          act( AT_ACTION, "You quaff $p.", ch, obj, NULL, TO_CHAR );
      }
      else if( obj->in_obj )
      {
        act( AT_ACTION, "$n quaffs $p from $P.", ch, obj, obj->in_obj, TO_ROOM );
        if( !hgflag )
          act( AT_ACTION, "You quaff $p from $P.", ch, obj, obj->in_obj, TO_CHAR );
      }
    }

    if( who_fighting( ch ) && IS_PKILL( ch ) )
      WAIT_STATE( ch, PULSE_PER_SECOND / 5 );
    else
      WAIT_STATE( ch, PULSE_PER_SECOND / 3 );

    gain_condition( ch, COND_THIRST, 1 );
    if( !IS_NPC( ch ) && ch->pcdata->condition[COND_THIRST] > 43 )
      act( AT_ACTION, "Your stomach is nearing its capacity.", ch, NULL, NULL, TO_CHAR );
    retcode = obj_cast_spell( obj->value[1], obj->value[0], ch, ch, NULL );
    if( retcode == rNONE )
      retcode = obj_cast_spell( obj->value[2], obj->value[0], ch, ch, NULL );
    if( retcode == rNONE )
      retcode = obj_cast_spell( obj->value[3], obj->value[0], ch, ch, NULL );
  }
  if( obj->pIndexData->vnum == OBJ_VNUM_FLASK_BREWING )
    sysdata.brewed_used++;
  else
    sysdata.upotion_val += obj->cost / 100;
  if( cur_obj == obj->serial )
    global_objcode = rOBJ_QUAFFED;
  extract_obj( obj );
  return;
}


void do_recite( CHAR_DATA * ch, char *argument )
{
  char arg1[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];
  CHAR_DATA *victim;
  OBJ_DATA *scroll;
  OBJ_DATA *obj;
  ch_ret retcode;

  argument = one_argument( argument, arg1 );
  argument = one_argument( argument, arg2 );

  if( arg1[0] == '\0' )
  {
    send_to_char( "Recite what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( scroll = get_obj_carry( ch, arg1 ) ) == NULL )
  {
    send_to_char( "You do not have that scroll.\n\r", ch );
    return;
  }

  if( scroll->item_type != ITEM_SCROLL )
  {
    act( AT_ACTION, "$n holds up $p as if to recite something from it...", ch, scroll, NULL, TO_ROOM );
    act( AT_ACTION, "You hold up $p and stand there with your mouth open.  (Now what?)", ch, scroll, NULL, TO_CHAR );
    return;
  }

  if( IS_NPC( ch ) && ( scroll->pIndexData->vnum == OBJ_VNUM_SCROLL_SCRIBING ) )
  {
    send_to_char( "As a mob, this dialect is foreign to you.\n\r", ch );
    return;
  }

  if( ( scroll->pIndexData->vnum == OBJ_VNUM_SCROLL_SCRIBING ) && ( ch->level + 10 < scroll->value[0] ) )
  {
    send_to_char( "This scroll is too complex for you to understand.\n\r", ch );
    return;
  }

  obj = NULL;
  if( arg2[0] == '\0' )
    victim = ch;
  else
  {
    if( ( victim = get_char_room( ch, arg2 ) ) == NULL && ( obj = get_obj_here( ch, arg2 ) ) == NULL )
    {
      send_to_char( "You can't find it.\n\r", ch );
      return;
    }
  }

  if( scroll->pIndexData->vnum == OBJ_VNUM_SCROLL_SCRIBING )
    sysdata.scribed_used++;
  separate_obj( scroll );
  act( AT_MAGIC, "$n recites $p.", ch, scroll, NULL, TO_ROOM );
  act( AT_MAGIC, "You recite $p.", ch, scroll, NULL, TO_CHAR );

  if( victim != ch )
    WAIT_STATE( ch, 2 * PULSE_VIOLENCE );
  else
    WAIT_STATE( ch, PULSE_PER_SECOND / 2 );

  retcode = obj_cast_spell( scroll->value[1], scroll->value[0], ch, victim, obj );
  if( retcode == rNONE )
    retcode = obj_cast_spell( scroll->value[2], scroll->value[0], ch, victim, obj );
  if( retcode == rNONE )
    retcode = obj_cast_spell( scroll->value[3], scroll->value[0], ch, victim, obj );

  if( scroll->serial == cur_obj )
    global_objcode = rOBJ_USED;
  extract_obj( scroll );
  return;
}


/*
 * Function to handle the state changing of a triggerobject (lever)  -Thoric
 */
void pullorpush( CHAR_DATA * ch, OBJ_DATA * obj, bool pull )
{
  char buf[MAX_STRING_LENGTH];
  CHAR_DATA *rch;
  bool isup;
  ROOM_INDEX_DATA *room, *to_room;
  EXIT_DATA *pexit, *pexit_rev;
  int edir;
  char *txt;

  if( IS_SET( obj->value[0], TRIG_UP ) )
    isup = TRUE;
  else
    isup = FALSE;
  switch ( obj->item_type )
  {
    default:
      sprintf( buf, "You can't %s that!\n\r", pull ? "pull" : "push" );
      send_to_char( buf, ch );
      return;
      break;
    case ITEM_SWITCH:
    case ITEM_LEVER:
    case ITEM_PULLCHAIN:
      if( ( !pull && isup ) || ( pull && !isup ) )
      {
        sprintf( buf, "It is already %s.\n\r", isup ? "up" : "down" );
        send_to_char( buf, ch );
        return;
      }
    case ITEM_BUTTON:
      if( ( !pull && isup ) || ( pull && !isup ) )
      {
        sprintf( buf, "It is already %s.\n\r", isup ? "in" : "out" );
        send_to_char( buf, ch );
        return;
      }
      break;
  }
  if( ( pull ) && HAS_PROG( obj->pIndexData, PULL_PROG ) )
  {
    if( !IS_SET( obj->value[0], TRIG_AUTORETURN ) )
      REMOVE_BIT( obj->value[0], TRIG_UP );
    oprog_pull_trigger( ch, obj );
    return;
  }
  if( ( !pull ) && HAS_PROG( obj->pIndexData, PUSH_PROG ) )
  {
    if( !IS_SET( obj->value[0], TRIG_AUTORETURN ) )
      SET_BIT( obj->value[0], TRIG_UP );
    oprog_push_trigger( ch, obj );
    return;
  }

  if( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
  {
    sprintf( buf, "$n %s $p.", pull ? "pulls" : "pushes" );
    act( AT_ACTION, buf, ch, obj, NULL, TO_ROOM );
    sprintf( buf, "You %s $p.", pull ? "pull" : "push" );
    act( AT_ACTION, buf, ch, obj, NULL, TO_CHAR );
  }

  if( !IS_SET( obj->value[0], TRIG_AUTORETURN ) )
  {
    if( pull )
      REMOVE_BIT( obj->value[0], TRIG_UP );
    else
      SET_BIT( obj->value[0], TRIG_UP );
  }
  if( IS_SET( obj->value[0], TRIG_TELEPORT )
      || IS_SET( obj->value[0], TRIG_TELEPORTALL ) || IS_SET( obj->value[0], TRIG_TELEPORTPLUS ) )
  {
    int flags;

    if( ( room = get_room_index( obj->value[1] ) ) == NULL )
    {
      bug( "PullOrPush: obj points to invalid room %d", obj->value[1] );
      return;
    }
    flags = 0;
    if( IS_SET( obj->value[0], TRIG_SHOWROOMDESC ) )
      SET_BIT( flags, TELE_SHOWDESC );
    if( IS_SET( obj->value[0], TRIG_TELEPORTALL ) )
      SET_BIT( flags, TELE_TRANSALL );
    if( IS_SET( obj->value[0], TRIG_TELEPORTPLUS ) )
      SET_BIT( flags, TELE_TRANSALLPLUS );

    teleport( ch, obj->value[1], flags );
    return;
  }

  if( IS_SET( obj->value[0], TRIG_RAND4 ) || IS_SET( obj->value[0], TRIG_RAND6 ) )
  {
    int maxd;

    if( ( room = get_room_index( obj->value[1] ) ) == NULL )
    {
      bug( "PullOrPush: obj points to invalid room %d", obj->value[1] );
      return;
    }

    if( IS_SET( obj->value[0], TRIG_RAND4 ) )
      maxd = 3;
    else
      maxd = 5;

    randomize_exits( room, maxd );
    for( rch = room->first_person; rch; rch = rch->next_in_room )
    {
      send_to_char( "You hear a loud rumbling sound.\n\r", rch );
      send_to_char( "Something seems different...\n\r", rch );
    }
  }
  if( IS_SET( obj->value[0], TRIG_DOOR ) )
  {
    room = get_room_index( obj->value[1] );
    if( !room )
      room = obj->in_room;
    if( !room )
    {
      bug( "PullOrPush: obj points to invalid room %d", obj->value[1] );
      return;
    }
    if( IS_SET( obj->value[0], TRIG_D_NORTH ) )
    {
      edir = DIR_NORTH;
      txt = "to the north";
    }
    else if( IS_SET( obj->value[0], TRIG_D_SOUTH ) )
    {
      edir = DIR_SOUTH;
      txt = "to the south";
    }
    else if( IS_SET( obj->value[0], TRIG_D_EAST ) )
    {
      edir = DIR_EAST;
      txt = "to the east";
    }
    else if( IS_SET( obj->value[0], TRIG_D_WEST ) )
    {
      edir = DIR_WEST;
      txt = "to the west";
    }
    else if( IS_SET( obj->value[0], TRIG_D_UP ) )
    {
      edir = DIR_UP;
      txt = "from above";
    }
    else if( IS_SET( obj->value[0], TRIG_D_DOWN ) )
    {
      edir = DIR_DOWN;
      txt = "from below";
    }
    else
    {
      bug( "PullOrPush: door: no direction flag set.", 0 );
      return;
    }
    pexit = get_exit( room, edir );
    if( !pexit )
    {
      if( !IS_SET( obj->value[0], TRIG_PASSAGE ) )
      {
        bug( "PullOrPush: obj points to non-exit %d", obj->value[1] );
        return;
      }
      to_room = get_room_index( obj->value[2] );
      if( !to_room )
      {
        bug( "PullOrPush: dest points to invalid room %d", obj->value[2] );
        return;
      }
      pexit = make_exit( room, to_room, edir );
      pexit->keyword = STRALLOC( "" );
      pexit->description = STRALLOC( "" );
      pexit->key = -1;
      pexit->exit_info = 0;
      top_exit++;
      act( AT_PLAIN, "A passage opens!", ch, NULL, NULL, TO_CHAR );
      act( AT_PLAIN, "A passage opens!", ch, NULL, NULL, TO_ROOM );
      return;
    }
    if( IS_SET( obj->value[0], TRIG_UNLOCK ) && IS_SET( pexit->exit_info, EX_LOCKED ) )
    {
      REMOVE_BIT( pexit->exit_info, EX_LOCKED );
      act( AT_PLAIN, "You hear a faint click $T.", ch, NULL, txt, TO_CHAR );
      act( AT_PLAIN, "You hear a faint click $T.", ch, NULL, txt, TO_ROOM );
      if( ( pexit_rev = pexit->rexit ) != NULL && pexit_rev->to_room == ch->in_room )
        REMOVE_BIT( pexit_rev->exit_info, EX_LOCKED );
      return;
    }
    if( IS_SET( obj->value[0], TRIG_LOCK ) && !IS_SET( pexit->exit_info, EX_LOCKED ) )
    {
      SET_BIT( pexit->exit_info, EX_LOCKED );
      act( AT_PLAIN, "You hear a faint click $T.", ch, NULL, txt, TO_CHAR );
      act( AT_PLAIN, "You hear a faint click $T.", ch, NULL, txt, TO_ROOM );
      if( ( pexit_rev = pexit->rexit ) != NULL && pexit_rev->to_room == ch->in_room )
        SET_BIT( pexit_rev->exit_info, EX_LOCKED );
      return;
    }
    if( IS_SET( obj->value[0], TRIG_OPEN ) && IS_SET( pexit->exit_info, EX_CLOSED ) )
    {
      REMOVE_BIT( pexit->exit_info, EX_CLOSED );
      for( rch = room->first_person; rch; rch = rch->next_in_room )
        act( AT_ACTION, "The $d opens.", rch, NULL, pexit->keyword, TO_CHAR );
      if( ( pexit_rev = pexit->rexit ) != NULL && pexit_rev->to_room == ch->in_room )
      {
        REMOVE_BIT( pexit_rev->exit_info, EX_CLOSED );
        /*
         * bug here pointed out by Nick Gammon 
         */
        for( rch = pexit->to_room->first_person; rch; rch = rch->next_in_room )
          act( AT_ACTION, "The $d opens.", rch, NULL, pexit_rev->keyword, TO_CHAR );
      }
      check_room_for_traps( ch, trap_door[edir] );
      return;
    }
    if( IS_SET( obj->value[0], TRIG_CLOSE ) && !IS_SET( pexit->exit_info, EX_CLOSED ) )
    {
      SET_BIT( pexit->exit_info, EX_CLOSED );
      for( rch = room->first_person; rch; rch = rch->next_in_room )
        act( AT_ACTION, "The $d closes.", rch, NULL, pexit->keyword, TO_CHAR );
      if( ( pexit_rev = pexit->rexit ) != NULL && pexit_rev->to_room == ch->in_room )
      {
        SET_BIT( pexit_rev->exit_info, EX_CLOSED );
        /*
         * bug here pointed out by Nick Gammon 
         */
        for( rch = pexit->to_room->first_person; rch; rch = rch->next_in_room )
          act( AT_ACTION, "The $d closes.", rch, NULL, pexit_rev->keyword, TO_CHAR );
      }
      check_room_for_traps( ch, trap_door[edir] );
      return;
    }
  }
}


void do_pull( CHAR_DATA * ch, char *argument )
{
  char arg[MAX_INPUT_LENGTH];
  OBJ_DATA *obj;

  one_argument( argument, arg );
  if( arg[0] == '\0' )
  {
    send_to_char( "Pull what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( !IS_NPC( ch ) && !str_cmp( arg, "tail" ) )
  { /*
     * if (xIS_SET((ch)->affected_by, AFF_OOZARU) ||
     * xIS_SET((ch)->affected_by, AFF_GOLDEN_OOZARU))
     * {
     * ch_printf(ch,"You can't do that while in a beserked Oozaru state.\n\r");
     * return;
     * } */
    if( xIS_SET( ( ch )->affected_by, AFF_OOZARU ) )
    {
      ch_printf( ch, "You can't do that while in a beserked Oozaru state.\n\r" );
      return;
    }
    if( xIS_SET( ch->affected_by, AFF_SSJ4 ) )
    {
      ch_printf( ch, "You can't do that while SSJ4.\n\r" );
      return;
    }
    if( ( is_saiyan( ch ) || is_hb( ch ) ) && ch->pcdata->tail > 0 )
    {
      remove_tail( ch );
      ch->hit -= 5;
      update_pos( ch );
      return;
    }
    else
    {
      act( AT_PLAIN, "You don't have a tail you can remove.", ch, NULL, arg, TO_CHAR );
      return;
    }
  }

  if( ( obj = get_obj_here( ch, arg ) ) == NULL )
  {
    act( AT_PLAIN, "I see no $T here.", ch, NULL, arg, TO_CHAR );
    return;
  }

  pullorpush( ch, obj, TRUE );
}

void do_push( CHAR_DATA * ch, char *argument )
{
  char arg[MAX_INPUT_LENGTH];
  OBJ_DATA *obj;

  one_argument( argument, arg );
  if( arg[0] == '\0' )
  {
    send_to_char( "Push what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( obj = get_obj_here( ch, arg ) ) == NULL )
  {
    act( AT_PLAIN, "I see no $T here.", ch, NULL, arg, TO_CHAR );
    return;
  }

  pullorpush( ch, obj, FALSE );
}

void do_rap( CHAR_DATA * ch, char *argument )
{
  EXIT_DATA *pexit;
  char arg[MAX_INPUT_LENGTH];

  one_argument( argument, arg );

  if( arg[0] == '\0' )
  {
    send_to_char( "Rap on what?\n\r", ch );
    return;
  }
  if( ch->fighting )
  {
    send_to_char( "You have better things to do with your hands right now.\n\r", ch );
    return;
  }
  if( ( pexit = find_door( ch, arg, FALSE ) ) != NULL )
  {
    ROOM_INDEX_DATA *to_room;
    EXIT_DATA *pexit_rev;
    char *keyword;
    if( !IS_SET( pexit->exit_info, EX_CLOSED ) )
    {
      send_to_char( "Why knock?  It's open.\n\r", ch );
      return;
    }
    if( IS_SET( pexit->exit_info, EX_SECRET ) )
      keyword = "wall";
    else
      keyword = pexit->keyword;
    act( AT_ACTION, "You rap loudly on the $d.", ch, NULL, keyword, TO_CHAR );
    act( AT_ACTION, "$n raps loudly on the $d.", ch, NULL, keyword, TO_ROOM );
    if( ( to_room = pexit->to_room ) != NULL && ( pexit_rev = pexit->rexit ) != NULL && pexit_rev->to_room == ch->in_room )
    {
      CHAR_DATA *rch;
      for( rch = to_room->first_person; rch; rch = rch->next_in_room )
      {
        act( AT_ACTION, "Someone raps loudly from the other side of the $d.", rch, NULL, pexit_rev->keyword, TO_CHAR );
      }
    }
  }
  else
  {
    act( AT_ACTION, "You make knocking motions through the air.", ch, NULL, NULL, TO_CHAR );
    act( AT_ACTION, "$n makes knocking motions through the air.", ch, NULL, NULL, TO_ROOM );
  }
  return;
}

/* pipe commands (light, tamp, smoke) by Thoric */
void do_tamp( CHAR_DATA * ch, char *argument )
{
  OBJ_DATA *pipe;
  char arg[MAX_INPUT_LENGTH];

  one_argument( argument, arg );
  if( arg[0] == '\0' )
  {
    send_to_char( "Tamp what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( pipe = get_obj_carry( ch, arg ) ) == NULL )
  {
    send_to_char( "You aren't carrying that.\n\r", ch );
    return;
  }
  if( pipe->item_type != ITEM_PIPE )
  {
    send_to_char( "You can't tamp that.\n\r", ch );
    return;
  }
  if( !IS_SET( pipe->value[3], PIPE_TAMPED ) )
  {
    act( AT_ACTION, "You gently tamp $p.", ch, pipe, NULL, TO_CHAR );
    act( AT_ACTION, "$n gently tamps $p.", ch, pipe, NULL, TO_ROOM );
    SET_BIT( pipe->value[3], PIPE_TAMPED );
    return;
  }
  send_to_char( "It doesn't need tamping.\n\r", ch );
}

void do_smoke( CHAR_DATA * ch, char *argument )
{
  OBJ_DATA *pipe;
  char arg[MAX_INPUT_LENGTH];

  send_to_char( "Don't you know?  Smoking is bad for your health.\n\r", ch );
  return;

  one_argument( argument, arg );
  if( arg[0] == '\0' )
  {
    send_to_char( "Smoke what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( pipe = get_obj_carry( ch, arg ) ) == NULL )
  {
    send_to_char( "You aren't carrying that.\n\r", ch );
    return;
  }
  if( pipe->item_type != ITEM_PIPE )
  {
    act( AT_ACTION, "You try to smoke $p... but it doesn't seem to work.", ch, pipe, NULL, TO_CHAR );
    act( AT_ACTION, "$n tries to smoke $p... (I wonder what $e's been putting in $s pipe?)", ch, pipe, NULL, TO_ROOM );
    return;
  }
  if( !IS_SET( pipe->value[3], PIPE_LIT ) )
  {
    act( AT_ACTION, "You try to smoke $p, but it's not lit.", ch, pipe, NULL, TO_CHAR );
    act( AT_ACTION, "$n tries to smoke $p, but it's not lit.", ch, pipe, NULL, TO_ROOM );
    return;
  }
  if( pipe->value[1] > 0 )
  {
    if( !oprog_use_trigger( ch, pipe, NULL, NULL, NULL ) )
    {
      act( AT_ACTION, "You draw thoughtfully from $p.", ch, pipe, NULL, TO_CHAR );
      act( AT_ACTION, "$n draws thoughtfully from $p.", ch, pipe, NULL, TO_ROOM );
    }

    if( IS_VALID_HERB( pipe->value[2] ) && pipe->value[2] < top_herb )
    {
      int sn = pipe->value[2] + TYPE_HERB;
      SKILLTYPE *skill = get_skilltype( sn );

      WAIT_STATE( ch, skill->beats );
      if( skill->spell_fun )
//    obj_cast_spell( sn, UMIN(skill->min_level, ch->level), ch, ch, NULL );
        if( obj_extracted( pipe ) )
          return;
    }
    else
      bug( "do_smoke: bad herb type %d", pipe->value[2] );

    SET_BIT( pipe->value[3], PIPE_HOT );
    if( --pipe->value[1] < 1 )
    {
      REMOVE_BIT( pipe->value[3], PIPE_LIT );
      SET_BIT( pipe->value[3], PIPE_DIRTY );
      SET_BIT( pipe->value[3], PIPE_FULLOFASH );
    }
  }
}

void do_light( CHAR_DATA * ch, char *argument )
{
  OBJ_DATA *pipe;
  char arg[MAX_INPUT_LENGTH];

  one_argument( argument, arg );
  if( arg[0] == '\0' )
  {
    send_to_char( "Light what?\n\r", ch );
    return;
  }

  if( ms_find_obj( ch ) )
    return;

  if( ( pipe = get_obj_carry( ch, arg ) ) == NULL )
  {
    send_to_char( "You aren't carrying that.\n\r", ch );
    return;
  }
  if( pipe->item_type != ITEM_PIPE )
  {
    send_to_char( "You can't light that.\n\r", ch );
    return;
  }
  if( !IS_SET( pipe->value[3], PIPE_LIT ) )
  {
    if( pipe->value[1] < 1 )
    {
      act( AT_ACTION, "You try to light $p, but it's empty.", ch, pipe, NULL, TO_CHAR );
      act( AT_ACTION, "$n tries to light $p, but it's empty.", ch, pipe, NULL, TO_ROOM );
      return;
    }
    act( AT_ACTION, "You carefully light $p.", ch, pipe, NULL, TO_CHAR );
    act( AT_ACTION, "$n carefully lights $p.", ch, pipe, NULL, TO_ROOM );
    SET_BIT( pipe->value[3], PIPE_LIT );
    return;
  }
  send_to_char( "It's already lit.\n\r", ch );
}

void do_empty( CHAR_DATA * ch, char *argument )
{
  OBJ_DATA *obj;
  char arg1[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];

  argument = one_argument( argument, arg1 );
  argument = one_argument( argument, arg2 );
  if( !str_cmp( arg2, "into" ) && argument[0] != '\0' )
    argument = one_argument( argument, arg2 );

  if( arg1[0] == '\0' )
  {
    send_to_char( "Empty what?\n\r", ch );
    return;
  }
  if( ms_find_obj( ch ) )
    return;

  if( ( obj = get_obj_carry( ch, arg1 ) ) == NULL )
  {
    send_to_char( "You aren't carrying that.\n\r", ch );
    return;
  }
  if( obj->count > 1 )
    separate_obj( obj );

  switch ( obj->item_type )
  {
    default:
      act( AT_ACTION, "You shake $p in an attempt to empty it...", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n begins to shake $p in an attempt to empty it...", ch, obj, NULL, TO_ROOM );
      return;
    case ITEM_PIPE:
      act( AT_ACTION, "You gently tap $p and empty it out.", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n gently taps $p and empties it out.", ch, obj, NULL, TO_ROOM );
      REMOVE_BIT( obj->value[3], PIPE_FULLOFASH );
      REMOVE_BIT( obj->value[3], PIPE_LIT );
      obj->value[1] = 0;
      return;
    case ITEM_DRINK_CON:
      if( obj->value[1] < 1 )
      {
        send_to_char( "It's already empty.\n\r", ch );
        return;
      }
      act( AT_ACTION, "You empty $p.", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n empties $p.", ch, obj, NULL, TO_ROOM );
      obj->value[1] = 0;
      save_house_by_vnum( ch->in_room->vnum );  /* House Object Saving */
      return;
    case ITEM_CONTAINER:
    case ITEM_QUIVER:
      if( IS_SET( obj->value[1], CONT_CLOSED ) )
      {
        act( AT_PLAIN, "The $d is closed.", ch, NULL, obj->name, TO_CHAR );
        return;
      }
    case ITEM_KEYRING:
      if( !obj->first_content )
      {
        send_to_char( "It's already empty.\n\r", ch );
        return;
      }
      if( arg2[0] == '\0' )
      {
        if( xIS_SET( ch->in_room->room_flags, ROOM_NODROP ) || ( !IS_NPC( ch ) || xIS_SET( ch->act, PLR_LITTERBUG ) ) )
        {
          set_char_color( AT_MAGIC, ch );
          send_to_char( "A magical force stops you!\n\r", ch );
          set_char_color( AT_TELL, ch );
          send_to_char( "Someone tells you, 'No littering here!'\n\r", ch );
          return;
        }
        if( xIS_SET( ch->in_room->room_flags, ROOM_NODROPALL ) || xIS_SET( ch->in_room->room_flags, ROOM_CLANSTOREROOM ) )
        {
          send_to_char( "You can't seem to do that here...\n\r", ch );
          return;
        }
        if( empty_obj( obj, NULL, ch->in_room ) )
        {
          act( AT_ACTION, "You empty $p.", ch, obj, NULL, TO_CHAR );
          act( AT_ACTION, "$n empties $p.", ch, obj, NULL, TO_ROOM );
          if( IS_SET( sysdata.save_flags, SV_EMPTY ) )
            save_char_obj( ch );
        }
        else
          send_to_char( "Hmmm... didn't work.\n\r", ch );
      }
      else
      {
        OBJ_DATA *dest = get_obj_here( ch, arg2 );

        if( !dest )
        {
          send_to_char( "You can't find it.\n\r", ch );
          return;
        }
        if( dest == obj )
        {
          send_to_char( "You can't empty something into itself!\n\r", ch );
          return;
        }
        if( dest->item_type != ITEM_CONTAINER && dest->item_type != ITEM_KEYRING && dest->item_type != ITEM_QUIVER )
        {
          send_to_char( "That's not a container!\n\r", ch );
          return;
        }
        if( IS_SET( dest->value[1], CONT_CLOSED ) )
        {
          act( AT_PLAIN, "The $d is closed.", ch, NULL, dest->name, TO_CHAR );
          return;
        }
        separate_obj( dest );
        if( empty_obj( obj, dest, NULL ) )
        {
          act( AT_ACTION, "You empty $p into $P.", ch, obj, dest, TO_CHAR );
          act( AT_ACTION, "$n empties $p into $P.", ch, obj, dest, TO_ROOM );
          if( !dest->carried_by && IS_SET( sysdata.save_flags, SV_EMPTY ) )
            save_char_obj( ch );
        }
        else
          act( AT_ACTION, "$P is too full.", ch, obj, dest, TO_CHAR );
      }
      return;
  }
}

/*
 * Apply a salve/ointment					-Thoric
 * Support for applying to others.  Pkill concerns dealt with elsewhere.
 */
void do_apply( CHAR_DATA * ch, char *argument )
{

  char arg1[MAX_INPUT_LENGTH];
  char arg2[MAX_INPUT_LENGTH];
  CHAR_DATA *victim;
  OBJ_DATA *salve;
  OBJ_DATA *obj;
  ch_ret retcode;

  argument = one_argument( argument, arg1 );
  argument = one_argument( argument, arg2 );
  if( arg1[0] == '\0' )
  {
    send_to_char( "Apply what?\n\r", ch );
    return;
  }
  if( ch->fighting )
  {
    send_to_char( "You're too busy fighting ...\n\r", ch );
    return;
  }
  if( ms_find_obj( ch ) )
    return;
  if( ( salve = get_obj_carry( ch, arg1 ) ) == NULL )
  {
    send_to_char( "You do not have that.\n\r", ch );
    return;
  }

  obj = NULL;
  if( arg2[0] == '\0' )
    victim = ch;
  else
  {
    if( ( victim = get_char_room( ch, arg2 ) ) == NULL && ( obj = get_obj_here( ch, arg2 ) ) == NULL )
    {
      send_to_char( "Apply it to what or who?\n\r", ch );
      return;
    }
  }

  /*
   * apply salve to another object 
   */
  if( obj )
  {
    send_to_char( "You can't do that... yet.\n\r", ch );
    return;
  }

  if( victim->fighting )
  {
    send_to_char( "Wouldn't work very well while they're fighting ...\n\r", ch );
    return;
  }

  if( salve->item_type != ITEM_SALVE )
  {
    if( victim == ch )
    {
      act( AT_ACTION, "$n starts to rub $p on $mself...", ch, salve, NULL, TO_ROOM );
      act( AT_ACTION, "You try to rub $p on yourself...", ch, salve, NULL, TO_CHAR );
    }
    else
    {
      act( AT_ACTION, "$n starts to rub $p on $N...", ch, salve, victim, TO_NOTVICT );
      act( AT_ACTION, "$n starts to rub $p on you...", ch, salve, victim, TO_VICT );
      act( AT_ACTION, "You try to rub $p on $N...", ch, salve, victim, TO_CHAR );
    }
    return;
  }
  separate_obj( salve );
  --salve->value[1];

  if( !oprog_use_trigger( ch, salve, NULL, NULL, NULL ) )
  {
    if( !salve->action_desc || salve->action_desc[0] == '\0' )
    {
      if( salve->value[1] < 1 )
      {
        if( victim != ch )
        {
          act( AT_ACTION, "$n rubs the last of $p onto $N.", ch, salve, victim, TO_NOTVICT );
          act( AT_ACTION, "$n rubs the last of $p onto you.", ch, salve, victim, TO_VICT );
          act( AT_ACTION, "You rub the last of $p onto $N.", ch, salve, victim, TO_CHAR );
        }
        else
        {
          act( AT_ACTION, "You rub the last of $p onto yourself.", ch, salve, NULL, TO_CHAR );
          act( AT_ACTION, "$n rubs the last of $p onto $mself.", ch, salve, NULL, TO_ROOM );
        }
      }
      else
      {
        if( victim != ch )
        {
          act( AT_ACTION, "$n rubs $p onto $N.", ch, salve, victim, TO_NOTVICT );
          act( AT_ACTION, "$n rubs $p onto you.", ch, salve, victim, TO_VICT );
          act( AT_ACTION, "You rub $p onto $N.", ch, salve, victim, TO_CHAR );
        }
        else
        {
          act( AT_ACTION, "You rub $p onto yourself.", ch, salve, NULL, TO_CHAR );
          act( AT_ACTION, "$n rubs $p onto $mself.", ch, salve, NULL, TO_ROOM );
        }
      }
    }
    else
      actiondesc( ch, salve, NULL );
  }

  WAIT_STATE( ch, salve->value[3] );
  retcode = obj_cast_spell( salve->value[4], salve->value[0], ch, victim, NULL );
  if( retcode == rNONE )
    retcode = obj_cast_spell( salve->value[5], salve->value[0], ch, victim, NULL );
  if( retcode == rCHAR_DIED || retcode == rBOTH_DIED )
  {
    bug( "do_apply:  char died", 0 );
    return;
  }

  if( !obj_extracted( salve ) && salve->value[1] <= 0 )
    extract_obj( salve );
  return;
}

/* generate an action description message */
void actiondesc( CHAR_DATA * ch, OBJ_DATA * obj, void *vo )
{
  char charbuf[MAX_STRING_LENGTH];
  char roombuf[MAX_STRING_LENGTH];
  char *srcptr = obj->action_desc;
  char *charptr = charbuf;
  char *roomptr = roombuf;
  const char *ichar = "You";
  const char *iroom = "Someone";

  while( *srcptr != '\0' )
  {
    if( *srcptr == '$' )
    {
      srcptr++;
      switch ( *srcptr )
      {
        case 'e':
          ichar = "you";
          iroom = "$e";
          break;

        case 'm':
          ichar = "you";
          iroom = "$m";
          break;

        case 'n':
          ichar = "you";
          iroom = "$n";
          break;

        case 's':
          ichar = "your";
          iroom = "$s";
          break;

          /*
           * case 'q':
           * iroom = "s";
           * break;
           */

        default:
          srcptr--;
          *charptr++ = *srcptr;
          *roomptr++ = *srcptr;
          break;
      }
    }
    else if( *srcptr == '%' && *++srcptr == 's' )
    {
      ichar = "You";
      iroom = IS_NPC( ch ) ? ch->short_descr : ch->name;
    }
    else
    {
      *charptr++ = *srcptr;
      *roomptr++ = *srcptr;
      srcptr++;
      continue;
    }

    while( ( *charptr = *ichar ) != '\0' )
    {
      charptr++;
      ichar++;
    }

    while( ( *roomptr = *iroom ) != '\0' )
    {
      roomptr++;
      iroom++;
    }
    srcptr++;
  }

  *charptr = '\0';
  *roomptr = '\0';

/*
sprintf( buf, "Charbuf: %s", charbuf );
log_string_plus( buf, LOG_HIGH, LEVEL_LESSER ); 
sprintf( buf, "Roombuf: %s", roombuf );
log_string_plus( buf, LOG_HIGH, LEVEL_LESSER ); 
*/

  switch ( obj->item_type )
  {
    case ITEM_BLOOD:
    case ITEM_FOUNTAIN:
      act( AT_ACTION, charbuf, ch, obj, ch, TO_CHAR );
      act( AT_ACTION, roombuf, ch, obj, ch, TO_ROOM );
      return;

    case ITEM_DRINK_CON:
      act( AT_ACTION, charbuf, ch, obj, liq_table[obj->value[2]].liq_name, TO_CHAR );
      act( AT_ACTION, roombuf, ch, obj, liq_table[obj->value[2]].liq_name, TO_ROOM );
      return;

    case ITEM_PIPE:
      return;

    case ITEM_ARMOR:
    case ITEM_WEAPON:
    case ITEM_LIGHT:
      return;

    case ITEM_COOK:
    case ITEM_FOOD:
    case ITEM_PILL:
      act( AT_ACTION, charbuf, ch, obj, ch, TO_CHAR );
      act( AT_ACTION, roombuf, ch, obj, ch, TO_ROOM );
      return;

    default:
      return;
  }
  return;
}

/*
 * Extended Bitvector Routines					-Thoric
 */

/* check to see if the extended bitvector is completely empty */
bool ext_is_empty( EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    if( bits->bits[x] != 0 )
      return FALSE;

  return TRUE;
}

void ext_clear_bits( EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    bits->bits[x] = 0;
}

/* for use by xHAS_BITS() -- works like IS_SET() */
int ext_has_bits( EXT_BV * var, EXT_BV * bits )
{
  int x, bit;

  for( x = 0; x < XBI; x++ )
    if( ( bit = ( var->bits[x] & bits->bits[x] ) ) != 0 )
      return bit;

  return 0;
}

/* for use by xSAME_BITS() -- works like == */
bool ext_same_bits( EXT_BV * var, EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    if( var->bits[x] != bits->bits[x] )
      return FALSE;

  return TRUE;
}

/* for use by xSET_BITS() -- works like SET_BIT() */
void ext_set_bits( EXT_BV * var, EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    var->bits[x] |= bits->bits[x];
}

/* for use by xREMOVE_BITS() -- works like REMOVE_BIT() */
void ext_remove_bits( EXT_BV * var, EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    var->bits[x] &= ~( bits->bits[x] );
}

/* for use by xTOGGLE_BITS() -- works like TOGGLE_BIT() */
void ext_toggle_bits( EXT_BV * var, EXT_BV * bits )
{
  int x;

  for( x = 0; x < XBI; x++ )
    var->bits[x] ^= bits->bits[x];
}

/*
 * Read an extended bitvector from a file.			-Thoric
 */
EXT_BV fread_bitvector( FILE * fp )
{
  EXT_BV ret;
  int c, x = 0;
  int num = 0;

  memset( &ret, '\0', sizeof( ret ) );
  for( ;; )
  {
    num = fread_number( fp );
    if( x < XBI )
      ret.bits[x] = num;
    ++x;
    if( ( c = getc( fp ) ) != '&' )
    {
      ungetc( c, fp );
      break;
    }
  }

  return ret;
}

/* return a string for writing a bitvector to a file */
char *print_bitvector( EXT_BV * bits )
{
  static char buf[XBI * 12];
  char *p = buf;
  int x, cnt = 0;

  for( cnt = XBI - 1; cnt > 0; cnt-- )
    if( bits->bits[cnt] )
      break;
  for( x = 0; x <= cnt; x++ )
  {
    sprintf( p, "%d", bits->bits[x] );
    p += strlen( p );
    if( x < cnt )
      *p++ = '&';
  }
  *p = '\0';

  return buf;
}

/*
 * Write an extended bitvector to a file			-Thoric
 */
void fwrite_bitvector( EXT_BV * bits, FILE * fp )
{
  fputs( print_bitvector( bits ), fp );
}


EXT_BV meb( int bit )
{
  EXT_BV bits;

  xCLEAR_BITS( bits );
  if( bit >= 0 )
    xSET_BIT( bits, bit );

  return bits;
}


EXT_BV multimeb( int bit, ... )
{
  EXT_BV bits;
  va_list param;
  int b;

  xCLEAR_BITS( bits );
  if( bit < 0 )
    return bits;

  xSET_BIT( bits, bit );

  va_start( param, bit );

  while( ( b = va_arg( param, int ) ) != -1 )
      xSET_BIT( bits, b );

  va_end( param );

  return bits;
}


#ifdef WIN32

/* routines not in Windows runtime libraries */

void gettimeofday( struct timeval *tv, struct timezone *tz )
{
  tv->tv_sec = time( 0 );
  tv->tv_usec = 0;
}

/* directory parsing stuff */

DIR *opendir( char *sDirName )
{
  DIR *dp = malloc( sizeof( DIR ) );

  dp->hDirectory = 0; /* if zero, we must do a FindFirstFile */
  strcpy( dp->sDirName, sDirName ); /* remember for FindFirstFile */
  return dp;
}

struct dirent *readdir( DIR * dp )
{

  /*
   * either read the first entry, or the next entry 
   */
  do
  {
    if( dp->hDirectory == 0 )
    {
      dp->hDirectory = FindFirstFile( dp->sDirName, &dp->Win32FindData );
      if( dp->hDirectory == INVALID_HANDLE_VALUE )
        return NULL;
    }
    else if( !FindNextFile( dp->hDirectory, &dp->Win32FindData ) )
      return NULL;

    /*
     * skip directories 
     */

  }
  while( dp->Win32FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY );

  /*
   * make a copy of the name string 
   */
  dp->dirinfo.d_name = dp->Win32FindData.cFileName;

/* return a pointer to the DIR structure */

  return &dp->dirinfo;
}

void closedir( DIR * dp )
{
  if( dp->hDirectory )
    FindClose( dp->hDirectory );
  free( dp );
}

#endif

void remove_tail( CHAR_DATA * ch )
{
  char buf[MAX_STRING_LENGTH];
  OBJ_DATA *tail;
  int regen_time = 0;

  regen_time = number_range( 60, 720 );

  act( AT_RED, "You grab ahold of your tail and pull... OUCH!!!", ch, NULL, NULL, TO_CHAR );
  act( AT_PLAIN, "$n grabs ahold of $s tail and pulls it off... OUCH!!!", ch, NULL, NULL, TO_ROOM );
  ch->pcdata->tail = regen_time * -1;

  if( xIS_SET( ch->affected_by, AFF_OOZARU ) || xIS_SET( ( ch )->affected_by, AFF_GOLDEN_OOZARU ) )
  {
    act( AT_WHITE, "You revert back to normal as your Oozaru powers fade.", ch, NULL, NULL, TO_CHAR );
    act( AT_WHITE, "$n reverts back to normal as $s Oozaru powers fade.", ch, NULL, NULL, TO_ROOM );
    xREMOVE_BIT( ch->affected_by, AFF_OOZARU );
    xREMOVE_BIT( ch->affected_by, AFF_GOLDEN_OOZARU );
    ch->pl = ch->exp;
  }

  tail = create_object( get_obj_index( 601 ), 0 );

  sprintf( buf, "furry tail %s", ch->name );
  STRFREE( tail->name );
  tail->name = STRALLOC( buf );

  sprintf( buf, tail->short_descr, ch->name );
  STRFREE( tail->short_descr );
  tail->short_descr = STRALLOC( buf );

  tail->timer = regen_time * 0.25;

  obj_to_char( tail, ch );

  return;
}