eldhamud/boards/
eldhamud/clans/
eldhamud/classes/
eldhamud/councils/
eldhamud/deity/
eldhamud/doc/
eldhamud/doc/DIKU/
eldhamud/doc/MERC/
eldhamud/doc/mudprogs/
eldhamud/houses/
eldhamud/src/o/CVS/
/*****************************************************
**     _________       __                           **
**     \_   ___ \_____|__| _____  ________  ___     **
**      /    \  \/_  __ \ |/     \/  ___/_ \/   \   **
**      \     \___|  | \/ |  | |  \___ \  / ) |  \  **
**       \______  /__| |__|__|_|  /____ \__/__|  /  **
**         ____\/____ _        \/ ___ \/      \/    **
**         \______   \ |_____  __| _/___            **
**          |    |  _/ |\__  \/ __ | __ \           **
**          |    |   \ |_/ __ \  / | ___/_          **
**          |_____  /__/____  /_  /___  /           **
**               \/Antipode\/  \/    \/             **
******************************************************
******************************************************
**       Copyright 2000-2003 Crimson Blade          **
******************************************************
** Contributors: Noplex, Krowe, Emberlyna, Lanthos  **
******************************************************/

/*
 * File: liquids.c
 * Name: Liquidtable Module (3.02b)
 * Author: John 'Noplex' Bellone (jbellone@comcast.net)
 * Terms:
 * If this file is to be re-disributed; you must send an email
 * to the author. All headers above the #include calls must be
 * kept intact. All license requirements must be met. License
 * can be found in the included license.txt document or on the
 * website.
 * Description:
 * This module is a rewrite of the original module which allowed for
 * a SMAUG mud to have a fully online editable liquidtable; adding liquids;
 * removing them; and editing them online. It allows an near-endless supply
 * of liquids for builder's to work with.
 * A second addition to this module allowed for builder's to create mixtures;
 * when two liquids were mixed together they would produce a different liquid.
 * Yet another adaptation to the above concept allowed for objects to be mixed
 * with liquids to produce a liquid.
 * This newest version offers a cleaner running code; smaller; and faster in
 * all ways around. Hopefully it'll knock out the old one ten fold ;)
 * Also in the upcoming 'testing' phase of this code; new additions will be added
 * including a better alchemey system for creating poitions as immortals; and as
 * mortals.
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "mud.h"

#ifdef KEY
#undef KEY
#endif
#define KEY( literal, field, value ) if ( !str_cmp( word, literal ) ){	field  = value;	  fMatch = TRUE;   break;}

#ifndef FCLOSE
 #define FCLOSE(fp) fclose(fp); fp=NULL;
#endif

#ifndef NULLSTR
 #define NULLSTR(str)         (!str || str[0] == '\0')
#endif

char *const liquid_types[LIQTYPE_TOP] =
{
  "Beverage", "Alcohol", "Poison", "Blood"
};

char *const mod_types[MAX_CONDS] =
{
  "Drunk", "Full", "Thirst", "Bloodthirst"
};

/* locals */
int         top_liquid;
int         liq_count;

/* liquid i/o functions */

/* save the liquids to the liquidtable.dat file in
 * the system directory    -Nopey */
void save_liquids(void)
{
  FILE *fp = NULL;
  LIQ_TABLE *liq = NULL;
  char filename[256];
  int i;

  sprintf(filename, "%sliquidtable.dat", SYSTEM_DIR);
  if((fp = fopen(filename, "w")) == NULL)
  {
    bug("save_liquids(): cannot open %s for writing", filename);
    return;
  }

  fprintf(fp, "#VERSION 3\n");
  for(i = 0; i < top_liquid; i++)
  {
    liq = liquid_table[i];

    fprintf(fp, "#LIQUID\n");
    fprintf(fp, "Name              %s~\n", liq->name);
    fprintf(fp, "Shortdesc         %s~\n", liq->shortdesc);
    fprintf(fp, "Color             %s~\n", liq->color);
    fprintf(fp, "Type              %d\n", liq->type);
    fprintf(fp, "Vnum              %d\n", liq->vnum);
    fprintf(fp, "Mod      %d %d %d %d\n", liq->mod[COND_DRUNK], liq->mod[COND_FULL], liq->mod[COND_THIRST], liq->mod[COND_BLOODTHIRST]);
    fprintf(fp, "End\n");
   }
  fprintf(fp, "#END\n");
  FCLOSE(fp);
  return;
}

/* load the liquids from the liquidtable.dat file in the
 * system directory -Nopey */
