Lyonesse/bin/
Lyonesse/doc/eng/
Lyonesse/doc/ita/
Lyonesse/lib/
Lyonesse/lib/buildings/
Lyonesse/lib/clans/
Lyonesse/lib/data/
Lyonesse/lib/etc/
Lyonesse/lib/house/
Lyonesse/lib/misc/
Lyonesse/lib/plralias/A-E/
Lyonesse/lib/plralias/F-J/
Lyonesse/lib/plralias/K-O/
Lyonesse/lib/plralias/P-T/
Lyonesse/lib/plralias/U-Z/
Lyonesse/lib/plralias/ZZZ/
Lyonesse/lib/plrobjs/A-E/
Lyonesse/lib/plrobjs/F-J/
Lyonesse/lib/plrobjs/K-O/
Lyonesse/lib/plrobjs/P-T/
Lyonesse/lib/plrobjs/U-Z/
Lyonesse/lib/plrobjs/ZZZ/
Lyonesse/lib/plrsave/A-E/
Lyonesse/lib/plrsave/F-J/
Lyonesse/lib/plrsave/K-O/
Lyonesse/lib/plrsave/P-T/
Lyonesse/lib/plrsave/U-Z/
Lyonesse/lib/plrsave/ZZZ/
Lyonesse/lib/ships/
Lyonesse/lib/stables/
Lyonesse/lib/text/help/
Lyonesse/lib/world/
Lyonesse/lib/world/bld/
Lyonesse/lib/world/ship/
Lyonesse/lib/world/shp/
Lyonesse/lib/world/wls/
Lyonesse/lib/world/wls/Life/
Lyonesse/lib/world/wls/Map/
Lyonesse/log/
/**************************************************************************
 * #   #   #   ##   #  #  ###   ##   ##  ###       http://www.lyonesse.it *
 * #    # #   #  #  ## #  #    #    #    #                                *
 * #     #    #  #  # ##  ##    #    #   ##   ## ##  #  #  ##             *
 * #     #    #  #  # ##  #      #    #  #    # # #  #  #  # #            *
 * ###   #     ##   #  #  ###  ##   ##   ###  #   #  ####  ##    Ver. 1.0 *
 *                                                                        *
 * -Based on CircleMud & Smaug-     Copyright (c) 2001-2002 by Mithrandir *
 *                                                                        *
 * ********************************************************************** */
/* ************************************************************************
*   File: shop.c                                        Part of CircleMUD *
*  Usage: shopkeepers: loading config files, spec procs.                  *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

/***
 * The entire shop rewrite for Circle 3.0 was done by Jeff Fink.  Thanks Jeff!
 ***/

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "comm.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "utils.h"
#include "shop.h"
#include "constants.h"

/* External variables */
extern TIME_INFO_DATA time_info;

/* Forward/External function declarations */
ACMD(do_tell);
ACMD(do_action);
ACMD(do_echo);
ACMD(do_say);
void sort_keeper_objs(CHAR_DATA *keeper, int shop_nr);

/* Local variables */
SHOP_DATA *shop_index;
int top_shop = -1;
int cmd_say, cmd_tell, cmd_emote, cmd_slap, cmd_puke;

/* local functions */
char *read_shop_message(int mnum, room_vnum shr, FILE *shop_f, const char *why);
int read_type_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max);
int read_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max, int type);
void shopping_list(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr);
void shopping_value(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr);
void shopping_sell(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr);
OBJ_DATA *get_selling_obj(CHAR_DATA *ch, char *name, CHAR_DATA *keeper, int shop_nr, int msg);
OBJ_DATA *slide_obj(OBJ_DATA *obj, CHAR_DATA *keeper, int shop_nr);
void shopping_buy(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr);
OBJ_DATA *get_purchase_obj(CHAR_DATA *ch, char *arg, CHAR_DATA *keeper, int shop_nr, int msg);
OBJ_DATA *get_hash_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list);
OBJ_DATA *get_slide_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list);
void boot_the_shops(FILE *shop_f, char *filename, int rec_count);
void assign_the_shopkeepers(void);
char *customer_string(int shop_nr, int detailed);
void list_all_shops(CHAR_DATA *ch);
void handle_detailed_list(char *buf, char *buf1, CHAR_DATA *ch);
void list_detailed_shop(CHAR_DATA *ch, int shop_nr);
void show_shops(CHAR_DATA *ch, char *arg);
int is_ok_char(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr);
int is_open(CHAR_DATA *keeper, int shop_nr, int msg);
int is_ok(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr);
void push(STACK_DATA *stack, int pushval);
int top(STACK_DATA *stack);
int pop(STACK_DATA *stack);
void evaluate_operation(STACK_DATA *ops, STACK_DATA *vals);
int find_oper_num(char token);
int evaluate_expression(OBJ_DATA *obj, char *expr);
int trade_with(OBJ_DATA *item, int shop_nr);
int same_obj(OBJ_DATA *obj1, OBJ_DATA *obj2);
int shop_producing(OBJ_DATA *item, int shop_nr);
int transaction_amt(char *arg);
char *times_message(OBJ_DATA *obj, char *name, int num);
int buy_price(OBJ_DATA *obj, int shop_nr);
int sell_price(CHAR_DATA *ch, OBJ_DATA *obj, int shop_nr);
char *list_object(OBJ_DATA *obj, int cnt, int index, int shop_nr);
int ok_shop_room(int shop_nr, ROOM_DATA *room);
SPECIAL(shop_keeper);
int ok_damage_shopkeeper(CHAR_DATA *ch, CHAR_DATA *victim);
int add_to_list(SHOP_BUY_DATA *list, int type, int *len, int *val);
int end_read_list(SHOP_BUY_DATA *list, int len, int error);
void read_line(FILE *shop_f, const char *string, void *data);


/* config arrays */
const char *operator_str[] =
{
  "[({",
  "])}",
  "|+",
  "&*",
  "^'"
};

/* Constant list for printing out who we sell to */
const char *trade_letters[] =
{
  "Good",		/* First, the alignment based ones */
  "Evil",
  "Neutral",
  "Magic User",		/* Then the class based ones */
  "Cleric",
  "Thief",
  "Warrior",
  "Sorcerer",
  "Human",		/* And finally the race based ones */
  "Elf",
  "Dwarf",
  "\n"
};


const char *shop_bits[] =
{
  "WILL_FIGHT",
  "USES_BANK",
  "\n"
};

/* ======================================================================= */

/*
 * Function to take a shop and mob and save any non-unique items to it
 * It saves any object vnums which aren't native producing, good for crashs
 */
