SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 * ------------------------------------------------------------------------ *
 *			 Shop and repair shop module								    *
 ****************************************************************************/

#include	"stdafx.h"
#include	"smaug.h"
#include	"area.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"descriptor.h"
#include	"character.h"


// Local function prototypes
CCharacter	*find_keeper (CCharacter* ch);
CCharacter	*find_fixer (CCharacter* ch);
int		get_cost (CCharacter* ch, CCharacter* keeper, CObjData* obj,
			BOOL fBuy);
int		get_repaircost (CCharacter* keeper, CObjData* obj);
BOOL	CanTrade (CCharacter* ch, CCharacter* keeper);


// Shopping commands.
CCharacter *find_keeper (CCharacter* ch)
{
	CShopData	*pShop = NULL;
	CCharacter	*keeper = ch->GetInRoom ()->first_person;

	for ( ; keeper; keeper = keeper->GetNextInRoom ())
		if (keeper->IsNpc () && (pShop = keeper->GetMobIndex ()->pShop))
			break;

	if (! pShop) {
		ch->SendText ("You can't do that here.\n\r");
		return NULL;
	}

	return keeper;
}


BOOL CanTrade (CCharacter* ch, CCharacter* keeper)
{
	char		buf [MAX_STRING_LENGTH];

	CShopData	*pShop = keeper->GetMobIndex ()->pShop;

	// Undesirables.
	if (! ch->IsNpc () && ch->IsKiller ()) {
		do_say (keeper, "Killers are not welcome!");
		sprintf (buf, "%s the KILLER is over here!\n\r", ch->GetName ());
		do_shout (keeper, buf);
		return FALSE;
	}

	if (! ch->IsNpc () && ch->IsThief ()) {
		do_say (keeper, "Thieves are not welcome!");
		sprintf (buf, "%s the THIEF is over here!\n\r", ch->GetName ());
		do_shout (keeper, buf);
		return FALSE;
	}

	// Shop hours.
	if (time_info.hour < pShop->open_hour) {
		do_say (keeper, "Sorry, come back later.");
		return FALSE;
	}

	if (time_info.hour > pShop->close_hour) {
		do_say (keeper, "Sorry, come back tomorrow.");
		return FALSE;
	}

	// Invisible or hidden people.
	if (! can_see (keeper, ch)) {
		do_say (keeper, "I don't trade with folks I can't see.");
		return FALSE;
	}

	if (! knows_language (keeper, ch->GetSpeaking (), ch)) {
		do_say (keeper, "I can't understand you.");
		return FALSE;
	}

	return TRUE;
}

/*
 * repair commands.
 */
CCharacter *find_fixer (CCharacter *ch)
{
    char buf[MAX_STRING_LENGTH];
    CCharacter *keeper;
    CRepairShopData *rShop;

    rShop = NULL;
    for (keeper = ch->GetInRoom ()->first_person;
	  keeper;
	  keeper = keeper->GetNextInRoom ())
	if (keeper->IsNpc () && (rShop = keeper->GetMobIndex ()->rShop) != NULL)
	    break;

    if (!rShop)
    {
	ch->SendText ("You can't do that here.\n\r");
	return NULL;
    }

    /*
     * Undesirables.
     */
    if (!ch->IsNpc () && ch->IsKiller ())
    {
	do_say (keeper, "Killers are not welcome!");
	sprintf (buf, "%s the KILLER is over here!\n\r", ch->GetName ());
	do_shout (keeper, buf);
	return NULL;
    }

    if (!ch->IsNpc () && ch->IsThief ())
    {
	do_say (keeper, "Thieves are not welcome!");
	sprintf (buf, "%s the THIEF is over here!\n\r", ch->GetName ());
	do_shout (keeper, buf);
	return NULL;
    }

    /*
     * Shop hours.
     */
    if (time_info.hour < rShop->open_hour)
    {
	do_say (keeper, "Sorry, come back later.");
	return NULL;
    }

    if (time_info.hour > rShop->close_hour)
    {
	do_say (keeper, "Sorry, come back tomorrow.");
	return NULL;
    }

    /*
     * Invisible or hidden people.
     */
    if (!can_see (keeper, ch))
    {
	do_say (keeper, "I don't trade with folks I can't see.");
	return NULL;
    }
    
    if (!knows_language (keeper, ch->GetSpeaking (), ch))
    {
	do_say (keeper, "I can't understand you.");
	return NULL;
    }

    return keeper;
}



