circlemud_squared_0.5.153/cnf/
circlemud_squared_0.5.153/etc/
circlemud_squared_0.5.153/etc/etc/
circlemud_squared_0.5.153/etc/house/
circlemud_squared_0.5.153/etc/misc/
circlemud_squared_0.5.153/etc/plralias/A-E/
circlemud_squared_0.5.153/etc/plralias/F-J/
circlemud_squared_0.5.153/etc/plralias/K-O/
circlemud_squared_0.5.153/etc/plralias/P-T/
circlemud_squared_0.5.153/etc/plralias/U-Z/
circlemud_squared_0.5.153/etc/plralias/ZZZ/
circlemud_squared_0.5.153/etc/plrobjs/
circlemud_squared_0.5.153/etc/plrobjs/A-E/
circlemud_squared_0.5.153/etc/plrobjs/F-J/
circlemud_squared_0.5.153/etc/plrobjs/K-O/
circlemud_squared_0.5.153/etc/plrobjs/P-T/
circlemud_squared_0.5.153/etc/plrobjs/U-Z/
circlemud_squared_0.5.153/etc/plrobjs/ZZZ/
circlemud_squared_0.5.153/etc/text/
circlemud_squared_0.5.153/etc/text/help/
circlemud_squared_0.5.153/src/util/
circlemud_squared_0.5.153/src/util/worldconv/
/**
 * @file zone.c
 * @ingroup zone
 *
 * Zone based code
 *
 * @author Geoff Davis <geoff@circlemudsquared.org>
 * @author Greg Buxton <greg@circlemudsquared.org>
 *
 * @par Copyright:
 *   Copyright (C) 2006 Geoff Davis <geoff@circlemudsquared.org><br>
 *                      Greg Buxton <greg@circlemudsquared.org>
 *
 * @par
 *   Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University<br>
 *   CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.
 *
 * @par
 *   All rights reserved.  See license.doc for complete information.
 *
 * @package cs
 * @version 1.0
 */

#define __ZONE_C__

#include <sys/types.h>
#include <dirent.h>
#include <string.h>

#include "base.h"
#include "structs.h"
#include "utils.h"

#include "comm.h"
#include "command.h"
#include "constants.h"
#include "dao.h"
#include "db.h"
#include "handler.h"
#include "log.h"
#include "interpreter.h"
#include "spells.h"
#include "room.h"
#include "item.h"
#include "extraDesc.h"
#include "mobile.h"
#include "zone.h"
#include "hashMap.h"
#include "memory.h"
#include "screen.h"
#include "shop.h"

/*
 * External variables.
 */
extern const char *zone_reset_types[];

/*
 * Local function prototypes.
 */
bool zoneData_saveFile(zoneData_t *zone);
void zoneData_resetCommandToDao(daoData_t *dao, int n, resetCommand_t *cmd);
void zoneDao_readFile(char *filename);
void zoneData_fromDao(daoData_t *zoneDao);
void resetCommand_fromDao(zoneData_t *zone, daoData_t *resetDao);
void zoneData_free(void *z);
void zones_linkWorld(void);
void zones_resolveResetCommands(void);

#define SAVEZONE_USAGE \
  "Usage: savezone <zone-keyword>\r\n" \
  "       savezone all\r\n"

/**
 * @brief The SAVEZONE command.
 *
 * This command allows a specific zone to be saved to disk.
 *
 * @param ch the character calling the command
 * @param argument the command line argument passed to the command
 * @param cmd the index of the corresponding entry in the cmd_info array
 * @returns none
 */
ACMD(do_savezone) {
  char arg[MAX_INPUT_LENGTH] = { '\0' };

  /* Read one word from the command line. */
  argument = any_one_arg(argument, arg);

  if (*arg == '\0') {
    send_to_char(ch, "%s", SAVEZONE_USAGE);
  } else if (strcasecmp(arg, "all") == 0) {
    /* Declare an iterator variable. */
    hashMapIterator_t *iter = hashMapIterator_create(zones);

    send_to_char(ch, "Saving zones:\r\n");

    /* Iterate each zone in the zone table. */
    while(iter && hashMapIterator_getValue(iter)) {
      zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);
      if (zone) {
        if (!zoneData_saveFile(zone)) {
          send_to_char(ch, "...Unable to save zone '%s'.\r\n", zone->name);
        }
      }
      zone = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);

    send_to_char(ch, "...Done.\r\n");
  } else {
    zoneData_t *zone = zoneData_find(arg);

    if (zone) {
      if (!zoneData_saveFile(zone)) {
        send_to_char(ch, "Unable to save zone '%s'.\r\n", zone->name);
      } else {
        send_to_char(ch, "Zone '%s' saved.\r\n", zone->name);
      }
    } else {
      send_to_char(ch, "Unable to find a zone '%s'.\r\n", arg);
    }
  }
}

/**
 * Save a zone to disk in DAO format
 * @param zone Zone to save
 * @returns TRUE or FALSE
 */
