Eldhamud_2.5.83/clans/
Eldhamud_2.5.83/classes/
Eldhamud_2.5.83/doc/
Eldhamud_2.5.83/doc/DIKU/
Eldhamud_2.5.83/doc/MERC/
Eldhamud_2.5.83/doc/mudprogs/
Eldhamud_2.5.83/houses/
/****************************************************************************
 *			Eldhamud Codebase V2.2				    *
 * ------------------------------------------------------------------------ *
 *          EldhaMUD code (C) 2003-2008 by Robert Powell (Tommi)            *
 * ------------------------------------------------------------------------ *
******************************************************
**       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;     				\
}

/* 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?\r\n", ch );

    return;

  }

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

  {

    if ( !NULLSTR ( liq->name ) )

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

    if ( !NULLSTR ( liq->shortdesc ) )

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

    if ( !NULLSTR ( liq->color ) )

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

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

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

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

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

      if ( liquid_table[i] )

        pager_printf ( ch, "&G%s:&g\t %d\r\n", mod_types[i], liq->mod[i] );

    return;

  }

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

    {

      send_to_char ( "Invaild liquid-vnum.\r\nUse 'showliquid' to gain a vaild liquidvnum.\r\n", ch );

      return;

    }

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

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

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

  {

    if ( !liquid_table[i] )

      continue;

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

  }

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

  send_to_pager ( "Use 'showmixture' to view the mixturetable.\r\n", 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?\r\n", ch );

    return;

  }

  smash_tilde ( argument );

  argument = one_argument ( argument, arg );

  if ( NULLSTR ( arg ) )

  {

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

    send_to_char ( " Fields being one of the following:\r\n" " name color type shortdesc drunk thrist blood full\r\n",
                   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\r\n"
                     "the hard-coded max number of liquids.\r\n", ch );

      return;

    }

    if ( NULLSTR ( argument ) )

    {

      send_to_char ( "Syntax: setliquid create <name>\r\n", 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.\r\n", ch );

    save_liquids(  );

    return;

  }

  else
    if ( !str_cmp ( arg, "delete" ) )

    {

      LIQ_TABLE * liq = NULL;

      if ( NULLSTR ( argument ) )

      {

        send_to_char ( "Syntax: setliquid delete <vnum>\r\n", 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.\r\n", 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.\r\n", 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.\r\n", 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>\r\n", ch );

        send_to_char ( " Fields being one of the following:\r\n" " name color shortdesc drunk thrist blood full\r\n", ch );

        return;

      }

      if ( ( liq = get_liq ( arg ) ) == NULL )

      {

        send_to_char ( "Invaild liquid-name or vnum.\r\n", ch );

        return;

      }

      if ( !str_cmp ( arg2, "name" ) )

      {

        if ( NULLSTR ( argument ) )

        {

          send_to_char ( "Syntax: setliquid <vnum> name <name>\r\n", 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>\r\n", 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>\r\n", 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>\r\n", 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>\r\n", ch );
                send_to_char ( " Fields being one of the following:\r\n"
                              " name color shortdesc drunk thrist blood full\r\n", 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.\r\n", ch );

      save_liquids(  );

      return;

    }

}

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

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

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

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

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

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

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

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

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

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

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

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

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

  send_to_pager ( "---------------------------------\r\n", 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:\r\n", ch );

    if ( !ingred1 )

    {

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

    }

    else

    {

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

    }

    if ( !ingred2 )

    {

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

    }

    else

    {

      pager_printf ( ch, "&wAnd part &G%s&w (%d)&D\r\n", 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\r\n", 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\r\n", ch );

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

      pager_printf ( ch, "&winto one part &G%s&w (%d)&D\r\n", 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?\r\n", 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.\r\nUse 'setmixture list' to gain a vaild name.\r\n", ch );

      return;

    }

  if ( !first_mixture )

  {

    send_to_char ( "There are currently no mixtures loaded.\r\n", ch );

    return;

  }

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

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

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

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

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

  send_to_pager ( "&gUse 'showliquid' to view the liquidtable.\r\n&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?\r\n", ch );

    return;

  }

  smash_tilde ( argument );

  argument = one_argument ( argument, arg );

  if ( NULLSTR ( arg ) )

  {

    send_to_char ( "Syntax: setmixture create <name>\r\n"
                   "        setmixture delete <name>\r\n"
                   "        setmixture list [name]\r\n"
                   "        setmixture save - (saves table)\r\n" "        setmixture <name> <field> <value>\r\n", ch );

    send_to_char ( " Fields being one of the following:\r\n" " name vnum1 vnum2 into object\r\n", 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.\r\nUse 'setmixture list' to gain a vaild name.\r\n", ch );

        return;

      }

    if ( !first_mixture )

    {

      send_to_char ( "There are currently no mixtures loaded.\r\n", ch );

      return;

    }

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

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

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

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

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

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

    return;

  }

  else
    if ( !str_cmp ( arg, "create" ) )

    {

      MIX_TABLE * mix = NULL;

      if ( NULLSTR ( argument ) )

      {

        send_to_char ( "Syntax: setmixture create <name>\r\n", 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.\r\n", ch );

      save_mixtures(  );

      return;

    }

    else
      if ( !str_cmp ( arg, "save" ) )

      {

        save_mixtures(  );

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

        return;

      }

      else
        if ( !str_cmp ( arg, "delete" ) )

        {

          MIX_TABLE * mix = NULL;

          if ( NULLSTR ( argument ) )

          {

            send_to_char ( "Syntax: setmixture delete <name>\r\n", ch );

            return;

          }

          if ( ( mix = get_mix ( argument ) ) == NULL )

          {

            send_to_char ( "That's not a mixture name.\r\n", ch );

            return;

          }

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

          STRFREE ( mix->name );

          DISPOSE ( mix );

          send_to_char ( "Done.\r\n", 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>\r\n", ch );

            send_to_char ( " Fields being one of the following:\r\n" " name vnum1 vnum2 into object\r\n", ch );

            return;

          }

          argument = one_argument ( argument, arg2 );

          if ( !str_cmp ( arg2, "name" ) )

          {

            if ( NULLSTR ( argument ) )

            {

              send_to_char ( "Syntax: setmixture <mixname> name <name>\r\n", 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.\r\n", ch );

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

                return;

              }

              if ( mix->object == TRUE )

              {

                OBJ_INDEX_DATA * obj = get_obj_index ( i );

                if ( !obj )

                {

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

                  return;

                }

                else

                {

                  mix->data[0] = i;

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

                }

              }

              else

              {

                liq = get_liq_vnum ( i );

                if ( !liq )

                {

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

                  return;

                }

                else

                {

                  mix->data[0] = i;

                  ch_printf ( ch, "Mixture Vnum1 set to %s \r\n", 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.\r\n", ch );

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

                  return;

                }

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

                if ( !liq )

                {

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

                  return;

                }

                else

                {

                  mix->data[1] = i;

                  ch_printf ( ch, "Mixture Vnum2 set to %s \r\n", 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.\r\n", ch );

                  }

                  else

                  {

                    mix->object = FALSE;

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

                  }

                }

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

                  {

                    int i;

                    if ( is_number ( argument ) )

                    {

                      i = atoi ( argument );

                    }

                    else

                    {

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

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

                      return;

                    }

                    liq = get_liq_vnum ( i );

                    if ( !liq )

                    {

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

                      return;

                    }

                    else

                    {

                      mix->data[2] = i;

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

                    }

                  }

          send_to_char ( "Done.. Saving Mixtures.\r\n", 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?\r\n", 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.\r\n", 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!\r\n", 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.\r\n", 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.\r\n", 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.\r\n", 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.\r\n", ch );

          return;

        }

      }

      else

      {

        send_to_char ( "Those two don't mix well together.\r\n", ch );

        return;

      }

  send_to_char ( "&cYou mix them together.&g\r\n", 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;
  argument = one_argument ( argument, arg );

  /*
   *
   * *
   * * * munch optional words
   */

  if ( !str_cmp ( arg, "from" ) && argument[0] != STRING_NULL )
    argument = one_argument ( argument, arg );

  if ( arg[0] == STRING_NULL )
  {
    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?\r\n", ch );
      return;
    }
  }
  else
  {
    if ( ( obj = get_obj_here ( ch, arg ) ) == NULL )
    {
      send_to_char ( "You can't find it.\r\n", ch );
      return;
    }
  }
  if ( obj->count > 1 && obj->item_type != ITEM_FOUNTAIN )
    separate_obj ( obj );
  
  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.\r\n", 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.\r\n", 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 ( !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.\r\n", ch );
        }
        break;
      }
    case ITEM_DRINK_CON:
      {
        LIQ_TABLE * liq = NULL;
        if ( obj->value[1] <= 0 )
        {
          send_to_char ( "It is already empty.\r\n", 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]); */
       
        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 );
          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 );
        }

        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.\r\n", ch );
        }
        break;
      }
  }
  if ( who_fighting ( ch ) && IS_PKILL ( ch ) )
    WAIT_STATE ( ch, PULSE_PER_SECOND / 3 );
  else
    WAIT_STATE ( ch, PULSE_PER_SECOND );
  return;
}


