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 shop.c
 * @ingroup common
 *
 * Shop code
 *
 *   This shop system is a completely re-coded one for CircleMUD^2, but was
 * based around the system originally created for CircleMUD by Jeff Fink.
 *
 *   The largest change with this code is it no longer works off of the
 * inventory of the keeper mobile.
 *
 * @author Greg Buxton
 *
 * @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
 */

#include "base.h"
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "comm.h"
#include "command.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "utils.h"
#include "shop.h"
#include "constants.h"
#include "log.h"
#include "zone.h"
#include "room.h"
#include "item.h"
#include "room.h"
#include "mobile.h"

/*
 * Internal Functions
 */
shopData_t *shopData_findInZone(zoneData_t *zone, int number);
shopData_t *shopData_new(zoneData_t *zone, int number);
void shopData_free(void *s);
void shop_addProducingItemByString(shopData_t *shop, char *sItem);
void shop_alterItemInventoryCount(shopData_t *shop, itemData_t *item, int change);
void shop_addTypeByString(shopData_t *shop, char *sType);
void shop_addRoomByString(shopData_t *shop, char *sRoom);

/*
 * External Variables
 */
extern timeInfoData_t time_info;

/*
 * Internal Variables
 */

/** Shop Trades type string values */
const char *trades_with[] = {
  "NOGOOD",
  "NOEVIL",
  "NONEUTRAL",
  "NOMAGIC_USER",
  "NO_CLERIC",
  "NOTHIEF",
  "NOWARRIOR",
  "\n"
};

/* ************************************************************************ */
/*  Save Shop Data to DAO                                                   */
/* ************************************************************************ */

/**
 * Save the main shop data to DAO
 *
 * @param parentDao DAO to save shop data to
 * @param shop Shop who's data is being saved
 * @return none
 */
void shopMain_toDao(daoData_t *parentDao, shopData_t *shop) {

  dao_newScalar(parentDao, "keeper", "%s:%d", (shop->keeper)->zone->keyword, (shop->keeper)->vnum);
  dao_newScalar(parentDao, "buyProfit", "%f", shop->profitForSale);
  dao_newScalar(parentDao, "sellProfit", "%f", shop->profitForPurchase);
  dao_newScalar(parentDao, "hourOpen1", "%d", shop->open1);
  dao_newScalar(parentDao, "hourClose1", "%d", shop->close1);
  dao_newScalar(parentDao, "hourOpen2", "%d", shop->open2);
  dao_newScalar(parentDao, "hourClose2", "%d", shop->close2);
}

/**
 * Save shop messages to DAO
 *
 * @param parentDao DAO to save shop messages to
 * @param shop Shop who's data is being saved
 * @return none
 */
void shopMessages_toDao(daoData_t *parentDao, shopData_t *shop) {
  daoData_t *subDao = NULL;

  subDao = dao_newChild(parentDao, "messgaes");
  if (subDao == NULL) {
    log("shopMessages_toDao(): dao_newChild() failed.  Aborting.");
    return;
  }

  dao_newScalar(subDao, "shopMissingItem", "%s", shop->shopMissingItem);
  dao_newScalar(subDao, "buyerMissingItem", "%s", shop->buyerMissingItem);
  dao_newScalar(subDao, "noBuy", "%s", shop->noBuy);
  dao_newScalar(subDao, "shopCantAfford", "%s", shop->shopCantAfford);
  dao_newScalar(subDao, "playerCantAfford", "%s", shop->playerCantAfford);
  dao_newScalar(subDao, "itemSold", "%s", shop->itemSold);
  dao_newScalar(subDao, "itemBought", "%s", shop->itemBought);
}

/**
 * Save shop producing items to DAO
 *
 * @param parentDao DAO to save shop items to
 * @param shop Shop who's data is being saved
 * @return none
 *
 * @todo Recode so producing[] is item pointers
 */
void shopItems_toDao(daoData_t *parentDao, shopData_t *shop) {
  daoData_t *subDao = NULL;
  shopItems_t *temp;
  int i = 0;

  if (shop->producing) {
    temp = shop->producing;
    while (temp) {
      char buf[MAX_STRING_LENGTH] = { "\0" };

      if (subDao == NULL) {
        subDao = dao_newChild(parentDao, "items");
        if (subDao == NULL) {
          log("shopItems_toDao(): dao_newChild() failed.  Aborting.");
          return;
        }
      }
      i++;

      /* Save the entry */
      snprintf(buf, sizeof(buf), "%d", i + 1);
      if (temp->item != NULL) {
        dao_newScalar(subDao, buf, "%s:%d", temp->item->zone->keyword, temp->item->vnum);
      } else {
        dao_newScalar(subDao, buf, "%s", temp->sItem);
      }

      temp = temp->next;
    }
  }
}

/**
 * Save shop trades-in-types to DAO
 *
 * @param parentDao DAO to save shop types to
 * @param shop Shop who's data is being saved
 * @return none
 */
void shopTypes_toDao(daoData_t *parentDao, shopData_t *shop) {

  if (shop == NULL) {
    log("shopTypes_toDao(): Invalid shopData_t 'shop'.");
  } else if (parentDao == NULL) {
    log("shopTypes_toDao(): Invalid daoData_t 'parentDao'.");
  } else if (shop->type != NULL) {
    daoData_t *subDao = NULL;
    shopBuyData_t *temp = NULL;
    int i = 0;

    /* Create the DAO container */
    subDao = dao_newChild(parentDao, "types");
    if (subDao == NULL) {
      log("shopTypes_toDao(): dao_newChild() failed.  Aborting.");
      return;
    }

    for (temp = shop->type; temp; temp = temp->next) {
      char buf[MAX_STRING_LENGTH] = { "\0" };
      snprintf(buf, sizeof(buf), "%d", ++i);

      dao_newScalar(subDao, buf, "%s", item_types[(temp->type)]);            
    }
  }
}

/**
 * Save shop rooms to DAO
 *
 * @param parentDao DAO to save shop rooms to
 * @param shop Shop who's data is being saved
 * @return none
 */
void shopRooms_toDao(daoData_t *parentDao, shopData_t *shop) {
  daoData_t *subDao = NULL;
  shopRooms_t *temp = NULL;
  int i = 0;

  if (shop->inRoom) {
    subDao = dao_newChild(parentDao, "rooms");
    if (subDao == NULL) {
      log("shopRooms_toDao(): dao_newChild() failed.  Aborting.");
      return;
    }
    for (temp = shop->inRoom; temp; temp = temp->next) {
      char buf[MAX_STRING_LENGTH] = { "\0" };
      i++;
      snprintf(buf, sizeof(buf), "%d", i + 1);
      dao_newScalar(subDao, buf, "%s:%d", temp->room->zone->keyword, temp->room->number);
    }
  }
}

/**
 * Save shop trade-flags to DAO
 *
 * @param parentDao DAO to save shop trade-flags to
 * @param shop Shop who's data is being saved
 * @return none
 */