void save_shop_nonnative(shop_vnum shop_num, CHAR_DATA *keeper)
{
	FILE *cfile = NULL;
	OBJ_DATA *obj;

	sprintf(buf, "shop_item/%ld", shop_num);
	
	if ((cfile = fopen(buf, "w")) == NULL)
	{
		mudlog("SYSERR: SHP: Can't write new shop_item file.", BRF, LVL_IMPL, TRUE);
		exit(0);
	}
	
	/*
	 * Loops through the keeper's item list, checks if its native or not
	 * if it isnt, it writes the item's VNUM to the file 
	 */
	*buf = '\0';
	*buf2 = '\0';

	sprintf(buf2, "#%ld\n%s\n", shop_num, GET_NAME(keeper));

	for (obj = keeper->first_carrying; obj; obj = obj->next_content)
	{
		if (!shop_producing(obj, shop_num) && !OBJ_FLAGGED(obj, ITEM_UNIQUE))
			sprintf(buf2 + strlen(buf2), "%ld\n", GET_OBJ_VNUM(obj));
	}

	fprintf(cfile, buf2);
	fprintf(cfile, "$\n");
	fclose(cfile);
}

void load_shop_nonnative(shop_vnum shop_num, CHAR_DATA *keeper)
{
	FILE *cfile = NULL;
	OBJ_DATA *obj;
	int line_num = 0, placer = 0;
	obj_vnum v_this;
	
	sprintf(buf, "shop_item/%ld", shop_num);
	
	/* Check to see if we have a file for this shop number */
	if ((cfile = fopen(buf, "r")) == NULL)
		return;
	
	// Shop number
	line_num += get_line(cfile, buf);
	if (sscanf(buf, "#%d", &placer) != 1)
	{
		fprintf(stderr, "Format error in shop_item %ld, line %d.\n", shop_num, line_num);
		exit(0);
	}
	
	// Name of shopkeeper
	line_num += get_line(cfile, buf);
	
	// Item list
	while (buf && *buf != '$')
	{
		line_num += get_line(cfile, buf);
		if (!buf || !*buf || *buf == '$')
			break;
		if (sscanf(buf, "%d", &placer) != 1)
		{
			fprintf(stderr, "Format error in shop_item %ld, line %d.\n", shop_num, line_num);
			exit(0);
		}
		v_this = placer;
		obj = read_object(v_this, VIRTUAL);
		slide_obj(obj, keeper, shop_num);
	}
}

/* ======================================================================= */

int is_ok_char(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr)
{
	char buf[MAX_INPUT_LENGTH];
	
	if (!CAN_SEE(keeper, ch))
	{
		char actbuf[MAX_INPUT_LENGTH] = MSG_NO_SEE_CHAR;
		do_say(keeper, actbuf, cmd_say, 0);
		return (FALSE);
	}
	if (IS_GOD(ch))
		return (TRUE);
	
	if ((IS_GOOD(ch) && NOTRADE_GOOD(shop_nr)) ||
		(IS_EVIL(ch) && NOTRADE_EVIL(shop_nr)) ||
		(IS_NEUTRAL(ch) && NOTRADE_NEUTRAL(shop_nr)))
	{
		sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_ALIGN);
		do_tell(keeper, buf, cmd_tell, 0);
		return (FALSE);
	}
	
	if (IS_NPC(ch))
		return (TRUE);
	
	if ((IS_MAGIC_USER(ch) && NOTRADE_MAGIC_USER(shop_nr)) ||
		(IS_CLERIC(ch) && NOTRADE_CLERIC(shop_nr)) ||
		(IS_THIEF(ch) && NOTRADE_THIEF(shop_nr)) ||
		(IS_WARRIOR(ch) && NOTRADE_WARRIOR(shop_nr)) ||
		(IS_SORCERER(ch) && NOTRADE_SORCERER(shop_nr)))
	{
		sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_CLASS);
		do_tell(keeper, buf, cmd_tell, 0);
		return (FALSE);
	}

	if ((IS_HUMAN(ch) && NOTRADE_HUMAN(shop_nr)) ||
		(IS_ELF(ch) && NOTRADE_ELF(shop_nr)) ||
		(IS_DWARF(ch) && NOTRADE_DWARF(shop_nr)))
	{
		sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_SELL_RACE);
		do_tell(keeper, buf, cmd_tell, 0);
		return (FALSE);
	}
	return (TRUE);
}


int is_open(CHAR_DATA *keeper, int shop_nr, int msg)
{
	char buf[MAX_INPUT_LENGTH];
	
	*buf = '\0';
	if (SHOP_OPEN1(shop_nr) > time_info.hours)
		strcpy(buf, MSG_NOT_OPEN_YET);
	else if (SHOP_CLOSE1(shop_nr) < time_info.hours)
	{
		if (SHOP_OPEN2(shop_nr) > time_info.hours)
			strcpy(buf, MSG_NOT_REOPEN_YET);
		else if (SHOP_CLOSE2(shop_nr) < time_info.hours)
			strcpy(buf, MSG_CLOSED_FOR_DAY);
	}

	if (!*buf)
		return (TRUE);
	if (msg)
		do_say(keeper, buf, cmd_tell, 0);
	
	return (FALSE);
}


int is_ok(CHAR_DATA *keeper, CHAR_DATA *ch, int shop_nr)
{
	if (is_open(keeper, shop_nr, TRUE))
		return (is_ok_char(keeper, ch, shop_nr));
	else
		return (FALSE);
}


void push(STACK_DATA *stack, int pushval)
{
	S_DATA(stack, S_LEN(stack)++) = pushval;
}


int top(STACK_DATA *stack)
{
	if (S_LEN(stack) > 0)
		return (S_DATA(stack, S_LEN(stack) - 1));
	else
		return (NOTHING);
}


int pop(STACK_DATA *stack)
{
	if (S_LEN(stack) > 0)
		return (S_DATA(stack, --S_LEN(stack)));
	else
	{
		log("SYSERR: Illegal expression %d in shop keyword list.", S_LEN(stack));
		return (0);
	}
}


void evaluate_operation(STACK_DATA *ops, STACK_DATA *vals)
{
	int oper;
	
	if ((oper = pop(ops)) == OPER_NOT)
		push(vals, !pop(vals));
	else
	{
		int val1 = pop(vals), val2 = pop(vals);
		
		/* Compiler would previously short-circuit these. */
		if (oper == OPER_AND)
			push(vals, val1 && val2);
		else if (oper == OPER_OR)
			push(vals, val1 || val2);
	}
}


int find_oper_num(char token)
{
	int index;
	
	for (index = 0; index <= MAX_OPER; index++)
		if (strchr(operator_str[index], token))
			return (index);
	return (NOTHING);
}