int get_cost (CCharacter *ch, CCharacter *keeper, CObjData *obj, BOOL fBuy)
{
	CShopData	*pShop;
	int			cost;
	BOOL		richcustomer;
	int			profitmod;

	if (!obj || (pShop = keeper->GetMobIndex ()->pShop) == NULL)
		return 0;

	if (ch->GetGold () > (ch->GetLevel () * ch->GetLevel () * 100000))
		richcustomer = TRUE;
	else
		richcustomer = FALSE;

	if (fBuy) {
		//  this line does nothing, and cost is unitialised! (Rustry)
//		cost = (int) (cost * (80 + UMIN (ch->GetLevel (), LEVEL_AVATAR))) / 100;

		profitmod = 13 - get_curr_cha (ch) + (richcustomer ? 15 : 0)
			+ ((URANGE (5,ch->GetLevel (),LEVEL_AVATAR)-20)/2);
		cost = (int) (obj->cost
			* UMAX ((pShop->profit_sell+1), pShop->profit_buy+profitmod))
			/ 100;
	} else {
		CObjData	*obj2;
		int			itype;

		profitmod = get_curr_cha (ch) - 13 - (richcustomer ? 15 : 0);
		cost = 0;
		for (itype = 0; itype < MAX_TRADE; itype++) {
			if (obj->item_type == pShop->buy_type [itype]) {
				cost = (int) (obj->cost
					* UMIN ((pShop->profit_buy-1),
					pShop->profit_sell + profitmod)) / 100;
				break;
			}
		}

		POSITION	pos = keeper->GetHeadCarryPos ();
		while (obj2 = keeper->GetNextCarrying (pos)) {
			if (obj->GetIndex () == obj2->GetIndex ()) {
				cost = 0;
				break;
			}
		}
	}

	if (obj->item_type == ITEM_STAFF || obj->item_type == ITEM_WAND)
		cost = (int) (cost * obj->value[2] / obj->value[1]);

	return cost;
}


int get_repaircost (CCharacter *keeper, CObjData *obj)
{
    CRepairShopData *rShop;
    int cost;
    int itype;
    BOOL found;

    if (!obj || (rShop = keeper->GetMobIndex ()->rShop) == NULL)
	return 0;

    cost = 0;
    found = FALSE;
    for (itype = 0; itype < MAX_FIX; itype++)
    {
	if (obj->item_type == rShop->fix_type[itype])
	{
	    cost = (int) (obj->cost * rShop->profit_fix / 1000);
	    found = TRUE;
	    break;
	}
    }

    if (!found)
      cost = -1;

    if (cost == 0)
      cost = 1;

    if (found && cost > 0)
    {
      switch (obj->item_type)
      {
	case ITEM_ARMOR:
	  if (obj->value[0] >= obj->value[1])
	    cost = -2;
	  else
	    cost *= (obj->value[1] - obj->value[0]);
	  break;
	case ITEM_WEAPON:
	  if (INIT_WEAPON_CONDITION == obj->value[0])
	    cost = -2;
	  else
	    cost *= (INIT_WEAPON_CONDITION - obj->value[0]);
	  break;
	case ITEM_WAND:
	case ITEM_STAFF:
	  if (obj->value[2] >= obj->value[1])
	    cost = -2;
	  else
	    cost *= (obj->value[1] - obj->value[2]);
      }
    }

    return cost;
}



