eldhamud/boards/
eldhamud/clans/
eldhamud/classes/
eldhamud/councils/
eldhamud/deity/
eldhamud/doc/
eldhamud/doc/DIKU/
eldhamud/doc/MERC/
eldhamud/doc/mudprogs/
eldhamud/gods/
eldhamud/houses/
eldhamud/lockers/
eldhamud/player/a/
/****************************************************************************
 *   _______  _        ______            _______  _______           ______   *
 *  (  ____ \( \      (  __  \ |\     /|(  ___  )(       )|\     /|(  __  \  *
 *  | (    \/| (      | (  \  )| )   ( || (   ) || () () || )   ( || (  \  ) *
 *  | (__    | |      | |   ) || (___) || (___) || || || || |   | || |   ) | *
 *  |  __)   | |      | |   | ||  ___  ||  ___  || |(_)| || |   | || |   | | *
 *  | (      | |      | |   ) || (   ) || (   ) || |   | || |   | || |   ) | *
 *  | (____/\| (____/\| (__/  )| )   ( || )   ( || )   ( || (___) || (__/  ) *
 *  (_______/(_______/(______/ |/     \||/     \||/     \|(_______)(______/  *
 *              +-+-+-+  +-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+-+-+              *
 *              |T|h|e|  |O|a|k|l|a|n|d|  |C|h|r|o|n|i|c|l|e|s|              *
 *              +-+-+-+  +-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+-+-+              *
 * ------------------------------------------------------------------------- *
 * EldhaMUD code (C) 2003-2005 by Robert Powell (Tommi)                      *
 * EldhaMUD Team: Celest, Altere and Krelowyn                                *
 * ------------------------------------------------------------------------- *
 *                                                                           *
 ****************************************************************************/
/*****************************************************
**     _________       __                           **
**     \_   ___ \_____|__| _____  ________  ___     **
**      /    \  \/_  __ \ |/     \/  ___/_ \/   \   **
**      \     \___|  | \/ |  | |  \___ \  / ) |  \  **
**       \______  /__| |__|__|_|  /____ \__/__|  /  **
**         ____\/____ _        \/ ___ \/      \/    **
**         \______   \ |_____  __| _/___            **
**          |    |  _/ |\__  \/ __ | __ \           **
**          |    |   \ |_/ __ \  / | ___/_          **
**          |_____  /__/____  /_  /___  /           **
**               \/Antipode\/  \/    \/             **
******************************************************
******************************************************
**       Copyright 2000-2003 Crimson Blade          **
******************************************************
** Contributors: Noplex, Krowe, Emberlyna, Lanthos  **
******************************************************/

/****************************************************************************
 * 	                       Version History                              *
 ****************************************************************************
 *  (v1.0) - Liquidtable converted into linked list, original 15 Smaug liqs *
 *           now read from a .dat file in /system                           *
 *  (v1.5) - OLC support added to create, edit, and delete liquids while    *
 *           the game is still running, automatic edit.                     *
 *  (v2.0) - Mixture support code added. Liquids can now be mixed with      *
 *           other liquids to form a result.                                *
 *  (v2.2) - Liquid statistics command added (liquids) shows all information*
 *           about the given liquid.                                        *
 *  (v2.3) - OLC addition for mixtures.                                     *
 *  (v2.4) - Mixtures are now saved into a seperate file and one linked list*
 *           because of some saving and loading issues. All the code has    *
 *           been modified to accept the new format. "liq_can_mix" function *
 *           introduced. "mix" command introduced to mix liquids.           *
 *  (v2.5) - Thanks to Samson for some polishing and bugfixing, we now have *
 *           a (hopefully) fully funcitonal copy =).                        *
 *  (v2.6) - "Fill" and "Empty" functions have been fixed to allow for the  *
 *           new liquidsystem.                                              *
 *  (v2.7) - Forgot to fix blood support... fixed.                          *
 *         - IS_VAMPIRE ifcheck placed in do_drink                          * 
 *  	   - Blood fix for blood on the ground.                             *
 *         - do_look/do_exam fix from Sirek.                                *
 *  (v2.8) - Ability to mix objects into liquids.                           *
 *	     (original code/concept -Sirek)                                 *
 *  (v2.9) - Added in checks to make sure you are adding valid vnums to a   *
 *           mixture.  Changed display of mixture to show names and vnums   *
 *           of components.  Added in a manual save and an updated help file*
 ****************************************************************************/