int evaluate_expression(OBJ_DATA *obj, char *expr)
{
	STACK_DATA ops, vals;
	char *ptr, *end, name[MAX_INPUT_LENGTH];
	int temp, index;
	
	if (!expr || !*expr)	/* Allows opening ( first. */
		return (TRUE);
	
	ops.len = vals.len = 0;
	ptr = expr;

	while (*ptr)
	{
		if (isspace(*ptr))
			ptr++;
		else
		{
			if ((temp = find_oper_num(*ptr)) == NOTHING)
			{
				end = ptr;
				while (*ptr && !isspace(*ptr) && find_oper_num(*ptr) == NOTHING)
					ptr++;
				strncpy(name, end, ptr - end);
				name[ptr - end] = '\0';
				for (index = 0; *extra_bits[index] != '\n'; index++)
				{
					if (!str_cmp(name, extra_bits[index]))
					{
						push(&vals, OBJ_FLAGGED(obj, 1 << index));
						break;
					}
				}
				if (*extra_bits[index] == '\n')
					push(&vals, isname(name, obj->name));
			}
			else
			{
				if (temp != OPER_OPEN_PAREN)
					while (top(&ops) > temp)
						evaluate_operation(&ops, &vals);
				
				if (temp == OPER_CLOSE_PAREN)
				{
					if ((temp = pop(&ops)) != OPER_OPEN_PAREN)
					{
						log("SYSERR: Illegal parenthesis in shop keyword expression.");
						return (FALSE);
					}
				}
				else
					push(&ops, temp);
				ptr++;
			}
		}
	}

	while (top(&ops) != NOTHING)
		evaluate_operation(&ops, &vals);
	temp = pop(&vals);
	if (top(&vals) != NOTHING)
	{
		log("SYSERR: Extra operands left on shop keyword expression stack.");
		return (FALSE);
	}
	return (temp);
}


int trade_with(OBJ_DATA *item, int shop_nr)
{
	int counter;
	
	if (GET_OBJ_COST(item) < 1)
		return (OBJECT_NOVAL);
	
	if (OBJ_FLAGGED(item, ITEM_NOSELL)  ||
	    OBJ_FLAGGED(item, ITEM_DONATED) ||
	    GET_OBJ_TYPE(item) == ITEM_MONEY)
		return (OBJECT_NOTOK);
	
	for (counter = 0; SHOP_BUYTYPE(shop_nr, counter) != NOTHING; counter++)
		if (SHOP_BUYTYPE(shop_nr, counter) == GET_OBJ_TYPE(item))
		{
			if (GET_OBJ_VAL(item, 2) == 0 &&
			    (GET_OBJ_TYPE(item) == ITEM_WAND ||
			     GET_OBJ_TYPE(item) == ITEM_STAFF))
				return (OBJECT_DEAD);
			else if (evaluate_expression(item, SHOP_BUYWORD(shop_nr, counter)))
				return (OBJECT_OK);
		}

	return (OBJECT_NOTOK);
}


int same_obj(OBJ_DATA *obj1, OBJ_DATA *obj2)
{
	int index;
	
	if (!obj1 || !obj2)
		return (obj1 == obj2);
	
	if (GET_OBJ_RNUM(obj1) != GET_OBJ_RNUM(obj2))
		return (FALSE);
	
	if (GET_OBJ_COST(obj1) != GET_OBJ_COST(obj2))
		return (FALSE);
	
	if (GET_OBJ_EXTRA(obj1) != GET_OBJ_EXTRA(obj2))
		return (FALSE);
	
	for (index = 0; index < MAX_OBJ_AFF; index++)
		if ((obj1->affected[index].location != obj2->affected[index].location) ||
		    (obj1->affected[index].modifier != obj2->affected[index].modifier))
			return (FALSE);
		
	return (TRUE);
}


int shop_producing(OBJ_DATA *item, int shop_nr)
{
	int counter;
	
	if (GET_OBJ_RNUM(item) < 0)
		return (FALSE);
	
	for (counter = 0; SHOP_PRODUCT(shop_nr, counter) != NOTHING; counter++)
		if (same_obj(item, &obj_proto[SHOP_PRODUCT(shop_nr, counter)]))
			return (TRUE);
	return (FALSE);
}


int transaction_amt(char *arg)
{
  int num;
  char *buywhat;

  /*
   * If we have two arguments, it means 'buy 5 3', or buy 5 of #3.
   * We don't do that if we only have one argument, like 'buy 5', buy #5.
   * Code from Andrey Fidrya <andrey@ALEX-UA.COM>
   */
  buywhat = one_argument(arg, buf);
  if (*buywhat && *buf && is_number(buf)) {
    num = atoi(buf);
    strcpy(arg, arg + strlen(buf) + 1);
    return (num);
  }
  return (1);
}


char *times_message(OBJ_DATA *obj, char *name, int num)
{
  static char buf[256];
  char *ptr;
  int len;

  if (obj) {
    strcpy(buf, obj->short_description);
    len = strlen(obj->short_description);
  } else {
    if ((ptr = strchr(name, '.')) == NULL)
      ptr = name;
    else
      ptr++;
    len = sprintf(buf, "%s %s", AN(ptr), ptr);
  }

  if (num > 1)
    sprintf(buf + len, " (x %d)", num);
  return (buf);
}


OBJ_DATA *get_slide_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list)
{
  OBJ_DATA *i, *last_match = NULL;
  int j, number;
  char tmpname[MAX_INPUT_LENGTH];
  char *tmp;

  strcpy(tmpname, name);
  tmp = tmpname;
  if (!(number = get_number(&tmp)))
    return (NULL);

  for (i = list, j = 1; i && (j <= number); i = i->next_content)
    if (isname(tmp, i->name))
      if (CAN_SEE_OBJ(ch, i) && !same_obj(last_match, i)) {
	if (j == number)
	  return (i);
	last_match = i;
	j++;
      }
  return (NULL);
}


OBJ_DATA *get_hash_obj_vis(CHAR_DATA *ch, char *name, OBJ_DATA *list)
{
  OBJ_DATA *loop, *last_obj = NULL;
  int index;

  if (is_number(name))
    index = atoi(name);
  else if (is_number(name + 1))
    index = atoi(name + 1);
  else
    return (NULL);

  for (loop = list; loop; loop = loop->next_content)
    if (CAN_SEE_OBJ(ch, loop) && (loop->obj_flags.cost > 0))
      if (!same_obj(last_obj, loop)) {
	if (--index == 0)
	  return (loop);
	last_obj = loop;
      }
  return (NULL);
}


OBJ_DATA *get_purchase_obj(CHAR_DATA *ch, char *arg, CHAR_DATA *keeper, int shop_nr, int msg)
{
	OBJ_DATA *obj;
	char name[MAX_INPUT_LENGTH];
	
	one_argument(arg, name);
	do
	{
		if (*name == '#' || is_number(name))
			obj = get_hash_obj_vis(ch, name, keeper->first_carrying);
		else
			obj = get_slide_obj_vis(ch, name, keeper->first_carrying);
		if (!obj || GET_OBJ_TYPE(obj) == ITEM_MONEY)
		{
			if (msg)
			{
				char buf[MAX_INPUT_LENGTH];

				sprintf(buf, shop_index[shop_nr].no_such_item1, GET_NAME(ch));
				do_tell(keeper, buf, cmd_tell, 0);
			}
			return (NULL);
		}
		if (GET_OBJ_COST(obj) <= 0)
		{
			extract_obj(obj);
			obj = NULL;
		}
	} while (!obj);

	return (NULL);
}