bool zoneData_saveFile(zoneData_t *zone) {
  bool retcode = FALSE;
  /* Declare a variable to contain the filename. */
  char filename[PATH_MAX] = { '\0' };

  if (zone == NULL) {
    log("zoneData_saveFile(): invalid 'zone' zoneData_t.");
  } else if (!get_filename(filename, sizeof(filename), ZONE_FILE, zone->keyword)) {
    log("zoneData_saveFile(): couldn't get filename for zone %s.", zone->keyword);
  } else {
    /* Declare some iterator variables. */
    register int i = 0;
    hashMapIterator_t *iter = NULL;

    /* Declare some temporary DAO pointers. */
    daoData_t *rootDao = NULL, *subContainerDao = NULL, *zoneDao = NULL;

    rootDao = dao_newDocument();
    zoneDao = dao_newChild(rootDao, "zone");
    dao_newScalar(zoneDao, "name", "%s", zone->name);
    dao_newScalar(zoneDao, "keyword", "%s", zone->keyword);
    dao_newScalar(zoneDao, "lifespan", "%d", zone->lifespan);

    if (zone->cmd && zone->cmd->command != ZCMD_STOP) {
      /* Construct the 'resetList' container DAO. */
      subContainerDao = dao_newChild(zoneDao, "resets");

      /* Iterate over the zone's reset command list. */
      for (i = 0; zone->cmd[i].command != ZCMD_STOP; i++) {
        /* Convert each zone command to its DAO representation. */
        zoneData_resetCommandToDao(subContainerDao, i, &(zone->cmd[i]));
      }
    }

    /* Set the subcontainer DAO back to NULL. */
    subContainerDao = NULL;

    /* Save rooms if any */
    if (zone->rooms) {
      /* Allocate the 'roomList' DAO container */
      subContainerDao = dao_newChild(zoneDao, "rooms");

      /* Iterate over the rooms in the zone, and save them */
      iter = hashMapIterator_create(zone->rooms);
      while(iter && hashMapIterator_getValue(iter)) {
        roomData_t *room = (roomData_t *)hashMapIterator_getValue(iter);
        if (room) {
          roomData_toDao(subContainerDao, room);
        }
        room = NULL;
        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);
    }

    /* Set the subcontainer DAO back to NULL. */
    subContainerDao = NULL;

    /* Save items if any */
    if (zone->items) {
      subContainerDao = dao_newChild(zoneDao, "itemPrototypes");
      
      /* Iterate over the items in the zone, and save them */
      iter = hashMapIterator_create(zone->items);
      while(iter && hashMapIterator_getValue(iter)) {
        itemData_t *item = (itemData_t *)hashMapIterator_getValue(iter);
        if (item) {
          itemData_toDao(subContainerDao, item);
        }
        item = NULL;
        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);

    }

    /* Set the subcontainer DAO back to NULL. */
    subContainerDao = NULL;

    /* Save mobiles if any */
    if (zone->mobiles) {
      subContainerDao = dao_newChild(zoneDao, "mobilePrototypes");

      /* Iterate and save */
      iter = hashMapIterator_create(zone->mobiles);
      while(iter && hashMapIterator_getValue(iter)) {
        charData_t *mobile = (charData_t *)hashMapIterator_getValue(iter);
        if (mobile) {
          mobile_toDao(subContainerDao, mobile);
        }
        mobile = NULL;
        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);
    }

    /* Save shops if any */
    if (zone->shops) {
      subContainerDao = dao_newChild(zoneDao, "shops");

      /* Iterate and save */
      iter = hashMapIterator_create(zone->shops);
      while(iter && hashMapIterator_getValue(iter)) {
        shopData_t *shop = (shopData_t *)hashMapIterator_getValue(iter);
        if (shop) {
          shopData_toDao(subContainerDao, shop);
        }
        shop = NULL;
        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);
    }

    /* Save the DAO out to disk. */
    retcode = dao_saveFile(rootDao, filename);

    /* Free the top-level DAO container. */
    dao_free(rootDao);
  }
  return (retcode);
}

/**
 * Handler function to cut down on duplicated code in the zone reset command saving
 *
 * @param dao DAO being built
 * @param key String key value for dao scalar being generated
 * @param item Item we're saving an entry for
 * @param sArg String argument value if item pointer is NULL
 * @return none
 */
void save_itemCmd(daoData_t *dao, char *key, itemData_t *item, char *sArg) {

  if (dao == NULL) {
    log("save_itemCmd(): Invalid daoData_t 'dao'.");
  } else if (!key || !*key) {
    log("save_itemCmd(): Invalid char * 'key'.");
  } else {
    if (item && item->zone) {
      dao_newScalar(dao, key, "%s:%d", item->zone->keyword, item->vnum);
    } else if (sArg && *sArg) {
      dao_newScalar(dao, key, "%s", sArg);
    } else {
      log("save_itemCmd(): NULL pointer and string value.  Unable to save.");
    }
  }
}

/**
 * Handler function to cut down on duplicated code in the zone reset command saving
 *
 * @param dao DAO being built
 * @param key String key value for dao scalar being generated
 * @param room Room we're saving an entry for
 * @param sArg String argument value if room pointer is NULL
 * @return none
 */
void save_roomCmd(daoData_t *dao, char *key, roomData_t *room, char *sArg) {

  if (dao == NULL) {
    log("save_roomCmd(): Invalid daoData_t 'dao'.");
  } else if (!key || !*key) {
    log("save_roomCmd(): Invalid char * 'key'.");
  } else {
    if (room && room->zone) {
      dao_newScalar(dao, key, "%s:%d", room->zone->keyword, room->number);
    } else if (sArg && *sArg) {
      dao_newScalar(dao, key, "%s", sArg);
    } else {
      log("save_roomCmd(): NULL pointer and string value.  Unable to save.");
    }
  }
}

/**
 * Handler function to cut down on duplicated code in the zone reset command saving
 *
 * @param dao DAO being built
 * @param key String key value for dao scalar being generated
 * @param mobile Mob we're saving an entry for
 * @param sArg String argument value if mobile pointer is NULL
 * @return none
 */
void save_mobCmd(daoData_t *dao, char *key, charData_t *mobile, char *sArg) {

  if (dao == NULL) {
    log("save_mobCmd(): Invalid daoData_t 'dao'.");
  } else if (!key || !*key) {
    log("save_mobCmd(): Invalid char * 'key'.");
  } else {
    if (mobile && mobile->zone) {
      dao_newScalar(dao, key, "%s:%d", mobile->zone->keyword, mobile->vnum);
    } else if (sArg && *sArg) {
      dao_newScalar(dao, key, "%s", sArg);
    } else {
      log("save_mobCmd(): NULL pointer and string value.  Unable to save.");
    }
  }
}

/**
 * Convert a zone reset to its DAO representation.
 * @param parentDao the container DAO to contain the reset's DAO
 * @param n the index of the zone reset in the zone's reset list
 * @param cmd the zone reset to be converted
 * @return none
 */