void do_buy (CCharacter *ch, char *argument)
{
	char	arg [MAX_INPUT_LENGTH];
	int		maxgold;

	argument = one_argument (argument, arg);
	if (arg [0] == '\0') {
		ch->SendText ("Buy what?\n\r");
		return;
	}

	if (ch->GetInRoom ()->IsPetShop ()) {
		char			buf [MAX_STRING_LENGTH];
		CCharacter		*pet;
		CRoomIndexData	*pRoomIndexNext;
		CRoomIndexData	*in_room;

		if (ch->IsNpc ())
			return;

		pRoomIndexNext = RoomTable.GetRoom (ch->GetInRoom ()->vnum + 1);
		if (! pRoomIndexNext) {
			bug ("Do_buy: bad pet shop at vnum %d.", ch->GetInRoom ()->vnum);
			ch->SendText ("Sorry, you can't buy that here.\n\r");
			return;
		}

		in_room = ch->GetInRoom ();
		ch->SetInRoom (pRoomIndexNext);
		pet = get_char_room (ch, arg);
		ch->SetInRoom (in_room);

		if (pet == NULL || ! pet->IsNpc () || ! pet->IsPet ()) {
			ch->SendText ("Sorry, you can't buy that here.\n\r");
			return;
		}

		if (ch->HasPet ()) {
			ch->SendText ("You already have a pet.\r\n");
			if (ch->GetPet ()->GetInRoom () != in_room)
				ch->SendText (
					"Your pet is lost. You had better go find it.\r\n");
			return;
		}

		if (ch->GetGold () < 10 * pet->GetLevel () * pet->GetLevel ()) {
			ch->SendText ("You can't afford it.\n\r");
			return;
		}

		if (ch->GetLevel () < pet->GetLevel ()) {
			ch->SendText ("You're not ready for this pet.\n\r");
			return;
		}

		maxgold = 10 * pet->GetLevel () * pet->GetLevel ();
		ch->AddGold (-maxgold);
		ch->GetInRoom ()->GetArea ()->BoostEconomy (maxgold);
		pet = create_mobile (pet->GetMobIndex ());
		ch->SetBoughtPet ();
		pet->SetPetFlag ();
		pet->SetCharmed ();

		argument = one_argument (argument, arg);
		if (arg [0] != '\0') {
			sprintf (buf, "%s %s", pet->GetName (), arg);
			pet->SetName (buf);
		}

		sprintf (buf, "%sA neck tag says 'I belong to %s'.\n\r",
			pet->GetDescription (), ch->GetName ());
		pet->SetDescription (buf);

		pet->SendToRoom (ch->GetInRoom ());
		add_follower (pet, ch);
		ch->SendText ("Enjoy your pet.\n\r");
		act (AT_ACTION, "$n bought $N as a pet.", ch, NULL, pet, TO_ROOM);
		return;
	} else {
		CObjData	*obj;
		int			cost;
		int			noi = 1;	// Number of items
		short		mnoi = 20;	// Max number of items to be bought at once

		CCharacter	*keeper = find_keeper (ch);
		if (! keeper || ! CanTrade (ch, keeper))
			return;

		maxgold = keeper->GetLevel () * keeper->GetLevel () * 50000;

		if (is_number (arg)) {
			noi = atoi (arg);
			argument = one_argument (argument, arg);
			if (noi > mnoi) {
				act (AT_TELL, "$n tells you 'I don't sell that many items at"
					" once.'", keeper, NULL, ch, TO_VICT);
				ch->SetReplier (keeper);
				return;
			}
		}

		obj  = get_obj_carry (keeper, arg);
		cost = (get_cost (ch, keeper, obj, TRUE) * noi);
		if (cost <= 0 || ! can_see_obj (ch, *obj)) {
			act (AT_TELL, "$n tells you 'I don't sell that -- try 'list'.'",
				keeper, NULL, ch, TO_VICT);
			ch->SetReplier (keeper);
			return;
		}

		if (! obj->IsInventory () && (noi > 1)) {
			interpret (keeper, "laugh");
			act (AT_TELL, "$n tells you 'I don't have enough of those in "
				"stock to sell more than one at a time.'",
				keeper, NULL, ch, TO_VICT);
			ch->SetReplier (keeper);
			return;
		}

		if (ch->GetGold () < cost) {
			act (AT_TELL, "$n tells you 'You can't afford to buy $p.'",
				keeper, obj, ch, TO_VICT);
			ch->SetReplier (keeper);
			return;
		}

		if (obj->level > ch->GetLevel ()) {
			act (AT_TELL, "$n tells you 'You can't use $p yet.'",
				keeper, obj, ch, TO_VICT);
			ch->SetReplier (keeper);
			return;
		}

		if (obj->IsPrototype () && ch->GetTrustLevel () < LEVEL_IMMORTAL) {
			act (AT_TELL, "$n tells you 'This is a only a prototype!  "
				"I can't sell you that...'", keeper, NULL, ch, TO_VICT);
			ch->SetReplier (keeper);
			return;
		}

		if (ch->carry_number + get_obj_number (obj) > ch->GetMaxItems ()) {
			ch->SendText ("You can't carry that many items.\n\r");
			return;
		}

		if (ch->GetCarryWeight () + (get_obj_weight (obj) * noi)
		  + (noi > 1 ? 2 : 0) > can_carry_w (ch)) {
			ch->SendText ("You can't carry that much weight.\n\r");
			return;
		}

		if (noi == 1) {
			act (AT_ACTION, "$n buys $p.", ch, obj, NULL, TO_ROOM);
			act (AT_ACTION, "You buy $p.", ch, obj, NULL, TO_CHAR);
		} else {
			CString	Descr = obj->GetShortDescr ();
			BOOL	bPlural = Descr.GetAt (Descr.GetLength () - 1) == 's';

			sprintf (arg, "$n buys %d $p%s.", noi, bPlural ? "" : "s");
			act (AT_ACTION, arg, ch, obj, NULL, TO_ROOM);

			sprintf (arg, "You buy %d $p%s.", noi, bPlural ? "" : "s");
			act (AT_ACTION, arg, ch, obj, NULL, TO_CHAR);
			act (AT_ACTION, "$N puts them into a bag and hands it to you.",
				ch, NULL, keeper, TO_CHAR);
		}

		ch->AddGold (-cost);
		keeper->AddGold (cost);

		if (keeper->GetGold () > maxgold) {
			keeper->GetInRoom ()->GetArea ()->BoostEconomy (
				keeper->GetGold () - maxgold/2);
			keeper->SetGold (maxgold/2);
			act (AT_ACTION, "$n puts some gold into a large safe.",
				keeper, NULL, NULL, TO_ROOM);
		}

		if (obj->IsInventory ()) {
			CObjData	*buy_obj, *bag;

			buy_obj = create_object (obj->GetIndex (), obj->level);

			// Due to grouped objects and carry limitations in SMAUG
			// The shopkeeper gives you a bag with multiple-buy,
			// and also, only one object needs be created with a count
			// set to the number bought.		-Thoric
			if (noi > 1) {
				bag=create_object (OIdxTable.GetObj (OBJ_VNUM_SHOPPING_BAG),1);
				// perfect size bag ;)
				bag->value [0] = bag->weight + (buy_obj->weight * noi);
				buy_obj->count = noi;
				obj->GetIndex ()->count += (noi - 1);
				obj_to_obj (buy_obj, bag);
				obj_to_char (bag, ch);
			}
			else obj_to_char (buy_obj, ch);
		} else {
			obj_from_char (obj);
			obj_to_char (obj, ch);
		}
		return;
	}
}


