LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 *			 Tracking/hunting module			     *
 *****************************************************************************/

#include <stdio.h>
#include "h/mud.h"

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

#define TRACK_THROUGH_DOORS

extern short 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
{
   BFS_DATA *next;
   ROOM_INDEX_DATA *room;
   char dir;
};

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

/* Utility macros */
#define MARK( room )      ( xSET_BIT( (room)->room_flags, BFS_MARK ) )
#define UNMARK( room )    ( xREMOVE_BIT( (room)->room_flags, BFS_MARK ) )
#define IS_MARKED( room ) ( xIS_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 * ) malloc( 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 * ) malloc( 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( "%s: Illegal value passed to find_first_step (track.c)", __FUNCTION__ );
      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;
}

CMDF( do_track )
{
   CHAR_DATA *vict;
   char arg[MIL];
   int dir, maxdist;

   if( !is_npc( ch ) && ch->pcdata->learned[gsn_track] <= 0 )
   {
      send_to_char( "You do not know of this skill yet.\r\n", ch );
      return;
   }

   one_argument( argument, arg );
   if( !arg || arg[0] == '\0' )
   {
      send_to_char( "Whom are you trying to track?\r\n", ch );
      return;
   }

   wait_state( ch, skill_table[gsn_track]->beats );

   if( !( vict = get_char_world( ch, arg ) ) )
   {
      send_to_char( "You can't find a trail of anyone like that.\r\n", ch );
      return;
   }

   maxdist = 100 + ch->level * 30;

   if( !is_npc( ch ) )
      maxdist = ( maxdist * LEARNED( ch, gsn_track ) ) / 100;

   dir = find_first_step( ch->in_room, vict->in_room, maxdist );

   switch( dir )
   {
      case BFS_ERROR:
         send_to_char( "Hmm... something seems to be wrong.\r\n", ch );
         break;
      case BFS_ALREADY_THERE:
         send_to_char( "You're already in the same room!\r\n", ch );
         break;
      case BFS_NO_PATH:
         send_to_char( "You can't sense a trail from here.\r\n", ch );
         learn_from_failure( ch, gsn_track );
         break;
      default:
         ch_printf( ch, "You sense a trail %s from here...\r\n", dir_name[dir] );
         learn_from_success( ch, gsn_track );
         break;
   }
}

void found_prey( CHAR_DATA *ch, CHAR_DATA *victim )
{
   char buf[MSL], victname[MSL];

   if( !victim )
   {
      bug( "%s: null victim", __FUNCTION__ );
      return;
   }

   if( !victim->in_room )
   {
      bug( "%s: null victim->in_room", __FUNCTION__ );
      return;
   }

   mudstrlcpy( victname, is_npc( victim ) ? victim->short_descr : victim->name, sizeof( victname ) );

   if( !can_see( ch, victim ) )
   {
      if( number_percent( ) < 90 )
         return;
      switch( number_bits( 2 ) )
      {
         case 0:
            snprintf( buf, sizeof( 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 );
            do_say( ch, "I can smell your blood!" );
            break;

         case 2:
            snprintf( buf, sizeof( buf ), "yell I'm going to tear %s apart!", victname );
            interpret( ch, buf );
            break;

         case 3:
            do_say( ch, "Just wait until I find you..." );
            break;
      }
      return;
   }

   if( xIS_SET( ch->in_room->room_flags, ROOM_SAFE ) )
   {
      if( number_percent( ) < 90 )
         return;
      switch( number_bits( 2 ) )
      {
         case 0:
            do_say( ch, "C'mon out, you coward!" );
            snprintf( buf, sizeof( buf ), "yell %s is a bloody coward!", victname );
            interpret( ch, buf );
            break;

         case 1:
            snprintf( buf, sizeof( buf ), "Let's take this outside, %s", victname );
            do_say( ch, buf );
            break;

         case 2:
            snprintf( buf, sizeof( buf ), "yell %s is a yellow-bellied wimp!", victname );
            interpret( 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;
      }
      return;
   }

   switch( number_bits( 2 ) )
   {
      case 0:
         snprintf( buf, sizeof( buf ), "yell Your blood is mine, %s!", victname );
         interpret( ch, buf );
         break;

      case 1:
         snprintf( buf, sizeof( buf ), "Alas, we meet again, %s!", victname );
         do_say( ch, buf );
         break;

      case 2:
         snprintf( buf, sizeof( 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 );
   multi_hit( ch, victim, TYPE_UNDEFINED );
}

void hunt_victim( CHAR_DATA *ch )
{
   bool found;
   CHAR_DATA *tmp;
   EXIT_DATA *pexit;
   short ret;

   if( !ch || !ch->hunting || ch->position < 5 )
      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, "Damn!  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 + ch->level * 25 );
   if( ret < 0 )
   {
      do_say( ch, "Damn!  Lost my prey!" );
      stop_hunting( ch );
      return;
   }
   else
   {
      if( !( pexit = get_exit( ch->in_room, ret ) ) )
      {
         bug( "%s: lost exit?", __FUNCTION__ );
         return;
      }
      move_char( ch, pexit, false );

      /* Crash bug fix by Shaddai */
      if( char_died( ch ) )
         return;

      if( !ch->hunting )
      {
         if( !ch->in_room )
         {
            bug( "%s: no ch->in_room!  Mob #%d, name: %s.  Placing mob in limbo.", __FUNCTION__, ch->pIndexData->vnum, ch->name );
            char_to_room( ch, get_room_index( sysdata.room_limbo ) );
            return;
         }
         do_say( ch, "Damn!  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 ) ) )
         {
            if( !mob_fire( ch, ch->hunting->who->name ) )
            {
               /* ranged spell attacks go here */
            }
         }
      }
   }
}