void shopTrades_toDao(daoData_t *parentDao, shopData_t *shop) {
  daoData_t *subDao = NULL;

  if (shop->flags != 0) {
    subDao = dao_newChild(parentDao, "flags");
    dao_newScalar(subDao, "NOGOOD", "%s", YESNO(IS_SET(shop->flags, TRADE_NOGOOD)));
    dao_newScalar(subDao, "NOEVIL", "%s", YESNO(IS_SET(shop->flags, TRADE_NOEVIL)));
    dao_newScalar(subDao, "NONEUTRAL", "%s", YESNO(IS_SET(shop->flags, TRADE_NONEUTRAL)));
    dao_newScalar(subDao, "NOMAGIC_USER", "%s", YESNO(IS_SET(shop->flags, TRADE_NOMAGIC_USER)));
    dao_newScalar(subDao, "NOCLERIC", "%s", YESNO(IS_SET(shop->flags, TRADE_NOCLERIC)));
    dao_newScalar(subDao, "NOTHIEF", "%s", YESNO(IS_SET(shop->flags, TRADE_NOTHIEF)));
    dao_newScalar(subDao, "NOWARRIOR", "%s", YESNO(IS_SET(shop->flags, TRADE_NOWARRIOR)));
  }
}

/**
 * Save a shop to DAO
 *
 * This is broken out into the sub functions above for readability.
 *
 * @param parentDao DAO to save shop to
 * @param shop Shop being saved
 * @return none
 */
void shopData_toDao(daoData_t *parentDao, shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_toDao(): invalid 'shop' shopData_t.");
  } else if (parentDao == NULL) {
    log("shopData_toDao(): invalid 'parentDao' daoData_t.");
  } else {
    /* Declare some dao pointers */
    daoData_t *shopDao = NULL;

    /* Create DAO for the shop */
    shopDao = dao_newChild(parentDao, "%d", shop->vnum);
    if (shopDao == NULL) {
      log("shopData_toDao(): dao_newChild() failed.  Aborting.");
      return;
    }

    /* Main shop data */
    shopMain_toDao(shopDao, shop);

    /* Messages */
    shopMessages_toDao(shopDao, shop);

    /* Shop Producing Items */
    shopItems_toDao(shopDao, shop);

    /* Item Types the shop trades in */
    shopTypes_toDao(shopDao, shop);

    /* Shop rooms */
    shopRooms_toDao(shopDao, shop);

    /* Trades */
    shopTrades_toDao(shopDao, shop);
  }
}

/* ************************************************************************ */
/*  Load Shop Data from DAO                                                 */
/* ************************************************************************ */

/**
 * Load a shop's messages from their DAO representation
 *
 * @param shop Shop we're loading messages for
 * @param messageDao Messages in DAO format
 * @return none
 */
void shopMessages_fromDao(shopData_t *shop, daoData_t *messageDao) {

  if (shop == NULL) {
    log("shopMessages_fromDao(): Invalid shopData_t 'shop'.");
  } else if (messageDao != NULL) {
    const char *shopMissingItem = dao_queryString(messageDao, "shopMissingItem", NULL);
    const char *buyerMissingItem = dao_queryString(messageDao, "buyerMissingItem", NULL);
    const char *noBuy = dao_queryString(messageDao, "noBuy", NULL);
    const char *shopCantAfford = dao_queryString(messageDao, "shopCantAfford", NULL);
    const char *playerCantAfford = dao_queryString(messageDao, "playerCantAfford", NULL);
    const char *itemSold = dao_queryString(messageDao, "itemSold", NULL);
    const char *itemBought = dao_queryString(messageDao, "itemBought", NULL);

    if (shopMissingItem)  shop->shopMissingItem = strdup(shopMissingItem);
    if (buyerMissingItem) shop->buyerMissingItem = strdup(buyerMissingItem);
    if (noBuy)            shop->noBuy = strdup(noBuy);
    if (shopCantAfford)   shop->shopCantAfford = strdup(shopCantAfford);
    if (playerCantAfford) shop->playerCantAfford = strdup(playerCantAfford);
    if (itemSold)         shop->itemSold = strdup(itemSold);
    if (itemBought)       shop->itemBought = strdup(itemBought);
  }
}

/**
 * Load the items sold in a shop
 *
 * @param shop Shop to load producing items for
 * @param itemsDao DAO representation of items
 * @return none
 */
void shopItems_fromDao(shopData_t *shop, daoData_t *itemsDao) {

  if (shop == NULL) {
    log("shopItems_fromDao(): Invalid shopData_t 'shop'.");
  } else if (itemsDao != NULL) {
    daoData_t *subDao = NULL;

    /* Load the data */
    for (subDao = itemsDao->children; subDao; subDao = subDao->next) {
      const char *oStr = dao_valueString(subDao, NULL);

      if (oStr != NULL) {
        shop_addProducingItemByString(shop, (char *)oStr);
      }
    }
  }
}

/**
 * Load the item types a shop trades in
 *
 * @param shop Shop to load trades types for
 * @param typesDao DAO representation of types
 * @return none
 */
void shopTypes_fromDao(shopData_t *shop, daoData_t *typesDao) {

  if (shop == NULL) {
    log("shopTypes_fromDao(): Invalid shopData_t 'shop'.");
  } else if (typesDao != NULL) {
    daoData_t *subDao = NULL;

    for (subDao = typesDao->children; subDao; subDao = subDao->next) {
      const char *tStr = dao_valueString(subDao, NULL);

      if (tStr != NULL) {
        shop_addTypeByString(shop, (char *)tStr);
      }
    }
  }
}

/**
 * Load the rooms a shop functions in
 *
 * @param shop Shop to load rooms for
 * @param roomsDao DAO representation of rooms
 * @return none
 */
void shopRooms_fromDao(shopData_t *shop, daoData_t *roomsDao) {

  if (shop == NULL) {
    log("shopRooms_fromDao(): Invalid shopData_t 'shop'.");
  } else if (roomsDao != NULL) {
    daoData_t *subDao = NULL;

    for (subDao = roomsDao->children; subDao; subDao = subDao->next) {
      const char *rStr = dao_valueString(subDao, NULL);

      if (rStr != NULL) {
        shop_addRoomByString(shop, (char *)rStr);
      }
    }
  }
}

/**
 * Load a shop from it's DAO representation
 *
 * @param zone Zone we're loading shops in
 * @param shopDao DAO representation of the shop to load
 * @return none
 */
void shopData_fromDao(zoneData_t *zone, daoData_t *shopDao) {

  if (zone == NULL) {
    log("shopData_fromDao(): Invalid zoneData_t 'zone'.");
  } else if (shopDao == NULL) {
    log("shopData_fromDao(): Invalid daoData_t 'shopDao'.");
  } else {
    shopData_t *shop = shopData_new(zone, dao_keyInt(shopDao, -1));

    if (shop != NULL) {
      /* Temp var holding the string ID of the keeper */
      const char *sKeeper = dao_queryString(shopDao, "keeper", NULL);

      /* Let's load the main shop data */
      if (sKeeper)
        shop->sKeeper = strdup(sKeeper);
      shop->profitForSale = dao_queryDouble(shopDao, "buyProfit", 1);
      shop->profitForPurchase = dao_queryDouble(shopDao, "seeProfit", 1);
      shop->open1 = dao_queryInt(shopDao, "hourOpen1", 0);
      shop->open2 = dao_queryInt(shopDao, "hourOpen2", 0);
      shop->close1 = dao_queryInt(shopDao, "hourClose1", 0);
      shop->close2 = dao_queryInt(shopDao, "hourClose2", 0);

      /* Load messages */
      shopMessages_fromDao(shop, dao_query(shopDao, "messages"));

      /* Load "producing" items */
      shopItems_fromDao(shop, dao_query(shopDao, "items"));

      /* Load the types */
      shopTypes_fromDao(shop, dao_query(shopDao, "types"));

      /* Load the rooms */
      shopRooms_fromDao(shop, dao_query(shopDao, "rooms"));

      /* Load the trades-with flags */
      shop->flags = dao_queryBits(shopDao, "flags", trades_with, 0);
    }    
  }
}

