/** * Keeps track of all the standard armours, weapons and clothing. * @index armour * @index weapon * @index scabard * @index clothing * @index clothes * @index jewellery * @index stationery * @index instruments * @author Furball * @changed Many many, by many people * @changed 4/1/97 Ceres * The discworld armoury was rewritten by Ceres on 4/1/97 to handle * subdirectories within the standard dirs. * @changed 20/06/01 Shaydz * Rewritten to remove a few annoying bugs, including not being able to * request items that had more than one word. * @changed 26/06/02 Sandoz * Removed all the recycling code, because it will never be reliable, * because of properties, effects and whatnot. * @changed 06/09/02 Sandoz * Added stationery and instrument lists, and new recycle code which * will hopefully work ( gotta love reload_object() :) ). * @changed 26/09/02 Sandoz * Changed to only recycle items that have been requested with request_item, * and to only accept an item for recycling if the item's miss count is * higher than the number of currently recycled items. */ #define CONTAINER "/std/objects/armoury_container" #define ITEM_LOG #ifdef ITEM_LOG #define SAVE_FILE HANDLER_SAVE_DIR "/armoury_log.o" #endif #define O_WEAPON 1 #define O_ARMOUR 2 #define O_CLOTHING 4 #define O_SCABBARD 8 #define O_JEWELLERY 16 #define O_STATIONERY 32 #define O_INSTRUMENT 64 #define O_ACCEPT 128 inherit ROOM_OBJ; /** * This class stores all the data for a specific armoury item. * @member file the file name of the item * @member recycled the recycled objects * @member hits recycle hits * @member misses recycle misses */ class item_data { string file; object *recycled; int hits; int misses; int flags; } #ifdef ITEM_LOG private int call_id; #endif // This mapping contains all our items, for faster access. private nosave mapping item_list; // This stores our container object, where we keep all out recycled stuff. private object cont; // This stores all the items that can be searched up from rooms. private object *guttered; #ifdef ITEM_LOG private void do_logging(); #endif void rehash(); int query_keep_room_loaded() { return 1; } protected void setup() { set_short("Armoury" ); add_property("determinate", "the "); set_light(50); set_long("You are in the Armoury. Here you can list all of the " "\"weapons\", \"armour\", \"clothing\", \"jewellery\", " "\"scabbards\", \"stationery\" items and musical \"instruments\" " "that are available, or \"request\" one of them. The armoury " "stores its list of items a mapping, which can be updated with " "\"rehash\".\n"); rehash(); guttered = ({ }); #ifdef ITEM_LOG call_id = call_out( (: do_logging :), 1200 ); if( file_exists(SAVE_FILE) ) unguarded( (: rm, SAVE_FILE :) ); #endif } /* setup() */ /** @ignore yes */ private string convert_fname( string file ) { return replace_string( explode( file, ".")[0], "_", " "); } /* convert_fname() */ /** * This method recycles a suitable object. * It will call reload_object on it, and make it * available from the armoury. * @param ob the object to try to recycle * @return 1 if recycled successfully, 0 if not */ int recycle_object( object ob ) { string base, name; class item_data data; name = convert_fname( explode( base = base_name(ob), "/")[<1] ); if( classp( data = item_list[name] ) && ( data->flags & O_ACCEPT ) && data->file == base && sizeof(data->recycled) < data->misses ) { object *inv; if( sizeof( inv = INV(ob) ) ) { inv->move("/room/rubbish"); if( sizeof( INV(ob) ) ) return 0; } if( !cont && !( cont = clone_object(CONTAINER) ) || ob->move(cont) ) return 0; reload_object(ob); data->recycled += ({ ob }); if( ob->query_weight() < roll_MdN( 4, 20 ) && !sizeof( ob->query_value_info() ) ) guttered += ({ ob }); return 1; } return 0; } /* recycle_object() */ /** * General item retreival function. It returns a new item * it handles any sort of item. The percentage is the * percentage condition in which it is returned. * The "word" parameter is the name of the requested item, essentially * the filename without directory and with "_"'s changed to " "'s. The * "percent" parameter is used as measure of how good it * is compared to a fully functional one. So a 50% dagger is only 50% as * good as a 100% one. * <p> * The file armoury.h defines the ARMOURY variable. You need to include * this into your file to use it. You should include this file and * uyse ythis define rather than the full path to the armoury, as * otherwise your code could stuff up :) * @param word the name of the item to retreive * @param percent the percentage condition to be in * @see /include/armoury.h * @return the requested object, or 0 on failure * @example * // get a slightly worn long sword and give it to the npc. * ARMOURY->request_item("long sword", 85)->move(npc); * @example * // Get a random condition armour. * armour = ARMOURY->request_item("leather jacket", random(100)); */ object request_item( string word, int percent ) { class item_data data; object thing, *obs; int i; if( !word ) error("No item name specified in request_item().\n"); if( percent < 1 ) error("Zero or negative item condition in request_item().\n"); if( !classp( data = item_list[word] ) ) return 0; if( cont && ( i = sizeof( obs = data->recycled ) ) ) { while( i-- ) { if( ( thing = obs[i] ) && ENV(thing) == cont ) { data->recycled = obs[0..i-1]; data->hits++; thing->set_percentage( percent ); guttered -= ({ 0, thing }); return thing; } } // We found nothing of use. data->recycled = ({ }); } if( !thing = clone_object( data->file ) ) { printf("The armoury failed to clone %O.\n", word ); log_file("ARMOURY", "%s - failed to clone %O (%s).\n", ctime(time()), word, file_name(PO) ); return 0; } data->misses++; if( !( data->flags & O_ACCEPT ) && !thing->query_property(NO_RECYCLE) ) data->flags |= O_ACCEPT; thing->set_percentage( percent ); return thing; } /* request_item() */ /** @ignore yes */ void dest_me() { if( cont ) cont->dest_me(); ::dest_me(); } /* dest_me() */ /** * Returns the current list of all items. * @return the items mapping */ mapping query_item_list() { return copy(item_list); } /** * This method returns the recycle hits count for a specific file. * @param word the name of the object to get the hits count for * @return the hits count */ int query_hits( string word ) { if( item_list[ word ] || item_list[ word = convert_fname( explode( word, "/")[<1] ) ] ) return item_list[word]->hits; return 0; } /* query_hits() */ /** * This method returns the recycle miss count for a specific file. * @param word the name of the object to get the misses count for * @return the misses count */ int query_misses( string word ) { if( item_list[ word ] || item_list[ word = convert_fname( explode( word, "/")[<1] ) ] ) return item_list[word]->misses; return 0; } /* query_misses() */ /** * This method returns the recycled items for a specific file. * @param word the name of the object to get the recycled objects for * @return the recycled objects */ object *query_recycled( string word ) { if( item_list[ word ] || item_list[ word = convert_fname( explode( word, "/")[<1] ) ] ) return item_list[word]->recycled; return 0; } /* query_recycled() */ /** * This method returns whether or not the armoury is currently accepting * the specified item for recycling. * @param word the name of the object to check * @return 1 if we are currently accepting the item, 0 if not */ int query_accepting( string word ) { if( item_list[ word ] || item_list[ word = convert_fname( explode( word, "/")[<1] ) ] ) return ( item_list[word]->flags & O_ACCEPT ) > 0; return 0; } /* query_accepting() */ /** @ignore yes */ string *item_list_filter( int type ) { return keys( filter( item_list, (: $2->flags & $3 :), type ) ); } /* item_list_filter() */ /** * Returns the current list of known weapons. * @return the weapons array */ string *query_weapons() { return item_list_filter( O_WEAPON ); } /** * Returns the current list of known armours. * @return the armours array */ string *query_armour() { return item_list_filter( O_ARMOUR ); } /** * Returns the current list of known clothing. * @return the clothing array */ string *query_clothing() { return item_list_filter( O_CLOTHING ); } /** * Returns the current list of known jewelley. * @return the jewellery array */ string *query_jewellery() { return item_list_filter( O_JEWELLERY ); } /** * Returns the current list of known scabbards. * @return the scabbards array */ string *query_scabbards() { return item_list_filter( O_SCABBARD ); } /** * Returns the current list of known stationery items. * @return the stationery items array */ string *query_stationery() { return item_list_filter( O_STATIONERY ); } /** * Returns the current list of known musical instruments. * @return the musical instruments array */ string *query_instruments() { return item_list_filter( O_INSTRUMENT ); } /** @ignore yes */ private void make_list( string base_dir, int type ) { string *dirs, dir, fname, tmp; mixed file; dirs = ({ }); foreach( file in get_dir( base_dir, -1 ) ) if( file[1] == -2 ) dirs += ({ base_dir+file[0]+"/" }); dirs += ({ base_dir }); foreach( dir in dirs ) { foreach( fname in get_dir( dir+"*.c") ) { if( undefinedp( item_list[ tmp = convert_fname(fname) ] ) ) { item_list[tmp] = new( class item_data ); item_list[tmp]->file = dir+fname[0..<3]; item_list[tmp]->recycled = ({ }); item_list[tmp]->flags = type; } else { printf("Failed to add %s (%s) - %s already exists in the " "mapping.\n", tmp, dir+fname[0..<3], item_list[tmp]->file ); log_file("ARMOURY", "Failed to add %s (%s) - %s already " "exists in the mapping.\n", tmp, dir+fname[0..<3], item_list[tmp]->file ); } } } } /* make_list() */ /** @ignore yes */ int clean_up( int parent ) { return 0; } /** * This method returns the list of items that could be * searched up from gutters. * @return the guttered items list */ object *query_guttered() { if( cont ) guttered = filter( guttered, (: $1 && ENV($1) == $2 :), cont ); else guttered = ({ }); return guttered; } /* query_guttered() */ /** * This method gets a guttered item from the armoury. * This is used by the search function in city rooms, * and returns a random smaller item from the armoury's * recycled items list. The condition percentage should * not be set too high for guttered items, and has been * capped at 50% for that very reason. * @param percent the percentage condition the item should be in * @return a guttered object or 0 if there are none */ object get_guttered( int percent ) { int i; if( percent < 1 ) error("Zero or negative item condition in get_guttered().\n"); if( percent > 50 ) percent = 50; if( cont && ( i = sizeof( query_guttered() ) ) ) { object ob; ob = guttered[ i = random( i ) ]; ob->set_percentage( percent ); guttered = delete( guttered, i, 1 ); return ob; } return 0; } /* get_guttered() */ /** @ignore yes */ void rehash() { mapping tmp; string file; class item_data data; if( sizeof(item_list) ) tmp = item_list; item_list = ([ ]); make_list("/obj/dwweapons/", O_WEAPON ); make_list("/obj/weapons/", O_WEAPON ); make_list("/obj/dwarmours/", O_ARMOUR ); make_list("/obj/armours/", O_ARMOUR ); make_list("/obj/dwclothes/", O_CLOTHING ); make_list("/obj/clothes/", O_CLOTHING ); make_list("/obj/dwjewellery/", O_JEWELLERY ); make_list("/obj/jewellery/", O_JEWELLERY ); make_list("/obj/dwscabbards/", O_SCABBARD ); make_list("/obj/scabbards/", O_SCABBARD ); make_list("/obj/stationery/", O_STATIONERY ); make_list("/obj/instruments/", O_INSTRUMENT ); if( sizeof(tmp) ) { string *keys; // Transfer the accept flag, recycled objects and hits/misses // from the old mapping. foreach( file, data in item_list ) { if( classp( tmp[file] ) ) { data->hits = tmp[file]->hits; data->misses = tmp[file]->misses; if( data->file == tmp[file]->file ) data->recycled = tmp[file]->recycled - ({ 0 }); else if( cont && sizeof( tmp[file]->recycled -= ({ 0 }) ) ) map( filter( tmp[file]->recycled, (: ENV($1) == $2 :), cont ), (: $1->dest_me() :) ); if( tmp[file]->flags & O_ACCEPT ) data->flags |= O_ACCEPT; } } // Clean up recycled items that are no longer needed. if( cont && sizeof( keys = keys(tmp) - keys(item_list) ) ) foreach( file in keys ) if( sizeof( tmp[file]->recycled -= ({ 0 }) ) ) map( filter( tmp[file]->recycled, (: ENV($1) == $2 :), cont ), (: $1->dest_me() :) ); } } /* rehash() */ /** @ignore yes */ private int print_list( string str, string *arr ) { printf( str, TP->query_cols(), implode( sort_array( arr, 1 ), "\n") ); return 1; } /* print_list() */ /** @ignore yes */ int weapons() { return print_list("Weapons:\n%-*#s\n", query_weapons() ); } /* weapons() */ /** @ignore yes */ int armour() { return print_list("Armour:\n%-*#s\n", query_armour() ); } /* armour() */ /** @ignore yes */ int clothing() { return print_list("Clothes:\n%-*#s\n", query_clothing() ); } /* clothing() */ /** @ignore yes */ int jewellery() { return print_list("Jewellery:\n%-*#s\n", query_jewellery() ); } /* jewellery() */ /** @ignore yes */ int scabbards() { return print_list("Scabbards:\n%-*#s\n", query_scabbards() ); } /* scabbards() */ /** @ignore yes */ int stationery() { return print_list("Stationery items:\n%-*#s\n", query_stationery() ); } /* scabbards() */ /** @ignore yes */ int instruments() { return print_list("Musical instruments:\n%-*#s\n", query_instruments() ); } /* scabbards() */ /** @ignore yes */ int request( string word ) { int percent, i; object thing; string *words; if( !word || word == "") return notify_fail("Request what?\n"); words = explode( word, " "); if( sizeof(words) < 2 ) return notify_fail("Syntax: request <item name> <percent>\n"); i = sizeof(words); if( !percent = to_int(words[i-1]) ) return notify_fail("Syntax: request <item name> <percent>\n"); words = words - ({ words[i-1] }); word = implode( words, " "); thing = request_item( word, percent ); if( !thing ) return notify_fail( word+" not found.\n"); if( (int)thing->move(TP) ) { write( (string)thing->the_short()+" has been placed in here.\n"); thing->move(TO); } else write( (string)thing->the_short()+" has been placed in your " "inventory.\n"); return 1; } /* request() */ /** @ignore yes */ int do_rehash() { rehash(); write("Rehash completed.\n"); return 1; } /* do_rehash() */ /** @ignore yes */ void init() { ::init(); add_command("rehash", ""); add_command("request", "<string'item name> <percent'>", (: request($4[0]) :) ); foreach( string comm in ({"weapons", "armour", "clothing", "jewellery", "scabbards", "stationery", "instruments"}) ) add_command( comm, "", (: call_other( TO, $(comm) ) :) ); } /* init() */ #ifdef ITEM_LOG /** @ignore yes */ private void do_logging() { mapping misses, unique, saved; string str; string missing = "", extra = ""; int i, total_missing, total_extra, changed; if( find_call_out(call_id) == -1 ) call_id = call_out( (: do_logging :), 3600 ); misses = ([ ]); if( file_exists(SAVE_FILE) ) saved = restore_variable( unguarded( (: read_file, SAVE_FILE :) ) ); if( !mapp(saved) ) saved = ([ ]); // First create a mapping of - ([ filename : misses ]) foreach( str, class item_data data in item_list ) misses[data->file] = data->misses; // Make a mapping of all cloned armoury objects based on their // base name - ([ basename : ({ objects }) ]) unique = unique_mapping( objects( (: clonep($1) && !undefinedp( $(misses)[base_name($1)] ) :) ), (: base_name($1) :) ); // Now check how many extra objects we have (bad, because something // is cloning items, but not through the armoury), or how many we are // missing (bad, because something is destructing items instead of // sending them to the rubbish room). // This will break a little when someone with a lot of armoury items // logs on though... But... nothing helps against that. foreach( str in sort_array( keys(misses), 1 ) ) { i = misses[str] - sizeof( unique[str] ); if( i ) { if( saved[str] != i ) { changed = 1; saved[str] = i; } if( i > 4 ) { missing += sprintf("%5d %s\n", i, str ); total_missing += i; continue; } if( i < -4 ) { extra += sprintf("%5d %s\n", -i, str ); total_extra -= i; continue; } } } if( changed ) { if( sizeof(saved) ) unguarded( (: write_file, SAVE_FILE, save_variable(saved), 1 :) ); if( missing != "") log_file("ARMOURY_RECYCLE_LOG", "%s - %i missing " "items:\n%s", ctime(time()), total_missing, missing ); if( extra != "") log_file("ARMOURY_RECYCLE_LOG", "%s - %i extra " "items:\n%s", ctime(time()), total_extra, extra ); } } /* do_logging() */ /** @ignore yes */ void force_logging() { if( adminp( previous_object(-1) ) ) do_logging(); } /* force_logging() */ #endif /** @ignore yes */ mixed stats() { mixed arr; int misses, hits; class item_data data; foreach( data in values(item_list) ) { hits += data->hits; misses += data->misses; } arr = ({ ({"total hits", hits }), ({"total misses", misses }) }); if( cont ) { arr += ({ ({"container", cont }), ({"recycled", sizeof(INV(cont)) }), }); } arr += ({ ({"weapons", sizeof( query_weapons() ) }), ({"armour", sizeof( query_armour() ) }), ({"clothes", sizeof( query_clothing() ) }), ({"jewellery", sizeof( query_jewellery() ) }), ({"scabbards", sizeof( query_scabbards() ) }), ({"stationery", sizeof( query_stationery() ) }), ({"instruments", sizeof( query_instruments() ) }), ({"total items", sizeof( item_list) }), ({"guttered items", sizeof( query_guttered() ) }), }); #ifdef ITEM_LOG if( ( hits = find_call_out(call_id) ) != -1 ) arr += ({ ({"next logging", hits }) }); #endif return arr; } /* stats() */ /** * Return the absolute filename to an item. * @param name the name of the item * @return the absolute filename of the item */ string query_item_filename(string item) { class item_data data; if(!classp(data = item_list[item])) { return 0; } return data->file; } /* query_item_filename() */