/*************************************************************************
 * Original track.c code from CircleMUD					 * 
 *                                                                       *
 * Modified for EmberMUD by Zak						 *
 * 									 *
 * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.              *
 *************************************************************************/

#define TRACK_THROUGH_DOORS
/* #define TRACK_IS_SKILL */
#undef TRACK_IS_SKILL

/* 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.
*/

#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#include <sys/time.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "merc.h"

DECLARE_DO_FUN(do_say );

struct track_queue_struct
{
   ROOM_INDEX_DATA *room;
   char   dir;
   struct track_queue_struct *next;
};

static struct track_queue_struct *queue_head = NULL, *queue_tail = NULL;

#define TRACK_NO_PATH          -1
#define TRACK_ALREADY_THERE    -2
#define TRACK_ERROR            -3


/* Utility macros */
#define MARK(room)      (SET_BIT((room)->room_flags, ROOM_MARK))
#define UNMARK(room)    (REMOVE_BIT((room)->room_flags, ROOM_MARK))
#define IS_MARKED(room) (IS_SET((room)->room_flags, ROOM_MARK))
#define TOROOM(x, y)    ((x)->exit[(y)] ? (x)->exit[(y)]->u1.to_room : NULL)
#define IS_CLOSED(x, y) (IS_SET((x)->exit[(y)]->exit_info, EX_CLOSED))
#define IS_SAME_AREA(x,y)  ((x)->area == (y)->area)

#ifdef TRACK_THROUGH_DOORS
#define VALID_EDGE(x, y) ((x)->exit[(y)] && \
			  (TOROOM((x), (y)) != NULL) &&	\
			  (!IS_MARKED(TOROOM((x), (y)))) &&  \
                          (IS_SAME_AREA((x), TOROOM((x),(y)))))
#else
#define VALID_EDGE(x, y) ((x)->exit[(y)] && \
			  (TOROOM((x), (y)) != NULL) &&	\
			  (!IS_CLOSED((x), (y))) &&		\
			  (!IS_MARKED(TOROOM((x), (y)))) && \
                          (IS_SAME_AREA((x), TOROOM((x),(y)))))
#endif

bool can_go( CHAR_DATA *ch, int dir )
{
  ROOM_INDEX_DATA *room;
  EXIT_DATA *exit;

  if ( !(room = ch->in_room) )
    return FALSE;

  if ( !(exit = ch->in_room->exit[dir]) )
    return FALSE;

  if ( !exit->u1.to_room )
    return FALSE;

  if ( !IS_SAME_AREA(room, exit->u1.to_room) )
    return FALSE;

#ifdef TRACK_THROUGH_DOORS
  if ( IS_CLOSED(room, dir) )
    return FALSE;
#endif

  return TRUE;
}
  

void track_enqueue(ROOM_INDEX_DATA *room, char dir)
{
  struct track_queue_struct *curr;

  curr = alloc_mem( sizeof( *curr ) );
  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 track_dequeue(void)
{
  struct track_queue_struct *curr;

  curr = queue_head;

  if (!(queue_head = queue_head->next))
    queue_tail = NULL;
  free_mem(curr,sizeof (*curr));
}


void track_clear_queue(void) 
{
  while (queue_head)
    track_dequeue();
}


/* find_first_step: given a source room and a target room, find the first
 *  step on the shortest path from the source to the target.
 *
 *  Intended usage: in mobile_activity, give a mob a dir to go if they're
 *  tracking another mob or a PC.  Or, a 'track' skill for PCs.
 */

int find_first_step(ROOM_INDEX_DATA *src, ROOM_INDEX_DATA *target)
{
   int curr_dir;
   ROOM_INDEX_DATA *curr_room;
   int vnum;
   
   if (!src || !target)
   {
      bug("Illegal value passed to find_first_step (track.c)",0);
      return TRACK_ERROR;
   }

   if (src == target)
      return TRACK_ALREADY_THERE;

   /* clear marks first */
   for (vnum = src->area->lvnum; vnum!=src->area->uvnum+1; vnum++)
     {
	if ((curr_room = get_room_index(vnum))!=NULL) UNMARK(curr_room);
     }
   
   MARK(src);

   /* first, enqueue the first steps, saving which direction we're going. */
   for (curr_dir = 0; curr_dir < 6; curr_dir++)
      if (VALID_EDGE(src, curr_dir))
      {
         MARK(TOROOM(src, curr_dir));
         track_enqueue(TOROOM(src, curr_dir), curr_dir);
      }

   /* now, do the track. */
   while (queue_head)
   {
      if (queue_head->room == target)
      {
	 curr_dir = queue_head->dir;
	 track_clear_queue();
	 return curr_dir;
      }
      else
      {
	for (curr_dir = 0; curr_dir < 6; curr_dir++)
	  if (VALID_EDGE(queue_head->room, curr_dir))
	  {
	    MARK(TOROOM(queue_head->room, curr_dir));
	    track_enqueue(TOROOM(queue_head->room, curr_dir),queue_head->dir);
	  }
	track_dequeue();
      }
   }

   return TRACK_NO_PATH;
}


/************************************************************************
*  Functions and Commands which use the above fns		        *
************************************************************************/

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

   const char *dir_text[]={"north","east","south","west","up","down"};
   
   one_argument(argument, arg);
   if (!*arg)
   {
      send_to_char( "Whom are you trying to track?\n\r", ch);
      return;
   }

   if (!(vict = get_char_world(ch, arg)))
   {
      send_to_char( "You can't find them.\n\r", ch);
      return;
   }

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

   switch(dir)
   {
      case TRACK_ERROR:
         send_to_char( "Hmm.. something seems to be wrong.\n\r", ch);
         break;
      case TRACK_ALREADY_THERE:
         send_to_char( "You're already in the same room!!\n\r", ch);
         break;
      case TRACK_NO_PATH:
         sprintf(buf, "You can't sense a trail to %s from here.\n\r",
		PERS(vict, ch));
         send_to_char( buf, ch);
         break;
      default:
         /* if you want to make this into a skill instead of a command,
            the next few lines make it give you a random direction if you
            fail the random skill roll.
         */

#ifdef TRACK_IS_SKILL
	 {
	   int counter;

	   if(!IS_NPC(ch) && number_percent() < ch->pcdata->learned[gsn_track])
	     for( counter = 0; counter < 50; counter++ )
	     {
	       dir = number_door();
	       if ( can_go( ch, dir ) )
		 break;
	       dir = -1;
	     }
	   else
	     update_skpell( ch, gsn_track );
	   if ( dir < 0 )
	   {
	     sprintf(buf, "You can't sense a trail to %s from here.\n\r",
		     PERS(vict,ch) );
	     send_to_char( buf, ch );
	     return;
	   }
	 }
#endif

      sprintf(buf, "You sense a trail %s from here!\n\r", dir_text[dir]);
         send_to_char( buf, ch);
         break;
   }
}