int buy_price(OBJ_DATA *obj, int shop_nr)
{
  return (GET_OBJ_COST(obj) * SHOP_BUYPROFIT(shop_nr));
}


void shopping_buy(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr)
{
	OBJ_DATA *obj, *last_obj = NULL;
	char tempstr[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH];
	int goldamt = 0, buynum, bought = 0;
	
	if (!is_ok(keeper, ch, shop_nr))
		return;
	
	if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper))
		sort_keeper_objs(keeper, shop_nr);
	
	if ((buynum = transaction_amt(arg)) < 0)
	{
		sprintf(buf, "%s A negative amount?  Try selling me something.",
			GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		return;
	}
	if (!*arg || !buynum)
	{
		sprintf(buf, "%s What do you want to buy??", GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		return;
	}
	if (!(obj = get_purchase_obj(ch, arg, keeper, shop_nr, TRUE)))
		return;
	
	if (buy_price(obj, shop_nr) > get_gold(ch) && !IS_GOD(ch))
	{
		char actbuf[MAX_INPUT_LENGTH];

		sprintf(buf, shop_index[shop_nr].missing_cash2, GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		
		switch (SHOP_BROKE_TEMPER(shop_nr))
		{
		case 0:
			do_action(keeper, strcpy(actbuf, GET_NAME(ch)), cmd_puke, 0);
			return;
		case 1:
			do_echo(keeper, strcpy(actbuf, "smokes on his joint."), cmd_emote, SCMD_EMOTE);
			return;
		default:
			return;
		}
	}

	if (IS_CARRYING_N(ch) + 1 > CAN_CARRY_N(ch))
	{
		sprintf(buf, "%s: You can't carry any more items.\r\n",
			fname(obj->name));
		send_to_char(buf, ch);
		return;
	}

	if (IS_CARRYING_W(ch) + get_real_obj_weight(obj) > CAN_CARRY_W(ch))
	{
		sprintf(buf, "%s: You can't carry that much weight.\r\n",
			fname(obj->name));
		send_to_char(buf, ch);
		return;
	}

	while (obj && (GET_GOLD(ch) >= buy_price(obj, shop_nr) || IS_GOD(ch))
		&& IS_CARRYING_N(ch) < CAN_CARRY_N(ch) && bought < buynum
		&& IS_CARRYING_W(ch) + get_real_obj_weight(obj) <= CAN_CARRY_W(ch))
	{
		bought++;
		/* Test if producing shop ! */
		if (shop_producing(obj, shop_nr))
			obj = read_object(GET_OBJ_RNUM(obj), REAL);
		else
		{
			obj_from_char(obj);
			SHOP_SORT(shop_nr)--;
		}
		obj = obj_to_char(obj, ch);
		
		goldamt += buy_price(obj, shop_nr);
		if (!IS_GOD(ch))
			sub_gold(ch, buy_price(obj, shop_nr));
		
		last_obj = obj;
		obj = get_purchase_obj(ch, arg, keeper, shop_nr, FALSE);
		if (!same_obj(obj, last_obj))
			break;
	}
	
	if (bought < buynum)
	{
		if (!obj || !same_obj(last_obj, obj))
			sprintf(buf, "%s I only have %d to sell you.", GET_NAME(ch), bought);
		else if (get_gold(ch) < buy_price(obj, shop_nr))
			sprintf(buf, "%s You can only afford %d.", GET_NAME(ch), bought);
		else if (IS_CARRYING_N(ch) >= CAN_CARRY_N(ch))
			sprintf(buf, "%s You can only hold %d.", GET_NAME(ch), bought);
		else if (IS_CARRYING_W(ch) + GET_OBJ_WEIGHT(obj) > CAN_CARRY_W(ch))
			sprintf(buf, "%s You can only carry %d.", GET_NAME(ch), bought);
		else
			sprintf(buf, "%s Something screwy only gave you %d.", GET_NAME(ch),
			bought);
		do_tell(keeper, buf, cmd_tell, 0);
	}
	
	if (!IS_GOD(ch))
		add_gold(keeper, goldamt);
	
	strcpy(tempstr, times_message(ch->first_carrying, 0, bought));
	sprintf(buf, "$n buys %s.", tempstr);
	act(buf, FALSE, ch, obj, 0, TO_ROOM);
	
	sprintf(buf, shop_index[shop_nr].message_buy, GET_NAME(ch), goldamt);
	do_tell(keeper, buf, cmd_tell, 0);
	sprintf(buf, "You now have %s.\r\n", tempstr);
	send_to_char(buf, ch);
	
	if (SHOP_USES_BANK(shop_nr))
	{
		if (get_gold(keeper) > MAX_OUTSIDE_BANK)
		{
			int amount = get_gold(keeper) - MAX_OUTSIDE_BANK;

			SHOP_BANK(shop_nr) += amount;
			sub_gold(keeper, amount);
		}
	}

	save_shop_nonnative(shop_nr, keeper);
}


OBJ_DATA *get_selling_obj(CHAR_DATA *ch, char *name, CHAR_DATA *keeper, int shop_nr, int msg)
{
  OBJ_DATA *obj;
  char buf[MAX_STRING_LENGTH];
  int result;

  if (!(obj = get_obj_in_list_vis_rev(ch, name, NULL, ch->last_carrying))) {
    if (msg) {
      sprintf(buf, shop_index[shop_nr].no_such_item2, GET_NAME(ch));
      do_tell(keeper, buf, cmd_tell, 0);
    }
    return (NULL);
  }
  if ((result = trade_with(obj, shop_nr)) == OBJECT_OK)
    return (obj);

  switch (result) {
  case OBJECT_NOVAL:
    sprintf(buf, "%s You've got to be kidding, that thing is worthless!",
	    GET_NAME(ch));
    break;
  case OBJECT_NOTOK:
    sprintf(buf, shop_index[shop_nr].do_not_buy, GET_NAME(ch));
    break;
  case OBJECT_DEAD:
    sprintf(buf, "%s %s", GET_NAME(ch), MSG_NO_USED_WANDSTAFF);
    break;
  default:
    log("SYSERR: Illegal return value of %d from trade_with() (%s)",
	    result, __FILE__);	/* Someone might rename it... */
    sprintf(buf, "%s An error has occurred.", GET_NAME(ch));
    break;
  }
  if (msg)
    do_tell(keeper, buf, cmd_tell, 0);
  return (NULL);
}


int sell_price(CHAR_DATA *ch, OBJ_DATA *obj, int shop_nr)
{
  return (GET_OBJ_COST(obj) * SHOP_SELLPROFIT(shop_nr));
}


/*
 * This function is a slight hack!  To make sure that duplicate items are
 * only listed once on the "list", this function groups "identical"
 * objects together on the shopkeeper's inventory list.  The hack involves
 * knowing how the list is put together, and manipulating the order of
 * the objects on the list.  (But since most of DIKU is not encapsulated,
 * and information hiding is almost never used, it isn't that big a deal) -JF
 */
OBJ_DATA *slide_obj(OBJ_DATA *obj, CHAR_DATA *keeper, int shop_nr)
{
	OBJ_DATA *loop;
	int temp;
	
	if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper))
		sort_keeper_objs(keeper, shop_nr);
	
	/* Extract the object if it is identical to one produced */
	if (shop_producing(obj, shop_nr))
	{
		temp = GET_OBJ_RNUM(obj);
		extract_obj(obj);
		return (&obj_proto[temp]);
	}

	SHOP_SORT(shop_nr)++;
	loop = keeper->first_carrying;
	obj = obj_to_char(obj, keeper);
	keeper->first_carrying = loop;

	while (loop)
	{
		if (same_obj(obj, loop))
		{
			obj->next_content = loop->next_content;
			loop->next_content = obj;
			return (obj);
		}
		loop = loop->next_content;
	}

	keeper->first_carrying = obj;
	return (obj);
}


