mux2.4/game/data/
mux2.4/src/tools/
// help.cpp -- Commands for giving help.
//
// $Id: help.cpp,v 1.12 2005/08/05 15:35:14 sdennis Exp $
//

#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#include <fcntl.h>

#include "help.h"
#include "command.h"

// Pointers to this struct is what gets stored in the help_htab's.
//
struct help_entry
{
    int  pos;       // Position, copied from help_indx
    char original;  // 1 for the longest name for a topic. 0 for
                    // abbreviations.
    char *key;      // The key this is stored under.
};

void helpindex_clean(int iHelpfile)
{
    CHashTable *htab = mudstate.aHelpDesc[iHelpfile].ht;
    if (htab == NULL)
    {
        return;
    }
    struct help_entry *htab_entry;
    for (htab_entry = (struct help_entry *)hash_firstentry(htab);
         htab_entry;
         htab_entry = (struct help_entry *)hash_nextentry(htab))
    {
        MEMFREE(htab_entry->key);
        htab_entry->key = NULL;
        MEMFREE(htab_entry);
        htab_entry = NULL;
    }
    delete mudstate.aHelpDesc[iHelpfile].ht;
    mudstate.aHelpDesc[iHelpfile].ht = NULL;
}

bool bHaveTopic;
long pos;
int lineno;
int ntopics;
FILE *rfp;

#define LINE_SIZE 4096
char Line[LINE_SIZE + 1];
int  nLine;

void HelpIndex_Start(FILE *fp)
{
    pos = 0L;
    lineno = 0;
    ntopics = 0;
    rfp = fp;
    bHaveTopic = false;
    nLine = 0;
}

bool HelpIndex_Read(help_indx *pEntry)
{
    for (;;)
    {
        while (nLine == 0)
        {
            if (fgets(Line, LINE_SIZE, rfp) == NULL)
            {
                if (bHaveTopic)
                {
                    pEntry->len = (int)(pos - pEntry->pos);
                    bHaveTopic = false;
                    return true;
                }
                return false;
            }
            ++lineno;

            nLine = strlen(Line);
            if (  nLine > 0
               && Line[nLine - 1] != '\n')
            {
                Log.tinyprintf("HelpIndex_Read, line %d: line too long\n", lineno);
            }
        }

        if (Line[0] == '&')
        {
            if (bHaveTopic)
            {
                pEntry->len = (int)(pos - pEntry->pos);
                bHaveTopic = false;
                return true;
            }

            ++ntopics;
            char *topic = Line + 1;
            while (  *topic == ' '
                  || *topic == '\t'
                  || *topic == '\r')
            {
                topic++;
            }
            char *s;
            int i;
            memset(pEntry->topic, 0, sizeof(pEntry->topic));
            for (i = -1, s = topic; *s != '\n' && *s != '\r' && *s != '\0'; s++)
            {
                if (i >= TOPIC_NAME_LEN - 1)
                {
                    break;
                }
                if (*s != ' ' || pEntry->topic[i] != ' ')
                {
                    pEntry->topic[++i] = *s;
                }
            }
            pEntry->topic[++i] = '\0';
            pEntry->pos = pos + (long)nLine;
            bHaveTopic = true;
        }
        pos += nLine;
        nLine = 0;
    }
}

void HelpIndex_End(void)
{
    pos = 0L;
    lineno = 0;
    ntopics = 0;
    rfp = NULL;
}

