/* -*- LPC -*- */ /* * $Id: modified_efuns.c,v 1.51 2001/04/04 18:12:20 ceres Exp wodan $ */ #define MAX_SIZE 50000 #define LIV "/obj/handlers/livings" // This define figures out the filename to log to. #define LOG_NAME(X) ((X[0] == '/') ? X : "/log/"+X) #include <login.h> #include <player_handler.h> #include <playtesters.h> inherit "/secure/simul_efun/base_name"; string back_trace(); #if !efun_defined(event) void event(mixed,string,mixed ...); #endif #if !efun_defined(add_action) int _notify_fail(string); int living(object ob); object find_player(string); #endif private nosave int _callouttime; private nosave mapping _calloutfunc = ([]); private nosave mapping _log_file_info = ([ ]); private nosave mapping _db_fds = ([ ]); private nosave mapping _db_obs = ([ ]); private nosave int _log_file_flush_id; private nosave string _reset_eval_message="simul_efun updated"; private nosave int _reset_eval_message_count=1; private nosave int _in_reference_allowed; // Every 15 seconds. #define DELAY_LOG_FLUSH 15 /** * Cotains some simul_efuns. * @author Pinkfish */ /** * @ignore yes */ varargs void say(string str, mixed avoid) { if (!pointerp(avoid)) { avoid = ({ this_player(), previous_object() }) + ({ avoid }); } else { avoid += ({ this_player(), previous_object() }); } if (!environment(previous_object())) { if(this_player() && environment(this_player())) { event(environment(this_player()), "say", str, avoid); } else { event(previous_object(), "say", str, avoid); } } else { if (environment(environment(previous_object()))) { event(environment(environment(previous_object())), "say", str, avoid); } else { event(environment(previous_object()), "say", str, avoid); } } } /* say()*/ /** * @ignore yes */ varargs void tell_room(mixed ob, string str, mixed avoid) { if (!ob || !(objectp(ob) || stringp(ob))) { return ; } if (stringp(ob)) { ob = load_object(ob); } event(ob, "say", str, avoid); } /* tell_room() */ /** * @ignore yes */ void tell_object(object ob, string str) { if (objectp(ob)) { ob->do_efun_write( str ); } } /* tell_object() */ /** * This method is used to test to see if a reference is allowed to this * object. It tests things like the allow array and that sort of stuff. * @param thing the object to test * @return 1 if the reference is allowed */ varargs int reference_allowed(object referree, mixed referrer) { string referrer_name; string *allowed; object referrer_obj; int ret, invis; if(!referree) return 0; invis = (int)referree->query_invis(); if(!invis || !referree->query_creator() || _in_reference_allowed) return 1; _in_reference_allowed = 1; if(!referrer) referrer = this_player(); if (objectp(referrer)) { if(!referrer || (referree == referrer)) { _in_reference_allowed = 0; return 1; } referrer_name = referrer->query_name(); referrer_obj = referrer; } else if (stringp(referrer)) { referrer_name = referrer; referrer_obj = find_player(referrer); } else { _in_reference_allowed = 0; return 0; } if(!referrer_name) { _in_reference_allowed = 0; return 1; } allowed = (string *)referree->query_allowed(); if ( pointerp( allowed ) ) { if ( member_array( referrer_name, allowed ) != -1 || (member_array("playtesters", allowed) != -1 && PLAYTESTER_HAND->query_playtester(referrer_name))) { _in_reference_allowed = 0; return 1; } } switch(invis) { case 3 : ret = master()->high_programmer( referrer_name ); break; case 2 : ret = master()->query_lord( referrer_name ); break; case 1 : if(referrer_obj) ret = referrer_obj->query_creator(); else ret = PLAYER_HANDLER->test_creator( referrer_name ); break; default : ret = 1; } _in_reference_allowed = 0; return ret; } /** * @ignore yes */ object find_living( string word ) { object thing; if ( !word ) { return 0; } #if efun_defined(find_living) thing = efun::find_living( word ); #else thing = LIV->find_living( word ); #endif if ( !thing || !this_player() ) { return thing; } if ( reference_allowed( thing ) ) { return thing; } return 0; } /* find_living() */ /** * @ignore yes */ object find_player( string word ) { object thing; if ( !word ) { return 0; } #if efun_defined(find_player) thing = efun::find_player( word ); #else thing = LIV->find_player( word ); #endif if ( !thing || !this_player() ) { return thing; } if ( reference_allowed( thing ) && thing->query_property( "player" ) ) { return thing; } return 0; } /* find_player() */ /** * @ignore yes */ object *users() { if ( !this_player() || ( previous_object() == master() ) || ( previous_object() == find_object( "/obj/shut" ) ) || ( previous_object() == find_object( "/obj/handlers/livings") ) ) { return efun::users(); } return filter( efun::users(), (: reference_allowed :) ); } /* users() */ /** @ignore yes */ object *named_livings() { #if efun_defined(named_livings) return filter( efun::named_livings(), (: reference_allowed :) ); #else return filter( LIV->named_livings(), (: reference_allowed :) ); #endif } /* named_livings() */ /** @ignore yes */ object *children(string name) { if (strsrch(name, "global/lord") != -1) { return filter( efun::children(name), (: reference_allowed :) ); } return efun::children(name); } /* children() */ /** * This method calls an event on all the users online. * It does not do any filtering, so it will send messages to people who * are earmuffed and ignoring or lord invisible or whatever. * * @param from the person it is from * @param name the name of the event * @param args the arguments to the event */ void user_event( mixed from, mixed first, mixed args ... ) { if ( stringp( from ) ) { call_other( efun::users(), "event_"+ from, previous_object(), first, args ... ); } else { if ( objectp( from ) && stringp( first ) ) { call_other( efun::users(), "event_"+ first, from, args ... ); } } /* event( efun::users(), first, args ... ); */ } /* user_event() */ mixed unguarded(function f); /** * This method flushes out all the buffered stuff for the log files. */ void flush_log_files() { string fname; string data; _log_file_flush_id = 0; foreach (fname, data in _log_file_info) { if (file_size(LOG_NAME(fname)) > MAX_SIZE) { unguarded((: rm, LOG_NAME(fname)+".old" :)); unguarded((: rename, LOG_NAME(fname), LOG_NAME(fname)+".old" :)); } map_delete(_log_file_info, fname); unguarded((: write_file, LOG_NAME(fname), data :)); } _log_file_info = ([ ]); } /* flush_log_files() */ /** * This method writes a message out ot a log file. The log files are * normally in "/log". If a file doesn't start with '/ then "/log/" will be * preppended to it. * This does automatic removal of the log files after they get over * a certain length. The fmt and args parameters are used to print the * actual message. * <p> * If more than one argument is passed to this function, then sprintf will * be used to print out the results. * <p> * ie: log_file("ROGER", "%s: frog and %s\n", ctime(time()), query_gumboot()); * * @param name the name of the log file * @param fmt the format string * @param args the arguments to the sprintf */ varargs void log_file(string name, string fmt, mixed *args ...) { /* five screens */ if ( strlen( fmt ) > 8000 ) { /* five screens */ fmt = fmt[ 0 .. 7999 ] +"\n\nPlus more...\n\n"; } fmt = terminal_colour(fmt, ([])); if (!_log_file_flush_id) { _log_file_flush_id = call_out((: flush_log_files :), DELAY_LOG_FLUSH); } if (!_log_file_info[name]) { _log_file_info[name] = ""; } if (sizeof(args)) { _log_file_info[name] += sprintf(fmt, args ...); } else { _log_file_info[name] += fmt; } } /* log_file() */ #ifdef MUD_NAME /** * This method returns the name of the mud. * * @return the name of the mud */ string mud_name() { return capitalize(MUD_NAME); } /* mud_name() */ #endif /** * Write the file out to the screen. This should not be used if possible. * It is an interface to read_file and other things. You should perhaps * look at using more_string ort more_file. * * @param file the file name to cat * @param start_line the line to start on * @param number the number of lines to print * * @see /global/more_string->more_string() * @see /global/more_file->more_file() */ void cat(string file, int start_line, int number) { string bing; bing = read_file(file, start_line, number); if (bing) { printf("%s", bing[0..5000]); } } /* cat() */ #ifdef NEW_DRIVER /** * This method tells us if the object is a wizard. * * @return is the object a wizard */ int wizardp(mixed arg) { if (!objectp(arg)) { return 0; } return interactive(arg) && arg->query_creator(); } /* wizardp() */ #endif /** * @ignore yes */ int exec(object to, object from) { string s; object prev; if (file_name(previous_object())[0..12] == "/secure/login") { return efun::exec(to, from); } s = ""; if (prev = this_player()) { s += "TP:"+ sprintf("%8s ", prev -> query_name()); } if (prev = this_player(1)) { s += "TP1:"+ sprintf("%8s ", prev -> query_name()); } s += "PO:"+ sprintf("%8s ", file_name(previous_object())); log_file("ILLEGAL", "Exec: %-40s : %s\n", s, ctime( time() ) ); return 0; } /* exec() */ /** * @ignore yes */ varargs int call_out(mixed fun, int delay, mixed *args ...) { string func; function wrap = function(mixed func, mixed *args) { int t = real_time(); if(stringp(func)) { call_other(this_object(), func, args ...); } else if(functionp(func)) { evaluate(func, args ...); } if((real_time() - t) > 1) { "/secure/simul_efun"->tell_creator("wodan", " %d "); } }; wrap = bind(wrap, previous_object()); if (_callouttime != time()) { _callouttime = time(); _calloutfunc = ([]); } if(delay == 0) { func = functionp(fun)?""+functionp(fun):fun + file_name(previous_object()); if(_calloutfunc[func] == -1) delay = 2; else if(++_calloutfunc[func] > 100) { delay = 2; _calloutfunc[func] = -1; if ( stringp(previous_object()->query_name())) { log_file("CALL_OUT_LOG", ctime(time()) + ": " + "Object %O (%s) seems to loop in function %O.\n", previous_object(), previous_object()->query_name(), fun); } else { log_file("CALL_OUT_LOG", ctime(time()) + ": " + "Object %O (%s) seems to loop in function %O.\n", previous_object(), fun); } } } return evaluate(bind((: efun::call_out($(wrap), $(delay), $(fun), $(args)) :) , previous_object())); } /* call_out() */ /** * This returns the current verb name. This works if the verb ius used * through add_command or through add_action. * * @return the name of the verb */ string query_verb() { string verb; #if efun_defined(query_verb) verb = efun::query_verb(); #else verb = ""; #endif if ( ( verb != "" ) || !objectp( this_player() ) ) { return verb; } return (string)this_player()->query_current_verb(); } /* query_verb() */ /** * Does a string compare... But case insensative... * * @param str1 the string to compare * @param str2 the other string to compare * @return 0 if they are the same, <0 if they are less than each other, >0 if not * @see efun::strcmp() */ int strcasecmp(string str1, string str2) { return strcmp(lower_case(str1), lower_case(str2)); } /* strcasecmp() */ #define COMPAT_TAIL #ifdef COMPAT_TAIL /* This version is strictly compatible with the old version */ /** * This will print the last bit of a file. * @param fname the file to read * @return 1 if it succeeded, 0 if it failed */ int tail(string fname) { int offset = file_size(fname); string str; if (offset < 0) { return 0; } offset -= 54 * 20; if (offset < 0) { offset = 0; } str = read_bytes(fname, offset, 1080); if (!str) { return 0; } if (offset) { str = str[strsrch(str, "\n")+1..]; } write(str); return 1; } /* tail() */ #else /* This version is slightly extended and compatible in spirit, but doesn't * reproduce the oddities of the original tail() efun. Note that it also * returns the string, so write(tail(fname)) is needed for strict * compatibility. */ /** * This will print the last bit of a file. * @param fname the file to read * @return 1 if it succeeded, 0 if it failed */ varargs string tail(string fname, int nlines) { int chunk = nlines * 80; int offset = file_size(fname); int num_nl, p, skip; string str = ""; reset_eval_cost(); while (offset > 0 && num_nl <= nlines) { num_nl = 0; offset -= chunk; if (offset < 0) { chunk += offset; /* negative */ offset = 0; } str = read_bytes(fname, offset, chunk) + str; p = -1; while (p < sizeof(str)-1 && p = member_array('\n', str, p+1)) num_nl++; } skip = num_nl - nlines; p = -1; while (skip--) p = member_array('\n', str, p+1); return str[p..]; } #endif /** * This method prints a message to the player. * @param str the message to send */ void write(mixed str) { if (!this_player()) { return ; } if (intp(str)) { str = ""+str; } this_player()->do_efun_write(str); } /* write() */ /** @ignore yes */ int notify_fail( mixed stuff ) { if (!this_player()) { return 0; } if ( functionp( stuff ) ) { stuff = evaluate( stuff ); } if ( !stringp( stuff ) ) { return 0; } this_player()->print_messages(); stuff = (string)this_player()->convert_message( stuff ); /* for observer-dependent shorts */ stuff = (string)this_player()->fit_message( stuff ); /* for capitalisation and indentation */ stuff = (string)this_player()->fix_string( stuff ); /* for colour */ #if efun_defined(notify_fail) return efun::notify_fail( stuff ); #else return _notify_fail( stuff ); #endif } /* notify_fail() */ /** * Replaces all occurances of a set of strings in the input string. Replaces * an individual or an array of strings with new values. If the second * argument is an array of strings, then the 1st, 3rd, 5th... elements will * be the strings to search for and the 2nd, 4th, 6th etc will be the strings * to replace with. If three arguments are specified then the second is the string * to search for, the third the one to replace. * <p> * Eg:<br> * str = replace(str, ({ "search", "replace", "orange", "apple" });<p> * That will replace all occurances of "search" with "replace" and "orange" * with "apple". * * @param str the string to do the replacement in * @param bing the search or array argument * @param rep the replacement string, or null * * @return the string with all the replacements done */ varargs string replace(string str, mixed bing, string rep) { int i; if (pointerp(bing)) { for (i=0;i<sizeof(bing);i+=2) { if (stringp(bing[i]) && stringp(bing[i+1])) { str = replace_string(str, bing[i], bing[i+1]); } } return str; } if(!stringp(str) || !stringp(rep)) return str; return replace_string(str, bing, rep); } /* replace() */ /** * This method returns a string with each word capitalized. *<p> * @param words the string to capitalize * @return the string with all words capitalized */ string cap_words(string words) { return implode(map(explode(words, " "), (: capitalize :)), " "); } /** * This method will return an array that contains no duplicates. * uniq_array.c * Written by: Wodan * <p> * This function will return an array that contains no duplicates. * Gotta love them mappings. :) * @param arr the array to convert * @return an array with no duplicates */ mixed *uniq_array(mixed *arr){ return keys(allocate_mapping(arr, 1)); } /* uniq_array() */ /** * This method sends a polite shout to everyone on line. It checks * for earmuffs and all that sort of stuff. * @param words the message to shout * @param avoid who not to tell the message to */ varargs void shout( string words, object avoid ) { object thing, *things; things = efun::users(); foreach( thing in things ) { if ( thing && ( thing != avoid ) && !thing->check_earmuffs( "shout" ) ) { if ( thing != this_player() ) { thing->event_say( previous_object(), words, ({ }) ); } } } } /* shout() */ /** @ignore yes */ void reset_eval_cost() { //string new_reset_eval_message; //_new_reset_eval_message = sprintf("%s: reset_eval_cost() called in %O", //ctime(time()), previous_object()); //if ( !strcmp(_new_reset_eval_message, _reset_eval_message) ) { //_reset_eval_message_count++; //} else { // log_file("RESET_EVAL_COST", sprintf("%s %i time(s).\n", // reset_eval_message, // reset_eval_message_count )); //_reset_eval_message_count = 1; //_reset_eval_message = new_reset_eval_message; //} efun::reset_eval_cost(); } /* reset_eval_cost() */ #if !efun_defined(event) /** @ignore yes */ varargs int member_array(mixed item, mixed arr, int start, int flag){ if(!flag) return efun::member_array(item, arr, start); if(stringp(item)){ string *tmp; tmp = map(arr, (:$1[0..$(strlen(item)-1)]:)); return efun::member_array(item, tmp, start); } } /** @ignore yes */ void event(mixed ob, string func, mixed rest ...){ object origin = previous_object(), *bing; string name = "event_" + func; if(arrayp(ob)){ ob = filter(ob, (:$1:)); call_other(ob, name, origin, rest ...); return; } else if(!objectp(ob)) { return; } call_other(ob, name, origin, rest ...); bing = all_inventory(ob); bing -= ({origin}); bing = filter(bing, (:$1:)); call_other(bing, name, origin, rest ...); } #endif #if !efun_defined(add_action) /** @ignore yes */ void move_object(mixed ob){ object tp, *obs, bing; if(stringp(ob)) { ob = find_object(ob); } evaluate(bind((: efun::move_object($(ob)):), previous_object())); if(ob->no_init()){ return; } tp = this_player(); if(living(previous_object())){ efun::set_this_player(previous_object()); ob->init(); (all_inventory(ob) - ({previous_object()}))->init(); } obs = filter(all_inventory(ob), (:living($1) && $1 != $(previous_object()):)); foreach(bing in obs){ efun::set_this_player(bing); previous_object()->init(); } if(living(ob)){ efun::set_this_player(ob); previous_object()->init(); } efun::set_this_player(tp); } #endif #if efun_defined(db_exec) /** @ignore yes */ varargs mixed db_exec(int fd, string fmt, mixed *args ...) { _db_fds[fd] = time(); if (!sizeof(args)) { return efun::db_exec(fd, fmt); } else { return efun::db_exec(fd, sprintf(fmt, args ...)); } } varargs int db_connect(mixed *args ...){ int fd; string ob = file_name(previous_object()); switch(sizeof(args)){ //gnaaaaaa case 0: case 1: error("Not enough arguments to db_connect."); case 2: fd = efun::db_connect(args[0], args[1]); break; case 3: fd = efun::db_connect(args[0], args[1], args[2]); break; default: fd = efun::db_connect(args[0], args[1], args[2], args[3]); } _db_fds[fd] = time(); _db_obs[fd] = ob; if(sizeof(keys(filter(_db_obs, (:$2 == $(ob):)))) > 3){ log_file("FDS", "file: %s\n", ob); } return fd; } mixed *db_fetch(int fd, int row){ _db_fds[fd] = time(); return efun::db_fetch(fd, row); } void db_close(int fd){ map_delete(_db_fds, fd); map_delete(_db_obs, fd); efun::db_close(fd); } string db_fds(){ int *fds = keys(_db_fds); return implode(fds, (:$1 + sprintf("fd: %d time: %s object: %s\n", $2, ctime($(_db_fds)[$2]), $(_db_obs)[$2]):), ""); } #else varargs int db_connect(mixed *args ...){ error("No database installed"); } /** @ignore yes */ varargs string db_exec(int fd, string fmt, mixed *args ...) { return "No database installed"; } /** @ignore yes */ mixed *db_fetch(int fd, int row){ error("No database installed"); } /** @ignore yes */ void db_close(int fd){} #endif /** @ignore yes */ void shutdown(int bing){ string thing = base_name(previous_object()); if(thing != "/obj/shut" && thing != "/global/lord"){ unguarded((:write_file, "/d/admin/log/SHUTDOWN.log", sprintf("value %d\n%s", bing, back_trace()):)); } efun::shutdown(bing); } /** @ignore yes */ #if !efun_defined(real_time) int real_time(){return time();} #endif #if efun_defined(memory_summary) string memory_summary() { return "Not on this mud...\n"; } /* memory_summary() */ #endif /** * Debug info Simul Efun * @author Terano * @started 17/8/00 * To fix gaping security holes. */ #if efun_defined(debug_info) string debug_info( int operation, object ob ) { string path; if ( operation != 2 ) return efun::debug_info( operation, ob ); path = base_name( ob ); path += ".c"; if ( !master()->valid_read( path, this_player()->query_name(), "debug_info" ) ) { this_object()->unguarded((:write_file, "/d/admin/log/DEBUG_INFO.log", sprintf( "%s: %s tried to debug %s.\n", ctime( time() ), this_player()->query_name(), base_name( ob ) ) :)); return "You are not authorised to do that.\n"; } if ( interactive( ob ) && !this_player()->query_lord() && ob != this_player()) { this_object()->unguarded((:write_file, "/d/admin/log/DEBUG_INFO.log", sprintf( "%s: %s tried to debug %s.\n", ctime( time() ), this_player()->query_name(), ob->query_name() ) :)); return "Only Lords can dump an interactive object.\n"; } return efun::debug_info( operation, ob ); } #endif varargs int new_call_out(mixed fun, int delay, mixed *args ...) { string func; function wrap = function(mixed func, mixed *args) { int t = real_time(); if(stringp(func)) { call_other(this_object(), func, args ...); } else if(functionp(func)) { evaluate(func, args ...); } if((real_time() - t) > 1) { "/secure/simul_efun"->tell_creator("wodan", " %d "); } }; wrap = bind(wrap, previous_object()); if (_callouttime != time()) { _callouttime = time(); _calloutfunc = ([]); } if(delay == 0) { func = functionp(fun)?""+functionp(fun):fun + file_name(previous_object()); if(_calloutfunc[func] == -1) delay = 2; else if(++_calloutfunc[func] > 100) { delay = 2; _calloutfunc[func] = -1; if ( stringp(previous_object()->query_name())) { log_file("CALL_OUT_LOG", ctime(time()) + ": " + "Object %O (%s) seems to loop in function %O.\n", previous_object(), previous_object()->query_name(), fun); } else { log_file("CALL_OUT_LOG", ctime(time()) + ": " + "Object %O (%s) seems to loop in function %O.\n", previous_object(), fun); } } } return evaluate(bind((: efun::call_out($(wrap), $(delay), $(fun), $(args)) :) , previous_object())); }