// This is a new list function which allows level limits to follow as
// arguments.  This code relies heavily on the items held by the shopkeeper
// being sorted in descending order by level.  obj_to_char in handler.c was
// modified to achieve this.  Anyways, this list command will now throw flags
// at the levels passed as arguments.  This helps pick out equipment which is
// usable by the char, etc.  This was done rather than just producing a list
// of equip at desired level because there would be an inconsistency between
// #.item on one list and #.item on the other.
// Syntax:
//      list            -       list the items for sale, should be sorted by
//                              level
//      list #          -       list items and throw a flag at #
//      list #1 #2      -       list items and throw flags at #1 and #2
// Note that this won't work in pets stores. Since you can't control
// the order in which the pets repop you can't guarantee a sorted list.
// Last Modified: May 25, 1997 -- Fireblade
void do_list (CCharacter* ch, char* argument)
{
	// Constants for producing the flags
	char *divleft = "----------------------------------[ ";
	char *divright = " ]----------------------------------";

	CRoomIndexData	&InRoom = *ch->GetInRoom ();

	if (InRoom.IsPetShop ()) {
		CRoomIndexData *pPetRoom = RoomTable.GetRoom (InRoom.vnum + 1);
		if (! pPetRoom) {
			bug ("Do_list: bad pet shop at vnum %d.", InRoom.vnum);
			ch->SendText ("You can't do that here.\n\r");
			return;
		}

		BOOL	bFound = FALSE;

		CCharacter	*pet = pPetRoom->first_person;
		for ( ; pet; pet = pet->GetNextInRoom ()) {
			if (pet->IsPet () && pet->IsNpc ()) {
				if (! bFound) {
					bFound = TRUE;
					send_to_pager ("Pets for sale:\n\r", ch);
				}
				pager_printf (ch, "[%2d] %8d - %s\n\r", pet->GetLevel (),
					10 * pet->GetLevel () * pet->GetLevel (),
					pet->GetShortDescr ());
			}
		}
		if (! bFound)
			ch->SendText ("Sorry, we're out of pets right now.\n\r");
		return;
	}

	char		arg [MAX_INPUT_LENGTH];
	char		*rest;
	CObjData	*obj;
	int			cost;

	rest = one_argument (argument, arg);

	CCharacter	*keeper = find_keeper (ch);
	if (! keeper || ! CanTrade (ch, keeper))
		return;

	int	lower = -2;
	int	upper = -1;

	// Get the level limits for the flags
	if (is_number (arg)) {
		lower = atoi (arg);
		rest = one_argument (rest, arg);

		if (is_number (arg)) {
			upper = atoi (arg);
			rest = one_argument (rest, arg);
		}
	}

	// Fix the limits if reversed
	if (lower >= upper) {
		int temp = lower;
		lower = upper;
		upper = temp;
	}

	// Loop until you see an object higher level than char
	// Note that this depends on the keeper having a sorted list
	BOOL	bFound = FALSE;
	POSITION	pos = keeper->GetHeadCarryPos ();
	while (obj = keeper->GetNextCarrying (pos)) {
		if (obj->wear_loc == WEAR_NONE
		  && can_see_obj (ch, *obj)
		  && (cost = get_cost (ch, keeper, obj, TRUE)) > 0
		  && (arg [0] == '\0' || nifty_is_name (arg, obj->GetName ()))) {
			if (! bFound) {
				bFound = TRUE;
				send_to_pager ("[Lv Price] Item\n\r", ch);
			}

			if (obj->GetLevel () <= upper) {
				pager_printf (ch, "%s%2d%s\n\r", divleft, upper,
					divright);
				upper = -1;
			}

			if (obj->GetLevel () < lower) {
				pager_printf (ch, "%s%2d%s\n\r", divleft, lower,
					divright);
				lower = -1;
			}

			pager_printf (ch, "[%2d %5d] %s.\n\r",
				obj->GetLevel (), cost, capitalize (obj->GetShortDescr ()));
		}
	}

	if (lower >= 0)
		pager_printf (ch, "%s%2d%s\n\r", divleft, lower, divright);

	if (! bFound) {
		if (arg [0] == '\0')
			ch->SendText ("You can't buy anything here.\n\r");
		else
			ch->SendText ("You can't buy that here.\n\r");
	}
}


void do_sell (CCharacter *ch, char *argument)
{
	char		buf [MAX_STRING_LENGTH];
	char		arg [MAX_INPUT_LENGTH];
	CObjData	*obj;
	int			cost;

	one_argument (argument, arg);

	if (arg [0] == '\0') {
		ch->SendText ("Sell what?\n\r");
		return;
	}

	CCharacter	*keeper = find_keeper (ch);
	if (! keeper || ! CanTrade (ch, keeper))
		return;

	if ((obj = get_obj_carry (ch, arg)) == NULL) {
		act (AT_TELL, "$n tells you 'You don't have that item.'",
		keeper, NULL, ch, TO_VICT);
		ch->SetReplier (keeper);
		return;
	}

	if (! can_drop_obj (ch, obj)) {
		ch->SendText ("You can't let go of it!\n\r");
		return;
	}

	if (obj->timer > 0) {
		act (AT_TELL, "$n tells you, '$p is depreciating in value too quickly...'", keeper, obj, ch, TO_VICT);
		return;
	}

	if ((cost = get_cost (ch, keeper, obj, FALSE)) <= 0) {
		act (AT_ACTION, "$n looks uninterested in $p.", keeper, obj, ch, TO_VICT);
		return;
	}

	if (cost >= keeper->GetGold ()) {
		act (AT_TELL, "$n tells you, '$p is worth more than I can "
			"afford...'", keeper, obj, ch, TO_VICT);
		return;
	}

	separate_obj (obj);
	act (AT_ACTION, "$n sells $p.", ch, obj, NULL, TO_ROOM);
	sprintf (buf, "You sell $p for %d gold piece%s.",
		cost, cost == 1 ? "" : "s");
	act (AT_ACTION, buf, ch, obj, NULL, TO_CHAR);
	ch->AddGold (cost);
	keeper->AddGold (-cost);
	if (keeper->GetGold () < 0)
		keeper->SetGold (0);

	if (obj->item_type == ITEM_TRASH)
		extract_obj (obj);
	else {
		obj_from_char (obj);
		obj_to_char (obj, keeper, ADDBYLEVEL);
	}
}



