calisto-20000323/
calisto-20000323/lib/
calisto-20000323/lib/etc/
calisto-20000323/lib/players/
calisto-20000323/lib/text/
calisto-20000323/log/
/*
 Calisto (c) 1998-1999 Peter Howkins, Matthew Howkins, Simon Howkins

 $Id: help.c,v 1.9 2000/03/02 21:29:06 peter Exp $

 $Log: help.c,v $
 Revision 1.9  2000/03/02 21:29:06  peter
 Now uses msnprintf()

 Revision 1.8  2000/02/07 20:23:14  peter
 Implemented help_reload()

 Revision 1.7  2000/01/12 21:15:41  peter
 Changed display of command list by command_help() to lower-case

 Revision 1.6  2000/01/08 22:51:45  peter
 Added help_usage() to print usage instructions for given command

 Revision 1.5  2000/01/08 18:30:05  peter
 Added some simple colouring to help

 Revision 1.4  2000/01/02 15:22:17  peter
 Now handles $comm; and $user; and processes them

 Revision 1.3  1999/12/20 21:49:02  peter
 Commented out debug output, and #include'd "config.h"

 Revision 1.2  1999/12/18 18:09:38  peter
 Created help_reload(). Doesn't yet do anything

 Revision 1.1  1999/12/16 21:31:33  peter
 Initial revision


 */
static char rcsid[] = "$Id: help.c,v 1.9 2000/03/02 21:29:06 peter Exp $";

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "help.h"
#include "library.h"
#include "log.h"
#include "msnprintf.h"
#include "strplus.h"
#include "structs.h"

typedef struct {
  const char *entry;
  const char *text;
} help_item;

static char *helptext = NULL;
static help_item *help_items;
static unsigned help_item_count;
static const char *help_path = NULL;

static char *getlinefromhelp(const char *text, char *buffer)
{
  while (*text && *text != '\n')
    *buffer++ = *text++;

  if (*text == '\n')
    text++;

  *buffer = '\0';
  return (char *) text;
}

static unsigned buffer_word_count(const char *buffer)
{
  unsigned wc = 0;

  while (*buffer) {
    while (*buffer && isspace(*buffer))
      buffer++;
    if (*buffer) {
      wc++;
      while (*buffer && !isspace(*buffer))
        buffer++;
    }
  }
  return wc;
}

static int help_process(void)
{
  char *t = helptext;
  char buffer[1024];
  bool ignoring = FALSE; /* whether we are ignoring lines at the moment, searching for lines
                            of interest */
  help_item *x;

  /* Count the number of helptext entries */
  help_item_count = 0;
  while (*t) {
    t = getlinefromhelp(t, buffer);
    if (!ignoring) {
      /* Count the number of words in the buffer at the moment */
      help_item_count += buffer_word_count(buffer);
      ignoring = TRUE;
    } else {
      /* Ignore lines until we find a line that is just a '#' on its own */
      if (STREQ(buffer, "#"))
        ignoring = FALSE;
    }
  }
/*  log(debug, "Help: Helptext entry count is %u", help_item_count);*/

  /* Allocate space for array of helptext entries */
  help_items = malloc(help_item_count * sizeof(help_item));
  if (!help_items) {
    log(usage, "Help: Not enough memory for help file index - no help available");
    helptext = NULL;
    return 1;
  }

  /* Parse the helptext again, changing '\n' to '\0', putting '\0' between index words,
     and filling in the index */
  t = helptext;
  ignoring = FALSE;
  x = help_items;
  while (*t) {
    char *newline = strchr(t, '\n');
    char *nextline;

    if (newline) {
      *newline = '\0';
      nextline = newline + 1;
    } else
      nextline = t + strlen(t);

    if (!ignoring) {
      /* Index the words from t at the moment */
      while (*t) {
        while (*t && isspace(*t))
          t++;
        if (*t) {
          x->entry = t;
          x->text  = nextline;
          x++;
          while (*t && !isspace(*t))
            t++;
          if (*t)
            *t++ = '\0';
        }
      }
      ignoring = TRUE;
    } else {
      if (STREQ(t, "#")) {
        ignoring = FALSE;
        *t = '\0';
      } else
        if (newline)
          *newline = '\n';
    }

    t = nextline;
  }
  return 0;
}

int help_item_compare(const void *a, const void *b)
{
  const help_item *c = a;
  const help_item *d = b;

  return stricmp(c->entry, d->entry);
}

/* Sort the helptext entries, ready for binary searching later */
static void help_sort(void)
{
  qsort(help_items, (size_t) help_item_count, sizeof(help_item), help_item_compare);
}

void help_init(const char *path)
{
  FILE *f;
  unsigned helpfilesize;

  help_path = path;
  /* Open help file to determine its presence and size */
  f = fopen(help_path, "rb");
  if (!f) {
    log(usage, "Help: Unable to open file '%s' - no help availble", help_path);
    return;
  }
  (void) fseek(f, 0, SEEK_END);
  helpfilesize = (unsigned) ftell(f);

/*  log(debug, "Help: Help file size is %u", helpfilesize);*/

  /* Allocate memory for, and load in the help file */
  helptext = malloc((size_t) helpfilesize + 1);
  if (!helptext) {
    log(usage, "Help: Not enough memory for help file - no help available");
    fclose(f);
    return;
  }
  rewind(f);
  if (fread(helptext, 1, (size_t) helpfilesize, f) != (size_t) helpfilesize) {
    log(usage, "Help: Problem loading help file - help not available");
    fclose(f);
    helptext = NULL;
    return;
  }

  fclose(f);

  /* Add a zero terminator to the help text */
  helptext[helpfilesize] = '\0';

  log(usage, "Help: Successfully loaded '%s'", path);

  if (help_process())
    return;
  help_sort();

  /* Log the helptext index to the debug LOG */
/*  {
    unsigned item;

    for (item = 0; item < help_item_count; item++) {
      log(debug, "Help: Item % 2u : %s", item, help_items[item].entry);
      log(debug, "Help:   %s", help_items[item].text);
    }
  }*/
}