/**
 * Load all shops in a zone DAO file
 *
 * @param zone Zone we're loading shops in
 * @param shopListDao DAO data to load the shops from
 * @return none
 */
void shopData_loadShopsInZone(zoneData_t *zone, daoData_t *shopListDao) {

  if (zone == NULL) {
    log("shopData_loadShopsInZone(): Invalid zoneData_t 'zone'.");
  } else {
    if (shopListDao != NULL) {
      daoData_t *entDao = NULL;

      for (entDao = shopListDao->children; entDao; entDao = entDao->next) {
        shopData_fromDao(zone, entDao);
      }
    }    
  }
}

/* ************************************************************************ */
/*  General Shop Functions                                                  */
/* ************************************************************************ */

/**
 * Free a shop's strings
 *
 * @param shop Shop to free strings on
 * @return none
 */
void shopData_freeStrings(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_freeStrings(): Invalid shopData_t 'shop'.");
  } else {

    /* Messages */

    if (shop->shopMissingItem)
      free(shop->shopMissingItem);
    if (shop->buyerMissingItem)
      free(shop->buyerMissingItem);
    if (shop->shopCantAfford)
      free(shop->shopCantAfford);
    if (shop->playerCantAfford)
      free(shop->playerCantAfford);
    if (shop->noBuy)
      free(shop->noBuy);
    if (shop->itemSold)
      free(shop->itemSold);
    if (shop->itemBought)
      free(shop->itemBought);

    /* Shopkeeper string */
    if (shop->sKeeper)
      free(shop->sKeeper);

    shop->shopMissingItem = NULL;
    shop->buyerMissingItem = NULL;
    shop->shopCantAfford = NULL;
    shop->playerCantAfford = NULL;
    shop->noBuy = NULL;
    shop->itemSold = NULL;
    shop->itemBought = NULL;
    shop->sKeeper = NULL;
  }
}

/**
 * Free a shop's producing items
 *
 * @param shop Shop to free producing items on
 * @return none
 */
void shopData_freeProducingItems(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_freeProducingItems(): Invalid shopData_t 'shop'.");
  } else if (shop->producing != NULL) {
    shopItems_t *temp = NULL;

    while (shop->producing) {
      temp = shop->producing;
      shop->producing = temp->next;

      if (temp->sItem)
        free(temp->sItem);
      temp->item = NULL;
      temp->next = NULL;

      free(temp);
      temp = NULL;       
    }
  }
}

/**
 * Free a shop's rooms
 *
 * @param shop Shop to free the rooms on
 * @return none
 */
void shopData_freeRooms(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_freeRooms(): Invalid shopData_t 'shop'.");
  } else if (shop->inRoom != NULL) {
    shopRooms_t *temp = NULL;

    while (shop->inRoom) {
      temp = shop->inRoom;
      shop->inRoom = temp->next;

      if (temp->sRoom)
        free(temp->sRoom);
      temp->room = NULL;
      temp->next = NULL;

      free(temp);
      temp = NULL;
    }
  }
}

/**
 * Free the trades types list
 *
 * @param shop Shop to free trade types on
 * @return none
 */
void shopData_freeTrades(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_freeTrades(): Invalid shopData_t 'shop'.");
  } else if (shop->type != NULL) {
    shopBuyData_t *temp = NULL;

    while (shop->type) {
      temp = shop->type;
      shop->type = temp->next;

      temp->next = NULL;

      free(temp);
      temp = NULL;
    }
  }
}

/**
 * Free the elements of a shop, but not the shop's allocation itself
 *
 * @param shop Shop to clear
 * @return none
 */
void shopData_clear(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_clear(): Invalid shopData_t 'shop'.");
  } else {
    /* Free the strings on the shop */
    shopData_freeStrings(shop);

    /* Free shop trade types */
    shopData_freeTrades(shop);

    /* Free producing items */
    shopData_freeProducingItems(shop);

    /* Free rooms */
    shopData_freeRooms(shop);
  }
}

/**
 * Add a new shop to a zone, by number
 *
 * This function will re-use an existing memory entry if one already exists
 * for that number.  This prevents pointers from breaking.
 *
 * @param zone Zone to create the new shop in
 * @param number Number of the shop to create
 * @return pointer to the new shop entry
 */
shopData_t *shopData_new(zoneData_t *zone, int number) {
  shopData_t *shop = NULL;

  if (zone == NULL) {
    log("shopData_new(): Invalid zoneData_t 'zone'.");
  } else if (number < 0) {
    log("shopData_new(): Invalid int 'number'.");
  } else {
    /* If the zone's shops hashMap doesn't exist, create it */
    if (zone->shops == NULL) {
      zone->shops = hashMap_create(0, hashVnum, NULL, shopData_free);
      if (zone->shops == NULL) {
        log("shopData_new(): NULL returned from hashMap_create().  Aborting.");
        exit(1);
      }
    }

    /* Attempt to get an item with this number from the zone */
    shop = shopData_findInZone(zone, number);

    if (shop == NULL) {
      CREATE(shop, shopData_t, 1);
      shop->vnum = number;
      hashMap_insert(zone->shops, &shop->vnum, shop);
    } else {
      /* Free the elements of the shop, but not it's pointer */
      shopData_clear(shop);
    }
    /* Set the zone */
    shop->zone = zone;
  }
  return (shop);
}

/**
 * List a single shop to a buffer
 *
 * @param buf Buffer to list shop in
 * @param shop Shop to list
 * @return none
 *
 * @todo Cleanup the pointers with alias wrappers
 */
void shop_addToList(char *buf, shopData_t *shop) {

  if (shop == NULL) {
    log("shop_addToList(): Invalid shopData_t 'shop'.");
  } else {
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%15s:%-7d -- Keeper: %s (%s:%d)\r\n",
             shop->zone->keyword,
             shop->vnum,
             shop->keeper->player.short_descr,
             shop->keeper->zone->keyword,
             shop->keeper->vnum);
  }
}

/**
 * List all shops in a zone into a buffer
 *
 * @param buf Buffer to generate list in
 * @param zone Zone who's shops to list
 * @return none
 */
void shops_listInZone(char *buf, zoneData_t *zone) {

  if (zone == NULL) {
    log("shops_listInZone(): Invalid zoneData_t 'zone'.");
  } else if (zone->shops != NULL) {
    hashMapIterator_t *iter = hashMapIterator_create(zone->shops);

    while (iter && hashMapIterator_getValue(iter)) {
      shopData_t *shop = (shopData_t *)hashMapIterator_getValue(iter);
      if (shop != NULL) {
        shop_addToList(buf, shop);
      }
      shop = NULL;
      hashMapIterator_next(iter);
    }
    hashMapIterator_free(iter);
  }
}

/**
 * Find a shop in a zone by number
 *
 * @param zone Zone to search for the shop in
 * @param number Numer of the shop to find
 * @return pointer to the shop or NULL
 */
shopData_t *shopData_findInZone(zoneData_t *zone, int number) {
  return (shopData_t*) hashMap_getValue(zone->shops, &number, NULL);
}

/**
 * Free a shop
 *
 * This function takes a void pointer for use in the hashMap
 *
 * @param s Shop to be freed, void pointer
 * @return none
 */
void shopData_free(void *s) {
  shopData_t *shop = (shopData_t *)(s);

  if (shop != NULL) {
    /* Free the parts of the shop */
    shopData_clear(shop);

    /* Free the shop itself */
    free(shop);
    shop = NULL;
  }
}

