dotd-2.3.7/area/
dotd-2.3.7/clans/
dotd-2.3.7/classes/
dotd-2.3.7/councils/
dotd-2.3.7/deity/
dotd-2.3.7/dict/
dotd-2.3.7/doc/mudprogs/
dotd-2.3.7/player/a/
dotd-2.3.7/player/g/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |   \\._.//   *
 * -----------------------------------------------------------|   (0...0)   *
 * SMAUG 1.0 (C) 1994, 1995, 1996 by Derek Snider             |    ).:.(    *
 * -----------------------------------------------------------|    {o o}    *
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |   / ' ' \   *
 * Scryn, Rennard, Swordbearer, Gorog, Grishnakh and Tricops  |~'~.VxvxV.~'~*
 * ------------------------------------------------------------------------ *
 *			 Tracking/hunting module			    *
 ****************************************************************************/

/*static char rcsid[] = "$Id: track.c,v 1.14 2004/04/06 22:00:11 dotd Exp $";*/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "mud.h"
#include "gsn.h"

DECLARE_DO_FUN(do_say);
DECLARE_DO_FUN(do_gossip);

#define BFS_ERROR	   -1
#define BFS_ALREADY_THERE  -2
#define BFS_NO_PATH	   -3
#define BFS_MARK    536870912

#define TRACK_THROUGH_DOORS

extern int	top_room;

/* You can define or not define TRACK_THOUGH_DOORS, above, depending on
 whether or not you want track to find paths which lead through closed
 or hidden doors.
 */

typedef struct bfs_queue_struct BFS_DATA;
struct bfs_queue_struct
{
    ROOM_INDEX_DATA *	room;
    char   dir;
    BFS_DATA *		next;
};

static BFS_DATA	*queue_head = NULL,*queue_tail = NULL,*room_queue = NULL;

/* Utility macros */
#define MARK(room)	(SET_BIT(	(room)->room_flags, BFS_MARK) )
#define UNMARK(room)	(REMOVE_BIT(	(room)->room_flags, BFS_MARK) )
#define IS_MARKED(room)	(IS_SET(	(room)->room_flags, BFS_MARK) )

bool valid_edge( EXIT_DATA *pexit )
{
    if ( pexit->to_room
#ifndef TRACK_THROUGH_DOORS
         &&  !IS_SET(pexit->exit_info, EX_CLOSED)
#endif
         &&  !IS_MARKED(pexit->to_room) )
        return TRUE;
    else
        return FALSE;
}

void bfs_enqueue(ROOM_INDEX_DATA *room, char dir)
{
    BFS_DATA *curr;
    
    curr = (BFS_DATA *)calloc( 1, sizeof(BFS_DATA) );
    curr->room = room;
    curr->dir = dir;
    curr->next = NULL;
    
    if ( queue_tail )
    {
        queue_tail->next = curr;
        queue_tail = curr;
    }
    else
        queue_head = queue_tail = curr;
}


void bfs_dequeue(void)
{
    BFS_DATA *curr;
    
    curr = queue_head;
    
    if ( !(queue_head = queue_head->next) )
        queue_tail = NULL;
    free(curr);
}


void bfs_clear_queue(void) 
{
    while (queue_head)
        bfs_dequeue();
}

void room_enqueue(ROOM_INDEX_DATA *room)
{
    BFS_DATA *curr;
    
    curr = (BFS_DATA *)calloc( 1, sizeof(BFS_DATA) );
    curr->room = room;
    curr->next = room_queue;
    
    room_queue = curr;
}

void clean_room_queue(void) 
{
    BFS_DATA *curr, *curr_next;
    
    for (curr = room_queue; curr; curr = curr_next )
    {
        UNMARK(curr->room);
        curr_next = curr->next;
        free(curr);
    }
    room_queue = NULL;
}


int find_first_step(ROOM_INDEX_DATA *src, ROOM_INDEX_DATA *target, int maxdist )
{
    int curr_dir, count;
    EXIT_DATA *pexit;
    
    if ( !src || !target )
    {
        bug("Illegal value passed to find_first_step (track.c)" );
        return BFS_ERROR;
    }
    
    if (src == target)
        return BFS_ALREADY_THERE;
    
/*    if ( src->area != target->area ) {
        return BFS_NO_PATH;
    }*/
    
    room_enqueue( src );
    MARK(src);
    
    /* first, enqueue the first steps, saving which direction we're going. */
    for ( pexit = src->first_exit; pexit; pexit = pexit->next )
        if (valid_edge(pexit))
        {
            curr_dir = pexit->vdir;
            MARK(pexit->to_room);
            room_enqueue(pexit->to_room);
            bfs_enqueue(pexit->to_room, curr_dir);
        }
    
    count = 0;
    while (queue_head)
    {
        if ( ++count > maxdist )
        {
            bfs_clear_queue();
            clean_room_queue();
            return BFS_NO_PATH;
        }
        if (queue_head->room == target)
        {
            curr_dir = queue_head->dir;
            bfs_clear_queue();
            clean_room_queue();
            return curr_dir;
        }
        else
        {
            for (pexit = queue_head->room->first_exit; pexit; pexit = pexit->next )
                if ( valid_edge(pexit) )
                {
                    curr_dir = pexit->vdir;
                    MARK(pexit->to_room);
                    room_enqueue(pexit->to_room);
                    bfs_enqueue(pexit->to_room,queue_head->dir);
                }
            bfs_dequeue();
        }
    }
    clean_room_queue();
    
    return BFS_NO_PATH;
}


int char_hunting( CHAR_DATA *ch )
{
  if (ch->hunting)
     if (ch->hunting->who)
        return TRUE;

  return FALSE;
}

void do_track( CHAR_DATA *ch, char *argument )
{
    CHAR_DATA *vict;
    char arg[MAX_INPUT_LENGTH];
    int dir, maxdist;

    if (!ch)
       return;

    if ( char_hunting(ch) && (argument[0] == '\0') )
    { 
       vict = ch->hunting->who;
    } else {
    
    if (number_percent() > (IS_NPC(ch)? GetMaxLevel(ch)+20:LEARNED(ch, gsn_hunt)) )
    {
        send_to_char("You fail at your attempt to track!\n\r", ch);
        if (CanUseSkill(ch, gsn_hunt))
           learn_from_failure(ch, gsn_hunt);
        return;
    }    

    one_argument(argument, arg);
    if ( arg[0]=='\0' )
    {
        send_to_char("Whom are you trying to track?\n\r", ch);
        return;
    }
    
    if ( !(vict = get_char_area(ch, arg)) )
    {
        send_to_char("You can't find a trail of anyone like that.\n\r", ch);
        return;
    }

  }
    
    if (!IS_NPC(ch))
        maxdist = LEARNED(ch, gsn_hunt)?LEARNED(ch, gsn_hunt):10;
    else
        maxdist = 10;
    
    if (IS_ACTIVE(ch, CLASS_THIEF))
        maxdist *= 3;

    switch (GET_RACE(ch))
    {
    case RACE_ELVEN:
        maxdist *= 2;
        break;
    case RACE_DEVIL:
    case RACE_DEMON:
    case RACE_GOD:
        maxdist = 30000;
        break;
    }
    
    if (is_affected(ch, gsn_minor_track))
        maxdist = GetMaxLevel(ch) * 50;
    else if (is_affected(ch, gsn_major_track))
        maxdist = GetMaxLevel(ch) * 100;
    
    if (IS_IMMORTAL(ch))
        maxdist = 30000;
    
    if (maxdist<=0)
        return;
    
    dir = find_first_step(ch->in_room, vict->in_room, maxdist);
    
    set_char_color(AT_RED, ch);
    
    switch(dir)
    {
    case BFS_ERROR:
        send_to_char("Hmm... something seems to be wrong.\n\r", ch);
        break;
    case BFS_ALREADY_THERE:
        if (ch->hunting)
        {
            send_to_char("You have found your prey!\n\r", ch);
            stop_hunting(ch);
        } else
            send_to_char("You're already in the same room!\n\r", ch);
        break;
    case BFS_NO_PATH:
        if (ch->hunting)
            stop_hunting(ch);
        send_to_char("You can't sense a trail from here.\n\r", ch);
        if (CanUseSkill(ch, gsn_hunt))
           learn_from_failure(ch, gsn_hunt);
        break;
    default:
        if (ch->hunting && ch->hunting->who != vict)
            stop_hunting(ch);
        start_hunting(ch, vict);
        ch_printf(ch, "You sense a trail %s from here...\n\r", dir_name(dir));
        if (CanUseSkill(ch, gsn_hunt))
           learn_from_success( ch, gsn_hunt );
        break;
    }
}

void found_prey( CHAR_DATA *ch, CHAR_DATA *victim )
{
    char buf[MAX_STRING_LENGTH];
    char victname[MAX_STRING_LENGTH];
    
    if (victim == NULL)
    {
        bug("Found_prey: null victim");
        return;
    }
    
    if ( victim->in_room == NULL )
    {
        bug( "Found_prey: null victim->in_room" );
        return;
    }
    
    sprintf( victname, "%s", IS_NPC( victim ) ? victim->short_descr : victim->name );
    
    if ( !can_see(ch, victim) )
    {
        if ( number_percent( ) < 90 || !is_hating( ch, victim ) )
            return;
        if (IsHumanoid(ch))
        {
            switch( number_bits( 3 ) )
            {
            case 0:
                sprintf( buf, "Don't make me find you, %s!", victname );
                do_say( ch, buf );
                break;
            case 1:
                act( AT_ACTION, "$n sniffs around the room for $N.", ch, NULL, victim, TO_NOTVICT );
                act( AT_ACTION, "You sniff around the room for $N.", ch, NULL, victim, TO_CHAR );
                act( AT_ACTION, "$n sniffs around the room for you.", ch, NULL, victim, TO_VICT );
                sprintf( buf, "I can smell your blood!" );
                do_say( ch, buf );
                break;
            case 2:
                sprintf( buf, "I'm going to tear %s apart!", victname );
                do_gossip( ch, buf );
                break;
            case 3:
                do_say( ch, "Just wait until I find you...");
                break;
            default:
                break;
            }
        }
        return;
    }
    
    if ( !is_hating( ch, victim ) )
    {
        stop_hunting( ch );
	return;
    }

    if ( IS_SET( ch->in_room->room_flags, ROOM_SAFE ) )
    {
        if ( number_percent( ) < 90 )
            return;
        if (IsHumanoid(ch))
        {
            switch( number_bits( 3 ) )
            {
            case 0:
                do_say( ch, "C'mon out, you coward!" );
                sprintf( buf, "%s is a bloody coward!", victname );
                do_gossip( ch, buf );
                break;
            case 1:
                sprintf( buf, "Let's take this outside, %s", victname );
                do_say( ch, buf );
                break;
            case 2:
                sprintf( buf, "%s is a yellow-bellied wimp!", victname );
                do_gossip( ch, buf );
                break;
            case 3:
                act( AT_ACTION, "$n takes a few swipes at $N.", ch, NULL, victim, TO_NOTVICT );
                act( AT_ACTION, "You try to take a few swipes $N.", ch, NULL, victim, TO_CHAR );
                act( AT_ACTION, "$n takes a few swipes at you.", ch, NULL, victim, TO_VICT );
                break;
            default:
                break;
            }
        }
        return;
    }
    
    if (IsHumanoid(ch))
    {
        switch( number_bits( 2 ) )
        {
        case 0:
            sprintf( buf, "Your blood is mine, %s!", victname );
            do_gossip( ch, buf);
            break;
        case 1:
            sprintf( buf, "Alas, we meet again, %s!", victname );
            do_say( ch, buf );
            break;
        case 2:
            sprintf( buf, "What do you want on your tombstone, %s?", victname );
            do_say( ch, buf );
            break;
        case 3:
            act( AT_ACTION, "$n lunges at $N from out of nowhere!", ch, NULL, victim, TO_NOTVICT );
            act( AT_ACTION, "You lunge at $N catching $M off guard!", ch, NULL, victim, TO_CHAR );
            act( AT_ACTION, "$n lunges at you from out of nowhere!", ch, NULL, victim, TO_VICT );
        }
    }
    
    stop_hunting( ch );
    set_fighting( ch, victim );
    if (IS_NPC(ch) && (IS_ACT_FLAG(ch, ACT_META_AGGR) || sysdata.percent_aggr > 100))
        multi_hit(ch, victim, TYPE_UNDEFINED);
    return;
} 

void hunt_victim( CHAR_DATA *ch )
{
    bool found;
    CHAR_DATA *tmp;
    EXIT_DATA *pexit;
    sh_int ret;
    
    if (!ch || !ch->hunting)
        return;
    
    /* make sure the char still exists */
    for ( found = FALSE, tmp = first_char; tmp && !found; tmp = tmp->next )
        if ( ch->hunting->who == tmp )
            found = TRUE;
    
    if (!found)
    {
        do_say(ch, "My prey is gone!!" );
        stop_hunting( ch );
        return;
    }
    
    if ( ch->in_room == ch->hunting->who->in_room )
    {
        if ( ch->fighting )
            return;
        found_prey( ch, ch->hunting->who );
        return;
    }
    
    ret = find_first_step(ch->in_room, ch->hunting->who->in_room, 500 +
                          GetMaxLevel(ch) * 25);
    if ( ret < 0 )
    {
        do_say( ch, "I lost my prey!" );
        stop_hunting( ch );
        return;
    }
    else
    {
        if ( (pexit=get_exit(ch->in_room, ret)) == NULL )
        {
            bug( "Hunt_victim: lost exit?" );
            return;
        }
        /*
         * Do this instead of move_char so the block specs can handle it
         * like the guildguards
         * Garil 02/04/2002
         */
        interpret(ch, exit_name(pexit));
/*        move_char( ch, pexit, FALSE );*/
        if ( !ch->hunting )
        {
            if ( !ch->in_room )
            {
                bug( "Hunt_victim: no ch->in_room!  Mob #%d, name: %s.  Placing mob in limbo.",
                     ch->vnum, ch->name );
                char_to_room( ch, get_room_index( ROOM_VNUM_LIMBO ) );
                return;
            } 
            do_say( ch, "I lost my prey!" );
            return;
        }
        if ( ch->in_room == ch->hunting->who->in_room )
            found_prey(ch, ch->hunting->who);
        else
        {
            CHAR_DATA *vch;
            
            /* perform a ranged attack if possible */
            /* Changed who to name as scan_for_victim expects the name and
             * Not the char struct. --Shaddai
             */
            if ( (vch=scan_for_victim(ch, pexit, ch->hunting->name)) != NULL )
            {
                if ( !mob_fire(ch, ch->hunting->who->name) )
                {
                    /* ranged spell attacks go here */
                }
            }
        }
        return;
    }
}