//***************************************************************************** // // help.c // // Contained within is the new help module for NakedMud, instituted at v3.6. // It allows for help files to exist both on disc, and to be built on the fly // by other game modules (but not saved to disc). // //***************************************************************************** #include <dirent.h> #include "../mud.h" #include "../utils.h" #include "../storage.h" #include "../character.h" #include "help.h" #include "hedit.h" //***************************************************************************** // mandatory modules //***************************************************************************** #include "../scripts/scripts.h" #include "../scripts/pymudsys.h" //***************************************************************************** // local variables, data structures, and functions //***************************************************************************** // the folder where we save persistent helpfiles to disc #define HELP_DIR "../lib/help" // the old file we used to store all help files in #define OLD_HELP_FILE "../lib/misc/help" // where we store all of our helpfiles NEAR_MAP *help_table; struct help_data { char *keywords; // words that bring up this helpfile char *info; // the information in the helpfile char *user_groups; // the user group the helpfile belongs to, if any char *related; // a list of other helpfiles that are related }; HELP_DATA *newHelp(const char *keywords, const char *info, const char *user_groups, const char *related) { HELP_DATA *data = malloc(sizeof(HELP_DATA)); data->keywords = strdupsafe(keywords); data->info = strdupsafe(info); data->user_groups = strdupsafe(user_groups); data->related = strdupsafe(related); return data; } void deleteHelp(HELP_DATA *data) { if(data->keywords) free(data->keywords); if(data->info) free(data->info); if(data->user_groups) free(data->user_groups); if(data->related) free(data->related); free(data); } HELP_DATA *helpRead(STORAGE_SET *set) { return newHelp(read_string(set, "keywords"), read_string(set, "info"), read_string(set, "user_group"), read_string(set, "related")); } STORAGE_SET *helpStore(HELP_DATA *data) { STORAGE_SET *set = new_storage_set(); store_string(set, "keywords", data->keywords); store_string(set, "info", data->info); store_string(set, "user_group", data->user_groups); store_string(set, "related", data->related); return set; } //***************************************************************************** // local functions //***************************************************************************** // // returns a list of all the help files that match the keyword in our help // table, in the event that what we are trying to look up is ambiguous. LIST *get_help_matches(const char *keyword) { return nearMapGetAllMatches(help_table, keyword); } // // returns where the help file would be stored on disc if it exists const char *get_help_file(const char *keyword) { static BUFFER *buf = NULL; char subdir = 'Z'; // Z is the default subdir for non-alpha kwds if(buf == NULL) buf = newBuffer(1); bufferClear(buf); if(isalpha(*keyword)) subdir = toupper(*keyword); bprintf(buf, "%s/%c/%s", HELP_DIR, subdir, keyword); return bufferString(buf); } //***************************************************************************** // in game commands //***************************************************************************** COMMAND(cmd_help) { char *kwd = NULL; HELP_DATA *data = NULL; if(!parse_args(ch, TRUE, cmd, arg, "| string", &kwd)) return; // did we supply an argument, or do we want to see a list of all help topics? if(kwd == NULL) { BUFFER *buf = newBuffer(1); NEAR_ITERATOR *near_i = newNearIterator(help_table); const char *kwd = NULL; HELP_DATA *data = NULL; LIST *viewable = newList(); LIST_ITERATOR *view_i = NULL; int count = 0; ITERATE_NEARMAP(kwd, data, near_i) { // if we can view the help file, list it if(!*data->user_groups || bitIsOneSet(charGetUserGroups(ch), data->user_groups)) // add it to our list of helps we can view listPut(viewable, strdupsafe(kwd)); } deleteNearIterator(near_i); // sort our list of helps listSortWith(viewable, strcasecmp); // build up our buffer of help topics view_i = newListIterator(viewable); ITERATE_LIST(kwd, view_i) { bprintf(buf, "%-20s", kwd); count++; // only 4 entries per row if((count % 4) == 0) bufferCat(buf, "\r\n"); } deleteListIterator(view_i); bufferCat(buf, "\r\n"); // show our list if(charGetSocket(ch)) page_string(charGetSocket(ch), bufferString(buf)); // garbage collection deleteBuffer(buf); deleteListWith(viewable, free); } // do we have a match? else if( (data = get_help(kwd, TRUE)) == NULL) send_to_char(ch, "No help exists on that topic.\r\n"); else if(*data->user_groups && !bitIsOneSet(charGetUserGroups(ch), data->user_groups)) send_to_char(ch, "You may not view that help file.\r\n"); else { BUFFER *buf = build_help(kwd); if(charGetSocket(ch)) page_string(charGetSocket(ch), bufferString(buf)); deleteBuffer(buf); } } //***************************************************************************** // implementation of help.h //***************************************************************************** BUFFER *build_help(const char *keyword) { HELP_DATA *data = get_help(keyword, TRUE); // no match if(data == NULL) return NULL; // build the info for our match else { BUFFER *buf = newBuffer(1); char header[128]; // +2 for \r\n, +1 for \0 center_string(header, data->keywords, 80, 128, TRUE); // build the header and the info bprintf(buf, "%s{n%s", header, data->info); // do we have a reference list? if(*data->related) bprintf(buf, "\r\nsee also: %s\r\n", data->related); return buf; } } void add_help(const char *keywords, const char *info, const char *user_groups, const char *related, bool persistent) { // build our help data HELP_DATA *data = newHelp(keywords, info, user_groups, related); // add it to the help table for all of our keywords LIST *kwds = parse_keywords(keywords); LIST_ITERATOR *kwd_i = newListIterator(kwds); char *kwd = NULL; ITERATE_LIST(kwd, kwd_i) { // remove any old copy we might of had nearMapRemove(help_table, kwd); // put our new entry nearMapPut(help_table, kwd, NULL, data); } deleteListIterator(kwd_i); // is this meant to be persistent? if(persistent && listSize(kwds) > 0) { STORAGE_SET *set = helpStore(data); char *primary = listGet(kwds, 0); storage_write(set, get_help_file(primary)); } // garbage collection deleteListWith(kwds, free); } void remove_help(const char *keyword) { HELP_DATA *help = get_help(keyword, FALSE); // does the helpfile exist? if(help != NULL) { // build up a list of all the keywords that reference this helpfile. Also, // figure out what the main topic is so we can delete its from disc if it // is persistent LIST *kwds = parse_keywords(help->keywords); LIST_ITERATOR *kwd_i = newListIterator(kwds); char *kwd = NULL; char *primary = listGet(kwds, 0); // remove all of our data from the help table ITERATE_LIST(kwd, kwd_i) { nearMapRemove(help_table, kwd); } deleteListIterator(kwd_i); // delete our primary file if(file_exists(get_help_file(primary))) unlink(get_help_file(primary)); // garbage collection deleteListWith(kwds, free); deleteHelp(help); } } // // returns the help file in our help table corresponding to the keyword HELP_DATA *get_help(const char *keyword, bool abbrev_ok) { return nearMapGet(help_table, keyword, abbrev_ok); } const char *helpGetKeywords(HELP_DATA *data) { return data->keywords; } const char *helpGetUserGroups(HELP_DATA *data) { return data->user_groups; } const char *helpGetRelated(HELP_DATA *data) { return data->related; } const char *helpGetInfo(HELP_DATA *data) { return data->info; } //***************************************************************************** // python hooks //***************************************************************************** PyObject *PyMudSys_add_help(PyObject *self, PyObject *args) { char *keywords = NULL; char *info = NULL; char *user_groups = NULL; char *related = NULL; if(!PyArg_ParseTuple(args,"ss|ss", &keywords, &info, &user_groups, &related)){ PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to add_help"); return NULL; } add_help(keywords, info, user_groups, related, FALSE); return Py_BuildValue("i", 1); } //***************************************************************************** // initialization //***************************************************************************** // // reads in all of our helpfiles from disc void read_help() { // directory info for reading in helpfiles char fname[SMALL_BUFFER]; struct dirent *entry = NULL; DIR *dir = NULL; char subdir = '\0'; // read in all of our help files from disc for(subdir = 'A'; subdir <= 'Z'; subdir++) { // build up our directory name sprintf(fname, "%s/%c", HELP_DIR, subdir); // open the directory dir = opendir(fname); // read in each file if(dir != NULL) { for(entry = readdir(dir); entry != NULL; entry = readdir(dir)) { if(*entry->d_name == '.') continue; sprintf(fname, "%s/%c/%s", HELP_DIR, subdir, entry->d_name); if(file_exists(fname)) { STORAGE_SET *set = storage_read(fname); add_help(read_string(set, "keywords"), read_string(set, "info"), read_string(set, "user_group"), read_string(set, "related"), FALSE); storage_close(set); } } closedir(dir); } } } // // read all of our helpfiles that were created with the old help system. The // master file is deleted after they are read in, since they are saved to the // new format. void read_old_help() { // if we still have the old help file, read in all of its contents and save // all of the entries in the new format. Then, delete the old help file if(file_exists(OLD_HELP_FILE)) { STORAGE_SET *set = storage_read(OLD_HELP_FILE); STORAGE_SET_LIST *list = read_list(set, "helpfiles"); STORAGE_SET *entry = NULL; BUFFER *dirbuf = newBuffer(1); // make all of our new directories mkdir(HELP_DIR, S_IRWXU | S_IRWXG); char subdir = '\0'; for(subdir = 'A'; subdir <= 'Z'; subdir++) { bufferClear(dirbuf); bprintf(dirbuf, "%s/%c", HELP_DIR, subdir); mkdir(bufferString(dirbuf), S_IRWXU | S_IRWXG); } deleteBuffer(dirbuf); // parse all of the helpfiles while( (entry = storage_list_next(list)) != NULL) add_help(read_string(entry, "keywords"), read_string(entry, "info"), read_string(entry, "user_group"), read_string(entry, "related"), TRUE); // close the set storage_close(set); // delete the old file; we don't need it any more unlink(OLD_HELP_FILE); } } void init_help() { // create our help table help_table = newNearMap(); // read in all of our helpfiles. Old helpfiles are read, merged into the // new system, and then deleted from disc as part of the old system read_help(); read_old_help(); // initialize our editing system init_hedit(); // add our commands add_cmd("help", NULL, cmd_help, "player", FALSE); // add all of our Python hooks PyMudSys_addMethod("add_help", PyMudSys_add_help, METH_VARARGS, "allows Python modules to add non-persistent helpfiles."); }