void zoneData_resetCommandToDao(daoData_t *dao, int n, resetCommand_t *cmd) {
  if (dao == NULL) {
    log("zoneData_resetCommandToDao(): invalid 'dao' daoData_t.");
  } else if (cmd == NULL) {
    log("zoneData_resetCommandToDao(): invalid 'cmd' resetCommand_t.");
  } else {
    /* Declare some working DAO pointers. */
    daoData_t *resetDao = NULL;

    resetDao = dao_newChild(dao, "%d", n);

    if (cmd->disabled)
      dao_newScalar(resetDao, "disabled", "%s", YESNO(cmd->disabled));
    if (cmd->if_flag)
      dao_newScalar(resetDao, "then", "%s", YESNO(cmd->if_flag));

    dao_newScalar(resetDao, "command", "%s", reset_command_types[cmd->command]);

    switch (cmd->command) {
      case ZCMD_LOADMOB:
        save_mobCmd(resetDao, "mobile", cmd->mobile, cmd->sArg1);
        save_roomCmd(resetDao, "inRoom", cmd->inRoom, cmd->sArg3);
        dao_newScalar(resetDao, "maxNumber", "%d", cmd->maxNum);
        break;
      case ZCMD_LOADITEM:
        save_itemCmd(resetDao, "item", cmd->item, cmd->sArg1);
        save_roomCmd(resetDao, "inRoom", cmd->inRoom, cmd->sArg3);
        dao_newScalar(resetDao, "maxNumber", "%d", cmd->maxNum);
        break;
      case ZCMD_GIVEITEM:
        save_itemCmd(resetDao, "item", cmd->item, cmd->sArg1);
        dao_newScalar(resetDao, "maxNumber", "%d", cmd->maxNum);        
        break;
      case ZCMD_SETDOOR:
        save_roomCmd(resetDao, "inRoom", cmd->inRoom, cmd->sArg1);
        dao_newScalar(resetDao, "direction", "%s", dirs[cmd->direction]);
        /* We need a save function based on types yet =P */
        switch(cmd->state) {
          case 0:
            dao_newScalar(resetDao, "state", "open");
            break;
          case 1:
            dao_newScalar(resetDao, "state", "closed");
            break;
          case 2:
            dao_newScalar(resetDao, "state", "locked");
            break;
        }
        break;
      case ZCMD_PURGEITEM:
        save_roomCmd(resetDao, "inRoom", cmd->inRoom, cmd->sArg1);
        save_itemCmd(resetDao, "item", cmd->item, cmd->sArg2);
        break;
      case ZCMD_PURGEMOB:
        save_roomCmd(resetDao, "inRoom", cmd->inRoom, cmd->sArg1);
        save_mobCmd(resetDao, "mobile", cmd->mobile, cmd->sArg2);
        break;
      case ZCMD_EQUIPMOB:
        save_itemCmd(resetDao, "item", cmd->item, cmd->sArg1);
        dao_newScalar(resetDao, "wearLocation", "%s", equip_slots[cmd->wearLocation]);
        dao_newScalar(resetDao, "maxNumber", "%d", cmd->maxNum);        
        break;
      case ZCMD_PUTITEM:
        save_itemCmd(resetDao, "item", cmd->item, cmd->sArg1);
        save_itemCmd(resetDao, "inItem", cmd->item, cmd->sArg2);
        dao_newScalar(resetDao, "maxNumber", "%d", cmd->maxNum);        
        break;
    }
  }
}

/* ************************************************************************ */
/*  Load Zone Data from DAO                                                 */
/* ************************************************************************ */

/**
 * Generate a hash value for the vnum hash.  Just return the vnum
 *
 * @param v The VNum to be hashed
 * @return hashKey_t value of the VNum
 */
hashKey_t hashVnum(const void *v) {
  hashKey_t hVnum = *((IDXTYPE *)v);

  return (hVnum);
}

/**
 * Generate a hash value for a string.
 *
 * @param s the String a hashKey_t is being generated for (void *)
 * @return The hahsKey_t value for the string
 */
hashKey_t hashStr(const void *s) { 
  char *string = *((char **)(s));
  register int i = 0; 
  hashKey_t hashKey = (hashKey_t) 0; 

  if (string && *string) { 
    /* initialize hash key to a prime number */ 
    hashKey = 17; 
  
    for (i = 0; string[i]; i++) { 
      hashKey = (hashKey * 37) + (int) string[i]; 
    } 
  } 
  return (hashKey); 
}

/**
 * Create the zones hashMap_t and then load the zones from disk
 *
 * @param none
 * @return none
 *
 * @todo Need to add alternatives to opendir() and readdir() for other OSes.  Windows: FindFirstFile/FindNextFile
 */
void zoneData_loadWorld() {
  struct dirent *dp = NULL;
  DIR *dfd = NULL;

  /* Now we'll get a list of all the files in the directory to load */
  /* This is the part that has to have cross-platform alternatives created */
  dfd = opendir(ETC_ZONES);
  if (dfd == NULL) {
    log("loadZoneMap(): invalid 'dfd' DIR.  Aborting.");
    exit(1);
  }

  while((dp = readdir(dfd)) != NULL) {
    if (strstr(dp->d_name, ".dao")) {
      zoneDao_readFile(dp->d_name);
    }
  }
  closedir(dfd);
  /* -- */

  /* Link the world */
  zones_linkWorld();
}

/**
 * Given a file name, load the zone dao data. 
 *
 * @param filename filename in ETC_ZONES to load
 * @return none
 */
void zoneDao_readFile(char *filename) {

  if (filename == NULL || *filename == '\0') {
    log("zoneDao_readFile(): invalid 'filename' char.");
  } else {
    char fullFilename[PATH_MAX] = { '\0' };
    daoData_t *rootDao = NULL, *zoneDao = NULL;
   
    /* Build the full filename */
    snprintf(fullFilename, sizeof(fullFilename), "%s%s", ETC_ZONES, filename);

    /* Load the file into a daoData_t, or exit */
    rootDao = dao_readFile(fullFilename);
    if (rootDao == NULL) {
      log("zoneDao_readFile(): Unable to load dao data from file '%s'", fullFilename);
    }

    /* Get the "zone" group from the daoData_t, or exit */
    zoneDao = dao_query(rootDao, "/zone");
    if (zoneDao == NULL) {
      log("zoneDao_readFile(): Unable to find zone dao container in file '%s'", fullFilename);
    }

    /* Load the data from the zone daoData_t */
    zoneData_fromDao(zoneDao);

    /* Free the daoData_t */
    dao_free(rootDao);
  }
}

