dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/win32/
/*  -*- LPC -*-  */
/*
 * $Id: vessel.c,v 1.65 2003/07/15 07:33:16 pinkfish Exp $
 */
/**
 * This is a container specifically for holding liquids.  The hope is
 * to reduce the memory requirements by taking all of the liquid stuff
 * out of /obj/container, since most of the containers are sacks and
 * chests.  Also, the reaction handler will replace the potion space.
 * <p>
 * Some additional notes:
 * <ol>
 *    <li>As is (hopefully) documented elsewhere, the base units of
 *        volume for most continuous media are drops and pinches, both of
 *        which are roughly 1/4 of a cc.  This means that water has
 *        200 drops per weight unit (1g/cc).  Non-continuous objects
 *        are assumed to be the same density as water.
 * </ol>
 * @@author Jeremy
 */


#define REACTION_HANDLER ("/obj/handlers/reaction")
#define TASTE_AMT 200

#include <tasks.h>
#include <move_failures.h>
#include <volumes.h>

inherit "/obj/baggage";

private int leak_rate;
private int hb_count;
private int sub_query_contents;
private int volume;
private int max_volume;
private int cont_volume;
private int is_liquid;
private nosave int* _fraction;
private nosave object* _liquids;

int drink_amount(int drinking, object player);
private int query_fighting(object player);


/**
 * This method sets the leak rate of the container.  The leak rate is how
 * fast stuff leaks out of the container.
 * @param i the new leak rate of the container
 * @see query_leak_rate() */
void set_leak_rate(int i) { leak_rate = i; }


/**
 * This method returns the leak rate of the container
 * @see set_leak_rate()
 * @return the current leak rate */
int query_leak_rate() { return leak_rate; }


/**
 * This method sets the maximum volume of the container.
 * @param v the new maximum volume
 * @see add_volume()
 * @see query_max_volume() */
void set_max_volume(int v) { max_volume = v; }


/**
 * This method returns the current maxium volume associated with this
 * container.
 * @return the current maximum volume */
int query_max_volume() { return max_volume; }


string *leak_verb = ({ " drips slowly",
                       " drips",
                       " dribbles",
                       " trickles slowly",
                       " trickles",
                       " trickles rapidly",
                       " pours sluggishly",
                       " pours",
                       " streams",
                       " gushes",
                       " fountains"
                     });


string *drink_pat = ({ "[from] <direct:object>",
                       "<fraction> {of|from} <direct:object>"
                   });                   
string *splash_pat = ({ "[from] <direct:object> {on|to|onto} <indirect:living>",
                       "<fraction> {of|from} <direct:object> {on|to|onto} "
                       "<indirect:living>"
                   });                   
string *apply_pat = ({ "[from] <direct:object> {on|to} <indirect:living>",
                       "<fraction> {of|from} <direct:object> {on|to} "
                       "<indirect:living>"
                   });                   
string *pour_pat = ({ "<direct:object> {from|into} <indirect:object>",
                      "<fraction> of <direct:object> {from|into} <indirect:object>"
                  });                  
string *fill_pat = ({ "<indirect:object> <fraction> full {from|into} <direct:object>",
                      "<indirect:object> <fraction> up {from|into} <direct:object>",
                      "<indirect:object> {from|into} <direct:object>"
                  });
                  
                  
void create() {
    do_setup++;
    ::create();
    do_setup--;
    if ( !do_setup ) {
      this_object()->setup();
    }
    add_help_file("vessel");
} /* create() */


/** @ignore yes */
void init() {
    ::init();
    add_command("drink", drink_pat);
    add_command("quaff", drink_pat);
    add_command("splash", splash_pat);
    add_command("rub", apply_pat);
    add_command("apply", apply_pat);
    add_command("pour", pour_pat);
    add_command("taste", "<direct:object>");
    add_command("sip", "<direct:object>");
    //add_command("smell",
    add_command("fill", fill_pat);
    add_command("empty", "<direct:object>");
} /* init() */


/**
 * This returns an adjective for how full the current object is with
 * liquid.  This is used in the parse command handling code.
 * @return the fullness adjective
 * @see query_max_volume() */
string *fullness_adjectives() {
   if (!max_volume) return ({ "totally", "empty" });
   switch (100 * volume / max_volume) {
   case 0..4: return ({ "totally", "empty" });
   case 5..13: return ({ "empty" });
   case 65..94: return ({ "full" });
   case 95..100: return ({ "totally", "full" });
   default: return ({ });
   }
} /* fullness_adjectives() */


/** @ingore yes */
mixed stats() {
  return ::stats() + ({
      ({ "leak_rate", leak_rate }),
      ({ "volume", volume }),
      ({ "cont_volume", cont_volume }),
      ({ "liquid", is_liquid }),
      ({ "max_volume", max_volume })
    });
} /* stats() */


int cmp_amount_r(object a, object b) {
    return ((int)b->query_amount() - (int)a->query_amount());
} /* cmp_amount() */


int cmp_weight_r(object a, object b) {
    if (a->query_weight() || b->query_weight())
      return ((int)b->query_weight() - (int)a->query_weight());
    return cmp_amount_r(a, b);
} /* cmp_weight() */


private void figure_out_liquids() {
   _liquids = filter(all_inventory(this_object()), (: $1->query_liquid() :));
   _liquids = sort_array(_liquids, "cmp_amount_r", this_object());
} /* figure_out_liquids() */


/**
 * This method returns the description of the liquid inside the vessel.
 * @return the current liquids description */
string query_liquid_desc() {
    object *contents, *solids = ({});
    int i;
    string desc, *shorts;
    mixed *others;

    contents = all_inventory(this_object());
    if( !sizeof( contents ) ) {
       return (string) 0;
    }

    figure_out_liquids();
    solids = contents - _liquids;
    if (sizeof(_liquids)) {
       _liquids = sort_array(_liquids, "cmp_amount_r", this_object());
       others = unique_array(_liquids->a_short(),
                          (: "/global/events"->convert_message($1) :));
       shorts = ({ });
       for (i = 0; i < sizeof(others); i++) {
          shorts += ({ others[i][0] });
       }
       desc = shorts[0];
       if (sizeof(shorts) > 1) {
          desc += ", mixed with ";
          if (sizeof(shorts) > 4) {
             desc += "other liquids";
          } else {
             desc += query_multiple_short(shorts[1..]);
          }
       }
    } else {
       desc = "";
    }
    if (!sizeof(solids)) {
        return desc;
    }
    solids = sort_array(solids, "cmp_weight_r", this_object());
    others = unique_array(solids,
                          (: $1->query_continuous()?
                           this_player()->convert_message($1->a_short()) :
                           $1 :) );

    for (i = 0; i < sizeof(others); i++) {
       if( arrayp( others ) ) {
          others[i] = others[i][0];
       } else {
          // Er...  OK, so wtf is it?
          tell_creator( "gruper", "others is %O.\n", others );
       }
    }

    if (sizeof(_liquids)) {
       desc += ", with ";
    }
    if (sizeof(others) > 10) {
        desc += "various undissolved substances";
    } else {
        desc += query_multiple_short(others);
    }
    if (sizeof(_liquids)) {
       desc += " floating in it";
    }
    return desc;
} /* query_liquid_desc() */