void sort_keeper_objs(CHAR_DATA *keeper, int shop_nr)
{
	OBJ_DATA *list = NULL, *temp;
	
	while (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper))
	{
		temp = keeper->first_carrying;
		obj_from_char(temp);
		temp->next_content = list;
		list = temp;
	}
	
	while (list)
	{
		temp = list;
		list = list->next_content;
		if (shop_producing(temp, shop_nr) &&
			!get_obj_in_list_num(GET_OBJ_RNUM(temp), keeper->first_carrying))
		{
			temp = obj_to_char(temp, keeper);
			SHOP_SORT(shop_nr)++;
		} else
			slide_obj(temp, keeper, shop_nr);
	}
}


void shopping_sell(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr)
{
	OBJ_DATA *obj;
	char tempstr[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH];
	int sellnum, sold = 0, goldamt = 0;
	
	if (!(is_ok(keeper, ch, shop_nr)))
		return;
	
	if ((sellnum = transaction_amt(arg)) < 0)
	{
		sprintf(buf, "%s A negative amount?  Try buying something.",
			GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		return;
	}
	if (!*arg || !sellnum)
	{
		sprintf(buf, "%s What do you want to sell??", GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		return;
	}
	one_argument(arg, name);
	if (!(obj = get_selling_obj(ch, name, keeper, shop_nr, TRUE)))
		return;
	
	if (get_gold(keeper) + SHOP_BANK(shop_nr) < sell_price(ch, obj, shop_nr))
	{
		sprintf(buf, shop_index[shop_nr].missing_cash1, GET_NAME(ch));
		do_tell(keeper, buf, cmd_tell, 0);
		return;
	}
	while (obj && get_gold(keeper) + SHOP_BANK(shop_nr) >= sell_price(ch, obj, shop_nr) &&
		sold < sellnum)
	{
		sold++;
		
		goldamt += sell_price(ch, obj, shop_nr);
		sub_gold(keeper, sell_price(ch, obj, shop_nr));
		
		obj_from_char(obj);
		slide_obj(obj, keeper, shop_nr);	/* Seems we don't use return value. */
		obj = get_selling_obj(ch, name, keeper, shop_nr, FALSE);
	}
	
	if (sold < sellnum)
	{
		if (!obj)
			sprintf(buf, "%s You only have %d of those.", GET_NAME(ch), sold);
		else if (get_gold(keeper) + SHOP_BANK(shop_nr) < sell_price(ch, obj, shop_nr))
			sprintf(buf, "%s I can only afford to buy %d of those.", GET_NAME(ch), sold);
		else
			sprintf(buf, "%s Something really screwy made me buy %d.", GET_NAME(ch), sold);
		
		do_tell(keeper, buf, cmd_tell, 0);
	}
	add_gold(ch, goldamt);
	strcpy(tempstr, times_message(0, name, sold));
	sprintf(buf, "$n sells %s.", tempstr);
	act(buf, FALSE, ch, obj, 0, TO_ROOM);
	
	sprintf(buf, shop_index[shop_nr].message_sell, GET_NAME(ch), goldamt);
	do_tell(keeper, buf, cmd_tell, 0);
	sprintf(buf, "The shopkeeper now has %s.\r\n", tempstr);
	send_to_char(buf, ch);
	
	if (get_gold(keeper) < MIN_OUTSIDE_BANK)
	{
		goldamt = MIN(MAX_OUTSIDE_BANK - get_gold(keeper), SHOP_BANK(shop_nr));
		SHOP_BANK(shop_nr) -= goldamt;
		add_gold(keeper, goldamt);
	}
	
	save_shop_nonnative(shop_nr, keeper);
}


void shopping_value(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr)
{
  OBJ_DATA *obj;
  char buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH];

  if (!is_ok(keeper, ch, shop_nr))
    return;

  if (!*arg) {
    sprintf(buf, "%s What do you want me to evaluate??", GET_NAME(ch));
    do_tell(keeper, buf, cmd_tell, 0);
    return;
  }
  one_argument(arg, name);
  if (!(obj = get_selling_obj(ch, name, keeper, shop_nr, TRUE)))
    return;

  sprintf(buf, "%s I'll give you %d gold coins for that!", GET_NAME(ch),
	  sell_price(ch, obj, shop_nr));
  do_tell(keeper, buf, cmd_tell, 0);

  return;
}


char *list_object(OBJ_DATA *obj, int cnt, int index, int shop_nr)
{
	static char buf[256];
	char buf2[300], buf3[200];
	
	if (shop_producing(obj, shop_nr))
		strcpy(buf2, "Unlimited   ");
	else
		sprintf(buf2, "%5d       ", cnt);
	sprintf(buf, " %2d)  %s", index, buf2);
	
	/* Compile object name and information */
	strcpy(buf3, obj->short_description);
	if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON && GET_OBJ_VAL(obj, 1))
		sprintf(END_OF(buf3), " of %s", drinks[GET_OBJ_VAL(obj, 2)]);
	
	/* FUTURE: */
	/* Add glow/hum/etc */
	
	if (GET_OBJ_TYPE(obj) == ITEM_WAND || GET_OBJ_TYPE(obj) == ITEM_STAFF)
		if (GET_OBJ_VAL(obj, 2) < GET_OBJ_VAL(obj, 1))
			strcat(buf3, " (partially used)");
		
	sprintf(buf2, "%-48s %6d\r\n", buf3, buy_price(obj, shop_nr));
	strcat(buf, CAP(buf2));
	return (buf);
}