/**
 * Given a zone dao entry, load the zone
 *
 * @param zoneDao the daoData_t for the zone to load
 * @return none
 */
void zoneData_fromDao(daoData_t *zoneDao) {

  if (zoneDao == NULL) {
    log("zoneData_fromDao(): Invalid 'zoneDao' daoData_t.");
  } else {
    zoneData_t *zone = zoneData_new((char *)dao_queryString(zoneDao, "keyword", NULL));
    daoData_t *subDao = NULL;

    if (zone != NULL) {
      /* Let's load the zone */
      /* Name */
      zoneData_setName(zone, (char *)dao_queryString(zoneDao, "name", "(none)"));

      /* Lifespan */
      zone->lifespan = dao_queryInt(zoneDao, "lifespan", -1);

      /* Reset Commands */
      subDao = dao_query(zoneDao, "resets");
      resetCommand_fromDao(zone, subDao);
      subDao = NULL;

      /* Rooms */
      subDao = dao_query(zoneDao, "rooms");
      roomData_loadRoomsInZone(zone, subDao);
      subDao = NULL;

      /* Items */
      subDao = dao_query(zoneDao, "itemPrototypes");
      itemData_loadItemsInZone(zone, subDao);
      subDao = NULL;

      /* Mobiles */
      subDao = dao_query(zoneDao, "mobilePrototypes");
      charData_loadMobilesInZone(zone, subDao);
      subDao = NULL;

      /* Shops */
      subDao = dao_query(zoneDao, "shops");
      shopData_loadShopsInZone(zone, subDao);
      subDao = NULL;

      /* Done! */
      log(" - %-28s (%3d R, %3d I, %3d M, %3d S)", 
          zone->name,
          zone->rooms ? zone->rooms->size : 0, 
          zone->items ? zone->items->size : 0, 
          zone->mobiles ? zone->mobiles->size : 0,
          zone->shops ? zone->shops->size : 0
         );
    }
  }
}

/**
 * Count and return the number of reset commands in the resetList dao
 *
 * @param resetDao the daoData_t for the reset commands to count
 * @return the number of reset commands
 */
int resetCommand_numInDao(daoData_t *resetDao) {
  int numEntries = 0;
  daoData_t *eDao = NULL;

  for (eDao = resetDao->children; eDao; eDao = eDao->next) {
    numEntries++;
  }

  return (numEntries);
}

/**
 * Load the reset commands in the resetList dao
 *
 * @param zone The zoneData_t for the zone being loaded
 * @param resetDao The daoData_t for the reset commands to load
 * @return none
 */
void resetCommand_fromDao(zoneData_t *zone, daoData_t *resetDao) {
  int cmdNum = 0, numTotal = 0;
  daoData_t *eDao = NULL;  

  if (resetDao == NULL) {
    /* If there isn't anything then we have to add the 'S' entry */
    CREATE(zone->cmd, resetCommand_t, 1);
    numTotal = 1;
  } else {
    numTotal = (resetCommand_numInDao(resetDao) + 1);
    
    CREATE(zone->cmd, resetCommand_t, numTotal);

    for (eDao = resetDao->children; eDao; eDao = eDao->next) {
      /* First, setup some string pointers */
      const char *mobile = NULL, *inRoom = NULL, *item = NULL, *inItem = NULL;

      /* Let's do some setting for sanity's sake */
      zone->cmd[cmdNum].sArg1 = NULL;
      zone->cmd[cmdNum].sArg2 = NULL;
      zone->cmd[cmdNum].sArg3 = NULL;

      zone->cmd[cmdNum].inRoom = NULL;
      zone->cmd[cmdNum].inItem = NULL;
      zone->cmd[cmdNum].item = NULL;
      zone->cmd[cmdNum].mobile = NULL;

      /* And let's load the command */
      zone->cmd[cmdNum].command = dao_queryType(eDao, "command", reset_command_types, -1);
      if (zone->cmd[cmdNum].command == -1) {
        /* 
         * We now have a disabled flag instead of changing the command value.
         * This is done so that when we save the resets we don't lose any that
         * might have errored.  They're preserved, but disabled.
         */
        zone->cmd[cmdNum].disabled = TRUE;
      }
      
      zone->cmd[cmdNum].if_flag = FALSE;
      if (strcasecmp(dao_queryString(eDao, "then", "NO"), "YES") == 0) {
        zone->cmd[cmdNum].if_flag = TRUE;
      }

      if (strcasecmp(dao_queryString(eDao, "disabled", "NO"), "YES") == 0) {
        zone->cmd[cmdNum].disabled = TRUE;
      } else {
        zone->cmd[cmdNum].disabled = FALSE;
      }

      /* Now load the arguments */	
      switch(zone->cmd[cmdNum].command) {
        case ZCMD_LOADMOB:
          /* Read the values */
          mobile = dao_queryString(eDao, "mobile", NULL);
          inRoom = dao_queryString(eDao, "inRoom", NULL);
          zone->cmd[cmdNum].maxNum = dao_queryInt(eDao, "maxNumber", 0);

          /* Save the string values to the zone struct for later resolving */
          if (mobile)
            zone->cmd[cmdNum].sArg1 = strdup(mobile);
          if (inRoom)
            zone->cmd[cmdNum].sArg3 = strdup(inRoom);

          /* Sanity */
          mobile = NULL;
          inRoom = NULL;
          break;
        case ZCMD_LOADITEM:
          item = dao_queryString(eDao, "item", NULL);
          inRoom = dao_queryString(eDao, "inRoom", NULL);
          zone->cmd[cmdNum].maxNum = dao_queryInt(eDao, "maxNumber", 0);
          if (item)
            zone->cmd[cmdNum].sArg1 = strdup(item);
          if (inRoom)
            zone->cmd[cmdNum].sArg3 = strdup(inRoom);
          item = NULL;
          inRoom = NULL;
          break;
        case ZCMD_GIVEITEM:
          item = dao_queryString(eDao, "item", NULL);
          zone->cmd[cmdNum].maxNum = dao_queryInt(eDao, "maxNumber", 0);
          if (item)
            zone->cmd[cmdNum].sArg1 = strdup(item);
          item = NULL;
          break;
        case ZCMD_SETDOOR:
          inRoom = dao_queryString(eDao, "inRoom", NULL);
          zone->cmd[cmdNum].direction = dao_queryType(eDao, "direction", dirs, 0);
          zone->cmd[cmdNum].state = dao_queryType(eDao, "state", door_states, 0);
          if (inRoom)
            zone->cmd[cmdNum].sArg1 = strdup(inRoom);
          inRoom = NULL;
          break;
        case ZCMD_PURGEITEM:
          inRoom = dao_queryString(eDao, "inRoom", NULL);
          item = dao_queryString(eDao, "item", NULL);
          if (inRoom)
            zone->cmd[cmdNum].sArg1 = strdup(inRoom);
          if (item)
            zone->cmd[cmdNum].sArg2 = strdup(item);
          inRoom = NULL;
          item = NULL;
          break;
        case ZCMD_PURGEMOB:
          inRoom = dao_queryString(eDao, "inRoom", NULL);
          mobile = dao_queryString(eDao, "mobile", NULL);
          if (inRoom)
            zone->cmd[cmdNum].sArg1 = strdup(inRoom);
          if (mobile)
            zone->cmd[cmdNum].sArg2 = strdup(mobile);
          break;
        case ZCMD_EQUIPMOB:
          item = dao_queryString(eDao, "item", NULL);
          zone->cmd[cmdNum].maxNum = dao_queryInt(eDao, "maxNumber", 0);
          zone->cmd[cmdNum].wearLocation = dao_queryType(eDao, "wearLocation", equip_slots, 0);
          if (item)
            zone->cmd[cmdNum].sArg1 = strdup(item);
          item = NULL;
          break;
        case ZCMD_PUTITEM:
          item = dao_queryString(eDao, "item", NULL);
          inItem = dao_queryString(eDao, "inItem", NULL);
          zone->cmd[cmdNum].wearLocation = dao_queryInt(eDao, "maxNumber", 0);
          if (item)
            zone->cmd[cmdNum].sArg1 = strdup(item);
          if (inItem)
            zone->cmd[cmdNum].sArg2 = strdup(inItem);
          item = NULL;
          inItem = NULL;
          break;
        default:
          log("Unknown reset command: %d", zone->cmd[cmdNum].command);
          break;
      }
      cmdNum++;
    }
  }

  /* And end all off with the stop command */

  zone->cmd[cmdNum].command = ZCMD_STOP;
  zone->cmd[cmdNum].if_flag = FALSE;
  zone->cmd[cmdNum].disabled = FALSE;

  zone->cmd[cmdNum].sArg1 = NULL;
  zone->cmd[cmdNum].sArg2 = NULL;
  zone->cmd[cmdNum].sArg3 = NULL;

  zone->cmd[cmdNum].inRoom = NULL;
  zone->cmd[cmdNum].inItem = NULL;
  zone->cmd[cmdNum].item = NULL;
  zone->cmd[cmdNum].mobile = NULL;
}