void load_liquids(void)
{
  FILE *fp = NULL;
  char filename[256];
  int file_version = 0;

  sprintf(filename, "%sliquidtable.dat", SYSTEM_DIR);
  if((fp = fopen(filename, "r")) == NULL)
  {
    bug("load_liquids(): cannot open %s for reading", filename);
    return;
  }

  top_liquid = -1;
  liq_count  = -1;

  for( ; ; )
  {
    char letter = fread_letter(fp);
    char *word;

    if(letter == '*')
    {
      fread_to_eol(fp);
      continue;
    }

    if(letter != '#')
    {
      bug("load_liquids(): # not found (%c)", letter);
      return;
    }

    word = fread_word(fp);
    if(!str_cmp(word, "VERSION"))
    {
      file_version = fread_number(fp);
      continue;
    }
    else
    if(!str_cmp(word, "LIQUID"))
    {
      LIQ_TABLE *liq = fread_liquid(fp);

      log_string("debugger: 6");
      if(!liq)
       bug("load_liquids(): returned NULL liquid");
      else
      {
       static char logger[1024];
       sprintf(logger, "debugger: added liquid %s(%d) to table", liq->name, liq->vnum);
       log_string(logger);
       liquid_table[liq->vnum] = liq;
       if(liq->vnum > top_liquid)
        top_liquid = liq->vnum;
       liq_count++;
      }
      continue;
    }
    else
    if(!str_cmp(word, "END"))
     break;
    else
    {
      bug("load_liquids(): no match for %s", word);
      continue;
    }
  }
  FCLOSE(fp);
  return;
}

/* read the liquids from a file descriptor and then
 * distribute them accordingly to the struct   -Nopey */
LIQ_TABLE *fread_liquid(FILE *fp)
{
  LIQ_TABLE *liq = NULL;
  bool fMatch = FALSE;
  int i;

  CREATE(liq, LIQ_TABLE, 1);
  liq->name              = STRALLOC("None");
  liq->color             = STRALLOC("None");
  liq->shortdesc         = STRALLOC("None");
  liq->vnum              = -1;
  liq->type              = -1;
  for(i = 0; i < MAX_CONDS; i++)
   liq->mod[i]           = -1;

  for( ; ; )
  {
    char *word = feof(fp) ? "End" : fread_word(fp);

    switch(UPPER(word[0]))
    {
      case '*':
       fread_to_eol(fp);
      break;

      case 'C':
       KEY("Color", liq->color, fread_string(fp));
      break;

      case 'E':
       if(!str_cmp(word, "End"))
       {
         if(liq->vnum <= -1)
          return NULL;
         return liq;
       }
      break;

      case 'N':
       KEY("Name", liq->name, fread_string(fp));
      break;

      case 'M':
       if(!str_cmp(word, "Mod"))
       {
         liq->mod[COND_DRUNK]        = fread_number(fp);
         liq->mod[COND_FULL]         = fread_number(fp);
         liq->mod[COND_THIRST]       = fread_number(fp);
         liq->mod[COND_BLOODTHIRST]  = fread_number(fp);
       }
      break;

      case 'S':
       KEY("Shortdesc", liq->shortdesc, fread_string(fp));
      break;

      case 'T':
       KEY("Type", liq->type, fread_number(fp));
      break;

      case 'V':
       KEY("Vnum", liq->vnum, fread_number(fp));
      break;
    }

    if(!fMatch)
     bug("fread_liquids(): no match for %s", word);
  }
  /* something went wrong if it gets here */
  return NULL;
}

/* save the mixtures to the mixture table -Nopey */
void save_mixtures(void)
{
  MIX_TABLE *mix = NULL;
  FILE *fp = NULL;
  char filename[256];

  sprintf(filename, "%smixturetable.dat", SYSTEM_DIR);
  if((fp = fopen(filename, "w")) == NULL)
  {
    bug("save_mixtures(): cannot open %s for writing", filename);
    return;
  }

  fprintf(fp, "#VERSION 3\n");
  for(mix = first_mixture; mix; mix = mix->next)
  {
    fprintf(fp, "#MIXTURE\n");
    fprintf(fp, "Name           %s~\n", mix->name);
    fprintf(fp, "Data      %d %d %d\n", mix->data[0], mix->data[1], mix->data[2]);
    fprintf(fp, "Object         %d\n", mix->object);
    fprintf(fp, "End\n");
  }
  fprintf(fp, "#END\n");
  FCLOSE(fp);
  return;
}

/* load the mixtures from the mixture table         -Nopey */
void load_mixtures(void)
{
  FILE *fp = NULL;
  char filename[256];
  static int file_version = 0;
  static bool file_old = FALSE;

  sprintf(filename, "%smixturetable.dat", SYSTEM_DIR);
  if((fp = fopen(filename, "r")) == NULL)
  {
    bug("load_mixtures(): cannot open %s for reading", filename);
    return;
  }

  for( ; ; )
  {
    char letter = fread_letter(fp);
    char *word;

    if(letter == '*')
    {
      fread_to_eol(fp);
      break;
    }

    if(letter != '#')
    {
      bug("load_mixtures(): # not found (%c)", letter);
      return;
    }

    word = fread_word(fp);
    if(str_cmp(word, "VERSION"))
    {
      file_old = TRUE;
      continue;
    }
    else if(!str_cmp(word, "VERSION"))
    {
      file_version = fread_number(fp);
      continue;
    }
    else
    if(!str_cmp(word, "MIXTURE"))
    {
      MIX_TABLE *mix = NULL;

      mix = fread_mixture(fp, file_old);
      if(!mix)
       bug("load_mixtures(): mixture returned NULL");
      else
       LINK(mix, first_mixture, last_mixture, next, prev);
    }
    else if(!str_cmp(word, "END"))
     break;
    else
    {
      bug("load_mixtures(): no match for %s", word);
      break;
    }
  }
  FCLOSE(fp);
  return;
}