/**
 * This method returns the fullness description of the vessel.
 * @return the fullness description of the vessel */
string query_fullness_desc() {
   int full;
   
/* Ok, here comes a terrible kludge, but it's all I can think of 
 * that will fix a buglet without introducing more serious Bugs.
 * It's a hack around continous objects and their volumes.   - Tilly */
   
   full = (100 * volume) / max_volume;
   if( full < 1 && query_liquid_desc() ) {
      full = 1;
   }
   switch (full) {
      case 0:       return "";
      case 1..12:   return "It is almost empty.";
      case 13..37:  return "It is about one-quarter full.";
      case 38..62:  return "It is about half full.";
      case 63..87:  return "It is about three-quarters full.";
      case 88..95:  return "It is almost full.";
      case 96..100: return "It is completely full.";
      default:      return "Its capacity defies the laws of physics.  " 
                           "Please submit a bug report.";
    }
}


/* This is a bit of a kludge, but I need a way to inhibit query_contents()
 * in long() of /obj/baggage.  It may be that I could permanently
 * replace query_contents() with query_liquid_desc(), but I'm not sure... */
/** @ignore yes */
varargs string query_contents(string str, object *obs ) {
    string s, c;

/* This next bit used to check for volume, but to deal with the buglet 
 * mentioned in the previous function, I changed it to check for the 
 * presence of some kind of contents instead.  - Tilly */
    if ( query_liquid_desc() ) {
        c = "\n"+ query_fullness_desc() +"\n";
    } else {
        c = "";
    }

    s = query_liquid_desc();
    if (sub_query_contents) {
        if (s) {
            return str + capitalize(s) + "." + c;
        }
        return ::query_contents(str, obs) + c;
    }
    return ::query_contents(str, obs);
} /* query_contents */

/** @ignore yes */
string short(int dark) {
  object *inv;

  if(query_opaque())
    return ::short(dark);

  inv = all_inventory(this_object());
  if(!sizeof(inv))
    return ::short(dark);

  return ::short(dark) + " of " + 
    "/global/events"->convert_message(query_multiple_short(map(inv, 
    (: $1->query_short() :))));
}

/** @ignore yes */
string long(string str, int dark) {
    string ret;

    sub_query_contents = 1;
    ret = ::long(str, dark);
    sub_query_contents = 0;
    return ret;
} /* long() */

int query_cont_volume() { return cont_volume; }


/**
 * This method returns if this vessel is currenly a liquid.  This means
 * it has a liquid inside it.
 * @see calc_liquid()
 * @return 1 if it is a liquid, 0 if not */
int query_liquid() { return is_liquid; }


/* This method determines if we have any liquids inside us at all. */
void calc_liquid() {
    if (sizeof(filter(all_inventory(), (: $1->query_liquid() :)))) {
      is_liquid = 1;
    } else {
      is_liquid = 0;
    }
}


/**
 * This method returns the current amount of liquid in the container.
 * @return the current amount of liquid in the container */
int query_volume() { return volume; }


/**
 * This method returns the amount of volume left for liquids to be
 * added into.
 * @return the amount of volume left
 * @see add_volume()
 * @see transfer_liquid_to() */
int query_volume_left() {
  if (!query_max_weight()) {
    return max_volume - volume;
  }
  return max_volume - volume - (max_volume*query_loc_weight())/query_max_weight();
} /* query_volume_left() */


/**
 * This method returns the amount of volume left for liquids to be
 * added into.
 * @param vol the amount of volume added
 * @return 1 if the addition was successful, 0 if not
 * @see add_volume() */
int add_volume(int vol) {
   if ((vol <= 0) || !max_volume || (vol + volume <= max_volume)) {
      volume += vol;
      if (previous_object()->query_continuous()) {
          cont_volume += vol;
      }
      return 1;
    }
    // Should spillage be handled here, or in the caller?
    return 0;
} /* add_volume() */


/** @ignore yes */
int add_weight(int n) {
    int v;

    // Debugging.  Can be removed if I forget -- Jeremy
    if (this_player() && (this_player()->query_name() == "pinkfish")) {
      tell_creator("pinkfish", "vessel::add_weight(%O)\n", n);
    }
    if (previous_object()->query_continuous()) {
       return (::add_weight(n));
    }
    //if (!(v = previous_object()->query_amount()))
      v = n*200;
    if (max_volume && (v + volume > max_volume)) {
       return 0;
    }
    if (::add_weight(n)) {
        //printf("Increasing volume by %d (add_weight)\n", v);
        volume += v;
        return 1;
    }
    return 0;
} /* add_weight() */


/**
 * This method removes some volume of liquid from the container.
 * @param vol_lost the amount of volume removed
 * @see add_volume()
 * @see query_volume() */
int remove_volume(int vol_lost)
{
    // Removes equal proportions of all continuous matter.
    int amt_lost, i, orig_cv;
    object *contents;

    if (!cont_volume) {
        return 0;
    }
    orig_cv = cont_volume;
    contents = all_inventory(this_object());
    for (i = 0; i < sizeof(contents); i++) {
        if (contents[i]->query_continuous()) {
            amt_lost = -to_int((int)contents[i]->query_amount()
                               * (to_float(vol_lost) / orig_cv));
            if (!amt_lost) {
                // Always take at least one unit
                amt_lost++;
            }
            contents[i]->adjust_amount(amt_lost);
        }
    }
    return vol_lost;
} /* remove_volume() */


/**
 * This method transfers a given amount of a liquid to a new container.
 * @param dest the destination of the liquid
 * @param vol_xferred the amount of volume transfered */