/* standard liquid functions           -Nopey */
void do_fill ( CHAR_DATA * ch, char *argument )
{
  char arg1[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] != STRING_NULL )
    argument = one_argument ( argument, arg2 );
  if ( arg1[0] == STRING_NULL )
  {
    send_to_char ( "Fill what?\r\n", ch );
    return;
  }
 
  if ( ( obj = get_obj_carry ( ch, arg1 ) ) == NULL )
  {
    send_to_char ( "You do not have that item.\r\n", 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.\r\n", 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.\r\n", ch );
      return;
    }
  }
  else
  {
    if ( obj->value[1] >= obj->value[0] )
    {
      send_to_char ( "It's already full as it can be.\r\n", 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.\r\n", ch );
    return;
  }
  if ( arg2[0] != STRING_NULL )
  {
    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.\r\n", 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.\r\n", ch );

          return;

        }

      }

  }

  else

    source = NULL;

  if ( !source && dest_item == ITEM_PIPE )

  {

    send_to_char ( "Fill it with what?\r\n", 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!\r\n", ch );

          return;

        case ITEM_FOUNTAIN:

          send_to_char ( "There is no fountain or pool here!\r\n", ch );

          return;

        case ITEM_BLOOD:

          send_to_char ( "There is no blood pool here!\r\n", ch );

          return;

        case ITEM_HERB_CON:

          send_to_char ( "There are no herbs here!\r\n", ch );

          return;

        case ITEM_HERB:

          send_to_char ( "You cannot find any smoking herbs.\r\n", 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!\r\n", 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.\r\n", 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.\r\n", ch );

        break;

      case ITEM_CORPSE_PC:

        if ( IS_NPC ( ch ) )

        {

          send_to_char ( "You can't do that.\r\n", 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.\r\n", 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.\r\n", 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.\r\n", 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.\r\n", ch );

        break;

    }

    return;

  }

  if ( source->value[1] < 1 )

  {

    send_to_char ( "There's none left!\r\n", 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...\r\n", ch );

      return;

    case ITEM_FOUNTAIN:

      if ( obj->value[1] != 0 && obj->value[2] != 0 )

      {

        send_to_char ( "There is already another liquid in it.\r\n", 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.\r\n", 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.\r\n", 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.\r\n", 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.\r\n", 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] != STRING_NULL )

    argument = one_argument ( argument, arg2 );

  if ( arg1[0] == STRING_NULL )

  {

    send_to_char ( "Empty what?\r\n", ch );

    return;

  }

  if ( ( obj = get_obj_carry ( ch, arg1 ) ) == NULL )

  {

    send_to_char ( "You aren't carrying that.\r\n", 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.\r\n", 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.\r\n", ch );

        return;

      }

      if ( arg2[0] == STRING_NULL )

      {

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

          send_to_char ( "&[tell]Someone tells you, 'No littering here!'\r\n", 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...\r\n", 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.\r\n", ch );

      }

      else

      {

        OBJ_DATA * dest = get_obj_here ( ch, arg2 );

        if ( !dest )

        {

          send_to_char ( "You can't find it.\r\n", ch );

          return;

        }

        if ( dest == obj )

        {

          send_to_char ( "You can't empty something into itself!\r\n", 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!\r\n", 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;

}