/** * This is the inheritable bit of the pub. The actual file you should * inherit is /std/shops/pub_shop.c * <p> * Based on the original "pub.c" code, this version allows you to buy an * actual drink (inna glass) instead of just forcefeeding you with whatever * you purchase. You can also create food and serve it on plates, in boxes, * or whatever. * <p> * The items on sale are either cloned from the armoury, cloned from a file * or cloned in the "create_item()" code in the pub code itself. Containers * for these items are done in the same way, using the "create_container()" * function. This is the same as the "create_object()" system in * "item_shop.c". You can also buy items for other people (or groups of * other people) in the pub. * <p> * There are several standard glasses and plates available for drinks and * food in the "pub_shop.h" header file, if you don't want to create the * glasses and plates yourself. These plates use the "/obj/misc/plate.c" * inheritable file. * <p> * A "counter" will automatically be placed in the pub. If any item cannot * be moved into the person who ordered it (i.e they are carrying too much ) * it will be placed on the counter. Empty glasses and so on will be * cleared from the counter after 3 seconds. To ensure that this happens, * make sure that the container object you use has the property "pub item" * added to it. Items will only be cleared if they are empty. * @author Lemming * @started 23/10/1999 * @see /std/shops/pub_shop.c * @see /include/shops/pub_shop.h * @see /include/volumes.h */ #include <shops/pub_shop.h> #include <armoury.h> #include <money.h> #include <move_failures.h> #include <map.h> inherit "/std/shops/inherit/open_shop"; inherit "/std/shops/inherit/shop_event"; // // Predefinitions needed from room.c // void add_hidden_object(object ob); private mapping _menu_items; private mapping _menu_aliases; private string _menu_header; private string _language; private int _display_header; private string *_menu_subheadings; private int _display_subheadings; private int no_standard_alias; private object _menu_object; private object _counter; void add_menu_alias( mixed alias, string actual ); string *calc_standard_aliases( string *aliases ); int do_buy( object *obs, string dir, string indir, mixed *args ); float query_discount( object ob ); object make_counter(); /** @ignore */ void create() { shop_event::create(); _menu_items = ([ ]); _menu_aliases = ([ ]); _menu_header = "The menu reads:"; _display_header = 1; _menu_subheadings = ({ "Appetisers", "Main Courses", "Desserts", "Alcoholic Beverages", "Soft Drinks", "Hot Drinks" }); _display_subheadings = 1; if( base_name( this_object() ) + ".c" != __FILE__ ) { _menu_object = clone_object( PUB_MENU_FILE ); _menu_object->set_pub( this_object() ); add_hidden_object( _menu_object ); _counter = make_counter(); } } /* create() */ /** @ignore */ void init() { this_player()->add_command( "buy", this_object(), "<string>" ); this_player()->add_command( "buy", this_object(), "<string> for <indirect:living:here>" ); this_player()->add_command( "order", this_object(), "<string>", (: do_buy( $1, $2, $3, $4 ) :) ); this_player()->add_command( "order", this_object(), "<string> for <indirect:living:here>", (: do_buy( $1, $2, $3, $4 ) :) ); } /* init() */ /** * This is the method used to add items to the menu. Only the first four * parameters are required, the rest are optional. * <p> * The "type" parameter is used to determine which section of the menu the * item should reside in, and should be selected from those in the * "pub_shop.h" header file. * <p> * The "item" parameter is used to generate the actual product on sale. This * value can be: * <ul> * <li> a name, which is passed to the "create_item()" function in the room code * <li> a filename, which is cloned, or * <li> an armoury identifier, which is passed to the armoury handler. * </ul> * <p> * The "container" parameter is the same as the "item" parameter, except that * it refers to the container the item comes in and is passed to * "create_container()" instead of "create_item()". You can also use the * standard containers listed in the "pub_shop.h" header file. It is * optional, and setting it to 0 will cause it to be ignored. * <p> * The "volume" parameter is optional and is passed directly to * "set_amount()" on the object cloned from "item". It can be used to alter * the volume of a liquid cloned from a file, so the file itself does not * have to be changed. It is optional, and setting it to 0 will cause it to * be ignored. If this is set to 0 then the item will fill up the * container. Standard volume definitions can be found in the "volumes.h" * header file. * <p> * The last parameter, "intox", is optional and is used only by NPCs to * determine how alcoholic an item is. This should be between 0 and 10, * with 0 being non-alcoholic and 10 being something like Suicider. It * defaults to 0. Note: This has no effect on the actual alcohol content * of the item. * <p> * The different volumes of standard containers can be taken from * /include/volumes.h which has defines for all the standard volumes. * @param name the description to be displayed on the menu * @param type the type of item * @param cost the cost of the item * @param item the name, filename or armoury identifier for the item itself * @param container the name, filename or armoury identifier for the * container (optional) * @param volume the volume that the item should be set to (optional) * @param intox the intoxification value, on a scale of 0 to 10 (optional) * @example * // Add a main course called "Big meat pie", cloned from the file * // "/obj/food/meatpie.food" * add_menu_item( "Big meat pie", PUB_MAINCOURSE, 1000, * "/obj/food/meatpie.food" ); * @example * // Same as above, but let's put the pie on a plate * add_menu_item( "Big meat pie", PUB_MAINCOURSE, 1000, * "/obj/food/meatpie.food", PUB_STD_PLATE ); * @example * // Add a glass of ale, with the ale cloned from a file and the glass * // cloned in the "create_container()" function in the room code * add_menu_item( "Pint of ale", PUB_ALCOHOL, 500, "/obj/food/ale.food", * "new_pint_glass" ); * @example * // The same as above, but we only want half a pint of ale in the glass, we * // want to use the standard glasses in the header file and we want to set * // the intoxification value of the ale to 5 * add_menu_item( "Half-pint of ale in a pint glass", PUB_ALCOHOL, 300, * "/obj/food/ale.food", PUB_STD_PINT, VOLUME_HALFPINT, 5 ); * @example * // Create a beefburger with added vodka in the "create_item()" function in * // the room code and put it in a small satchel from the armoury * add_menu_item( "Beefburger with special sauce", PUB_MAINCOURSE, 800, * "vodka_burger", "small satchel", 0, 7 ); * @see query_menu_items() * @see remove_menu_item() * @see /include/volumes.h */ varargs void add_menu_item( string name, int type, int cost, string item, string container, int volume, int intox ) { class menu_item new_item; string noun, alias; string *adjectives, *aliases; if( intox < 0 ) intox = 0; if( intox > 10 ) intox = 10; new_item = new( class menu_item ); new_item->type = type; new_item->cost = cost; new_item->item = item; new_item->container = container; new_item->volume = volume; new_item->intox = intox; _menu_items[name] = new_item; if( no_standard_alias ) { if( lower_case( name ) != name ) add_menu_alias( lower_case( name ), name ); return; } adjectives = explode( lower_case( name ), " " ); noun = adjectives[sizeof(adjectives) - 1]; adjectives = adjectives[0..sizeof(adjectives) - 2]; aliases = calc_standard_aliases( adjectives ); foreach( alias in aliases ) { add_menu_alias( implode( ({ alias, noun }), " " ), name ); } } /* add_menu_item() */ /** @ignore */ string *calc_standard_aliases( string *array ) { int i, num_aliases; string *new_alias, *aliases; if( !sizeof( array ) ) return ({ 0 }); if( sizeof( array ) == 1 ) return ({ array[0], 0 }); aliases = calc_standard_aliases( array[0..sizeof( array ) - 2] ); num_aliases = sizeof( aliases ); for( i = 0; i < num_aliases; i++ ) { new_alias = ({ aliases[i], array[ sizeof( array ) - 1] }); aliases += ({ implode( new_alias, " " ) }); } return aliases; } /* calc_standard_aliases() */ /** * This method returns a list of all the items currently available in * the pub. * @return items available, listing type, price and intoxification value * @see add_menu_item() * @see remove_menu_item() */ mapping query_menu_items() { return _menu_items; } /* query_menu_items() */ /** * This method checks to see if this is a pub. * @return always return 1 */ int query_pub() { return 1; } /* query_pub() */ /** * This method sets the language to use in the shop. * @param language the language to use */ void set_language(string language) { _language = language; } /* set_language() */ /** * This method returns the language used in the shop. * @return the language used in the shop */ string query_language() { return _language; } /* query_language() */ /** * This method allows you to remove an item from those currently available * in the pub. * @param name the name of the item to remove * @return 1 if successful, 0 if unsuccessful * @see add_menu_item() * @see query_menu_items() */ int remove_menu_item( string name ) { if( !_menu_items[name] ) { return 0; } map_delete( _menu_items, name ); return 1; } /* remove_menu_item() */ /** @ignore */ string string_menu( string *items ) { int loop; string str, place; str = ""; place = this_object()->query_property( "place" ); if( !place || ( place == "" ) ) { place = "default"; } for( loop = 0; loop < sizeof(items); loop++ ) { str += sprintf( " %-30s %s\n", items[loop], MONEY_HAND->money_value_string( _menu_items[items[loop]]->cost, place ) ); } return str; } /* string_menu() */ /** @ignore */ string *query_items_of_type( int type ) { int i; string *selected; string *items; selected = ({ }); items = keys( _menu_items ); for( i = 0; i < sizeof(items); i++ ) { if( _menu_items[items[i]]->type == type ) { selected += ({ items[i] }); } } selected = sort_array( selected, (: _menu_items[$1]->cost - _menu_items[$2]->cost :) ); return selected; } /* query_items_of_type() */ /** @ignore */ string string_menu_of_type( int type ) { string str; string *items; items = query_items_of_type( type ); if( !sizeof(items) ) { return ""; } if( _display_subheadings ) { str = _menu_subheadings[ type ] + "\n"; } str += string_menu( items ); return sprintf( "%-=*s\n", (int)this_player()->query_cols(), str ); } /* string_menu_of_type() */ /** * This method produces the menu from the item information, with the menu * header at the top, all items available grouped by type and sorted by * cost. If you don't want the menu printed this way then mask this * function and return your own. * @return the menu text * @see add_menu_item() * @see set_display_header() * @see set_menu_header() * @see set_display_subheadings() * @see set_menu_subheadings() */ string read() { string ret; ret = "\n"; if( _display_header ) { ret += _menu_header + "\n"; } ret += string_menu_of_type( PUB_APPETISER ); ret += string_menu_of_type( PUB_MAINCOURSE ); ret += string_menu_of_type( PUB_DESSERT ); ret += string_menu_of_type( PUB_ALCOHOL ); ret += string_menu_of_type( PUB_SOFTDRINK ); ret += string_menu_of_type( PUB_HOTDRINK ); return ret; } /* read() */ /** * This method allows you to switch the menu header (defaults to "The menu * reads:") that appears at the top of the menu on and off. * @param value set to 1 to display header or 0 to remove them * @see query_display_header() * @see set_menu_header() * @see query_menu_header() */ void set_display_header( int value ) { _display_header = value; } /* set_display_header() */ /** * This method returns a flag stating whether display of the menu header * is on or off. * @return 1 for header, 0 for no header * @see set_display_header() * @see set_menu_header() * @see query_menu_header() */ int query_display_header() { return _display_header; } /* query_display_header() */ /** * This method sets the header that appears at the top of the menu. By * default this is "The menu reads:". * @param header the new menu header * @see query_menu_header() * @see set_display_header() * @see query_display_header() */ void set_menu_header( string header ) { _menu_header = header; } /* set_menu_header() */ /** * This method returns the current menu header text. * @return the menu header text * @see set_menu_header() * @see set_display_header() * @see query_display_header() */ string query_menu_header() { return _menu_header; } /* query_menu_header() */ /** * This method allows you to switch the subheadings ("Alcoholic Beverages", * "Meals", etc) that appear above different types of items on and off. * @param value set to 1 to display subheadings or 0 to remove them * @see query_display_subheadings() * @see set_menu_subheadings() * @see query_menu_subheadings() */ void set_display_subheadings( int value ) { _display_subheadings = value; } /* set_display_subheadings() */ /** * This method returns a flag stating whether display of the menu subheadings * is on or off. * @return 1 for headings, 0 for no headings * @see set_display_subheadings() * @see set_menu_subheadings() * @see query_menu_subheadings() */ int query_display_subheadings() { return _display_subheadings; } /* query_display_subheadings() */ /** * This method sets the subheadings that appear at the top of the menu. * @param subheading the subheading to change (use the #defines listed in * "pub_shop.h") * @param text the new menu subheading text * @see query_menu_subheadings() * @see set_display_subheadings() * @see query_display_subheadings() */ void set_menu_subheadings( int subheading, string text ) { _menu_subheadings[ subheading ] = text; } /* set_menu_subheadings() */ /** * This method returns the current menu subheading text. * @return the menu subheadings * @see set_menu_subheadings() * @see set_display_subheadings() * @see query_display_subheadings() */ string *query_menu_subheadings() { return _menu_subheadings; } /* query_menu_subheadings() */ /** * This method allows you to add an alias to an item sold in the pub. Many * aliases are added by default so you shouldn't have to use this too * often. See the help on "set_no_standard_alias()" to see what aliases are * added automatically. * @param alias the alias to add * @param alias the real item that the alias refers to * @example * // Allow "buy lancre ale" instead of "buy Ale from Lancre" * add_menu_alias( "lancre ale", "Ale from Lancre" ); * @see add_menu_aliases() * @see query_menu_aliases() * @see remove_menu_alias() * @see set_no_standard_alias() */ void add_menu_alias( mixed alias, string actual ) { string bing; if (arrayp(alias)) { foreach (bing in alias) { add_menu_alias(bing, actual); } } _menu_aliases[alias] = actual; } /* add_menu_alias() */ /** * This method allows you to add multiple aliases at once. Many aliases are * added by default so you shouldn't have to use this too often. See the * help on "set_no_standard_alias()" to see what aliases are added * automatically. * @param aliases an array of aliases to add * @param alias the real item that the aliases refer to * @example * // Add friendly aliases to "Beef burger and chips" * add_menu_aliases( ({ "beef burger", * "beefburger", "burger" }), "Beef burger and chips" ); * @see add_menu_alias() * @see query_menu_aliases() * @see remove_menu_alias() * @see set_no_standard_alias() */ void add_menu_aliases( string *aliases, string actual ) { string alias; foreach( alias in aliases ) { add_menu_alias( alias, actual ); } } /* add_menu_aliases() */ /** * This method returns a list of all the aliases currently available in * the pub. * @return alias : real name * @see add_menu_alias() * @see add_menu_aliases() * @see remove_menu_alias() * @see set_no_standard_alias() */ mapping query_menu_aliases() { return _menu_aliases; } /* query_menu_aliases() */ /** * This method allows you to remove an alias from those currently available * in the pub. * @param alias the alias to remove from the list * @return 1 if successful, 0 if unsuccessful * @see add_menu_alias() * @see add_menu_aliases() * @see query_menu_aliases() * @see set_no_standard_alias() */ int remove_menu_alias( string alias ) { if( !_menu_aliases[alias] ) { return 0; } map_delete( _menu_aliases, alias ); return 1; } /* remove_menu_alias() */ /** * This method allows you to turn on or off the addition of standard aliases * when new menu items are added. By default it is turned on. Standard * aliases are added as follows: * If you added an item called "Lancre vintage wine" the aliases added would * be: * <ul> * <li> vintage wine * <li> lancre wine * <li> wine * </ul> * An alias of the name in lowercase is always added regardless of whether * or not this flag is turned on or off. You may wish to turn this off if * you are adding several items which could be mistaken for each other, for * instance "Lancre beer" and "Morporkian beer". * @param flag 0 if standard aliases should be added, 1 if not * @see add_menu_alias() * @see query_menu_aliases() * @see remove_alias() * @see query_no_standard_alias() */ void set_no_standard_alias( int flag ) { no_standard_alias = flag; } /* set_no_standard_alias() */ /** * This method returns a flag stating whether standard aliases will be added * or not. * @return 0 if standard aliases will be added, 1 if not * @see set_no_standard_alias() */ int query_no_standard_alias() { return no_standard_alias; } /* set_no_standard_alias() */ /** @ignore */ object create_real_object( string name ) { object item, container; if( _menu_items[name]->container ) { container = this_object()->create_container( _menu_items[name]->container ); if( !container ) { container = clone_object( _menu_items[name]->container ); } if( !container ) { container = ARMOURY->request_item( _menu_items[name]->container, 100 ); } } if( _menu_items[name]->item ) { item = this_object()->create_item( _menu_items[name]->item ); if( !item ) { item = clone_object( _menu_items[name]->item ); } if( !item ) { item = ARMOURY->request_item( _menu_items[name]->item, 100 ); } } if( item && _menu_items[name]->volume ) { item->set_amount( _menu_items[name]->volume ); } else if ( item && !_menu_items[name]->volume && ( _menu_items[name]->type == PUB_ALCOHOL || _menu_items[name]->type == PUB_HOTDRINK || _menu_items[name]->type == PUB_SOFTDRINK ) ) { item->set_amount( container->query_max_volume() - container->query_volume() ); } if( item && container ) { if( (int)item->move( container ) != MOVE_OK ) { write( "The " + container->short() + " is too small to hold " + item->the_short() + ". Please file a bug report.\n" ); item->move( "/room/rubbish" ); } } if( container ) { return container; } if( item ) { return item; } return 0; } /* create_real_object() */ /** @ignore */ int do_buy( object *obs, string dir, string indir, mixed *args ) { int value, cost; string str, place; object person, thing; object *succeededpeople, *deadpeople, *failedpeople, *poorpeople; succeededpeople = ({ }); deadpeople = ({ }); failedpeople = ({ }); poorpeople = ({ }); str = args[0]; if( this_player()->query_property( "dead" ) ) { add_failed_mess( "How can you expect to buy " + str + " when you're " "dead?\n" ); return 0; } if( _menu_aliases[str] ) { str = _menu_aliases[str]; } if( !_menu_items[str] ) { if (!broadcast_shop_event(PUB_EVENT_NOT_AVAILABLE, this_player(), str)) { add_failed_mess( "Sorry, " + str + " is not on the menu.\n" ); } return 0; } // Check to make sure the pub is open. if ( !is_open( this_player(), _menu_items[str]->type ) ) { broadcast_shop_event(PUB_EVENT_NOT_OPEN, this_player()); return 0; } if( !sizeof( obs ) ) { obs = ({ this_player() }); } foreach( person in obs ) { if( person->query_property( "dead" ) ) { deadpeople += ({ person }); continue; } if( !living( person ) || !interactive( person ) && !person->query_property( "npc" ) ) { failedpeople += ({ person }); continue; } cost = ( _menu_items[str]->cost ) * query_discount( this_player() ); place = this_object()->query_property( "place" ); if( !place || ( place == "" ) ) { place = "default"; } value = (int)this_player()->query_value_in( place ); if( place != "default" ) { value += (int)this_player()->query_value_in( "default" ); } if( cost > value ) { poorpeople += ({ person }); continue; } thing = create_real_object( str ); if( !thing ) { add_failed_mess( "Something is buggered. Please file a bug report. " "Thank you.\n" ); return 0; } this_player()->pay_money( (mixed *)MONEY_HAND->create_money_array( cost, place ), place ); succeededpeople += ({ person }); if( (int)thing->move( person ) != MOVE_OK ) { if( (int)thing->move( _counter ) != MOVE_OK ) { thing->move( this_object() ); write("You cannot pick " + thing->a_short() + " up. It's left on the floor for you.\n" ); } else { write("You cannot pick " + thing->a_short() + " up. It's left on the counter for you.\n" ); } } } /* This will be overidden if necessary */ //add_succeeded_mess( this_object(), "", ({ }) ); if( sizeof( succeededpeople ) ) { // Quite why compare_arrays() isn't an efun is beyond me... if (!broadcast_shop_event(PUB_EVENT_BOUGHT_STUFF, this_player(), succeededpeople, str)) { if( !MAP_HANDLER->compare_arrays( succeededpeople, ({ this_player() }) ) ) { this_player()->add_succeeded_mess( this_object(), "$N $V $I.\n", ({ add_a(str) }) ); } else { add_succeeded_mess("$N $V " + query_multiple_short( ({ thing }), "a" ) + " for $I.\n", succeededpeople); } } } else { if( sizeof( deadpeople ) ) { if (!broadcast_shop_event(PUB_EVENT_DEAD_PEOPLE, this_player(), deadpeople, str)) { add_failed_mess("What use " + ( sizeof( deadpeople ) > 1?"have ":"has " ) + query_multiple_short( deadpeople, "one" ) + " got for " + str + "?\n" ); } } if( sizeof( failedpeople ) ) { if (!broadcast_shop_event(PUB_EVENT_FAILED_PEOPLE, this_player(), failedpeople, str)) { add_failed_mess("You can't buy anything for " + query_multiple_short( failedpeople, "one" ) + ".\n" ); } } if( sizeof( poorpeople ) ) { if (!broadcast_shop_event(PUB_EVENT_POOR_PERSON, this_player(), poorpeople, str)) { add_failed_mess("You cannot afford to order " + str + " for " + query_multiple_short( poorpeople, "one" ) + ".\n" ); } } return 0; } return 1; } /* do_buy() */ /** @ignore */ void dest_me() { if( _menu_object ) { _menu_object->dest_me(); } } /* dest_me() */ /** * This function can be masked and used to determine a discount that is * applied to all items sold, so for instance you could check the guild of * 'ob' and give a discount to Witches, or something. By default the * discount is zero, so query_discount returns 1.0 * @param ob the object doing the buying * @return a float to multiply the price by * @example * // Give Priests a 10% discount * float query_discount( object ob ) { * if( ob->query_guild_ob() == "/std/guilds/priest.c" ) * return 0.9; * else * return 1.0; * } */ float query_discount( object ob ) { return 1.0; } /* query_discount() */ /** * This function creates the counter for the pub. It defaults to cloning * PUB_COUNTER_FILE and making it a hidden object. * If you are creating your own counter object then making it hidden is a * good idea. If it's not hidden, make sure it at least cannot be moved or * buried. * The counter should clear empty objects with the "pub item" property if * they are placed on it. Making it clear non-empty objects is a bad idea, * since items will be placed here if the purchaser is unable to carry * them. * @return the new counter object * @see query_counter() * @see /include/shops/pub_shop.h */ object make_counter() { object ob; ob = clone_object( PUB_COUNTER_FILE ); add_hidden_object( ob ); return ob; } /* make_counter() */ /** * This method returns the object currently being used as a counter in the * pub. * @return the file name of the counter * @see make_counter() */ object query_counter() { return _counter; } /* query_counter() */ /** * This method returns the object currently being used as a menu in the * pub. * @return the file name of the menu */ object query_menu() { return _menu_object; } /* query_menu() */