/* ************************************************************************ */
/*  General room functions                                                  */
/* ************************************************************************ */

/**
 * Create a new zone in the zones global hashMap with keyword zKey
 *
 * @param zKey Keyword of the zone to add
 * @return pointer to zoneData_t for new zone
 */
zoneData_t *zoneData_new(char *zKey) {
  extern hashMap_t *zones;
  zoneData_t *zone = NULL;

  if (zKey == NULL) {
    log("zoneData_new(): invalid 'zKey' char: %s", zKey);
  } else {
    /* Create the zones hashMap if it isn't set */
    if (zones == NULL) {
      zones = hashMap_create(0, hashStr, NULL, zoneData_free);
      if (zones == NULL) {
        log("zoneData_new(): NULL returned from hashMap_create().  Aborting.");
        exit(1);
      }
    }
    
    /* Attempt to get a zone with this keyword from the zone */
    zone = zoneData_find(zKey);

    /* If we don't have a zone returned, create one.  Otherwise free it out */
    if (zone == NULL) {
      CREATE(zone, zoneData_t, 1);

      /* Set the Keyword */
      zone->keyword = strdup(zKey);

      /* Since this zone isn't in the hashMap, add it */
      hashMap_insert(zones, &zone->keyword, zone);
    } else {
      if (zone->name)
        free(zone->name);
      if (zone->rooms)
        hashMap_free(zone->rooms);
      if (zone->items)
        hashMap_free(zone->items);
      if (zone->mobiles)
        hashMap_free(zone->mobiles);
    }

    zone->age = 0;

    /* Sanity's sake... */

    zone->name = NULL;
    zone->rooms = NULL;
    zone->items = NULL;
    zone->mobiles = NULL;
  }

  return (zone);
}

/**
 * Set a zone's name
 *
 * @param zone Zone that's being changed
 * @param name New name for zone
 * @return none
 */
void zoneData_setName(zoneData_t *zone, char *name) {

  if (zone == NULL) {
    log("zoneData_setName(): invalid 'zone' zoneData_t.");
  } else if (name == NULL || *name == '\0') {
    log("zoneData_setName(): invalid 'name' char.");
  } else {
    if (zone->name)
      free(zone->name);

    zone->name = strdup(name);
  }
}

/**
 * Free a zone entry
 *
 * @param z The Zone to be free()d
 * @return none
 */
void zoneData_free(void *z) {
  zoneData_t *zone = (zoneData_t *)(z);

  if (zone == NULL) {
    log("zoneData_free(): invalid 'zone' zoneData_t.");
  } else {
    if (zone->name)
      free(zone->name);
    if (zone->cmd)
      free(zone->cmd);
    if (zone->rooms)
      hashMap_free(zone->rooms);
    if (zone->items)
      hashMap_free(zone->items);
    if (zone->mobiles)
      hashMap_free(zone->mobiles);

    free(zone);
    zone = NULL;
  }
}

/**
 * Remove and free all rooms in a zone.
 *
 * This is called as part of zoneData_free() which is used for the zones hashMap.
 *
 * @param zone Zone to be emptied of rooms
 * @return none
 */