int xfer_volume(int vol_xferred, object dest) {
    // Transfers equal portions of all continuous matter to dest.
    // If successful, returns 0; if it failed for some reason, it returns
    // the volume not transferred (note that full checks should be done
    // by the caller).
    int vol_to_go;
    int i;
    int amt_xferred;
    int tmp;
    int orig_cv;
    object *contents;
    object copy;
    string file_path;
    mapping map;

    vol_to_go = vol_xferred;
    if (!cont_volume) {
        return 0;
    }
    orig_cv = cont_volume;
    contents = filter(all_inventory(this_object()),
                      (: $1->query_continuous() :));
    for (i = 0; i < sizeof(contents) && vol_to_go > 0; i++) {
        // This should prevent roundoff errors.
        if (i == sizeof(contents) - 1) {
            amt_xferred = vol_to_go;
        } else {
            amt_xferred = to_int((int)contents[i]->query_amount()
                                 * (to_float(vol_xferred) / orig_cv));
        }
        if (!amt_xferred) {
            // Always take at least one unit
            amt_xferred++;
        }
        file_path = explode( file_name(contents[i]), "#" )[ 0 ];
        copy = clone_object(file_path);
        map = (mapping)contents[i]->query_dynamic_auto_load();
        copy->init_dynamic_arg( map );
        map = (mapping)contents[i]->query_static_auto_load();
        if (map) {
            copy->init_static_arg( map );
        }
        copy->set_amount(amt_xferred);
        tmp = copy->move(dest);
        if (tmp == MOVE_OK) {
            // There should probably be enough checks before here to
            // make sure the move succeeds, or an explanation why it
            // didn't.
            vol_to_go -= amt_xferred;
            contents[i]->adjust_amount(-amt_xferred);
        } else {
            copy->dest_me();
        }
    }
    return vol_to_go;
} /* xfer_volume() */


/** @ignore yes */
void heart_beat() {
    // Note that having a leak rate can be expensive, so it should only
    // be done if it's important to the application (such as using it
    // to impose a time restriction).
    int lost, off;

    if (leak_rate == 0 || !is_liquid) {
        set_heart_beat(0);
        return;
    }
    if (hb_count--) return ;
    hb_count = 10;
    lost = leak_rate;
    if (lost > cont_volume)
      lost = cont_volume;
    off = lost/100;
    if (off > 10)
        off = 10;
 // tell_room(environment(),
 //     capitalize(query_liquid_desc())+leak_verb[off]+" out of the "+
 //     short(1)+".\n");
 /* This is hacked because as far as I can tell there is no way to get a 'generic short'
  * for a continuous liquid, ie: some water, instead of two pints of water. */
    if ( interactive( environment() ) )
        tell_object( environment(), "$C$Some " + query_multiple_short( map( all_inventory(),
        (: $1->query_short() :) ) ) + leak_verb[off] + " out of the "+ short(1) + ".\n" );
    else 
      tell_room( environment(), "$C$Some " + query_multiple_short( map( all_inventory(),
        (: $1->query_short() :) ) ) + leak_verb[off] + " out of the "+ short(1) + ".\n" );
  
  (void)remove_volume(lost);
  if (!cont_volume) {
    set_heart_beat(0);
  }
} /*heart_beat() */


/** @ignore yes */
int do_pour(object *to, mixed *args_b, mixed *args_a, mixed *args) {
    int m, n, volume_needed, their_volume, their_max, ovf, xfer_result;

    if (query_fighting(this_player())) {
       add_failed_mess("You cannot attempt to do this while in combat.\n");
       return 0;
    }

    if (environment(this_object()) != this_player()) {
        add_failed_mess("You aren't carrying $D.\n");
        return 0;
    }
    if (sizeof(args) == 5) {
        //m = args[1];
        //n = args[2];
        sscanf(args[0] + " " + args[1], "%d %d", m, n);
        if ((m > n) || (m < 0) || (n <= 0)) {
            add_failed_mess("Interesting fraction you have there!\n");
            return 0;
        }
    } else {
        m = 1;
        n = 1;
    }

    if (query_locked()) {
        add_failed_mess("$C$$D $V$0=is,are$V$ locked!\n");
        return 0;
    }
    if (query_closed()) {
        if (do_open()) {
            write("You open the " + short(0) + ".\n");
        } else {
            add_failed_mess("You cannot open $D.\n");
            return 0;
        }
    }

    if (cont_volume <= 0) {
        add_failed_mess("$C$$D has nothing to pour!\n");
        return 0;
    }

    if (sizeof(to) > 1) {
        add_failed_mess("You can only pour into one object at a time.\n");
        return 0;
    }

    their_volume = (int)to[0]->query_volume();
    their_max = (int)to[0]->query_max_volume();
    if (their_max <= 0) {
        add_failed_mess("$C$" + to[0]->the_short(0) +
            " doesn't look like it can be filled!\n");
        return 0;
    }

    if (their_volume >= their_max) {
        add_failed_mess("The " + to[0]->short(0)
              + " $V$0=is,are$V$ full to the brim already.\n");
        their_volume = their_max;
    }
    if ((m == 1) && (n == 1)) {
        volume_needed = volume;
    } else {
        volume_needed = max_volume * m / n;
    }
    if (volume < volume_needed) {
        add_failed_mess("$C$$D $V$0=is,are$V$ less than " + m + "/" + n +
              " full.\n");
        return 0;
    }
    if (volume_needed > 120) {
        // +/- 1 ounce (could make this skill-dependent...)
        volume_needed += random(240) - 120;
    }
    if (volume_needed > (their_max - their_volume)) {
        volume_needed = their_max - their_volume;
        ovf = 1;
    }

    if (volume_needed > cont_volume) {
        add_failed_mess("You drain the " + short(0) + " into the "
              + to[0]->short(0) + " but it $V$0=is,are$V$ not enough.\n");
        volume_needed = cont_volume;
        this_player()->add_succeeded(to[0]);
    } else {
        this_player()->add_succeeded(to[0]);
    }

    xfer_result = xfer_volume( volume_needed, to[0] );

    //If the result is less then needed, then it worked.
    //If it is exactly the same, then 0 was transferred.
    if (xfer_result < volume_needed) {
        this_player()->add_succeeded_mess(this_object(), "$N $V $D into $I.\n", ({to[0]}));
    }
    else {
        add_failed_mess( "You were unable to $V $D into $I.\n", ({to[0]}));
        return 0;
    }
    if (ovf) {
        this_player()->add_succeeded_mess(this_object(), "$N $V $D into $I, " +
            "spilling some in the process.\n", ({to[0]}));
    }
    return 1;
} /* do_pour() */