void do_value (CCharacter *ch, char *argument)
{
	char		buf [MAX_STRING_LENGTH];
	CObjData	*obj;
	int			cost;

	if (argument [0] == '\0') {
		ch->SendText ("Value what?\n\r");
		return;
	}

	CCharacter	*keeper = find_keeper (ch);
	if (! keeper || ! CanTrade (ch, keeper))
		return;

	if ((obj = get_obj_carry (ch, argument)) == NULL) {
		act (AT_TELL, "$n tells you 'You don't have that item.'",
		keeper, NULL, ch, TO_VICT);
		ch->SetReplier (keeper);
		return;
	}

	if (! can_drop_obj (ch, obj)) {
		ch->SendText ("You can't let go of it!\n\r");
		return;
	}

	if ((cost = get_cost (ch, keeper, obj, FALSE)) <= 0) {
		act (AT_ACTION, "$n looks uninterested in $p.", keeper, obj, ch, TO_VICT);
		return;
	}

	sprintf (buf, "$n tells you 'I'll give you %d gold coins for $p.'", cost);
	act (AT_TELL, buf, keeper, obj, ch, TO_VICT);
	ch->SetReplier (keeper);
}


/*
 * Repair a single object. Used when handling "repair all" - Gorog
 */
void repair_one_obj (CCharacter *ch, CCharacter *keeper, CObjData *obj,
                 char *arg, int maxgold, char *fixstr, char*fixstr2)
{
   char buf[MAX_STRING_LENGTH];
   int cost;

   if (!can_drop_obj (ch, obj))
       ch->SendTextf ("You can't let go of %s.\n\r", obj->GetName ());
   else if ((cost = get_repaircost (keeper, obj)) < 0)
   {
       if (cost != -2)
       act (AT_TELL, "$n tells you, 'Sorry, I can't do anything with $p.'", 
            keeper, obj, ch, TO_VICT);
       else
	  act (AT_TELL, "$n tells you, '$p looks fine to me!'", keeper, obj, ch, TO_VICT);
   }
               /* "repair all" gets a 10% surcharge - Gorog */

   else if ((cost = strcmp ("all",arg) ? cost : 11*cost/10) > ch->GetGold ())
   {
      sprintf (buf,
       "$N tells you, 'It will cost %d piece%s of gold to %s %s...'", cost,
        cost == 1 ? "" : "s", fixstr, obj->GetName ());
      act (AT_TELL, buf, ch, NULL, keeper, TO_CHAR);
      act (AT_TELL, "$N tells you, 'Which I see you can't afford.'", ch,
              NULL, keeper, TO_CHAR);
   }
   else
   {
      sprintf (buf, "$n gives $p to $N, who quickly %s it.", fixstr2);
      act (AT_ACTION, buf, ch, obj, keeper, TO_ROOM);
      sprintf (buf, "$N charges you %d gold piece%s to %s $p.",
          cost, cost == 1 ? "" : "s", fixstr);
      act (AT_ACTION, buf, ch, obj, keeper, TO_CHAR);
      ch->AddGold (-cost);
      keeper->AddGold (cost);
      if (keeper->GetGold () < 0)
          keeper->SetGold (0);
      else
      if (keeper->GetGold () > maxgold) {
          keeper->GetInRoom ()->GetArea ()->BoostEconomy (
			keeper->GetGold () - maxgold/2);
          keeper->SetGold (maxgold/2);
          act (AT_ACTION, "$n puts some gold into a large safe.", keeper, 
		NULL, NULL, TO_ROOM);
      }

      switch (obj->item_type)
      {
          default:
            ch->SendText ("For some reason, you think you got ripped off...\n\r");
            break;
          case ITEM_ARMOR:
            obj->value[0] = obj->value[1];
            break;
          case ITEM_WEAPON:
            obj->value[0] = INIT_WEAPON_CONDITION;
            break;
          case ITEM_WAND:
          case ITEM_STAFF:
            obj->value[2] = obj->value[1];
            break;
      }

      oprog_repair_trigger (ch, obj);
   }
}

