nakedmud-mod/
nakedmud-mod/html/tutorials/
nakedmud-mod/html/tutorials/building_extras/
nakedmud-mod/html/tutorials/c/
nakedmud-mod/html/tutorials/reference/
nakedmud-mod/html/tutorials/scripting/
nakedmud-mod/html/tutorials/scripting_extras/
nakedmud-mod/lib/
nakedmud-mod/lib/help/A/
nakedmud-mod/lib/help/B/
nakedmud-mod/lib/help/C/
nakedmud-mod/lib/help/D/
nakedmud-mod/lib/help/G/
nakedmud-mod/lib/help/H/
nakedmud-mod/lib/help/J/
nakedmud-mod/lib/help/L/
nakedmud-mod/lib/help/M/
nakedmud-mod/lib/help/O/
nakedmud-mod/lib/help/P/
nakedmud-mod/lib/help/R/
nakedmud-mod/lib/help/S/
nakedmud-mod/lib/help/W/
nakedmud-mod/lib/logs/
nakedmud-mod/lib/misc/
nakedmud-mod/lib/players/
nakedmud-mod/lib/pymodules/polc/
nakedmud-mod/lib/txt/
nakedmud-mod/lib/world/
nakedmud-mod/lib/world/zones/examples/
nakedmud-mod/lib/world/zones/examples/mproto/
nakedmud-mod/lib/world/zones/examples/oproto/
nakedmud-mod/lib/world/zones/examples/reset/
nakedmud-mod/lib/world/zones/examples/rproto/
nakedmud-mod/lib/world/zones/examples/trigger/
nakedmud-mod/lib/world/zones/limbo/
nakedmud-mod/lib/world/zones/limbo/room/
nakedmud-mod/lib/world/zones/limbo/rproto/
nakedmud-mod/src/alias/
nakedmud-mod/src/dyn_vars/
nakedmud-mod/src/editor/
nakedmud-mod/src/example_module/
nakedmud-mod/src/help2/
nakedmud-mod/src/set_val/
nakedmud-mod/src/socials/
nakedmud-mod/src/time/
//*****************************************************************************
//
// 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.");
}