next up previous contents index Search
Next: 0.2.7.5 One-way hashing Up: 0.2.7 Hashing Previous: 0.2.7.3 Choosing a Table

0.2.7.4 Source Code

Below is some code I wrote as an extension to an online game. The code uses a fixed-size hash table and the method of external chaining to store items based on an integer key. Although it is not immediately useful as-is it certainly gives you an idea of how to implement a hash table.

//+----------------------------------------------------------------------------
//
// File:     random.c
//
// Module:   Smaug add-on
//
// Synopsis: Routines for random loading objs and mobs.
//
// Copyright (C) 1999-2000 Scott Gasch 
//
// Created:  12 Jun 2000
//
//+----------------------------------------------------------------------------
#include 
#include 
#include "mud.h"
#include "random.h"

#define HASH_SIZE       1024
#define COMMAND_ADD     1
#define COMMAND_DEL     2
#define COMMAND_LIST    3
#define COMMAND_CLEAR   4
#define COMMAND_UNKNOWN 0
#define RANDOM_MOB_FILE "../system/random.mob"
#define RANDOM_OBJ_FILE "../system/random.obj"

//
// Global data structs and identifiers
//
typedef struct _RANDOM_TABLE_ENTRY
{
    int iVnum;
    int iChance;
    struct _RANDOM_TABLE_ENTRY *pNext;
    int iNumChained;
} RANDOM_TABLE_ENTRY;

static RANDOM_TABLE_ENTRY g_ObjHash[HASH_SIZE];
static RANDOM_TABLE_ENTRY g_MobHash[HASH_SIZE];

//
// Local function protos
//
static inline void ClearTable(RANDOM_TABLE_ENTRY *pRte);
static inline void ClearRandTables(void);
static bool AddObject(RANDOM_TABLE_ENTRY *pHash, int iVnum, int iChance);
static bool DeleteObject(RANDOM_TABLE_ENTRY *pHash, int iVnum);
static void ListObjects(RANDOM_TABLE_ENTRY *pRte, CHAR_DATA *pCh);
static void ShowObjRecurse(RANDOM_TABLE_ENTRY *pEnt, CHAR_DATA *pCh,
                           RANDOM_TABLE_ENTRY *pList);
static RANDOM_TABLE_ENTRY *NameToList(char *szName);
static int GetCommand(char *szCom);
static void ReadRandTable(RANDOM_TABLE_ENTRY *pRand, FILE *pFile);
static void WriteRandTable(RANDOM_TABLE_ENTRY *pRand, FILE *pFile);
static void WriteObjRecurse(RANDOM_TABLE_ENTRY *pEnt, FILE *pFile);


//+----------------------------------------------------------------------------
//
// Function:  GetObjRandom (interface)
//
// Synopsis:  
//
// Arguments: void
//            
// Returns:   OBJ_INDEX_DATA
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
OBJ_INDEX_DATA *GetObjRandom(void)
{
    int iNum = number_range(0, HASH_SIZE);
    int iStart = iNum;
    RANDOM_TABLE_ENTRY *pRte;
    RANDOM_TABLE_ENTRY *pChain;
    int iWhich;
    int iVnum = 0;
    int iChance = 0;

    do
    {
        pRte = &(g_ObjHash[iNum]);

        //
        // Is there something here?
        //
        if (pRte->iVnum)
        {
            //
            // Is it more than one thing?
            //
            if (pRte->iNumChained)
            {
                iWhich = number_range(0, pRte->iNumChained);

                for (pChain = pRte;
                     (iWhich && pChain);
                     pChain = pChain->pNext); // note the semi
                iVnum = pChain->iVnum;
                iChance = pChain->iChance;
            }
            else
            {
                //
                // Nope, only one thing.
                //
                iVnum = pRte->iVnum;
                iChance = pRte->iChance;
            }
        }
        
        //
        // Be ready with the next one...
        //
        iNum++;
        if (iNum >= HASH_SIZE) iNum = 0;
    }
    while ((!iVnum) && (iNum != iStart));

    //
    // Get the thing...
    //
    if ((iVnum == 0) || 
        (iVnum < 0))
    {
        return(NULL);
    }

    //
    // Roll for it
    //
    if (number_range(0, 10000) <= iChance)
    {
        return(get_obj_index(iVnum));
    }
    return(NULL);
}