/**
 * Add a producing item to a shop by string ID
 *
 * This function is used in shop loading from DAO.
 * In-game (OLC) would pass an item pointer to: shop_addProducingItem()
 *
 * @param shop Shop to add the producing item to
 * @param sItem String ID of item
 * @return none
 *
 * @todo Update to linkedList_t system
 */
void shop_addProducingItemByString(shopData_t *shop, char *sItem) {

  if (shop == NULL) {
    log("shop_addProducingItemByString(): Invalid shopData_t 'shop'.");
  } else if (sItem == NULL || *sItem == '\0') {
    log("shop_addProducingItemByString(): Invalid char 'sItem'.");
  } else {
    shopItems_t *new = NULL, *temp = NULL;

    /* Create and populate */
    CREATE(new, shopItems_t, 1);
    new->sItem = strdup(sItem);
    new->item = NULL;
    new->next = NULL;

    if (shop->producing) {
      temp = shop->producing;
      while (temp && temp->next) {
        temp = temp->next;
      }
      temp->next = new;
    } else {
      shop->producing = new;
    }
  }
}

/**
 * Add a trade type to a shop by string
 *
 * This function does a search block on item_types and adds
 * a new entry to the linked list if that type was found.
 *
 * @param shop Shop to add type to
 * @param sType String value of type
 * @return none
 *
 * @todo Update to linkedList_t system
 */
void shop_addTypeByString(shopData_t *shop, char *sType) {

  if (shop == NULL) {
    log("shop_addTradeTypeByString(): Invalid shopData_t 'shop'.");
  } else if (sType == NULL || *sType == '\0') {
    log("shop_addTradeTypeByString(): Invalid char 'sType'.");
  } else {
    int tNum = search_block(sType, item_types, TRUE);

    if (tNum != -1) {
      shopBuyData_t *new = NULL, *temp = NULL;

      CREATE(new, shopBuyData_t, 1);
      new->type = tNum;
      new->next = NULL;

      if (shop->type) {
        temp = shop->type;
        while (temp && temp->next) {
          temp = temp->next;
        }
        temp->next = new;
      } else {
        shop->type = new;
      }
    }
  }
}

/**
 * Add a room to a shop by string
 *
 * This is used in loading, for OLC later add a version to add by pointer
 *
 * @param shop Shop to add the room to
 * @param sRoom String value of room
 * @return none
 *
 * @todo Update to linkedList_t system
 */ 
void shop_addRoomByString(shopData_t *shop, char *sRoom) {

  if (shop == NULL) {
    log("shop_addRoomByString(): Invalid shopData_t 'shop'.");
  } else if (sRoom == NULL || *sRoom == '\0') {
    log("shop_addRoomByString(): Invalid char 'sRoom'.");
  } else {
    shopRooms_t *new = NULL, *temp = NULL;

    CREATE(new, shopRooms_t, 1);
    new->sRoom = strdup(sRoom);
    new->room = NULL;
    new->next = NULL;

    if (shop->inRoom) {
      temp = shop->inRoom;
      while (temp && temp->next) {
        temp = temp->next;
      }
      temp->next = new;
    } else {
      shop->inRoom = new;
    }
  }
}


/**
 * Add a producing item to a shop
 *
 * @param shop Shop to add the producing item to
 * @param item Item to add (pointer to prototype)
 * @return none
 */
void shop_addProducingItem(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_addProducingItem(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_addProducingItem(): Invalid itemData_t 'item'.");
  } else {
    shopItems_t *new = NULL, *temp = NULL;

    /* Create and populate */
    CREATE(new, shopItems_t, 1);
    new->sItem = NULL;
    new->item = item;
    new->next = NULL;

    if (new) {
      if (shop->producing) {
        temp = shop->producing;
        while (temp && temp->next) {
          temp = temp->next;
        }
        temp->next = new;
      } else {
        shop->producing = new;
      }
    }
  }
}

/**
 * Check if a shop produces an item
 *
 * @param shop Shop to check
 * @param item Item to check for
 * @return TRUE or FALSE
 */
bool shop_hasItemInProducing(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_hasItemInProducing(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_hasItemInProducing(): Invalid itemData_t 'item'.");
  } else if (shop->producing) {
    shopItems_t *temp = NULL;

    temp = shop->producing;
    while (temp) {
      if (temp->item && (temp->item == item || (item->prototype && temp->item == item->prototype))) {
        return (TRUE);
      }
      temp = temp->next;
    }
  }

  return (FALSE);
}

/**
 * Check if a shop trades in an item
 * 
 * @param shop Shop to check
 * @param item Item to see if shop trades in
 * @return TRUE or FALSE
 */
bool shop_tradesInItem(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_tradesInItem(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_tradesInItem(): Invalid itemData_t 'item'.");
  } else {
    shopBuyData_t *temp = NULL;

    temp = shop->type;
    while (temp) {
      if (temp->type == GET_ITEM_TYPE(item)) {
        return (TRUE);
      }
      temp = temp->next;
    }
  }

  return (FALSE);
}

/**
 * Check if shopkeeper is in a room it can operate in
 *
 * @param keeper Shopkeeper
 * @return TRUE or FALSE
 */
bool shop_keeperInShopRoom(charData_t *keeper) {

  if (keeper == NULL) {
    log("shop_KeeperInShopRoom(): Invalid charData_t 'keeper'.");
  } else {
    if (keeper) {
      if (keeper->shop->inRoom == NULL) {
        return (TRUE);
      } else {
        shopRooms_t *temp = NULL;

        temp = keeper->shop->inRoom;
        while(temp) {
          if (keeper->room == temp->room) {
            return (TRUE);
          }
          temp = temp->next;
        }
      }
    }
  }

  return (FALSE);
}


/**
 * Check if an item is in a shop's inventory list
 *
 * @param shop Shop to check
 * @param item Item to check for
 * @return TRUE or FALSE
 */
bool shop_hasItemInInventory(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_hasItemInInventory(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_hasItemInInventory(): Invalid itemData_t 'item'.");
  } else if (shop->inventory) {
    shopInventory_t *temp = NULL;

    temp = shop->inventory;
    while (temp) {
      if (temp->item && (temp->item == item || (item->prototype && temp->item == item->prototype))) {
        return (TRUE);
      }
      temp = temp->next;
    }
  }

  return (FALSE);
}

/**
 * Add an inventory item to a shop
 *
 * @param shop Shop to add the inventory item to
 * @param item Item to add
 * @return none
 */
void shop_addInventoryItem(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_addInventoryItem(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_addInventoryItem(): Invalid itemData_t 'item'.");
  } else if (shop_hasItemInInventory(shop, item) == TRUE) {
    shop_alterItemInventoryCount(shop, item, 1);
  } else {
    shopInventory_t *new = NULL, *temp = NULL;

    CREATE(new, shopInventory_t, 1);
    if (item->prototype) {
      new->item = item->prototype;
    } else {
      new->item = item;
    }
    new->count = 1;
    new->next = NULL;

    if (shop->inventory) {
      temp = shop->inventory;
      while (temp && temp->next) {
        temp = temp->next;
      }
      temp->next = new;
    } else {
      shop->inventory = new;
    }
  }
}

/**
 * Remove an inventory item from a shop
 *
 * @param shop Shop to remove the inventory item from
 * @param item Item to remove
 * @return none
 */
