Various Buy BUG Fixes v1 for EmberMUD
                         Fix Sheet by Rindar (Ron Cole)
                           Fixes by Raven and Rindar

The Bug:  There are a number of bugs in the BUY command.  Some of 
these bugs simple give unappropriate advancement in certain skills,
while others can crash the game.  This fix tries to repair them 
all, though I seriously doubt that the buy command is now perfect.

The Check:  First, attempt to purchase an obscene number of items.  
We're talking huge numbers like 10000000000000000000000000000000000 
and such.  If this creates large slowdowns in the game or crashes 
it, you have the bug.
     
     Next, get a character with the haggling skill to buy a 
large number of items for which they do not have enough gold to 
pay for.  Have them do this multiple times and see whether or not
their haggling skill increases.  If it does, you have the bug.


The Bugfix:  This fix is slightly more invasive than the others 
so far, so you might just want to pop in the new buy snippet that
is at the bottom of this file.  If you've made changes and would
rather put the fix in manually, here's how:

        1)  Open act_obj.c
        2)  Find the function "void do_buy"
        3)  Find this line at the beginning of the function: 

             int cost,roll;

        4)  Below it, add the following boolean variable:     
            
            bool fHaggle = FALSE;
        
        5)  Find the following line:
         
    if ( ( keeper = find_keeper( ch ) ) == NULL ) /* is there a shopkeeper here? */
       return;
         
        6) Cut it out and place it in the object purchase code section 
        after the argument splits.  The cut lines should be placed 
        after the following lines:
    
    argument = one_argument (argument, arg); /* get first argument */
    argument = one_argument (argument, arg2); /* get another argument, if any */

        
        7)  Add the following line of code.  The lines before and 
        after have been edited out with /*'s.

         /*
         if (!is_number(arg))
         {
             send_to_char ("Syntax for BUY is: BUY [number] <item>\n\r\"number\" is an optional number of items to buy.\n\r",ch);
             return;
         }
         */

         if (strlen( arg ) > 4)
         {
            act( "$n tells you 'You're joking! I don't have that much in stock!'.",keeper, NULL, ch, TO_VICT );
            ch->reply = keeper;
            return;
         }

         /*        
         item_count = atoi (arg); /* first argument is the optional count */
         */
        
        8)  Go to the /* Haggle */ section of the function.  
          Replace:
            send_to_char(buf,ch);
            check_improve (ch, gsn_haggle,TRUE,4);
          With:
            send_to_char(buf,ch);
            fHaggle = TRUE;
          
        9)  Add the following lines of code after the character 
        and the merchant have exchanged gold.  The lines before the 
        added function have been sectioned of with /* */.
         /*
         ch->gold     -= cost*item_count;
         keeper->gold += cost*item_count;
         */

         /* haggle */
         if ( fHaggle )
              check_improve(ch,gsn_haggle,TRUE,4);
       
       10) Save and recompile.  You should be done.


All done!  If you find other bugs in the buy function, or would
have some other improvements, please feel free to send them along
to me.  My e-mail address is below.

-= Rindar
clogar@concentric.net


** Note:  This function is provided "as is" and may be used so long as 
1)  The author's name is kept at the top of the function (if requested) and
2)  all other previous licensing aggreements are abided by.  The author 
assumes no responsibility for problems that occur through use or install-
ation, including lost wages, bugs, deletions, downtimes, etc...  Use at 
your own risk.  All new code is copyrighted by its author.


New Buy Code (in act_obj.c):