//+----------------------------------------------------------------------------
//
// Function:  GetObjRandom
//
// Synopsis:  
//
// Arguments: void
//            
// Returns:   MOB_INDEX_DATA
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
MOB_INDEX_DATA *GetMobRandom(void)
{
    int iNum = number_range(0, HASH_SIZE);
    int iStart = iNum;
    RANDOM_TABLE_ENTRY *pRte;
    RANDOM_TABLE_ENTRY *pChain;
    int iWhich;
    int iVnum = 0;
    int iChance = 0;

    do
    {
        pRte = &(g_ObjHash[iNum]);

        //
        // Is there something here?
        //
        if (pRte->iVnum)
        {
            //
            // Is it more than one thing?
            //
            if (pRte->iNumChained)
            {
                iWhich = number_range(0, pRte->iNumChained);

                for (pChain = pRte;
                     (iWhich && pChain);
                     pChain = pChain->pNext); // note the semi
                iVnum = pChain->iVnum;
                iChance = pChain->iChance;
            }
            else
            {
                //
                // Nope, only one thing.
                //
                iVnum = pRte->iVnum;
	        iChance = pRte->iChance;
            }
        }
        
        //
        // Be ready with the next one...
        //
        iNum++;
        if (iNum >= HASH_SIZE) iNum = 0;
    }
    while ((!iVnum) && (iNum != iStart));

    //
    // Get the thing...
    //
    if ((iVnum == 0) || 
        (iVnum < 0))
    {
        return(NULL);
    }

    //
    // Roll for it...
    //
    if (number_range(0, 10000) <= iChance)
    {
        return(get_mob_index(iVnum));
    }
    return(NULL);
}


//+----------------------------------------------------------------------------
//
// Function:  do_random (interface)
//
// Synopsis:  Handler for the "random" command... a god command to modify
//            (add to, delete from, list or clear) a random loader table.
//
// Arguments: CHAR_DATA *pCh - who is calling us
//            char *szArgument - their args
//            
// Returns:   void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
void do_random(CHAR_DATA *pCh, char *szArgument)
{
    char szArg1[MAX_INPUT_LENGTH];            // buf for arguments 
    char szArg2[MAX_INPUT_LENGTH];            // buf for arguments
    RANDOM_TABLE_ENTRY *pList = NULL;         // random hash table start
    int iVnum, iChance;                       // vnum / chance for new entry
    char buf[1024];
    
    //
    // Sanity check the character.
    //
    set_char_color(AT_PLAIN, pCh);
    if (!pCh->desc)
    {
        send_to_char("You have no descriptor.\n\r", pCh);
        return;
    }

    //
    // Figure out what they're trying to do... syntax as follows:
    //
    // random obj add vnum chance
    // random obj del vnum
    // random mob add vnum chance
    // random mob del vnum
    // random obj list
    // random mob list
    // random obj clear
    // random mob clear
    //
    szArgument = one_argument(szArgument, szArg1);
    if (NULL == (pList = NameToList(szArg1)))
    {
        goto syntax;
    }

    //
    // Now we know what list they want to affect, lets find out what
    // they want to do!
    //
    szArgument = one_argument(szArgument, szArg2);
    switch (GetCommand(szArg2))
    {
    case COMMAND_ADD:
        szArgument = one_argument(szArgument, szArg1);
        if (szArg1[0] == 0) goto syntax;
        szArgument = one_argument(szArgument, szArg2);
        if (szArg2[0] == 0) goto syntax;
        if (0 == (iVnum = atoi(szArg1))) goto syntax;
        if (0 == (iChance = atoi(szArg2))) goto syntax;
        if ((iChance < 0) || (iChance > 10000))
        {
            send_to_char("Invalid percentage: 0=0.00%, 10000=100.00%\n\r",
                         pCh);
            return;
        }
        if (FALSE == AddObject(pList, iVnum, iChance))
        {
            send_to_char("There was an error updating the random table.\n\r"
                         "Perhaps the vnum you tried to add is already "
                         "in the table?\n\r", pCh);
            return;
        }
        sprintf(buf, "Added vnum %d with a %d.%d%% load rate.\n\r", 
                iVnum, iChance / 100, iChance % 100);
        send_to_char (buf, pCh);    
        break;

    case COMMAND_DEL:
        szArgument = one_argument(szArgument, szArg1);
        if (szArg1[0] == 0) goto syntax;
        if (0 == (iVnum = atoi(szArg1))) goto syntax;
        if (FALSE == DeleteObject(pList, iVnum))
        {
            send_to_char("There was an error updating the random table.\n\r"
                         "Perhaps the vnum you tried to delete is not in "
                         "the table?\n\r",
                         pCh);
            return;
        }
        send_to_char("Vnum deleted.\n\r", pCh);
        break;

    case COMMAND_LIST:
        ListObjects(pList, pCh);
        send_to_char("End of list.\n\r", pCh);
        break;

    case COMMAND_CLEAR:
        ClearTable(pList);
        send_to_char("Table cleared.\n\r", pCh);
        break;

    default:
        goto syntax;
        break;
    }

    return;

 syntax:
    send_to_char("Syntax: random  add  \n\r"
                 "        random  del \n\r"
                 "        random  list\n\r"
                 "        random  clear\n\r", pCh);
    return;
}