void do_repair (CCharacter *ch, char *argument)
{
    CCharacter *keeper;
    CObjData *obj;
    char *fixstr;
    char *fixstr2;
    int maxgold;

    if (argument[0] == '\0')
    {
	ch->SendText ("Repair what?\n\r");
	return;
    }

    if ((keeper = find_fixer (ch)) == NULL)
	return;

    maxgold = keeper->GetLevel () * keeper->GetLevel () * 100000;
    switch (keeper->GetMobIndex ()->rShop->shop_type)
    {
	default:
	case SHOP_FIX:
	  fixstr  = "repair";
	  fixstr2 = "repairs";
	  break;
	case SHOP_RECHARGE:
	  fixstr  = "recharge";
	  fixstr2 = "recharges";
	  break;
    }

	if (! strcmp (argument, "all")) {
		POSITION	pos = ch->GetHeadCarryPos ();
		while (obj = ch->GetNextCarrying (pos)) {
			if (obj->wear_loc  == WEAR_NONE
			  && can_see_obj (ch, *obj)
			  && (obj->item_type == ITEM_ARMOR
			  || obj->item_type == ITEM_WEAPON
			  || obj->item_type == ITEM_WAND
			  || obj->item_type == ITEM_STAFF))
				repair_one_obj (ch, keeper, obj, argument, maxgold,
					fixstr, fixstr2);
		}
		return;
	}

    if ((obj = get_obj_carry (ch, argument)) == NULL)
    {
	act (AT_TELL, "$n tells you 'You don't have that item.'",
		keeper, NULL, ch, TO_VICT);
	ch->SetReplier (keeper);
	return;
    }

    repair_one_obj (ch, keeper, obj, argument, maxgold, fixstr, fixstr2);
}


void appraise_all (CCharacter *ch, CCharacter *keeper, char *fixstr)
{
	char		buf [MAX_STRING_LENGTH];
	CObjData	*obj;
	int			cost, total=0;

	POSITION	pos = ch->GetHeadCarryPos ();
	while (obj = ch->GetNextCarrying (pos)) {
		if (obj->wear_loc  == WEAR_NONE
		  && can_see_obj (ch, *obj)
		  && (obj->item_type == ITEM_ARMOR
		  || obj->item_type == ITEM_WEAPON
		  || obj->item_type == ITEM_WAND
		  || obj->item_type == ITEM_STAFF))

			if (! can_drop_obj (ch, obj))
				ch->SendTextf ("You can't let go of %s.\n\r", obj->GetName ());
			else if ((cost = get_repaircost (keeper, obj)) < 0) {
				if (cost != -2)
					act (AT_TELL,
						"$n tells you, 'Sorry, I can't do anything with $p.'",
						keeper, obj, ch, TO_VICT);
				else
					act (AT_TELL, "$n tells you, '$p looks fine to me!'",
						keeper, obj, ch, TO_VICT);
			}
		else {
			sprintf (buf,
				"$N tells you, 'It will cost %d piece%s of gold to %s %s'",
				cost, cost == 1 ? "" : "s", fixstr, obj->GetName ());
			act (AT_TELL, buf, ch, NULL, keeper, TO_CHAR);
			total += cost;
		}
	}

	if (total > 0) {
		ch->SendText ("\n\r");
		sprintf (buf,
			"$N tells you, 'It will cost %d piece%s of gold in total.'",
			total, cost == 1 ? "" : "s");
		act (AT_TELL, buf, ch, NULL, keeper, TO_CHAR);
		strcpy (buf,
			"$N tells you, 'Remember there is a 10% surcharge for repair all.'");
		act (AT_TELL, buf, ch, NULL, keeper, TO_CHAR);
	}
}
	

void do_appraise (CCharacter *ch, char *argument)
{
    char buf[MAX_STRING_LENGTH];
    char arg[MAX_INPUT_LENGTH];
    CCharacter *keeper;
    CObjData *obj;
    int cost;
    char *fixstr;

    one_argument (argument, arg);

    if (arg[0] == '\0')
    {
	ch->SendText ("Appraise what?\n\r");
	return;
    }

    if ((keeper = find_fixer (ch)) == NULL)
	return;

    switch (keeper->GetMobIndex ()->rShop->shop_type)
    {
	default:
	case SHOP_FIX:
	  fixstr  = "repair";
	  break;
	case SHOP_RECHARGE:
	  fixstr  = "recharge";
	  break;
    }

    if (!strcmp (arg, "all"))
    {
    appraise_all (ch, keeper, fixstr);
    return;
    }

    if ((obj = get_obj_carry (ch, arg)) == NULL)
    {
	act (AT_TELL, "$n tells you 'You don't have that item.'",
		keeper, NULL, ch, TO_VICT);
	ch->SetReplier (keeper);
	return;
    }

    if (!can_drop_obj (ch, obj))
    {
	ch->SendText ("You can't let go of it.\n\r");
	return;
    }

    if ((cost = get_repaircost (keeper, obj)) < 0)
    {
      if (cost != -2)
	act (AT_TELL, "$n tells you, 'Sorry, I can't do anything with $p.'", keeper, obj, ch, TO_VICT);
      else
	act (AT_TELL, "$n tells you, '$p looks fine to me!'", keeper, obj, ch, TO_VICT);
      return;
    }

    sprintf (buf,
       "$N tells you, 'It will cost %d piece%s of gold to %s that...'", cost,
       cost == 1 ? "" : "s", fixstr);
    act (AT_TELL, buf, ch, NULL, keeper, TO_CHAR);
    if (cost > ch->GetGold ())
      act (AT_TELL, "$N tells you, 'Which I see you can't afford.'", ch,
	 NULL, keeper, TO_CHAR);

    return;
}


