SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * 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 Staerfeldt, 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	"stdafx.h"
#include	"smaug.h"
#include	"SysData.h"
#include	"skill.h"
#include	"objects.h"
#include	"rooms.h"
#include	"Exits.h"
#include	"descriptor.h"
#include	"character.h"

extern int	top_exit;

/*
 * Fill a container
 * Many enhancements added by Thoric (ie: filling non-drink containers)
 */
void do_fill (CCharacter *ch, char *argument)
{
	char		arg1 [MAX_INPUT_LENGTH];
	char		arg2 [MAX_INPUT_LENGTH];
	CObjData	*obj;
	CObjData	*source;
	short		dest_item, src_item1, src_item2, src_item3;
	int			diff;
	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')
    {
	ch->SendText ("Fill what?\n\r");
	return;
    }

    if (ms_find_obj (ch))
	return;

    if ((obj = get_obj_carry (ch, arg1)) == NULL)
    {
	ch->SendText ("You do not have that item.\n\r");
	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);
	  ch->SendText ("You cannot fill that.\n\r");
	  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->GetName (), TO_CHAR);
	    return;
	}
	if (get_obj_weight (obj) / obj->count
	>=   obj->value[0])
	{
	   ch->SendText ("It's already full as it can be.\n\r");
	   return;
	}
    }
    else
    {
	diff = obj->value[0] - obj->value[1];
	if (diff < 1 || obj->value[1] >= obj->value[0])
	{
	   ch->SendText ("It's already full as it can be.\n\r");
	   return;
	}
    }

    if (dest_item == ITEM_PIPE
    &&   IS_SET (obj->value[3], PIPE_FULLOFASH))
    {
	ch->SendText ("It's full of ashes, and needs to be emptied first.\n\r");
	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)
	{
	   ch->SendText ("You don't have that item.\n\r");
	   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)
	{
	   ch->SendText ("You cannot find that item.\n\r");
	   return;
	}
      }
    }
    else
	source = NULL;

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

	if (! source) {
		BOOL      found = FALSE;

		found = FALSE;
		separate_obj (obj);
		CObjectList	&List = ch->GetInRoom ()->GetContentList ();
		POSITION	pos = List.GetHeadPosition ();

		while (source = List.GetNext (pos)) {
			if (dest_item == ITEM_CONTAINER) {
				if (! source->CanWear (ITEM_TAKE)
					|| (source->IsPrototype () && !can_take_proto (ch))
					|| ch->GetCarryWeight () + get_obj_weight (source) > can_carry_w (ch)
					|| (get_obj_weight (source) + get_obj_weight (obj)/obj->count)
					> obj->value [0])
						continue;
				if (all && arg2 [3] == '.'
					&& !nifty_is_name (&arg2 [4], source->GetName ()))
						continue;
				obj_from_room (source);
				if (source->item_type == ITEM_MONEY) {
					ch->AddGold (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:
				ch->SendText ("There is nothing appropriate here!\n\r");
				return;
			  case ITEM_FOUNTAIN:
				ch->SendText ("There is no fountain or pool here!\n\r");
				return;
			  case ITEM_BLOOD:
				ch->SendText ("There is no blood pool here!\n\r");
				return;
			  case ITEM_HERB_CON:
				ch->SendText ("There are no herbs here!\n\r");
				return;
			  case ITEM_HERB:
				ch->SendText ("You cannot find any smoking herbs.\n\r");
				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)
    {
	CObjData *otmp;
	char name[MAX_INPUT_LENGTH];
	CCharacter *gch;
	char *pd;
	BOOL found = FALSE;

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

	switch (source->item_type)
	{
	    default:	/* put something in container */
		if (!source->in_room	/* disallow inventory items */
		|| ! source->CanWear (ITEM_TAKE)
		||   (source->IsPrototype () && !can_take_proto (ch))
		||    ch->GetCarryWeight () + get_obj_weight (source) > can_carry_w (ch)
		||   (get_obj_weight (source) + get_obj_weight (obj)/obj->count)
		    > obj->value[0])
		{
		    ch->SendText ("You can't do that.\n\r");
		    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:
		ch->SendText ("You can't do that... yet.\n\r");
		break;
	    case ITEM_CORPSE_PC:
		if (ch->IsNpc ())
		{
		    ch->SendText ("You can't do that.\n\r");
		    return;
		}
		if (! source->IsClanCorpse () || ! ch->IsPkiller ()) {
		    pd = NCCP source->GetShortDescr ();
		    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->GetName ()) && ch->IsMortal ())
		    {
			BOOL fGroup;

			fGroup = FALSE;
			for (gch = first_char; gch; gch = gch->GetNext ())
			{
			    if (!gch->IsNpc ()
			    &&   is_same_group (ch, gch)
			    &&   !str_cmp (name, gch->GetName ()))
			    {
				fGroup = TRUE;
				break;
			    }
			}
			if (!fGroup)
			{
			    ch->SendText ("That's someone else's corpse.\n\r");
			    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->GetName (), TO_CHAR);
		    return;
		}
	    case ITEM_CORPSE_NPC:
		if (source->GetContentList ().IsEmpty ()) {
		    ch->SendText ("It's empty.\n\r");
		    return;
		}
		separate_obj (obj);

		CObjectList	&List = source->GetContentList ();
		POSITION	pos = List.GetHeadPosition ();
		while (otmp = List.GetNext (pos)) {
		    if (! otmp->CanWear (ITEM_TAKE)
		    ||   (otmp->IsPrototype () && !can_take_proto (ch))
		    ||    ch->carry_number + otmp->count > ch->GetMaxItems ()
		    ||    ch->GetCarryWeight () + get_obj_weight (otmp) > can_carry_w (ch)
		    ||   (get_obj_weight (source) + get_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
		   ch->SendText ("There is nothing appropriate in there.\n\r");
		break;
	}
	return;
    }

    if (source->value[1] < 1)
    {
	ch->SendText ("There's none left!\n\r");
	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);
	  ch->SendText ("Something went wrong...\n\r");
	  return;
	case ITEM_FOUNTAIN:
	  if (obj->value[1] != 0 && obj->value[2] != 0)
	  {
	     ch->SendText ("There is already another liquid in it.\n\r");
	     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)
	  {
	     ch->SendText ("There is already another liquid in it.\n\r");
	     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])
	  {
	     ch->SendText ("There is already another type of herb in it.\n\r");
	     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])
	  {
	     ch->SendText ("There is already another type of herb in it.\n\r");
	     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])
	  {
	     ch->SendText ("There is already another liquid in it.\n\r");
	     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 (CCharacter *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    CObjData *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') {
		POSITION	pos = ch->GetInRoom ()->GetHeadContentPos ();
		while (obj = ch->GetInRoom ()->GetNextContent (pos))
			if ((obj->item_type == ITEM_FOUNTAIN)
				|| (obj->item_type == ITEM_BLOOD))
					break;

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

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

    if (!ch->IsNpc () && ch->GetPcData ()->condition[COND_DRUNK] > 40)
    {
	ch->SendText ("You fail to reach your mouth.  *Hic*\n\r");
	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, NCCP obj->GetName ());
	else
	   ch->SendText ("You're not carrying that.\n\r");
	break;

    case ITEM_BLOOD:
	if (ch->IsVampire () && !ch->IsNpc ())
	{
	    if (obj->timer > 0		/* if timer, must be spilled blood */
	    &&   ch->GetLevel () > 5
	    &&   ch->GetPcData ()->condition[COND_BLOODTHIRST] > (5+ch->GetLevel ()/10))
	    {
		ch->SendText ("It is above you to stoop to drinking blood from the ground!\n\r");
		ch->SendText ("Unless in dire need, you'd much rather have blood from a victim's neck!\n\r");
		return;
	    }
	    if (ch->GetPcData ()->condition[COND_BLOODTHIRST] < (10 + ch->GetLevel ()))
	    {
		if (ch->GetPcData ()->condition[COND_FULL] >= 48
		||   ch->GetPcData ()->condition[COND_THIRST] >= 48)
		{
		    ch->SendText ("You are too full to drink any blood.\n\r");
		    return;
		}

		if (!oprog_use_trigger (ch, obj, NULL, NULL, NULL))
		{
		   act (AT_BLOOD, "$n drinks from the spilled blood.", ch, NULL, NULL, TO_ROOM);
		   set_char_color (AT_BLOOD, ch);
		   ch->SendText ("You relish in the replenishment of this vital fluid...\n\r");
		   if (obj->value[1] <=1) 
                   {
			set_char_color (AT_BLOOD, ch);
			ch->SendText ("You drink the last drop of blood from the spill.\n\r");
			act (AT_BLOOD, "$n drinks the last drop of blood from the spill.", ch, NULL, NULL, TO_ROOM);
		   }
		}

		gain_condition (ch, COND_BLOODTHIRST, 1);
		gain_condition (ch, COND_FULL, 1);
		gain_condition (ch, COND_THIRST, 1);
		if (--obj->value[1] <=0)
		{
		   if (obj->serial == cur_obj)
		     global_objcode = rOBJ_DRUNK;
		   extract_obj (obj);
		   make_bloodstain (ch);
		}
	    }
	    else
	      ch->SendText ("Alas... you cannot consume any more blood.\n\r");
	}
	else
	  ch->SendText ("It is not in your nature to do such things.\n\r");
	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);
	   ch->SendText ("You take a long thirst quenching drink.\n\r");
	}

	if (!ch->IsNpc ())
	    ch->GetPcData ()->condition[COND_THIRST] = 40;
	break;

    case ITEM_DRINK_CON:
	if (obj->value[1] <= 0)
	{
	    ch->SendText ("It is already empty.\n\r");
	    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 (!ch->IsNpc ())
	{
	    if (ch->GetPcData ()->condition[COND_DRUNK]  > 24)
		ch->SendText ("You feel quite sloshed.\n\r");
	    else
	    if (ch->GetPcData ()->condition[COND_DRUNK]  > 18)
		ch->SendText ("You feel very drunk.\n\r");
	    else
	    if (ch->GetPcData ()->condition[COND_DRUNK]  > 12)
		ch->SendText ("You feel drunk.\n\r");
	    else
	    if (ch->GetPcData ()->condition[COND_DRUNK]  > 8)
		ch->SendText ("You feel a little drunk.\n\r");
	    else
	    if (ch->GetPcData ()->condition[COND_DRUNK]  > 5)
		ch->SendText ("You feel light headed.\n\r");

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

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

	if (obj->value[3])
	{
	    /* The drink was poisoned! */
	    CAffectData 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->SetMentalState (URANGE (20, ch->GetMentalState () + 5, 100));
	    af.type      = gsn_poison;
	    af.duration  = 3 * obj->value[3];
	    af.location  = APPLY_NONE;
	    af.modifier  = 0;
	    af.bitvector = AFF_POISON;
	    affect_join (ch, &af);
	}

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

void do_eat (CCharacter *ch, char *argument)
{
    CObjData *obj;
    ch_ret retcode;
    int foodcond;

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

    if (ch->IsNpc () || ch->GetPcData ()->condition[COND_FULL] > 5)
	if (ms_find_obj (ch))
	    return;

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

    if (ch->IsMortal ())
    {
	if (obj->item_type != ITEM_FOOD && obj->item_type != ITEM_PILL)
	{
	    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 (!ch->IsNpc () && ch->GetPcData ()->condition[COND_FULL] > 40)
	{
	    ch->SendText ("You are too full to eat more.\n\r");
	    return;
	}
    }

    /* required due to object grouping */
    separate_obj (obj);
    if (ch->IsPkiller ())
        WAIT_STATE (ch, PULSE_PER_SECOND/4);
    else
        WAIT_STATE (ch, PULSE_PER_SECOND/2);
    if (obj->in_obj)
    {
	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 (!oprog_use_trigger (ch, obj, NULL, NULL, NULL))
    {
      if (! obj->HasValidActionDescr ())
      {
        act (AT_ACTION, "$n eats $p.",  ch, obj, NULL, TO_ROOM);
        act (AT_ACTION, "You eat $p.", ch, obj, NULL, TO_CHAR);
      }
      else
        actiondesc (ch, obj, NULL); 
    }

    switch (obj->item_type)
    {

    case ITEM_FOOD:
	if (obj->timer > 0 && obj->value[1] > 0)
	   foodcond = (obj->timer * 10) / obj->value[1];
	else
	   foodcond = 10;

	if (!ch->IsNpc ())
	{
	    int condition;

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

	if ( obj->value[3] != 0
	||   (foodcond < 4 && number_range (0, foodcond + 1) == 0))
	{
	    /* The food was poisoned! */
	    CAffectData 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->SetMentalState (URANGE (20, ch->GetMentalState () + 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->SetMentalState (URANGE (15, ch->GetMentalState () + 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 = AFF_POISON;
	    affect_join (ch, &af);
	}
	break;

    case ITEM_PILL:
	/* allow pills to fill you, if so desired */
	if (!ch->IsNpc () && obj->value[4])
	{
	    int condition;

	    condition = ch->GetPcData ()->condition[COND_FULL];
	    gain_condition (ch, COND_FULL, obj->value[4]);
	    if (condition <= 1 && ch->GetPcData ()->condition[COND_FULL] > 1)
		ch->SendText ("You are no longer hungry.\n\r");
	    else if (ch->GetPcData ()->condition[COND_FULL] > 40)
		ch->SendText ("You are full.\n\r");
	}
	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 (CCharacter *ch, char *argument)
{
    CObjData *obj;
    ch_ret retcode;

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

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

    if (obj->item_type != ITEM_POTION)
    {
	if (obj->item_type == ITEM_DRINK_CON)
	   do_drink (ch, NCCP obj->GetName ());
	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;
    }

    /*
     * Fullness checking					-Thoric
     */
    if (!ch->IsNpc ()
    && (ch->GetPcData ()->condition[COND_FULL] >= 48
    ||   ch->GetPcData ()->condition[COND_THIRST] >= 48))
    {
	ch->SendText ("Your stomach cannot contain any more.\n\r");
	return;
    }

    separate_obj (obj);
    if (obj->in_obj)
    {
	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->GetFightData () && number_percent () > (ch->GetDexterity () * 2 + 48))
    {
	act (AT_MAGIC, "$n accidentally drops $p and it smashes into a thousand fragments.", ch, obj, NULL, TO_ROOM);
	act (AT_MAGIC, "Oops... $p gets knocked from your hands and smashes into pieces!", ch, obj, NULL ,TO_CHAR);
    }
    else
    {
	if (!oprog_use_trigger (ch, obj, NULL, NULL, NULL))
	{
	    act (AT_ACTION, "$n quaffs $p.",  ch, obj, NULL, TO_ROOM);
	    act (AT_ACTION, "You quaff $p.", ch, obj, NULL, TO_CHAR);
	}

        if (ch->IsPkiller ())
            WAIT_STATE (ch, PULSE_PER_SECOND/4);
        else         
	    WAIT_STATE (ch, PULSE_PER_SECOND/2);

	gain_condition (ch, COND_THIRST, 1);
	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 (cur_obj == obj->serial)
      global_objcode = rOBJ_QUAFFED;
    extract_obj (obj);
    return;
}


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

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

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

    if (ms_find_obj (ch))
	return;

    if ((scroll = get_obj_carry (ch, arg1)) == NULL)
    {
	ch->SendText ("You do not have that scroll.\n\r");
	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 (ch->IsNpc () 
    && (scroll->pIndexData->vnum == OBJ_VNUM_SCROLL_SCRIBING))
    {
	ch->SendText ("As a mob, this dialect is foreign to you.\n\r");
	return;
    }

    if ((scroll->pIndexData->vnum == OBJ_VNUM_SCROLL_SCRIBING)
      && (ch->GetLevel () + 10 < scroll->value[0]))
    {
        ch->SendText ("This scroll is too complex for you to understand.\n\r");
        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)
	{
	    ch->SendText ("You can't find it.\n\r");
	    return;
	}
    }

    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 if (ch->IsPkiller ())
        WAIT_STATE (ch, PULSE_PER_SECOND/4);
    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 (CCharacter *ch, CObjData *obj, BOOL pull)
{
    char buf[MAX_STRING_LENGTH];
    CCharacter		*rch;
    BOOL		 isup;
    CRoomIndexData	*room,  *to_room;
    CExitData		*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");
	  ch->SendText (buf);
	  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");
		ch->SendText (buf);
		return;
 	  }
	case ITEM_BUTTON:
	  if ((!pull && isup) || (pull & !isup))
	  {
		sprintf (buf, "It is already %s.\n\r", isup ? "in" : "out");
		ch->SendText (buf);
		return;
	  }
	  break;
    }
    if (pull && obj->GetIndex ()->HasPullProg ())
    {
	if (!IS_SET (obj->value[0], TRIG_AUTORETURN))
 	  REMOVE_BIT (obj->value[0], TRIG_UP);
 	oprog_pull_trigger (ch, obj);
        return;
    }
    if (! pull && obj->GetIndex ()->HasPushProg ())
    {
	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 = RoomTable.GetRoom (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 = RoomTable.GetRoom (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->GetNextInRoom ())
	{
	   rch->SendText ("You hear a loud rumbling sound.\n\r");
	   rch->SendText ("Something seems different...\n\r");
	}
    }
    if (IS_SET (obj->value[0], TRIG_DOOR))
    {
	room = RoomTable.GetRoom (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 = RoomTable.GetRoom (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->SetFlags (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) && pexit->IsLocked ()) {
	    pexit->ClrLocked ();
	    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->GetToRoom () == ch->GetInRoom ())
			pexit_rev->ClrLocked ();
	    return;
	}

	if (IS_SET (obj->value[0], TRIG_LOCK)
	  && ! pexit->IsLocked ()) {
	    pexit->SetLocked ();
	    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->GetToRoom () == ch->GetInRoom ())
			pexit_rev->SetLocked ();
	    return;
	}

	if (IS_SET (obj->value [0], TRIG_OPEN)
	  && pexit->IsClosed ()) {
	    pexit->ClrClosed ();
	    for (rch = room->first_person; rch; rch = rch->GetNextInRoom ())
			act (AT_ACTION, "The $d opens.", rch, NULL,
				pexit->keyword, TO_CHAR);
	    if ((pexit_rev = pexit->rexit) != NULL
	      && pexit_rev->GetToRoom () == ch->GetInRoom ()) {
			pexit_rev->ClrClosed ();
			for (rch = pexit->GetToRoom ()->first_person; rch; rch = rch->GetNextInRoom ())
			    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)
	  && ! pexit->IsClosed ()) {
	    pexit->SetClosed ();
	    for (rch = room->first_person; rch; rch = rch->GetNextInRoom ())
		act (AT_ACTION, "The $d closes.", rch, NULL, pexit->keyword, TO_CHAR);
	    if ((pexit_rev = pexit->rexit) != NULL
	      && pexit_rev->GetToRoom () == ch->GetInRoom ()) {
			pexit_rev->SetClosed ();
			for (rch = pexit->GetToRoom ()->first_person; rch; rch = rch->GetNextInRoom ())
			    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 (CCharacter *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    CObjData *obj;

    one_argument (argument, arg);
    if (arg[0] == '\0')
    {
	ch->SendText ("Pull what?\n\r");
	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, TRUE);
}

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

    one_argument (argument, arg);
    if (arg[0] == '\0')
    {
	ch->SendText ("Push what?\n\r");
	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 (CCharacter* ch, char* argument)
{
	CExitData	*pExit;
	char		arg [MAX_INPUT_LENGTH];

	one_argument (argument, arg);

	if (arg [0] == '\0') {
		ch->SendText ("Rap on what?\n\r");
		return;
	}

	if (ch->GetFightWho ()) {
		ch->SendText (
			"You have better things to do with your hands right now.\n\r");
		return;
	}

	if (pExit = find_door (ch, arg, FALSE)) {
		CRoomIndexData	*to_room;
		CExitData		*pRev;
		char			*keyword;

		if (! pExit->IsClosed ()) {
			ch->SendText ("Why knock?  It's open.\n\r");
			return;
		}

		if (pExit->IsSecret ())
			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->GetToRoom ())
		  && (pRev = pExit->rexit)
		  && pRev->GetToRoom () == ch->GetInRoom ()) {
			CCharacter	*rch = to_room->first_person;
			for ( ; rch; rch = rch->GetNextInRoom ()) {
				act (AT_ACTION,
					"Someone raps loudly from the other side of the $d.",
					rch, NULL, pRev->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);
	}
}


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

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

    if (ms_find_obj (ch))
	return;

    if ((pipe = get_obj_carry (ch, arg)) == NULL)
    {
	ch->SendText ("You aren't carrying that.\n\r");
	return;
    }
    if (pipe->item_type != ITEM_PIPE)
    {
	ch->SendText ("You can't tamp that.\n\r");
	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;
    }
    ch->SendText ("It doesn't need tamping.\n\r");
}

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

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

    if (ms_find_obj (ch))
	return;

    if ((pipe = get_obj_carry (ch, arg)) == NULL)
    {
	ch->SendText ("You aren't carrying that.\n\r");
	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 his $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 (HerbTable.IsValid (pipe->value[2]) && pipe->value[2] < HerbTable.GetCount ())
	{
	    int sn		= pipe->value[2] + TYPE_HERB;
	    CSkill *skill	= SkillTable.GetValidSkill (sn);

	    WAIT_STATE (ch, skill->GetBeats ());
	    if (skill->GetSpellFunction ())
		obj_cast_spell (sn, UMIN (skill->GetMinLevel (), ch->GetLevel ()),
			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 (CCharacter *ch, char *argument)
{
    CObjData *pipe;
    char arg[MAX_INPUT_LENGTH];

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

    if (ms_find_obj (ch))
	return;

    if ((pipe = get_obj_carry (ch, arg)) == NULL)
    {
	ch->SendText ("You aren't carrying that.\n\r");
	return;
    }
    if (pipe->item_type != ITEM_PIPE)
    {
	ch->SendText ("You can't light that.\n\r");
	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;
    }
    ch->SendText ("It's already lit.\n\r");
}

void do_empty (CCharacter *ch, char *argument)
{
    CObjData *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')
    {
	ch->SendText ("Empty what?\n\r");
	return;
    }
    if (ms_find_obj (ch))
	return;

    if ((obj = get_obj_carry (ch, arg1)) == NULL)
    {
	ch->SendText ("You aren't carrying that.\n\r");
	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)
	  {
		ch->SendText ("It's already empty.\n\r");
		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;
	  return;
	case ITEM_CONTAINER:
		if (IS_SET (obj->value [1], CONT_CLOSED)) {
			act (AT_PLAIN, "The $d is closed.", ch, NULL, obj->GetName (),
				TO_CHAR);
			return;
		}
		if (obj->GetContentList ().IsEmpty ()) {
			ch->SendText ("It's already empty.\n\r");
			return;
		}
		if (arg2 [0] == '\0') {
			if (ch->GetInRoom ()->IsNoDrop ()
			  || (! ch->IsNpc () && ch->IsLitterBug ())) {
				set_char_color (AT_MAGIC, ch);
				ch->SendText ("A magical force stops you!\n\r");
				set_char_color (AT_TELL, ch);
				ch->SendText ("Someone tells you, 'No littering here!'\n\r");
				return;
			}
			if (ch->GetInRoom ()->IsNoDropAll () 
			  || ch->GetInRoom ()->IsClanStoreRoom ()) {
				ch->SendText ("You can't seem to do that here...\n\r");
				return;
			}
			if (empty_obj (obj, NULL, ch->GetInRoom ())) {
				act (AT_ACTION, "You empty $p.", ch, obj, NULL, TO_CHAR);
				act (AT_ACTION, "$n empties $p.", ch, obj, NULL, TO_ROOM);
				if (SysData.IsSaveOnDrop ())
					save_char_obj (ch);
			}
			else
				ch->SendText ("Hmmm... didn't work.\n\r");
	  }
	  else
	  {
		CObjData *dest = get_obj_here (ch, arg2);

		if (!dest)
		{
		    ch->SendText ("You can't find it.\n\r");
		    return;
		}
		if (dest == obj)
		{
		    ch->SendText ("You can't empty something into itself!\n\r");
		    return;
		}
		if (dest->item_type != ITEM_CONTAINER)
		{
		    ch->SendText ("That's not a container!\n\r");
		    return;
		}
		if (IS_SET (dest->value[1], CONT_CLOSED))
		{
		    act (AT_PLAIN, "The $d is closed.", ch, NULL, dest->GetName (), 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 && SysData.IsSaveOnPut ())
				save_char_obj (ch);
		}
		else
		    act (AT_ACTION, "$P is too full.", ch, obj, dest, TO_CHAR);
	  }
	  return;
    }
}
 
/*
 * Apply a salve/ointment					-Thoric
 */
void do_apply (CCharacter *ch, char *argument)
{
    CObjData *obj;
    ch_ret retcode;

    if (argument[0] == '\0')
    {
	ch->SendText ("Apply what?\n\r");
	return;
    }

    if (ms_find_obj (ch))
	return;

    if ((obj = get_obj_carry (ch, argument)) == NULL)
    {
	ch->SendText ("You do not have that.\n\r");
	return;
    }

    if (obj->item_type != ITEM_SALVE)
    {
	act (AT_ACTION, "$n starts to rub $p on $mself...",  ch, obj, NULL, TO_ROOM);
	act (AT_ACTION, "You try to rub $p on yourself...", ch, obj, NULL, TO_CHAR);
	return;
    }

    separate_obj (obj);

    --obj->value[1];
    if (!oprog_use_trigger (ch, obj, NULL, NULL, NULL))
    {
	if (! obj->HasValidActionDescr ())
	{
	    act (AT_ACTION, "$n rubs $p onto $s body.",  ch, obj, NULL, TO_ROOM);
	    if (obj->value[1] <= 0)
		act (AT_ACTION, "You apply the last of $p onto your body.", ch, obj, NULL, TO_CHAR);
	    else
		act (AT_ACTION, "You apply $p onto your body.", ch, obj, NULL, TO_CHAR);
	}
	else
	    actiondesc (ch, obj, NULL); 
    }

    WAIT_STATE (ch, obj->value[2]);
    retcode = obj_cast_spell (obj->value[4], obj->value[0], ch, ch, NULL);
    if (retcode == rNONE)
	retcode = obj_cast_spell (obj->value[5], obj->value[0], ch, ch, NULL);

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

    return;
}

void actiondesc (CCharacter *ch, CObjData *obj, void *vo)
{
    char charbuf[MAX_STRING_LENGTH];
    char roombuf[MAX_STRING_LENGTH];
    const char *srcptr = obj->GetActionDescr ();
    char *charptr = charbuf;
    char *roomptr = roombuf;
    const char *ichar;
    const char *iroom;

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 = ch->IsNpc () ? ch->GetShortDescr () : ch->GetName ();
  }
  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);
gpDoc->LogString (buf, LOG_HIGH, LEVEL_LESSER); 
sprintf (buf, "Roombuf: %s", roombuf);
gpDoc->LogString (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_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;
}