//+----------------------------------------------------------------------------
//
// Function:  InitRandTables (interface)
//
// Synopsis:  Reads hash tables from disk.
//
// Arguments: void
//            
// Returns:   void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
void InitRandTables(void)
{
    FILE *pFile;                              // File to read from
    char buf[1024];                           // For error msg

    //
    // Start with clear tables.
    //
    ClearRandTables();

    //
    // Open the mob file.
    //
    if (NULL != (pFile = fopen(RANDOM_MOB_FILE, "r")))
    {
        //
        // Parse it and build memory hash table.
        //
        ReadRandTable(g_MobHash, pFile);
        fclose(pFile);
    }
    else
    {
        //
        // File not found... no big deal but log it.
        //
        sprintf(buf, "Cannot open %s file for reading... mob random table "
                "will be empty.", RANDOM_MOB_FILE);
        bug(buf, 0);
    }

    //
    // Open the obj file.
    //
    if (NULL != (pFile = fopen(RANDOM_OBJ_FILE, "r")))
    {
        //
        // Parse it and build memory hash table.
        //
        ReadRandTable(g_ObjHash, pFile);
        fclose(pFile);
    }
    else
    {
        //
        // File not found... no big deal but log it.
        //
        sprintf(buf, "Cannot open %s file for reading... obj random table "
                "will be empty.", RANDOM_OBJ_FILE);
        bug(buf, 0);
    }
}


//+----------------------------------------------------------------------------
//
// Function:  CleanupRandTables (interface)
//
// Synopsis:  Deallocates memory and writes hash tables to disk.
//
// Arguments: void
//            
// Returns:   void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
void CleanupRandTables(void)
{
    RANDOM_TABLE_ENTRY *pNuke;                // A table entry to dealloc
    int i;                                    // Loop control
    FILE *pFile;                              // file ptr
    char buf[1024];                           // for error messages

    //
    // Open the mob file for writing.
    //
    if (NULL != (pFile = fopen(RANDOM_MOB_FILE, "w+")))
    {
        //
        // Parse it and build memory hash table.
        //
        WriteRandTable(g_MobHash, pFile);
        fclose(pFile);
    }
    else
    {
        //
        // File not found... no big deal but log it.
        //
        sprintf(buf, "Cannot open %s file for writing... could not save "
                "random mob list.", RANDOM_MOB_FILE);
        bug(buf, 0);
    }

    //
    // Open the obj file.
    //
    if (NULL != (pFile = fopen(RANDOM_OBJ_FILE, "w+")))
    {
        //
        // Parse it and build memory hash table.
        //
        WriteRandTable(g_ObjHash, pFile);
        fclose(pFile);
    }
    else
    {
        //
        // File not found... no big deal but log it.
        //
        sprintf(buf, "Cannot open %s file for writing... could not save "
                "random obj list.", RANDOM_OBJ_FILE);
        bug(buf, 0);
    }
    
    //
    // Deallocate any externally chained entries.
    //
    for (i = 0; i < HASH_SIZE; i++)
    {
        while (g_ObjHash[i].pNext)
        {
            pNuke = g_ObjHash[i].pNext;
            g_ObjHash[i].pNext = pNuke->pNext;
            free(pNuke);
        }

        while (g_MobHash[i].pNext)
        {
            pNuke = g_MobHash[i].pNext;
            g_MobHash[i].pNext = pNuke->pNext;
            free(pNuke);
        }
    }

    //
    // Clear the lists
    //
    ClearRandTables();
}



