inherit "/std/room"; /* Reworked by Hamlet July/August 1995. */ /* New features: 1) DOCUMENTATION! 2) A more sensical scaled_value() (makes more sense to me at least) 3) Ability to force-set the price the shop will offer for an item; set_resale_value(), adjust_resale_value(), query_resale_value(), prevent_resale(), and allow_resale() all added to /std/basic/misc.c (so, /std/object). 4) Stolen items are worth less now. % modifier and set_stolen_modifier() to alter that modifier. Also set_stolen_modifier(), query_stolen_modifier(), and no_sell_if_stolen() in /std/basic/misc.c 5) A shop can be sell-only. The commands "value" and "sell" are turned off for that. Put sell_only() in setup(); 6) You can specify a special function in your shop to be run ever time something is sold or bought. call is set_sell_func("func") and set_buy_func("func"); func should look like: void func(object *stuff); Yes, this means that stuff is an ARRAY of items being bought or sold. */ // Edited 11 SEP 94 by Timion and Taniwha to fix the sell item bug. /* * Original made who knows when. * Modified by bil to make the list a lot nicer. * Modified by Pinkfish to give shops types and make them send out * reps to sell/buy things from other shops. */ /* I am not sure whose code is whose any more. This *is* an extensive rewrite, and a lot of it is now mine. Credits stay, though, since I don't know which is which. - Hamlet */ /* Note: rewrite is not done. A lot of the code is original still. Don't expect that to be true for long (muhahaha) */ #include "money.h" #include "move_failures.h" #include "shop.h" #include "armoury.h" mixed our_storeroom; mixed buy_mess, sell_mess, list_mess, value_mess, browse_mess, open_cond, *other_shops; int amount_sold, amount_bought; string shop_type; int stolen_modifier; /* This will be a percent value */ int only_sell = 0; string sell_func, buy_func; string query_name(object ob); string query_short(object ob); int check_inv(object ob, string str); string shop_list(mixed arr, int detail); void do_buy(object *ob, int cost, object pl); string shop_parse(string str, mixed ob, object client, string money, string extra); void do_parse(mixed arr, mixed ob, object client, string money, string extra); 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 = "The $ob$ is valued at $money$.\n"; browse_mess = "The $ob$ costs $money$, it looks like:\n$extra$"; open_cond = 1; other_shops = ({ }); shop_type = "general"; stolen_modifier = 33; /* 33% of normal value */ ::create(); } /* These make spiffy messages for various actions. */ void set_sell_message(mixed str) { sell_mess = str; } void set_buy_message(mixed str) { buy_mess = str; } void set_value_message(mixed str) { value_mess = str; } void set_browse_message(mixed str) { browse_mess = str; } void set_list_message(mixed str) { list_mess = str; } void set_open_condition(mixed str) { open_cond = str; } mixed query_sell_mess() { return sell_mess; } mixed query_list_mess() { return list_mess; } mixed query_value_mess() { return value_mess; } mixed query_buy_mess() { return buy_mess; } mixed query_browse_mess() { return browse_mess; } /* These initialize sell_func and buy_func, which are special functions that can be set to run when something is bought or sold. */ void set_sell_func(string str) { sell_func = str; } void set_buy_func(string str) { buy_func = str; } mixed query_open_condition() { return open_cond; } /* This is whether the shop is open or not. open_cond can be an int (true/false). Or a string, in which case it 'points' to a function in the current object to run. OR, it can be an array. ({ "object/name", "function_name" }) where it runs "object/name"->function_name() to determine whether the shop is open. */ int test_open() { if (stringp(open_cond)) return (int)call_other(this_object(), open_cond); if (intp(open_cond)) return open_cond; return (int)call_other(open_cond[0], open_cond[1]); } int sell_only() { only_sell = 1; return 1; } void init() { ::init(); add_action("sell", "sell"); add_action("buy", "buy"); add_action("list", "list"); add_action("browse", "browse"); add_action("value", "value"); } /* This determines how much the shopkeeper will offer for an item, according to its value. It only gets called if set_no_resell() has not been called and resale_value has not been hand-set to something. */ int scaled_value(int n) { int *prates; int i, tot; tot = 0; prates = PAY_RATES; for(i=0;( (i<sizeof(prates)) && (n > prates[i]) );i+=2); if(i>0) i-=2;/* prates[i] is now the rate-increment directly below the value. */ tot = (n - prates[i]) * ((prates[i+2] / prates[i+3]) - (prates[i] / prates[i+1])); tot /= (prates[i+2] - prates[i]); tot += (prates[i] / prates[i+1]); /* For those curious, we just defined a line segment. Basically, we used y - y1 = m(x - x1). And found the y value for x = n. Read shop.h for better explanation. */ return tot; } int set_stolen_modifier(int amt) { if(amt > 100) amt = 100; if(amt < 0) amt = 0; stolen_modifier = amt; return amt; } /* This checks the player to make sure they have the item. I *think* this is a paranoia check, but I'll leave it. */ int check_inv(object thing, string str) { int i; object *inv = find_match(str,this_player()); for(i =0; i < sizeof(inv); i++) { if(inv[i] == thing) return(1); } return(0); } int sell(string str) { object *obs, *selling, *cannot; mixed *m_array; int i, j, no, amt, value, total_amt; string s; if (!test_open()) return 0; if(only_sell) { tell_object(this_player(),"This shop does not buy merchandise.\n"); return 1; } if (!str || str == "") { notify_fail("Usage: sell <objects>\n"); return 0; } obs = find_match(str, this_player()); if (!sizeof(obs)) { notify_fail("Nothing to sell.\n"); 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 = ({ }); for (i=0;i<sizeof(obs);i++) { if( (obs[i]->query_value() > 0) && !obs[i]->do_not_sell() && (obs[i]->query_resale_value() != -1) && ((obs[i]->query_stolen_modifier() != -1) || !obs[i]->query_property("stolen")) && !obs[i]->query_in_use()) { /* O.K. this SHOULD check to see if the item IS in the inventory before we move it */ if(check_inv(obs[i],str)) { if (obs[i]->move(our_storeroom)) { if (obs[i]->short()) cannot += ({ obs[i] }); continue; } /* the call other (buried) in the below is so that we can update /std/shop and have all the shops prices fall in line consistently */ amt = obs[i]->query_resale_value(); if(!amt) { amt = (int)"/std/shop"->scaled_value((int)obs[i]->query_value()); if(amt > MAX_AMOUNT) amt = MAX_AMOUNT; } /* Let's not let something sell back for more than it was bought for */ if(amt > obs[i]->query_value()) amt = obs[i]->query_value(); /* "hot" goods lose value! */ if(obs[i]->query_property("stolen")) { if(obs[i]->query_stolen_modifier() == 0) amt = amt * stolen_modifier / 100; else amt = amt * obs[i]->query_stolen_modifier() / 100; } total_amt += amt; selling += ({ obs[i] }); obs[i]->being_sold(); } else if (obs[i]->short()) cannot += ({ obs[i] }); /* end of test if exists, not that this DOESN'T do the cannot that it should */ } else if (obs[i]->short()) cannot += ({ obs[i] }); } if (!sizeof(selling)) { if (sizeof(cannot)) notify_fail("You cannot sell "+query_multiple_short(cannot)+", maybe you are holding or wearing it, or just don't have one.\n"); else notify_fail("Nothing to sell.\n"); return 0; } amount_sold += total_amt; m_array = (mixed *)MONEY_HAND->create_money_array(total_amt); this_player()->adjust_money(m_array); if (sizeof(cannot)) write("You cannot sell "+query_multiple_short(cannot)+", maybe you are wearing or holding it, or just don't have one.\n"); do_parse(sell_mess, selling, this_player(), (string)MONEY_HAND->money_string(m_array), ""); if(sell_func) call_other(this_object(),sell_func,selling); return 1; } int buy(string str) { int i, amt, ob_amt, total_cost; object *obs, *to_buy, *cannot, *too_much; string s; if (!test_open()) return 0; if (!str || str == "") { notify_fail("Usage: buy <objects>\n"); return 0; } obs = find_match(str, our_storeroom); if (!sizeof(obs)) { notify_fail("Cannot find "+str+".\n"); return 0; } if(sizeof(obs) > MAX_OBS) { write("The shopkeeper can't cope with all those objects.\n"); obs = obs[0..MAX_OBS-1]; } to_buy = too_much = cannot = ({ }); amt = (int)this_player()->query_value(); while (i<sizeof(obs)) { if ((ob_amt = (int)obs[i]->query_value()) > amt) { if (obs[i]->short()) too_much += ({ obs[i] }); obs = delete(obs, i, 1); continue; } if (obs[i]->move(this_player())) { if (obs[i]->short()) cannot += ({ obs[i] }); i++; continue; } obs[i]->move(our_storeroom); amt -= ob_amt; total_cost += ob_amt; to_buy += ({ obs[i] }); i++; } amount_bought += total_cost; s = ""; if (sizeof(cannot)) s += "You cannot pick up "+query_multiple_short(cannot)+".\n"; if (sizeof(too_much)) s += capitalize(query_multiple_short(too_much))+" costs too much.\n"; if(!sizeof(to_buy)) { if(s != "") notify_fail(s); else notify_fail("Nothing to buy.\n"); return 0; } else { write(s); } do_buy(to_buy, total_cost, this_player()); if(buy_func) call_other(this_object(),buy_func,to_buy); return 1; } void do_buy(object *obs, int cost, object pl) { int i; mixed fish; for (i=0;i<sizeof(obs);i++) obs[i]->move(pl); pl->pay_money(fish = (int)MONEY_HAND->create_money_array(cost)); do_parse(buy_mess, obs, pl, (string)MONEY_HAND->money_string(fish), ""); } int list(string str) { object ob; if (!test_open()) return 0; if (!str || str == "" || str == "all") { if (objectp(our_storeroom)) ob = our_storeroom; else ob = find_object(our_storeroom); do_parse(list_mess, this_object(), this_player(), "", shop_list(all_inventory(ob), 0)); return 1; } do_parse(list_mess, this_object(), this_player(), "", shop_list(find_match(str, our_storeroom), 1)); return 1; } int browse(string str) { object *obs; int i; if (!test_open()) return 0; if (!str || str == "") { notify_fail("Usage: browse <objects>\n"); return 0; } obs = find_match(str, our_storeroom); if (!sizeof(obs)) { notify_fail("Cannot find "+str+".\n"); return 0; } for (i=0;i<sizeof(obs);i++) do_parse(browse_mess, obs[i], this_player(), (string)MONEY_HAND->money_value_string(obs[i]->query_value()), (string)obs[i]->long()); /* write("You look at "+obs[i]->short()+" it costs "+ MONEY_HAND->money_string(obs[i]->query_money_array())+"\n"+ obs[i]->long()); */ return 1; } int value(string str) { object *obs; int i, val; if (!test_open()) return 0; if(only_sell) { tell_object(this_player(),"This shop cannot appraise your goods.\n"); return 1; } if (!str || str =="") { notify_fail("Usage: value <object>\n"); return 0; } obs = find_match(str, this_player()); if (!sizeof(obs)) { notify_fail("Cannot find "+str+".\n"); return 0; } for (i=0;i<sizeof(obs);i++) { /* the call other is so that we can change the PAY_RATES array, and then just update /std/shop to immediately and consistently effect all shops */ val = obs[i]->query_resale_value(); if(!val) { val = (int)"/std/shop"->scaled_value((int)obs[i]->query_value()); if (val > MAX_AMOUNT) val = MAX_AMOUNT; } if(val > obs[i]->query_value()) val = obs[i]->query_value(); /* "hot" goods lose value! */ if(obs[i]->query_property("stolen")) { if(obs[i]->query_stolen_modifier() == 0) val = val * stolen_modifier / 100; else val = val * obs[i]->query_stolen_modifier() / 100; } do_parse(value_mess, obs[i], this_player(), (string)MONEY_HAND->money_value_string(val), (string)(obs[i]->do_not_sell() || (obs[i]->query_resale_value() == -1)) ); } return 1; } string shop_list(mixed arr, int detail) { mapping inv, costs; object *list; string s, mon, *shorts, *vals; int i, j; mixed ind; if (pointerp(arr)) list = arr; else list = all_inventory(this_object()); /* only keep track of things with shorts ;) */ inv = ([ ]); for (i=0; i<sizeof(list); i++) { s = (string)list[i]->short(); if (!s || !list[i]->query_value()) continue; if(!stringp(s)) s = "get a creator for this one!"; if (inv[s]) inv[s] += ({ list[i] }); else inv[s] = ({ list[i] }); } /* ok print it */ s = ""; shorts = m_indices(inv); if(!sizeof(shorts)) { if(detail) return "The shop is all out of what you wanted.\n"; else return "The shop is totally out of stock.\n"; } s = "You find on offer:\n"; for (i=0; i<sizeof(shorts); i++) { ind = inv[shorts[i]]; switch(sizeof(ind)) { case 1: s += "Our very last " + shorts[i]; break; case 2..5: s += capitalize(query_num(sizeof(ind), 0) + " " + (string)ind[0]->query_plural()); break; default: if(detail) s += capitalize(query_num(sizeof(ind), 0) + " " + (string)ind[0]->query_plural()); else s += "A large selection of " + (string)ind[0]->query_plural(); } if(detail) { costs = ([ ]); for(j=0;j<sizeof(ind);j++) { mon=(string)MONEY_HAND->money_value_string((int)ind[j]->query_value()); if(!costs[mon]) costs[mon] = ({ "" + (j + 1) }); else costs[mon] += ({ "" + (j + 1) }); } if(m_sizeof(costs) == 1) { s += " for " + m_indices(costs)[0]; if(sizeof(m_values(costs)[0]) > 1) s += " each.\n"; else s += ".\n"; } else { s += ":-\n"; vals = m_indices(costs); for(j=0;j<sizeof(vals);j++) s += " [#" + implode(costs[vals[j]], ",") + "] for " + vals[j] + ".\n"; } } else { s += ".\n"; } } return s; } void set_store_room(mixed ob) { if (stringp(ob)) { our_storeroom = find_object(ob); if (!our_storeroom) call_other(ob, "??"); our_storeroom = find_object(ob); } our_storeroom = ob; our_storeroom->add_property("no_clean_up",1); } object query_store_room() { return our_storeroom; } void do_parse(mixed arr, mixed ob, object client, string money, string extra) { if (stringp(arr)) write(shop_parse(arr, ob, client, money, extra)); else { write(shop_parse(arr[0], ob, client, money, extra)); say(shop_parse(arr[1], ob, client, money, extra)); } } string shop_parse(string str, mixed ob, object client, string money, string extra) { string s1, s2, s3, rest; rest = ""; while(sscanf(str,"%s$%s$%s", s1, s2, s3) == 3) switch (s2) { case "ob" : if (pointerp(ob)) str = s1+query_multiple_short(ob)+s3; else str = s1+ob->short()+s3; break; case "client" : str = s1+client->query_cap_name()+s3; break; case "extra" : str = s1+extra+s3; break; case "money" : str = s1+money+s3; break; default : rest = s1+"$"+s2+"$"; str = s3; break; } return rest+str; } /* * The shop types are: * Jewelry * Armory * Magic * General */ object *query_stock(string type) { mixed *obs; int i; obs = unique_array(all_inventory(our_storeroom), "query_shop_type"); for (i=0;i<sizeof(obs);i++) if ((string)obs[i][0]->query_shop_type() == type) return obs[i]; return ({ }); } mixed *stats() { return ::stats() + ({ ({ "total sold", amount_sold }), ({ "total bought", amount_bought }), }); } string query_shop_type() { return shop_type; } void set_shop_type(string ty) { shop_type = ty; } /* This stuff is for 'sales reps' that run from shop to shop buying and selling. I can't imagine the point. Leaving it in case someone goes crazy and actually wants it. */ /* Other shop handling code */ /* int add_other_shop(mixed shop) { mixed *co_ord; if (pointerp(shop)) { *//* Given us a co-ordinate *//* other_shops += ({ shop }); return 1; } co_ord = (mixed *)shop->query_co_ord(); if (!pointerp(co_ord)) return 0; other_shops += ({ co_ord }); } */ /* Ok, now, sales rep creation... Creates a rep */ /* object create_rep() { object ob; ob = clone_object("/obj/monster"); ob->set_name("rep"); ob->set_short("sales rep"); ob->add_adjective("sales"); ob->set_long("A tall strong looking sales rep. He stares at you with " "bright pierceing eyes.\n"); ob->set_guild("fighter"); ob->set_race("human"); ob->adjust_bon_str(15); *//* Strong so they can carry all the junk *//* ob->set_level(300); *//* Don't want em killed. They make a lot of money :) *//* ARMOURY->request_weapon("bastard", 100)->move(ob); ARMOURY->request_weapon("platemail", 100)->move(ob); ARMOURY->request_weapon("med_shield", 100)->move(ob); ob->init_equip(); ob->add_property("rep type", shop_type); } */ /* Send them out! Watch them run! */ /* void send_out_reps() { int i; object ob; for (i=0;i<sizeof(other_shops);i++) { ob = (object)this_object()->create_rep(); ob->add_property("goto co-ordinate", other_shops[i]); ob->add_property("goto property", "shop"); ob->move(this_object()); ob->add_triggered_action("froggy", "goto_co_ord", this_object(), "rep_made_it"); } } void rep_made_it(int bing) { object *obs; int i, cost; if (!bing) { previous_object()->init_command("'Oh no! I am utterly lost"); previous_object()->init_command("sigh"); call_out("set_up_return", 5, previous_object()); return ; } obs = (object *)environment(previous_object())->query_stock(shop_type); if (!sizeof(obs)) { this_object()->none_to_sell(); call_out("set_up_return", 5, previous_object()); return ; } for (i=0;i<sizeof(obs);i++) cost += (int)obs[i]->query_value()*2/3; call_out("do_rep_buy", 5, ({ previous_object(), obs, cost })); previous_object()->adjust_value(cost); } void do_rep_buy(mixed *bing) { object rep, *obs; int i, cost; rep = bing[0]; obs = bing[1]; cost = bing[2]; do_buy(obs, cost, rep); call_out("set_up_return", 5, rep); } void set_up_return(object rep) { rep->add_property("goto co-ordinate", this_object()->query_co_ord()); rep->add_triggered_action("froggy", "goto_co_ord", this_object(), "rep_came_back"); } int rep_came_back() { int i; object *obs; obs = all_inventory(previous_object()); for (i=0;i<sizeof(obs);i++) obs[i]->move(our_storeroom); previous_object()->dest_me(); } */