/* Do not remove the headers from this file! see /USAGE for more info. */ /* $Id: m_vendor.c,v 1.5 1998/03/05 01:54:30 monica Exp $ */ /* Vendor module * * Zifnab wrote it at some point the first time but didn't put his name in here * Darklord went nuts and redid it 15-Feb-97 * Bunch of changes and additions 25-Apr-97 by Vette * Mostly rewritten 6-Feb-98 by MonicaS */ //:MODULE //m_vendor is used to create vendor objects that buy and sell stuff //they work as traditional shopkeepers and also bartenders //See: /domains/std/shopkeeper.c /* TODO compatibility of buying and selling vehicles problem lies in ob->move(this_object()), and vehicles are too large. */ // Shall the vendor sell objects which were in the store-room befor the start? // If you define this vendors may not share a store-room. #ifndef VENDOR_SHARE_STORAGE #define SELL_OLD_OBJECTS #endif // the minimal value of sold items (for objects with query_value() == 0) #ifdef VENDOR_MIN_VALUE #define MIN_VALUE VENDOR_MIN_VALUE #else #define MIN_VALUE 70 #endif // Value of what the objects value is multiplied by to achieve its cost #ifdef VENDOR_COST_MULT #define COST_MULT VENDOR_COST_MULT #else #define COST_MULT 1.5 #endif #include <move.h> class item { string short; string long; string file; int value; string array ids; int amount; object array objects; } string short(); string query_subjective(); int check_uniqueness(object ob); private mixed for_sale; private mixed will_buy; private mixed currency_type = "gold"; private mapping stored_items = ([ ]); private string unique_inv = ""; private int all_unique = 0; private int max_item_number = 0; private float cost_mult = COST_MULT; //:FUNCTION set_cost_multiplicator float set_cost_multiplicator(float m) { cost_mult = m; } //:FUNCTION selling_cost //multiply the objects value with cost_mult to get the cost for selling float selling_cost(float cost) { if (cost < MIN_VALUE) return MIN_VALUE * cost_mult; else return cost * cost_mult; } //:FUNCTION buying_cost //override if you want a different way to determine cost float buying_cost(float cost) { return cost; } private class item init_item(object ob) { class item item; item = new(class item, short: ob->short(), long: ob->long(), ids: ob->query_id() + ({ ob->short(), ob->plural_short() }), value: ob->query_value(), amount: 1); if (item->long[<1] != '\n') item->long += "\n"; return item; } //:FUNCTION add_sell //enables you to add items to the vendors stored_item's mapping void add_sell(string file, int amt) { object ob; class item item; if(!amt) amt = 1; if(file) { if (ob = new (file)) { item = init_item(ob); item->amount = amt; item->file = base_name(ob); stored_items[++max_item_number] = item; destruct(ob); } else error("No such item: " + file + "\n"); } } //:FUNCTION set_sell // with a mapping you can set many items into the vendor's to sell list void set_sell(mapping items) { string item; int amt; foreach (item, amt in items) add_sell(item, amt); } //:FUNCTION add_sell_object // adds a unique item to the vendor's stored_items mapping void add_sell_object(object ob) { class item item; foreach (item in values(stored_items)) { if (item->objects && compare_objects(item->objects[0], ob)) { item->objects += ({ ob }); item->amount++; return; } } item = init_item(ob); item->objects = ({ ob }); stored_items[++max_item_number] = item; } //:FUNCTION set_for_sale //Set the array of object names which this living object is willing to sell. //set_for_sale(1) means everything is for sale. set_for_sale(0) means nothing //is. If a function is passed it will get the object to sell as argument. //If a single string is returned it will be used as error message. void set_for_sale(mixed x) { for_sale = x; } mixed query_for_sale() { return for_sale; } //:FUNCTION set_will_buy //Set the array of object names which this living object is willing to buy. //set_will_buy(1) means it will buy anything. set_will_buy(0) means it wont //by anything. If a function is passed it will get the object to buy as //argument. If a single string is returned it will be used as error message. void set_will_buy(mixed x) { will_buy = x; } mixed query_will_buy() { return will_buy; } //:FUNCTION set_currency_type //Sets the type of currency the vendor will buy/sell in mixed set_currency_type(string type) { if(!MONEY_D->is_currency(type)) catch("Invalid currency set in shopkeeper!\n"); currency_type = type; } //:FUNCTION query_currency_type //Queries the type of currency the vendor will buy/sell in mixed query_currency_type() { return currency_type; } // do NOT overload any of the direct or indirect rules. They are not called // if this is the only vendor and no vender is specified in the call. // Use set_will_buy() and set_for_sale() instead. mixed direct_ask_liv_about_wrd(object liv, string item) { return 1; } mixed direct_ask_liv_about_str(object liv, string item) { return 1; } mixed direct_list_from_liv(object liv) { return 1; } mixed direct_list_str_from_liv(string item, object liv) { return 1; } mixed direct_buy_str_from_liv(string item, object liv) { return 1; } mixed indirect_sell_obj_to_liv(object ob, object liv) { return 1; } int test_buy(object ob) { mixed result; if (!will_buy) { write(capitalize(short())+" doesn't buy anything.\n"); return 0; } result = evaluate(will_buy, ob); if (result == 1) return 1; if (stringp(result)) { if (result[<1] != '\n') result += "\n"; write(result); return 0; } if (arrayp(result)) { foreach (string name in result) { if (ob->id(name)) return 1; } } write(capitalize(short())+" doesn't want to buy "+ob->the_short()+".\n"); return 0; } //FUNCTION: buy_object //gets called from the verb sell. Addes bought object to the list of //stored_items depending on check_uniqueness() void buy_object(object ob) { float cost; mixed item; string file; mapping money; int exchange_rate = MONEY_D->query_exchange_rate(currency_type); if (!test_buy(ob)) return; cost = buying_cost(to_float(ob->query_value())) / to_float(exchange_rate); if (cost < 0.01) { write("You wouldn't get any "+currency_type+" for "+ob->the_short()+".\n"); return; } if (ob->move(this_object()) != MOVE_OK) { write("You can't seem to give "+ob->the_short()+" to "+short()+".\n"); return; } money = MONEY_D->calculate_denominations(cost, currency_type); foreach (string type, int amount in money) { this_body()->add_money(type, amount); } this_body()->my_action("$N $vsell a $o for " + MONEY_D->currency_to_string(money, currency_type) + ".\n", ob); this_body()->other_action("$N $vsell a $o.\n", ob); switch (check_uniqueness(ob)) { case 0: /* object is not unique, so just keep the filename. */ file = base_name(ob); destruct(ob); foreach (item in values(stored_items)) { if (item->file && item->file == file) { if (item->amount != -1) item->amount++; return; } } add_sell(file, 1); break; case 1: /* object is unique */ add_sell_object(ob); ob->move(load_object(unique_inv)); break; case 2: /* object is destroyable(), so don't sell it */ destruct(ob); break; } } //:FUNCTION query_items //gets called from the verb ask and the rule ask obj about str //The player commands buy and list use it too. //This function shows the players what items the shopkeeper has. //If flag is set the it will show the long() too int query_items(string item, int flag) { int *keys = ({}); int key, num; string cost; float exchange_rate = to_float(MONEY_D->query_exchange_rate(currency_type)); if(sizeof(stored_items) == 0 || !for_sale) { write("This shop has nothing to sell.\n"); return 0; } if (item == "all") { keys = keys(stored_items); } else { foreach (key in keys(stored_items)) { if (member_array(item, stored_items[key]->ids) != -1) { keys += ({ key }); } } } if (sizeof(keys) == 0) { write("Nothing in this shop matches that!\n"); return 0; } keys = sort_array(keys, 1); printf("%|6s %|8s %-24s %s\n","List #", "Amount", "Name/id", capitalize(currency_type)); foreach (key in keys) { cost = MONEY_D->currency_to_string( selling_cost(to_float(stored_items[key]->value)) / exchange_rate, currency_type); num = stored_items[key]->amount; if (num != -1) printf("%|6d %|8d %-24s %s\n", key, num, stored_items[key]->short, cost); else printf("%|6d %|8s %-24s %s\n", key, "Numerous", stored_items[key]->short, cost); if (flag) write(stored_items[key]->long); } return 1; } int test_sell(object ob) { // for_sale == 0 is tested in sell_stored_objects() already mixed result = evaluate(for_sale, ob); if (result == 1) return 1; if (stringp(result)) { if (result[<1] != '\n') result += "\n"; write(result); return 0; } if (arrayp(result)) { foreach (string name in result) { if (ob->id(name)) return 1; } } write(capitalize(short())+" doesn't want to sell "+ob->the_short()+".\n"); return 0; } protected int sell_object(object ob) { float exchange_rate = to_float(MONEY_D->query_exchange_rate(currency_type)); float cost; mapping array money; object cont; if (!test_sell(ob)) return 0; cost = selling_cost(to_float(ob->query_value())) / exchange_rate; if (cost > this_body()->query_amt_currency(currency_type)) { printf("Sorry, that costs %s, which you don't have!\n", MONEY_D->currency_to_string(cost, currency_type)); return 0; } if (ob->query_default_container()) { cont = new(ob->query_default_container()); ob->move(cont); ob = cont; } if (ob->move(this_body()) != MOVE_OK) { write(capitalize(query_subjective()) + " can't seem to give " + ob->the_short() + " to you.\n"); if(ob->move(environment(this_body())) == MOVE_OK) { write(capitalize(query_subjective()) + " leaves it on the floor instead.\n"); return 1; } return 0; } money = MONEY_D->handle_subtract_money(this_body(), cost, currency_type); this_body()->my_action("$N $vbuy a $o for " +MONEY_D->currency_to_string(cost, currency_type) +". You give " +MONEY_D->currency_to_string(money[0], currency_type) +" to $o1" +(sizeof(money[1]) ? " and get " +MONEY_D->currency_to_string(money[1],currency_type) +" as change" : "") +".\n", ob, this_object()); this_body()->other_action("$N $vbuy a $o.\n", ob); return 1; } private void sell_items(int key, int amount) { int i; class item item; object ob; item = stored_items[key]; if (item->amount != -1 && item->amount < amount) amount = item->amount; for (i = 1; i <= amount; i++) { if (item->objects) ob = item->objects[0]; else ob = new(item->file); if (sell_object(ob)) { if (item->objects) item->objects -= ({ ob }); if (item->amount != -1) { item->amount--; if (item->amount <= 0) map_delete(stored_items, key); } } else return; } } //:FUNCTION sell_stored_objects //Gets called from the buy verb with the string from obj rule. //This is the way the players can buy objects from the shopkeeper //that he has stored away. void sell_stored_objects(string item, int number, int amount) { if (!for_sale) { write("This shop doesn't sell anything.\n"); return; } if (amount == 0) amount = 1; if (number) { if (!stored_items[number]) { write("There is no item #"+number+" available.\n"); return; } if (item == "all") sell_items(number, amount); else { if (member_array(item, stored_items[number]->ids) != -1) sell_items(number, amount); else write("There is no '"+item+"' at #"+number+".\n"); } } else { foreach (int key in keys(stored_items)) { if (member_array(item, stored_items[key]->ids) != -1) { sell_items(key, amount); return; } } write("There is no '"+item+"' in the list.\n"); } } // for debugging only mapping query_stored() { return stored_items; } //:FUNCTION set_unique_inventory //void set_unique_inventory(string str) //This function determines if the vendor should hold onto what he //buys instead of desting it and replacing it with an original. For //Example without unique set if you sell a sword to the vendor, no //matter what the condition is, it will be dested and replaced with //a new one. Some muds would prefer the old way of what you sell is //what you buy. The unique inventory is set by sending the room //where the inventory is kept. //ex: set_unique_inventory("/domains/std/rooms/storage"); //NOTE: only armor, weaps, vehicles are uniqued //Unless the object has a is_unique() { return 1; } function in it //See set_all_unique to unique everything void set_unique_inventory(string str) { unique_inv = str; #ifdef SELL_OLD_OBJECTS foreach (object ob in all_inventory(load_object(str))) add_sell_object(ob); #endif } string query_unique_inventory() { return unique_inv; } //:FUNCTION set_all_unique //Sets ALL objects to be uniqued. Only works when set_unique_inventory() //is used. void set_all_unique(int i) { all_unique = i; } int query_all_unique() { return all_unique; } int query_vendor() { return (for_sale || will_buy); } //:FUNCTION check_uniqueness //This fuction test if an object should be destroyed or saved when bought, //depending on destroyable(), set_all_unique() and is_unique(). int check_uniqueness(object ob) { /* If the object is supposed to be removed from the game upon selling */ /* ie. quest objects */ //:FUNCTION destroyable //place int destroyable() { return 1; } if you do not wish your object //to be purchaseable from a vendor. Players may sell it to receive its //value, but it will then be destroyed. Mostly used for quest objects if (ob->destroyable()) return 2; /* If we dont allow uniqueness at all, atuo return 0 */ if (!unique_inv || unique_inv == "") return 0; /* If we want everything to be unique, return 1 */ if (all_unique) return 1; /* If the object has been coded to be unique, return 1 */ //:FUNCTION is_unique //put int is_unique() { return 1; } in your object if you wish it //to be kept in a storeroom. This is most likely because your //object would have variable changes in it. For example, weapons //can have their wc reduced by wear and you'd rather someone not be //able to purchase a brand new sword after selling a broken one. if (ob->is_unique()) return 1; if (ob->is_armor()) return 1; if (ob->is_weapon()) return 1; if (ob->is_vehicle()) return 1; return 0; } mapping lpscript_attributes() { return ([ "currency_type" : ({ LPSCRIPT_STRING, "setup", "set_currency_type" }), "for_sale" : ({ LPSCRIPT_BOOLEAN, "setup", "set_for_sale" }), "will_buy" : ({ LPSCRIPT_BOOLEAN, "setup", "set_will_buy" }), "sell" : ({ LPSCRIPT_INT_MAPPING, "setup", "set_sell" }), ]); }