int helpindex_read(int iHelpfile)
{
    helpindex_clean(iHelpfile);

    mudstate.aHelpDesc[iHelpfile].ht = new CHashTable;
    CHashTable *htab = mudstate.aHelpDesc[iHelpfile].ht;

    char szTextFilename[SBUF_SIZE+8];
    sprintf(szTextFilename, "%s.txt", mudstate.aHelpDesc[iHelpfile].pBaseFilename);

    help_indx entry;

    FILE *fp = fopen(szTextFilename, "rb");
    if (fp == NULL)
    {
        STARTLOG(LOG_PROBLEMS, "HLP", "RINDX");
        char *p = alloc_lbuf("helpindex_read.LOG");
        sprintf(p, "Can't open %s for reading.", szTextFilename);
        log_text(p);
        free_lbuf(p);
        ENDLOG;
        return -1;
    }
    DebugTotalFiles++;
    int count = 0;
    HelpIndex_Start(fp);
    while (HelpIndex_Read(&entry))
    {
        // Convert the entry to all lowercase letters and add all leftmost substrings.
        //
        // Substrings already added will be rejected by hashaddLEN.
        //
        mux_strlwr(entry.topic);
        bool bOriginal = true; // First is the longest.
        int nTopic = strlen(entry.topic);

        for (nTopic = strlen(entry.topic); nTopic > 0; nTopic--)
        {
            if (mux_isspace(entry.topic[nTopic-1]))
            {
                continue;
            }
            struct help_entry *htab_entry = (struct help_entry *)MEMALLOC(sizeof(struct help_entry));
            ISOUTOFMEMORY(htab_entry);
            htab_entry->pos = entry.pos;
            htab_entry->original = bOriginal;
            bOriginal = false;
            htab_entry->key = StringCloneLen(entry.topic, nTopic);

            if ((hashaddLEN(entry.topic, nTopic, htab_entry, htab)) == 0)
            {
                count++;
            }
            else
            {
                MEMFREE(htab_entry->key);
                htab_entry->key = NULL;
                MEMFREE(htab_entry);
                htab_entry = NULL;
            }
        }
    }
    HelpIndex_End();
    if (fclose(fp) == 0)
    {
        DebugTotalFiles--;
    }
    hashreset(htab);
    return count;
}

void helpindex_load(dbref player)
{
    for (int i = 0; i < mudstate.nHelpDesc; i++)
    {
        helpindex_read(i);
    }
    if (  player != NOTHING
       && !Quiet(player))
    {
        notify(player, "Cache for help indexes refreshed.");
    }
}

void helpindex_init(void)
{
    helpindex_load(NOTHING);
}

const char *MakeCanonicalTopicName(char *topic_arg)
{
    const char *topic;
    if (topic_arg[0] == '\0')
    {
        topic = "help";
    }
    else
    {
        mux_strlwr(topic_arg);
        topic = topic_arg;
    }
    return topic;
}

void ReportMatchedTopics(dbref executor, const char *topic, CHashTable *htab)
{
    bool matched = false;
    char *topic_list = NULL;
    char *buffp = NULL;
    struct help_entry *htab_entry;
    for (htab_entry = (struct help_entry *)hash_firstentry(htab);
         htab_entry != NULL;
         htab_entry = (struct help_entry *)hash_nextentry(htab))
    {
        mudstate.wild_invk_ctr = 0;
        if (  htab_entry->original
           && quick_wild(topic, htab_entry->key))
        {
            if (!matched)
            {
                matched = true;
                topic_list = alloc_lbuf("help_write");
                buffp = topic_list;
            }
            safe_str(htab_entry->key, topic_list, &buffp);
            safe_chr(' ', topic_list, &buffp);
            safe_chr(' ', topic_list, &buffp);
        }
    }
    if (!matched)
    {
        notify(executor, tprintf("No entry for '%s'.", topic));
    }
    else
    {
        notify(executor, tprintf("Here are the entries which match '%s':", topic));
        *buffp = '\0';
        notify(executor, topic_list);
        free_lbuf(topic_list);
    }
}