/*
 * File: liquids.c
 * Name: Liquidtable Module (3.0b)
 * 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 "./Headers/mud.h"

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



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

/* globals */
LIQ_TABLE * liquid_table[MAX_LIQUIDS];

MIX_TABLE * first_mixture;

MIX_TABLE * last_mixture;


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


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



/* locals */
int top_liquid;

int liq_count;

int file_version;

bool file_old;


/* 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;


  snprintf( filename, 256, "%sliquids.dat", SYSTEM_DIR );

  if( !( fp = fopen( filename, "w" ) ) )

  {

    bug( "save_liquids(): cannot open %s for writing", filename );

    return;

  }

  fprintf( fp, "%s", "#VERSION 3\n" );

  for( i = 0; i < top_liquid; i++ )

  {

    if( !liquid_table[i] )

      continue;


    liq = liquid_table[i];


    fprintf( fp, "%s", "#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\n", liq->mod[COND_DRUNK], liq->mod[COND_FULL], liq->mod[COND_THIRST] );

    fprintf( fp, "%s", "End\n\n" );

  }

  fprintf( fp, "%s", "#END\n" );

  fclose( fp );

  fp = NULL;

  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->vnum = -1;

  liq->type = -1;

  for( i = 0; i < MAX_CONDS; i++ )

    liq->mod[i] = -1;


  for( ;; )

  {

    const 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 );

        if( file_version < 3 )

          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( "%s: no match for %s", __FUNCTION__, word );

      fread_to_eol( fp );

    }

  }

}



/* load the liquids from the liquidtable.dat file in the system directory -Nopey */
void load_liquids( void )
{

  FILE * fp = NULL;

  char filename[256];


  file_version = 0;

  snprintf( filename, 256, "%sliquids.dat", SYSTEM_DIR );

  if( !( fp = fopen( filename, "r" ) ) )

  {

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


      if( !liq )

        bug( "%s", "load_liquids(): returned NULL liquid" );

      else

      {

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

  fp = NULL;

  return;

}



/* save the mixtures to the mixture table -Nopey */
void save_mixtures( void )
{

  MIX_TABLE * mix = NULL;

  FILE * fp = NULL;

  char filename[256];


  snprintf( filename, 256, "%smixtures.dat", SYSTEM_DIR );

  if( !( fp = fopen( filename, "w" ) ) )

  {

    bug( "save_mixtures(): cannot open %s for writing", filename );

    return;

  }


  fprintf( fp, "%s", "#VERSION 3\n" );

  for( mix = first_mixture; mix; mix = mix->next )

  {

    fprintf( fp, "%s", "#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, "%s", "End\n" );

  }

  fprintf( fp, "%s", "#END\n" );

  fclose( fp );

  fp = NULL;

  return;

}



/* read the mixtures into the structure -Nopey */
MIX_TABLE * fread_mixture( FILE * fp )
{

  MIX_TABLE * mix = NULL;

  bool fMatch = FALSE;


  CREATE( mix, MIX_TABLE, 1 );

  mix->data[0] = -1;

  mix->data[1] = -1;

  mix->data[2] = -1;

  mix->object = FALSE;


  for( ;; )

  {

    const 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" ) )

      {

        return mix;

      }

      break;


    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;


    case 'W':

      if( !str_cmp( word, "With" ) )

      {

        mix->data[0] = fread_number( fp );

        mix->data[1] = fread_number( fp );

      }

      break;

    }

    if( !fMatch )

    {

      bug( "%s: no match for %s", __FUNCTION__, word );

      fread_to_eol( fp );

    }

  }

}



/* load the mixtures from the mixture table         -Nopey */
void load_mixtures( void )
{

  FILE * fp = NULL;

  char filename[256];


  file_version = 0;


  snprintf( filename, 256, "%smixtures.dat", SYSTEM_DIR );

  if( !( fp = fopen( filename, "r" ) ) )

  {

    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_version = fread_number( fp );

      continue;

    }

    else if( !str_cmp( word, "MIXTURE" ) )

    {

      MIX_TABLE * mix = NULL;


      mix = fread_mixture( fp );

      if( !mix )

        bug( "%s", "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 );

  fp = NULL;

  return;

}



/* 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;

}



/* Function to display liquid list. - Tarl 9 Jan 03 */
void do_showliquid( CHAR_DATA * ch, char *argument )
{

  LIQ_TABLE * liq = NULL;

  int i;


  if( !IS_IMMORTAL( ch ) || IS_NPC( ch ) )

  {

    send_to_char( "Huh?\n\r", ch );

    return;

  }


  if( !NULLSTR( argument ) && ( ( liq = get_liq( argument ) ) != NULL ) )

  {

    if( !NULLSTR( liq->name ) )

      pager_printf( ch, "&GLiquid information for:&g %s\n\r", liq->name );

    if( !NULLSTR( liq->shortdesc ) )

      pager_printf( ch, "&GLiquid shortdesc:&g\t %s\n\r", liq->shortdesc );

    if( !NULLSTR( liq->color ) )

      pager_printf( ch, "&GLiquid color:&g\t %s\n\r", liq->color );

    pager_printf( ch, "&GLiquid vnum:&g\t %d\n\r", liq->vnum );

    pager_printf( ch, "&GLiquid type:&g\t %s\n\r", liquid_types[liq->type] );

    send_to_pager( "&GLiquid Modifiers\n\r", ch );

    for( i = 0; i < MAX_CONDS; i++ )

      if( liquid_table[i] )

        pager_printf( 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 'showliquid' to gain a vaild liquidvnum.\n\r", ch );

    return;

  }

  send_to_pager( "&G[&gVnum&G] [&gName&G]\n\r", ch );

  send_to_pager( "-------------------------\n\r", ch );

  for( i = 0; i <= top_liquid; i++ )

  {

    if( !liquid_table[i] )

      continue;

    pager_printf( ch, "  %-7d %s\n\r", liquid_table[i]->vnum, liquid_table[i]->name );

  }

  send_to_pager( "\n\rUse 'showliquid [vnum]' to view individual liquids.\n\r", ch );

  send_to_pager( "Use 'showmixture' to view the mixturetable.\n\r", ch );

  return;


}



/* olc function for liquids   -Nopey */
void do_setliquid( CHAR_DATA * ch, char *argument )
{

  char arg[MIL];


  if( !IS_IMMORTAL( ch ) || IS_NPC( ch ) )

  {

    send_to_char( "Huh?\n\r", ch );

    return;

  }


  smash_tilde( argument );

  argument = one_argument( argument, arg );


  if( NULLSTR( arg ) )

  {

    send_to_char( "Syntax: setliquid <vnum> <field> <value>\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, "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->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;


    if( NULLSTR( argument ) )

    {

      send_to_char( "Syntax: setliquid delete <vnum>\n\r", ch );

      return;

    }


    if( !is_number( argument ) )

    {

      if( !( liq = get_liq( argument ) ) )

      {

        send_to_char( "No such liquid type. Use 'showliquid' to get a valid list.\n\r", ch );

        return;

      }

    }

    else

    {

      int i = atoi( argument );


      if( !( liq = get_liq_vnum( i ) ) )

      {

        send_to_char( "No such vnum. Use 'showliquid' to get the vnum.\n\r", ch );

        return;

      }

    }


    STRFREE( liq->name );

    STRFREE( liq->color );

    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.. Liquids saved.\n\r", ch );

    save_liquids(  );

    return;

  }

  else

  {

    char arg2[MIL];

    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;

      }

      STRFREE( liq->name );

      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;

      }

      STRFREE( liq->color );

      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;

      }

      STRFREE( liq->shortdesc );

      liq->shortdesc = STRALLOC( argument );

    }

    else if( !str_cmp( arg2, "type" ) )

    {

      char arg3[MIL];

      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" };


      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;

  }

}


void displaymixture( CHAR_DATA * ch, MIX_TABLE * mix )
{

  send_to_pager( " .-.                ,\n\r", ch );

  send_to_pager( "`._ ,\n\r", ch );

  send_to_pager( "   \\ \\                 o\n\r", ch );

  send_to_pager( "    \\ `-,.\n\r", ch );

  send_to_pager( "   .'o .  `.[]           o\n\r", ch );

  send_to_pager( "<~- - , ,[].'.[] ~>     ___\n\r", ch );

  send_to_pager( " :               :     (-~.)\n\r", ch );

  send_to_pager( "  `             '       `|'\n\r", ch );

  send_to_pager( "   `           '         |\n\r", ch );

  send_to_pager( "    `-.     .-'          |\n\r", ch );

  send_to_pager( "-----{. _ _ .}-------------------\n\r", ch );


  pager_printf( ch, "&gRecipe for Mixture &G%s&g:\n\r", mix->name );

  send_to_pager( "---------------------------------\n\r", ch );

  if( !mix->object )  /*this is an object */

  {

    LIQ_TABLE * ingred1 = get_liq_vnum( mix->data[0] );

    LIQ_TABLE * ingred2 = get_liq_vnum( mix->data[1] );

    send_to_pager( "&wCombine two liquids to create this mixture:\n\r", ch );

    if( !ingred1 )

    {

      pager_printf( ch, "Vnum1 (%d) is invalid, tell an Admin\n\r", mix->data[0] );

    }

    else

    {

      pager_printf( ch, "&wOne part &G%s&w (%d)\n\r", ingred1->name, mix->data[0] );

    }


    if( !ingred2 )

    {

      pager_printf( ch, "Vnum2 (%d) is invalid, tell an Admin\n\r", mix->data[1] );

    }

    else

    {

      pager_printf( ch, "&wAnd part &G%s&w (%d)&D\n\r", ingred2->name, mix->data[1] );

    }

  }

  else

  {

    OBJ_INDEX_DATA * obj = get_obj_index( mix->data[0] );

    if( !obj )

    {

      pager_printf( ch, "%s has a bad object vnum %d, inform an Admin\n\r", mix->name, mix->data[0] );

      return;

    }

    else

    {

      LIQ_TABLE * ingred1 = get_liq_vnum( mix->data[1] );

      send_to_pager( "Combine an object and a liquid in this mixture\n\r", ch );

      pager_printf( ch, "&wMix &G%s&w (%d)\n\r", obj->name, mix->data[0] );

      pager_printf( ch, "&winto one part &G%s&w (%d)&D\n\r", ingred1->name, mix->data[1] );

    }

  }

  return;

}



/* Function for showmixture - Tarl 9 Jan 03 */
void do_showmixture( CHAR_DATA * ch, char *argument )
{

  MIX_TABLE * mix = NULL;


  if( !IS_IMMORTAL( ch ) || IS_NPC( ch ) )

  {

    send_to_char( "Huh?\n\r", ch );

    return;

  }


  if( !NULLSTR( argument ) && ( ( mix = get_mix( argument ) ) != NULL ) )

  {

    displaymixture( ch, mix );

    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 are currently no mixtures loaded.\n\r", ch );

    return;

  }


  send_to_pager( "&G[&gType&G] &G[&gName&G]\n\r", ch );

  send_to_pager( "-----------------------\n\r", ch );

  for( mix = first_mixture; mix; mix = mix->next )

    pager_printf( ch, "  %-12s &g%s&D\n\r", mix->object ? "&PObject&D" : "&BLiquid&D", mix->name );


  send_to_pager( "\n\r&gUse 'showmixture [name]' to view individual mixtures.\n\r", ch );

  send_to_pager( "&gUse 'showliquid' to view the liquidtable.\n\r&d", ch );

  return;

}




/* olc funciton for mixtures  -Nopey */
void do_setmixture( CHAR_DATA * ch, char *argument )
{

  char arg[MIL];

  LIQ_TABLE * liq = NULL;


  if( !IS_IMMORTAL( ch ) || IS_NPC( ch ) )

  {

    send_to_char( "Huh?\n\r", ch );

    return;

  }


  smash_tilde( argument );

  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 save - (saves table)\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 ) )

    {

      displaymixture( ch, mix );

      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 are currently no mixtures loaded.\n\r", ch );

      return;

    }


    send_to_pager( "&G[&gType&G] &G[&gName&G]\n\r", ch );

    send_to_pager( "-----------------------\n\r", ch );

    for( mix = first_mixture; mix; mix = mix->next )

      pager_printf( ch, "  %-12s &g%s&D\n\r", mix->object ? "&PObject&D" : "&BLiquid&D", mix->name );


    send_to_pager( "\n\r&gUse 'showmixture [name]' to view individual mixtures.\n\r", ch );

    send_to_pager( "&gUse 'showliquid' to view the liquidtable.&d\n\r", ch );

    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, "save" ) )

  {

    save_mixtures(  );

    send_to_char( "Mixture table saved.\n\r", ch );

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

    STRFREE( mix->name );

    DISPOSE( mix );

    send_to_char( "Done.\n\r", ch );

    save_mixtures(  );

    return;

  }

  else

  {

    char arg2[MIL];

    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;

      }

      STRFREE( mix->name );

      mix->name = STRALLOC( argument );

    }

    else if( !str_cmp( arg2, "vnum1" ) )

    {

      int i = 0;


      if( is_number( argument ) )

      {

        i = atoi( argument );

      }

      else

      {

        send_to_char( "Invalid liquid vnum.\n\r", ch );

        send_to_char( "Syntax: setmixture <mixname> vnum1 <liqvnum or objvnum>\n\r", ch );

        return;

      }


      if( mix->object == TRUE )

      {

        OBJ_INDEX_DATA * obj = get_obj_index( i );

        if( !obj )

        {

          ch_printf( ch, "Invalid object vnum %d\n\r", i );

          return;

        }

        else

        {

          mix->data[0] = i;

          ch_printf( ch, "Mixture object set to %d - %s\n\r", i, obj->name );

        }

      }

      else

      {

        liq = get_liq_vnum( i );

        if( !liq )

        {

          ch_printf( ch, "Liquid vnum %d does not exist\n\r", i );

          return;

        }

        else

        {

          mix->data[0] = i;

          ch_printf( ch, "Mixture Vnum1 set to %s \n\r", liq->name );


        }

      }

    }

    else if( !str_cmp( arg2, "vnum2" ) )

    {

      int i = 0;


      if( is_number( argument ) )

      {

        i = atoi( argument );

      }

      else

      {

        send_to_char( "Invalid liquid vnum.\n\r", ch );

        send_to_char( "Syntax: setmixture <mixname> vnum2 <liqvnum>\n\r", ch );

        return;

      }


      /*
       * Verify liq exists
       */
      liq = get_liq_vnum( i );

      if( !liq )

      {

        ch_printf( ch, "Liquid vnum %d does not exist\n\r", i );

        return;

      }

      else

      {

        mix->data[1] = i;

        ch_printf( ch, "Mixture Vnum2 set to %s \n\r", liq->name );

      }

    }

    else if( !str_cmp( arg2, "object" ) )

    {

      if( mix->object == FALSE )

      {

        mix->object = TRUE;

        send_to_char( "Mixture -vnum1- is now an object-vnum.\n\r", ch );

      }

      else

      {

        mix->object = FALSE;

        send_to_char( "Both mixture vnums are now liquids.\n\r", ch );

      }

    }

    else if( !str_cmp( arg2, "into" ) )

    {

      int i;


      if( is_number( argument ) )

      {

        i = atoi( argument );

      }

      else

      {

        send_to_char( "Invalid liquid vnum.\n\r", ch );

        send_to_char( "Syntax: setmixture <mixname> into <liqvnum>\n\r", ch );

        return;

      }


      liq = get_liq_vnum( i );

      if( !liq )

      {

        ch_printf( ch, "Liquid vnum %d does not exist\n\r", i );

        return;

      }

      else

      {

        mix->data[2] = i;

        ch_printf( ch, "Mixture will now turn into %s \n\r", liq->name );

      }

    }


    send_to_char( "Done.. Saving Mixtures.\n\r", ch );

    save_mixtures(  );

    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;

      separate_obj( iObj );

      obj_from_char( iObj );

      extract_obj( iObj );

      return liq;

    }

  }

  return NULL;

}