/* read the mixtures into the structure -Nopey */
MIX_TABLE *fread_mixture(FILE *fp, bool file_old)
{
  MIX_TABLE *mix = NULL;
  bool fMatch = FALSE;

  CREATE(mix, MIX_TABLE, 1);
  mix->name              = STRALLOC("");
  mix->data[0]           = -1;
  mix->data[1]           = -1;
  mix->data[2]           = -1;
  mix->object            = FALSE;

  for( ; ; )
  {
    char *word = feof(fp) ? "End" : fread_word(fp);

    switch(UPPER(word[0]))
    {
      case '*':
       fread_to_eol(fp);
      break;

      case 'D':
       if(!str_cmp(word, "Data"))
       {
         mix->data[0]    = fread_number(fp);
         mix->data[1]    = fread_number(fp);
         mix->data[2]    = fread_number(fp);
       }
      break;

      case 'E':
       if(!str_cmp(word, "End"))
       {
         if(NULLSTR(mix->name))
          mix->name      = STRALLOC("Not Set");

         return mix;
       }
      break;

      if(file_old)
      {
        case 'I':
         KEY("Into", mix->data[2], fread_number(fp));
        break;
      }

      case 'N':
       KEY("Name", mix->name, fread_string(fp));
      break;

      case 'O':
       KEY("Object", mix->object, fread_number(fp));
      break;

     if(file_old)
     {
       case 'W':
        if(!str_cmp(word, "With"))
        {
          mix->data[0] = fread_number(fp);
          mix->data[1] = fread_number(fp);
        }
       break;
     }
    }

    if(!fMatch)
     bug("fread_mixture(): no match for %s", word);
  }
  /* something went wrong if it gets here */
  return NULL;
}

/* figure out a vnum for the next liquid  -Nopey */
static int figure_liq_vnum(void)
{
  int i;

  /* incase a liquid gets removed; we can fill it's place */
  for(i=0; liquid_table[i] != NULL; i++);

  /* add to the top */
  if(i > top_liquid)
   top_liquid = i;

  return i;
}

/* lookup func for liquids      -Nopey */
LIQ_TABLE *get_liq(char *str)
{
  int i;

  if(is_number(str))
  {
    i = atoi(str);

    return liquid_table[i];
  }
  else
  {
    for(i = 0; i < top_liquid; i++)
     if(!str_cmp(liquid_table[i]->name, str))
      return liquid_table[i];
  }
  return NULL;
}

LIQ_TABLE *get_liq_vnum(int vnum)
{
  return liquid_table[vnum];
}

/* lookup func for mixtures      -Nopey */
MIX_TABLE *get_mix(char *str)
{
  MIX_TABLE *mix = NULL;

  for(mix = first_mixture; mix; mix = mix->next)
   if(!str_cmp(mix->name, str))
    return mix;

  return NULL;
}

