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