/** * This the base shop. It buys and sells stuff form players. * <p> * A storeroom must be set for the shop! * <p> * Original made who knows when. * @author Pinkfish * @see set_storeroom() * @change bil * to make the list a lot nicer. * @change Pinkfish * to give shops types and make them send out * reps to sell/buy things from other shops. * @change Ceres * to add burglable storerooms. * @change 23-11-97, Gototh * to add buy, sell, list, browse, value, * cost_too_muchy and not_worthy functions. */ #include <money.h> #include <move_failures.h> #include <shop.h> inherit "/std/shops/inherit/shopkeeper_base"; inherit ROOM_OBJ; mixed our_storeroom, buy_mess, sell_mess, list_mess, value_mess, too_costly_mess, not_worthy_mess, browse_mess, open_cond, *other_shops; int amount_sold, amount_bought, strict_shop, no_steal, sell_stolen, steal_difficulty, min_amount, max_inventory, sell_large; mixed shop_type, buy_func, sell_func, value_func, too_costly_func, cannot_sell_func, browse_func, list_func; nosave string shoplift_handler; string original_storeroom; string shop_list(mixed arr, int detail); string shop_parse(string str, mixed ob, object client, string money, string extra, string which); string find_free_exit(); int do_buy(object *ob); int do_list_these(object *obs); void do_buy_things( object *obs, int cost, object pl ); void do_parse(mixed arr, mixed ob, object client, string money, string extra); object create_mercenary(object rep); void event_shoplift(object command_ob, object thief, object victim); object query_store_room(); void create() { buy_mess = ({ "You buy $ob$ for $money$.\n", "$client$ buys $ob$.\n"}); sell_mess = ({ "You sell $ob$ for $money$.\n", "$client$ sells $ob$.\n"}); list_mess = "$extra$"; value_mess = "$ob$ is valued at $money$.\n"; too_costly_mess = "$ob$ is worth too much to be sold here.\n"; not_worthy_mess = "$ob$ is not worth enough to be sold here.\n"; browse_mess = "$ob$ costs $money$, it looks like:\n$extra$"; open_cond = 1; other_shops = ({ }); max_inventory = MAX_INVENTORY; min_amount = 50; add_help_file("shop"); ::create(); } /* create() */ /** @ignore yes */ void reset() { if( !random(3) ) { // This is incremented by shoplifting. remove_property("inventory_loss"); } call_out("send_out_reps", 2 ); } /* reset() */ /** * This method sets the function to call when buying something. The * function will be called with two elements, the first being the * player doing the buying and the second being the array * of objects being bought. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_value_function() * @see set_too_costly_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_buy_function( mixed func ) { if( stringp(func) || functionp(func) ) buy_func = func; } /* set_buy_function() */ /** * This method sets the function to call when selling something. The * function will be called with two elements, the first being the * player doing the selling and the second being the array * of objects being sold. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_buy_function() * @see set_value_function() * @see set_too_costly_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_sell_function( mixed func ) { if( stringp(func) || functionp(func) ) sell_func = func; } /* set_sell_function() */ /** * This method sets the function to call when an item(*s) are being * valued. The * function will be called with three elements, the first being the * player doing the valueing and the second being the array * of objects being valued and the third being the string * value of the objects. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_buy_function() * @see set_too_costly_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_value_function( mixed func ) { if( stringp(func) || functionp(func) ) value_func = func; } /* set_value_function() */ /** * This method sets the function to call when buying something and it * costs too much. The * function will be called with two elements, the first being the * player doing the buying and the second being the array * of objects which cost too much. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_value_function() * @see set_buy_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_too_costly_function( mixed func ) { if( stringp(func) || functionp(func) ) too_costly_func = func; } /* set_too_costly_function() */ /** * This method sets the function to call when selling something * which fails for some reason. The * function will be called with two elements, the first being the * player doing the selling and the second being the array * of objects being sold. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_value_function() * @see set_too_costly_function() * @see set_buy_function() * @see set_browse_function() * @see set_list_function() */ void set_cannot_sell_function( mixed func ) { if( stringp(func) || functionp(func) ) cannot_sell_func = func; } /* set_cannot_sell_function() */ /** * This method sets the function to call when browsing something. The * function will be called with two elements, the first being the * player doing the browsing and the second being the array * of objects being browsed. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_value_function() * @see set_too_costly_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_browse_function( mixed func ) { if( stringp(func) || functionp(func) ) browse_func = func; } /* set_browse_function() */ /** * This method sets the function to call when listing something. The * function will be called with two elements, the first being the * player doing the listing and the second being the array * of objects being listed. * <p> * If the function is a string then the function of that name * will be called on the shop, if it is a function pointer * then the function pointer will be evaluated. * @param func the function to call * @see set_sell_function() * @see set_value_function() * @see set_too_costly_function() * @see set_cannot_sell_function() * @see set_browse_function() * @see set_list_function() */ void set_list_function( mixed func ) { if( stringp(func) || functionp(func) ) list_func = func; } /* set_list_function() */ /** * This sets the message which will be told to the players when * they sell something at the shop. If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_sell_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_no_worthy_message() * @see set_browse_message() * @see set_list_message() * @example * set_sell_message("You sell $ob$ for $money$.\n"); * @example * set_sell_message( ({ "You sell $ob$ for $money$.\n", * "$client$ sells $ob$.\n"}); */ void set_sell_message( mixed str ) { sell_mess = str; } /* set_sell_message() */ /** * This sets the message which will be told to the players when * they buy something at the shop. If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_sell_message() * @see query_buy_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_no_worthy_message() * @see set_browse_message() * @see set_list_message() * @example * set_buy_message("You buy $ob$ for $money$.\n"); * @example * set_buy_message( ({ "You buy $ob$ for $money$.\n", * "$client$ buys $ob$.\n"}); */ void set_buy_message( mixed str ) { buy_mess = str; } /* set_buy_message() */ /** * This sets the message which will be told to the players when * they value something at the shop. If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_value_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_no_worthy_message() * @see set_browse_message() * @see set_list_message() * @example * set_value_message("$ob$ is valued at $money$.\n"); * @example * set_sell_message( ({ "$ob$ is valued at $money$.\n", * "$client$ values $ob$.\n"}); */ void set_value_message( mixed str ) { value_mess = str; } /* set_value_message() */ /** * This sets the message which will be told to the players when * they buy something at the shop and it costs too much. * If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_too_costly_message() * @see set_set_value_message() * @see set_browse_message() * @see set_no_worthy_message() * @see set_sell_message() * @see set_list_message() * @example * set_browse_message("$ob$ is worth too much to be sold here.\n"); * @example * set_browse_message( ({ "$ob$ is worth too much to be sold here.\n", * "$client$ tries to sell the terribly expensive $ob$.\n"}); */ void set_too_costly_message( mixed str ) { too_costly_mess = str; } /* set_too_costly_message() */ /** * This sets the message which will be told to the players when * they sell soemthign that is not worth enough at the shop. * If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_not_worthy_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_browse_message() * @see set_sell_message() * @see set_list_message() * @example * set_not_worthy_message("$ob$ is not worth enough to be sold here.\n"); * @example * set_not_worthy_message( ({ "$ob$ is not worth enough to be sold here.\n", * "$client$ tries to sell the rubbishy $ob$.\n"}); */ void set_not_worthy_message( mixed str ) { not_worthy_mess = str; } /* set_not_worthy_message() */ /** * This sets the message which will be told to the players when * they browse something at the shop. If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. In the case oif a browse the * extra information is the long description of the object. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_browse_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_no_worthy_message() * @see set_sell_message() * @see set_list_message() * @example * set_browse_message("$ob$ costs $money$, it looks like:\n$extra$"); * @example * set_browse_message( ({ "$ob$ costs $money$, it looks like:\n$extra$", * "$client$ browses $ob$.\n"}); */ void set_browse_message( mixed str ) { browse_mess = str; } /* set_browse_message() */ /** * This sets the message which will be told to the players when * they list something at the shop. If the parameter is a string * then that message is sent to the player and nothing is sent to * the other people in the room. If the message is a two element * array, the first element is sent to the player and the second * element is sent to the others in the room. In both these * cases the pattersn $ob$ will be replaces with the objects * being sold, $client$ will be replaced with the client, $money$ * will be replaced with the money information, $extra$ will be replaces * with any extra information. In the case of a list, * the extra information *is* the list. * <p> * If the parameter is a function pointer, then this is called with * the parameter func(obs, client, money, extra); * @param str the message to print * @see set_buy_message() * @see query_buy_message() * @see set_set_value_message() * @see set_too_costly_message() * @see set_no_worthy_message() * @see set_browse_message() * @see set_list_message() * @example * set_list_message("$extra$.\n"); * @example * set_list_message( ({ "You list $ob$ for $money$.\n", * "$client$ lists $ob$.\n"}); */ void set_list_message( mixed str ) { list_mess = str; } /* set_list_message() */ /** * This sets the conditons which the shop will be open during. If this * is set to an integer the shop will always be in that state, so * if you set the open condition to 0, it would always be closed. * If it is set to a string, then that function will be called on * this object to test to see if it is open. If it iset to * function pointer, the function pointer will be evaluated. If it * is set to an array, the first element specifies the object and the * second specifies the function to call. * @see query_open_condition() * @see test_open() */ void set_open_condition( mixed str ) { open_cond = str; } /* set_open_condition() */ /** * This method sets the no steal property. If a shop is set * as no steal, then it cannot be shoplifted. * @param i the new value of the no_steal property * @see set_steal_difficulty() * @see query_no_steal() * @see set_sell_stolen() */ void set_no_steal( int i ) { no_steal = i; } /* set_no_steal() */ /** * This method sets the difficulty at which to steal stuff from * this shop. * @see set_no_steal() * @see set_sell_stolen() * @see query_steal_difficulty() * @param i the new value for the steal difficulty */ void set_steal_difficulty( int i ) { steal_difficulty = i; } /* set_steal_difficulty() */ /** * This method sets the shop as a place which will receive and * sell stolen goods. It automaticly sets the no_steal property * to 1. * @see set_no_steal() * @see set_steal_difficulty() * @see query_sell_stolen() * @param i the new value for the stolen property */ void set_sell_stolen( int i ) { sell_stolen = i; no_steal = 1; } /* set_sell_stolen() */ /** * This method sets the shop to sell very large objects. ie. If a player * cannot carry items they are placed in the room for the player rather * than not being sold to the player. */ void set_sell_large( int i ) { sell_large = i; } /** * This method returns the current sell message of the shop. * @see set_sell_mess() * @return the current sell message of the shop */ mixed query_sell_mess() { return sell_mess; } /** * This method returns the current list message of the shop. * @see set_list_mess() * @return the current list message of the shop */ mixed query_list_mess() { return list_mess; } /** * This method returns the current value message of the shop. * @see set_value_mess() * @return the current value message of the shop */ mixed query_value_mess() { return value_mess; } /** * This method returns the current too costly message of the shop. * @see set_too costly_mess() * @return the current too costly message of the shop */ mixed query_too_costly_mess() { return too_costly_mess; } /** * This method returns the current not worthy message of the shop. * @see set_not worthy_mess() * @return the current not worthy message of the shop */ mixed query_not_worthy_mess() { return not_worthy_mess; } /** * This method returns the current buy message of the shop. * @see set_buy_mess() * @return the current buy message of the shop */ mixed query_buy_mess() { return buy_mess; } /** * This method returns the current browse message of the shop. * @see set_browse_mess() * @return the current browse message of the shop */ mixed query_browse_mess() { return browse_mess; } /** * This method returns the current open conditon of the shop. * @see set_open_conditon_mess() * @return the current open condition message of the shop */ mixed query_open_condition() { return open_cond; } /** * This method returns the current no steal property. * @see set_no_steal_mess() * @return the current no steal properyt of the shop */ int query_no_steal() { return no_steal; } /** * This method returns the current steal difficulty of the shop. * @see set_steal_difficulty_mess() * @return the current steal difficulty message of the shop */ int query_steal_difficulty() { return steal_difficulty; } /** * This method returns the shop lift response handler for the shop. * This allows the shop to respond in someway to someone shop * lifting stuff, like the heavys in Ankh-Morpork. * @returns the shop lift response handler * @see set_shoplift_response_handler() * @see set_no_steal() * @see set_sell_stolen() * @see set_steal_difficulty() */ string query_shoplift_response_handler() { return shoplift_handler; } /** * This method tells us if the shop is really a shop or not. * @return 1 always */ int query_shop() { return 1; } /** * This method returns the items which can potentially be shop lifted * with the passed in string. * @param str the name for the object to attempt to shop lift * @return the array of matching objects * @see query_steal_difficulty() * @see query_shoplift_response_handler() */ object *query_shop_lift_items( string str, object player ) { return match_objects_for_existence( str, ({ query_store_room() }) ); } /* query_shop_lift_items() */ /** * This method turns the objects into real objects (if that is * nessessary, it is not with a normal shop). * @param ob the object to turn into a normal object */ object shoplift_success( object ob ) { return ob; } /* shoplift_success() */ /** * This method sets the shop lift response handler for the shop. * This allows the shop to respond in someway to shop lifting * stuff, like the heavies in Ankh-Morpork. The function * 'handle_shoplift' will be called on the handler when the * shop lift is attempted. It will be passed two arguments * the first is the thief, the second is the room being * shoplifted. This can be set to afucntion pointer * which will be evaluated and passed in two arguemtns * when a shoplift occurs. * @see query_shoplift_response_handler() * @see set_no_steal() * @see set_sell_stolen() * @see set_steal_difficulty() * @param word the new shop lift response handler */ void set_shoplift_response_handler( string word ) { shoplift_handler = word; } /* set_shoplift_response_handler() */ /** * This method sets the minimum value of items that can be sold here. * @see query_min_amount() */ void set_min_amount( int i ) { min_amount = i; } /** * This method sets the maximum number of inventory items this shop will * take before it starts to refuse to purchase items from players and * deleting items from its inventory. * The default for this is defined as MAX_INVENTORY in shop.h */ void set_max_inventory( int i ) { max_inventory = i; } /** * This method tests to see if the shop is actually open. * @return non-zero if the shop is open * @see set_open_condition() * @see query_open_condition() */ int test_open() { if( !check_shopkeeper_open() ) return 0; if( stringp(open_cond) ) return (int)call_other( TO, open_cond ); if( intp(open_cond) ) return open_cond; if( functionp(open_cond) ) return evaluate(open_cond); return (int)call_other( open_cond[0], open_cond[1] ); } /* test_open() */ /** @ignore yes */ void init() { string room; ::init(); if( stringp(our_storeroom) ) { room = our_storeroom; } else if( our_storeroom ) { room = file_name(our_storeroom); } else { tell_room( TO, "Oh dear, we don't seem to have a storeroom.\n"); } add_command("sell", "<indirect:object:me>"); add_command("buy", "<indirect:object:"+room+">"); add_command("list", "[all]"); add_command("list", "<indirect:object:"+room+">", (: do_list_these($1) :)); add_command("browse", "<indirect:object:"+room+">"); add_command("value", "<indirect:object:me>"); } /* init() */ /** * This method returns the maximum value oif an object that can * be sold here. * @see query_min_amount() * @return the maximum amount * @see /handlers/money_handler.c */ int query_max_amount() { return MAX_AMOUNT; } /** * This method returns the minimum value oif an object that can * be sold here. * @see query_max_amount() * @return the minimum amount * @see /handlers/money_handler.c */ int query_min_amount() { return min_amount; } /** * This method returns the value of the object in this shop. * @return the value of the object here * @param thing the thing to value * @param sell are we selling it? * @see query_max_amount() * @see query_min_amount() */ varargs int query_value( object thing, int sell ) { return (int)thing->query_value_at(TO); } /* query_value() */ /** * This returns the amount of money you can sell and object for, * which is less than the amount it will be sold for in the shop later. * @param n the value to scale * @return the sell value */ int scaled_value( int n ) { int profit; profit = 5 + to_int( sqrt( to_float( n / 5 ) ) ); if( profit > MAX_PROFIT ) profit = MAX_PROFIT; return n * ( 100 - profit ) / 100; } /* scaled_value() */ /** * This method does the actual selling. * @param in_obs the objects to sell * @return 1 on success, 0 on failure */ int do_sell(object *in_obs) { int amt, total_amt; string place; object money, *obs, *selling, *cannot, *stolen, storeob, ob; mixed m_array; if( !test_open() ) return 0; obs = filter( in_obs, (: !$1->query_keep() :) ); if( !sizeof(obs) ) { TP->add_failed_mess( TO, "All of $I you previously decided to " "keep.\n", in_obs ); return 0; } in_obs = TP->query_holding() + TP->query_armours(); cannot = filter(obs, (: member_array( $1, $2 ) != -1 :), in_obs ); if( sizeof(cannot) ) { obs -= cannot; tell_object( TP, "You decide not to sell "+ query_multiple_short( cannot, "the")+", because you are wearing " "or holding $V$0=it,them$V$.\n"); } // If there are more items in the storeroom than max_inventory allows // return a failure message. if( objectp(our_storeroom) ) { storeob = our_storeroom; } else { our_storeroom->rabbit_away(); storeob = find_object(our_storeroom); } if( sizeof( INV(storeob) ) > max_inventory ) { TP->add_failed_mess( TO, "Sorry, the shop is full up and isn't " "buying items!\n", ({ }) ); call_out("tidy_inventory", random(20) ); return 0; } if( sizeof(obs) > MAX_OBS ) { write("The shopkeeper can't cope with all those objects.\n"); obs = obs[0..MAX_OBS - 1]; } selling = cannot = stolen = ({ }); place = query_property("place"); if( !place || place == "" ) place = "default"; foreach( ob in obs ) { if( !sell_stolen && ob->query_property("stolen") ) { stolen += ({ ob }); continue; } else { ob->remove_property("stolen"); } if( TO->query_value(ob) > 0 && !ob->do_not_sell() && !TO->do_not_buy(ob) && ( !strict_shop || shop_type == ob->query_property("shop type") ) && ENV(ob) == TP ) { if( ob->move(our_storeroom) ) { if( ob->short() ) cannot += ({ ob }); continue; } amt = (int)TO->query_value(ob); if( !ob->query_property("sale_value") ) amt = scaled_value( amt ); if( amt <= (int)TO->query_max_amount() && amt >= (int)TO->query_min_amount() ) { if( shop_type != ob->query_property("shop type") ) amt = (amt * 90) / 100; total_amt += amt; selling += ({ ob }); ob->being_sold(); } else { if( ob->short() ) cannot += ({ ob }); ob->move(TP); } } else if( ob->short() ) { cannot += ({ ob }); } } if( !sizeof(selling) ) { if( sizeof(cannot) ) { if( stringp(cannot_sell_func) ) call_other( TO, cannot_sell_func, TP, cannot ); else if( functionp(cannot_sell_func) ) evaluate( cannot_sell_func, TP, cannot ); TP->add_failed_mess( TO, "You cannot sell $I.\n", cannot ); } else if( sizeof(stolen) ) { TP->add_failed_mess( TO, "You cannot sell $I because "+ ({"it's", "they're"})[query_group(stolen)]+" stolen!\n", stolen ); } else { TP->add_failed_mess( TO, "You have nothing to sell.\n", ({ }) ); } return 0; } if( TO->cannot_afford(total_amt) ) { selling->move(TP); TP->add_failed_mess( TO, "The shop cannot afford to buy $I from " "you.\n", selling ); return 0; } amount_sold += total_amt; m_array = (mixed)MONEY_H->create_money_array( total_amt, place ); money = clone_object(MONEY_OBJECT); money->set_money_array( m_array ); if( sizeof(cannot) ) { if( stringp(cannot_sell_func) ) call_other( TO, cannot_sell_func, TP, cannot ); else if( functionp(cannot_sell_func) ) evaluate( cannot_sell_func, TP, cannot ); write("You cannot sell "+query_multiple_short(cannot)+".\n"); cannot->move(TP); } if( stringp(sell_func) ) call_other( TO, sell_func, TP, obs ); else if( functionp(sell_func) ) evaluate( sell_func, TP, obs ); do_parse( sell_mess, selling, TP, (string)MONEY_H->money_string(m_array), ""); if( (int)money->move(TP) != MOVE_OK ) { tell_object( TP, "You're too heavily burdened to accept all that " "money, so the shopkeeper puts it on the floor.\n"); money->move(TO); } TO->made_transaction( -total_amt, selling ); return 1; } /* do_sell() */ /** * This method determines whether or not a given object is a creator object * and should be in this shop or not. * @param ob the object to check * @return 1 if the object is not allowed, 0 if it is. */ int creator_object( object ob ) { string path; if( file_name( ob )[0..2] == "/w/" ) return 1; if( ( path = ob->query_property( "virtual name" ) ) && path[0..2] == "/w/" ) return 1; return 0; } /* creator_object() */ /** * This method does the actual buying. * @param obs the objects to buy * @return 1 on success, 0 on failure */ int do_buy( object *obs ) { int i, amt, ob_amt, total_cost; string place; object money, *to_buy, *cannot, *too_much; object *creator_obs; if( !test_open() ) return 0; if( sizeof(obs) > MAX_OBS ) { write("The shopkeeper can't cope with all those objects.\n"); obs = obs[0..MAX_OBS-1]; } creator_obs = filter( obs, (: creator_object( $1 ) :) ); if( sizeof( creator_obs ) && file_name()[0..2] != "/w/" ) { tell_object( TP, "You cannot buy "+ query_multiple_short( creator_obs, "the" )+" because they " "shouldn't be in the game!\n"); obs -= creator_obs; } to_buy = too_much = cannot = ({ }); place = query_property("place"); if( !place || place == "" ) place = "default"; if( !money = present( MONEY_ALIAS, TP ) ) { if( stringp( too_costly_func ) ) call_other( TO, too_costly_func, TP, obs ); else if( functionp(too_costly_func) ) evaluate( too_costly_func, TP, obs ); TP->add_failed_mess( TO, "You have no money.\n", obs ); return 0; } amt = (int)money->query_value_in(place); if( place != "default" ) amt += (int)money->query_value_in("default"); while( i < sizeof(obs) ) { ob_amt = (int)TO->query_value( obs[i], 1 ); if( ob_amt > amt ) { if( obs[i]->short() ) too_much += ({ obs[i] }); obs = delete( obs, i, 1 ); continue; } if( obs[i]->move(TP) ) { if( !sell_large ) { if( obs[i]->short() ) cannot += ({ obs[i] }); i++; continue; } else { obs[i]->move(TO); } } amt -= ob_amt; total_cost += ob_amt; to_buy += ({ obs[i] }); i++; } amount_bought += total_cost; if( sizeof(cannot) ) TP->add_failed_mess( TO, "You cannot pick up $I.\n", cannot ); if( sizeof(too_much) ) { if( stringp(too_costly_func) ) call_other( TO, too_costly_func, TP, cannot ); else if( functionp(too_costly_func) ) evaluate( too_costly_func, TP, cannot ); TP->add_failed_mess( TO, "$I $V$0=costs,cost$V$ too much.\n", too_much ); } if( !sizeof(to_buy) ) return 0; do_buy_things( to_buy, total_cost, TP ); do_shopkeeper_buy(); return 1; } /* do_buy() */ /** @ignore yes */ void do_buy_things( object *obs, int cost, object pl ) { int i, j; string place; object money, change; mixed m_array, p_array; place = query_property("place"); if( !place || place == "" ) place = "default"; if( !money = present( MONEY_ALIAS, pl ) ) { if( stringp(too_costly_func) ) call_other( TO, too_costly_func, TP, obs ); else if( functionp(too_costly_func) ) evaluate( too_costly_func, TO, obs ); TP->add_failed_mess( TO, "You don't have any money.\n", obs ); return 0; } change = clone_object(MONEY_OBJ); m_array = (int)MONEY_H->create_money_array( cost, place ); for( i = 0; i < sizeof(m_array); i += 2 ) { p_array = (mixed)MONEY_H->make_payment( m_array[i], m_array[i + 1], money, place ); 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] ); change->adjust_money( p_array[1] ); } do_parse( buy_mess, obs, pl, (string)MONEY_H->money_string(m_array), ""); if( stringp(buy_func) ) call_other( TO, buy_func, pl, obs ); else if( functionp(buy_func) ) evaluate( buy_func, pl, obs ); if( (int)change->move(pl) != MOVE_OK ) { tell_object( pl, "You are too heavily burdened to accept your " "change, so the shopkeeper puts it on the floor.\n"); change->move(TO); } TO->made_transaction( cost, obs ); } /* do_buy() */ /** * This method lists all the objects in stock. * @return 1 on succes, 0 on failure * @param do_list_these() */ int do_list() { object ob; if( !test_open() ) return 0; if( objectp(our_storeroom) ) { ob = our_storeroom; } else { if( original_storeroom ) { our_storeroom = load_object( original_storeroom ); ob = our_storeroom; } else { add_failed_mess("Please notify a creator: the storeroom for " "this shop cannot load or has gone missing.\n"); return 0; } } if( stringp(list_func) ) call_other( TO, list_func, TP ); else if( functionp(list_func) ) evaluate( list_func, TP ); do_parse( list_mess, ({ TO }), TP, "", shop_list( INV(ob), 0 ) ); do_shopkeeper_list(); return 1; } /* do_list() */ /** * This method lists only the specified objects. * @return 1 on success, 0 on failure * @see do_list() */ int do_list_these( object *obs ) { if( !test_open() ) return 0; do_parse( list_mess, ({ TO }), TP, "", shop_list( obs, 1 ) ); do_shopkeeper_list(); return 1; } /* do_list_these() */ /** * This method is called when the player is browseing stuff. * @param obs the objects to browse * @return 1 on success, 0 on failure */ int do_browse( object *obs ) { int value; string place; object ob; if( !test_open() ) return 0; place = query_property("place"); if( !place || place == "" ) place = "default"; if( stringp(browse_func) ) call_other( TO, browse_func, TP, obs ); else if( functionp(browse_func) ) evaluate( browse_func, TP, obs ); foreach( ob in obs ) { value = (int)TO->query_value( ob, 1 ); do_parse( browse_mess, ({ ob }), TP, (string)MONEY_H->money_value_string(value, place), (string)ob->long() ); } do_shopkeeper_browse(); return 1; } /* do_browse() */ /** * This method is called when the player is valueing stuff. * @param obs the objects to value * @return 1 on success, 0 on failure */ int do_value(object *obs) { int val; int total; string place; object ob; if( !test_open() ) return 0; place = query_property("place"); if( !place || place == "" ) place = "default"; foreach( ob in obs ) { if( ob->do_not_sell() || TO->do_not_buy(ob) || ENV(ob) != TP || ( strict_shop && ( shop_type != (string)ob->query_property("shop type") ) ) ) { val = 0; } else { val = (int)TO->query_value(ob); if( shop_type != (string)ob->query_property("shop type") ) val = (val * 90) / 100; } val = scaled_value(val); total += val; if( val > (int)TO->query_max_amount() ) { do_parse( too_costly_mess, ({ ob }), TP, "", (string)ob->do_not_sell() ); } else if( val < (int)TO->query_min_amount() ) { do_parse( not_worthy_mess, ({ ob }), TP, "", (string)ob->do_not_sell() ); } else { do_parse( value_mess, ({ ob }), TP, (string)MONEY_H->money_value_string( val, place ), (string)ob->do_not_sell() ); if( stringp(value_func) ) { call_other( TO, value_func, TP, obs, MONEY_H->money_string( MONEY_H->create_money_array( val, place ) ) ); } else if( functionp(value_func) ) { evaluate( value_func, TP, obs, MONEY_H->money_string( MONEY_H->create_money_array( val, place ) ) ); } } } if( sizeof(obs) > 1 ) write("This gives you a total value of "+ MONEY_H->money_value_string(total, place)+".\n"); return 1; } /* do_value() */ /** * This method creates the list for the shop. * @param arr the array of objects to list * @param detail display them in detail? * @return the string list */ string shop_list( mixed arr, int detail ) { int j, value; string s, mon, place, *shorts; object *list; mapping inv, costs; mixed ind, item; list = ( pointerp(arr) ? arr : INV(TO) ); // Only keep track of things with shorts. inv = ([ ]); foreach( item in list ) { s = (string)item->short(); if( !s || !TO->query_value( item, 1 ) ) continue; if( !stringp(s) ) s = "get a creator for this one!"; if( inv[s] ) inv[s] += ({ item }); else inv[s] = ({ item }); } // Okay, print it. s = ""; shorts = keys(inv); if( !sizeof(shorts) ) { return ( detail ? "The shop is all out of what you wanted." : "The shop is totally out of stock." )+"\n"; } s = "You find on offer:\n"; place = query_property("place"); if( !place || place == "" ) place = "default"; foreach( item in shorts ) { ind = inv[item]; switch( sizeof(ind) ) { case 1: s += "Our very last " + item; break; case 2..5 : s += CAP( query_num( sizeof(ind), 0 )+" "+ (string)ind[0]->query_plural() ); break; default: s += ( detail ? CAP( query_num( sizeof(ind), 0 ) ) : "A large selection of" )+" "+(string)ind[0]->query_plural(); } if( detail ) { costs = ([ ]); for( j = 0; j < sizeof(ind); j++ ) { value = (int)TO->query_value( ind[j], 1 ); mon = (string)MONEY_H->money_value_string( value, place ); if( !costs[mon] ) costs[mon] = ({""+(j + 1)}); else costs[mon] += ({""+(j + 1)}); } if( sizeof(costs) == 1 ) { s += " for "+keys(costs)[0]; if( sizeof(values(costs)[0]) > 1 ) { s += " each.\n"; } else { s += ".\n"; } } else { s += ":-\n"; foreach( mon in keys(costs) ) s += " [#"+implode(costs[mon], ",")+"] for "+mon+".\n"; } } else { s += ".\n"; } } return s; } /* shop_list() */ /** * This method sets the current store room associated with * the shop. This is important! A shop needs a storeroom. * @param ob the storeroom to set * @example * set_store_room(PATH+"store_room"); */ void set_store_room( mixed ob ) { if( stringp(ob) ) { original_storeroom = ob; our_storeroom = find_object(ob); if( !our_storeroom ) our_storeroom = load_object(ob); } else our_storeroom = ob; } /* set_store_room() */ /** @ignore yes */ void guards( object tp ) { object ob; if( ENV(tp) != TO && ENV(tp) != our_storeroom ) return; while( !random(6) ) { ob = create_mercenary(0); ob->move( ENV(tp) ); ob->attack_ob(tp); } } /* guards() */ /** * This method returns the storeroom associated with the shop. * @see set_store_room() * @return the current store room */ object query_store_room() { return our_storeroom; } /** * This method is the major message processing function for * the buye messages, sell messages etc. It handles calling * the functions and setting the results back onto the * player. * @param arr the value of the message * @param ob the objects to process * @param money the money string * @param extra the extra string */ void do_parse( mixed arr, object *ob, object client, string money, string extra ) { if( stringp(arr) ) { TP->show_message("$P$List$P$"+ TP->convert_message( replace( arr, ({ "$ob$", query_multiple_short(ob), "$client$", TP->short(), "$money$", money, "$extra$", extra }) ) ) ); TP->add_succeeded_mess( TO, "", ob ); } else if( functionp(arr) ) { evaluate( arr, ob, client, money, extra ); } else { TP->show_message("$P$List$P$"+ TP->convert_message( replace( arr[0], ({ "$ob$", query_multiple_short(ob), "$client$", TP->short(), "$money$", money, "$extra$", extra }) ) ) ); TP->add_succeeded_mess( TO, ({ "", replace(arr[1], ({ "$ob$", "$I", "$client$", "$N", "$money$", money, "$extra$", extra }) ) }), ob ); } } /* do_parse() */ /** @ignore yes */ string shop_parse( string str, mixed ob, object client, string money, string extra, string which ) { if( sizeof(ob) ) str = replace( str, "$ob$", query_multiple_short( ob, which ) ); else str = replace( str, "$ob$", call_other( ob, which +"_short" ) ); if( client ) str = replace( str, "$client$", client->the_short() ); return replace( str, ({ "$money$", money, "$extra$", extra }) ); } /* shop_parse() */ /** * This method adds a shop with shich we will exchange inventories * of certain types. So the main shop can sell off its swords and * stuff to the sword shop. * @param shop the other shop */ void add_other_shop(mixed shop) { // Should give a nice string telling us the other room. other_shops += ({shop}); } /* add_other_shop() */ /** * This method returns the type of the shop. This conttrols what sort * of merchandise the shop will buy and sell. * <p>The types of allowed shops are: * <ul> * <li> jewelery * <li> armoury * <li> clothes * <li> magic * <li> none set (ie: 0, general type) * </ul> * @return the shop type * @see set_shop_type() * @see set_strict_shop() */ string query_shop_type() { return shop_type; } /** * This method sets the type of the shop. This controls what sort of * mechandise the shop will buy and sell. * <p>The types of allowed shops are: * <ul> * <li> jewelery * <li> armoury * <li> clothes * <li> magic * <li> none set (ie: 0, general type) * </ul> * @see query_shop_type() * @see set_strict_shop() * @param ty the type of the shop */ void set_shop_type( string ty ) { shop_type = ty; } /** * This method sets the strictness of the shop, if the shop is strict * it will not deal in items of other types at all. * @param i the new strict value * @see set_shop_type() * @see query_strict_shop() */ void set_strict_shop( int i ) { strict_shop = i; } /** * This method returns the structness of the shop, if the shop is strict * it will not deal in items of other types at all. * @return the current strict value * @see set_shop_type() * @see set_strict_shop() */ int query_strict_shop() { return strict_shop; } /** * This method creates the sales representative which is sent off * to exchange goods with other shops. * @return the sales representative * @see add_other_shop() * @see send_out_reps() */ object create_rep() { object ob; ob = clone_object(NPC_OBJ); ob->set_name("rep"); ob->set_short("sales rep"); ob->add_adjective("sales"); ob->set_long("This is tall strong looking sales rep. He " "stares at you with bright piercing eyes.\n"); ob->add_alias("Sales rep alias"); ob->basic_setup("human", 60, 1 ); ob->adjust_bon_str(15); ob->get_item("dagger", 100 ); ob->get_item("cloth robe", 100 ); ob->init_equip(); ob->add_property("rep type", shop_type ); INV(ob)->add_property("mine", 1 ); return ob; } /* create_rep() */ // Send out the reps. /** * This method checks to see if there are any other shops * associated with ours and sends out representatives to them * to exchange goods. * @see create_rep() * @see add_other_shop() */ void send_out_reps() { int i; object ob; for( i = 0; i < sizeof(other_shops); i++ ) { ob = (object)TO->create_rep(); ob->add_property("goto destination", other_shops[i]); ob->add_property("goto property", "shop"); ob->move( TO, "$N stride$s determinedly into the room."); ob->add_triggered_action("froggy", "goto_destination", file_name(TO), "rep_made_it"); } } /* send_out_reps() */ /** * This method is called onces the representative * reaches its destination. * @param bing we mkde it ok */ void rep_made_it( int bing ) { object *obs, rep; int i, cost; if( !bing ) { PO->init_command("'Oh no! I am utterly lost!"); PO->init_command("sigh"); call_out("set_up_return", 5, PO ); return ; } rep = present("Sales rep alias", (object)PO->query_current_room() ); obs = (PO->query_current_room())->query_stock(shop_type) || ({ }); if( !sizeof(obs) ) { TO->none_to_sell(); call_out("set_up_return", 5, PO ); return ; } for( i = 0; i < sizeof(obs); i++ ) { if( obs[i] ) { cost += (int)TO->query_value( obs[ i ], 1 ) * 2 / 3; } } call_out("do_rep_buy", 5, ({ PO, obs, cost }) ); cost += (int)TO->query_value(obs[i], 1) * 2 / 3; call_out("do_rep_buy", 5, ({ PO, obs, cost }) ); PO->adjust_value(cost); } /* rep_made_it() */ /** * This method creates a mercenaries to wander along with the * sales rep to get the stuff to the destination. * @param rep the representative to protect * @return the new mercenary */ object create_mercenary( object rep ) { object ob; string nam; if( rep ) { nam = implode(rep->query_adjectives(), " ")+" "+rep->query_name(); } ob = clone_object(NPC_OBJ); ob->set_name("mercenary"); ob->add_alias("troll"); ob->add_adjective("troll"); ob->set_short("troll mercenary"); ob->set_main_plural("troll mercenaries"); ob->basic_setup("troll", 200 + random( 200 ), 1 ); ob->set_long("This is a large, hulking troll. He looks " "quite competent and capable of mashing you with or " "without a weapon.\n"); if( rep ) { ob->move(ENV(rep)); ob->do_command("follow "+nam); ob->do_command("protect "+nam); ob->add_property("merchant", rep ); } else { ob->get_item("spiked club", 100 ); } ob->add_skill_level("fighting", 300 + random(100) ); ob->set_natural(1); ob->init_equip(); ob->set_join_fights("The troll mercenary yells something " "incomprehensible.\n"); ob->set_join_fight_type(0); // So they only beat up players. return ob; } /* create_mercenry() */ /* The shop types are: * jewelery * armoury * clothes * magic * none set (ie: 0, general type) */ /** * This method returns alkl the stock in the shop of various * types. The types are: * <ul> * <li> jewelery * <li> armoury * <li> clothes * <li> magic * <li> none set (ie: 0, general type) * </ul> * @param type the type of stock to return * @return the array of objects of the type */ object *query_stock(string type) { mapping blue; blue = (mapping)our_storeroom->query_shop_type_mapping(); if( !blue[type] ) return ({ }); return blue[type]; } /* query_stock() */ /** * This method is called when the rep tries to buy stuff. * @param bing the stuff to buy */ void do_rep_buy( mixed bing ) { object rep, *obs; int cost; rep = bing[0]; obs = bing[1]; cost = bing[2]; rep->adjust_money(cost, "brass"); rep->query_current_room()->do_buy(obs, cost, rep); obs->move( present("Sales rep alias", rep->query_current_room() ) ); call_out("set_up_return", 5, rep); } /* do_rep_buy() */ /** * This method sets up the rep to return home. * @param rep the rep to return home */ void set_up_return(object rep) { rep->add_property("goto destination", file_name(TO) ); rep->add_triggered_action("froggy", "goto_destination", TO, "rep_came_back"); } /* set_up_return() */ /** * This method is called when the rep gets back home. */ void rep_came_back() { object *obs, *obs2, rep, ob; obs = PO->find_inv_match("all", PO ); obs2 = ({ }); foreach( ob in obs ) { if( ob->query_property("mine") || ob->query_property("money") ) continue; ob->move(our_storeroom); obs2 += ({ ob }); } if( sizeof(obs2) ) { tell_room( TO, PO->short()+" puts "+ query_multiple_short(obs2)+" into the stock.\n"); } rep = present("Sales rep alias", (object)PO->query_current_room() ); obs = INV(TO); obs2 = ({ }); foreach( ob in obs ) { if( (object)ob->query_property("merchant") == rep ) obs2 += ({ ob }); } if( sizeof(obs2) ) tell_room( TO, query_multiple_short( obs2 + ({ PO }), "the")+" go " "away.\n"); else tell_room( TO, PO->short()+" goes away.\n"); obs2->dest_me(); PO->dest_me(); } /* rep_came_back() */ // Used to create guards to protect the shop. /** * This method is yused to handle guards to protect the shop. * @param tp the object which is shop lifting * @see event_shoplift() * @see set_shoplift_handler() */ void summon_guards( object tp ) { object ob; int i; if( ENV(tp) != TO ) return; if( !ob = ENV(tp)->create_mercenary(0) ) return; ob->move( TO, "$N charge$s in to protect the shop!"); ob->attack_ob(tp); for( i = 0; i < random(5); i++ ) { ob = create_mercenary(0); ob->move( TO, "$N charge$s in to protect the shop!"); ob->attack_ob(tp); } } /* summon_guards() */ /** * This method is called when a shop lift is done on the shop. * @param command_ob the command object * @param thief the theif doing the shop lifting * @param victim the victim of the shoplifiting, us I guess :) * @see event_shoplift() * @see set_shoplift_handler() */ void event_shoplift( object command_ob, object thief, object victim ) { if( stringp(shoplift_handler) ) { if( shoplift_handler != "none" ) theft_handler->handle_shoplift( thief, victim ); } else if( functionp(shoplift_handler) ) evaluate( shoplift_handler, thief, victim ); else THEFT_H->handle_shoplift( thief, victim ); } /* event_shoplift() */ /** * This method is used by the shop to tidy up its inventory. * It does this by desting objects at random until it has * reduced the inventory to 3/4 of its maximum. */ void tidy_inventory() { object storeob; object *inventory; int i, inv_to_leave; int count; inv_to_leave = max_inventory - ( max_inventory / 4 ); if( objectp(our_storeroom) ) storeob = our_storeroom; else { our_storeroom->rabbit_away(); storeob = find_object(our_storeroom); } if( sizeof( inventory = INV(storeob) ) < inv_to_leave ) return; while( sizeof(inventory) > inv_to_leave ) { i = random( sizeof(inventory) ); if( inventory[i] ) inventory[i]->dest_me(); // safety code. if( count++ > 500 ) break; inventory = INV(storeob); } } /* tidy_inventory() */ /** @ignore yes */ void dest_me() { if( our_storeroom ) our_storeroom->dest_me(); ::dest_me(); } /* dest_me() */ /** @ignore yes */ int query_keep_room_loaded() { return 1; } /** @ignore yes */ mixed stats() { return ::stats()+({ ({"total sold", amount_sold }), ({"total bought", amount_bought }), ({"shop type", shop_type }), ({"shoplift handler", shoplift_handler }), ({"strict shop", strict_shop }) }); } /* stats() */