//*****************************************************************************
//
// 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 a list of all help keywords
LIST *get_all_help(void) {
LIST *all = newList();
NEAR_ITERATOR *help_i = newNearIterator(help_table);
const char *name = NULL;
HELP_DATA *data = NULL;
ITERATE_NEARMAP(name, data, help_i) {
listQueue(all, strdup(name));
} deleteNearIterator(help_i);
return all;
}
//
// 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(*arg == '\'' || *arg == '"')
one_arg(arg, arg);
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, 79, 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
// HELP_DATA *old = nearMapRemove(help_table, kwd);
// if(old != NULL)
// deleteHelp(old);
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));
storage_close(set);
}
// 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 extensions
//*****************************************************************************
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);
}
PyObject *PyMudSys_get_help(PyObject *self, PyObject *args) {
char *name = NULL;
bool abbrev_ok = FALSE;
if(!PyArg_ParseTuple(args, "s|b", &name, &abbrev_ok)) {
PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to get_help");
return NULL;
}
// find the helpfile we're looking for
HELP_DATA *help = get_help(name, abbrev_ok);
// did we find it?
if(help == NULL)
return Py_BuildValue("OOOO", Py_None, Py_None, Py_None, Py_None);
else
return Py_BuildValue("ssss",
helpGetKeywords(help),
helpGetInfo(help),
helpGetUserGroups(help),
helpGetRelated(help));
}
PyObject *PyMudSys_list_help(PyObject *self, PyObject *args) {
char *keyword = NULL;
if(!PyArg_ParseTuple(args, "|s", &keyword)) {
PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to get_help");
return NULL;
}
// find our matches; all topics if there is no keyword
LIST *matches = (keyword ? get_help_matches(keyword) : get_all_help());
// get_help_matches likes to return NULL if it doesn't find anything.
// kind of annoying.
if(matches == NULL)
matches = newList();
// convert the list of string to Python strings
PyObject *pymatches = PyList_fromList(matches, PyString_FromString);
// garbage collection
deleteListWith(matches, free);
return Py_BuildValue("O", pymatches);
}
//*****************************************************************************
// 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;
// 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,
"add_help(keywords, info, user_groups='', related='')\n\n"
"Add a new, non-persistent helpfile to the mud's help database.");
PyMudSys_addMethod("get_help", PyMudSys_get_help, METH_VARARGS,
"get_help(keyword)\n\n"
"Returns a tuple of a helpfile's keywords, info, user_groups, and related\n"
"or None if the helpfile does not exist.");
PyMudSys_addMethod("list_help", PyMudSys_list_help, METH_VARARGS,
"list_help(keyword='')\n\n"
"Returns a list of helpfiles that match the specified keyword. If no\n"
"keywordi s supplied, return all helpfiles.");
}