/* ------------------ Shop Building and Editing Section ----------------- */


void do_makeshop (CCharacter *ch, char *argument)
{
    CShopData		*shop;
    int				vnum;
    CMobIndexData	*mob;

    if (!argument || argument[0] == '\0')
    {
	ch->SendText ("Usage: makeshop <mobvnum>\n\r");
	return;
    }

    vnum = atoi (argument);
    
    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!can_medit (ch, mob))
      return;

    if (mob->pShop)
    {
	ch->SendText ("This mobile already has a shop.\n\r");
	return;
    }

    shop = new CShopData;

    LINK (shop, first_shop, last_shop);
    shop->keeper	= vnum;
    shop->profit_buy	= 120;
    shop->profit_sell	= 90;
    shop->open_hour	= 0;
    shop->close_hour	= 23;
    mob->pShop		= shop;
    ch->SendText ("Done.\n\r");
    return;
}


void do_shopset (CCharacter *ch, char *argument)
{
    CShopData		*shop;
    CMobIndexData	*mob, *mob2;
    char			arg1 [MAX_INPUT_LENGTH];
    char			arg2 [MAX_INPUT_LENGTH];
    int				vnum;
    int				value;
    
    argument = one_argument (argument, arg1);
    argument = one_argument (argument, arg2);

    if (arg1[0] == '\0' || arg2[0] == '\0')
    {
	ch->SendText ("Usage: shopset <mob vnum> <field> value\n\r");
	ch->SendText ("\n\rField being one of:\n\r");
	ch->SendText ("  buy0 buy1 buy2 buy3 buy4 buy sell open close keeper\n\r");
	return;
    }

    vnum = atoi (arg1);

    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!can_medit (ch, mob))
      return;

    if (!mob->pShop)
    {
	ch->SendText ("This mobile doesn't keep a shop.\n\r");
	return;
    }
    shop = mob->pShop;
    value = atoi (argument);

    if (!str_cmp (arg2, "buy0"))
    {
	if (!is_number (argument))
	    value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	shop->buy_type[0] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "buy1"))
    {
	if (!is_number (argument))
	    value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	shop->buy_type[1] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "buy2"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	shop->buy_type[2] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "buy3"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	shop->buy_type[3] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "buy4"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	shop->buy_type[4] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "buy"))
    {
	if (value <= (shop->profit_sell+5) || value > 1000)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	shop->profit_buy = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "sell"))
    {
	if (value < 0 || value >= (shop->profit_buy-5))
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	shop->profit_sell = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "open"))
    {
	if (value < 0 || value > 23)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	shop->open_hour = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "close"))
    {
	if (value < 0 || value > 23)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	shop->close_hour = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "keeper"))
    {
	if ((mob2 = MobTable.GetMob (vnum)) == NULL)
	{
	    ch->SendText ("Mobile not found.\n\r");
	    return;
	}
	if (!can_medit (ch, mob))
	    return;
	if (mob2->pShop)
	{
	    ch->SendText ("That mobile already has a shop.\n\r");
	    return;
	}
	mob->pShop  = NULL;
	mob2->pShop = shop;
	shop->keeper = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    do_shopset (ch, "");
    return;
}


void do_shopstat (CCharacter *ch, char *argument)
{
    CShopData		*shop;
    CMobIndexData	*mob;
    int				vnum;
    
    if (argument[0] == '\0')
    {
	ch->SendText ("Usage: shopstat <keeper vnum>\n\r");
	return;
    }

    vnum = atoi (argument);
    
    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!mob->pShop)
    {
	ch->SendText ("This mobile doesn't keep a shop.\n\r");
	return;
    }
    shop = mob->pShop;

    ch->SendTextf ("Keeper: %d  %s\n\r", shop->keeper, mob->GetShortDescr ());
    ch->SendTextf ("buy0 [%s]  buy1 [%s]  buy2 [%s]  buy3 [%s]  buy4 [%s]\n\r",
		o_types[shop->buy_type[0]],
		o_types[shop->buy_type[1]],
		o_types[shop->buy_type[2]],
		o_types[shop->buy_type[3]],
		o_types[shop->buy_type[4]]);
    ch->SendTextf ("Profit:  buy %3d%%  sell %3d%%\n\r",
			shop->profit_buy,
			shop->profit_sell);
    ch->SendTextf ("Hours:   open %2d  close %2d\n\r",
			shop->open_hour,
			shop->close_hour);
    return;
}


void do_shops (CCharacter *ch, char *argument)
{
    CShopData *shop;

    if (!first_shop)
    {
	ch->SendText ("There are no shops.\n\r");
	return;
    }

    set_char_color (AT_NOTE, ch);
    for (shop = first_shop; shop; shop = shop->GetNext ())
	ch->SendTextf ("Keeper: %5d Buy: %3d Sell: %3d Open: %2d Close: %2d Buy: %2d %2d %2d %2d %2d\n\r",
		shop->keeper,	   shop->profit_buy, shop->profit_sell,
		shop->open_hour,   shop->close_hour,
		shop->buy_type[0], shop->buy_type[1],
		shop->buy_type[2], shop->buy_type[3], shop->buy_type[4]);
    return;
}


/* -------------- Repair Shop Building and Editing Section -------------- */