/* the actual -mix- funciton  -Nopey */
void do_mix( CHAR_DATA * ch, char *argument )
{

  char arg[MIL];

  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( "&cYou mix them together.&g\n\r", ch );

  return;

}



/* modified do_drink function -Nopey */
void do_drink( CHAR_DATA * ch, char *argument )
{

  char arg[MIL];

  OBJ_DATA * obj;

  AFFECT_DATA af;

  int amount;

  bool immuneH = FALSE, immuneT = FALSE;


  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] > 40 )

  {

    send_to_char( "You fail to reach your mouth.  *Hic*\n\r", ch );

    return;

  }


  if( !IS_NPC( ch ) )

  {

    if( ch->pcdata->condition[COND_THIRST] == -1 )

      immuneT = TRUE;

    if( ch->pcdata->condition[COND_FULL] == -1 )

      immuneH = TRUE;

  }


  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:

    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 )

    {

      char buf[MIL];


      snprintf( buf, MIL, "quaff %s", obj->name );

      do_quaff( ch, buf );

    }

    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] = 100;


      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] );

      }

      else if( !IS_NPC( ch ) && obj->value[2] == 0 )

        ch->pcdata->condition[COND_THIRST] = 100;


      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 
       */
      if( !IS_NPC( ch ) && ( ch->pcdata->condition[COND_THIRST] == 100
                             ||ch->pcdata->condition[COND_FULL] == 100 ) )

      {

        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( 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] > 50 && ch->pcdata->condition[COND_DRUNK] < 40 )

          send_to_char( "You feel quite sloshed.\n\r", ch );

        else if( ch->pcdata->condition[COND_DRUNK] >= 40 && ch->pcdata->condition[COND_DRUNK] < 60 )

          send_to_char( "You start to feel a little drunk.\n\r", ch );

        else if( ch->pcdata->condition[COND_DRUNK] >= 60 && ch->pcdata->condition[COND_DRUNK] < 90 )

          send_to_char( "Your vision starts to get blurry.\n\r", ch );

        else if( ch->pcdata->condition[COND_DRUNK] >= 90 && ch->pcdata->condition[COND_DRUNK] < 100 )

          send_to_char( "You feel very drunk.\n\r", ch );

        else if( ch->pcdata->condition[COND_DRUNK] == 100 )

          send_to_char( "You feel like your going to pass out.\n\r", ch );


        if( ch->pcdata->condition[COND_THIRST] > 50 && ch->pcdata->condition[COND_THIRST] < 40 )

          send_to_char( "Your stomach begins to slosh around.\n\r", ch );

        else if( ch->pcdata->condition[COND_THIRST] >= 40 && ch->pcdata->condition[COND_THIRST] < 60 )

          send_to_char( "You start to feel bloated.\n\r", ch );

        else if( ch->pcdata->condition[COND_THIRST] >= 60 && ch->pcdata->condition[COND_THIRST] < 90 )

          send_to_char( "You feel bloated.\n\r", ch );

        else if( ch->pcdata->condition[COND_THIRST] >= 90 && ch->pcdata->condition[COND_THIRST] < 100 )

          send_to_char( "You stomach is almost filled to it's brim!\n\r", ch );

        else if( ch->pcdata->condition[COND_THIRST] == 100 )

          send_to_char( "Your stomach is full, you can't manage to get anymore down.\n\r", ch );

      }


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

      }

      break;

    }

  }


  if( !IS_NPC( ch ) )

  {

    if( immuneH )

      ch->pcdata->condition[COND_FULL] = -1;

    if( immuneT )

      ch->pcdata->condition[COND_THIRST] = -1;

  }


  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[MIL], arg2[MIL];

  OBJ_DATA * obj;

  OBJ_DATA * source;

  short 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

  {

    if( 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;


    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[MIL];

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

    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[MIL], arg2[MIL];


  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 || 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 || arg2[0] == '\0' )

    {

      if( xIS_SET( ch->in_room->room_flags, ROOM_NODROP ) || xIS_SET( ch->act, PLR_LITTERBUG ) )

      {

        send_to_char( "&[magic]A magical force stops you!\n\r", ch );

        send_to_char( "&[tell]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;

  }

}


void free_liquiddata( void )
{

  MIX_TABLE * mix, *mix_next;

  LIQ_TABLE * liq;

  int loopa;


  for( mix = first_mixture; mix; mix = mix_next )

  {

    mix_next = mix->next;

    UNLINK( mix, first_mixture, last_mixture, next, prev );

    STRFREE( mix->name );

    DISPOSE( mix );

  }

  for( loopa = 0; loopa <= top_liquid; loopa++ )

  {

    liq = get_liq_vnum( loopa );

    STRFREE( liq->name );

    STRFREE( liq->color );

    STRFREE( liq->shortdesc );

    DISPOSE( liq );

  }

  return;

}