//+----------------------------------------------------------------------------
//
// Function:  ClearTable
//
// Synopsis:  Clears a hash table pointed to by arg.
//
// Arguments: RANDOM_TABLE_ENTRY *pRte - table to clear
//            
// Returns:   static inline void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static inline void ClearTable(RANDOM_TABLE_ENTRY *pRte)
{
    memset(pRte, 0, sizeof(RANDOM_TABLE_ENTRY) * HASH_SIZE);
}

//+----------------------------------------------------------------------------
//
// Function:  ClearRandTables
//
// Synopsis:  Clears both hash tables.
//
// Arguments: void
//            
// Returns:   static inline void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static inline void ClearRandTables(void)
{
    ClearTable(g_ObjHash);
    ClearTable(g_MobHash);
}

//+----------------------------------------------------------------------------
//
// Function:  AddObject
//
// Synopsis:  Adds an object (vnum, chance) to a hash table.
//
// Arguments: RANDOM_TABLE_ENTRY *pHash - start of table to add to
//            iVnum - vnum to add
//            iChance - chance it loads
//            
// Returns:   bool - false on error
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static bool AddObject(RANDOM_TABLE_ENTRY *pHash, int iVnum, int iChance)
{
    int iHashLoc = iVnum % HASH_SIZE;         // Where the vnum should be
    RANDOM_TABLE_ENTRY *pRte =                // Ptr to this node
        (pHash + iHashLoc);
    RANDOM_TABLE_ENTRY *pChain;               // Used for chained nodes
    bool fRetVal = FALSE;                     // Our return value

    //
    // Is this hash position full?
    //
    if (pRte->iVnum)
    {
        //
        // Before we add this vnum make sure its not already in the
        // table.  Check the first table location and any external
        // chaining we've done from there.
        //
        if (iVnum == pRte->iVnum)
        {
            goto end; // returns false
        }

        for (pChain = pRte->pNext;
             pChain;
             pChain = pChain->pNext)
        {
            if (iVnum == pChain->iVnum)
            {
                goto end; // returns false
            }
        }

        //
        // If we get here it's not already in the table... BUT the
        // node where we want to add it is already full.  Add the new
        // entry by allocating a new node and chaining it off this
        // one.
        //
        pRte->iNumChained++;
        pChain = (RANDOM_TABLE_ENTRY *) malloc(sizeof(RANDOM_TABLE_ENTRY));
        if (NULL == pChain)
        {
            bug ("Out of memory adding to random table!", 0);
            goto end;
        }

        pChain->iVnum = iVnum;
        pChain->iChance = iChance;
        pChain->pNext = pRte->pNext;
        pRte->pNext = pChain;
        fRetVal = TRUE;
    }
    else
    {
        //
        // The hash position is not full, just add this info!
        //
        pRte->iVnum = iVnum;
        pRte->iChance = iChance;
        pRte->pNext = NULL;
        pRte->iNumChained = 0;
        fRetVal = TRUE;
    }

 end:
    return(fRetVal);
}