void do_makerepair (CCharacter *ch, char *argument)
{
    CRepairShopData	*repair;
    int				vnum;
    CMobIndexData	*mob;

    if (!argument || argument[0] == '\0')
    {
	ch->SendText ("Usage: makerepair <mobvnum>\n\r");
	return;
    }

    vnum = atoi (argument);
    
    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!can_medit (ch, mob))
      return;

    if (mob->rShop)
    {
	ch->SendText ("This mobile already has a repair shop.\n\r");
	return;
    }

    repair = new CRepairShopData;

    LINK (repair, first_repair, last_repair);
    repair->keeper	= vnum;
    repair->profit_fix	= 100;
    repair->shop_type	= SHOP_FIX;
    repair->open_hour	= 0;
    repair->close_hour	= 23;
    mob->rShop		= repair;
    ch->SendText ("Done.\n\r");
    return;
}


void do_repairset (CCharacter *ch, char *argument)
{
    CRepairShopData	*repair;
    CMobIndexData	*mob, *mob2;
    char			arg1 [MAX_INPUT_LENGTH];
    char			arg2 [MAX_INPUT_LENGTH];
    int				vnum;
    int				value;
    
    argument = one_argument (argument, arg1);
    argument = one_argument (argument, arg2);

    if (arg1[0] == '\0' || arg2[0] == '\0')
    {
	ch->SendText ("Usage: repairset <mob vnum> <field> value\n\r");
	ch->SendText ("\n\rField being one of:\n\r");
	ch->SendText ("  fix0 fix1 fix2 profit type open close keeper\n\r");
	return;
    }

    vnum = atoi (arg1);

    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!can_medit (ch, mob))
      return;

    if (!mob->rShop)
    {
	ch->SendText ("This mobile doesn't keep a repair shop.\n\r");
	return;
    }
    repair = mob->rShop;
    value = atoi (argument);

    if (!str_cmp (arg2, "fix0"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	repair->fix_type[0] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "fix1"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	repair->fix_type[1] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "fix2"))
    {
	if (!is_number (argument))
	  value = get_otype (argument);
	if (value < 0 || value > MAX_ITEM_TYPE)
	{
	    ch->SendText ("Invalid item type!\n\r");
	    return;
	}
	repair->fix_type[2] = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "profit"))
    {
	if (value < 1 || value > 1000)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	repair->profit_fix = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "type"))
    {
	if (value < 1 || value > 2)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	repair->shop_type = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "open"))
    {
	if (value < 0 || value > 23)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	repair->open_hour = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "close"))
    {
	if (value < 0 || value > 23)
	{
	    ch->SendText ("Out of range.\n\r");
	    return;
	}
	repair->close_hour = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    if (!str_cmp (arg2, "keeper"))
    {
	if ((mob2 = MobTable.GetMob (vnum)) == NULL)
	{
	  ch->SendText ("Mobile not found.\n\r");
	  return;
	}
	if (!can_medit (ch, mob))
	  return;
	if (mob2->rShop)
	{
	  ch->SendText ("That mobile already has a repair shop.\n\r");
	  return;
	}
	mob->rShop  = NULL;
	mob2->rShop = repair;
	repair->keeper = value;
	ch->SendText ("Done.\n\r");
	return;
    }

    do_repairset (ch, "");
    return;
}


void do_repairstat (CCharacter *ch, char *argument)
{
    CRepairShopData	*repair;
    CMobIndexData	*mob;
    int				vnum;
    
    if (argument[0] == '\0')
    {
	ch->SendText ("Usage: repairstat <keeper vnum>\n\r");
	return;
    }

    vnum = atoi (argument);
    
    if ((mob = MobTable.GetMob (vnum)) == NULL)
    {
	ch->SendText ("Mobile not found.\n\r");
	return;
    }

    if (!mob->rShop)
    {
	ch->SendText ("This mobile doesn't keep a repair shop.\n\r");
	return;
    }
    repair = mob->rShop;

    ch->SendTextf ("Keeper: %d  %s\n\r", repair->keeper, mob->GetShortDescr ());
    ch->SendTextf ("fix0 [%s]  fix1 [%s]  fix2 [%s]\n\r",
			o_types[repair->fix_type[0]],
			o_types[repair->fix_type[1]],
			o_types[repair->fix_type[2]]);
    ch->SendTextf ("Profit: %3d%%  Type: %d\n\r",
			repair->profit_fix,
			repair->shop_type);
    ch->SendTextf ("Hours:   open %2d  close %2d\n\r",
			repair->open_hour,
			repair->close_hour);
    return;
}


void do_repairshops (CCharacter *ch, char *argument)
{
    CRepairShopData *repair;

    if (!first_repair)
    {
	ch->SendText ("There are no repair shops.\n\r");
	return;
    }

    set_char_color (AT_NOTE, ch);
    for (repair = first_repair; repair; repair = repair->GetNext ())
	ch->SendTextf ("Keeper: %5d Profit: %3d Type: %d Open: %2d Close: %2d Fix: %2d %2d %2d\n\r",
		repair->keeper,	     repair->profit_fix, repair->shop_type,
		repair->open_hour,   repair->close_hour,
		repair->fix_type[0], repair->fix_type[1], repair->fix_type[2]);
    return;
}