void zoneData_freeRooms(zoneData_t *zone) {
  hashMapIterator_t *iter = hashMapIterator_create(zone->rooms);

  while (iter && hashMapIterator_hasNext(iter)) {
    roomData_t *room = (roomData_t *)hashMapIterator_nextValue(iter);

    roomData_free(room);
    room = NULL;
  }
  hashMapIterator_free(iter);
}

/**
 * Find a zone by keyword in the zones hashMap
 *
 * @param zKey Keyword of the zone to find
 * @return pointer to zoneData_t for the zone with matching Name
 */
zoneData_t *zoneData_find(char *zKey) {
  return (zoneData_t*) hashMap_getValue(zones, &zKey, NULL);
}

/**
 * Return the number of number of prototypes in the world
 *
 * @param protoType Which type of prototype to return. ZONES, ROOMS, MOBILES, ITEMPS
 * @return Number of prototypes found
 */
size_t numPrototypes(int protoType) {
  size_t retNum = 0;

  if (zones) {
    if (protoType == ZONES) {
      retNum = zones->size;
    } else {
      hashMapIterator_t *iter = hashMapIterator_create(zones);
      while (iter && hashMapIterator_getValue(iter)) {
        zoneData_t *zone = hashMapIterator_getValue(iter);
        if (zone) {
          switch(protoType) {
            case ROOMS:
              if (zone->rooms)
                retNum += zone->rooms->size;
              break;
            case MOBILES:
              if (zone->mobiles)
                retNum += zone->mobiles->size;
              break;
            case ITEMPS:
              if (zone->items)
                retNum += zone->items->size;
              break;
          }
        }
        zone = NULL;
        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);
    }
  }

  return retNum;
}

/**
 * Age all zones, and reset any that hit their lifespan
 *
 * @param none
 * @return none
 *
 * @todo Replace this with event scheudled resets when the event code is in
 *
 */
void zones_ageAll(void) {
  static int timer = 0;

  if (((++timer * PULSE_ZONE) / PASSES_PER_SEC) >= 60) {
    /* one minute has passed */
    /*
     * NOT accurate unless PULSE_ZONE is a multiple of PASSES_PER_SEC or a
     * factor of 60
     */
    timer = 0;

    if (zones) {
      hashMapIterator_t *iter = hashMapIterator_create(zones);
      while (iter && hashMapIterator_getValue(iter)) {
        zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);

        /* Age the zone */
        zone->age++;

        /* Reset if need be */
        if (zone->age >= zone->lifespan) {
          zone->age = 0;
          zoneData_reset(zone);
          mudlog(CMP, AUTH_WIZARD, FALSE, "Auto zone rest: %s (%s)", zone->name, zone->keyword);
        }

        hashMapIterator_next(iter);
      }
      hashMapIterator_free(iter);
    }
  }
}


/**
 * Reset a zone
 *
 * @param zone Zone to reset
 * @returns none
 */