void do_buy( CHAR_DATA *ch, char *argument )
{
   char arg[MAX_INPUT_LENGTH];
   char buf[MAX_STRING_LENGTH];
   int cost,roll;
   bool fHaggle = FALSE;

    if ( argument[0] == '\0' )
    {
        send_to_char( "Buy what?\n\r", ch );
        return;
    }

    if ( IS_SET(ch->in_room->room_flags, ROOM_PET_SHOP) )
    {
        CHAR_DATA *pet;
        ROOM_INDEX_DATA *pRoomIndexNext;
        ROOM_INDEX_DATA *in_room;

        if ( IS_NPC(ch) )
            return;

        argument = one_argument(argument,arg);

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

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

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

        if ( ch->pet != NULL )
        {
            send_to_char("You already own a pet.\n\r",ch);
            return;
        }

        cost = 10 * pet->level * pet->level;

        if ( ch->gold < cost )
        {
            send_to_char( "You can't afford it.\n\r", ch );
            return;
        }

        if ( ch->level < pet->level )
        {
            send_to_char( "You're not powerful enough to master this pet.\n\r", ch );
            return;
        }

        /* haggle */
        roll = number_percent();
        if (!IS_NPC(ch) && roll < ch->pcdata->learned[gsn_haggle])
        {
            cost -= cost / 2 * roll / 100;
            sprintf(buf,"You haggle the price down to %d coins.\n\r",cost);
            send_to_char(buf,ch);
            check_improve(ch,gsn_haggle,TRUE,4);
        
        }

        ch->gold                -= cost;
        pet                     = create_mobile( pet->pIndexData );
        SET_BIT(ch->act, PLR_BOUGHT_PET);
        SET_BIT(pet->act, ACT_PET);
        SET_BIT(pet->affected_by, AFF_CHARM);
        pet->comm = COMM_NOTELL|COMM_NOSHOUT|COMM_NOCHANNELS;

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

        sprintf( buf, "%sA neck tag says 'I belong to %s'.\n\r",
            pet->description, ch->name );
        free_string( pet->description );
        pet->description = str_dup( buf );

        char_to_room( pet, ch->in_room );
        add_follower( pet, ch );
        pet->leader = ch;
        ch->pet = pet;
        send_to_char( "Enjoy your pet.\n\r", ch );
        act( "$n bought $N as a pet.", ch, NULL, pet, TO_ROOM );
        return;
    }
/* Multiple object buy routine

  This code was written by Erwin Andreasen, 4u2@aarhues.dk
  Please mail me if you decide to use this code, or if you have any
  additions, bug reports, ideas or just comments.
  Please do not remove my name from the files.

*/
    else /* object purchase code begins HERE */
    {
        CHAR_DATA *keeper;
        OBJ_DATA *obj;
        int cost;
        char arg2[MAX_INPUT_LENGTH]; /* 2nd argument */
        int item_count = 1;          /* default: buy only 1 item */

    argument = one_argument (argument, arg); /* get first argument */
    argument = one_argument (argument, arg2); /* get another argument, if any */

    if ( ( keeper = find_keeper( ch ) ) == NULL ) /* is there a shopkeeper here? */
            return;

    if (arg2[0]) /* more than one argument specified? then arg2[0] <> 0 */
    {
        /* check if first of the 2 args really IS a number */

        if (!is_number(arg))
        {
            send_to_char ("Syntax for BUY is: BUY [number] <item>\n\r\"number\" is an optional number of items to buy.\n\r",ch);
            return;
        }

        if (strlen( arg ) > 4)
        {
           act( "$n tells you 'You're joking! I don't have that much in stock!'.",keeper, NULL, ch, TO_VICT );
           ch->reply = keeper;
           return;
        }
           
        item_count = atoi (arg); /* first argument is the optional count */
        strcpy (arg,arg2);       /* copy the item name to its right spot */
    }

/* find the pointer to the object */
        obj  = get_obj_carry( keeper, arg );
        cost = get_cost( keeper, obj, TRUE );

    if ( cost <= 0 || !can_see_obj( ch, obj ) ) /* cant buy what you cant see */
        {
            act( "$n tells you 'I don't sell that -- try 'list''.",keeper, NULL, ch, TO_VICT );
            ch->reply = keeper;
            return;
        }

/* check for valid positive numeric value entered */
    if (!(item_count > 0))
    {
            send_to_char ("Buy how many? Number must be positive!\n\r",ch);
            return;
    }
    
                /* haggle */
        roll = number_percent();
        if (!IS_NPC(ch) && roll < ch->pcdata->learned[gsn_haggle])
        {
            cost -= obj->cost / 2 * roll / 100;
            sprintf(buf,"You haggle the price down to %d coins.\n\r",cost);
            send_to_char(buf,ch);
            fHaggle = TRUE;
        }


/* can the character afford it ? */
    if ( ch->gold < (cost * item_count) )
    {
        if (item_count == 1) /* he only wanted to buy one */
        {
            act( "$n tells you 'You can't afford to buy $p'.",keeper, obj, ch, TO_VICT );
        }
        else
        {
            if ( (ch->gold / cost) > 0) /* how many CAN he afford? */
                sprintf (buf, "$n tells you 'You can only afford %ld of those!", (ch->gold / cost));
            else /* not even a single one! what a bum! */
                sprintf (buf, "$n tells you '%s? You must be kidding - you can't even afford a single one, let alone %d!'",capitalize(obj->short_descr), item_count);

            act(buf,keeper, obj, ch, TO_VICT );
            ch->reply = keeper; /* like the character really would reply to the shopkeeper... */
            return;
        }

        ch->reply = keeper; /* like the character really would reply to the shopkeeper... */
        return;
    }

/* Can the character use the item at all ? */
    if ( obj->level > ch->level )
    {
        act( "$n tells you 'You can't use $p yet'.",
            keeper, obj, ch, TO_VICT );
        ch->reply = keeper;
        return;
    }
/* can the character carry more items? */
    if ( ch->carry_number + (get_obj_number(obj)*item_count) > can_carry_n( ch ) )
        {
            send_to_char( "You can't carry that many items.\n\r", ch );
            return;
        }

/* can the character carry more weight? */
    if ( ch->carry_weight + item_count*get_obj_weight(obj) > can_carry_w( ch ) )
        {
            send_to_char( "You can't carry that much weight.\n\r", ch );
            return;
        }

/* check for objects sold to the keeper */
    if ( (item_count > 1) && !IS_SET (obj->extra_flags,ITEM_INVENTORY))
    {
        act( "$n tells you 'Sorry - $p is something I have only one of'.",keeper, obj, ch, TO_VICT );
        ch->reply = keeper;
        return;
    }
    


/* change this to reflect multiple items bought */
    if (item_count == 1)
    {
        act( "$n buys $p.", ch, obj, NULL, TO_ROOM );
        act( "You buy $p.", ch, obj, NULL, TO_CHAR );
    }
    else /* inform of multiple item purchase */
    {
/* "buys 5 * a piece of bread" seems to be the easiest and least gramatically incorerct solution. */
        sprintf (buf, "$n buys %d * $p.", item_count);
        act (buf, ch, obj, NULL, TO_ROOM); /* to char self */
        sprintf (buf, "You buy %d * $p.", item_count);
        act(buf, ch, obj, NULL, TO_CHAR ); /* to others */
    }

    ch->gold     -= cost*item_count;
    keeper->gold += cost*item_count;

        /* haggle */
        if ( fHaggle )
            check_improve(ch,gsn_haggle,TRUE,4);


    if ( IS_SET( obj->extra_flags, ITEM_INVENTORY ) ) /* 'permanent' item */
    {
        /* item_count of items */
        for ( ; item_count > 0; item_count--) /* create item_count objects */
        {
            obj = create_object( obj->pIndexData, obj->level );
            obj_to_char( obj, ch );
        }
    }
    else /* single item */
    {
        obj_from_char( obj );
        obj_to_char( obj, ch );
    }
    return;
    } /* else */
} /* do_buy */