void shopping_list(char *arg, CHAR_DATA *ch, CHAR_DATA *keeper, int shop_nr)
{
	OBJ_DATA *obj, *last_obj = NULL;
	char buf[MAX_STRING_LENGTH], name[MAX_INPUT_LENGTH];
	int cnt = 0, index = 0, found = FALSE;
	/* cnt is the number of that particular object available */
	
	if (!is_ok(keeper, ch, shop_nr))
		return;
	
	if (SHOP_SORT(shop_nr) < IS_CARRYING_N(keeper))
		sort_keeper_objs(keeper, shop_nr);
	
	one_argument(arg, name);
	strcpy(buf, " ##   Available   Item                                               Cost\r\n");
	strcat(buf, "-------------------------------------------------------------------------\r\n");
	if (keeper->first_carrying)
	{
		for (obj = keeper->first_carrying; obj; obj = obj->next_content)
		{
			if (CAN_SEE_OBJ(ch, obj) && GET_OBJ_COST(obj) > 0 &&
				GET_OBJ_TYPE(obj) != ITEM_MONEY )
			{
				if (!last_obj)
				{
					last_obj = obj;
					cnt = 1;
				}
				else if (same_obj(last_obj, obj))
					cnt++;
				else
				{
					index++;
					if (!*name || isname(name, last_obj->name))
					{
						strcat(buf, list_object(last_obj, cnt, index, shop_nr));
						found = TRUE;
					}
					cnt = 1;
					last_obj = obj;
				}
			}
		}
	}
	index++;
	if (!last_obj) /* we actually have nothing in our list for sale, period */
		strcpy(buf, "Currently, there is nothing for sale.\r\n");
	else if (!*name || isname(name, last_obj->name)) /* show last obj */
		strcat(buf, list_object(last_obj, cnt, index, shop_nr));
	else if (!found) /* nothing the char was looking for was found */
		strcpy(buf, "Presently, none of those are for sale.\r\n");
	
	page_string(ch->desc, buf, TRUE);
}


int ok_shop_room(int shop_nr, ROOM_DATA *room)
{
	int index;
	
	for (index = 0; SHOP_ROOM(shop_nr, index) != NULL; index++)
		if (SHOP_ROOM(shop_nr, index) == room)
			return (TRUE);
	return (FALSE);
}


SPECIAL(shop_keeper)
{
	CHAR_DATA *keeper = (CHAR_DATA *) me;
	char argm[MAX_INPUT_LENGTH];
	int shop_nr;
	
	for (shop_nr = 0; shop_nr <= top_shop; shop_nr++)
	{
		if (SHOP_KEEPER(shop_nr) == keeper->nr)
			break;
	}
	
	if (shop_nr > top_shop)
		return (FALSE);
	
	if (SHOP_FUNC(shop_nr))	/* Check secondary function */
	{
		if ((SHOP_FUNC(shop_nr)) (ch, me, cmd, argument))
			return (TRUE);
	}
	
	if (keeper == ch)
	{
		if (cmd)
			SHOP_SORT(shop_nr) = 0;	/* Safety in case "drop all" */
		return (FALSE);
	}

	if (!ok_shop_room(shop_nr, ch->in_room))
		return (0);
	
	if (!AWAKE(keeper))
		return (FALSE);
	
	if (CMD_IS("steal"))
	{
		sprintf(argm, "$N shouts '%s'", MSG_NO_STEAL_HERE);
		do_action(keeper, GET_NAME(ch), cmd_slap, 0);
		act(argm, FALSE, ch, 0, keeper, TO_CHAR);
		return (TRUE);
	}
	
	if (CMD_IS("buy"))
	{
		shopping_buy(argument, ch, keeper, shop_nr);
		return (TRUE);
	}
	else if (CMD_IS("sell"))
	{
		shopping_sell(argument, ch, keeper, shop_nr);
		return (TRUE);
	}
	else if (CMD_IS("value"))
	{
		shopping_value(argument, ch, keeper, shop_nr);
		return (TRUE);
	}
	else if (CMD_IS("list"))
	{
		shopping_list(argument, ch, keeper, shop_nr);
		return (TRUE);
	}
	
	return (FALSE);
}


int ok_damage_shopkeeper(CHAR_DATA *ch, CHAR_DATA *victim)
{
  char buf[MAX_INPUT_LENGTH];
  int index;

  /* Prevent "invincible" shopkeepers if they're charmed. */
  if (AFF_FLAGGED(victim, AFF_CHARM))
    return (TRUE);

  if (IS_MOB(victim) && mob_index[GET_MOB_RNUM(victim)].func == shop_keeper)
    for (index = 0; index <= top_shop; index++)
      if (GET_MOB_RNUM(victim) == SHOP_KEEPER(index) && !SHOP_KILL_CHARS(index))
      {
	do_action(victim, GET_NAME(ch), cmd_slap, 0);
	sprintf(buf, "%s %s", GET_NAME(ch), MSG_CANT_KILL_KEEPER);
	do_tell(victim, buf, cmd_tell, 0);
	return (FALSE);
      }
  return (TRUE);
}


/* val == obj_vnum and obj_rnum (?) */
int add_to_list(SHOP_BUY_DATA *list, int type, int *len, int *val)
{
	if (*val != NOTHING)
	{
		if (*len < MAX_SHOP_OBJ)
		{
			if (type == LIST_PRODUCE)
				*val = real_object(*val);
			if (*val != NOTHING)
			{
				BUY_TYPE(list[*len]) = *val;
				BUY_WORD(list[(*len)++]) = 0;
			}
			else
				*val = NOTHING;
			return (FALSE);
		}
		else
			return (TRUE);
	}
	return (FALSE);
}


int end_read_list(SHOP_BUY_DATA *list, int len, int error)
{
	if (error)
		log("SYSERR: Raise MAX_SHOP_OBJ constant in shop.h to %d", len + error);
	BUY_WORD(list[len]) = NULL;
	BUY_TYPE(list[len++]) = NOTHING;
	return (len);
}


void read_line(FILE *shop_f, const char *string, void *data)
{
	if (!get_line(shop_f, buf) || !sscanf(buf, string, data))
	{
		log("SYSERR: Error in shop #%d\n", SHOP_NUM(top_shop));
		exit(1);
	}
}


int read_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max, int type)
{
	int count, temp, len = 0, error = 0;
	
	if (new_format)
	{
		do
		{
			read_line(shop_f, "%d", &temp);
			error += add_to_list(list, type, &len, &temp);
		} while (temp >= 0);
	}
	else
		for (count = 0; count < max; count++)
		{
			read_line(shop_f, "%d", &temp);
			error += add_to_list(list, type, &len, &temp);
		}
	
	return (end_read_list(list, len, error));
}

