//***************************************************************************** // // help.c // // This helpfile system has been set up to be amenable to allowing players to // edit helpfiles without having to worry about malicious users. // //***************************************************************************** #include "../mud.h" #include "../utils.h" #include "../storage.h" #include "../character.h" #include "../socket.h" #include "../editor/editor.h" #include "help.h" //***************************************************************************** // // local variables, data structures, and functions // //***************************************************************************** // the file we keep all of our help in #define HELP_FILE "../lib/misc/help" // how many buckets are in our help table #define HELP_TABLE_BUCKETS 27 // 26 for letters, 1 for non-alphas // the table we store all of our helpfiles in LIST *help_table[HELP_TABLE_BUCKETS]; typedef struct help_data { char *keywords; // words that bring up this helpfile char *info; // the information in the helpfile char *editor; // who edited the helpfile? char *timestamp; // when was it last edited? // user_group is currently unused char *user_group; // the user group the helpfile belongs to, if any LIST *backups; // a chronologically sorted list of backup helps } HELP_DATA; HELP_DATA *newHelp(const char *editor, const char *timestamp, const char *keywords, const char *user_group, const char *info) { HELP_DATA *data = malloc(sizeof(HELP_DATA)); data->keywords = strdupsafe(keywords); data->editor = strdupsafe(editor); data->info = strdupsafe(info); data->timestamp = strdupsafe(timestamp); data->user_group = strdupsafe(user_group); data->backups = newList(); return data; } void deleteHelp(HELP_DATA *data) { deleteListWith(data->backups, deleteHelp); if(data->keywords) free(data->keywords); if(data->editor) free(data->editor); if(data->timestamp) free(data->timestamp); if(data->user_group)free(data->user_group); if(data->info) free(data->info); free(data); } HELP_DATA *helpRead(STORAGE_SET *set) { HELP_DATA *data = newHelp(read_string(set, "editor"), read_string(set, "timestamp"), read_string(set, "keywords"), read_string(set, "user_group"), read_string(set, "info")); deleteList(data->backups); data->backups = gen_read_list(read_list(set, "backups"), helpRead); return data; } STORAGE_SET *helpStore(HELP_DATA *help) { STORAGE_SET *set = new_storage_set(); store_string(set, "keywords", help->keywords); store_string(set, "editor", help->editor); store_string(set, "timestamp", help->timestamp); store_string(set, "user_group",help->user_group); store_string(set, "info", help->info); store_list (set, "backups", gen_store_list(help->backups, helpStore)); return set; } // // HELP_DATA holds all of the information about a helpfile, but HELP_ENTRY // is a key/value pair used for storing helpfiles in the help table. // typedef struct help_entry { char *keyword; HELP_DATA *help; } HELP_ENTRY; HELP_ENTRY *newHelpEntry(const char *keyword, HELP_DATA *data) { HELP_ENTRY *entry = malloc(sizeof(HELP_ENTRY)); entry->keyword = strdupsafe(keyword); entry->help = data; return entry; } void deleteHelpEntry(HELP_ENTRY *entry) { if(entry->keyword) free(entry->keyword); free(entry); } int hentrycmp(HELP_ENTRY *entry1, HELP_ENTRY *entry2) { return strcasecmp(entry1->keyword, entry2->keyword); } int is_help_entry(const char *help, HELP_ENTRY *entry) { return strcasecmp(help, entry->keyword); } int is_help_abbrev(const char *help, HELP_ENTRY *entry) { return strncasecmp(help, entry->keyword, strlen(help)); } // // return the bucket that the specified helpfile belongs to // int helpbucket(const char *help) { if(isalpha(*help)) return (tolower(*help) - 'a'); else return HELP_TABLE_BUCKETS-1; } // // save all of the helpfiles // void save_help() { STORAGE_SET *set = new_storage_set(); LIST *help_list = newList(); // iterate across the table and save all the unique helpfiles int i; for(i = 0; i < HELP_TABLE_BUCKETS; i++) { LIST_ITERATOR *row_i = newListIterator(help_table[i]); HELP_ENTRY *entry = NULL; ITERATE_LIST(entry, row_i) listPut(help_list, entry->help); deleteListIterator(row_i); } store_list(set, "helpfiles", gen_store_list(help_list, helpStore)); deleteList(help_list); // write the set storage_write(set, HELP_FILE); // close the set storage_close(set); } // // add a helpfile to the help table. Assocciate all of its keywords with it // void add_help(HELP_DATA *help) { LIST *keywords = parse_keywords(help->keywords); LIST_ITERATOR *key_i = newListIterator(keywords); char *key = NULL; ITERATE_LIST(key, key_i) { LIST *row = help_table[helpbucket(key)]; HELP_ENTRY *new_entry = newHelpEntry(key, help); HELP_ENTRY *old_entry = NULL; // make sure we're not already in the help table if((old_entry = listRemoveWith(row, new_entry, hentrycmp)) != NULL) deleteHelpEntry(old_entry); listPutWith(row, new_entry, hentrycmp); } deleteListIterator(key_i); // garbage collection deleteListWith(keywords, free); } // // Returns the datastructure for the helpfile // HELP_DATA *get_help_data(const char *keyword, bool abbrev_ok) { HELP_ENTRY *entry = listGetWith(help_table[helpbucket(keyword)], keyword, (abbrev_ok ? is_help_abbrev : is_help_entry)); return (entry ? entry->help : NULL); } // // remove a helpfile from the help table // HELP_DATA *remove_help(const char *keyword) { HELP_ENTRY *entry = listRemoveWith(help_table[helpbucket(keyword)], keyword, is_help_entry); HELP_DATA *help = (entry ? entry->help : NULL); if(entry) deleteHelpEntry(entry); return help; } // // return a list of matches for the specified help keyword. If we find an // exact match, only that match will be contained within the list. // List must be deleted after use. // LIST *help_matches(const char *keyword) { int bucket = helpbucket(keyword); int key_len = strlen(keyword); LIST_ITERATOR *buck_i = newListIterator(help_table[bucket]); HELP_ENTRY *entry = NULL; LIST *matches = newList(); ITERATE_LIST(entry, buck_i) { // exact match if(!strcasecmp(keyword, entry->keyword)) { while(listSize(matches) > 0) listPop(matches); listPut(matches, entry); break; } else if(!strncasecmp(keyword, entry->keyword, key_len)) listQueue(matches, entry); } deleteListIterator(buck_i); return matches; } // // Show the contents of a helpfile to the character. If no argument is // supplied, list all of the helpfiles. If we try to get info on a help // that does not exist, list near-matches. // // usage: // help <topic> // COMMAND(cmd_help) { // make sure we can view it, first HELP_DATA *help = get_help_data(arg, TRUE); if(help != NULL && *help->user_group) { if(!bitIsSet(charGetUserGroups(ch), help->user_group)) { send_to_char(ch, "You may not view that help file.\r\n"); return; } } BUFFER *buf = build_help(arg); if(buf == NULL) send_to_char(ch, "No help exists on that topic.\r\n"); else { // send this to the character if(charGetSocket(ch)) page_string(charGetSocket(ch), bufferString(buf)); deleteBuffer(buf); } } // // same as cmd_write, but loads the contents of the helpfile into the buffer // COMMAND(cmd_hedit) { if(!arg || !*arg) { send_to_char(ch, "Which helpfile are you trying to edit?\r\n"); return; } if(!charGetSocket(ch)) { send_to_char(ch, "You must have a socket attached to use hedit.\r\n"); return; } // because we replace underscores with spaces in hlink, // we should do the same thing here for consistancy's sake char *ptr; for(ptr = arg; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; HELP_DATA *data = get_help_data(arg, FALSE); socketSetNotepad(charGetSocket(ch), (data ? data->info : "")); socketStartNotepad(charGetSocket(ch), text_editor); } // // Edit the contents of a helpfile // COMMAND(cmd_hupdate) { if(!arg || !*arg) { send_to_char(ch, "Which helpfile were you trying to update?\r\n"); return; } if(!charGetSocket(ch) || !bufferLength(socketGetNotepad(charGetSocket(ch)))) { send_to_char(ch, "You have nothing in your notepad! Try writing something.\r\n"); return; } // because we replace underscores with spaces in hlink, // we should do the same thing here for consistancy's sake char *ptr; for(ptr = arg; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; update_help(charGetName(ch), arg, bufferString(socketGetNotepad(charGetSocket(ch)))); } // // Link a keyword to an already-existing helpfile. underscores must be // used instead of spaces. // usage: // hlink [new_topic] [old_topic] // COMMAND(cmd_hlink) { char new_help[SMALL_BUFFER]; if(!arg || !*arg) { send_to_char(ch, "Link which helpfile to which?\r\n"); return; } arg = one_arg(arg, new_help); if(!*new_help || !*arg) { send_to_char(ch, "You must provide a new keyword and an old keyword " "to link it to.\r\n"); return; } // kill all of the underscores, and put spaces in instead char *ptr; for(ptr = new_help; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; for(ptr = arg; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; // find the help we're trying to link to HELP_DATA *help = get_help_data(arg, FALSE); if(help == NULL) { send_to_char(ch, "No help exists for %s.\r\n", arg); return; } // perform the link link_help(new_help, arg); send_to_char(ch, "%s linked to %s.\r\n", new_help, arg); } // // Unlink a keyword from its assocciated helpfile. // usage: // hunlink [topic] // COMMAND(cmd_hunlink) { if(!arg || !*arg) { send_to_char(ch, "Unlink which helpfile?\r\n"); return; } // because we replace underscores with spaces in hlink, // we should do the same thing here for consistancy's sake char *ptr; for(ptr = arg; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; // find the social we're trying to link to HELP_DATA *help = get_help_data(arg, FALSE); if(help == NULL) { send_to_char(ch, "No help exists for %s.\r\n", arg); return; } // perform the unlinking unlink_help(arg); send_to_char(ch, "The %s helpfile was unlinked.\r\n", arg); } //***************************************************************************** // // implementation of help.h // //***************************************************************************** void init_help() { // init all of our buckets int i; for(i = 0; i < HELP_TABLE_BUCKETS; i++) help_table[i] = newList(); // add all of our commands add_cmd("help", NULL, cmd_help, POS_UNCONSCIOUS, POS_FLYING, "player", FALSE, FALSE); add_cmd("hlink", NULL, cmd_hlink, POS_UNCONSCIOUS, POS_FLYING, "admin", FALSE, FALSE); add_cmd("hunlink", NULL, cmd_hunlink, POS_UNCONSCIOUS, POS_FLYING, "admin", FALSE, FALSE); add_cmd("hupdate", NULL, cmd_hupdate, POS_SITTING, POS_FLYING, "builder", FALSE, TRUE); add_cmd("hedit", NULL, cmd_hedit, POS_SITTING, POS_FLYING, "builder", FALSE, TRUE); // read in all of our helps STORAGE_SET *set = storage_read(HELP_FILE); STORAGE_SET_LIST *list = read_list(set, "helpfiles"); STORAGE_SET *entry = NULL; // parse all of the helpfiles while( (entry = storage_list_next(list)) != NULL) add_help(helpRead(entry)); storage_close(set); } BUFFER *build_help(const char *keyword) { // we have to edit the keyword... dup it char *arg = strdup(keyword); BUFFER *buf = newBuffer(MAX_BUFFER); // no argument. Show a list of all our help topics if(!*arg) { int count, i; bprintf(buf, "Help is available on the following topics:\r\n"); for(i = 0, count = 0; i < HELP_TABLE_BUCKETS; i++) { LIST_ITERATOR *buck_i = newListIterator(help_table[i]); HELP_ENTRY *entry = NULL; ITERATE_LIST(entry, buck_i) { bprintf(buf, "%-16s", entry->keyword); count++; // only 4 entries per row if((count % 5) == 0) bufferCat(buf, "\r\n"); } deleteListIterator(buck_i); } // make sure we add a newline to the end if((count % 5) != 0) bufferCat(buf, "\r\n"); } // pull out the helpfile else { // because we replace underscores with spaces in hlink, // we should do the same thing here for consistancy's sake char *ptr; for(ptr = arg; *ptr; ptr++) if(*ptr == '_') *ptr = ' '; LIST *matches = help_matches(arg); HELP_ENTRY *entry = NULL; // no matches found if(listSize(matches) == 0) bprintf(buf, "No help exists for %s.\r\n", arg); // one match found else if(listSize(matches) == 1) { entry = listPop(matches); char header[128]; // +2 for \r\n, +1 for \0 center_string(header, entry->keyword, 80, 128, TRUE); bprintf(buf, "%s{wBy: %-36s%40s\r\n\r\n{n%s", header, entry->help->editor, entry->help->timestamp, entry->help->info); } // more than one match found. Tell person to narrow search else { bprintf(buf, "More than one entry matched your query: \r\n"); LIST_ITERATOR *match_i = newListIterator(matches); ITERATE_LIST(entry, match_i) { bprintf(buf, "{c%s ", entry->keyword); } deleteListIterator(match_i); bufferCat(buf, "{n\r\n"); } // delete the list of matches we found deleteList(matches); } // clean up our mess free(arg); // return whatever we created return buf; } void update_help(const char *editor, const char *keyword, const char *info) { HELP_DATA *data = get_help_data(keyword, FALSE); if(data != NULL) { HELP_DATA *help_old = newHelp(data->editor, data->timestamp, data->keywords, "", data->info); if(data->editor) free(data->editor); if(data->timestamp) free(data->timestamp); if(data->info) free(data->info); data->editor = strdupsafe(editor); data->info = strdupsafe(info); data->timestamp = strdup(get_time()); listPut(data->backups, help_old); } else add_help(newHelp(editor, get_time(), keyword, "", info)); save_help(); } void unlink_help(const char *keyword) { // remove the helpfile HELP_DATA *data = remove_help(keyword); // unlink the helpfile if(data != NULL) { remove_keyword(data->keywords, keyword); // if no links are left, delete the helpfile if(!*data->keywords) deleteHelp(data); // save our changes save_help(); } } void link_help(const char *keyword, const char *old_word) { // check for the old keyword HELP_DATA *data = get_help_data(old_word, FALSE); // link the new keyword to the old one if(data != NULL) { // first, remove the current new_keyword help, if it exists unlink_help(keyword); // add the new keyword to the help add_keyword(&(data->keywords), keyword); // add the new command to the help table LIST *row = help_table[helpbucket(keyword)]; HELP_ENTRY *new_entry = newHelpEntry(keyword, data); listPutWith(row, new_entry, hentrycmp); // save our changes save_help(); } }