/* olc function for liquids   -Nopey */
void do_setliquid(CHAR_DATA *ch, char *argument)
{
  char arg[MAX_INPUT_LENGTH];

  if(!IS_IMMORTAL(ch) || IS_NPC(ch))
  {
    send_to_char("Huh?\n\r", ch);
    return;
  }

  argument = one_argument(argument, arg);
  if(NULLSTR(arg))
  {
    send_to_char("Syntax: setliquid <vnum> <field> <value>\n\r"
                 "        setliquid list [vnum]\n\r"
                 "        setliquid create <name>\n\r"
                 "        setliquid delete <vnum>\n\r", ch);
    send_to_char(" Fields being one of the following:\n\r"
                 " name color type shortdesc drunk thrist blood full\n\r", ch);
    return;
  }

  if(!str_cmp(arg, "list"))
  {
    LIQ_TABLE *liq = NULL;
    int i;

    if(!liquid_table[0])
    {
      send_to_char("There is currently no liquids loaded.\n\r", ch);
      send_to_char("WARNING:\n\rHaving no liquids loaded will result in no drinks providing\n\r"
                               "nurishment. This includes water. The default set of liquids\n\r"
                               "should always be backed up.\n\r", ch);
      return;
    }

    if(!NULLSTR(argument) && ((liq = get_liq(argument)) != NULL))
    {
      if(!NULLSTR(liq->name))
       pager_printf_color(ch, "&GLiquid information for:&g %s\n\r", liq->name);
      if(!NULLSTR(liq->shortdesc))
       pager_printf_color(ch, "&GLiquid shortdesc:&g\t %s\n\r", liq->shortdesc);
      if(!NULLSTR(liq->color))
       pager_printf_color(ch, "&GLiquid color:&g\t %s\n\r", liq->color);
      pager_printf_color(ch, "&GLiquid vnum:&g\t %d\n\r", liq->vnum);
      pager_printf_color(ch, "&GLiquid type:&g\t %s\n\r", liquid_types[liq->type]);
      pager_printf_color(ch, "&GLiquid Modifiers\n\r");
       for(i = 0; i < MAX_CONDS; i++)
        if(liquid_table[i])
         pager_printf_color(ch, "&G%s:&g\t %d\n\r", mod_types[i], liq->mod[i]);
      return;
    }
    else if(!NULLSTR(argument) && ((liq = get_liq(argument)) == NULL))
    {
      send_to_char("Invaild liquid-vnum.\n\rUse 'setliquid list' to gain a vaild liquidvnum.\n\r", ch);
      return;
    }

    pager_printf_color(ch, "&G[&gVnum&G] [&gName&G]\n\r");
    for(i = 0; i < top_liquid; i++)
     pager_printf_color(ch, "  %-7d %s\n\r", liquid_table[i]->vnum, liquid_table[i]->name);

    send_to_char("Use 'setliquid list [vnum]' to view individual liquids.\n\r", ch);
    send_to_char("Use 'setmixture list' to view the mixturetable.\n\r", ch);
    return;
  }
  else
  if(!str_cmp(arg, "create"))
  {
    LIQ_TABLE *liq = NULL;
    int i;

    if(liq_count >= MAX_LIQUIDS)
    {
      send_to_char("Liquid count is at the hard-coded max. Remove some liquids or raise\n\r"
                   "the hard-coded max number of liquids.\n\r", ch);
      return;
    }

    if(NULLSTR(argument))
    {
      send_to_char("Syntax: setliquid create <name>\n\r", ch);
      return;
    }

    CREATE(liq, LIQ_TABLE, 1);
    liq->name              = STRALLOC(argument);
    liq->color             = STRALLOC("");
    liq->shortdesc         = STRALLOC(argument);
    liq->vnum              = figure_liq_vnum( );
    liq->type              = -1;
    for(i = 0; i < MAX_CONDS; i++)
     liq->mod[i]           = -1;
    liquid_table[liq->vnum] = liq;
    liq_count++;
    send_to_char("Done.\n\r", ch);
    save_liquids( );
    return;
  }
  else if(!str_cmp(arg, "delete"))
  {
    LIQ_TABLE *liq = NULL;
    int i = atoi(argument);

    if(NULLSTR(argument))
    {
      send_to_char("Syntax: setliquid delete <vnum>\n\r", ch);
      return;
    }

    if((liq = get_liq_vnum(i)) == NULL)
    {
      send_to_char("No such vnum. Use 'setliquid list' to gain the vnums.\n\r", ch);
      return;
    }

    if(!NULLSTR(liq->name))
     STRFREE(liq->name);
    if(!NULLSTR(liq->color))
     STRFREE(liq->color);
    if(!NULLSTR(liq->shortdesc))
     STRFREE(liq->shortdesc);
    if(liq->vnum >= top_liquid)
    {
      int j;

       for(j = 0; j != liq->vnum; j++)
        if(j > top_liquid)
         top_liquid = j;
    }
    liquid_table[liq->vnum] = NULL;
    liq_count--;
    DISPOSE(liq);
    send_to_char("Done.\n\r", ch);
    save_liquids( );
    return;
  }
  else
  {
    char arg2[MAX_INPUT_LENGTH];
    LIQ_TABLE *liq = NULL;

    argument = one_argument(argument, arg2);
    if(NULLSTR(arg2))
    {
      send_to_char("Syntax: setliquid <vnum> <field> <value>\n\r", ch);
      send_to_char(" Fields being one of the following:\n\r"
                   " name color shortdesc drunk thrist blood full\n\r", ch);
      return;
    }

    if((liq = get_liq(arg)) == NULL)
    {
      send_to_char("Invaild liquid-name or vnum.\n\r", ch);
      return;
    }

    if(!str_cmp(arg2, "name"))
    {
      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setliquid <vnum> name <name>\n\r", ch);
        return;
      }
      liq->name               = STRALLOC(argument);
    }
    else
    if(!str_cmp(arg2, "color"))
    {
      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setliquid <vnum> color <color>\n\r", ch);
        return;
      }
      liq->color               = STRALLOC(argument);
    }
    else
    if(!str_cmp(arg2, "shortdesc"))
    {
      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setliquid <vnum> shortdesc <shortdesc>\n\r", ch);
        return;
      }
      liq->shortdesc              = STRALLOC(argument);
    }
    else if(!str_cmp(arg2, "type"))
    {
      char arg3[MAX_INPUT_LENGTH];
      int i;
      bool found = FALSE;

      argument = one_argument(argument, arg3);

      /* bah; forgot to add this shit -- */
      for(i = 0; i < LIQTYPE_TOP; i++)
       if(!str_cmp(arg3, liquid_types[i]))
       {
         found = TRUE;
         liq->type       = i;
       }
      if(!found)
      {
        send_to_char("Syntax: setliquid <vnum> type <liquidtype>\n\r", ch);
        return;
      }
    }
    else
    {
     int i;
     bool found = FALSE;
     static char * const arg_names[MAX_CONDS] =
     { "drunk", "full", "thirst", "blood" };

     if(NULLSTR(argument))
     {
       send_to_char("Syntax: setliquid <vnum> <field> <value>\n\r", ch);
       send_to_char(" Fields being one of the following:\n\r"
                    " name color shortdesc drunk thrist blood full\n\r", ch);
       return;
     }

     for(i = 0; i < MAX_CONDS; i++)
      if(!str_cmp(arg2, arg_names[i]))
      {
        found = TRUE;
        liq->mod[i]           = atoi(argument);
      }
     if(!found)
     {
      do_setliquid(ch, "");
      return;
     }
    }
    send_to_char("Done.\n\r", ch);
    save_liquids( );
    return;
  }
}

