/* * Fixed vessels loading with 5 max_weight if max_weight was unset. * Max_weight defaults to 1/200th of max_volume. * Sandoz, 06th July 2001. */ /** * 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 ("/handlers/reaction") #define TASTE_AMT 200 #include <tasks.h> #include <move_failures.h> #include <volumes.h> inherit BAGGAGE_OBJ; 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; 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. This also * sets the maximum weight of the container to one tenth of the * volume (if there is no current maximum weight). * @param v the new maximum volume * @see add_volume() * @see query_max_volume() * @see set_max_weight() */ void set_max_volume(int v) { if (!max_weight && v) { max_weight = v/200; } max_volume = v; } /* set_max_volume() */ /** * 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] <indirect:living>", "<fraction> {of|from} <direct:object> on[to] " "<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> into <indirect:object>", "<fraction> of <direct:object> 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 ) { TO->setup(); } } /* create() */ /** @ignore yes */ void init() { ::init(); add_command("drink", 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 */ string *parse_command_adjectiv_id_list() { return fullness_adjectives() + ::parse_command_adjectiv_id_list(); } /* parse_command_adjectiv_id_list() */ /** @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() */ /** * This method returns the description of the liquid inside the vessel. * @return the current liquids description */ string query_liquid_desc() { object *contents, *liquids = ({}), *solids = ({}); int i; string desc, *shorts; mixed *others; contents = all_inventory(TO); // I could use filter_array here, but I'd have to create 3 other // functions and search the whole array 3 times... for (i = 0; i < sizeof(contents); i++) { if (contents[i]->query_liquid()) { liquids += ({contents[i]}); } else { solids += ({contents[i]}); } } if (!sizeof(liquids)) { // TCRE("shaydz","returns 0"); return 0; } liquids = sort_array(liquids, "cmp_amount_r", TO); others = unique_array(liquids->a_short(), (: "/global/player/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..]); } } if (!sizeof(solids)) { // TCRE("shaydz",sprintf("desc1:%s",desc)); return desc; } solids = sort_array(solids, "cmp_weight_r", TO); others = unique_array(solids, (: $1->query_continuous()? TP->convert_message($1->a_short()) : $1 :) ); for (i = 0; i < sizeof(others); i++) others[i] = others[i][0]; //printf("others = %O\n", others); desc += ", with "; if (sizeof(others) > 10) { desc += "various undissolved substances"; } else { desc += query_multiple_short(others); } desc += " floating in it"; // TCRE("shaydz",sprintf("desc2:%s",desc)); 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; full = (100 * volume) / max_volume; 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; c = ( volume ? query_fullness_desc() + "\n" : "" ); if( sub_query_contents ) { s = query_liquid_desc(); return ( s ? str + CAP(s) + ".\n" + c : ::query_contents( str, obs ) + c ); } return ::query_contents( str, obs ); } /* query_contents */ /** @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() { is_liquid = ( sizeof( filter( INV(TO), (: $1->query_liquid() :) ) ) ? 1 : 0 ); } /* calc_liquid() */ /** * 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( !max_weight ) return max_volume - volume; return max_volume - volume - ( max_volume * loc_weight ) / 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( PO->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; if( PO->query_continuous() ) return ::add_weight(n); v = n * 200; if( max_volume && ( v + volume > max_volume ) ) return 0; if( ::add_weight(n) ) { volume += v; return 1; } return 0; } /* add_weight() */ /** * This method removes some volume of liquid from the container. * Removes equal proportions of all continuous matter. * @param vol_lost the amount of volume removed * @see add_volume() * @see query_volume() */ int remove_volume( int vol_lost ) { int amt_lost, orig_cv; object *contents, thing; if( !cont_volume ) return 0; orig_cv = cont_volume; contents = filter( INV(TO), (: $1->query_continuous() :) ); foreach( thing in contents ) { amt_lost = -to_int( (int)thing->query_amount() * ( to_float(vol_lost) / orig_cv ) ); if( !amt_lost) amt_lost++; thing->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, i, amt_xferred, tmp, orig_cv; object *contents, copy; string file_path; mapping map; vol_to_go = vol_xferred; if( !cont_volume ) return 0; orig_cv = cont_volume; contents = filter( INV(TO), (: $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 = base_name(contents[i]); 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); contents[i]->adjust_amount(-amt_xferred); if( !tmp = copy->move(dest) ) { vol_to_go -= 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( ENV(TO), CAP( query_liquid_desc() ) + 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; if( query_fighting(TP) ) { add_failed_mess("You cannot attempt to do this while in combat.\n"); return 0; } if( ENV(TO) != TP ) { 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( sizeof(to) > 1 ) { add_failed_mess("You can only pour the contents of $D into one object " "at a time.\n"); return 0; } if( to[0] == TO ) { add_failed_mess("You cannot pour the contents of $D into itself.\n"); return 0; } if( query_locked() ) { add_failed_mess("$D is 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("$D has nothing to pour!\n"); return 0; } their_volume = (int)to[0]->query_volume(); their_max = (int)to[0]->query_max_volume(); if( their_max <= 0 ) { TP->add_failed_mess(TO, "$I doesn't look like it can be filled!\n", to); return 0; } if( their_volume >= their_max ) { write("The " + to[0]->short(0) + " is 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("$D is 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 ) { write("You drain the " + short(0) + " into the " + to[0]->short(0) + " but it is not enough.\n"); volume_needed = cont_volume; TP->add_succeeded(to[0]); } else { TP->add_succeeded(to[0]); } xfer_volume(volume_needed, to[0]); if( ovf ) { TP->add_succeeded_mess( TO, "$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, n, i, run_out, volume_needed, their_volume, their_max, amount_not_poured, ok; if( query_fighting(TP) ) { add_failed_mess("You cannot attempt to do this while in combat.\n"); return 0; } // This is to fix an oddity in find_match, where a red bottle is // matched by "red bottle 1/2 full". Otherwise, failed fractional // fills get called again as complete fills. if( sizeof( regexp(({args[0]}), "/") ) ) 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("$D is 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("$D has nothing to pour!\n"); return 0; } run_out = 0; for( i = 0; i < sizeof(to) && !run_out; i++ ) { if( ENV(TO) != TP && ENV(to[i]) != TP ) { write("You're not carrying " + the_short() + " or " + to[i]->the_short() + ".\n"); continue; } if( to[i]->query_closed() ) { add_failed_mess("$I is 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 is 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 is 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 ) { write("You drain " + the_short() + " into " + to[i]->the_short() + " but it is not enough.\n"); volume_needed = cont_volume; run_out = 1; TP->add_succeeded(to[i]); } else { TP->add_succeeded(to[i]); } amount_not_poured = xfer_volume(volume_needed, to[i]); if( amount_not_poured ) { ok--; } } 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, *tmp; if( player->query_fighting() ) return 1; foreach( ob in INV(ENV(player)) ) { if( living(ob) ) { if( ( tmp = ob->query_attacker_list() ) && member_array( TP, tmp ) != -1 ) { return 1; } } } if( ENV(ob)->query_mirror_room() ) { foreach( ob in INV(ENV(player)->query_mirror_room() ) ) { if( living(ob) ) { if( ( tmp = ob->query_attacker_list() ) && member_array( TP, tmp ) != -1 ) { return 1; } } } } } /* query_fighting() */ /* @ignore yes */ void do_damage( string type, int amount ) { return ::do_damage( type, ( !query_property("fragile") ? amount : amount * query_property("fragile") / 40 ) ); } /* do_damage() */ /** * 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(), (: ENV($1) == $2 :), ENV(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, otherwsie // 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( ENV(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"); break; case DEFAWARD : tell_object( ob, "%^YELLOW%^You feel more able to smash bottles " "out of peoples' hands than before.\n%^RESET%^"); case DEFWIN : if( !query_holder() && me->move( ENV(player) ) == MOVE_OK ) { tell_room( ENV(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, ob }) ); /* added message to the attacker */ tell_object( ob, "You smash " + the_short() + " out of " + player->the_short() + "'s hands onto the ground causing "+ "some of the liquid to splash out.\n"); 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"); } else { tell_room( ENV(player), ob->the_short() + " smashes " + the_short() + " away from " + player->the_short() + "'s " "mouth causing some of the liquid to splash out.\n", ({ player, ob }) ); /* added message to the attacker */ tell_object( ob, "You smash " + the_short() + " away from " + player->the_short() + "'s mouth causing some of the " + "liquid to splash out.\n"); tell_object( player, ob->the_short() + " smashes " + the_short() + " away from your mouth causing some of the " "liquid to splash out.\n"); } stopped = 1; break; } if( stopped ) break; } } return stopped; } /* is_fighting_bottle_smashed() */ /** * 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; cap_amount = ( max_volume / 20 > VOLUME_WINE ? max_volume / 20 : 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; amount_can_be_drunk = ( 8000 - (int)TP->query_volume(2) ) * (int)TP->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( TP, TO ) ) { // 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 filter( INV(TO), (: $1->query_continuous() :) ) ) { ob->adjust_amount( -( amt_to_drink == cont_volume ? ob->query_amount() : ob->query_amount() * amt_to_drink / orig_cv ) ); } add_succeeded_mess(""); do_damage( "crush", roll_MdN( 8, 8 ) ); return 1; } add_succeeded_mess("$N $V " + query_liquid_desc() + " from $D.\n"); if( amt_to_drink - drinking > ( max_volume / 40 ) && amt_to_drink - drinking > VOLUME_MINIMUM_DRINKABLE ) { add_succeeded_mess( ({ "Whoops! You seem to have gulped down too " "much.\n", "" }) ); } // Call consume() on food objects, I guess ignore the others. contents = filter( INV(TO), (: $1->query_continuous() :) ); orig_cv = cont_volume; foreach( ob in contents ) { ob->consume( TP, ( amt_to_drink == orig_cv ? ob->query_amount() : 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, m, n; if( sizeof(dest) ) { add_failed_mess("Drinking is a very simple operation - " "please don't complicate matters.\n"); return 0; } if( ENV(TO) != TP ) { add_failed_mess("You aren't carrying $D.\n"); return 0; } if( !ensure_open() ) { return 0; } if( !is_liquid ) { add_failed_mess("$D is 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("$D is 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, TP)) { return 0; } switch ((TP->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 pissed.\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_empty(object *dest, string me, string him, string prep) { if( ENV(TO) != TP ) { 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("$D is not open.\n"); return 0; } if( cont_volume == 0 && !sizeof(all_inventory() ) ) { add_failed_mess("$D is already empty.\n"); return 0; } (void)remove_volume(cont_volume); // Remove the inventory too. INV(TO)->move(ENV(TP)); /* 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(TO) != TP) { write("You aren't carrying the " + short(0) + ".\n"); return 0; } if (!ensure_open()) return 0; if (!is_liquid) { write("The " + short(0) + " is 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( INV(TO), (:check_splashable:), TP, dest[0] ); if( !sizeof(contents) ){ add_failed_mess("You can't splash anything in $D.\n"); } 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) { write("The " + short(0) + " is less than " + m + "/" + n + " full.\n"); return 0; } if (amt_to_splash > cont_volume) { amt_to_splash = cont_volume; } } if(TP == dest[0]){ TP->add_succeeded_mess(TO, "$N $V " + query_multiple_short(contents) + " from $D onto "+TP->query_objective()+"self.\n", ({})); }else{ TP->add_succeeded_mess(TO, "$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(TO) != TP) { write("You aren't carrying the " + short(0) + ".\n"); return 0; } if (!ensure_open()) return 0; if (!is_liquid) { write("The " + short(0) + " is 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(TO), (:check_applicable:), TP, 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) { write("The " + short(0) + " is less than " + m + "/" + n + " full.\n"); return 0; } if (amt_to_apply > cont_volume) { amt_to_apply = cont_volume; } } if(TP == dest[0]){ TP->add_succeeded_mess(TO, "$N $V " + query_multiple_short(contents) + " from $D onto "+TP->query_objective()+"self.\n", ({})); }else{ TP->add_succeeded_mess(TO, "$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(TO) != TP) { write("You aren't carrying the " + short(0) + ".\n"); return 0; } if (!ensure_open()) { return 0; } if (!cont_volume || !is_liquid) { write("The " + short(0) + " is 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, TP); } /* do_taste() */ /** @ignore yes */ int do_sip() { int amount_tasted; /* be kind to tasters! */ if (environment(TO) != TP) { write("You aren't carrying the " + short(0) + ".\n"); return 0; } if (!ensure_open()) { return 0; } if (!cont_volume || !is_liquid) { write("The " + short(0) + " is 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, TP); } /* 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 */ 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( TO ), "#" )[ 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) == TO) { // Adjust volume if (ob->query_continuous()) { ob_vol = ob->query_amount(); ob_cont = 1; is_liquid = is_liquid || ob->query_liquid(); } 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; } //write("Increasing volume by " + ob_vol + "(event_enter)\n"); 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) == TO) { // 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; } 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, liv; // Dest all the liquid... liquid = filter( INV(TO), (: $1->query_liquid() :) ); if( sizeof(liquid) ) { if( living( liv = ENV(TO) ) ) { tell_object( liv, query_multiple_short(liquid) + " splashes " "all over the place as your " + short(0)+" breaks!\n"); tell_room( ENV(liv), query_multiple_short(liquid) + " splashes " "all over the place as " + (string)liv->the_short()+"'s " + short(0) + " breaks!\n", liv ); } else { tell_room( ENV(TO), 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, ({ TO }) }); } return 0; } /* parse_match_object() */