void zoneData_reset(zoneData_t *zone) {

  if (zone == NULL) {
    log("zoneData_reset(): Invalid zoneData_t 'zone'.");
  } else {
    /* Declare some temp vars */
    register int i = 0;
    bool lastSucceeded = FALSE;
    charData_t *mobile = NULL;
    itemData_t *item = NULL, *inItem = NULL;

    for (i = 0; zone->cmd[i].command != ZCMD_STOP; i++) {
      if (zone->cmd[i].disabled == TRUE)
        continue;
      if (zone->cmd[i].if_flag == TRUE && lastSucceeded == FALSE)
        continue;

      /* Let's do the command */
      switch (zone->cmd[i].command) {
        case ZCMD_LOADMOB:
          if ((zone->cmd[i].mobile)->number < zone->cmd[i].maxNum && zone->cmd[i].inRoom && (mobile = read_mobile(zone->cmd[i].mobile))) {
            char_toRoom(mobile, zone->cmd[i].inRoom);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_LOADITEM:
          if ((zone->cmd[i].item)->number < zone->cmd[i].maxNum && zone->cmd[i].inRoom && (item = read_item(zone->cmd[i].item))) {
            itemData_toRoom(item, zone->cmd[i].inRoom);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_GIVEITEM:
          if (mobile == NULL) {
            log("zoneData_reset(): Attempt to give item to non-existant mob.  Disabling.");
            zone->cmd[i].disabled = TRUE;
            lastSucceeded = FALSE;
            break;
          }
          if ((zone->cmd[i].item)->number < zone->cmd[i].maxNum && (item = read_item(zone->cmd[i].item))) {
            itemData_toChar(item, mobile);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_SETDOOR:
          if (zone->cmd[i].inRoom && zone->cmd[i].direction >= 0 && zone->cmd[i].direction < NUM_OF_DIRS) {
            if ((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)] != NULL) {
              switch(zone->cmd[i].state) {
                case 0:
                  REMOVE_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_CLOSED);
                  REMOVE_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_LOCKED);
                  break;
                case 1:
                  SET_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_CLOSED);
                  REMOVE_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_LOCKED);
                  break;
                case 2:
                  SET_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_CLOSED);
                  SET_BIT((zone->cmd[i].inRoom)->dir[(zone->cmd[i].direction)]->exitInfo, EX_LOCKED);
                  break;
              }
              lastSucceeded = TRUE;
            } else {
              log("zoneData_reset(): Attempt to set invalid direction in room '%s:%d'.  Disabling.", zone->cmd[i].inRoom->zone->keyword, zone->cmd[i].inRoom->number);
              zone->cmd[i].disabled = TRUE;
              lastSucceeded = FALSE;
              break;
            }
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_PURGEITEM:
          if (zone->cmd[i].inRoom && (item = itemData_getInList((zone->cmd[i].inRoom)->contents, (zone->cmd[i].item)))) {
            itemData_extract(item);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_PURGEMOB:
          if (zone->cmd[i].inRoom && (mobile = charData_getMobileInList((zone->cmd[i].inRoom)->people, (zone->cmd[i].mobile)))) {
            char_extract(mobile);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_EQUIPMOB:
          if (mobile == NULL) {
            log("zoneData_reset(): Attempt to equip item on non-existant mob.  Disabling.");
            zone->cmd[i].disabled = TRUE;
            lastSucceeded = FALSE;
            break;
          }
          if ((zone->cmd[i].item)->number < zone->cmd[i].maxNum && (item = read_item(zone->cmd[i].item)) && zone->cmd[i].wearLocation >= 0 && zone->cmd[i].wearLocation < NUM_WEARS) {
            char_equipItem(mobile, item, zone->cmd[i].wearLocation);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        case ZCMD_PUTITEM:
          /* I don't know what to think about this reset command.
           * I think it should be changed to be a "then" command to work off the last item loaded
           * instead of the stock behaviour of just finding the first matching item to "inItem"
           * and using that.
           */
          if (zone->cmd[i].item->number < zone->cmd[i].maxNum && (item = read_item(zone->cmd[i].item)) && (inItem = itemData_getInWorld(zone->cmd[i].inItem))) {
            itemData_toItem(item, inItem);
            lastSucceeded = TRUE;
          } else {
            lastSucceeded = FALSE;
          }
          break;
        default:
          log("zoneData_reset(): Unknown zone reset command.  Zone '%s' command '%d'", zone->keyword, i);
          break;
      }
    }

    /* Set the age back to 0 */
    zone->age = 0;
  }
}

/**
 * Link the pointers in a zone's reset command list
 *
 * This is seperate from the loading from DAO as we have to have all zones 
 * loaded before we can resolve them.  It's an extra step, but the end result
 * is worth it.
 *
 * @param zone Zone on which to resolve reset command pointers
 * @return none
 */
void zoneData_resolveResetCommands(zoneData_t *zone) {

  if (zone == NULL) {
    log("zoneData_resolveResetCommands(): Invalid zoneData_t 'zone'.");
  } else if (zone->cmd[0].command != ZCMD_STOP) {
    /* We don't bother logging if a zone has no reset commands */
    register int i = 0;

    /* Loop through the reset commands */
    for (i = 0; zone->cmd[i].command != ZCMD_STOP; i++) {
      switch(zone->cmd[i].command) {
        case ZCMD_LOADMOB:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].mobile = charData_findMobilePrototype(zone->cmd[i].sArg1);
          if (zone->cmd[i].sArg3)
            zone->cmd[i].inRoom = roomData_find(zone->cmd[i].sArg3);

          if (zone->cmd[i].mobile == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve mobile: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }
          if (zone->cmd[i].inRoom == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve room: %s", zone->cmd[i].sArg3);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_LOADITEM:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].item = itemData_find(zone->cmd[i].sArg1);
          if (zone->cmd[i].sArg3)
            zone->cmd[i].inRoom = roomData_find(zone->cmd[i].sArg3);

          if (zone->cmd[i].item == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }
          if (zone->cmd[i].inRoom == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve room: %s", zone->cmd[i].sArg3);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_GIVEITEM:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].item = itemData_find(zone->cmd[i].sArg1);

          if (zone->cmd[i].item == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_SETDOOR:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].inRoom = roomData_find(zone->cmd[i].sArg1);

          if (zone->cmd[i].inRoom == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve room: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_PURGEITEM:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].inRoom = roomData_find(zone->cmd[i].sArg1);
          if (zone->cmd[i].sArg2)
            zone->cmd[i].item = itemData_find(zone->cmd[i].sArg2);

          if (zone->cmd[i].inRoom == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve room: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }
          if (zone->cmd[i].item == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg2);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_PURGEMOB:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].inRoom = roomData_find(zone->cmd[i].sArg1);
          if (zone->cmd[i].sArg2)
            zone->cmd[i].mobile = charData_findMobilePrototype(zone->cmd[i].sArg2);

          if (zone->cmd[i].inRoom == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve room: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }
          if (zone->cmd[i].mobile == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve mobile: %s", zone->cmd[i].sArg2);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_EQUIPMOB:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].item = itemData_find(zone->cmd[i].sArg1);

          if (zone->cmd[i].item == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }

          break;
        case ZCMD_PUTITEM:
          if (zone->cmd[i].sArg1)
            zone->cmd[i].item = itemData_find(zone->cmd[i].sArg1);
          if (zone->cmd[i].sArg2)
            zone->cmd[i].inItem = itemData_find(zone->cmd[i].sArg2);

          if (zone->cmd[i].item == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg1);
            zone->cmd[i].disabled = TRUE;
          }
          if (zone->cmd[i].inItem == NULL) {
            log("zoneData_resolveResetCommands(): Unable to resolve item: %s", zone->cmd[i].sArg2);
            zone->cmd[i].disabled = TRUE;
          }

          break;
      }
    }
  }
}

/**
 * Pass all zones to zoneData_resolveResetCommands()
 *
 * @param none
 * @return none
 */
void zones_resolveResetCommands(void) {

  if (zones) {
    hashMapIterator_t *iter = hashMapIterator_create(zones);
    while (iter && hashMapIterator_getValue(iter)) {
      zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);
      if (zone) {
        zoneData_resolveResetCommands(zone);
      }
      zone = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);
  }
}

/**
 * Search through all entities in the world looking for matches
 *
 * @param ch Character to display results to
 * @param type Type of vnum to search for
 * @param keyword Keyword to match
 * @return Number of matches found
 */