/* olc funciton for mixtures  -Nopey */
void do_setmixture(CHAR_DATA *ch, char *argument)
{
  char arg[MAX_INPUT_LENGTH];

  if(!IS_IMMORTAL(ch) || IS_NPC(ch))
  {
    send_to_char("Huh?\n\r", ch);
    return;
  }

  argument = one_argument(argument, arg);
  if(NULLSTR(arg))
  {
    send_to_char("Syntax: setmixture create <name>\n\r"
                 "        setmixture delete <name>\n\r"
                 "        setmixture list [name]\n\r"
                 "        setmixture <name> <field> <value>\n\r", ch);
    send_to_char(" Fields being one of the following:\n\r"
                 " name vnum1 vnum2 into object\n\r", ch);
    return;
  }

  if(!str_cmp(arg, "list"))
  {
    MIX_TABLE *mix = NULL;

    if(!NULLSTR(argument) && ((mix = get_mix(argument)) != NULL))
    {
     if(!mix->object)
      pager_printf_color(ch,
      "&G%s&g mixes with two liquids; &G%d&g and &G%d&g\n\r", mix->name, mix->data[0], mix->data[1]);
     else
      pager_printf_color(ch,
      "&G%s&g mixes with one object; &G%d&g; and one liquid, &G%d&g\n\r", mix->name, mix->data[0], mix->data[1]);

     pager_printf_color(ch, "&G%s&g mixes into liquid &G%d&g\n\r", mix->name, mix->data[2]);
     return;
    }
    else if(!NULLSTR(argument) && ((mix = get_mix(argument)) == NULL))
    {
      send_to_char("Invaild mixture-name.\n\rUse 'setmixture list' to gain a vaild name.\n\r", ch);
      return;
    }

    if(!first_mixture)
    {
      send_to_char("There is currently no mixtures loaded.\n\r", ch);
      return;
    }

    /* BUGFIX - thanks to Samson */
    send_to_pager_color("&G[&gType&G] &G[&gName&G]\n\r", ch);
    for(mix = first_mixture; mix; mix = mix->next)
     pager_printf_color(ch, "  %-8s %s", mix->object ? "Object" : "Liquids", mix->name);

    pager_printf_color(ch, "Use 'setmixture list [name]' to view individual mixtures.\n\r");
    pager_printf_color(ch, "Use 'setliquid list' to view the liquidtable.\n\r");
    return;
  }
  else
  if(!str_cmp(arg, "create"))
  {
    MIX_TABLE *mix = NULL;

    if(NULLSTR(argument))
    {
      send_to_char("Syntax: setmixture create <name>\n\r", ch);
      return;
    }

    CREATE(mix, MIX_TABLE, 1);
    mix->name              = STRALLOC(argument);
    mix->data[0]           = -1;
    mix->data[1]           = -1;
    mix->data[2]           = -1;
    mix->object            = FALSE;
    LINK(mix, first_mixture, last_mixture, next, prev);
    send_to_char("Done.\n\r", ch);
    save_mixtures( );
    return;
  }
  else if(!str_cmp(arg, "delete"))
  {
    MIX_TABLE *mix = NULL;

    if(NULLSTR(argument))
    {
      send_to_char("Syntax: setmixture delete <name>\n\r", ch);
      return;
    }

    if((mix = get_mix(argument)) == NULL)
    {
      send_to_char("That's not a mixture name.\n\r", ch);
      return;
    }

    UNLINK(mix, first_mixture, last_mixture, next, prev);
    if(!NULLSTR(mix->name))
     STRFREE(mix->name);
    DISPOSE(mix);
    send_to_char("Done.\n\r", ch);
    save_mixtures( );
    return;
  }
  else
  {
    char arg2[MAX_INPUT_LENGTH];
    MIX_TABLE *mix = NULL;

    if(NULLSTR(arg) || ((mix = get_mix(arg)) == NULL))
    {
      send_to_char("Syntax: setmixture <mixname> <field> <value>\n\r", ch);
      send_to_char(" Fields being one of the following:\n\r"
                   " name vnum1 vnum2 into object\n\r", ch);
      return;
    }

    argument = one_argument(argument, arg2);

    if(!str_cmp(arg2, "name"))
    {
      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setmixture <mixname> name <name>\n\r", ch);
        return;
      }
      mix->name               = STRALLOC(argument);
    }
    else if(!str_cmp(arg2, "vnum1"))
    {
      int i = atoi(argument);

      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setmixture <mixname> vnum1 <liqvnum>\n\r", ch);
        return;
      }
      mix->data[0]            = i;
    }
    else if(!str_cmp(arg2, "vnum2"))
    {
      int i = atoi(argument);

      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setmixture <mixname> vnum2 <liqvnum>\n\r", ch);
        return;
      }
      mix->data[1]            = i;
    }
    else if(!str_cmp(arg2, "object"))
    {
      mix->object = !mix->object;
      if(mix->object)
       send_to_char("Mixture -vnum2- is now an object-vnum.\n\r", ch);
      else
       send_to_char("Both mixture vnums are now liquids.\n\r", ch);
    }
    else if(!str_cmp(arg2, "into"))
    {
      int i = atoi(argument);

      if(NULLSTR(argument))
      {
        send_to_char("Syntax: setmixture <mixname> into <liqvnum>\n\r", ch);
        return;
      }
      mix->data[2]            = i;
    }
    send_to_char("Done.\n\r", ch);
    save_mixtures( );
    return;
  }
}