/** @ignore yes */
int do_fill(object *to, mixed *args_b, mixed *args_a, mixed *args) {
    int m;
    int n;
    int i;
    int run_out;
    int volume_needed;
    int their_volume;
    int their_max;
    int amount_not_poured;
    int ok;

    if (query_fighting(this_player())) {
       add_failed_mess("You cannot attempt to do this while in combat.\n");
       return 0;
    }

    if (sizeof(args) == 4) {
        //m = args[1];
        //n = args[2];
        sscanf(args[1] + " " + args[2], "%d %d", m, n);
        if ((m > n) || (m < 0) || (n <= 0)) {
            add_failed_mess("Interesting fraction you have there!\n");
            return 0;
        }
    } else {
        m = 1;
        n = 1;
    }

    if (query_closed() &&
        query_locked()) {
        add_failed_mess("$C$$D $V$0=is,are$V$ locked!\n");
        return 0;
    }

    if (query_closed()) {
        if (do_open()) {
            write("You open the " + short(0) + ".\n");
        } else {
            add_failed_mess("You cannot open $D.\n");
            return 0;
        }
    }

    if (cont_volume <= 0) {
        add_failed_mess("$C$$D has nothing to pour!\n");
        return 0;
    }

    run_out = 0;
    for (i = 0; i < sizeof(to) && !run_out; i++) {
        if ((environment(this_object()) != this_player()) &&
            (environment(to[i]) != this_player())) {
            write("You're not carrying " + the_short() + " or " +
                  to[i]->the_short() + ".\n");
            continue;
        }

        if (to[i]->query_closed()) {
            add_failed_mess("$I $V$0=is,are$V$ closed.\n", to[i..i]);
            continue;
        }

        their_volume = (int)to[i]->query_volume();
        their_max = (int)to[i]->query_max_volume();
        if (their_max <= 0) {
            add_failed_mess("$I doesn't look like it can be filled!\n",
                            to[i..i]);
            continue;
        }

        if (their_volume >= their_max) {
            add_failed_mess("$I $V$0=is,are$V$ full to the brim already.\n", to[i..i]);
            continue;
        }

        volume_needed = their_max * m / n;
        if (their_volume >= volume_needed) {
            add_failed_mess("$I $V$0=is,are$V$ more than " + m + "/" + n +
                  " full already.\n", to[i..i]);
            continue;
        }
        if (volume_needed > 120) {
            // +/- 1 ounce (could make this skill-dependent...)
            volume_needed += random(240) - 120;
        }
        if (volume_needed > their_max) {
            volume_needed = their_max;
        }
        ok++;
        volume_needed -= their_volume;

        if (volume_needed > cont_volume) {
            add_failed_mess("You drain " + the_short() + " into "
                  + to[i]->the_short() + " but it $V$0=is,are$V$ not enough.\n");
            volume_needed = cont_volume;
            run_out = 1;
            this_player()->add_succeeded(to[i]);
        } else {
            this_player()->add_succeeded(to[i]);
        }
        amount_not_poured = xfer_volume(volume_needed, to[i]);
        if (amount_not_poured) {
           ok--;
        }
    }

    if (!ok) {
       add_failed_mess("You cannot seem to do anything useful with this "
                       "container, it seems unwilling to accept what you "
                       "offer.\n");
    }

    return ok;
} /* do_fill() */


/**
 * This method checks to see if they are fighting anyone and if anyone
 * (that can see them) is fighting them.
 * @param player the player to check */
private int query_fighting(object player) {
   object ob;

   if (!player || !environment(player)) {
      return 0;
   }

   if (player->query_fighting()) {
      return 1;
   }

   foreach (ob in all_inventory(environment(player))) {
      if (living(ob)) {
         if (ob->query_attacker_list() &&
             member_array(this_player(), ob->query_attacker_list()) != -1) {
            return 1;
         }
      }
   }

   if (environment(ob)->query_mirror_room()) {
      foreach (ob in all_inventory(environment(player)->query_mirror_room())) {
         if (living(ob)) {
            if (member_array(this_player(), ob->query_attacker_list()) != -1) {
               return 1;
            }
         }
      }
   }
} /* query_fighting() */


/**
 * This method checks to see if the person doing the drinking can hold onto
 * their bottle without loosing it while in combat.  Warning!  This code
 * may be used in other objects to deal with handling drinking while in
 * combat.
 * @return 1 if the bottle is stopped, 0 if it is not
 * @param player the player doing the drinking
 * @param me the object being drunk */
int is_fighting_bottle_smashed(object player,
                               object me) {
   object* fighting;
   object ob;
   object weapon;
   string skill;
   string my_skill;
   int bonus;
   int stopped;

   stopped = 0;

// See if we are in combat and give a chance to drop the item if we
// are.  Chance is higher if we have no free hands. */
   if (query_fighting(player)) {
   // Ok, we are in combat.  Check out free hands.  The more people
   // we are fighting the harder it is.
      fighting = filter(player->query_attacker_list(),
                        (: environment($1) == $2 :), environment(player));
      if (query_holder()) {
         bonus = 0;
         bonus += -10 * player->query_free_limbs();
      } else {
         bonus = 20;
      }
      if (sizeof(fighting)) {
         bonus += (sizeof(fighting) - 1) * 20;
      }
      if (player->query_free_limbs() < 2 &&
          !query_holder()) {
         bonus += 50 - player->query_free_limbs() * 25;
      }

   // Check against the other persons fighting skill.
      foreach (ob in fighting) {
      // If they are using a weapon use the weapon skill, otherwise unarmed.
         if (sizeof(ob->query_holding() - ({ 0 }))) {
            weapon = (ob->query_holding() - ({ 0 }))[0];
            skill = weapon->query_weapon_type();
            if (skill == "mixed" ||
                (skill != "sharp" && skill != "blunt" && skill != "pierce")) {
               skill = ({ "sharp", "blunt", "pierce" })[random(3)];
            }
            skill = "fighting.combat.melee." + skill;
         } else {
            skill = "fighting.combat.melee.unarmed";
         }
         my_skill = "fighting.combat.special.unarmed";
         switch (player->query_combat_response()) {
         case "parry" :
            my_skill = "fighting.combat.parry.melee";
            break;
         case "dodge" :
            my_skill = "fighting.combat.dodging.melee";
            break;
         case "neutral" :
            my_skill = "fighting.combat." + 
            (({ "parry", "dodging" })[random(2)]) + ".melee";
         }
         
         switch (TASKER->compare_skills(player,
                                       my_skill,
                                       ob,
                                       skill,
                                       -bonus,
                                       TM_FREE,
                                       TM_FREE)) {
         case OFFAWARD :
            if (player->query_combat_response() == "dodge") {
               tell_object(player,
                           "%^YELLOW%^You nimbly dodge an attack to avoid "
                           "getting " +
                           me->poss_short() +
                           " smashed out of your hand and feel better about "
                           "attempting it next time.\n%^RESET%^");
            } else {
               tell_object(player,
                           "%^YELLOW%^You nimbly parry an attack to avoid "
                           " getting " +
                           me->poss_short() +
                           " smashed out of your hand and feel better about "
                           "attempting it next time.\n%^RESET%^");
            }
         case OFFWIN :
            tell_room(environment(player),
                      player->the_short() + " avoids getting " +
                      me->poss_short() + " smashed by " +
                      query_multiple_short(({ ob })) + ".\n",
                               ({ player }));
            tell_object(player,
                        "You avoid getting " + me->poss_short() +
                        " smashed by " + query_multiple_short(({ ob })) +
                        ".\n");

         // Do less, but still damage it.
            do_damage("crush", 20 + random(50));
            break;
         case DEFAWARD :
            tell_object(ob,
                        "%^YELLOW%^You feel much more able to smash "
                        "bottles out of peoples hands than "
                        "before.\n%^RESET%^");
         case DEFWIN :
            if (!query_holder() &&
                me->move(environment(player)) == MOVE_OK) {
               tell_room(environment(player),
                   ob->the_short() + " smashes " + the_short() + " out of " +
                   player->the_short() + "'s hands onto the ground "
                   "causing some of the liquid to splash out.\n", 
                   ({ player, me }));
               tell_object(player,
                   ob->the_short() + " smashes " + the_short() + " out of " +
                   "your hands onto the ground "
                   "causing some of the liquid to splash out.\n");
               tell_object(me,
                   ob->the_short() + " smash " + the_short() + " out of " +
                   player->the_short() + "'s hands onto the ground "
                   "causing some of the liquid to splash out.\n");    
               
            } else {
               tell_room(environment(player),
                   ob->the_short() + " smashes " + the_short() + " away "
                   "from " + player->the_short() + "'s mouth causing "
                   "some of the liquid to splash out.\n", ({ player, me }));
               tell_object(player,
                   ob->the_short() + " smashes " + the_short() + " away "
                   "from " + "your mouth causing some of the liquid to "
                   "splash out.\n");
               tell_object(me,
                   ob->the_short() + " smash " + the_short() + " out of " +
                   player->the_short() + "'s hands onto the ground "
                   "causing some of the liquid to splash out.\n"); 
            }
            stopped = 1;
            break;
         }
         if (stopped) {
            break;
         }
      }
   }
   return stopped;
} /* is_fighting_bottle_smashed() */


