/* -*- LPC -*- */ /* * $Locker: pinkfish $ * $Id: help.c,v 1.43 2003/02/19 08:58:20 ceres Exp pinkfish $ * */ /** * Does all the help stuff you know and love. * * @author Pinkfish * @started Tue Nov 4 14:55:39 EST 1997 */ inherit "/cmds/base"; #include <nroff.h> #include <soul.h> #include <log.h> #include <autodoc.h> #include <login_handler.h> #include <player_handler.h> #include <command.h> #define SYNONYMS "/doc/SYNONYMS" #define MATCH_THRESHOLD 55 void do_help(mixed *stuff); private void list_help(string title, string dir); private mixed *help_list(string name); int command_cmd(string name); int here_cmd(); int object_cmd(object ob); int spell_cmd(string name, int spell); int file_help(string name); void rehash_dirs(); mixed *query_help_on(string name, int creator, int, int); mixed *create_help_files(string *names, string nroff_dir); private mapping read_directories(string *directories, int recurse); private mapping read_synonyms(); private string letter_name(int letter, mixed *things); private string start_letter( mixed *things ); private string end_letter( mixed *things ); private int query_number_from_string(string name, mixed *things ); private nosave mapping _help_files_player; private nosave mapping _help_files_creator; private nosave mapping _help_files_playtester; private nosave mapping _help_files_lord; private nosave mapping _synonyms; private nosave string *_player_dirs; private nosave string *_creator_dirs; private nosave string *_lord_dirs; private nosave string *_playtester_dirs; void create() { ::create(); /* These dirs will all be depth searched... */ _player_dirs = ({ "/doc/helpdir/", "/doc/concepts/", "/doc/known_command/", "/doc/room/", "/doc/spells/", "/doc/object/" }); _creator_dirs = ({"/doc/creator/", "/doc/driver/", "/doc/policy/", "/doc/new/" }); _playtester_dirs = ({ "/doc/playtesters/" }); _lord_dirs = ({"/doc/lord/"}); unguarded((: rehash_dirs() :)); } /* create() */ int *find_match_in_array( string entry, string *items ) { int i; int j; int elength; int ilength; int this_match; int best_match; int 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() */ /** * 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) { class command cmd; mixed *list; string str; string suggestion; int i; int *matches; list = help_list(name); // find out if they're looking for a synonym // eg. colour == colour or plan == finger. if (sizeof(list) == 0 && mapp(_synonyms) && _synonyms[name]) { list = help_list(_synonyms[name]); } if (sizeof(list) == 0) { if (PLAYER_HANDLER->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 (!this_player()->query_creator()) { log_file("MISSING_HELP", "%s %s looked for help on %s, " "recommended %s\n", ctime(time()), this_player()->query_name(), name, suggestion); } // No help so lets try some options. str = "Could not find any help on '" + name + "'. "; if (!suggestion && this_player()->query_known_command("newbie")) str += "Many useful commands are explained in 'help essentials'. "; else if(suggestion) str += "Perhaps you are looking for 'help " +suggestion + "'? "; cmd = new(class command, verb : name); // Is it a command? if((CMD_D->IsGRCommand(name) && this_player()->query_known_command(name)) || (CMD_D->HandleStars(cmd) && sizeof(((mixed *)CMD_D->GetPaths(cmd->verb) & (mixed *)this_player()->GetSearchPath()))) || sizeof(this_player()->query_parse_command_objects(name))) str += "The syntax for the command '" + name + "' can be found by " "entering 'syntax " + name + "'. "; if (environment(this_player()) && environment(this_player())->help_function()) str += "Type 'help here' for help on how to use this room."; return notify_fail(str + "\n"); } if (sizeof(list) == 1) { /* Cool... */ do_help(list[0]); return 1; } str = ""; for (i = 0; i < sizeof(list); i++) { // str += sprintf("%c) %s\n", ('a' + i), list[i][0]); str += sprintf("%s) %s\n", letter_name(i, list), list[i][0]); } printf("Discworld help found multiple matches, please choose one of:\n" "%-*#s\nChoice: ", this_player()->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, list ) ) == -1 ) { printf("Incorrect choice, must be between %s and %s.\nChoice: ", start_letter( list ), end_letter( list ) ); input_to("help_input", 0, list); return ; } // num = str[0] - 'a'; do_help(list[num]); } /* help_input() */ /** @ignore yes */ void do_help(mixed *stuff) { string str; str = evaluate(stuff[1]); if (!str || !strlen(str)) { write("Broken help file!\n"); } else { write("$P$" + stuff[0] + "$P$" + str); //this_player()->more_string(str, stuff[0]); } } /* do_help() */ /** * This method deals with 'help here'. */ int here_cmd() { mixed str; mixed *list; int i; str = environment(this_player())->help_function(); if(pointerp(str)) list = str; else if(str) list += ({ ({ environment(this_player())->short(), str }) }); else { write("There is no help available for this room.\n"); return 1; } if (sizeof(list) == 1) { /* Cool... */ do_help(list[0]); return 1; } str = ""; for (i = 0; i < sizeof(list); i++) { str += sprintf("%s) %s\n", letter_name(i, list), list[i][0]); } printf("Discworld help found multiple matches, please choose one of:\n" "%-*#s\nChoice: ", this_player()->query_cols(), str); input_to("help_input", 0, list); return 1; } /** * This method deals with 'help here'. */ int object_cmd(object ob) { mixed str; mixed *list; int i; str = ob->help_function(); if(pointerp(str)) list = str; else if(str) list += ({ ({ environment(this_player())->short(), str }) }); else { add_failed_mess("There is no help available for $I.\n", ({ob})); return -1; } if (sizeof(list) == 1) { /* Cool... */ do_help(list[0]); return 1; } str = ""; for (i = 0; i < sizeof(list); i++) { str += sprintf("%s) %s\n", letter_name(i, list), list[i][0]); } printf("Discworld help found multiple matches, please choose one of:\n" "%-*#s\nChoice: ", this_player()->query_cols(), str); input_to("help_input", 0, list); return 1; } /** * 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; help = this_player()->help_command(name); if (!help) { notify_fail("No such command as '" + name + "'.\n"); return 0; } else { if (functionp(help)) { help = evaluate(help); } write("$P$" + name + "$P$P" + help); 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; help = SOUL_OBJECT->help_string(name); if (!help) { notify_fail("No such soul as '" + name + "'.\n"); return 0; } else { write("$P$" + name + "$P$P" + help); 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; help = this_player()->help_spell(name); if (!help) { notify_fail("No such spell as '" + name + "'.\n"); return 0; } else { if (functionp(help)) { this_player()->move_string(evaluate(help), name); } else { write("$P$" + name + "$P$P" + help); } return 1; } } /* spell_cmd() */ /** * 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() */ /** * This method traps the error log stuff for some reason. * * @return always returns 0 */ int error_log_cmd() { notify_fail("Unable to get help on '" + ERROR_LOG + "'.\n"); return 0; } /* error_log_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" }); write("$P$Help$P$" + sprintf("%s\n%-#*s\n", title, (int)this_player()->query_cols(), implode(files, "\n"))); } /* list_help() */ /* * Returns a list of possible help files... */ /*private */ mixed *help_list(string name) { string* stuff; mixed str; object* fluff; object blue; stuff = query_help_on(name, this_player()->query_creator(), this_player()->query_lord(), this_player()->query_playtester()); if (name == "room" || name == "here") { str = environment(this_player())->help_function(); if (pointerp(str)) { stuff += str; } else if (str) { stuff += ({ ({ environment(this_player())->short(), str }) }); } else { add_failed_mess("There is no help available for this room.\n"); return 0; } } str = this_player()->help_spell(name); if (stringp(str)) { stuff += ({ ({ name + " (Spell)", (: $(str) :) }) }); } if (functionp(str)) { stuff += ({ ({ name + " (Spell)", str }) }); } str = SOUL_OBJECT->help_string(name); if (str) { stuff += ({ ({ name + " (Soul)", (: $(SOUL_OBJECT)->help_string($(name)) :) }) }); } fluff = filter(match_objects_for_existence(name, ({ this_player(), environment(this_player()) })), (: $1 && $1->help_function() :)); if (sizeof(fluff)) { foreach (blue in fluff) { stuff += blue->help_function(); } } return stuff; } /* help_list() */ /** * Gives a letter in the array for a given array position. * @param letter the letter number to return. * @param things the help list array to look for * @return Returns a letter between 'a' and 'z', if things is less * than 27 elements. Otherwise, it returns an array between 'aa' and 'zz'. */ private string letter_name(int letter, mixed *things) { string bing; if (sizeof(things) > 26) { bing = "aa"; bing[0] = 'a' + (letter / 26); bing[1] = 'a' + (letter % 26); return bing; } bing = "a"; bing[0] = 'a' + letter; return bing; } /* letter_name() */ /** * Returns the first letter in an help list * @param things the help list array to look in */ private string start_letter( mixed *things ) { return letter_name(0, things); } /* start_letter() */ /** * Returns the last letter in an help list * @param things the help list array to look in */ private string end_letter( mixed *things ) { return letter_name(sizeof(things) - 1, things); } /* end_letter() */ /** * This method translates a user inputted help id into an * actual array position. * @param name the text to look for * @param things the array to look in * @return an array index, with -1 for a bounds error. */ private int query_number_from_string(string name, mixed *things ) { int pos; if (sizeof(things) > 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 >= sizeof(things)) { 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 >= sizeof(things)) { return -1; } return pos; } /* query_number_from_string() */ /** @ignore yes */ mixed *query_patterns() { return ({ "<string>", (: cmd($4[0]) :), "here", (: here_cmd() :), "object <indirect:object:me-here'item'>", (: object_cmd($1[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() :), // ERROR_LOG, (: error_log_cmd() :), "", (: concepts_list_cmd() :) }); } /* query_patterns() */ /** * This goes through and recreates the hash table for the dirs. */ void rehash_dirs() { _help_files_player = read_directories(_player_dirs, 1); _help_files_creator = read_directories(_creator_dirs, 1); _help_files_lord = read_directories(_lord_dirs, 1); _help_files_playtester = read_directories(_playtester_dirs, 1); _synonyms = read_synonyms(); } /* rehash_dirs() */ /** * This method rehashes a specific directory. * @param dir the directory to rehash */ void rehash_specific_dir(string dir) { string start; mapping ret; string name; string* files; if (dir[<1] != '/') { dir += "/"; } start = "/" + implode(explode(dir, "/")[0..1], "/") + "/"; if (member_array(start, _player_dirs) != -1) { ret = read_directories(({ dir }), 0); foreach (name, files in ret) { if (_help_files_player[name]) { _help_files_player[name] &= files; } else { _help_files_player[name] = files; } } } if (member_array(start, _creator_dirs) != -1) { ret = read_directories(({ dir }), 0); foreach (name, files in ret) { if (_help_files_creator[name]) { _help_files_creator[name] &= files; } else { _help_files_creator[name] = files; } } } if (member_array(start, _lord_dirs) != -1) { ret = read_directories(({ dir }), 0); foreach (name, files in ret) { if (_help_files_lord[name]) { _help_files_lord[name] &= files; } else { _help_files_lord[name] = files; } } } if (member_array(start, _playtester_dirs) != -1) { ret = read_directories(({ dir }), 0); foreach (name, files in ret) { if (_help_files_playtester[name]) { _help_files_playtester[name] &= files; } else { _help_files_playtester[name] = files; } } } } private mapping read_synonyms() { string *bits, *bits2; int i; mapping tmp; tmp = ([ ]); bits = explode(read_file(SYNONYMS), "\n"); for(i=0; i<sizeof(bits); i++) { bits2 = explode(bits[i], " "); tmp[bits2[0]] = bits2[1]; } return tmp; } /** * 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, int recurse) { string *files; string fname; int i; string dir; mapping ret; ret = ([ ]); for (i = 0; i < sizeof(directories); i++) { dir = directories[i]; files = get_dir(dir + "*") - ({ "ERROR_REPORTS" }); foreach (fname in files) { if (file_size( dir + fname) == -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() */ /** * 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 * @param lord is this a lord searching * @return an array of arrays */ mixed *query_help_on(string name, int creator, int lord, int pt) { string *files; mapping map; files = ({ }); name = replace_string(name, " ", "_"); if (_help_files_player[name]) { files += create_help_files(_help_files_player[name], NROFF_DIR); } if (lord || creator || pt) { if(_help_files_playtester && _help_files_playtester[name]) { files += create_help_files(_help_files_playtester[name], NROFF_DIR); } } if (lord || creator) { if(_help_files_creator && _help_files_creator[name]) { files += create_help_files(_help_files_creator[name], NROFF_DIR); } map = AUTODOC_HANDLER->query_help_map(); if (map && map[name]) { files += create_help_files(map[name], NROFF_DIR); } } if (lord) { if(_help_files_lord && _help_files_lord[name]) { files += create_help_files(_help_files_lord[name], NROFF_DIR); } map = AUTODOC_HANDLER->query_help_map(); if (map && map[name]) { files += create_help_files(map[name], NROFF_DIR); } } return files; } /* query_help_on() */ string query_synonym(string name) { if(mapp(_synonyms) && _synonyms[name]) return _synonyms[name]; return ""; } /* * Makea string from a nroff input... */ private string nroff_file(string name, string nroff_dir) { string nroff_fn; string str; nroff_fn = nroff_dir + replace(name, "/", "."); str = NROFF_HAND->cat_file(nroff_fn, 1); if (!str) { NROFF_HAND->create_nroff(name, nroff_fn); str = NROFF_HAND->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) { int i; mixed *ret; string *bits; ret = ({ }); for (i = 0; i < sizeof(names); i++) { bits = explode(names[i], "/"); ret += ({ ({ bits[<1] + " (" + names[i] + ")", (: nroff_file($(names[i]), $(nroff_dir)) :) }) }); } return ret; } /* create_help_files() */ /** * 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; } /** @ignore yes */ mixed *stats() { mapping map; map = AUTODOC_HANDLER->query_help_map(); return ({ ({ "player help files", sizeof( keys(_help_files_player) ) }), ({ "creator help files", sizeof( keys(_help_files_creator) ) }), ({ "autodoc help map", sizeof(keys(map)) }) , }); } /* stats() */ /** @ignore yes * Prevent this command being cleaned up because it takes a long time * to initialise. */ int clean_up() { return 0; } /** Really make sure it cannot be unloaded. */ void dest_me() { }