/* END_OF inefficient. */
int read_type_list(FILE *shop_f, SHOP_BUY_DATA *list, int new_format, int max)
{
  int index, num, len = 0, error = 0;
  char *ptr;

  if (!new_format)
    return (read_list(shop_f, list, 0, max, LIST_TRADE));
  do {
    fgets(buf, MAX_STRING_LENGTH - 1, shop_f);
    if ((ptr = strchr(buf, ';')) != NULL)
      *ptr = '\0';
    else
      *(END_OF(buf) - 1) = '\0';
    for (index = 0, num = NOTHING; *item_types[index] != '\n'; index++)
      if (!strn_cmp(item_types[index], buf, strlen(item_types[index]))) {
	num = index;
	strcpy(buf, buf + strlen(item_types[index]));
	break;
      }
    ptr = buf;
    if (num == NOTHING) {
      sscanf(buf, "%d", &num);
      while (!isdigit(*ptr))
	ptr++;
      while (isdigit(*ptr))
	ptr++;
    }
    while (isspace(*ptr))
      ptr++;
    while (isspace(*(END_OF(ptr) - 1)))
      *(END_OF(ptr) - 1) = '\0';
    error += add_to_list(list, LIST_TRADE, &len, &num);
    if (*ptr)
      BUY_WORD(list[len - 1]) = str_dup(ptr);
  } while (num >= 0);
  return (end_read_list(list, len, error));
}


char *read_shop_message(int mnum, room_vnum shr, FILE *shop_f, const char *why)
{
  int cht, ss = 0, ds = 0, err = 0;
  char *tbuf;

  if (!(tbuf = fread_string(shop_f, why)))
    return (NULL);

  for (cht = 0; tbuf[cht]; cht++) {
    if (tbuf[cht] != '%')
      continue;

    if (tbuf[cht + 1] == 's')
      ss++;
    else if (tbuf[cht + 1] == 'd' && (mnum == 5 || mnum == 6)) {
      if (ss == 0) {
        log("SYSERR: Shop #%d has %%d before %%s, message #%d.", shr, mnum);
        err++;
      }
      ds++;
    } else if (tbuf[cht + 1] != '%') {
      log("SYSERR: Shop #%d has invalid format '%%%c' in message #%d.", shr, tbuf[cht + 1], mnum);
      err++;
    }
  }

  if (ss > 1 || ds > 1) {
    log("SYSERR: Shop #%d has too many specifiers for message #%d. %%s=%d %%d=%d", shr, mnum, ss, ds);
    err++;
  }

  if (err) {
    free(tbuf);
    return (NULL);
  }
  return (tbuf);
}


void boot_the_shops(FILE *shop_f, char *filename, int rec_count)
{
	SHOP_BUY_DATA list[MAX_SHOP_OBJ + 1];
	char *buf, buf2[MAX_INPUT_LENGTH];
	int temp, count, new_format = FALSE;
	int done = FALSE;
	
	sprintf(buf2, "beginning of shop file %s", filename);
	
	while (!done)
	{
		buf = fread_string(shop_f, buf2);
		if (*buf == '#')		/* New shop */
		{
			sscanf(buf, "#%d\n", &temp);
			sprintf(buf2, "shop #%d in shop file %s", temp, filename);
			free(buf);		/* Plug memory leak! */

			top_shop++;
			if (!top_shop)
				CREATE(shop_index, SHOP_DATA, rec_count);
			
			SHOP_NUM(top_shop) = temp;

			temp = read_list(shop_f, list, new_format, MAX_PROD, LIST_PRODUCE);

			CREATE(shop_index[top_shop].producing, obj_vnum, temp);
			for (count = 0; count < temp; count++)
				SHOP_PRODUCT(top_shop, count) = BUY_TYPE(list[count]);
			
			read_line(shop_f, "%f", &SHOP_BUYPROFIT(top_shop));
			read_line(shop_f, "%f", &SHOP_SELLPROFIT(top_shop));
			
			temp = read_type_list(shop_f, list, new_format, MAX_TRADE);

			CREATE(shop_index[top_shop].type, SHOP_BUY_DATA, temp);
			for (count = 0; count < temp; count++)
			{
				SHOP_BUYTYPE(top_shop, count) = BUY_TYPE(list[count]);
				SHOP_BUYWORD(top_shop, count) = BUY_WORD(list[count]);
			}
			
			shop_index[top_shop].no_such_item1	= read_shop_message(0, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].no_such_item2	= read_shop_message(1, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].do_not_buy		= read_shop_message(2, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].missing_cash1	= read_shop_message(3, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].missing_cash2	= read_shop_message(4, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].message_buy	= read_shop_message(5, SHOP_NUM(top_shop), shop_f, buf2);
			shop_index[top_shop].message_sell	= read_shop_message(6, SHOP_NUM(top_shop), shop_f, buf2);
			
			read_line(shop_f, "%d", &SHOP_BROKE_TEMPER(top_shop));
			read_line(shop_f, "%d", &SHOP_BITVECTOR(top_shop));
			read_line(shop_f, "%hd", &SHOP_KEEPER(top_shop));
			
			SHOP_KEEPER(top_shop)			= real_mobile(SHOP_KEEPER(top_shop));
			read_line(shop_f, "%d", &SHOP_TRADE_WITH(top_shop));
			
			temp = read_list(shop_f, list, new_format, 1, LIST_ROOM);
			CREATE(shop_index[top_shop].in_room, ROOM_DATA *, temp);
			for (count = 0; count < temp; count++)
				SHOP_ROOM(top_shop, count) = get_room(BUY_TYPE(list[count]));
			
			read_line(shop_f, "%d", &SHOP_OPEN1(top_shop));
			read_line(shop_f, "%d", &SHOP_CLOSE1(top_shop));
			read_line(shop_f, "%d", &SHOP_OPEN2(top_shop));
			read_line(shop_f, "%d", &SHOP_CLOSE2(top_shop));
			
			SHOP_BANK(top_shop) = 0;
			SHOP_SORT(top_shop) = 0;
			SHOP_FUNC(top_shop) = NULL;
		}
		else
		{
			if (*buf == '$')		/* EOF */
				done = TRUE;
			else if (strstr(buf, VERSION3_TAG))	/* New format marker */
				new_format = TRUE;
			free(buf);		/* Plug memory leak! */
		}
	}
}


void assign_the_shopkeepers(void)
{
  int index;

  cmd_say = find_command("say");
  cmd_tell = find_command("tell");
  cmd_emote = find_command("emote");
  cmd_slap = find_command("slap");
  cmd_puke = find_command("puke");
  for (index = 0; index <= top_shop; index++) {
    if (SHOP_KEEPER(index) == NOBODY)
      continue;
    if (mob_index[SHOP_KEEPER(index)].func)
      SHOP_FUNC(index) = mob_index[SHOP_KEEPER(index)].func;
    mob_index[SHOP_KEEPER(index)].func = shop_keeper;
  }
}


char *customer_string(int shop_nr, int detailed)
{
  int index, cnt = 1;
  static char buf[256];

  *buf = 0;
  for (index = 0; *trade_letters[index] != '\n'; index++, cnt *= 2)
    if (!(SHOP_TRADE_WITH(shop_nr) & cnt)) {
      if (detailed) {
	if (*buf)
	  strcat(buf, ", ");
	strcat(buf, trade_letters[index]);
      } else
	sprintf(END_OF(buf), "%c", *trade_letters[index]);
    } else if (!detailed)
      strcat(buf, "_");

  return (buf);
}