//+----------------------------------------------------------------------------
//
// Function:  DeleteObject
//
// Synopsis:  Deletes an item by vnum from a hash table
//
// Arguments: RANDOM_TABLE_ENTRY *pHash - hash table to delete from
//            iVnum - vnum to delete
//            
// Returns:   bool - false on error
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static bool DeleteObject(RANDOM_TABLE_ENTRY *pHash, int iVnum)
{
    int iHashLoc = iVnum % HASH_SIZE;         // Which node it should be at
    RANDOM_TABLE_ENTRY *pRte =                // Ptr to this node
        (pHash + iHashLoc);
    RANDOM_TABLE_ENTRY *pChain, *pPrev;       // Used for chaining & deleting
    bool fRetVal = FALSE;                     // Our return value
   
    //
    // Is the vnum at the hash node we picked?
    //
    if (iVnum == pRte->iVnum)
    {
        //
        // Yes, so is there any chaining going on?
        //
        if (NULL == pRte->pNext)
        {
            //
            // No, so just clear the node in question!
            //
            pRte->iVnum = 0;
            pRte->iChance = 0;
            pRte->pNext = NULL;
            pRte->iNumChained = 0;
            fRetVal = TRUE;
            goto end;
        }
        else
        {
            //
            // Ok, there is something chained too... just promote
            // it to be the head of the hash.
            //
            pRte->iNumChained--;
            pRte->iVnum = pRte->pNext->iVnum;
            pRte->iChance = pRte->pNext->iChance;
            pRte->pNext = pRte->pNext->pNext;
            fRetVal = TRUE;
            goto end;
        }
    }
    else
    {
        //
        // This vnum is not at the head hash node... let's kill it
        // in the chain if there is one.
        //
        pPrev = pRte;

        for (pChain = pRte->pNext;
             pChain;
             pChain = pChain->pNext)
        {
            if (iVnum == pChain->iVnum)
            {
                pRte->iNumChained--;
                pPrev->pNext = pChain->pNext;
                free(pChain);
                fRetVal = TRUE;
                goto end;
            }

            pPrev = pChain;
        }
    }

    //
    // Not found.
    //
    fRetVal = FALSE;

 end:
    return(fRetVal);
}


//+----------------------------------------------------------------------------
//
// Function:  ListObjects
//
// Synopsis:  Show all the objects in a table.
//
// Arguments: RANDOM_TABLE_ENTRY *pRte,
//            CHAR_DATA *pCh
//            
// Returns:   void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static void ListObjects(RANDOM_TABLE_ENTRY *pRte, CHAR_DATA *pCh)
{
    int i;                                    // Loop control
   
    send_to_pager("VNUM    PROB    ITEM/MOB NAME\n\r", pCh); 
    send_to_pager("----------------------------------------------------------"
	          "-----\n\r", pCh);
    for (i = 0; i < HASH_SIZE; i++)
    {
        ShowObjRecurse((pRte + i), pCh, pRte);
    }
}

//+----------------------------------------------------------------------------
//
// Function:  ShowObjRecurse
//
// Synopsis:  Show an object
//
// Arguments: RANDOM_TABLE_ENTRY *pEnt,
//            CHAR_DATA *pCh
//            
// Returns:   static void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static void ShowObjRecurse(RANDOM_TABLE_ENTRY *pEnt, CHAR_DATA *pCh, 
                           RANDOM_TABLE_ENTRY *pList)
{
    char buf[1024];                           // To send to character
    OBJ_INDEX_DATA *pObjIndex;                // To get the short descr
    MOB_INDEX_DATA *pMobIndex;                // Ditto

    //
    // If this item has chained items further down, show them first.
    //
    if (pEnt->pNext)
    {
        ShowObjRecurse(pEnt->pNext, pCh, pList);
    }

    //
    // Now show it.
    //
    if (pEnt->iVnum)
    {
    	if ((pList == g_ObjHash) && (pObjIndex = get_obj_index(pEnt->iVnum)))
        {
            sprintf(buf, "%-5d (%02d.%02d%%) - %s\n\r", pEnt->iVnum,
                    pEnt->iChance / 100, pEnt->iChance % 100,
                    pObjIndex->short_descr);
            send_to_pager(buf, pCh);
        }
        else if ((pList == g_MobHash) && 
                 (pMobIndex = get_mob_index(pEnt->iVnum)))
        {
            sprintf(buf, "%-5d (%02d.%02d%%) - %s\n\r", pEnt->iVnum,
                    pEnt->iChance / 100, pEnt->iChance % 100,
                    pMobIndex->short_descr);
            send_to_pager(buf, pCh);
        }
    }
}
    