void shop_removeInventoryItem(shopData_t *shop, itemData_t *item) {

  if (shop == NULL) {
    log("shop_removeInventoryItem(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_removeInventoryItem(): Invalid itemData_t 'item'.");
  } else if (shop_hasItemInInventory(shop, item) == TRUE) {
    shopInventory_t *temp = NULL, *last = NULL;

    temp = shop->inventory;
    while (temp) {
      if (temp->item && (temp->item == item || (item->prototype && temp->item == item->prototype))) {
        if (last == NULL) {
          shop->inventory = temp->next;
        } else {
          last->next = temp->next;
        }
        temp->next = NULL;
        temp->item = NULL;
        free(temp);
        temp = NULL;
        return;
      }
      last = temp;
      temp = temp->next;
    }
  }
}

/**
 * Update the count of an item in a shop's inventory
 *
 * @param shop Shop to check
 * @param item Item to change the count of
 * @param change Amount to change inventory by
 * @return none
 */
void shop_alterItemInventoryCount(shopData_t *shop, itemData_t *item, int change) {

  if (shop == NULL) {
    log("shop_alterItemInventoryCount(): Invalid shopData_t 'shop'.");
  } else if (item == NULL) {
    log("shop_alterItemInventoryCount(): Invalid itemData_t 'item'.");
  } else if (shop_hasItemInInventory(shop, item) == TRUE) {
    shopInventory_t *temp = NULL;

    temp = shop->inventory;
    while (temp) {
      if (temp->item && (temp->item == item || (item->prototype && temp->item == item->prototype))) {
        temp->count += change;
        if (temp->count <= 0) {
          shop_removeInventoryItem(shop, item);
        }
        return;
      }
      temp = temp->next;
    }
  } else {
    shop_addInventoryItem(shop, item);
  }
}

/**
 * Resolve item pointers in the producing items list
 *
 * @param shop Shop to resolve producing item pointers on
 * @return none
 */
void shopData_resolveItemPointers(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_resolveItemPointers(): Invalid shopData_t 'shop'.");
  } else {
    if (shop->producing) {
      shopItems_t *temp = NULL;

      temp = shop->producing;
      while (temp) {
        if (temp->item == NULL && temp->sItem && *temp->sItem) {
          temp->item = itemData_find(temp->sItem);
          if (temp->item == NULL) {
            log("shopData_resolveItemPointers(): Unable to find item '%s'.", temp->sItem);
          }
        }
        temp = temp->next;
      }
    }
  }  
}

/**
 * Resolve room pointers in the inRoom rooms list
 *
 * @param shop Shop to resolve the room pointers on
 * @return none
 */
void shopData_resolveRoomPointers(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_resolveRoomPointers(): Invalid shopData_t 'shop'.");
  } else {
    if (shop->inRoom) {
      shopRooms_t *temp = NULL;

      temp = shop->inRoom;
      while (temp) {
        if (temp->room == NULL && temp->sRoom && *temp->sRoom) {
          temp->room = roomData_find(temp->sRoom);
        }
        if (temp->room == NULL) {
          log("shopData_resolveRoomPointers(): Unable to find room '%s'.", temp->sRoom);
        }
        temp = temp->next;
      }

    } 
  }
}

/**
 * Resolve pointers in a shop
 *
 * @param shop Shop to resolve
 * @return none
 */
void shopData_resolvePointers(shopData_t *shop) {

  if (shop == NULL) {
    log("shopData_resolvePointers(): Invalid shopData_t 'shop'.");
  } else {

    /* Resolve the keeper */
    if (shop->sKeeper && *shop->sKeeper) {
      /* Set the pointer to the keeper */
      shop->keeper = charData_findMobilePrototype(shop->sKeeper);

      /* Set the keeper's shop pointer */
      shop->keeper->shop = shop;
    }

    /* Resolve the producing items */
    shopData_resolveItemPointers(shop);

    /* Resolve the operating rooms */
    shopData_resolveRoomPointers(shop);
  }
}

/**
 * Pass all shops in a zone to shopData_resolvePointers()
 *
 * @param zone Zone to iterate through shops in
 * @return none
 */