bool ReportTopic(dbref executor, struct help_entry *htab_entry, int iHelpfile,
    char *result)
{
    char szTextFilename[SBUF_SIZE+8];
    sprintf(szTextFilename, "%s.txt", mudstate.aHelpDesc[iHelpfile].pBaseFilename);

    int offset = htab_entry->pos;
    FILE *fp = fopen(szTextFilename, "rb");
    if (fp == NULL)
    {
        STARTLOG(LOG_PROBLEMS, "HLP", "OPEN");
        char *line = alloc_lbuf("ReportTopic.open");
        sprintf(line, "Can't open %s for reading.", szTextFilename);
        log_text(line);
        free_lbuf(line);
        ENDLOG;
        return false;
    }
    DebugTotalFiles++;
    if (fseek(fp, offset, 0) < 0L)
    {
        STARTLOG(LOG_PROBLEMS, "HLP", "SEEK");
        char *line = alloc_lbuf("ReportTopic.seek");
        sprintf(line, "Seek error in file %s.", szTextFilename);
        log_text(line);
        free_lbuf(line);
        ENDLOG;
        if (fclose(fp) == 0)
        {
            DebugTotalFiles--;
        }
        return false;
    }
    char *line = alloc_lbuf("ReportTopic");
    char *bp = result;
    for (;;)
    {
        if (  fgets(line, LBUF_SIZE - 1, fp) == NULL
           || line[0] == '&'
           || line[0] == '\0')
        {
            break;
        }

        bool bEval = mudstate.aHelpDesc[iHelpfile].bEval;
        if (bEval)
        {
            char *str = line;
            mux_exec(result, &bp, executor, executor, executor,
                     EV_NO_COMPRESS | EV_FIGNORE | EV_EVAL, &str, (char **)NULL, 0);
        }
        else
        {
            safe_str(line, result, &bp);
        }
    }
    *bp = '\0';
    if (fclose(fp) == 0)
    {
        DebugTotalFiles--;
    }
    free_lbuf(line);
    return true;
}

void help_write(dbref executor, char *topic_arg, int iHelpfile)
{
    const char *topic = MakeCanonicalTopicName(topic_arg);

    CHashTable *htab = mudstate.aHelpDesc[iHelpfile].ht;
    struct help_entry *htab_entry =
        (struct help_entry *)hashfindLEN(topic, strlen(topic), htab);
    if (htab_entry)
    {
        char *result = alloc_lbuf("help_write");
        if (ReportTopic(executor, htab_entry, iHelpfile, result))
        {
            notify(executor, result);
        }
        else
        {
            notify(executor, "Sorry, that function is temporarily unavailable.");
        }
        free_lbuf(result);
    }
    else
    {
        ReportMatchedTopics(executor, topic, htab);
        return;
    }
}

bool ValidateHelpFileIndex(int iHelpfile)
{
    if (  iHelpfile < 0
       || mudstate.mHelpDesc <= iHelpfile)
    {
        char *buf = alloc_mbuf("do_help.LOG");
        STARTLOG(LOG_BUGS, "BUG", "HELP");
        sprintf(buf, "Unknown help file number: %d", iHelpfile);
        log_text(buf);
        ENDLOG;
        free_mbuf(buf);
        return false;
    }
    return true;
}

/*
 * ---------------------------------------------------------------------------
 * * do_help: display information from new-format news and help files
 */

void do_help(dbref executor, dbref caller, dbref enactor, int key, char *message)
{
    int iHelpfile = key;

    if (!ValidateHelpFileIndex(iHelpfile))
    {
        notify(executor, "No such indexed file found.");
        return;
    }
    help_write(executor, message, iHelpfile);
}

void help_helper(dbref executor, int iHelpfile, char *topic_arg,
    char *buff, char **bufc)
{
    if (!ValidateHelpFileIndex(iHelpfile))
    {
        return;
    }

    const char *topic = MakeCanonicalTopicName(topic_arg);

    CHashTable *htab = mudstate.aHelpDesc[iHelpfile].ht;
    struct help_entry *htab_entry =
        (struct help_entry *)hashfindLEN(topic, strlen(topic), htab);
    if (htab_entry)
    {
        char *result = alloc_lbuf("help_helper");
        if (ReportTopic(executor, htab_entry, iHelpfile, result))
        {
            safe_str(result, buff, bufc);
        }
        else
        {
            safe_str("#-1 ERROR", buff, bufc);
        }
        free_lbuf(result);
    }
    else
    {
        safe_str("#-1 TOPIC DOES NOT EXIST", buff, bufc);
    }
}