/* Process the string from src into dest.
   Look for fields starting with a $, and process appropriately. */
static void help_process_text(char *dest, size_t size, const char *src,
                              const character *c, const char *help_item)
{
  while (*src) {
    if (*src == '$') {
      src++;
      if (STRINEQ(src, "comm;", 5)) {
        src += 5;
        dest += msnprintf(dest, size, "%s", help_item);
      } else if (STRINEQ(src, "user;", 5)) {
        src += 5;
        dest += msnprintf(dest, size, "%s", c->name);
      } else {
        /* Leave the token unchanged, and copy it over.
           We only need the dollar, the rest will arrive later */
        *dest++ = '$';
      }
    } else {
      *dest++ = *src++;
    }
  }
  *dest = '\0';
}

void command_help(character *c, char *r)
{
  if (!helptext) {
    send_to_char(c, "There was a problem loading helptext during startup.\n"
                    "Help is therefore not available. Try moaning at an Admin\n");
    return;
  }

  if (*r == '\0') {
    unsigned i;

    /* They typed just help, therefore we list availble help entries */
    send_to_char(c, "Usage: help <help entry>\n"
                    "where <help entry> is one of the following:\n");
    for (i = 0; i < help_item_count; i++) {
      char buffer[256];
      STRNCOPY(buffer, help_items[i].entry, sizeof(buffer));
      strlower(buffer);
      send_to_char(c, "%s ", buffer);
    }
    send_to_char(c, "\nAlso, try typing \'commands\' to list available commands\n");
  } else {
    const help_item *item;
    help_item key;

    key.entry = r;
    item = bsearch(&key, help_items, (size_t) help_item_count,
                   sizeof(help_item), help_item_compare);
    if (item) {
      const char *t = item->text;
      char buffer[1024], buffer2[1024];

      while (*t) {
        char *b = buffer;

        t = getlinefromhelp(t, b);
        if (*b == '#') {
          b++;
          if (STRINEQ(b, "usage", 5)) {
            b += 5;
            while (*b && isspace(*b))
              b++;
            help_process_text(buffer2, sizeof(buffer2), b, c, r);
            send_to_char(c, "^YUsage: ^W%s^n\n", buffer2);
          } else if (STRINEQ(b, "seealso", 7)) {
            b += 7;
            while (*b && isspace(*b))
              b++;
            help_process_text(buffer2, sizeof(buffer2), b, c, r);
            send_to_char(c, "^YSee also: ^W%s^n\n", buffer2);
          }
        } else {
          help_process_text(buffer2, sizeof(buffer2), b, c, r);
          send_to_char(c, "%s\n", buffer2);
        }
      }
    } else
      send_to_char(c, "Help on %s not found\n", r);
  }
}

void help_reload(character *c)
{
  FILE *f;
  unsigned helpfilesize;
  void *ok;

  /* Open help file to determine its presence and size */
  f = fopen(help_path, "rb");
  if (!f) {
    send_to_char(c, "Unable to open file '%s' - leaving help unchanged\n",
                 help_path);
    return;
  }
  (void) fseek(f, 0, SEEK_END);
  helpfilesize = (unsigned) ftell(f);

  /* Allocate memory for, and load in the help file */
  ok = realloc(helptext, (size_t) helpfilesize + 1);
  if (!ok) {
    send_to_char(c, "Not enough memory for help file - leaving help unchanged");
    fclose(f);
    return;
  }
  helptext = ok;
  rewind(f);
  if (fread(helptext, 1, (size_t) helpfilesize, f) != (size_t) helpfilesize) {
    send_to_char(c, "Problem loading help file - help not available");
    fclose(f);
    free(helptext);
    helptext = NULL;
    return;
  }

  fclose(f);

  /* Add a zero terminator to the help text */
  helptext[helpfilesize] = '\0';

  if (help_process())
    return;
  help_sort();
}

void help_usage(character *c, const char *command)
{
  const help_item *item;
  help_item key;

  /* Check for helptext being unavailable */
  if (!helptext)
    return;

  key.entry = command;
  item = bsearch(&key, help_items, (size_t) help_item_count,
                 sizeof(help_item), help_item_compare);
  /* Check for help item not found */
  if (!item)
    return;

  {
    const char *t = item->text;
    char buffer[1024], buffer2[1024];

    while (*t) {
      char *b = buffer;

      t = getlinefromhelp(t, b);
      if (*b == '#') {
        b++;
        if (STRINEQ(b, "usage", 5)) {
          b += 5;
          while (*b && isspace(*b))
            b++;
          help_process_text(buffer2, sizeof(buffer2), b, c, command);
          send_to_char(c, "Usage: %s\n", buffer2);
        }
      }
    }
  }
}