/* END_OF inefficient */
void list_all_shops(CHAR_DATA *ch)
{
  int shop_nr;

  *buf = '\0';
  for (shop_nr = 0; shop_nr <= top_shop; shop_nr++) {
    if (!(shop_nr % (PAGE_LENGTH - 2))) {
      strcat(buf, " ##   Virtual   Where    Keeper    Buy   Sell   Customers\r\n");
      strcat(buf, "---------------------------------------------------------\r\n");
    }
    sprintf(buf2, "%3d   %6d   %6d    ", shop_nr + 1, SHOP_NUM(shop_nr),
	    SHOP_ROOM(shop_nr, 0)->number);
    if (SHOP_KEEPER(shop_nr) < 0)
      strcpy(buf1, "<NONE>");
    else
      sprintf(buf1, "%6d", mob_index[SHOP_KEEPER(shop_nr)].vnum);
    sprintf(END_OF(buf2), "%s   %3.2f   %3.2f    ", buf1,
	    SHOP_SELLPROFIT(shop_nr), SHOP_BUYPROFIT(shop_nr));
    strcat(buf2, customer_string(shop_nr, FALSE));
    sprintf(END_OF(buf), "%s\r\n", buf2);
  }

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


void handle_detailed_list(char *buf, char *buf1, CHAR_DATA *ch)
{
  if (strlen(buf1) + strlen(buf) < 78 || strlen(buf) < 20)
    strcat(buf, buf1);
  else {
    strcat(buf, "\r\n");
    send_to_char(buf, ch);
    sprintf(buf, "            %s", buf1);
  }
}


void list_detailed_shop(CHAR_DATA *ch, int shop_nr)
{
  OBJ_DATA *obj;
  CHAR_DATA *k;
  int index;
  ROOM_DATA *temp;

  sprintf(buf, "Vnum:       [%5d], Rnum: [%5d]\r\n", SHOP_NUM(shop_nr),
	  shop_nr + 1);
  send_to_char(buf, ch);

  strcpy(buf, "Rooms:      ");
  for (index = 0; SHOP_ROOM(shop_nr, index) != NULL; index++)
  {
    if (index)
      strcat(buf, ", ");
    if ((temp = SHOP_ROOM(shop_nr, index)) != NULL)
      sprintf(buf1, "%s (#%d)", temp->name, temp->number);
    else
      sprintf(buf1, "<UNKNOWN> (#%d)", SHOP_ROOM(shop_nr, index));
    handle_detailed_list(buf, buf1, ch);
  }
  if (!index)
    send_to_char("Rooms:      None!\r\n", ch);
  else {
    strcat(buf, "\r\n");
    send_to_char(buf, ch);
  }

  strcpy(buf, "Shopkeeper: ");
  if (SHOP_KEEPER(shop_nr) >= 0) {
    sprintf(END_OF(buf), "%s (#%d), Special Function: %s\r\n",
	    GET_NAME(&mob_proto[SHOP_KEEPER(shop_nr)]),
	mob_index[SHOP_KEEPER(shop_nr)].vnum, YESNO(SHOP_FUNC(shop_nr)));
    if ((k = get_char_num(SHOP_KEEPER(shop_nr)))) {
      send_to_char(buf, ch);
      sprintf(buf, "Coins:      [%9d], Bank: [%9d] (Total: %d)\r\n",
	 get_gold(k), SHOP_BANK(shop_nr), get_gold(k) + SHOP_BANK(shop_nr));
    }
  } else
    strcat(buf, "<NONE>\r\n");
  send_to_char(buf, ch);

  strcpy(buf1, customer_string(shop_nr, TRUE));
  sprintf(buf, "Customers:  %s\r\n", (*buf1) ? buf1 : "None");
  send_to_char(buf, ch);

  strcpy(buf, "Produces:   ");
  for (index = 0; SHOP_PRODUCT(shop_nr, index) != NOTHING; index++) {
    obj = &obj_proto[SHOP_PRODUCT(shop_nr, index)];
    if (index)
      strcat(buf, ", ");
    sprintf(buf1, "%s (#%d)", obj->short_description,
	    obj_index[SHOP_PRODUCT(shop_nr, index)].vnum);
    handle_detailed_list(buf, buf1, ch);
  }
  if (!index)
    send_to_char("Produces:   Nothing!\r\n", ch);
  else {
    strcat(buf, "\r\n");
    send_to_char(buf, ch);
  }

  strcpy(buf, "Buys:       ");
  for (index = 0; SHOP_BUYTYPE(shop_nr, index) != NOTHING; index++) {
    if (index)
      strcat(buf, ", ");
    sprintf(buf1, "%s (#%d) ", item_types[SHOP_BUYTYPE(shop_nr, index)],
	    SHOP_BUYTYPE(shop_nr, index));
    if (SHOP_BUYWORD(shop_nr, index))
      sprintf(END_OF(buf1), "[%s]", SHOP_BUYWORD(shop_nr, index));
    else
      strcat(buf1, "[all]");
    handle_detailed_list(buf, buf1, ch);
  }
  if (!index)
    send_to_char("Buys:       Nothing!\r\n", ch);
  else {
    strcat(buf, "\r\n");
    send_to_char(buf, ch);
  }

  sprintf(buf, "Buy at:     [%4.2f], Sell at: [%4.2f], Open: [%d-%d, %d-%d]%s",
     SHOP_SELLPROFIT(shop_nr), SHOP_BUYPROFIT(shop_nr), SHOP_OPEN1(shop_nr),
   SHOP_CLOSE1(shop_nr), SHOP_OPEN2(shop_nr), SHOP_CLOSE2(shop_nr), "\r\n");

  send_to_char(buf, ch);

  sprintbit(SHOP_BITVECTOR(shop_nr), shop_bits, buf1);
  sprintf(buf, "Bits:       %s\r\n", buf1);
  send_to_char(buf, ch);
}


void show_shops(CHAR_DATA *ch, char *arg)
{
  int shop_nr;

  if (!*arg)
    list_all_shops(ch);
  else {
    if (!str_cmp(arg, ".")) {
      for (shop_nr = 0; shop_nr <= top_shop; shop_nr++)
	if (ok_shop_room(shop_nr, ch->in_room))
	  break;

      if (shop_nr > top_shop) {
	send_to_char("This isn't a shop!\r\n", ch);
	return;
      }
    } else if (is_number(arg))
      shop_nr = atoi(arg) - 1;
    else
      shop_nr = -1;

    if (shop_nr < 0 || shop_nr > top_shop) {
      send_to_char("Illegal shop number.\r\n", ch);
      return;
    }
    list_detailed_shop(ch, shop_nr);
  }
}