//+----------------------------------------------------------------------------
//
// Function:  GetCommand
//
// Synopsis:  Parse a command
//
// Arguments: char *szCom
//            
// Returns:   static int
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static int GetCommand(char *szCom)
{
    if (szCom[0] == 0)
    {
        return(COMMAND_UNKNOWN);
    }
    else if (!str_cmp(szCom, "add"))
    {
        return(COMMAND_ADD);
    }
    else if (!str_cmp(szCom, "del"))
    {
        return(COMMAND_DEL);
    }
    else if (!str_cmp(szCom, "list"))
    {
        return(COMMAND_LIST);
    }
    else if (!str_cmp(szCom, "clear"))
    {
        return(COMMAND_CLEAR);
    }
    return(COMMAND_UNKNOWN);
}


//+----------------------------------------------------------------------------
//
// Function:  NameToList
//
// Synopsis:  Parse a name (mob or obj) to a list pointer.
//
// Arguments: char *szName
//            
// Returns:   static RANDOM_TABLE_ENTRY
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static RANDOM_TABLE_ENTRY *NameToList(char *szName)
{
    if (szName[0] == 0)
    {
        return(NULL);
    }
    else if (!str_cmp(szName, "mob"))
    {
        return(g_MobHash);
    }
    else if (!str_cmp(szName, "obj"))
    {
        return(g_ObjHash);
    }
    return(NULL);
}


//+----------------------------------------------------------------------------
//
// Function:  ReadRandTable
//
// Synopsis:  
//
// Arguments: FILE *pFile,
//            HASH_TABLE_ENTRY *pList
//            
// Returns:   static
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static void ReadRandTable(RANDOM_TABLE_ENTRY *pList, FILE *pFile)
{
    char buf[1024];                           // A line from the file
    char *pVnum, *pChance;                    // Vnum and Chance on the line
    int iVnum, iChance;                       // Above converted to ints

    //
    // Lines look like: VNUM CHANCE\n
    //
    while (fgets(buf, 1024, pFile))
    {
        pVnum = buf;
        while (isspace(*pVnum))
        {
            pVnum++;
        }

        pChance = pVnum;
        while (!isspace(*pChance))
        {
            pChance++;
        }
        while (isspace(*pChance))
        {
            pChance++;
        }
    
        if (0 == (iVnum = atoi(pVnum)))
        {
            bug("Error reading random file.", 0);
            continue;
        }

        if (0 == (iChance = atoi(pChance)))
        {
            bug("Error reading random file.", 0);
            continue;
        }

        if (FALSE == AddObject(pList, iVnum, iChance))
        {
            return;
        }
    }
}


//+----------------------------------------------------------------------------
//
// Function:  WriteRandTable
//
// Synopsis:  
//
// Arguments: FILE *pFile,
//            HASH_TABLE_ENTRY *pList
//            
// Returns:   static
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static void WriteRandTable(RANDOM_TABLE_ENTRY *pList, FILE *pFile)
{
    int i;                                    // Loop control
    
    for (i = 0; i < HASH_SIZE; i++)
    {
        WriteObjRecurse((pList + i), pFile);
    }
}


//+----------------------------------------------------------------------------
//
// Function:  WriteObjRecurse
//
// Synopsis:  
//
// Arguments: RANDOM_TABLE_ENTRY *pEnt,
//            FILE *pFile
//            
// Returns:   static void
//
// History:   sgasch Created Header    13 Jun 2000
//
//+----------------------------------------------------------------------------
static void WriteObjRecurse(RANDOM_TABLE_ENTRY *pEnt, FILE *pFile)
{
    char buf[1024];
    OBJ_INDEX_DATA *pObjIndex;
    
    if (pEnt->pNext)
    {
        WriteObjRecurse(pEnt->pNext, pFile);
    }

    if (pEnt->iVnum && (pObjIndex = get_obj_index(pEnt->iVnum)))
    {
        sprintf(buf, "%d %d\n", pEnt->iVnum, pEnt->iChance);
        if (0 != fputs(buf, pFile))
        {
            return;
        }
    }
}

Scott Gasch
1999-07-09