eldhamud/boards/
eldhamud/clans/
eldhamud/classes/
eldhamud/councils/
eldhamud/deity/
eldhamud/doc/
eldhamud/doc/DIKU/
eldhamud/doc/MERC/
eldhamud/doc/mudprogs/
eldhamud/houses/
/*****************************************************
**     _________       __                           **
**     \_   ___ \_____|__| _____  ________  ___     **
**      /    \  \/_  __ \ |/     \/  ___/_ \/   \   **
**      \     \___|  | \/ |  | |  \___ \  / ) |  \  **
**       \______  /__| |__|__|_|  /____ \__/__|  /  **
**         ____\/____ _        \/ ___ \/      \/    **
**         \______   \ |_____  __| _/___            **
**          |    |  _/ |\__  \/ __ | __ \           **
**          |    |   \ |_/ __ \  / | ___/_          **
**          |_____  /__/____  /_  /___  /           **
**               \/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 "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;
}