/**
* Does all the help stuff you know and love.
* @author Pinkfish
* @started Tue Nov 4 14:55:39 EST 1997
*/
#include <nroff.h>
#define SYNONYMS "/doc/SYNONYMS"
#define MATCH_THRESHOLD 55
private nosave mapping help_files_player, help_files_creator, help_files_pt;
private nosave mapping synonyms;
private nosave string *player_dirs, *creator_dirs, *pt_dirs;
/**
* Reads in the directories and places the results neatly into a mapping.
* @param directories the directories to recursively read
* @return a mapping with the locations of the help files
*/
private mapping read_directories( string *directories ) {
string fname, dir;
mixed file, files;
mapping ret;
int i;
ret = ([ ]);
for( i = 0; i < sizeof(directories); i++ ) {
dir = directories[i];
files = get_dir( dir+"*", -1 ) - ({"ERROR_REPORTS"});
foreach( file in files ) {
fname = file[0];
if( file[1] == -2 ) {
if( fname != "." &&
fname != ".." &&
fname != "old" &&
fname != "RCS") {
directories += ({ dir + fname + "/" });
}
} else if( fname != "." && fname != ".." && fname != "old") {
if( !ret[fname] )
ret[fname] = ({ dir + fname });
else
ret[fname] += ({ dir + fname });
// Turn '_' into spaces.
if( strsrch( fname, "_") > 0 ) {
fname = replace( fname, "_", " ");
if( !ret[fname] )
ret[fname] = ({ dir + fname });
else
ret[fname] += ({ dir + fname });
}
}
}
}
return ret;
} /* read_directories() */
private mapping read_synonyms() {
string *bits, bit;
mapping tmp;
tmp = ([ ]);
bits = explode( read_file(SYNONYMS), "\n");
foreach( bit in bits ) {
bits = explode( bit, " ");
tmp[bits[0]] = bits[1];
}
return tmp;
} /* read_synonyms() */
/**
* This goes through and recreates the hash table for the dirs.
*/
void rehash_dirs() {
help_files_player = read_directories(player_dirs);
help_files_creator = read_directories(creator_dirs);
help_files_pt = read_directories(pt_dirs);
synonyms = read_synonyms();
} /* rehash_dirs() */
void create() {
// These dirs will all be depth searched...
player_dirs = ({ "/doc/helpdir/",
"/doc/concepts/",
"/doc/known_command/",
"/doc/room/",
"/doc/object/"
});
creator_dirs = ({ "/doc/creator/",
"/doc/driver/",
"/doc/policy/",
"/doc/new/"
});
pt_dirs = ({"/doc/playtesters/"});
unguarded( (: rehash_dirs() :) );
} /* create() */
int *find_match_in_array( string entry, string *items ) {
int i, j, elength, ilength, this_match, best_match, best_try;
elength = strlen( entry );
best_match = this_match = -1;
for( i = sizeof( items ) - 1; i >= 0; i--, this_match = 0 ) {
ilength = strlen( items[ i ] );
for( j = 0; j < elength && j < ilength; j++ )
if( entry[ j ] == items[ i ][ j ] ||
entry[ j ] == items[ i ][ j - 1 + ( j == 0 ) ] ||
entry[ j ] == items[ i ][ j + 1 - ( j + 1 == ilength ) ] )
++this_match;
this_match = 100 * this_match / ( j == elength ? ilength : elength );
if( this_match > best_match ) {
best_match = this_match;
best_try = i;
}
}
return ({ best_try, best_match });
} /* find_match_in_array() */
/** @ignore yes */
private string letter_name( int letter, int size ) {
string ret;
if( size > 26 ) {
ret = "aa";
ret[0] = 'a' + ( letter / 26 );
ret[1] = 'a' + ( letter % 26 );
return ret;
}
ret = "a";
ret[0] = 'a' + letter;
return ret;
} /* letter_name() */
/** @ignore yes */
private string start_letter( int size ) { return letter_name( 0, size ); }
/** @ignore yes */
private string end_letter( int size ) {
return letter_name( size - 1, size );
} /* end_letter() */
/** @ignore yes */
private int query_number_from_string( string name, int size ) {
int pos;
if( size > 26) {
if( strlen(name) != 2 )
return -1;
name = lower_case(name);
if( name[0] < 'a' || name[0] > 'z' )
return -1;
if( name[1] < 'a' || name[1] > 'z' )
return -1;
pos = ( name[0] - 'a' ) * 26 + name[1] - 'a';
if( pos >= size )
return -1;
return pos;
}
if( strlen(name) != 1 )
return -1;
name = lower_case(name);
if( name[0] < 'a' || name[0] > 'z' )
return -1;
pos = name[0] - 'a';
if( pos >= size )
return -1;
return pos;
} /* query_number_from_string() */
/** @ignore yes */
void do_help( mixed stuff ) {
string str;
str = evaluate(stuff[1]);
if( !str || !strlen(str) ) {
write("Broken help file!\n");
return;
}
TP->more_string( str, stuff[0] );
} /* do_help() */
/*
* Make a string from nroff input...
*/
private string nroff_file( string name, string nroff_dir ) {
string nroff_fn, str;
nroff_fn = nroff_dir + replace( name, "/", ".");
str = NROFF_H->cat_file( nroff_fn, 1 );
if( !str ) {
NROFF_H->create_nroff( name, nroff_fn );
str = NROFF_H->cat_file( nroff_fn, 0 );
}
return str;
} /* nroff_file() */
/**
* This method nips through the list of names doing the nroff stuff.
* The array which is returned is an array of arrays, each internal
* array consists of a name and help string.
* @param names the array of names to process
* @param nroff_dir the nroff directory to use for the output
* @return an array of arrays
*/
mixed create_help_files( string *names, string nroff_dir ) {
mixed ret;
string *bits, name;
ret = ({ });
foreach( name in names ) {
bits = explode( name, "/");
ret += ({ ({ bits[<1] + " (" + name + ")",
(: nroff_file( $(name), $(nroff_dir) ) :) }) });
}
return ret;
} /* create_help_files() */
/**
* Searches the lists for things which we might have help on.
* The array which is returned is an array of arrays, each internal
* array consists of a name and help string.
* @param name the help to search for
* @param creator is this a creator searching
* @return an array of arrays
*/
mixed query_help_on( string name, int creator, int playtester ) {
string *files, *tmp;
files = ({ });
name = replace_string( name, " ", "_");
if( help_files_player[name] )
files += create_help_files( help_files_player[name], NROFF_DIR );
if( playtester && help_files_pt && help_files_pt[name] )
files += create_help_files( help_files_pt[name], NROFF_DIR );
if( creator ) {
if( help_files_creator && help_files_creator[name] )
files += create_help_files( help_files_creator[name], NROFF_DIR );
if( tmp = AUTODOC_H->query_help_on_func(name) )
files += create_help_files( tmp, NROFF_DIR );
}
return files;
} /* query_help_on() */
/*
* Returns a list of possible help files...
*/
private mixed help_list( string name ) {
string *stuff;
mixed str;
object *fluff, blue;
stuff = query_help_on( name, creatorp(TP), playtesterp(TP) );
if( name == "room" || name == "here") {
str = ENV(TP)->help_function();
if( pointerp(str) )
stuff += str;
else if( str )
stuff += ({ ({ ENV(TP)->short(), str }) });
}
if( stringp( str = TP->help_spell(name) ) )
stuff += ({ ({ name + " (Spell)", (: $(str) :) }) });
if( functionp(str) )
stuff += ({ ({ name + " (Spell)", str }) });
if( member_array( name, TP->query_channels() ) != -1 &&
stringp( str = CHANNEL_H->channel_syntax( name, 1 ) ) )
stuff += ({ ({ name + " (Channel)", (: $(str) :) }) });
if( str = SOUL_H->help_string(name) )
stuff += ({ ({ name + " (Soul)",
(: $(SOUL_H)->help_string($(name)) :) }) });
fluff = filter( match_objects_for_existence( name, ({ TP, ENV(TP) }) ),
(: $1 && $1->help_function() :) );
if( sizeof(fluff) )
foreach( blue in fluff )
stuff += blue->help_function();
return stuff;
} /* help_list() */
/**
* This method deals with the case where an entire string matches.
* @param name the name to look for help on
* @return 1 if the help was found, 0 if not
*/
int cmd( string name ) {
mixed list;
string suggestion, str;
int *matches, i;
list = help_list(name);
// find out if they're looking for a synonym
// eg. colour == colour or plan == finger.
if( !sizeof(list) && mapp(synonyms) && synonyms[name] )
list = help_list(synonyms[name]);
if( !sizeof(list) ) {
if( PLAYER_H->test_user(name) ) {
add_failed_mess("That is a player, silly.\n");
return 0;
}
// try a match for similarity.
list = keys(help_files_player) + ({"command_list", "concepts"});
matches = find_match_in_array( name, list );
if( matches[1] > MATCH_THRESHOLD ) {
suggestion = list[matches[0]];
} else {
// try a match for similarity among the synonyms
list = keys(synonyms);
matches = find_match_in_array( name, list );
if( matches[1] > MATCH_THRESHOLD )
suggestion = synonyms[list[matches[0]]];
}
if( !creatorp(TP) ) {
log_file("MISSING_HELP", "%s %s looked for help on %s, "
"recommended %s\n", ctime(time()), TP->query_name(),
name, suggestion );
}
if( !suggestion )
return notify_fail("Could not find any help on '"+name+"'. You "
"might find what you're looking for in 'help essentials'.\n");
return notify_fail("Could not find any help on '"+name+"'. Perhaps "
"you are looking for "+suggestion+".\n");
}
if( sizeof(list) == 1 ) {
if( creatorp(TP) )
tell_object( TP, "Reading - "+list[0][0]+":\n");
do_help(list[0]);
return 1;
}
str = "";
for( i = 0; i < sizeof(list); i++ )
str += sprintf("%s) %s\n", letter_name( i, sizeof(list) ), list[i][0] );
printf("%s help found multiple matches, please choose one of:\n"
"%-*#s\nChoice: ", mud_name(), TP->query_cols(), str );
input_to("help_input", 0, list );
return 1;
} /* cmd() */
/**
* The input loop for the help routines.
* @param str the just inputed string
* @param list the set of helps to choose from
*/
void help_input( string str, mixed list ) {
int num;
str = lower_case(str);
if( str == "quit" || str == "**" || str == "." || str == "" ) {
write("Ok, exiting help.\n");
return ;
}
if( ( num = query_number_from_string( str, sizeof(list) ) ) == -1 ) {
printf("Incorrect choice, must be between %s and %s.\nChoice: ",
start_letter( sizeof(list) ), end_letter( sizeof(list) ) );
input_to("help_input", 0, list );
return;
}
if( creatorp(TP) )
tell_object( TP, "Reading - "+list[num][0]+":\n");
do_help(list[num]);
} /* help_input() */
/**
* This method deals with the case where a command pattern was matched.
* @param name the command to get help on
* @return 0 if the command does not exist, 1 if it does exist
*/
int command_cmd( string name ) {
mixed help;
if( !help = TP->help_command(name) )
return notify_fail("No such command as '"+name+"'.\n");
if( functionp(help) )
help = evaluate(help);
TP->more_string( help, name );
return 1;
} /* command_cmd() */
/**
* This method deals with the case where a soul pattern was matched.
* @param name the soul to get help on
* @return 0 if the soul does not exist, 1 if it does exist
*/
int soul_cmd(string name) {
string help;
if( !help = SOUL_H->help_string(name) )
return notify_fail("No such soul as '"+name+"'.\n");
TP->more_string( help, name );
return 1;
} /* soul_cmd() */
/**
* This method deals with the case where a ritual or spell pattern was matched.
* @param name the ritual or spell to get help on
* @param spell 0 if it is a spell, 1 if it is a ritual
* @return 0 if the ritual or spell does not exist, 1 if it does exist
*/
int spell_cmd(string name, int spell) {
mixed help;
if( !help = TP->help_spell(name) )
return notify_fail("No such spell as '" + name + "'.\n");
TP->more_string( ( functionp(help) ? evaluate(help) : help ), name );
return 1;
} /* spell_cmd() */
/*
* Print all the names of all the files in a dir...
*/
private void list_help( string title, string dir ) {
string *files;
files = get_dir( dir+"*") - ({".", "..", "ERROR_REPORTS", "RCS", "old"});
printf("%s\n%-#*s\n", title, (int)TP->query_cols(),
implode( files, "\n") );
} /* list_help() */
/**
* This method gives the list of commands currently available.
* @return always returns 1
*/
int command_list_cmd() {
list_help("Command list, try 'help concepts' for a list of concepts.",
"/doc/helpdir/");
return 1;
} /* command_list_cmd() */
/**
* This method gives the list of concepts currently available.
* @return always returns 1
*/
int concepts_list_cmd() {
list_help("Concepts list, try 'help command_list' for a list of commands.",
"/doc/concepts/");
return 1;
} /* concepts_list_cmd() */
string query_synonym( string name ) {
if( mapp(synonyms) && synonyms[name] )
return synonyms[name];
return "";
} /* query_synonym() */
/**
* This method returns the mapping of all the player help files.
* @return the mapping of player help files
*/
mapping query_help_files_player() { return help_files_player; }
/**
* This method returns the mapping of all the creator help files.
* @return the mapping of creator help files
*/
mapping query_help_files_creator() { return help_files_creator; }
/**
* This method returns the mapping of all the creator help files.
* @return the mapping of creator help files
*/
mapping query_help_files_pt() { return help_files_pt; }
/** @ignore yes */
mixed query_patterns() {
return ({ "<string>", (: cmd($4[0]) :),
"command <string>", (: command_cmd($4[0]) :),
"spell <string>", (: spell_cmd($4[0], 0 ) :),
"ritual <string>", (: spell_cmd($4[0], 1 ) :),
"soul <string>", (: soul_cmd($4[0]) :),
"command_list", (: command_list_cmd() :),
"concepts", (: concepts_list_cmd() :),
"", (: concepts_list_cmd() :) });
} /* query_patterns() */
/** @ignore yes */
mixed stats() {
return ({
({"player help files", sizeof( keys(help_files_player) ) }),
({"creator help files", sizeof( keys(help_files_creator) ) }),
({"playtester help files", sizeof( keys(help_files_pt) ) }),
({"autodoc help map", AUTODOC_H->query_help_map_size() }) ,
});
} /* stats() */