/**
 * @ignore
 * This method returns a description of what and how much of the
 * contents of the vessel that was consumed. */
private string consumed_desc( int consumed_amount ) {
   string amount_desc;
   object* contents;
   string* contents_descs = ({ });
   string contents_desc;

   contents = all_inventory( this_object() );

   if( !consumed_amount || !sizeof( contents ) )
      return "nothing much";

// Bugger accuracy!
   switch( consumed_amount ) {
   case 1..30:
      amount_desc = "a teaspoon";
      break;
   case 31..80:
      amount_desc = "a tablespoon";
      break;
   case 81..140:
      amount_desc = "an ounce";
      break;
   case 141..800:
      amount_desc = "a few ounces";
      break;
   case 801..1200:
      amount_desc = "a cup";
      break;
   case 1201..2000:
      amount_desc = "a couple of cups";
      break;
   case 2001..3500:
      amount_desc = "a pint";
      break;
   case 3501..7000:
      amount_desc = "a couple of pints";
      break;
   case 7001..15000:
      amount_desc = "several pints";
      break;
   case 15001..25000:
      amount_desc = "about a gallon";
      break;
   default:
      amount_desc = "several gallons";
   }

// They only drink the liquids, no matter what they have been mixed with. 
   contents = filter( contents, (: $1->query_liquid() :) );

// Try to describe contents in nice way.  Er...  So that the desc will look 
// nice, not the code (obviously).
   foreach( string* frog in unique_array( contents->short(), (: $1 :) ) ) {
      if( sizeof( frog ) > 1 ) {
         contents_descs += ({ pluralize( frog[0] ) });
      } else {
         contents_descs += ({ frog[0] });
      }
   }

   contents_desc = query_multiple_short( contents_descs );

   return amount_desc +" of "+ contents_desc;

} /* consumed_desc() */


/**
 * This method drinks a certain amount of the liquid in the container.
 * It will do all the fudging for being in combat and drinking too
 * much, as well as dropping the bottle and so on.
 * @param drinking the amount to drink 
 */
int drink_amount(int drinking,
                 object player) {
   int cap_amount;
   int amt_to_drink;
   int amount_can_be_drunk;
   object* contents;
   object ob;
   int orig_cv;

   amt_to_drink = drinking;

   if (amt_to_drink > cont_volume) {
      amt_to_drink = cont_volume;
   }

   if (max_volume / 20 > VOLUME_WINE) {
      cap_amount = max_volume / 20;
   } else {
      cap_amount = VOLUME_WINE;
   }

/* Ok, now fudge the values around if they are in combat or trying
 * to drink really small amounts. */
   if (amt_to_drink < cap_amount) {
      if (query_fighting(player)) {
         amt_to_drink += random(2 * (cap_amount - amt_to_drink)) -
                         cap_amount - amt_to_drink;
         if (amt_to_drink < VOLUME_MINIMUM_DRINKABLE * 2) {
            amt_to_drink = VOLUME_MINIMUM_DRINKABLE * 2;
         }
      } else {
         amt_to_drink += random((cap_amount - amt_to_drink) / 6) -
                         (cap_amount - amt_to_drink) / 12;
      }
   }

   if (amt_to_drink < VOLUME_MINIMUM_DRINKABLE) {
      amt_to_drink = VOLUME_MINIMUM_DRINKABLE;
   }

   if (amt_to_drink < (max_volume / 100)) {
      amt_to_drink = max_volume / 100;
   }

/* Make sure that we don't try to drink more than there actually
 * is, after all of this rounding. */
   if( amt_to_drink > cont_volume ) {
      amt_to_drink = cont_volume;
   }

   amount_can_be_drunk = (8000 - (int)this_player()->query_volume(2)) *
     (int)this_player()->query_con() / 12;
   /* should do some fudging to add +/- 5 mls or something
    * possibly skill/stat dependent */
   if (amt_to_drink > amount_can_be_drunk) {
       write("You drink some of the liquid, "
             "but simply cannot fit it all in.\n");
       amt_to_drink = amount_can_be_drunk;
   }

   if (is_fighting_bottle_smashed(this_player(), this_object())) {
   // Ok, now do the spilling if it has been done.
   // Throw out at least as much as they wanted to drink and up to
   // 10 times more.
      amt_to_drink *= (1 + random(15));
      if (amt_to_drink > volume) {
         amt_to_drink = volume;
      }

      orig_cv = cont_volume;
      foreach (ob in all_inventory(this_object())) {
         if (ob->query_continuous()) {
            if (amt_to_drink == cont_volume) {
               ob->adjust_amount(-ob->query_amount());
            } else {
               ob->adjust_amount(-(ob->query_amount() * amt_to_drink / orig_cv));
            }
         }
      }
      add_succeeded_mess("");
      
   // Drop the condition of the object by 10% as well.  Opps, no condition
   // at the moment.
      do_damage("crush", 50 + random(300));
      return 1;
   }


   contents = all_inventory(this_object());

/* If the contents has an eat message of its own, we'll use that.
 * And if it doesn't, we'll use a more generic one.  It's a bit of 
 * a cheap hack since it only checks the first of possibly several
 * things in the contents, but I couldn't be arsed.   - Tilly  */
   if( sizeof( contents[0]->query_eat_mess() ) ) {
      add_succeeded_mess( contents[0]->query_eat_mess() );
   } else {
      add_succeeded_mess("$N $V "+ consumed_desc( amt_to_drink ) +
                         " from $D.\n");
   }

   if (amt_to_drink - drinking > (max_volume / 40) &&
       amt_to_drink - drinking > VOLUME_MINIMUM_DRINKABLE) {
      add_succeeded_mess(({ "Whoops!  You seemed to have gulped "
                            "too much down.\n",
                            "" }));
   }


/* if (modifier - m < -5) {
 *    add_succeeded_mess(({ "You don't think that was quite as much as you "
 *                          "wanted to drink.\n",
 *                          "" }));
 * } */

/* Note that contents has been set just a few lines above, we don't need
 * to do that again. */
   orig_cv = cont_volume;
   foreach (ob in contents) {
       if (ob->query_continuous()) {
           if (amt_to_drink == orig_cv) {
               ob->consume(this_player(), ob->query_amount());
           } else {
               // Consume proportionate amounts of all food.
               ob->consume(this_player(),
                           ob->query_amount() * amt_to_drink / orig_cv);
           }
       }
   }

   return 1;
} /* drink_amount() */


