/** * <BR> * This is the main inheritable for food objects. It allows you to * create food and drink. Virtual files may also be coded if given * the *.food extention. There are many examples of these virtual * food files in the /obj/food directory. Food does the following * things. * <BR><BR> * - It can be eaten or drunk. * <BR> * - Food can be changed from solid to liquid with set_liquid(), * and changed from liquid to solid with reset_liquid(). * <BR> * - The weight of each bite can be set with set_weight_per_bite(). * <BR> * - Effects can be added to the player when they are splashed with the food, * get food rubbed on them, or get food applied to them with * add_external_effect. * <BR> * - Splashing, applying, and rubbing can be pk-checked with * set_external_pk_check() * <BR> * - Splashing can be activated with set_splashable() * <BR> * - Splashing can be deactivated with unset_splashable() * <BR> * - Applying can be activated with set_applicable() * <BR> * - Applying can be deactivated with unset_applicable() * <BR> * - Effects can be added to the player when they consume the food * with add_eat_effect(). * <BR> * - Functions can be added to be carried out when the food is * consumed with add_eat_func(). This function can be stored * on another object if the set_eat_object() is used. * <BR> * - An eat message can be set with set_eat_mess(). * <BR> * - Food can be cut up into pieces with sharp weapons. * <BR> * - The description of the new 'pieces' can be set using * set_piece_description(), set_piece_short(), set_piece_plural(), * and set_piece_substance(). * <BR> * - Non-liquid food decays and will eventually crumble to dust. * <BR> * - The decay speed can be set with set_decay_speed(). * <BR> * - Food can be 'pickled' to prevent decay using do_cure(). */ #include <diseases.h> #include <bits_controller.h> #include <drinks.h> #include <weapon.h> #include <move_failures.h> inherit "/obj/cont_medium"; #define DEFAULT_DECAY 7200 // Two hours for something to decay. #define STATE_CHANGE ("/handlers/state_change") private int _liquid; private int _bites_gone; private int _weight_per_bite; private int _in_pieces; private int _decay_level; private int _decay_speed; private int _splashable; private int _applicable; private int _external_pk_check; private float _divisor = to_float(DEFAULT_DECAY) / to_float(6 * TIME_OUT); private string _eat_object; private string _eat_func; private string _eat_mess; private string _piece_desc; private string _piece_short; private string _piece_plural; private string _piece_substance; private string _food_type; private string _eat_piece_mess; private string _eat_amt_mess; private string _eat_last_mess; private mapping _eat_effects; private mapping _external_effects; varargs int do_cut( int num ); varargs int do_eat( int no_mess ); int do_mince(); void set_decay_speed(int decay); int check_for_container(); private void move_check_for_container(); void create() { do_setup++; ::create(); do_setup--; reset_continuous(); _eat_effects = ([ ]); _external_effects = ([ ]); set_decay_speed(DEFAULT_DECAY); if( !do_setup ) TO->setup(); } /* create() */ /** * This function returns true if the food is a liquid. * @return 1 if the food is a liquid, and 0 if it is not */ int query_liquid() { return _liquid; } /** * This tells us if the food is edible (ie. can be * consumed using 'eat' instead of 'drink'. If it is * edible then it is not a liquid. * @return 1 if the food is a liquid, and 0 if it is not * @see set_liquid() * @see query_liquid() */ int query_edible() { return !_liquid; } /** * This changes the food object into a liquid. Liquids are * automatically a 'continuous' medium, which means it will * combine with other liquids of the same type. * <p> * This will automatically set the decay speed to 0. Liquids * do not decay. * @see reset_liquid() * @see set_decay_speed() * @see /obj/cont_medium->set_continuous() */ void set_liquid() { set_continuous(); _liquid = 1; if( environment() ) environment()->calc_liquid(); set_decay_speed(0); set_determinate("some "); } /* set_liquid() */ /** * This changes the food object into a solid. Solids are not * 'continuous' and the decay speed will be set back to the * default. * @see set_liquid() * @see set_decay_speed() */ void reset_liquid() { reset_continuous(); _liquid = 0; if( environment() ) environment()->calc_liquid(); set_decay_speed(DEFAULT_DECAY); _divisor = to_float(DEFAULT_DECAY) / to_float(6 * TIME_OUT); } /* reset_liquid() */ /** * This method returns 1 if the item is a food object. * @return 1 when it is a food object */ int query_food_object() { return 1; } /** @ignore yes */ void init() { if( query_continuous() ){ add_command("rub", "<direct:object> <preposition> <indirect:living>"); add_command("apply", "<direct:object> <preposition> <indirect:living>"); } if( _liquid ) { add_command("drink", "<direct:object>"); add_command("splash", "<direct:object> <preposition> <indirect:living>"); } else { add_command("eat", "<direct:object>", (: do_eat(0) :) ); add_command("cut", "<direct:object> into <number'number, eg: 3'> pieces", (: do_cut($4[1]) :) ); add_command("cut", "<direct:object>", (: do_cut() :) ); add_command("slice", "<direct:object>", (: do_cut() :) ); add_command("mince", "<direct:object>", (: do_mince() :) ); } } /* init() */ /** * This query tells us whether the food decays. All objects * which decay should have this function on them. * @return 1 if the object decays * @see set_decay_speed() */ int query_decays() { return _decay_speed != 0; } /** * This returns the number of bites that have been taken out * of the food object. * @return the number of bites which have been taken from * the object * @see set_bites_gone() * @see set_weight_per_bite() */ int query_bites_gone() { return _bites_gone; } /** * This sets the weight of each bite of the food object. * This is used along with the total weight of the object * to calculate how many bites of the food can be taken. * @see /std/basic/misc->set_weight() * @see set_weight_per_bite() * @see weight * @return the weight of each bite */ int query_weight_per_bite() { return _weight_per_bite; } /** * This sets which type the food is, for the disease handler. * @param type the food type to set * @see query_food_type() */ void set_food_type( string type ) { _food_type = type; } /** * This method returns the food type. * @see set_food_type() * @return the food type */ string query_food_type() { return _food_type; } /** * This returns the eat object on which the eat function * of the food is stored. * @see set_eat_object() * @see set_eat_func() * @see set_eat_mess() * @return the name of the eat object */ string query_eat_object() { return _eat_object; } /** * This returns the name of the eat function to carry out * when the object is eaten. * @see set_eat_object() * @see set_eat_func() * @see set_eat_mess() * @return the name of the eat function */ string query_eat_func() { return _eat_func; } /** * This returns the eat message of the object. * @see set_eat_object() * @see set_eat_func() * @see set_eat_mess() * @return the eat message */ string query_eat_mess() { return _eat_mess; } /** * This returns the long description of the food object when * it is cut up into pieces. * @see set_piece_description() * @see query_in_pieces() * @return the piece description */ string query_piece_description() { return _piece_desc; } /** * If the food has been cut up (using the 'cut' command) * then this will return true. * @see do_cut() * @return 1 if it is in pieces, 0 if otherwise */ int query_in_pieces() { return _in_pieces; } /** * This returns the decay speed. If this is 0 then the object * is not decaying. This is the length of time it takes * for the whole object to decay. * @see set_decay_speed() * @return the decay speed */ int query_decay_speed() { return _decay_speed; } /** * This returns the level of the decay. This will be a number * between 1 and 6. * @see set_decay_speed() * @return the level of decay, which is between 1 and 6 */ int query_decay_level() { return _decay_level; } /** * This method returns which niche the food object has decayed to. * Should never be more than 6. */ int query_decay() { return ( _decay_level / _divisor ); } /** * This sets the number of bites gone. * This helps determine the total weight of the object. * @see set_weight_per_bite() * @param number the number of bites gone */ void set_bites_gone(int number) { _bites_gone = number; } /** * This sets weight of each bite of the food object. This is * used along with the total weight of the object to calculate * how many bites of the food can be taken. * @see /std/basic/misc->set_weight() * @see weight * @param number the weight each bite should be */ void set_weight_per_bite(int number) { _weight_per_bite = number; } /** * This sets the object on which the eat function is stored. * <p> * If the eat object is set, then the eat function needs to * be set as well. If the function is not set then setting * the object has no effect. * <p> * The function will be called with two parameters, the first * being the object being eaten and the second being the * person eating the object. * * @see set_eat_object() * @see set_eat_mess() * @param word The object containing the eat function. * @example * void create_food() { * object food; * food = clone_object("/obj/food/apple.food"); * food->set_eat_object(TO); * food->set_eat_func("eat_apple"); * } /\* create_food() *\/ * * void eat_apple(object food, object eater) { * /\* Do something wonderful! *\/ * } /\* eat_rabbit() *\/ */ void set_eat_object(string word) { _eat_object = word; } /** * This sets the eat function of the food. This is called when * the food is eaten. * <p> * If the eat object is set, then the eat function needs to be * set as well. If the function is not set then setting the * object has no effect. * <p> * The function will be called with two parameters, the first being the * object being eaten and the second being the person eating the object. * * @see set_eat_object() * @see set_eat_mess() * @param word The function to set as the eat function. * @example * See the example for set_eat_object() */ void set_eat_func(string word) { _eat_func = word; } /** * This sets the eat function of the food. The function is * carried out when the food is consumed. The eat message, * set with set_eat_mess(), is displayed when this function * is carried out. * <p> * The eat mess should be treated exactly the same as a * add_succeeded_mess(), as it is processed later as * add_succeeded_mess(eat_mess, ({ })); * * @see set_eat_object() * @see set_eat_func() * @see /global/new_parse->add_succeeded_mess() * @param word The string or array to be used as * the add_succeeded_mess() when eating the food. */ void set_eat_mess(string word) { _eat_mess = word; } /** * This sets up the eat messages for pieces and stuff. The amount message * will replace the $amt$ string in the message with the amount_string(). * @param piece the eat piece message * @param amt the amount messages * @param last the message to print out when the last bit gets eaten */ void setup_eat_piece_messages(string piece, string amt, string last) { _eat_piece_mess = piece; _eat_amt_mess = amt; _eat_last_mess = last; } /* setup_eat_piece_message() */ /** * This sets the long description of the food when it is has * 'cut' into pieces. * <p> * If this is set then the short of the object is changed * to 'piece of cake'. If the piece description is not * set the short description of the object is not changed. * * @param word The long description to use when cut into pieces. * @see query_in_pieces() * @see set_piece_short() * @see set_piece_plural() * @see set_piece_substance() * @example * set_piece_description("The brown, soft, moist cake looks " * "totally wonderful.\n"); */ void set_piece_description(string word) { _piece_desc = word; } /** * This is the description used in the short for the pieces. * When the object is cut up this is used as part of the * short description. For example, it can change * 'piece of cake' to 'slice of cake', 'piece of chocolate' * to 'square of chocolate', and so on. If this the piece * short has not been set it is set to the default which is * simply "piece". * * @param word The short to use for the 'piece' when cut into * pieces. * @see query_in_pieces() * @see set_piece_description() * @see set_piece_plural() * @see set_piece_substance() * @example * set_piece_short("slice"); */ void set_piece_short(string short) { _piece_short = short; } /** * This plural description used for the pieces. It is only * really of use if the piece short pluralises in an unusual * manner. If it is not set the piece plural is set to what * the pice short has been set to with an "s" stuck on the * end, for example, slice to slices. * * @param word The plural used for the 'pieces' when cut into * pieces. * @see query_in_pieces() * @see set_piece_description() * @see set_piece_short() * @see set_piece_substance() * @example * set_piece_plural("slices"); */ void set_piece_plural(string plural) { _piece_plural = plural; } /** * This sets a new description for what there are pieces of. * For example, if you want the short of the food before it * is cut up to be 'bar of chocolate', without the piece * substance being set, when it is cut up it will become * 'a piece of bar of chocolate'. If you set this in enables * you to change that unwieldly description to 'a piece of * chocolate'. * * @param word The description of what the 'piece' is of. * @see query_in_pieces() * @see set_piece_description() * @see set_piece_short() * @see set_piece_plural() * @example * set_piece_substance("goat cheese"); */ void set_piece_substance(string substance) { _piece_substance = substance; } /** * This sets the in_pieces flag. This allows you to make the * food seem as though it has already been cut into pieces. * @param number the number of pieces * @see query_in_pieces() * @see set_piece_description() * @see set_piece_short() * @see set_piece_plural() * @see set_piece_substance() */ void set_in_pieces(int number) { _in_pieces = number; } /* set_in_pieces() */ /** * This sets the decay level. This should be anumber * between 1 and 6. It lets you make a food object seem * as though it is already decaying. * @see query_decay_level() * @see set_decay_speed() * @param level The new decay level. */ void set_decay_level(int level) { _decay_level = level; } /** * This is called by the pickling stick. It stops the food * object decaying. * @see set_decay_speed() * @return always returns 1 */ int do_cure() { set_decay_speed(0); } /** * This sets the speed at which a food object decays. The * speed is set in seconds and is the total amount of time * before the food decays. * <p> * If the decay speed is set to 0, then the object will never decay. * * @param speed The speed at which the object will decays. * @see set_decay_level() * @see query_decay_speed() * @example * /\* This will make the object decay totaly in 30 minutes *\/ * set_decay_speed(1800); * * /\* This will stop the object from ever decaying *\/ * set_decay_speed(0); */ void set_decay_speed(int decay) { float tmp; if( decay != _decay_speed ) { BITS_CONTROLLER->remove_bit(TO); _decay_speed = decay; if( decay ) { tmp = _divisor; _divisor = to_float(decay) / to_float( 6 * TIME_OUT ); _decay_level = to_int(_decay_level * _divisor / tmp ); BITS_CONTROLLER->add_bit(TO); } } } /* set_decay_speed() */ /** * @ignore yes * This is called by the bits controller. */ void do_decay() { _decay_level++; if( _decay_level > 6 * _divisor ) { tell_room( ENV(TO), CAP( the_short() ) + " $V$0=crumbles,crumble$V$ " "to dust.\n"); move("/room/rubbish"); } } /* do_decay() */ /** @ignore yes */ int query_weight() { return ::query_weight() - _bites_gone * _weight_per_bite; } /* query_weight() */ /** @ignore yes */ string long( string words, int dark ) { int twelfths; string ret; ret = ::long( words, dark ); if( _liquid ) return ret; if( _weight_per_bite && _bites_gone ) { twelfths = ( _bites_gone * _weight_per_bite * 12 ) / ::query_weight(); switch (twelfths) { case 0..1 : ret += "It has just been nibbled on."; break; case 2 : ret += "It is about one sixth gone."; break; case 3 : ret += "It is about one quarter gone."; break; case 4 : ret += "It is about one third gone."; break; case 5 : ret += "It is just under half gone."; break; case 6 : ret += "It is about half gone."; break; case 7 : ret += "It is just over half gone."; break; case 8 : ret += "It is about two thirds gone."; break; case 9 : ret += "It is about three quarters gone."; break; case 10 : ret += "It is about five sixths gone."; break; case 11 : ret += "It is almost all gone."; break; default : ret += "Schroedinger's cat has been at it."; break; } ret += "\n"; } if( _decay_speed ) { switch( to_int( _decay_level / _divisor ) ) { case 0..1 : ret += "It looks nice and fresh."; break; case 2 : ret += "It looks a bit rotten."; break; case 3 : ret += "It is half rotten."; break; case 4 : ret += "It is mostly rotten."; break; case 5 : ret += "It is almost completely rotten."; break; case 6 : ret += "It is completely rotten."; break; } ret += "\n"; } return ret; } /* long() */ /** * This sets up the eat object and function. This is used when * the food is eaten. The eat function is called on the eat * object. If successful the eat message is added to the player. * @see set_eat_object() * @see set_eat_func() * @param ob the object to use * @param func the function to use */ void setup_eat(string ob, string func) { _eat_object = ob; _eat_func = func; } /* setup_eat() */ /** * The eat effects of the food. The mapping contains a set of * strings (the effect) and a number associated with the * effect. * @see add_eat_effect() * @return a mapping containing the eat effects */ mapping query_eat_effects() { if( undefinedp(_eat_effects) ) _eat_effects = ([ ]); return _eat_effects; } /* query_eat_effects() */ /** * This sets all the eat effects. This function should NOT * be used. Please use add_eat_effect. * @param map the eat effects mapping */ void set_eat_effects(mapping map) { if( mappingp(map) ) _eat_effects = map; } /* set_eat_effects() */ /** * This adds an eat effect to the food. This will be added to * the player or NPC when the food is eaten. * <p> * The effect is added with a number as the argument. The number * is based on the amount which is eaten (in weight units), and * usually represents the strength of the effect. * If the object is continuous, the weight is calculated from * the amount and weight_unit array... * <p> * <br> * <code> * eff_num = (amount * number * weight_unit[0]) / weight_unit[1] * </code> * <p> * ...where the number is passed into the add_eat_effect() function. * If the effect already exists, then the number is added onto * the existing number. * @see /obj/cont_medium->set_weight_unit() * @see remove_eat_effect() * @param word the file name of the effect to add. * @param number the number to set to the effect to. * @return the current value of the effect in the mapping */ int add_eat_effect( mixed word, int number ) { if( undefinedp(_eat_effects) ) _eat_effects= ([]); if( mappingp(word) ) { int num; string name; foreach( name, number in word ) { num += add_eat_effect( name, number ); } return num; } if( _eat_effects[word] ) { _eat_effects[word] += number; } else { _eat_effects[word] = number; } return _eat_effects[word]; } /* add_eat_effect() */ /** * This removes the eat effect of the given name. * @param word the name of the effect to delete * @see add_eat_effect() */ void remove_eat_effect(string word) { if( mapp( _eat_effects ) ) map_delete( _eat_effects, word ); } /* remove_eat_effect() */ /** * This makes the external effects pk checked. * @see add_external_effect(), set_applicable(), set_splashable() * @see unset_external_pk_check() */ void set_external_pk_check() { _external_pk_check = 1; } /** * This makes the external effects not pk checked. * @see add_external_effect(), set_applicable(), set_splashable() * @see set_external_pk_check() */ void unset_external_pk_check() { _external_pk_check = 0; } /** * The external (splash,apply,rub) effects of the food. The * mapping contains a set of strings (the effect) and a number * associated with the effect. * * @see set_splashable(), set_applicable() * @return A mapping containing the external effects. */ mapping query_external_effects() { if( undefinedp(_external_effects) ) _external_effects = ([ ]); return _external_effects + ([ ]); } /* query_external_effects() */ /** * This sets all the external effects. This function should NOT * be used. Please use add_external_effect. * @param map the external effects mapping */ void set_external_effects(mapping map) { _external_effects = map; } /** * This adds an external effect to the food. This will be added to * the player or NPC when the liquid is splashed, applied, or rubbed on them. * <p> * The effect is added with a number as the argument. The number * is based on the amount which is splashed (in weight units), and * usually represents the strength of the effect. * If the object is continuous, the weight is calculated from * the amount and weight_unit array... * <p> * <br> * <code> * eff_num = (amount * number * weight_unit[0]) / weight_unit[1] * </code> * <p> * ...where the number is passed into the add_external_effect() function. * If the effect already exists, then the number is added onto * the existing number. * * @see /obj/cont_medium->set_weight_unit() * @see set_splashable() * @see unset_splashable() * @see set_applicable() * @see unset_applicable * @see remove_external_effect() * @see set_external_pk_check() * @param word The file name of the effect to add. * @param number The number to set to the effect to. * @return The current value of the effect in the mapping */ int add_external_effect(string word, int number) { if( undefinedp(_external_effects) ) _external_effects = ([]); if( _external_effects[word] ) _external_effects[word] += number; else _external_effects[word] = number; return _external_effects[word]; } /* add_external_effect() */ /** * This removes the external effect of the given name. * @param word the name of the effect to delete * @see add_external_effect() */ void remove_external_effect(string word) { if( mapp( _external_effects ) ) map_delete( _external_effects, word ); } /* remove_external_effect() */ /** * This sets it so the food can be splashed. * Note that the food can only be splashed when liquid in any case. * Note this is NOT the default case. * @see unset_splashable(), set_applicable(), unset_applicable(), * add_external_effect() */ void set_splashable() { _splashable = 1; } /** * This sets it so the food cannot be splashed. * Note that the food can only be splashed when liquid in any case. * @see set_splashable() */ void unset_splashable() { _splashable = 0; } /** * This queries splashability of the food. * Note that the food can only be splashed when liquid and when * set_splashable() has been called * @param splasher - Who splashes. * @param splashee - Who is splashed. * @return 1 if it can splash, 0 if it can't * @see set_splashable(), unset_splashable(), set_liquid(), * set_external_pk_check() */ int query_splashable(object splasher, object splashee) { if( splasher == splashee || !_external_pk_check ) return ( _splashable && _liquid ); return ( _splashable && _liquid && !pk_check( splasher, splashee ) ); } /* query_splashable() */ /** * This sets it so the food can be applied. * Note this is the default case. * @see unset_applicable() */ void set_applicable() { _applicable = 1; } /** * This sets it so the food cannot be applied. * @see set_applicable() */ void unset_applicable() { _applicable = 0; } /** * This queries applicability of the food. (rubbing on someone) * Note that the food can only be applied when * set_applicable() has been called * @param applier - Who applies it * @param appliee - Who it is applied to. * @return 1 if it can be applied, 0 if it can't * @see set_applicable(), unset_applicable(), set_external_pk_check() */ int query_applicable(object applier, object appliee) { if( applier == appliee || !_external_pk_check ) return _applicable; return ( _applicable && !pk_check(applier, appliee) ); } /* query_applicable() */ /** @ignore yes */ void being_joined_by(object thing) { int that, this; string *words, word; mapping new_effects; that = (int)thing->query_amount(); this = query_amount(); if( !( this + that ) ) return; if( thing->query_splashable( 0, 0 ) && !_splashable ) set_splashable(); if( thing->query_applicable( 0, 0 ) && !_applicable ) set_applicable(); if( thing->query_external_pk_check() && !_external_pk_check ) set_external_pk_check(); new_effects = (mapping)thing->query_eat_effects(); // If new_effects isn't a mapping, then there's something // wrong, so it should give an error so that someone knows // and can get it fixed. words = keys(_eat_effects); foreach( word in words ) { if( new_effects[word] ) { new_effects[word] *= that; new_effects[word] += this * _eat_effects[word]; } else { new_effects[word] = this * _eat_effects[word]; } } words = keys(new_effects); foreach( word in words ) new_effects[word] /= this + that; _eat_effects = new_effects; new_effects = (mapping)thing->query_external_effects(); // If new_effects isn't a mapping, then there's something // wrong, so it should give an error so that someone knows // and can get it fixed. words = keys(_external_effects); foreach( word in words ) { if( _external_effects[word] ) { new_effects[word] *= that; new_effects[word] += this * _external_effects[word]; } else { new_effects[word] = this * _external_effects[word]; } } words = keys(new_effects); foreach( word in words ) new_effects[word] /= this + that; _external_effects = new_effects; } /* being_joined_by() */ /** @ignore yes */ void consume_effect( object who, string what, int amount ) { if( who ) who->add_effect( what, amount ); } /* consume_effect() */ /** * This consumes some of the food. * @param consumer The person doing the consumption. * @param amount The amount being consumed. * @param type Use "splash" or "apply" or "external" to use external * effects, anything else for eat effects. */ varargs void consume( object consumer, int amount, string type ) { int denominator, numerator, wholes; string *words, word; switch(type){ case "splash" : case "apply" : case "external" : words = keys(_external_effects); break; default : words = keys(_eat_effects); break; } if( !amount && query_continuous() ) amount = query_amount(); if( amount ) { numerator = amount * query_weight_unit()[0]; denominator = query_weight_unit()[1]; if( type != "apply" && type != "splash" && type != "external" ) { DISEASE_HAND->consume_food( consumer, query_decay(), ( numerator / denominator ), _food_type ); consumer->adjust_volume( ( _liquid? D_DRINK : D_FOOD ), ( 50 * numerator ) / denominator ); } if( !TO->query_property("playtester") || playtesterp(consumer) ) { foreach( word in words ) { switch( type ) { case "splash" : case "apply" : case "external" : numerator = _external_effects[word] * amount * query_weight_unit()[0]; break; default : numerator = _eat_effects[word] * amount * query_weight_unit()[0]; break; } wholes = numerator / denominator; numerator %= denominator; if( random(denominator) < numerator ) wholes++; call_out( (: consume_effect :), 0, consumer, word, wholes ); } } adjust_amount(-amount); } else if( _weight_per_bite ) { _bites_gone++; if( type != "apply" && type != "splash" && type != "external" ) { DISEASE_HAND->consume_food( consumer, query_decay(), _weight_per_bite, _food_type ); consumer->adjust_volume( D_FOOD, 50 * _weight_per_bite ); } if( !TO->query_property("playtester") || playtesterp(consumer) ) { foreach( word in words ) { switch(type) { case "splash" : case "apply" : case "external" : call_out( (: consume_effect :), 0, consumer, word, _external_effects[word] * _weight_per_bite ); break; default : call_out( (: consume_effect :), 0, consumer, word, _eat_effects[word] * _weight_per_bite ); break; } } } } else { DISEASE_HAND->consume_food( consumer, query_decay(), _weight_per_bite, _food_type ); consumer->adjust_volume( D_FOOD, 50 * weight ); if( !TO->query_property("playtester") || playtesterp(consumer) ) { foreach( word in words ) { switch( type ) { case "apply" : case "splash" : case "external" : call_out( (: consume_effect :), 0, consumer, word, _external_effects[word] * weight ); break; default : call_out( (: consume_effect :), 0, consumer, word, _eat_effects[word] * weight ); break; } } } } if( type != "apply" && type != "splash" && type != "external" ) { if( _eat_func ) call_other( _eat_object || TO, _eat_func, TO, consumer ); } } /* consume() */ /** * This is the eat command call. This is setup with add_command() * and does the actual eating. * @param no_mess don't use success messages * @return Return 1 on success, 2 on success + last bit eaten and return 0 on failure. * @see /global/new_parse->add_command() * @see /global/new_parse->add_succeeded_mess() * @see set_eat_mess() */ varargs int do_eat( int no_mess ) { int ret; if( weight <= 0 && !query_continuous() ) { if( _eat_mess ) add_succeeded_mess( _eat_mess ); move("/room/rubbish"); return 1; } if( ( weight <= ( _bites_gone + 1 ) * _weight_per_bite ) || !_weight_per_bite ) { if( !no_mess ) { if( !query_continuous() ) { if( _weight_per_bite ) { add_succeeded_mess( _eat_last_mess || "$N $V the last of $D.\n"); } else if( _eat_mess ) { // This only gets done if the whole thing is eaten. add_succeeded_mess( _eat_mess ); } } else { if( _eat_amt_mess ) add_succeeded_mess( replace_string( _eat_amt_mess, "$amt$", amount_size() ) ); else add_succeeded_mess("$N $V "+amount_size()+" of $D.\n"); } } ret = 2; move("/room/rubbish"); } else { if( !no_mess ) add_succeeded_mess( _eat_piece_mess || "$N $V a piece of $D.\n"); ret = 1; } consume( TP, 0 ); // This increases bites gone too. return ret; } /* do_eat() */ /** * This checks to see if a weapon is sharp. This is used later * to determine whather an object can be cut up with the weapon. * @param weapon the weapon to check to see if it is sharp * @return 1 if it is sharp, or 0 if not */ int check_sharp( object weapon ) { mixed data; int i; if( !weapon ) return 0; data = weapon->query_attack_data(); for( i = W_TYPE; i < sizeof(data); i += W_ARRAY_SIZE ) { if( data[i] == "sharp" ) return 1; } return 0; } /* check_sharp() */ /** * This cuts the food up into bits. This is the command called * with add_command() and does the actual cutting up into bits. If * the num_pieces parameter is undefined, the food is cut in half. * If the food is continuous, the cutting is handled by * the state_change handler (all continuous objects must be cut this * way). * @see set_piece_description() * @see query_in_pieces() * @see query_piece_short() * @see query_piece_plural() * @see query_piece_substance() * @param num_pieces the number of pieces to cut the food into * @return 1 if it succeeded, 0 if not. */ varargs int do_cut( int num_pieces ) { object bing, *obs, with; string name, *exploded_short; int i, j, k, size_of_each, gone; int portion_of_whole; int portion_of_parent; string temp_short, temp_plural; mixed rabbit; if( query_liquid() ) { add_failed_mess("You cannot $V $D.\n"); return -1; } // First, check to see if they have a sharp weapon. obs = TP->query_holding() - ({ 0 }); foreach( bing in obs ) { if( check_sharp(bing) ) { if( !with || ( with && with->query_weight() > bing->query_weight() ) ) { with = bing; } } } if( !with ) { add_failed_mess("You have to be holding a sharp object to $V $D.\n"); return -1; } if( undefinedp(num_pieces) ) num_pieces = 2; if( query_continuous() ) return( do_mince() ); if( !_weight_per_bite ) { add_failed_mess("$D cannot be cut up.\n"); return 0; } if( num_pieces < 2 || num_pieces > ( ( query_weight() / _weight_per_bite ) - _bites_gone ) ) { if( num_pieces == 2 || ( ( query_weight() / _weight_per_bite ) - _bites_gone ) < 2 ) add_failed_mess("$D cannot be cut up.\n"); else add_failed_mess("You can't $V $D into that many pieces.\n"); return -1; } // Now, do the cutting. name = base_name(TO); size_of_each = ( query_weight() - _weight_per_bite * _bites_gone ) / num_pieces; gone = ( ::query_weight() - size_of_each ) / _weight_per_bite; for( i = 0; i < num_pieces; i++ ) { // Duplicate ourselves. bing = clone_object(name); bing->init_static_arg( query_static_auto_load() ); bing->init_dynamic_arg( query_dynamic_auto_load() ); bing->set_weight(size_of_each); bing->add_property("whole:weight", query_property("whole:weight") || query_weight() ); bing->add_property("whole:short", query_property("whole:short") || TP->convert_message( a_short() ) ); portion_of_whole = bing->query_property("whole:weight") / size_of_each; portion_of_parent = query_weight() / size_of_each; if( !_in_pieces ) { if( _piece_desc ) { bing->set_long( _piece_desc ); if( !_piece_short ) { if( portion_of_whole == 1 ) temp_short = "piece"; else if( portion_of_whole == 2 ) temp_short = "half"; else if( portion_of_whole == 4 ) temp_short = "quarter"; else temp_short = word_ordinal( portion_of_whole ); } if( !_piece_plural ) { if( _piece_short ) _piece_plural = pluralize( _piece_short ); else temp_plural = pluralize( temp_short ); } rabbit = _piece_substance || bing->query_property("whole:short"); bing->set_short( ( _piece_short || temp_short )+ " of "+rabbit ); bing->set_main_plural( ( _piece_plural || temp_plural ) + " of " + rabbit ); } } else { bing->set_bites_gone( gone ); } /* These next two might not be valid if _in_pieces isn't 0. Good * thing it always is! */ bing->add_alias( _piece_short || temp_short ); bing->add_plural( _piece_plural || temp_plural ); exploded_short = explode( bing->query_short(), " " ); j = sizeof( exploded_short ); bing->set_name( exploded_short[ j - 1 ] ); for( k = 0; k < sizeof( exploded_short ) - 1; k++ ) bing->add_adjective( exploded_short[k] ); bing->set_in_pieces( 1 ); // This doesn't do anything! if( _piece_desc ) bing->set_long(_piece_desc); bing->move( environment( TO ) ); } move("/room/rubbish"); // Use the default message. if( with->query_weight() < 20 ) { add_succeeded_mess( ({ sprintf("$N $V $D into %s pieces with %s%s.\n", query_num( num_pieces ), with->poss_short(), ( num_pieces * size_of_each ) < query_weight() ? ", carelessly dribbling a bit while you do so" : "" ), sprintf("$N $V $D into %s pieces with %s.\n", query_num( num_pieces ), with->poss_short() ) }) ); } else { add_succeeded_mess( ({ sprintf("$N stand back and hack $D into %s pieces with %s%s.\n", query_num( num_pieces ), with->poss_short(), ( num_pieces * size_of_each ) < query_weight() ? ", carelessly obliterating a bit when you do so" : "" ), sprintf("$N stand$s back and hack$s $D into %s pieces with %s.\n", query_num( num_pieces ), with->poss_short() ) }) ); } return 1; } /* do_cut() */ varargs int do_mince() { object *obs, with, transformed; int rem_pieces; if( query_liquid() ) { add_failed_mess("You cannot $V $D.\n"); return -1; } // First, check to see if they have a sharp weapon. obs = TP->query_holding() - ({ 0 }); foreach( transformed in obs ) { if( check_sharp( transformed ) ) { if( !with || ( with && with->query_weight() > transformed->query_weight() ) ) { with = transformed; } } } if( !with ) { add_failed_mess( "You have to be holding a sharp object to $V " "$D.\n" ); return -1; } transformed = STATE_CHANGE->transform( TO, "slice" ); // if it doesn't mince, cut into the remaining number of pieces if( !objectp( transformed )) { if( query_continuous() ) { add_failed_mess("$D cannot be cut up any further.\n"); return -1; } if( _weight_per_bite ) { rem_pieces = ( query_weight() / _weight_per_bite ) - _bites_gone; return( do_cut( rem_pieces ) ); } return -1; } TO->move( "/room/rubbish" ); if( transformed->move( TP ) != MOVE_OK ) { call_out( "tell_object", 0, TP, "You drop "+transformed->the_short()+".\n"); call_out( "tell_room", 0, ENV( TP ), TP->the_short()+" drops "+ transformed->the_short()+".\n", TP ); transformed->move( environment( TP ) ); } add_succeeded_mess("$N $V $D with "+with->the_short()+" into $I.\n", ({ transformed }) ); return 1; } /* do_mince() */ /** * This gives the appropriate message for drinking things. * @see do_eat() * @see consume(*) * @see set_liquid() * @return Return 1 if it succeeded, or return 0 if it failed. */ int do_drink() { if( !check_for_container() ) { add_failed_mess("You go to try and drink $D, but it suddenly " "remembers what happens when it is not in a container and " "drains away to dust.\n"); move("/room/rubbish"); return 0; } TP->add_succeeded_mess( TO, "$N $V "+amount_size()+" of $D.\n", ({ }) ); consume( TP, 0 ); return 1; } /* do_drink() */ /** * This handles applying things. * @see set_applicable() * @see consume(*) * @return Return 1 if it succeeded, or return 0 if it failed. */ int do_apply(object *things) { if( sizeof(things) > 1 ) { add_failed_mess("You can $V $D to only one person.\n", ({ }) ); return 0; } if( !_applicable ) { add_failed_mess("You cannot $V $D to anybody.\n"); return 0; } if( _external_pk_check && pk_check(TP, things[0]) ) { add_failed_mess("You feel it would be wrong to rub $D on $I.\n", things ); return 0; } TP->add_succeeded_mess( TO, "$N $V "+amount_size()+" of $D to $I.\n", ({ things[0] }) ); consume( things[0], 0, "apply" ); return 1; } /* do_apply() */ /** @ignore yes */ int do_rub(object *things) { /* identical to do_apply, except for one little syntax thing */ if( sizeof(things) > 1 ){ add_failed_mess("You can $V $D on only one person.\n", ({ }) ); return 0; } if( !_applicable ) { add_failed_mess("You cannot $V $D on anybody.\n"); return 0; } if( _external_pk_check && pk_check( TP, things[0] ) ) { add_failed_mess("You feel it would be wrong to rub $D on $I.\n", things); return 0; } TP->add_succeeded_mess( TO, "$N $V "+amount_size()+" of $D on $I.\n", ({ things[0] }) ); consume( things[0], 0, "apply" ); return 1; } /* do_rub() */ int do_splash(object *things) { if( sizeof(things) > 1 ) { add_failed_mess("You can $V $D on only one person.\n", ({ }) ); return 0; } if( !_splashable || !_liquid ) { add_failed_mess("You cannot $V $D on anybody.\n", ({ }) ); return 0; } if( _external_pk_check && pk_check( TP, things[0] ) ) { add_failed_mess("You feel it would be wrong to splash $D on $I.\n", things); return 0; } TP->add_succeeded_mess( TO, "$N $V "+amount_size()+" of $D on $I.\n", ({ things[0] }) ); consume( things[0], 0, "splash" ); return 1; } /* do_splash() */ /** @ignore yes */ mapping int_query_static_auto_load() { mapping map; map = ([ "::" : ::int_query_static_auto_load() ]); if( _liquid ) map["liquid"] = _liquid; if( _weight_per_bite ) map["weight_per_bite"] = _weight_per_bite; if( _eat_object ) map["eat_object"] = _eat_object; if( _eat_func ) map["eat_func"] = _eat_func; if( _eat_mess ) map["eat_mess"] = _eat_mess; if( _splashable ) map["splashable"] = _splashable; if( _applicable ) map["applicable"] = _applicable; if( _external_pk_check ) map["external_pk_check"] = _external_pk_check; return map; } /* int_query_static_auto_load() */ /** @ignore yes */ mapping query_static_auto_load() { if( !query_name() || query_name() == "object" ) return 0; if( base_name(TO) + ".c" == __FILE__ || query_continuous() ) return int_query_static_auto_load(); return 0; } /* query_static_auto_load() */ /** @ignore yes */ void init_static_arg( mapping map ) { if( !mapp(map) ) return; if( map["::"] ) ::init_static_arg(map["::"]); _liquid = map["liquid"]; _weight_per_bite = map["weight_per_bite"]; _eat_object = map["eat_object"]; _eat_func = map["eat_func"]; _eat_mess = map["eat_mess"]; _splashable = map["splashable"]; _applicable = map["applicable"]; _external_pk_check = map["external_pk_check"]; } /* init_static_arg() */ /** @ignore yes */ mapping query_dynamic_auto_load() { mapping map; map = ([ "::" : ::query_dynamic_auto_load() ]); if( _bites_gone ) map["bites_gone"] = _bites_gone; if( sizeof(_eat_effects) ) map["eat_effects"] = _eat_effects; if( sizeof(_external_effects) ) map["external_effects"] = _external_effects; if( _in_pieces ) map["in pieces"] = _in_pieces; if( _piece_desc ) map["piece desc"] = _piece_desc; if( _piece_short ) map["piece short"] = _piece_short; if( _piece_plural ) map["piece plural"] = _piece_plural; if( _piece_substance ) map["piece substance"] = _piece_substance; if( _decay_speed ) map["decay speed"] = _decay_speed; if( _decay_level ) map["decay level"] = _decay_level; if( _divisor ) map["divisor"] = _divisor; return map; } /* query_dynamic_auto_load() */ /** @ignore yes */ void init_dynamic_arg( mapping map ) { if( !mapp(map) ) return; if( map["::"] ) ::init_dynamic_arg(map["::"]); _bites_gone = map["bites_gone"]; _eat_effects = map["eat_effects"] || ([ ]); _external_effects = map["external_effects"] || ([ ]); _in_pieces = map["in pieces"]; _piece_desc = map["piece desc"]; _piece_short = map["piece short"]; _piece_plural = map["piece plural"]; _piece_substance = map["piece substance"]; if( _in_pieces && _piece_desc ) set_long(_piece_desc); set_decay_speed(map["decay speed"]); _decay_level = map["decay level"]; _divisor = map["divisor"]; } /* init_dynamic_arg() */ /** @ignore yes */ mixed stats() { string word, *words; mixed args; int i; args = ::stats() + ({ ({"liquid", _liquid }), ({"bites gone", _bites_gone }), ({"weight per bite", _weight_per_bite }), ({"eat object", _eat_object }), ({"eat function", _eat_func }), ({"eat message", _eat_mess }), ({"in pieces", _in_pieces }), ({"decay speed", _decay_speed }), ({"decay level", _decay_level }), ({"food type", _food_type }), }); words = keys(_eat_effects); foreach( word in words ) args += ({ ({"eat effect#"+i++, word+" ("+_eat_effects[word]+")"}) }); args += ({ ({"applicable", _applicable }) }); args += ({ ({"splashable", _splashable }) }); if( _splashable && !_liquid ) args += ({ ({"***not splashable***", "because not a liquid"}) }); args += ({ ({"external_pk_check", _external_pk_check }) }); if( _external_effects ) { words = keys(_external_effects); i = 0; foreach( word in words ) args += ({ ({"external effect#"+i++, word+" ("+ _external_effects[word]+")"}) }); } return args; } /* stats() */ /** * This checks the outside of us for a container and then destroys ourselves * if it is not a container. */ int check_for_container() { if( query_liquid() ) { if( !environment()->query_max_volume() || living(environment()) ) { return 0; } } return 1; } /* check_for_container() */ private void move_check_for_container() { if( !check_for_container() ) { // Opps, not a container. Destroy ourselves. tell_room( environment(), the_short()+" dribbles all the place " "and disappears into the dust.\n"); move("/room/rubbish"); } } /* check_for_container() */ /** * @ignore yes * Check to see if the thing outside us can handle liquids. */ varargs int move(mixed dest, string mess1, string mess2) { int ret; ret = ::move(dest, mess1, mess2); if( ret == MOVE_OK && dest != "/room/rubbish") call_out("move_check_for_container", 2 ); return ret; } /* move() */