void shopData_resolveInZone(zoneData_t *zone) {

  if (zone != NULL && zone->shops != NULL) {
    hashMapIterator_t *iter = hashMapIterator_create(zone->shops);

    while (iter && hashMapIterator_getValue(iter)) {
      shopData_t *shop = (shopData_t *)hashMapIterator_getValue(iter);

      /* Pass the shop */
      shopData_resolvePointers(shop);

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

/**
 * Adjust price of an item based on charisma
 *
 *  Shopkeeper higher charisma (markup)
 *  ^  15: 1.2142  14: 1.2000  13: 1.1857  12: 1.1714  11: 1.1571
 *  |  10: 1.1428   9: 1.1285   8: 1.1142   7: 1.1000   6: 1.0857
 *  |   5: 1.0714   4: 1.0571   3: 1.0428   2: 1.0285   1: 1.0142
 *  +   0: 1.0000
 *  |  -1: 0.9858  -2: 0.9715  -3: 0.9572  -4: 0.9429  -5: 0.9286
 *  |  -6: 0.9143  -7: 0.9000  -8: 0.8858  -9: 0.8715 -10: 0.8572
 *  v -11: 0.8429 -12: 0.8286 -13: 0.8143 -14: 0.8000 -15: 0.7858
 *  Player higher charisma (discount)
 *
 * Most mobiles have 11 charisma so an 18 charisma player would get a 10%
 * discount beyond the basic price.  That assumes they put a lot of points
 * into charisma, because on the flip side they'd get 11% inflation by
 * having a 3.
 *
 * @param item Item who's price is being modified
 * @param keeper Shopkeeper
 * @param buyer Character buying the item
 * @return modified price
 */
int shop_sellPrice(itemData_t *item, charData_t *keeper, charData_t *buyer) {

  return (int) (item->cost * (keeper->shop)->profitForSale) * (1 + (GET_CHA(keeper) - GET_CHA(buyer)) / (float)70);
}

/**
 * Reverse of shop_sellPrice().
 *
 * When the shopkeeper is buying, we reverse the discount. Also make sure
 * we don't buy for more than we sell for, to prevent infinite money-making.
 *
 * @param item Item who's price is being modified
 * @param keeper Shopkeeper
 * @param buyer Character buying the item
 * @return modified price
 */
int shop_buyPrice(itemData_t *item, charData_t *keeper, charData_t *seller) {
  int buyPrice = (int) (item->cost * (keeper->shop)->profitForPurchase) * (1 - (GET_CHA(keeper) - GET_CHA(seller)) / (float)70);
  int sellPrice = shop_sellPrice(item, keeper, seller);

  if (buyPrice > sellPrice)
    buyPrice = sellPrice;

  return (buyPrice);
}

/**
 * Find an item in a shop by it's # (from LIST)
 *
 * @param shop Shop to find the item in
 * @param itemNum Number of the item to return
 * @return Pointer to the item (prototype) or NULL
 */
itemData_t *shop_findItemByNumber(shopData_t *shop, int itemNum) {

  if (shop == NULL) {
    log("shop_findItemByNumber(): Invalid shopData_t 'shop'.");
  } else if (itemNum > 0) {
    shopItems_t *tA = NULL;
    shopInventory_t *tB = NULL;
    int n = 0;
    
    if (shop->producing != NULL) {
      tA = shop->producing;
      while (tA) {
        n++;
        if (n == itemNum)
          return (tA->item);
        tA = tA->next;
      }
    }

    if (shop->inventory != NULL) {
      tB = shop->inventory;
      while (tB) {
        n++;
        if (n == itemNum)
          return (tB->item);
        tB = tB->next;
      }
    }

  }

  return (NULL);
}

/**
 * Add a shop's producing items to a buffer for LIST
 *
 * @param buf Buffer to add output to
 * @param ch Character who will receive the list
 * @param shop Shop to add items from
 * @param number Shop item number we're up to
 * @return Number of items listed to the buffer
 */
int shopData_addProducingItemsToBuf(char *buf, charData_t *ch, shopData_t *shop, int *num) {
  int numAdded = 0;

  if (buf == NULL) {
    log("shopData_addProducingItemsToBuf(): Invalid char 'buf'.");
  } else if (shop == NULL) {
    log("shopData_addProducingItemsToBuf(): Invalid shopData_t 'shop'.");
  } else {
    shopItems_t *temp = NULL;

    if (shop->producing != NULL) {
      temp = shop->producing;
      while(temp) {
        if (temp->item) {
          (*num)++;
          numAdded++;

          snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 
                   "%3d) Unlimited %-50s %7d\r\n", 
                   (*num), 
                   temp->item->shortDescription, 
                   shop_sellPrice(temp->item, shop->keeper, ch));
        }
        temp = temp->next;
      }
    }
  }

  return (numAdded);
}

/**
 * Add a shop's inventory items to a buffer for LIST
 * 
 * @param buf Buffer to add output to
 * @param ch Character who will receive the list
 * @param shop Shop to add items from
 * @param number Shop item number we're up to
 * @return Number of items listed to the buffer
 */
int shopData_addInventoryItemsToBuf(char *buf, charData_t *ch, shopData_t *shop, int *num) {
  int numAdded = 0;

  if (buf == NULL) {
    log("shopData_addInventoryItemsToBuf(): Invalid char 'buf'.");
  } else if (shop == NULL) {
    log("shopData_addInventoryItemsToBuf(): Invalid shopData_t 'shop'.");
  } else {
    shopInventory_t *temp = NULL;

    if (shop->inventory != NULL) {
      temp = shop->inventory;
      while(temp) {
        if (temp->item) {
          (*num)++;
          numAdded++;

          snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 
                   "%3d) %-9d %-50s %7d\r\n", 
                   (*num),
                   temp->count, 
                   temp->item->shortDescription, 
                   shop_sellPrice(temp->item, shop->keeper, ch));
        }
        temp = temp->next;
      }
    }
  }

  return (numAdded);
}

/**
 * List items for sale in a shop to a character
 *
 * @param ch Character to receive the listing
 * @param shop Shop to list items in
 * @return none
 */
void shopData_listItemsToChar(charData_t *ch, shopData_t *shop) {

  if (ch == NULL) {
    log("shopData_listItemsToChar(): Invalid charData_t 'ch'.");
  } else if (shop == NULL) {
    log("shopData_listItemsToChar(): Invalid shopData_t 'shop'.");
  } else {
    char output[MAX_STRING_LENGTH] = { "\0" };
    int ret = 0, num = 0;

    /* Build the header listing */
    snprintf(output, sizeof(output), 
             "Items that %s is selling:\r\n\r\n"
             "Num  Quantity  Item                                               Cost\r\n"
             "---- --------- -------------------------------------------------- -------\r\n", 
             shop->keeper->player.short_descr);

    /* Add items the shop produces */
    if (shop->producing) {
      ret += shopData_addProducingItemsToBuf(output, ch, shop, &num);
    }

    /* Add items the shop has in inventory */
    if (shop->inventory) {
      ret += shopData_addInventoryItemsToBuf(output, ch, shop, &num);
    }

    if (ret > 0) {
      page_string(ch->desc, output, TRUE);
    } else {
      send_to_char(ch, "It seems %s doesn't have anything for sale!\r\n", HSSH(shop->keeper));
    }
  }
}

/**
 * Check if a character can shop at a shop
 *
 * @param shop Shop to check
 * @param ch Character to check
 * @return TRUE or FALSE
 */
bool shop_canCharShop(shopData_t *shop, charData_t *ch) {

  if (shop == NULL) {
    log("shop_canCharShop(): Invalid shopData_t 'shop'.");
  } else if (ch == NULL) {
    log("shop_canCharShop(): Invalid charData_t 'ch'.");
  } else {
    if (GET_AUTH(ch) >= AUTH_OWNER)
      return (TRUE);

    if ((IS_GOOD(ch) && SHOP_TRADES(shop, TRADE_NOGOOD)) ||
        (IS_EVIL(ch) && SHOP_TRADES(shop, TRADE_NOEVIL)) ||
        (IS_NEUTRAL(ch) && SHOP_TRADES(shop, TRADE_NONEUTRAL)) ||
        (IS_MAGIC_USER(ch) && SHOP_TRADES(shop, TRADE_NOMAGIC_USER)) ||
        (IS_CLERIC(ch) && SHOP_TRADES(shop, TRADE_NOCLERIC)) || 
        (IS_THIEF(ch) && SHOP_TRADES(shop, TRADE_NOTHIEF)) ||
        (IS_WARRIOR(ch) && SHOP_TRADES(shop, TRADE_NOWARRIOR)))
      return (FALSE);

    return (TRUE);
  }

  return (FALSE);
}

/** 
 * Check if a character can sell an item to a shop
 *
 * This is where you can put in critera for if a shop can buy an item.
 * For instance, trades-types, money, or if it's a used item.
 * 
 * @param shop Shop to check
 * @param keeper Shopkeeper to check
 * @param ch Character to check
 * @param item Item to check
 * @return TRUE or FALSE
 */
bool shop_canCharSellItem(shopData_t *shop, charData_t *keeper, charData_t *ch, itemData_t *item) {
  bool ret = FALSE;

  if (shop == NULL) {
    log("shop_canCharSellItem(): Invalid shopData_t 'shop'.");
  } else if (ch == NULL) {
    log("shop_canCharSellItem(): Invalid charData_t 'ch'.");
  } else if (item == NULL) {
    log("shop_canCharSellItem(): Invalid itemData_t 'item'.");
  } else {
    char bStr[MAX_STRING_LENGTH] = { "\0" };

    if (shop->type == NULL) {
      snprintf(bStr, sizeof(bStr), "%s I don't buy things!", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
    } else if (shop_tradesInItem(shop, item) == FALSE) {
      snprintf(bStr, sizeof(bStr), "%s I don't deal in those!", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
    } else if (GET_GOLD(keeper) + GET_BANK_GOLD(keeper) < shop_buyPrice(item, keeper, ch)) {
      snprintf(bStr, sizeof(bStr), "%s I can't afford to buy any of those", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
    } else if ((GET_ITEM_TYPE(item) == ITEM_WAND || GET_ITEM_TYPE(item) == ITEM_STAFF) && (GET_ITEM_VAL(item, 2) < GET_ITEM_VAL(item, 1))) {
      snprintf(bStr, sizeof(bStr), "%s I don't buy used goods.", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
    } else {
      ret = TRUE;
    }
  }

  return (ret);
}

/**
 * Number of shopkeepers in a room
 *
 * @param ch Character looking
 * @return number of shopkeepers found
 */
int shopKeeper_numInRoom(charData_t *ch) {
  charData_t *temp = NULL;
  int num = 0;

  if (ch->room->people)
    for (temp = ch->room->people; temp; temp = temp->next_in_room)
      if (IS_NPC(temp) && CAN_SEE(ch, temp) && temp->shop != NULL)
        num++;

  return (num);
}

/**
 * Get first shopkeeper from the room ch can see
 *
 * @param ch Character looking
 * @return pointer to first shopkeeper in the room or NULL
 */
charData_t *shopKeeper_findFirstInRoom(charData_t *ch) {
  charData_t *temp = NULL;

  if (ch->room->people)
    for (temp = ch->room->people; temp; temp = temp->next_in_room)
      if (IS_NPC(temp) && CAN_SEE(ch, temp) && temp->shop != NULL)
        return (temp);

  return (NULL);
}

/**
 * Check if a shop is open or closed
 *
 * @param shop Shop to check
 * @return TRUE or FALSE
 */
bool shop_isOpen(shopData_t *shop) {
  bool retValue = FALSE;

  if (shop == NULL) {
    log("shop_isOpen(): Invalid shopData_t 'shop'.");
  } else {
    if (shop->open1 <= time_info.hours && time_info.hours <= shop->close1) {
      retValue = TRUE;
    } else if (shop->open2 > 0 && shop->close2 > 0 && 
               (shop->open2 <= time_info.hours && time_info.hours <= shop->close2)) {
      retValue = TRUE;
    }
  }

  return (retValue);
}

/**
 * Try to find the shopkeeper the character wants
 *
 * @param ch Character looking
 * @param name Name of keeper to look for or NULL
 * @return pointer to shopkeeper or NULL
 */
charData_t *shopKeeper_findInRoom(charData_t *ch, char *name) {
  char bStr[MAX_STRING_LENGTH] = { "\0" };
  int numKeepers = shopKeeper_numInRoom(ch);
  charData_t *keeper = NULL;

  if (numKeepers == 0) {
    send_to_char(ch, "There doesn't seem to be anyone here selling anything.\r\n");
  } else if (numKeepers == 1) {
    keeper = shopKeeper_findFirstInRoom(ch);
  } else if (numKeepers > 1) {
    if (name && *name) {
      keeper = get_char_room_vis(ch, name, NULL);
      if (keeper && (IS_NPC(keeper) == FALSE || keeper->shop == FALSE)) {
        send_to_char(ch, "%s don't appear to be selling anything.\r\n", UHSSH(keeper));
        keeper = NULL;
      }
    } else {
      send_to_char(ch, "Which shopkeeper are you trying to work with?\r\n");
    }
  }

  /* Found a keeper ch can see, let's see if we need to block transacations */
  if (keeper) {
    if (shop_canCharShop(keeper->shop, ch) == FALSE) {
      snprintf(bStr, sizeof(bStr), "%s I don't deal with your kind!", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
      keeper = NULL;
    } else if (shop_isOpen(keeper->shop) == FALSE) {
      snprintf(bStr, sizeof(bStr), "%s %s", GET_NAME(ch), MSG_NOT_OPEN_YET);
      do_tell(keeper, bStr, find_command("tell"));
      keeper = NULL;
    } else if (shop_keeperInShopRoom(keeper) == FALSE) {
      snprintf(bStr, sizeof(bStr), "%s I don't have a permit to sell here.", GET_NAME(ch));
      do_tell(keeper, bStr, find_command("tell"));
      keeper = NULL;
    } 
  }

  return (keeper);
}

/**
 * Add or remove gold from a shopkeeper
 *
 * @param keeper Keeper to modify
 * @param amount Amount to modify keeper's gold by
 * @return none
 */
void shop_changeKeeperGold(charData_t *keeper, int amount) {

  if (keeper == NULL) {
    log("shop_changeKeeperGold(): Invalid charData_t 'keeper'.");
  } else {
    if (amount > 0) {
      /* Add gold to keeper */
      GET_GOLD(keeper) += amount;
    } else {
      /* We're taking the keeper's gold, from them or the bank */
      /* First, switch it to a positive amount */
      amount *= -1;

      /* Shift from the bank if neither covers */
      if (GET_GOLD(keeper) < amount && GET_BANK_GOLD(keeper) < amount) {
        GET_GOLD(keeper) += GET_BANK_GOLD(keeper);
        GET_BANK_GOLD(keeper) = 0;
      }

      if (GET_GOLD(keeper) >= amount) {
        GET_GOLD(keeper) -= amount;
      } else if (GET_BANK_GOLD(keeper) >= amount) {
        GET_BANK_GOLD(keeper) -= amount;
      } else {
        log("shop_changeKeeperGold(): Reached change request illegally.");
      }
    }
  }
}

/**
 * LIST command
 *
 * This command lets a user list the items in a shop
 *
 * @param ch the character performing the command
 * @param argument the additional text passed after the command
 * @param cmd the command object that was performed
 * @return none
 */
ACMD(do_list) {
  charData_t *keeper = NULL;

  skip_spaces(&argument);
  keeper = shopKeeper_findInRoom(ch, argument);

  if (keeper) {
    shopData_listItemsToChar(ch, keeper->shop);
  }
}

/**
 * SELL command
 *
 * @param ch the character performing the command
 * @param argument the additional text passed after the command
 * @param cmd the command object that was performed
 * @return none
 */
ACMD(do_sell) {
  char kStr[MAX_INPUT_LENGTH] = { "\0" };
  char iStr[MAX_INPUT_LENGTH] = { "\0" };
  char qStr[MAX_INPUT_LENGTH] = { "\0" };
  char bStr[MAX_STRING_LENGTH] = { "\0" };
  charData_t *keeper = NULL;
  itemData_t *item = NULL;
  int quantity = 0, nArgs = 0, nGold = 0, i = 0;

  /* Parse and organize arguments */
  skip_spaces(&argument);
  nArgs = numWords(argument);

  if (nArgs == 1) {
    one_argument(argument, iStr);
    quantity = 1;
  } else if (nArgs == 2) {
    argument = one_argument(argument, kStr);
    if (kStr && *kStr && isNumber(kStr))
      quantity = atoi(kStr);
    else
      quantity = 1;
    one_argument(argument, iStr);
  } else if (nArgs >= 3) {
    argument = one_argument(argument, kStr);
    argument = one_argument(argument, qStr);
    if (qStr && *qStr && isNumber(qStr))
      quantity = atoi(qStr);
   one_argument(argument, iStr);
  } else {
    send_to_char(ch, "Usage\r\n\r\n  SELL [keeper] [qty] item\r\n");
    return;
  }

  /* Find the keeper if any */
  keeper = shopKeeper_findInRoom(ch, kStr);

  if (quantity < 1) {
    send_to_char(ch, "How many do you want to sell?\r\n");
  } else if (get_item_in_list_vis(ch, iStr, NULL, ch->carrying) == NULL) {
    send_to_char(ch, "You don't seem to have any of those.\r\n");
  } else if (keeper && shop_canCharSellItem(keeper->shop, keeper, ch, get_item_in_list_vis(ch, iStr, NULL, ch->carrying))) {
    /* Let's loop through and sell quantity number of items if we can. */
    for (i = 0; i < quantity; i++) {
      item = get_item_in_list_vis(ch, iStr, NULL, ch->carrying);

      if (item == NULL)
        break;
      if (GET_GOLD(keeper) + GET_BANK_GOLD(keeper) < shop_buyPrice(item, keeper, ch))
        break;
      if (item->prototype == NULL)
        continue;
      if (shop_tradesInItem(keeper->shop, item) == FALSE)
        continue;
      if (shop_canCharSellItem(keeper->shop, keeper, ch, item) == FALSE)
        continue;

      /* Take the money from the shopkeeper */
      shop_changeKeeperGold(keeper, (-1 * (shop_buyPrice(item, keeper, ch))));

      /* Tally the value */
      nGold += shop_buyPrice(item, keeper, ch);

      /* Kick item over to shop */
      shop_addInventoryItem(keeper->shop, item);

      /* Remove the item from the seller */
      itemData_fromChar(item);
      itemData_extract(item);
    }

    /* Build the keeper's message to the player */
    if (i != quantity) {
      /* We didn't sell all the ones the char wanted to sell */
      /* If i < quantity then we broke out of the loop before freeing the item if we had one */
      if (item == NULL) {
        snprintf(bStr, sizeof(bStr), "%s You only have %d of those!", GET_NAME(ch), i);
      } else if (GET_GOLD(keeper) + GET_BANK_GOLD(keeper) < shop_buyPrice(item, keeper, ch)) {
        snprintf(bStr, sizeof(bStr), "%s I can only afford to buy %d of those.", GET_NAME(ch), i);
      } else {
        snprintf(bStr, sizeof(bStr), "%s For some reason I'm only buying %d.", GET_NAME(ch), i);
      }
    } else {
      snprintf(bStr, sizeof(bStr), keeper->shop->itemBought, GET_NAME(ch), nGold);
    }

    /* Send the message */
    do_tell(keeper, bStr, find_command("tell"));

    /* Give the player the gold */
    GET_GOLD(ch) += nGold;

    /* Message the room */
    act("$n performs a transaction with $N.", FALSE, ch, NULL, keeper, TO_ROOM);
  }
}

/**
 * APPRAISE command
 *
 * @param ch the character performing the command
 * @param argument the additional text passed after the command
 * @param cmd the command object that was performed
 * @return none
 */
ACMD(do_appraise) {
  char kStr[MAX_INPUT_LENGTH] = { "\0" };
  char iStr[MAX_INPUT_LENGTH] = { "\0" };
  char bStr[MAX_STRING_LENGTH] = { "\0" };
  charData_t *keeper = NULL;
  itemData_t *item = NULL;
  int nArgs = 0;

  /* Parse and organize arguments */
  skip_spaces(&argument);
  nArgs = numWords(argument);

  if (nArgs == 1) {
    one_argument(argument, iStr);
  } else if (nArgs == 2) {
    argument = one_argument(argument, kStr);
    one_argument(argument, iStr);
  } else {
    send_to_char(ch, "Usage\r\n\r\n  APPRAISE [keeper] item\r\n");
    return;
  }

  /* Find the keeper if any */
  keeper = shopKeeper_findInRoom(ch, kStr);

  /* Find the item if any */
  item = get_item_in_list_vis(ch, iStr, NULL, ch->carrying);

  if (item == NULL) {
    send_to_char(ch, "You don't seem to have any of those.\r\n");
  } else if (keeper && shop_canCharSellItem(keeper->shop, keeper, ch, item)) {
    snprintf(bStr, sizeof(bStr), "%s I'd pay %d for one of those.", GET_NAME(ch), shop_buyPrice(item, keeper, ch));
    do_tell(keeper, bStr, find_command("tell"));
  }
}

/**
 * BUY command
 *
 * @param ch the character performing the command
 * @param argument the additional text passed after the command
 * @param cmd the command object that was performed
 * @return none
 */
ACMD(do_buy) {
  char kStr[MAX_INPUT_LENGTH] = { "\0" };
  char iStr[MAX_INPUT_LENGTH] = { "\0" };
  char qStr[MAX_INPUT_LENGTH] = { "\0" };
  char bStr[MAX_STRING_LENGTH] = { "\0" };
  charData_t *keeper = NULL;
  itemData_t *item = NULL;
  int quantity = 0, itemNum = 0, nArgs = 0, count = 0;

  /* Parse and organize arguments */
  skip_spaces(&argument);
  nArgs = numWords(argument);

  if (nArgs == 1) {
    one_argument(argument, iStr);
    quantity = 1;
  } else if (nArgs == 2) {
    argument = one_argument(argument, kStr);
    if (kStr && *kStr && isNumber(kStr))
      quantity = atoi(kStr);
    else
      quantity = 1;
    one_argument(argument, iStr);
  } else if (nArgs >= 3) {
    argument = one_argument(argument, kStr);
    argument = one_argument(argument, qStr);
    if (qStr && *qStr && isNumber(qStr))
      quantity = atoi(qStr);
   one_argument(argument, iStr);
  } else {
    send_to_char(ch, "Usage\r\n\r\n  BUY [keeper] [qty] item\r\n");
    return;
  }

  if (iStr && *iStr && isNumber(iStr)) {
    itemNum = atoi(iStr);
  } else {
    itemNum = -1;
  }

  /* Find the keeper if any */
  keeper = shopKeeper_findInRoom(ch, kStr);

  if (keeper) {
    if ((item = shop_findItemByNumber(keeper->shop, itemNum)) == NULL) {
      send_to_char(ch, "What are you trying to buy?\r\n");
    } else if (quantity < 1) {
      send_to_char(ch, "How many are you trying to buy?\r\n");
    } else if (IS_CARRYING_N(ch) + 1 > CAN_CARRY_N(ch)) {
      send_to_char(ch, "You arms are full!\r\n");
    } else if (IS_CARRYING_W(ch) + GET_ITEM_WEIGHT(item) > CAN_CARRY_W(ch)) {
      send_to_char(ch, "You couldn't lift it!\r\n");
    } else if (GET_AUTH(ch) < AUTH_OWNER && GET_GOLD(ch) < shop_sellPrice(item, keeper, ch)) {
      send_to_char(ch, "You can't afford it!\r\n");
    } else {
      int totalCost = 0;

      for (count = 0; count < quantity; count++) {
        itemData_t *tItem = NULL;

        item = shop_findItemByNumber(keeper->shop, itemNum);

        if (item == FALSE) {
          snprintf(bStr, sizeof(bStr), "%s I only had %d of those.", GET_NAME(ch), count);
          break;
        } else if (GET_AUTH(ch) < AUTH_OWNER && GET_GOLD(ch) < shop_sellPrice(item, keeper, ch)) {
          snprintf(bStr, sizeof(bStr), "%s You could only afford %d of those.", GET_NAME(ch), count);
          break;
        } else if (IS_CARRYING_N(ch) + 1 > CAN_CARRY_N(ch)) {
          snprintf(bStr, sizeof(bStr), "%s You could only carry %d of them.", GET_NAME(ch), count);
          break;
        } else if (IS_CARRYING_W(ch) + GET_ITEM_WEIGHT(item) > CAN_CARRY_W(ch)) {
          snprintf(bStr, sizeof(bStr), "%s You could only carry %d of them.", GET_NAME(ch), count);
          break;
        }

        if (shop_hasItemInInventory(keeper->shop, item) == TRUE)
          shop_alterItemInventoryCount(keeper->shop, item, -1);

        if (GET_AUTH(ch) < AUTH_OWNER) {
          GET_GOLD(ch) -= shop_sellPrice(item, keeper, ch);
          totalCost += shop_sellPrice(item, keeper, ch);
        }

        tItem = read_item(item);
        itemData_toChar(tItem, ch);                
      }

      if (count == quantity) {
        if (GET_AUTH(ch) < AUTH_OWNER) {
          snprintf(bStr, sizeof(bStr), "%s There you go, that'll be %d coins.", GET_NAME(ch), totalCost);
        } else {
          snprintf(bStr, sizeof(bStr), "%s Take %s with my blessing, my liege.", GET_NAME(ch), count > 1 ? "them" : "it");
        }
      }

      do_tell(keeper, bStr, find_command("tell"));
    }
  }
}