/** @ignore yes */
int do_drink(object *dest, mixed me, mixed him, mixed args, string pattern)
{
    int amt_to_drink;
    int m;
    int n;

    //printf("indirect_o=%O\nindir_s=%O\ndir_s=%O\nargs=%O\npattern=%O\n",
    //       dest, me, him, args, pattern);
    if (sizeof(dest)) {
        add_failed_mess("Drinking is a very simple operation "
              "- please don't complicate matters.\n");
        return 0;
    }
    if (environment(this_object()) != this_player()) {
        add_failed_mess("You aren't carrying $D.\n");
        return 0;
    }
    if (!ensure_open()) {
       return 0;
    }
    if (!is_liquid) {
        add_failed_mess("$C$$D $V$0=is,are$V$ bone dry!\n");
        return 0;
    }
    // add_command() mucks around with the pattern strings...
    if (pattern == drink_pat[1]) {
        m = to_int(args[0]);
        n = to_int(args[1]);
        //sscanf(args[0] + " " + args[1], "%d %d", m, n);
        /** Yes, its a kludge. T. **/
        if ( n > 100 ) {
          add_failed_mess( "You can't drink with that much precision!\n" );
          return 0;
        }
        if ((m > n) || (m < 0) || (n <= 0)) {
            add_failed_mess("Interesting fraction you have there!\n");
            return 0;
        }
    } else {
        m = 1;
        n = 1;
    }

    if (_fraction) {
        m = _fraction[0];
        n = _fraction[1];
    }

    if ((m == 1) && (n == 1)) {
        amt_to_drink = cont_volume;
    } else {
        amt_to_drink = (max_volume*m)/n;
        if (amt_to_drink > volume) {
            add_failed_mess("$C$$D $V$0=is,are$V$ less than " + m + "/" + n +
                  " full.\n");
            return 0;
        }

        if (amt_to_drink > cont_volume) {
            amt_to_drink = cont_volume;
        }
    }

    if (!drink_amount(amt_to_drink, this_player())) {
       return 0;
    }

    switch ((this_player()->query_volume(2) + 100) / 200) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
        break;
      case 5:
        write("You feel mildly full of liquid.\n");
        break;
      case 6:
        write("You feel very full of liquid.\n");
        break;
      case 7:
        write("You feel all the liquid sloshing around inside you.\n");
        break;
      case 8:
        write("You are awash with liquid.\n");
        break;
      case 9:
        write("You are full to the brim with liquid.\n");
        break;
      default:
        write("You feel you would burst if you drank any more.\n");
        break;
    }
    return 1;
} /* do_drink() */

/** @ignore yes */
int do_quaff(object *dest, mixed me, mixed him, mixed args, string pattern) {
   return do_drink(dest, me, him, args, pattern);
}

/** @ignore yes */
int do_empty(object *dest, string me, string him, string prep)
{
   if (environment(this_object()) != this_player()) {
       add_failed_mess("You are not carrying $D.\n");
       return 0;
   }
   if (sizeof(dest)) {
       write("Passing on to pour ... bad move.\n");
       //return do_pour(dest, me, him, prep);
   }
   /* this completely fails to work :( ^^^ */

   if (!ensure_open()) {
      add_failed_mess("$C$$D $V$0=is,are$V$ not open.\n");
      return 0;
   }

   if (cont_volume == 0 &&
       !sizeof(all_inventory())) {
       add_failed_mess("$C$$D $V$0=is,are$V$ already empty.\n");
       return 0;
   }

   (void)remove_volume(cont_volume);

   // Remove the inventory too.
   all_inventory()->move(environment(this_player()));

   /* should check spillage */
   return 1;
} /* do_empty */


/** @ignore yes */
int check_splashable(object ob, object splasher, object splashee){
  return ob->query_splashable(splasher, splashee);
}


