/** * This is a inheritable that provides an efficient way of storing * lots of items. Only one copy of the item is effectively in storage * and everytime one is removed, an exact copy is duplicated and * put back in the store. Objects are added to the store via * add_object(). The actual storage container object * can be returned by query_cont() and this container should be * searched when you want to find what objects the store contains. * Any object can inherit this, and methods * should be put in place in the inheriting file that end up calling * create_real_object() which will sort out duplicating the item * and returning an object pointer to the one you can deal with. * @example * inherit "clone_on_demand"; * int do_buy( objects *obs ); * * void setup(){ * set_name("shop"); * set_short("widget shop"); * add_object( "sprocket" ); * } * * object create_object( string arg ){ * if( arg == "sprocket" ) * return clone_object("/path/of/sprocket"); * } * * void init(){ * add_command("buy", "<indirect:object:" + * base_name( query_cont() ) + ">"); * } * * int do_buy( object *obs ){ * object ob; * foreach(ob in obs){ * widget = create_real_object(ob); * widget->move( this_player() ); * } * add_succeeded_mess( "$N buy$s $I.\n", obs ); * return 1; * } * * @author Pinkfish Aquilo * @see add_object * @changed Changed to use a permanent temporary container so that we * wouldn't have to clone/dest a temporary container each time a real * object is created. We also don't have to move each object twice in * create_real_object() anymore. * - Sandoz. */ #include <armoury.h> #define INFINITE_LEFT 10000 #define MAX_PROP "max per reset" #define NUM_REMOVED "number removed this reset" #define REFERENCE "name we are reference by" #define DISPLAY "display in shop" nosave object _cont, _tmp_cont; /** * This method returns the container which is used to keep one copy of * each items in storage. * @return the object container */ object query_cont() { return _cont; } private object make_shop_container() { object ob; ob = clone_object("/std/container"); ob->set_name("clone_on_demand-store"); ob->add_property("parent", file_name(TO) ); return ob; } /* make_shop_container() */ /** @ignore yes */ void reset() { object ob; if( !_cont ) _cont = make_shop_container(); foreach( ob in INV(_cont) ) ob->remove_property( NUM_REMOVED ); } /* reset() */ /** * This method is used to add an item to the storage. * When this method is called, create_object() is called * (with the object name as an arg) in the inheriting file. * If no object is returned by that function, * the name is cloned with clone_object(), and failing that * request_item() is called in the armoury against the name. * * This method makes add_weapon() and add_armour() obsolete. * * @example * add_object( "frog", 1 + random( 3 ) ); * // This will try and create an object called frog, in the order mentioned * // above * @example * add_object( "/obj/food/apple.food", 0 ); * // Add unlimited numbers of apples. * @param name the name of the object to add. * @param max_per_reset the maximum number of items to be available at any one time * @param display_as the name that this item is displayed as in shops * @return 1 if the item was added successfully to the store, 0 if it was not. */ varargs int add_object( string name, int max_per_reset, string display_as ) { object ob; if( !_cont ) _cont = make_shop_container(); if( !( ob = TO->create_object(name) ) && !( ob = ARMOURY_H->request_item( name, 100 ) ) ) ob = clone_object(name); if( ob ) { if( ob->query_decays() ) { // Stop decaying objects decaying in inventories. ob->set_decay_speed(0); } ob->move(_cont); if( display_as ) ob->add_property( DISPLAY, display_as ); ob->add_property( REFERENCE, name ); ob->add_property( MAX_PROP, max_per_reset ); return 1; } return 0; } /* add_object() */ /** * Returns how more times object ob can be duplicated * @param ob the object to test * @return how many more times */ int query_num_items_left( object ob ) { int max, num; max = ob->query_property( MAX_PROP ); num = ob->query_property( NUM_REMOVED ); if( max ) return max - num; if( max == -1 ) return INFINITE_LEFT; return 0; } /* query_num_items_left() */ /** * This function returns the quantity of particular object available * to be cloned on demand. In matching which object is the one in * question it uses the short name of the object, which is passed as * an argument to the function. * @param name is the short name of the object you wish to query. * @return the number left, returns INFINITE_LEFT if the shop has an infinite * number, returns -1 if the item isn't stocked. */ int query_number_left( string name ) { object thing; thing = filter( INV(_cont), (: $1->query_short() == $(name) :) )[0]; if( !thing ) return -1; // We don't have that item. return query_num_items_left( thing ); } /* query_number_left() */ /** * This function can be used to check the quantity left of an array * of items. It returns a parallel array of integers. In other words * the array it returns contains the numbers of stock in array positions * corresponding to the array positions of the objects it was passed. * @example * query_items_left( ({ "banana" , "melon" }) ) * would return ({ 12 , 6 }) if there were 12 bananas and 6 melons left. * @param names an array of the short names of the items you wish to query * @return an array of integers, each one returning like query_number_left * would for the object in that position of the object array. * @see query_number_left */ int *query_items_left( string *names ) { int sz, *numbers; // Empty arrays not allowed. if( ( sz = sizeof( names ) ) < 1 ) return ({ }); numbers = allocate( sz ); while( sz-- ) numbers[sz] = query_number_left( names[sz] ); return numbers; } /* query_items_left() */ /** * This function transfers certain tracking properties from the * original item to the new item which replaces it in storage. To * transfer any additional properties, have switch_extra_properties() * defined in your inheriting object and return an array of extra * properties to transfer */ private void switch_properties( object newone, object original ) { mixed extra, *props, prop; props = ({ MAX_PROP, NUM_REMOVED, REFERENCE }); if( sizeof( extra = TO->switch_extra_properties() ) ) props += extra; foreach( prop in props ){ newone->add_property( prop, original->query_property( prop ) ); original->remove_property( prop ); } if( newone->query_decay_speed() ) { original->set_decay_speed( newone->query_decay_speed() ); // Stop decaying objects decaying in inventories. newone->set_decay_speed(0); } } /* switch_properties() */ /** * The main point of entry. 'thing' should be an object already placed * in the clone_on_demand store container via 'add_object'. This * method then duplicates that object, replaces the original copy in * the container with this new one, and returns the original which can * be delt with as normal. * @param thing an object in the store * @return the original object * @see add_object */ protected object create_real_object( object thing ) { string name; object new_thing; name = thing->query_property( REFERENCE ); if( !( new_thing = TO->create_object( name ) ) && !( new_thing = ARMOURY_H->request_item( name, 80 + random( 20 ) ) ) ) new_thing = clone_object(name); if( new_thing ) { int i, max, num; object tmp, *inv; switch_properties( new_thing, thing ); // Restore contents' order. inv = INV(_cont); i = member_array( thing, inv ); inv[i] = new_thing; i = sizeof(inv); thing->move("/room/void"); if( !_tmp_cont ) _tmp_cont = make_shop_container(); while( i-- ) inv[i]->move(_tmp_cont); // Switch containers. tmp = _cont; _cont = _tmp_cont; _tmp_cont = tmp; // Decrement store if applicable. num = new_thing->query_property( NUM_REMOVED ); if( max = new_thing->query_property( MAX_PROP ) ) new_thing->add_property( NUM_REMOVED, num + 1 ); } return thing; } /* create_real_object() */ /** @ignore yes */ void dest_me() { // Let's recycle too - Sandoz. if( _cont ) { INV(_cont)->move("/room/rubbish"); _cont->dest_me(); } // _tmp_cont should always be empty. if( _tmp_cont ) _tmp_cont->dest_me(); } /* dest_me() */ /** @ignore yes */ mixed stats() { return ({ ({"container", _cont }), ({"temporary container", _tmp_cont }), }); } /* stats() */