/* the actual -mix- funciton  -Nopey */
void do_mix(CHAR_DATA *ch, char *argument)
{
  char arg[MAX_INPUT_LENGTH];
  OBJ_DATA *iObj, *tObj = NULL;

  argument = one_argument(argument, arg);
  /* null arguments */
  if(NULLSTR(arg) || NULLSTR(argument))
  {
    send_to_char("What would you like to mix together?\n\r", ch);
    return;
  }

  /* check for objects in the inventory */
  if(((iObj = get_obj_carry(ch, arg)) == NULL) || ((tObj = get_obj_carry(ch, argument)) == NULL))
  {
    send_to_char("You aren't carrying that.\n\r", ch);
    return;
  }

  /* check itemtypes */
  if((iObj->item_type != ITEM_DRINK_CON && iObj->item_type != ITEM_DRINK_MIX)
   || (tObj->item_type != ITEM_DRINK_CON && tObj->item_type != ITEM_DRINK_MIX))
   {
     send_to_char("You can't mix that!\n\r", ch);
     return;
   }

  /* check to see if it's empty or not */
  if(iObj->value[1] <= 0 || tObj->value[1] <= 0)
  {
    send_to_char("It's empty.\n\r", ch);
    return;
  }

  /* two liquids */
  if(iObj->item_type == ITEM_DRINK_CON && tObj->item_type == ITEM_DRINK_CON)
  {
    /* check to see if the two liquids can be mixed together
     * and return the final liquid -Nopey */
    if(!liq_can_mix(iObj, tObj))
    {
      send_to_char("Those two don't mix well together.\n\r", ch);
      return;
    }
  }
  else if(iObj->item_type == ITEM_DRINK_MIX && tObj->item_type == ITEM_DRINK_CON)
  {
    if(!liqobj_can_mix(tObj, iObj))
    {
      send_to_char("Those two don't mix well together.\n\r", ch);
      return;
    }
  }
  else if(iObj->item_type == ITEM_DRINK_CON && tObj->item_type == ITEM_DRINK_MIX)
  {
    if(!liqobj_can_mix(iObj, tObj))
    {
      send_to_char("Those two don't mix well together.\n\r", ch);
      return;
    }
  }
  else
  {
    send_to_char("Those two don't mix well together.\n\r", ch);
    return;
  }
  send_to_char_color("&cYou mix them together.&g\n\r", ch);
  return;
}

/* mix a liquid with a liquid; return the final product    -Nopey */
LIQ_TABLE *liq_can_mix(OBJ_DATA *iObj, OBJ_DATA *tObj)
{
  MIX_TABLE *mix = NULL;
  bool mix_found = FALSE;

  for(mix = first_mixture; mix; mix = mix->next)
   if(mix->data[0] == iObj->value[2] || mix->data[1] == iObj->value[2])
   {
     mix_found = TRUE;
     break;
   }

  if(!mix_found)
   return NULL;

  if(mix->data[2] > -1)
  {
    LIQ_TABLE *liq = NULL;

    if((liq = get_liq_vnum(mix->data[2])) == NULL)
     return NULL;
    else
    {
      iObj->value[1]        += tObj->value[1];
      iObj->value[2]        = liq->vnum;
      tObj->value[1]        = 0;
      tObj->value[2]        = -1;
      return liq;
    }
  }
  return NULL;
}

/* used to mix an object with a liquid to form
 * another liquid; returns the result  -Nopey */