/** @ignore yes */
int do_splash(object *dest, mixed me, mixed him, mixed args, string pattern) {
    int amt_to_splash, i, m, n, orig_cv;
    object *contents;

    if (!sizeof(dest)) {
        add_failed_mess("Splash it on who?\n");
        return 0;
    }
    if(sizeof(dest) >1){
        add_failed_mess("You may only splash one person at a time.\n");
        return 0;
    }
    if (environment(this_object()) != this_player()) {
        write("You aren't carrying the " + short(0) + ".\n");
        return 0;
    }
    if (!ensure_open()) return 0;
    if (!is_liquid) {
        add_failed_mess("The " + short(0) + " $V$0=is,are$V$ bone dry!\n");
        return 0;
    }

 // add_command() mucks around with the pattern strings...
    if (pattern == splash_pat[1]) {
        m = to_int(args[0]);
        n = to_int(args[1]);
        if ((m > n) || (m < 0) || (n <= 0)) {
            notify_fail("Interesting fraction you have there!\n");
            return 0;
        }
    } else {
        m = 1;
        n = 1;
    }
    contents = filter( all_inventory(this_object()), (:check_splashable:),
          this_player(), dest[0]);

    if( !sizeof(contents) ){
       add_failed_mess("You can't splash anything in $D.\n");
       return 0;
    }

    orig_cv = cont_volume;
    if ( m == n ){
        amt_to_splash = cont_volume;
    } else {
        amt_to_splash = (max_volume*m)/n;
        if (amt_to_splash > volume) {
            add_failed_mess("The " + short(0) + " $V$0=is,are$V$ less than "
               + m + "/" + n + " full.\n");
            return 0;
        }
        if (amt_to_splash > cont_volume) {
            amt_to_splash = cont_volume;
        }
    }
    if(this_player() == dest[0]){
       this_player()->add_succeeded_mess(this_object(),
                     "$N $V " + query_multiple_short(contents) +
                     " from $D onto "+this_player()->query_objective() +
                     "self.\n", ({}));
    }else{
       this_player()->add_succeeded_mess(this_object(),
                     "$N $V " + query_multiple_short(contents) +
                     " from $D onto $I.\n", dest);
    }
 // Call consume() on food objects, I guess ignore the others.
    for (i = 0; i < sizeof(contents); i++) {
        if (amt_to_splash == cont_volume) {
           contents[i]->consume(dest[0],
           contents[i]->query_amount(), "splash");
        } else {
           // Consume proportionate amounts of all food.
           contents[i]->consume(dest[0],
              (int)contents[i]->query_amount()
              * amt_to_splash / orig_cv, "splash");
        }
    }
    return 1;
} /* do_splash() */


/** @ignore yes */
int check_applicable(object ob, object applier, object appliee){
  return ob->query_applicable(applier, appliee);
}


/** @ignore yes */
int do_rub(object *dest, mixed me, mixed him, mixed args, string pattern) {
    int amt_to_apply, i, m, n, orig_cv;
    object *contents;

    if (!sizeof(dest)) {
        add_failed_mess("Rub it on who?\n");
        return 0;
    }
    if(sizeof(dest) >1){
        add_failed_mess("You may only rub stuff on one person at a time.\n");
        return 0;
    }
    if (environment(this_object()) != this_player()) {
        write("You aren't carrying the " + short(0) + ".\n");
        return 0;
    }
    if (!ensure_open()) return 0;
    if (!is_liquid) {
        add_failed_mess("The " + short(0) + " $V$0=is,are$V$ bone dry!\n");
        return 0;
    }

 // add_command() mucks around with the pattern strings...
    if (pattern == apply_pat[1]) {
        m = to_int(args[0]);
        n = to_int(args[1]);
        if ((m > n) || (m < 0) || (n <= 0)) {
            notify_fail("Interesting fraction you have there!\n");
            return 0;
        }
    } else {
        m = 1;
        n = 1;
    }
    contents = filter( all_inventory(this_object()), (:check_applicable:),
        this_player(), dest[0]);
    if( !sizeof(contents) ){
       add_failed_mess("You can't rub anything in $D on $I.\n",dest);
    }
    orig_cv = cont_volume;
    if ( m == n ){
        amt_to_apply = cont_volume;
    } else {
        amt_to_apply = (max_volume*m)/n;
        if (amt_to_apply > volume) {
            add_failed_mess("The " + short(0) + " $V$0=is,are$V$ less than " 
               + m + "/" + n + " full.\n");
            return 0;
        }
        if (amt_to_apply > cont_volume) {
            amt_to_apply = cont_volume;
        }
    }
    if (this_player() == dest[0]){
       this_player()->add_succeeded_mess(this_object(),
                     "$N $V " + query_multiple_short(contents) +
                     " from $D onto "+this_player()->query_objective()+"self.\n", ({}));
    } else {
       this_player()->add_succeeded_mess(this_object(),
                     "$N $V " + query_multiple_short(contents) +
                     " from $D onto $I.\n", dest);
    }
 // Call consume() on food objects, I guess ignore the others.
    for (i = 0; i < sizeof(contents); i++) {
        if (amt_to_apply == cont_volume) {
           contents[i]->consume(dest[0],
                          contents[i]->query_amount(),"apply");
        } else {
        // Consume proportionate amounts of all food.
           contents[i]->consume(dest[0],
              (int)contents[i]->query_amount()
              * amt_to_apply / orig_cv, "apply");
        }
    }
    return 1;
} /* do_rub() */


/** @ignore yes */
int do_apply(object *dest, mixed me, mixed him, mixed args, string pattern){
   return do_rub(dest, me, him, args, pattern);
} /* do_apply() */


/** @ignore yes */
int do_taste() {
    int amount_tasted;
    /* be kind to tasters! */

    if (environment(this_object()) != this_player()) {
        write("You aren't carrying the " + short(0) + ".\n");
        return 0;
    }
    if (!ensure_open()) {
       return 0;
    }
    if (!cont_volume || !is_liquid) {
        add_failed_mess("The " + short(0) + " $V$0=is,are$V$ bone dry!\n");
        return 0;
    }

 // Put code here to give description of contents' taste.
    amount_tasted = VOLUME_SHOT;
    if (cont_volume < amount_tasted) {
        amount_tasted = cont_volume;
    }

    if (amount_tasted < max_volume / 100) {
       amount_tasted = max_volume / 100;
    }

    return drink_amount(amount_tasted, this_player());
} /* do_taste() */


/** @ignore yes */
int do_sip() {
   int amount_tasted;
/* be kind to tasters! */

   if (environment(this_object()) != this_player()) {
      write("You aren't carrying the " + short(0) + ".\n");
      return 0;
   }
   if (!ensure_open()) {
      return 0;
   }
   if (!cont_volume || !is_liquid) {
      add_failed_mess("The " + short(0) + " $V$0=is,are$V$ bone dry!\n");
      return 0;
   }

 // Put code here to give description of contents' taste.
   amount_tasted = VOLUME_SHOT * 2;
   if (cont_volume < amount_tasted) {
      amount_tasted = cont_volume;
   }

   if (amount_tasted < max_volume / 70) {
      amount_tasted = max_volume / 70;
   }

   return drink_amount(amount_tasted, this_player());
} /* do_sip() */


/** @ignore yes */
int do_smell() {
/* be kind to smellers! */

   if (!ensure_open()) {
      return 0;
   }

// Put code here to give description of contents' smell.
   write("Smelling isn't implemented yet.  Sorry.\n");

// Put code here to handle effects of smelling contents.
   return 1;
} /* do_smell() */


/** @ignore yes */
protected int handle_restore_inventory(object ob) {
   int ret;

   ret = ::handle_restore_inventory(ob);
   if (ret == MOVE_OK) {
      return MOVE_OK;
   }

// If it is a liquid or continuous object, we squeeze into the bottle.
   if (ob->query_liquid()) {
      ob->set_amount( query_max_volume() - query_volume() );
      ret = ::handle_restore_inventory(ob);
      if (ret == MOVE_OK) {
         return MOVE_OK;
      }
   }
   return ret;
} /* handle_restore_inventory() */


