nakedmudv3.3/
nakedmudv3.3/lib/
nakedmudv3.3/lib/logs/
nakedmudv3.3/lib/misc/
nakedmudv3.3/lib/players/
nakedmudv3.3/lib/txt/
nakedmudv3.3/lib/world/
nakedmudv3.3/lib/world/examples/
nakedmudv3.3/lib/world/examples/mproto/
nakedmudv3.3/lib/world/examples/oproto/
nakedmudv3.3/lib/world/examples/reset/
nakedmudv3.3/lib/world/examples/rproto/
nakedmudv3.3/lib/world/examples/trigger/
nakedmudv3.3/lib/world/limbo/
nakedmudv3.3/lib/world/limbo/room/
nakedmudv3.3/lib/world/limbo/rproto/
nakedmudv3.3/src/alias/
nakedmudv3.3/src/char_vars/
nakedmudv3.3/src/editor/
nakedmudv3.3/src/example_module/
nakedmudv3.3/src/help/
nakedmudv3.3/src/set_val/
nakedmudv3.3/src/socials/
nakedmudv3.3/src/time/
//*****************************************************************************
//
// 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();
  }
}