LIQ_TABLE *liqobj_can_mix(OBJ_DATA *iObj, OBJ_DATA *oLiq)
{
  MIX_TABLE *mix = NULL;
  bool mix_found = FALSE;

  for(mix = first_mixture; mix; mix = mix->next)
   if(mix->object && (mix->data[0] == iObj->value[2] || mix->data[1] == iObj->value[2]))
    if(mix->data[0] == oLiq->value[2] || mix->data[1] == oLiq->value[2])
    {
      mix_found = TRUE;
      break;
    }

  if(!mix_found)
   return NULL;

  if(mix->data[2] > -1)
  {
    LIQ_TABLE *liq = NULL;

    if((liq = get_liq_vnum(mix->data[2])) == NULL)
     return NULL;
    else
    {
      oLiq->value[1]        += iObj->value[1];
      oLiq->value[2]        = liq->vnum;
      obj_from_char(iObj);
      extract_obj(iObj);
      return liq;
    }
  }
  return NULL;
}

/* modified do_drink function -Nopey */
void do_drink( CHAR_DATA *ch, char *argument )
{
   char arg[MAX_INPUT_LENGTH];
   OBJ_DATA *obj;
   AFFECT_DATA af;
   int amount;


   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 )
                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] > MAX_COND_VALUE - 8 )
   {
        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_BLOOD:
        if ( IS_VAMPIRE(ch) && !IS_NPC(ch) )
        {
            if ( obj->timer > 0         /* if timer, must be spilled blood */
            &&   ch->level > 5
            &&   ch->pcdata->condition[COND_BLOODTHIRST] > (5+ch->level/10) )
            {
                send_to_char( "It is beneath you to stoop to drinking blood from the ground!\n\r", ch );
                send_to_char( "Unless in dire need, you'd much rather have blood from a victim's neck!\n\r", ch );
                return;
            }
            if ( ch->pcdata->condition[COND_BLOODTHIRST] < (10 + ch->level) )
            {
                if ( ch->pcdata->condition[COND_FULL] >= 100
                ||   ch->pcdata->condition[COND_THIRST] >= 100 )
                {
                    send_to_char( "You are too full to drink any blood.\n\r", ch );
                    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 );
                   send_to_char( "You relish in the replenishment of this vital fluid...\n\r", ch );
                   if (obj->value[1] <=1)
                   {
                        set_char_color( AT_BLOOD, ch );
                        send_to_char( "You drink the last drop of blood from the spill.\n\r", ch);
                        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
              send_to_char( "Alas... you cannot consume any more blood.\n\r", ch );
        }
        else
          send_to_char( "It is not in your nature to do such things.\n\r", ch );
        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_FOUNTAIN:
      {
        LIQ_TABLE *liq = NULL;

        if(obj->value[1] <= 0)
         obj->value[1] = MAX_COND_VALUE;

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

         if(!IS_NPC(ch) && obj->value[2] != 0)
         {
             gain_condition(ch, COND_THIRST, liq->mod[COND_THIRST]);
             gain_condition(ch, COND_FULL, liq->mod[COND_FULL]);
             gain_condition(ch, COND_DRUNK, liq->mod[COND_DRUNK]);
             if(IS_VAMPIRE(ch))
              gain_condition(ch, COND_BLOODTHIRST, liq->mod[COND_BLOODTHIRST]);
         }
         else if(!IS_NPC(ch) && obj->value[2] == 0)
           ch->pcdata->condition[COND_THIRST] = 200;

         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 );
         }
         break;
      }

      case ITEM_DRINK_CON:
      {
        LIQ_TABLE *liq = NULL;

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

           /* allow water to be drank; but nothing else on a full stomach     -Nopey */
           /* added is_npc check; thanks to Olcerin */
            if(!IS_NPC(ch) && (ch->pcdata->condition[COND_THIRST] == MAX_COND_VALUE || ch->pcdata->condition[COND_FULL] == MAX_COND_VALUE))
            {
              send_to_char( "Your stomach is too full to drink anymore!\n\r", ch );
              return;
            }

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

           if ( !oprog_use_trigger( ch, obj, NULL, NULL, NULL ) )
           {
              act( AT_ACTION, "$n drinks $T from $p.", ch, obj, liq->shortdesc, TO_ROOM );
              act( AT_ACTION, "You drink $T from $p.", ch, obj, liq->shortdesc, TO_CHAR );
           }

           amount = 1; /* UMIN(amount, obj->value[1]); */

           /* gain conditions accordingly              -Nopey */
           gain_condition( ch, COND_DRUNK,  liq->mod[COND_DRUNK] );
           gain_condition( ch, COND_FULL,   liq->mod[COND_FULL] );
           gain_condition( ch, COND_THIRST, liq->mod[COND_THIRST] );
           if(IS_VAMPIRE(ch))
            gain_condition( ch, COND_BLOODTHIRST, liq->mod[COND_BLOODTHIRST] );

         if( liq->type == LIQTYPE_POISON )
         {
              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  = obj->value[3];
              af.location  = APPLY_NONE;
              af.modifier  = 0;
              af.bitvector = meb(AFF_POISON);
              affect_join( ch, &af );
         }

         if( !IS_NPC(ch) )
         {
              if( ch->pcdata->condition[COND_DRUNK] > ( MAX_COND_VALUE / 2 ) && ch->pcdata->condition[COND_DRUNK] < ( MAX_COND_VALUE * .4 ) )
                   send_to_char( "You feel quite sloshed.\n\r", ch );
              else if( ch->pcdata->condition[COND_DRUNK] >= ( MAX_COND_VALUE * .4 ) && ch->pcdata->condition[COND_DRUNK] < ( MAX_COND_VALUE * .6 ) )
                   send_to_char( "You start to feel a little drunk.\n\r", ch );
              else if( ch->pcdata->condition[COND_DRUNK] >= ( MAX_COND_VALUE * .6 ) && ch->pcdata->condition[COND_DRUNK] < ( MAX_COND_VALUE * .9 ) )
                   send_to_char( "Your vision starts to get blurry.\n\r", ch );
              else if( ch->pcdata->condition[COND_DRUNK] >= ( MAX_COND_VALUE * .9 ) && ch->pcdata->condition[COND_DRUNK] < MAX_COND_VALUE )
                   send_to_char( "You feel very drunk.\n\r", ch );
              else if( ch->pcdata->condition[COND_DRUNK] == MAX_COND_VALUE )
                   send_to_char( "You feel like your going to pass out.\n\r", ch );

              if( ch->pcdata->condition[COND_THIRST] > ( MAX_COND_VALUE / 2 ) && ch->pcdata->condition[COND_THIRST] < ( MAX_COND_VALUE * .4 ) )
                   send_to_char( "Your stomach begins to slosh around.\n\r", ch );
              else if( ch->pcdata->condition[COND_THIRST] >= ( MAX_COND_VALUE * .4 ) && ch->pcdata->condition[COND_THIRST] < ( MAX_COND_VALUE * .6 ) )
                   send_to_char( "You start to feel bloated.\n\r", ch );
              else if( ch->pcdata->condition[COND_THIRST] >= ( MAX_COND_VALUE * .6 ) && ch->pcdata->condition[COND_THIRST] < ( MAX_COND_VALUE * .9 ) )
                   send_to_char( "You feel bloated.\n\r", ch );
              else if( ch->pcdata->condition[COND_THIRST] >= ( MAX_COND_VALUE * .9 ) && ch->pcdata->condition[COND_THIRST] < MAX_COND_VALUE )
                   send_to_char( "You stomach is almost filled to it's brim!\n\r", ch );
              else if( ch->pcdata->condition[COND_THIRST] == MAX_COND_VALUE)
                   send_to_char( "Your stomach is full, you can't manage to get anymore down.\n\r", ch );

           /* Hopefully this is the reason why that crap was happening. =0P */
           if( IS_VAMPIRE(ch) )
           {
              if( ch->pcdata->condition[COND_BLOODTHIRST] > ( MAX_COND_VALUE / 2 ) && ch->pcdata->condition[COND_BLOODTHIRST] < ( MAX_COND_VALUE * .4 ) )
                send_to_char_color( "&rYou replenish your body with the vidal fluid.\n\r", ch );
              else if( ch->pcdata->condition[COND_BLOODTHIRST] >= ( MAX_COND_VALUE * .4 ) && ch->pcdata->condition[COND_BLOODTHIRST] < ( MAX_COND_VALUE * .6 ) )
                send_to_char_color( "&rYour thirst for blood begins to decrease.\n\r", ch );
              else if( ch->pcdata->condition[COND_BLOODTHIRST] >= ( MAX_COND_VALUE *.6 ) && ch->pcdata->condition[COND_BLOODTHIRST] < ( MAX_COND_VALUE * .9 ) )
                send_to_char_color( "&rThe thirst for blood begins to leave you...\n\r", ch );
              else if( ch->pcdata->condition[COND_BLOODTHIRST] >= ( MAX_COND_VALUE * .9 ) && ch->pcdata->condition[COND_BLOODTHIRST] < MAX_COND_VALUE )
                send_to_char( "&rYou drink the last drop of the fluid, the thirst for more leaves your body.\n\r", ch );
           }
           else if( !IS_VAMPIRE(ch) && ch->pcdata->condition[COND_BLOODTHIRST] >= MAX_COND_VALUE)
           {
                ch->pcdata->condition[COND_BLOODTHIRST] = MAX_COND_VALUE;
           }
         }

           obj->value[1] -= amount;
           if ( obj->value[1] <= 0 ) /* Come now, what good is a drink container that vanishes?? */
           {
              obj->value[1] = 0; /* Prevents negative values - Samson */
              send_to_char( "You drink the last drop from your container.\n\r", ch );
              if ( cur_obj == obj->serial )
                 global_objcode = rOBJ_DRUNK;
              /* extract_obj( obj );      Modified 4-21-98 - Samson */
           }
           break;
      }
   }

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

/* standard liquid functions           -Nopey */
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 = MAX_COND_VALUE;
        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_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;
          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 )
                ||  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;
    }
}