/** @ignore yes */
mapping int_query_static_auto_load() {
  mapping tmp;

  tmp = ::int_query_static_auto_load();
  return ([ "::" : tmp, "leak rate" : leak_rate,
           "max volume" : max_volume,
          ]);
} /* int_query_static_auto_load() */


/** @ignore yes */
mapping query_static_auto_load() {
  if ( !query_name() || ( query_name() == "object" ) ) {
    return 0;
  }
  if ( explode( file_name( this_object() ), "#" )[ 0 ] == "/obj/vessel" ) {
    return int_query_static_auto_load();
  }
  return ([ ]);
} /* query_static_auto_load() */

/** @ignore yes */
void init_static_arg(mapping args) {
  if (args["::"])
    ::init_static_arg(args["::"]);
  if (!undefinedp(args["leak rate"]))
    leak_rate = args["leak rate"];
  if (!undefinedp(args["max volume"]))
    max_volume = args["max volume"];
} /* init_static_arg() */


/* Added so you cant get things in or out of a close container. */
/** @ignore yes */
int test_add(object ob, int flag) {
    int new_vol;

    if ( !::test_add( ob, flag ) ) {
        return 0;
    }
    if (ob->query_continuous()) {
        new_vol = ob->query_amount();
    } else if (ob->query_property("density")) {
     // A hook for later use (maybe :)
        new_vol = (int)ob->query_weight()*(int)ob->query_property("density");
    } else {
     // Density is nominally that of water
     //new_vol = (int)ob->query_weight()*200;
     // The above is essentially correct.  However, through some odd
     // sequence of events, the weight (and hence, the calculated volume)
     // get added before this function.  So new_vol should be 0 here.
        new_vol = 0;
    }
    if ((new_vol + volume) > max_volume) {
     //write("Failed: new_vol = " + new_vol + ", volume = " + volume +
     //      ", max_volume = " + max_volume + "\n");
        return 0;
    }
    return 1;
} /* test_add() */


/** @ignore yes */
void event_enter(object ob, string message, object from) {
    int ob_vol, ob_cont;

    if (environment(ob) == this_object()) {
     // Adjust volume
        if (ob->query_continuous() ) {
            ob_vol = ob->query_amount();
            ob_cont = 1;
        } else if (ob->query_property("density")) {
         // A hook for later use (maybe :)
            ob_vol = (int)ob->query_weight()*
              (int)ob->query_property("density");
        } else {
         // Density is nominally that of water
         //ob_vol = (int)ob->query_weight()*200;
         // The above is essentially correct.  However, through some odd
         // sequence of events, the weight (and hence the calculated volume)
         // get added before this function.  So ob_vol should be 0 here.
            ob_vol = 0;
        }
        if (ob->query_liquid()) {
           is_liquid = 1;
           _liquids = ({ });
        }
#ifdef DEBUG        
        debug_printf("Increasing volume by " + ob_vol + ".\n");
#endif
        volume += ob_vol;
        if (ob_cont) cont_volume += ob_vol;
        // Check for reactions
        REACTION_HANDLER->check_reaction(ob);
        if (leak_rate > 0) {
            set_heart_beat(1);
        }
    }
} /* event_enter() */


/** @ignore yes */
void event_exit(object ob, string mess, object to) {
    int ob_vol, ob_cont;

    if (environment(ob) == this_object()) {
     // Adjust volume
        if (ob->query_continuous()) {
            ob_vol = ob->query_amount();
            ob_cont = 1;
        } else if (ob->query_property("density")) {
         // A hook for later use (maybe :)
            ob_vol = (int)ob->query_weight()*
              (int)ob->query_property("density");
        } else {
         // Density is nominally that of water
         //ob_vol = (int)ob->query_weight()*200;
         // The above is essentially correct.  However, through some odd
         // sequence of events, the weight (and hence the calculated volume)
         // get added before this function.  So ob_vol should be 0 here.
            ob_vol = 0;
        }
#ifdef DEBUG
        debug_printf("Decreasing volume by " + ob_vol + ".\n");
#endif
        volume -= ob_vol;
        if (ob_cont) cont_volume -= ob_vol;
        if (volume <= 0) is_liquid = 0;
    }
} /* event_exit() */


/** @ignore yes */
void break_me() {
  object *liquid;

// Dest all the liquid...
   liquid = filter(all_inventory(), (: $1->query_liquid() :));
   if (sizeof(liquid)) {
      tell_room(environment(),
         query_multiple_short(liquid) + " splashes all over the place "
         "as " + the_short() + " breaks.\n");
       liquid->move("/room/rubbish");
   }
   ::break_me();
} /* break_me() */


/** @ignore yes */
mixed* parse_match_object(string* input, object player,
                          class obj_match_context context) {
   int result;

   result = ::is_matching_object(input, player, context);
   if (result) {
      _fraction = context->fraction;
      if (update_parse_match_context(context, 1, result)) {
         return ({ result, ({ this_object() }) });
      }
   }
   return 0;
} /* parse_match_object() */


/** @ignore yes */
string *parse_command_adjectiv_id_list() {
   string *ret;

   ret =  fullness_adjectives() + ::parse_command_adjectiv_id_list();
   if (is_liquid && !query_opaque()) {
      object liquid;

      if (!sizeof(_liquids)) {
         figure_out_liquids();
      }
      _liquids -= ({ 0 });
      foreach (liquid in _liquids) {
         ret += liquid->parse_command_adjectiv_id_list();
      }
      return ret;
   }
   return ret;
} /* parse_command_adjectiv_id_list() */


/** @ignore yes */
string* parse_command_id_list() {
   if (is_liquid && !query_opaque()) {
      string* ret;
      object liquid;

      if (!sizeof(_liquids)) {
         figure_out_liquids();
      }
      _liquids -= ({ 0 });
      ret = ::parse_command_id_list();
      foreach (liquid in _liquids) {
         ret += liquid->parse_command_id_list();
      }
      return ret;
   }
   return ::parse_command_id_list();
} /* parse_command_id_list() */


/** @ignore yes */
string* parse_command_plural_id_list() {
   if (is_liquid && !query_opaque()) {
      string* ret;
      object liquid;

      if (!sizeof(_liquids)) {
         figure_out_liquids();
      }
      _liquids -= ({ 0 });
      ret = ::parse_command_plural_id_list();
      foreach (liquid in _liquids) {
         ret += liquid->parse_command_plural_id_list();
      }
      return ret;
   }
   return ::parse_command_plural_id_list();
} /* parse_command_plural_id_list() */


int query_vessel() { return 1; }