/** * This handles all the methods for determining values of coins and * the current valid set of coins. It also handles change calculation. * This was written originaly by Pinkfish, reworked significantly by * Deutha to add in the multiple currency areas. * @see /std/living/money.c * @author Pinkfish * @changed Fixed up the code that deals with different currency areas. * Also added money_array_from_string() and made value_from_string() * work properly with different types of currencies. * - Sandoz, April 2003. */ #define DATA ((class money_data)details[type]) #define __MONEY_CLASS__ #include <money.h> private mapping values; private mapping details; private mapping aliases; private object parse_cont; private void add_coin_type( string type, string head_short, string tail_short, string head_long, string tail_long, string material, string plural, string adj, int weight ) { DATA = new( class money_data, head_short : head_short, tail_short : tail_short, head_long : head_long, tail_long : tail_long, material : material, plural : plural, adj : adj, weight : weight ); } /* add_coin_type() */ void create() { values = ([ "default": ({ "copper", 1, "silver", 10, "electrum", 100, "gold", 2000, "platinum", 6000, }), "calarien": ({ "Calarien rahn", 1, "Calarien tablis", 100, "Calarien reaal", 10000, }), ]); details = ([ ]); add_coin_type("copper", "heads", "tails", "a head", "a tail", "copper", 0, "small", 10 ); add_coin_type("silver", "heads", "tails", "a head", "a tail", "silver", 0, "thin and slighty scratched", 13 ); add_coin_type("electrum", "heads", "tails", "a head", "a tail", "electrum", 0, "slightly scratched", 15 ); add_coin_type("gold", "heads", "tails", "a head", "a tail", "gold", 0, "thin", 18 ); add_coin_type("platinum", "heads", "tails", "a head", "a tail", "platinum", 0, "large and pretty heavy", 20 ); // Weight a little less than 3 grams. add_coin_type("Calarien rahn", "heads", "tails", "an ornate number one, imprinted in a grubby surface", "a scratched carving of what might be a blooming sunflower", "copper", "rahns", "thin and scratched", 5 ); // Weight a little over 7 grams. add_coin_type("Calarien tablis", "heads", "tails", "a crudely carved sun", "a delicate waxing moon", "silver", "tabli", "slightly scratched", 15 ); // Weight a little over 12 grams. add_coin_type("Calarien reaal", "heads", "tails", "a small imprint of " "the Calarien crest along with the words \"1 REAAL\" underneath it", "a delicate carving of an eagle, its beak wide open and wings spread " "in their full glory", "gold", "reaali", "large and fairly heavy", 25 ); aliases = ([ "Calarien rahn" : ({"rahn"}), "Calarien tablis" : ({"tablis"}), "Calarien reaal" : ({"reaal"}), ]); } /* create() */ /** * This method returns the mapping containing all the values of the * currently valid money types. The mapping has keys of the domain * of the money and has a value of an array. The array contains * alternating name, value pairs. * <pre> * ([ "default": ({ "brass", 1, "copper", 10, "silver", 100, * "gold", 2000, "platinum", 6000 }) ]) * </pre> * @return the mapping of values * @see query_values() * @see query_values_in() */ mapping query_all_values() { return copy(values); } /** * This method returns the current set of areas in which types can * be found. * @return the set of places */ string *query_all_places() { return keys(values); } /** * This method returns the values in the default area. * This method returns the array as given in the value above. * It contains name, value pairs and is for the "default" * area. * @return the array of values * @see query_all_values() * @see query_values_in() */ mixed *query_values() { return copy( values["default"] ); } /** * This method returns the values in the specified area. * It contains name, value pairs and is for the "default" * area. * @return the array of values * @param where the area in which to return the values for * @see query_all_values() * @see query_values() * @see add_type() */ mixed query_values_in( string where ) { if( !where || where == "") where = "default"; return copy( values[where] ); } /* query_values_in() */ /** * This method returns all the details for the current set of * coins. The details are information which is shown when the coin * is looked at. Stuff about heads and tails and things. * @return the mapping of details for all coin types * @see /include/money.h */ mapping query_details() { return copy( details ); } /** * This method returns the details for a specified type of money. * It will return a class of the form: * <pre> * ({ forward short, reverse short, * forward long, reverse long, composition, * plural, adjectives, weight }) * </pre> * @param type the money type to get the details for * @see /include/money.h */ class money_data query_details_for( string type ) { if( DATA ) return copy(DATA); return 0; } /* query_details_for() */ /** * This method returns all the current aliases for the given type * of money. * @param type the type of money to get the aliases for * @return the aliases for the money type */ string *query_aliases_for( string type ) { string *ret; if( !DATA || !DATA->plural ) ret = ({ "coin" }); else ret = ({ explode( type, " ")[<1] }); if( aliases[ type ] ) ret += aliases[ type ]; return ret; } /* query_aliases_for() */ /** * This method returns the short description of the money type. * @param type the money type to get the short description for * @return the short description for the money object * @see query_main_plural_for() */ string query_short_for( string type ) { if( !DATA || !DATA->plural ) return type+" coin"; return type; } /* query_short_for() */ /** * This method returns the short plural description of the money type. * This returns just the one word, like 'coins' or 'talons'. * @param type the money type to get the short plural description for * @return the short plural description for the money object * @see query_main_plural_for() */ string query_plural_for( string type ) { if( !DATA || !DATA->plural ) return "coins"; return DATA->plural; } /* query_plural_for() */ /** * This method returns the main short plural description of the money type. * This returns the expanded plural version like 'Ankh-Morpork pennies'. * @param type the money type to get the short plural description for * @return the short plural description for the money object * @see query_plural_for() * @see query_short_for() */ string query_main_plural_for( string type ) { if( !DATA || !DATA->plural ) return type+" coins"; return implode( explode( type, " ")[0..<2] + ({ DATA->plural }), " "); } /* query_main_plural_for() */ /** * This method returns the weight of 100 coins of the money type. * @param type the money type to get the weight of 100 coins for * @return the weight of 100 coins of the specified type */ int query_weight_for( string type ) { if( !DATA ) return 10; return DATA->weight || 10; } /* query_weight_for() */ /** * This method returns the value of a specified type of money in a certain * money area. * @param type the type of money to get the value for * @param where the money area the money is in * @return the integer value of the money * @see query_total_value() */ varargs int query_value( string type, string where ) { int i; if( !where || where == "") where = "default"; if( !values[where] ) return 0; if( ( i = member_array( type, values[where] ) ) == -1 ) return 0; return values[where][i+1]; } /* query_value() */ /** * This method determines the total value of a specified money array. * A money array consists of pairs of values ({ type, number }) * @param mon_array the array to find the value of * @param where the money area to get the value in * @return the total value as an integer * @see query_value() */ varargs int query_total_value( mixed mon_array, string where ) { int i, amt; if( !where || where == "") where = "default"; for( i = 0; i < sizeof( mon_array ); i += 2 ) amt += mon_array[ i + 1 ] * query_value( mon_array[ i ], where ); return amt; } /* query_total_value() */ /** * This method converts a money array into a string so it can be displayed. * @param mon_array the money array to convert into a string * @see money_value_string() */ string money_string( mixed mon_array ) { int i; string ret; if( !sizeof(mon_array) ) return "nothing"; ret = ""; while( i < sizeof(mon_array) ) { if( !mon_array[ i + 1 ] ) mon_array = delete( mon_array, i, 2 ); else i += 2; } for( i = 0; i < sizeof(mon_array); i += 2 ) { ret += query_num(mon_array[ i + 1 ])+" "; if( mon_array[ i + 1 ] == 1 ) ret += query_short_for( mon_array[ i ] ); else ret += query_main_plural_for( mon_array[ i ] ); if( i == sizeof(mon_array) - 4 ) ret += " and "; else if ( i != sizeof(mon_array) - 2 ) ret += ", "; } return ret; } /* money_string() */ /** * This method creates a money array from a certain value in a particular * money area. A money array consists of ({ type, number }) pairs in an * array. ie: ({ "brass", 12, "copper", 24 }). * @example * place = query_property("place"); * if (!place) { * place = "default"; * } * mon_array = create_money_array( 1000, place); * @param value the value to get the money array for * @param where the money area to get the value in * @return a money array for the value in the area * @see money_value_string() */ varargs mixed create_money_array( int value, string where ) { int i, amt; mixed mon_array; if( !where || where == "") where = "default"; if( !value ) return ({ }); mon_array = ({ }); for( i = sizeof( values[ where ] ) - 2; i >= 0; i -= 2 ) { if( value >= values[ where ][ i + 1 ] ) { mon_array += ({ values[ where ][ i ], amt = value / values[ where ][ i + 1 ] }); value -= amt * values[ where ][ i + 1]; } } return mon_array; } /* create_money_array() */ /** * This method returns a string which is based on the value of * the money in a certain money area. * @param value the value to get the string for * @param where the place to get the string for * @return a string of the money value in the certain money area * @see create_money_array() * @see money_string() * @see value_from_string() */ varargs string money_value_string( int value, string where ) { if( !where || where == "") where = "default"; if( value < 0 ) return "negative "+money_string( create_money_array( -value, where ) ); return money_string( create_money_array( value, where ) ); } /* money_value_string() */ /** * @ignore yes * This method is used both by value_from_string() and * money_array_from_string(). If the flag argument is 0, then it * will return the overall value of the money in the input string. * If the flag argument is 1, then it will parse the input string * and return it as a money array, if the flag is greater than 1, * then it will return on partial matches. */ private mixed int_money_from_string( string str, string where, int flag ) { mixed types, bits, ret; if( !where ) where = "default"; if( !types = values[where] ) return 0; str = lower_case(str); bits = explode( replace( str, ({" and ", ",", " & ", ","}) ), ","); bits = map( bits, (: explode( $1, " ") - ({""}) :) ); if( flag ) ret = ({ }); for( int i = 0; i < sizeof(types); i += 2 ) { string type, plural, *adj, *alias; type = types[i]; if( sizeof( alias = aliases[type] ) ) { adj = explode( lower_case(type), " ")[0..<2]; plural = DATA->plural; } else { alias = ({"coin"}); adj = explode( lower_case(type), " "); plural = "coins"; } for( int j = 0; j < sizeof(bits); j++ ) { string *bit, *match; int number; if( !sizeof( bit = bits[j] ) ) { bits = delete( bits, j--, 1 ); continue; } if( sscanf( bit[0], "%d", number ) == 1 ) { // Nothing but a number, or a negative number specified. if( number < 1 || !sizeof( bit = bit[1..] ) ) { bits = delete( bits, j--, 1 ); continue; } if( number == 1 ) match = alias; else match = ({ plural }); } else { number = 1; match = alias; } // Let's not match on a mere "1 coin". if( sizeof( bit ) == 1 ) { switch( bit[0] ) { case "coin": case "coins": bits = delete( bits, j--, 1 ); continue; default: } } // No match on the name/alias. if( member_array( bit[<1], match ) == -1 ) continue; if( sizeof(bit) > 1 ) { int k, fail; fail = 0; k = sizeof(bit) - 1; while( k-- ) { // Invalid adjective in the input string. // No match. if( member_array( bit[k], adj ) == -1 ) { fail = 1; break; } } if( fail ) continue; } // Everything matched. if( flag ) { int k; if( ( k = member_array( type, ret ) ) == -1 ) ret += ({ type, number }); else ret[k+1] += number; } else { ret += number * types[i+1]; } bits = delete( bits, j--, 1 ); } } switch( flag ) { case 0 : case 1: return ret; default: // Do not accept partial matches with a flag greater than 1. if( !sizeof(bits) ) return ret; return 0; } } /* int_money_from_string() */ /** * This method attempts to find a money value from a string. It will * attempt to do fuzzy matching of the type. This means it will match on * partial matches, this could lead to somewhat weird behaviour... So it * goes. * @param str the string to find the value of * @see money_value_string() * @see money_array_from_string() * @example * // This will tell us the integer money value of the string. * write( MONEY_H->value_from_string("1 dollar and 12 pence", * "Ankh-Morpork")); */ int value_from_string( string str, string where ) { return int_money_from_string( str, where, 0 ); } /* value_from_string() */ /** * This method attempts to create a money array from a string. It will * attempt to do fuzzy matching of the type. This means it will match on * partial matches, this could lead to somewhat weird behaviour. * @param str the string to find the value of * @param where the currency area to get the money array for * @param flag if the flag is set, then we will return 0 on a partial match * @see money_value_string() * @see value_from_string() * @example * MONEY_H->money_array_from_string("12 Calarien rahns and 37 tabli", * "calarien"); */ mixed money_array_from_string( string str, string where, int flag ) { return int_money_from_string( str, where, flag ? 2 : 1 ); } /* money_array_from_string() */ /** * This method calculates the change of a certain value from a * given money array. This makes sure that the change does not include * money that does not actually exist. * @param value the value of the change to calculate * @param mon_array the money array to determine the change from * @return the money array containing the change to use * @see make_payment() * @see pay_amount_from() */ mixed calc_change( int value, mixed *mon_array ) { int i, num; mixed ret; ret = ({ }); for( i = sizeof( mon_array ) - 2; i >= 0; i -= 2 ) { if( value >= mon_array[ i + 1 ] ) { num = value / mon_array[ i + 1 ]; value = value % mon_array[ i + 1 ]; ret += ({ mon_array[ i ], num }); if( !value ) return ret; } } return ret; } /* calc_change() */ /** * This method makes a payment from a money array. It returns the * depleted money array, the amount taken out and the change * needed. If the type is not set, then the best fit for the value * is found from the array. * <p> * The return array is formated as:<br> * ({ depleted_money_array, change, taken_from })<br> * The change is an integer value. * @param type the type of money to take out * @param value the amount of the type to take out * @param mon_array the money array to use * @param where the money area * @param use_default allow the use of the default money type * @return the return array as formated above */ mixed make_money_array_payment( string type, int value, mixed mon_array, string where, int use_default ) { int i, j, num, total, cur_match; string mon_name; mixed poss_values, ret; // Figure out the money type. if( !where || where == "") where = "default"; // See if the money is there and it's all easy. if( type ) { if( ( i = member_array( type, mon_array ) ) != -1 ) { if( value <= mon_array[ i + 1 ] ) { mon_array[ i + 1] -= value; return ({ ({ type, value }), 0, mon_array }); } } // Damn, its not easy. Figure out the real value and see if we can // get it out of the arrays. value = value * query_value( type, where ); } // Check to make sure the total is ok. total = query_total_value( mon_array, where ); if( use_default && where != "default") total += query_total_value( mon_array, "default"); // If the value is more than the total... if( value > total ) return 0; // Get the possible values. poss_values = ({ }); if( where != "default" && use_default ) poss_values += values["default"]; poss_values += values[ where ]; ret = ({ }); // This attempts an exact match of coins. for( i = ( sizeof( poss_values ) - 2 ); i >= 0; i -= 2 ) { j = member_array( poss_values[ i ], mon_array ); if( j != - 1 ) { if ( poss_values[ i + 1 ] <= value ) { num = value / poss_values[ i + 1 ]; if( num > mon_array[ j + 1 ] ) num = mon_array[ j + 1 ]; mon_array[ j + 1] -= num; value -= num * poss_values[ i + 1 ]; ret += ({ poss_values[ i ], num }); if( !value ) return ({ ret, value, mon_array }); } } } // No exact match... // Now we need to figure out how much change to give. // Zoom through the array finding which one has the closest match. cur_match = value + 10000000; for( i = 0; i < sizeof(poss_values); i +=2 ) { j = member_array( poss_values[ i ], mon_array ); if( j != -1 && mon_array[j+1] > 0 && poss_values[i+1] >= value && poss_values[i+1] - value <= cur_match - value ) { cur_match = poss_values[i+1]; mon_name = poss_values[i]; } } if( mon_name ) { j = member_array( mon_name, mon_array ); i = member_array( mon_name, poss_values ); mon_array[j+1] -= 1; value = poss_values[i+1] - value; ret += ({ poss_values[ i ], 1 }); } else { return 0; } return ({ ret, value, mon_array }); } /* make_money_array_payment() */ /** * This method makes a payment of a particular amount in a particular * money area. Please note that player or living objects can double * as money objects in this circumstance. The first element of the * payment array is the values which should be used to take off * the player, the second element is the change needed to be payed * back. * @param type the type of money to pay in * @param value the number of the type to pay * @param thing the thing which is doing the payment (money object) * @param where the money area the payment will occur in * @return the payment array * @see pay_amount_from() * @see calc_change() */ varargs mixed make_payment( string type, int value, object thing, string where ) { mixed mon_array, stuff; if( !type ) return 0; mon_array = copy( thing->query_money_array() ); stuff = make_money_array_payment( type, value, mon_array, where, 1 ); if( !stuff ) return stuff; if( stuff[MONEY_PAY_CHANGE] ) return ({ stuff[MONEY_PAY_RETURN], calc_change( stuff[MONEY_PAY_CHANGE], values[where] ) }); return ({ stuff[MONEY_PAY_RETURN], stuff[MONEY_PAY_CHANGE] }); } /* make_payment() */ /** * This method makes a payment from a specified money object. * @param value the amount to pay * @param money the money object to pay from * @param where the money area the payment occurs in * @return the change object * @see make_payment() * @see calc_change() */ varargs object pay_amount_from( int value, object money, string where ) { int i, j; object change; mixed m_array, p_array; if( !where || where == "") where = "default"; change = clone_object( MONEY_OBJECT ); m_array = create_money_array( value, where ); for( i = 0; i < sizeof(m_array); i += 2 ) { p_array = make_payment( m_array[ i ], m_array[ i + 1 ], money, where ); if( !pointerp( p_array ) ) continue; for( j = 0; j < sizeof( p_array[0] ); j += 2 ) money->adjust_money( -p_array[ 0 ][ j + 1 ], p_array[ 0 ][ j ] ); if( sizeof( p_array[ 1 ] ) ) change->adjust_money( p_array[ 1 ] ); } if( !sizeof( change->query_money_array() ) ) { change->dest_me(); return 0; } return change; } /* pay_amount_from() */ /** * This method creates a money object of a certain value in a certain * money area. * @param value the value to create the new money object with * @param where the area to create the new money object in * @return the new money object */ varargs object make_new_amount( int value, string where ) { object money; if( !where || where == "") where = "default"; money = clone_object( MONEY_OBJECT ); money->set_money_array( create_money_array( value, where ) ); if( !money->query_value_in( where ) ) { money->dest_me(); return 0; } return money; } /* make_new_amount() */ /** * This method figures out the legal and illegal tender money from * the specified money object in the specified money area. This method * returns a two element array which consists of the legal and illegal * tender for the given money area. ({ legal, illegal }) * @param money the money object to get the legal tender from * @param where the money area the tender is for * @return an two element array of objects ({ legal, illegal }) * @see parse_money() */ varargs object *filter_legal_tender( object money, string where ) { int i; object good, no_good; mixed mon_array, poss_values; if( !where || where == "") where = "default"; if( !sizeof( mon_array = money->query_money_array() ) ) { money->dest_me(); return ({ 0, 0 }); } if( !( poss_values = values[ where ] ) ) poss_values = ({ }); if( where != "default" ) poss_values += values["default"]; for( i = 0; i < sizeof(mon_array); i += 2 ) { if( member_array( mon_array[ i ], poss_values ) != -1 ) { if( !good ) good = clone_object(MONEY_OBJECT); good->adjust_money( mon_array[ i + 1 ], mon_array[ i ] ); } else { if( !no_good ) no_good = clone_object(MONEY_OBJECT); no_good->adjust_money( mon_array[ i + 1 ], mon_array[ i ] ); } } money->dest_me(); if( good && !sizeof( good->query_money_array() ) ) good->dest_me(); if( no_good && !sizeof( no_good->query_money_array() ) ) no_good->dest_me(); return ({ good, no_good }); } /* filter_legal_tender() */ /** * This method determines all the money from the player object and moves * it into a container. It then figured out the legal tender for * specified money area and tells the player if the given money is * legal tender for the current area. * @param words the string to match the money on * @param player the player who is attempting the transaction * @param place the money area the transaction is taking place * @return a money object consisting of the legal tender * @see filter_legal_tender() */ varargs mixed parse_money( string words, object player, string place ) { object thing, *monies, *things; things = match_objects_for_existence( words, ({ player }) ); if( !sizeof(things) ) return NO_MATCH; if( !parse_cont ) { parse_cont = clone_object("/std/container"); } else if( sizeof( INV(parse_cont) ) ) { parse_cont->dest_me(); parse_cont = clone_object("/std/container"); } foreach( thing in things ) if( thing->query_property("money") ) thing->move(parse_cont); if( !sizeof( things = INV(parse_cont) ) ) return NO_MONEY; monies = filter_legal_tender( things[ 0 ], place ); if( monies[ 1 ] ) { if( monies[ 1 ]->move(player) ) { monies[ 1 ]->move( ENV(player) ); tell_object( player, "Oh dear, you seem to be somewhat " "overburdened and fumble "+monies[ 1 ]->the_short()+".\n"); } tell_object( player, monies[ 1 ]->the_short()+ ( monies[ 1 ]->query_number_coins() == 1 ? " is" : " are")+" not " "legal tender here.\n"); if( !monies[ 0 ] ) return NO_LEGAL; } return monies[ 0 ]; } /* parse_money() */ /** * This method makes a payment from one person to another. * This method figures out what money should be given to the player * and what should be taken from the other to make a payment of the * correct value in the correct place. * @param value the value to pay * @param place the place to make the payment in * @param payer the person the money is payed from * @param payee the person the money is payed to * @return two element array, or 0 if it cannot be done */ mixed query_person_payments( int value, string place, object payer, object payee ) { mixed stuff, mon_array, rabbit; mon_array = copy( payer->query_money_array() ); stuff = make_money_array_payment( 0, value, mon_array, place, 0 ); if( !stuff ) return 0; if( stuff[MONEY_PAY_CHANGE] ) { // Now check to see if we can get the change from the other guy. mon_array = copy( payee->query_money_array() ); rabbit = make_money_array_payment( 0, stuff[MONEY_PAY_CHANGE], mon_array, place, 0 ); if( !rabbit || rabbit[MONEY_PAY_CHANGE] ) return 0; return ({ stuff[MONEY_PAY_RETURN], rabbit[MONEY_PAY_RETURN] }); } return ({ stuff[MONEY_PAY_RETURN], ({ }) }); } /* query_person_payments() */ /** * This returns a list of valid coin types * @return an array of valid coin types */ string *query_valid_types() { string *tmp, *valid_types = ({ }); mixed elem; int i; tmp = values( query_all_values() ); foreach( elem in tmp ) { for( i = 0; i < sizeof(elem); i++ ) { if( stringp( elem[i] ) ) valid_types += ({ elem[i] }); } } return valid_types; }/* query_valid_types() */ /** * This takes a coin type and returns the place it is associated with. * @param type the coin type i.e. "Ankh-Morpork dollar" * @return the place i.e. "Ankh-Morpork" */ string query_origin_of( string type ) { string elem, *places; if( member_array( type, query_valid_types() ) == -1 ) return 0; places = query_all_places(); foreach( elem in places ) if( member_array( type, query_values_in( elem ) ) != -1 ) return elem; } /* query_origin_of() */ /** * This converts a currency type's alias (i.e. "royal" ) and returns * its 'real' names (i.e. "Ankh-Morpork royal" * @param word the alias to find the real name of * @return an array of real names, or 0 if it's not a real alias */ string *query_aliases_of( string word ) { string *types, elem, *aliases = ({ }); types = query_valid_types(); foreach( elem in types ) { if( member_array( word, query_aliases_for( elem ) ) != -1 ) aliases += ({ elem }); } return aliases; } /* query_aliases_of */ /** * This returns the value of a currency type. * @param type currency type * @return an int of the currency type's value */ int query_value_of( string type ) { if( member_array( type, query_valid_types() ) == -1 ) return 0; return query_value( type, query_origin_of( type ) ); } /* query_value_of() */ /** * This returns the smallest unit of currency in this place. * @param place The place to query. * @return a string of the smallest unit of currency */ string smallest_in( string place ) { int i, j, smallest; mixed values; values = query_values_in( place ); for( i = 1; i < sizeof(values); i += 2 ) { if( !smallest || values[i] < smallest ) { smallest = values[i]; j = i; } } return values[j-1]; } /* smallest_in() */