int vnum_search(charData_t *ch, int type, char *keyword) {
  int numFound = 0;
  char output[MAX_STRING_LENGTH] = { "\0" };

  if (zones) {
    hashMapIterator_t *iter = hashMapIterator_create(zones);

    snprintf(output, sizeof(output), "Matching Results\r\n----------------\r\n");

    while (iter && hashMapIterator_getValue(iter)) {
      zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);
      if (zone) {
        hashMapIterator_t *itt = NULL;
        itemData_t *tItem = NULL;
        roomData_t *tRoom = NULL;
        charData_t *tMob = NULL;
        switch(type) {
          case ITEMPS:
            if (zone->items)
              itt = hashMapIterator_create(zone->items);
            break;
          case MOBILES:
            if (zone->mobiles)
              itt = hashMapIterator_create(zone->mobiles);
            break;
          case ROOMS:
            if (zone->rooms)
              itt = hashMapIterator_create(zone->rooms);
            break;
        }
        while (itt && hashMapIterator_getValue(itt)) {
          switch(type) {
            case ITEMPS:
              tItem = (itemData_t *)hashMapIterator_getValue(itt);
              if (tItem != NULL && isname(keyword, tItem->name))
                snprintf(output + strlen(output), sizeof(output), "%5d. [%s%25s%s:%s%-7d%s] %s\r\n", 
                         ++numFound, 
                         CCCYN(ch, C_NRM), tItem->zone->keyword, CCNRM(ch, C_NRM), 
                         CCCYN(ch, C_NRM), tItem->vnum, CCNRM(ch, C_NRM), 
                         tItem->shortDescription);
              break;
            case MOBILES:
              tMob = (charData_t *)hashMapIterator_getValue(itt);
              if (tMob != NULL && isname(keyword, tMob->player.name))
                snprintf(output + strlen(output), sizeof(output), "%5d. [%s%25s%s:%s%-7d%s] %s\r\n", 
                         ++numFound, 
                         CCCYN(ch, C_NRM), tMob->zone->keyword, CCNRM(ch, C_NRM), 
                         CCCYN(ch, C_NRM), tMob->vnum, CCNRM(ch, C_NRM), 
                         tMob->player.short_descr);
              break;
            case ROOMS:
              tRoom = (roomData_t *)hashMapIterator_getValue(itt);
              if (tRoom != NULL && isname(keyword, tRoom->name))
                snprintf(output + strlen(output), sizeof(output), "%5d. [%s%25s%s:%s%-7d%s] %s\r\n", 
                         ++numFound, 
                         CCCYN(ch, C_NRM), tRoom->zone->keyword, CCNRM(ch, C_NRM), 
                         CCCYN(ch, C_NRM), tRoom->number, CCNRM(ch, C_NRM),  
                         tRoom->name);
              break;
          }
          hashMapIterator_next(itt);
        }
        if (itt)
          hashMapIterator_free(itt);
      }
      zone = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);
  }

  if (numFound > 0) {
    page_string(ch->desc, output, TRUE);
  }

  return numFound;
}

/**
 * Link exits in a room (room pointers and key pointers)
 *
 * @param room Room to link exits in
 * @return none
 */
void roomData_linkExits(roomData_t *room) {

  if (room == NULL) {
    log("roomData_linkExits(): Invalid roomData_t 'room'.");
  } else {
    int i = 0;

    for (i = 0; i < NUM_OF_DIRS; i++) {
      if (room->dir[i] == NULL)
        continue;

      /* Link the exit if we can */
      if (room->dir[i]->toRoomString) {
        room->dir[i]->toRoom = roomData_find(room->dir[i]->toRoomString);
        if (room->dir[i]->toRoom == NULL) {
          log("roomData_linkExits(): Unable to link direction %s in %s:%d to '%s'.", 
              dirs[i], room->zone->keyword, room->number, room->dir[i]->toRoomString);
        }
      }

      /* Set a pointer to the key object for the exit if any, same as above */
      if (room->dir[i]->keyString) {
        room->dir[i]->key = itemData_find(room->dir[i]->keyString);
        if (room->dir[i]->key == NULL) {
          log("roomData_linkExits(): Unable to link key '%s' in %s:%d.",
              room->dir[i]->keyString, room->zone->keyword, room->number);
        }
      }
    }
  }
}

/**
 * Link rooms in a zone (room pointers and key pointers)
 *
 * @param zone Zone to link rooms in
 * @return none
 */
void zoneData_linkRooms(zoneData_t *zone) {

  if (zone == NULL) {
    log("zoneData_linkRooms(): Invalid zoneData_t 'zone'.");
  } else {
    if (zone->rooms) {
      hashMapIterator_t *itt = hashMapIterator_create(zone->rooms);
      while(itt && hashMapIterator_getValue(itt)) {
        roomData_t *room = (roomData_t *)hashMapIterator_getValue(itt);
        if (room) {
          roomData_linkExits(room);
        }
        room = NULL;
        hashMapIterator_next(itt);
      }
      hashMapIterator_free(itt);
    }
  }
}

/**
 * Link the world
 *
 * (Let them know it's Christmas Time...)
 *
 * @param none
 * @return none
 */
void zones_linkWorld(void) {

  if (zones) {
    hashMapIterator_t *iter = hashMapIterator_create(zones);
    while (iter && hashMapIterator_getValue(iter)) {
      zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);
      if (zone) {
        /* Link the zone's reset commands */
        zoneData_resolveResetCommands(zone);

        /* Link the zone's rooms */
        zoneData_linkRooms(zone);

        /* Link the zone's shops */
        shopData_resolveInZone(zone);

      }
      zone = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);
  }
}

/**
 * List world elements to player
 *
 * @param ch Character to receive list
 * @param listType What's being listed
 */
void world_listToPlayer(charData_t *ch, int listType) {

  if (ch == NULL) {
    log("world_listToPlayer(): Invalid charData_t 'ch'.");
  } else if (zones) {
    hashMapIterator_t *iter = hashMapIterator_create(zones);
    char output[MAX_STRING_LENGTH] = { "\0" };

    switch(listType) {
      case SHOPS:
        snprintf(output, sizeof(output), "Shops\r\n-----\r\n");
        break;       
      case DEATHTRAPS:
        snprintf(output, sizeof(output), "Deathtraps\r\n----------\r\n");
        break;
      case WIZROOMS:
        snprintf(output, sizeof(output), "Wizrooms\r\n--------\r\n");
        break;
      default:
        return;
    }

    while (iter && hashMapIterator_getValue(iter)) {
      zoneData_t *zone = (zoneData_t *)hashMapIterator_getValue(iter);
      if (zone != NULL) {

        switch(listType) {
          case SHOPS:
            shops_listInZone(output, zone);
            break;
          case DEATHTRAPS:
            rooms_listInZone(DEATHTRAPS, output, zone);
            break;
          case WIZROOMS:
            rooms_listInZone(WIZROOMS, output, zone);
            break;
        }
      }
      zone = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);

    page_string(ch->desc, output, TRUE);
  }
}