/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * fpath.c
 *
 * Interface to the routing functions
 *
 */

// route success printf's
//#define DEBUG_GROUP0
// way point printf's
//#define DEBUG_GROUP1
// gateway route printf's
//#define DEBUG_GROUP2

#include "lib/framework/frame.h"

BOOL	fpathDoMessage;
#undef DBP2
#define DBP2( x ) \
	if (fpathDoMessage) \
		debug( LOG_NEVER, x )

#include "objects.h"
#include "map.h"
#include "raycast.h"
#include "geometry.h"
#include "hci.h"
#include "order.h"

#ifdef TEST_BED
#include "main.h"
#endif
#include "astar.h"
#include "gateway.h"
#include "gatewayroute.h"
#include "action.h"
#include "formation.h"

#include "fpath.h"

/* minimum height difference for VTOL blocking tile */
#define	LIFT_BLOCK_HEIGHT_LIGHTBODY		  30
#define	LIFT_BLOCK_HEIGHT_MEDIUMBODY	 350
#define	LIFT_BLOCK_HEIGHT_HEAVYBODY		 350

#define NUM_DIR		8
// Convert a direction into an offset
// dir 0 => x = 0, y = -1
static POINT aDirOffset[NUM_DIR] =
{
	{ 0, 1},
	{-1, 1},
	{-1, 0},
	{-1,-1},
	{ 0,-1},
	{ 1,-1},
	{ 1, 0},
	{ 1, 1},
};

/* global pointer for object being routed - GJ hack -
 * currently only used in fpathLiftBlockingTile */
static	BASE_OBJECT	*g_psObjRoute = NULL;

// function pointer for the blocking tile check
BOOL (*fpathBlockingTile)(SDWORD x, SDWORD y);

// if a route is spread over a number of frames this stores the object
// the route is being done for
static BASE_OBJECT *psPartialRouteObj = NULL;

// coords of the partial route
static SDWORD partialSX,partialSY, partialTX,partialTY;

// the last frame on which the partial route was calculatated
static SDWORD	lastPartialFrame;


// the maximum amount of routing to do per frame
SDWORD	astarMaxRoute = FPATH_MAX_ROUTE_INIT;

BOOL fpathFindRoute(DROID *psDroid, SDWORD sX,SDWORD sY, SDWORD tX,SDWORD tY);

// initialise the findpath module
BOOL fpathInitialise(void)
{
	fpathBlockingTile = fpathGroundBlockingTile;
	psPartialRouteObj = NULL;

	return TRUE;
}

// update routine for the findpath system
void fpathUpdate(void)
{
	if ((psPartialRouteObj != NULL) &&
		((psPartialRouteObj->died) ||
		 (((DROID*)psPartialRouteObj)->sMove.Status != MOVEWAITROUTE) ||
		 ((lastPartialFrame + 5) < (SDWORD)frameGetFrameNumber()) ) )
	{
		psPartialRouteObj = NULL;
	}
}


// access functions for the loop limit
void fpathSetMaxRoute(SDWORD max)
{
	astarMaxRoute = max;
}
SDWORD fpathGetMaxRoute(void)
{
	return astarMaxRoute;
}

#define	VTOL_MAP_EDGE_TILES		1

// Check if the map tile at a location blocks a droid
BOOL fpathGroundBlockingTile(SDWORD x, SDWORD y)
{
	MAPTILE	*psTile;
//	FEATURE	*psFeat;

    //doesn't look like we need this - pickATile wasn't working with it! - AB 8/2/99
	/* check VTOL limits if not routing */
	/*if ( g_psObjRoute == NULL )
	{
		if ( x < VTOL_MAP_EDGE_TILES || y < VTOL_MAP_EDGE_TILES ||
			 x >= (SDWORD)mapWidth-VTOL_MAP_EDGE_TILES || y >= (SDWORD)mapHeight-VTOL_MAP_EDGE_TILES)
		{
			// coords off map - auto blocking tile
			return TRUE;
		}
	}
	else*/
	{
		if (x < scrollMinX+1 || y < scrollMinY+1 ||
			x >= scrollMaxX-1 || y >= scrollMaxY-1)
		{
			// coords off map - auto blocking tile
			return TRUE;
		}
	}

	ASSERT( !(x <1 || y < 1 ||	x >= (SDWORD)mapWidth-1 || y >= (SDWORD)mapHeight-1),
		"fpathBlockingTile: off map" );

	psTile = mapTile((UDWORD)x, (UDWORD)y);
/*
	// THIS CAN'T BE HERE - TESTING ONLY FIXME
	if( (TILE_HAS_STRUCTURE(psTile)) AND
		(getTileStructure(x,y)->pStructureType->type == REF_BLASTDOOR) AND	  // slow bit
		(getTileStructure(x,y)->player==selectedPlayer) )
	{
		return(FALSE);
	}
*/

//#ifndef PSX // Must have to do this on PSX as well?
/*  This god awful hack RIP - John 15.2.99
	if(TILE_HAS_FEATURE(psTile))
	{
		psFeat = getTileFeature(x,y);
		if ((psFeat != NULL) &&
			(psFeat->psStats->subType == FEAT_GEN_ARTE OR psFeat->psStats->subType == FEAT_OIL_DRUM))
		{
			return(FALSE);
		}
	}*/
//#endif

#ifndef TEST_BED
	if ((psTile->tileInfoBits & BITS_FPATHBLOCK) ||
		(TILE_OCCUPIED(psTile) && !TILE_IS_NOTBLOCKING(psTile)) ||
		(TERRAIN_TYPE(psTile) == TER_CLIFFFACE) ||
		(TERRAIN_TYPE(psTile) == TER_WATER))
#else
	if (psTile->tileInfoBits & BLOCKED)
#endif
	{
		return TRUE;
	}
	return FALSE;
}

#ifndef TEST_BED
// Check if the map tile at a location blocks a droid
BOOL fpathHoverBlockingTile(SDWORD x, SDWORD y)
{
	MAPTILE	*psTile;

	if (x < scrollMinX+1 || y < scrollMinY+1 ||
		x >= scrollMaxX-1 || y >= scrollMaxY-1)
	{
		// coords off map - auto blocking tile
		return TRUE;
	}

	ASSERT( !(x <1 || y < 1 ||	x >= (SDWORD)mapWidth-1 || y >= (SDWORD)mapHeight-1),
		"fpathBlockingTile: off map" );

	psTile = mapTile((UDWORD)x, (UDWORD)y);

	if ((psTile->tileInfoBits & BITS_FPATHBLOCK) ||
		(TILE_OCCUPIED(psTile) && !TILE_IS_NOTBLOCKING(psTile)) ||
		(TERRAIN_TYPE(psTile) == TER_CLIFFFACE))
	{
		return TRUE;
	}
	return FALSE;
}

// Check if the map tile at a location blocks a vtol
BOOL fpathLiftBlockingTile(SDWORD x, SDWORD y)
{
	MAPTILE		*psTile;
	SDWORD		iLiftHeight, iBlockingHeight;
	DROID		*psDroid = (DROID *) g_psObjRoute;

	ASSERT( PTRVALID(g_psObjRoute, sizeof(BASE_OBJECT)),
		"fpathLiftBlockingTile: invalid object pointer" );
	ASSERT( PTRVALID(psDroid, sizeof(DROID)),
		"fpathLiftBlockingTile: invalid droid pointer" );

	if (psDroid->droidType == DROID_TRANSPORTER )
	{
		if ( x<1 || y<1 || x>=(SDWORD)mapWidth-1 || y>=(SDWORD)mapHeight-1 )
		{
			return TRUE;
		}

		psTile = mapTile((UDWORD)x, (UDWORD)y);

		if ( TILE_HAS_TALLSTRUCTURE(psTile) )
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}

	if ( x < VTOL_MAP_EDGE_TILES ||
		 y < VTOL_MAP_EDGE_TILES ||
		 x >= (SDWORD)mapWidth-VTOL_MAP_EDGE_TILES ||
		 y >= (SDWORD)mapHeight-VTOL_MAP_EDGE_TILES   )
	{
		// coords off map - auto blocking tile
		return TRUE;
	}

	ASSERT( !(x <1 || y < 1 ||	x >= (SDWORD)mapWidth-1 || y >= (SDWORD)mapHeight-1),
			"fpathLiftBlockingTile: off map" );

	/* no tiles are blocking if returning to rearm */
	if( psDroid->action == DACTION_MOVETOREARM )
	{
		return FALSE;
	}

	psTile = mapTile((UDWORD)x, (UDWORD)y);

	/* consider cliff faces */
	if ( TERRAIN_TYPE(psTile) == TER_CLIFFFACE )
	{
		switch ( (asBodyStats + psDroid->asBits[COMP_BODY].nStat)->size )
		{
			case SIZE_LIGHT:
				iBlockingHeight = LIFT_BLOCK_HEIGHT_LIGHTBODY;
				break;
			case SIZE_MEDIUM:
				iBlockingHeight = LIFT_BLOCK_HEIGHT_MEDIUMBODY;
				break;
			case SIZE_HEAVY:
				iBlockingHeight = LIFT_BLOCK_HEIGHT_HEAVYBODY;
				break;
			default:
				iBlockingHeight = LIFT_BLOCK_HEIGHT_LIGHTBODY;
		}

		/* approaching cliff face; block if below it */
		iLiftHeight = (SDWORD) map_Height( x<<TILE_SHIFT, y<<TILE_SHIFT ) -
					  (SDWORD) map_Height( g_psObjRoute->x, g_psObjRoute->y );
		if ( iLiftHeight > iBlockingHeight )
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	else if ( TILE_HAS_TALLSTRUCTURE(psTile) )
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

// Check if an edge map tile blocks a vtol (for sliding at map edge)
BOOL fpathLiftSlideBlockingTile(SDWORD x, SDWORD y)
{
	if ( x < 1 || y < 1 ||
		 x >= (SDWORD)GetWidthOfMap()-1  ||
		 y >= (SDWORD)GetHeightOfMap()-1    )
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}
#endif

// Calculate the distance to a tile from a point
SDWORD fpathDistToTile(SDWORD tileX,SDWORD tileY, SDWORD pointX, SDWORD pointY)
{
	SDWORD	xdiff,ydiff, dist;
	SDWORD	tx,ty;

	// get the difference in tile coords
	xdiff = tileX - (pointX >> TILE_SHIFT);
	ydiff = tileY - (pointY >> TILE_SHIFT);

	ASSERT( (xdiff >= -1 && xdiff <= 1 && ydiff >= -1 && ydiff <= 1),
		"fpathDistToTile: points are more than one tile apart" );
	ASSERT( xdiff != 0 || ydiff != 0,
		"fpathDistToTile: points are on same tile" );

	// not the most elegant solution but it works
	switch (xdiff + ydiff * 10)
	{
	case 10:	// xdiff == 0, ydiff == 1
		dist = TILE_UNITS - (pointY & TILE_MASK);
		break;
	case 9:		// xdiff == -1, ydiff == 1
		tx = pointX & TILE_MASK;
		ty = TILE_UNITS - (pointY & TILE_MASK);
		dist = tx > ty ? tx + ty/2 : tx/2 + ty;
		break;
	case -1:	// xdiff == -1, ydiff == 0
		dist = pointX & TILE_MASK;
		break;
	case -11:	// xdiff == -1, ydiff == -1
		tx = pointX & TILE_MASK;
		ty = pointY & TILE_MASK;
		dist = tx > ty ? tx + ty/2 : tx/2 + ty;
		break;
	case -10:	// xdiff == 0, ydiff == -1
		dist = pointY & TILE_MASK;
		break;
	case -9:	// xdiff == 1, ydiff == -1
		tx = TILE_UNITS - (pointX & TILE_MASK);
		ty = pointY & TILE_MASK;
		dist = tx > ty ? tx + ty/2 : tx/2 + ty;
		break;
	case 1:		// xdiff == 1, ydiff == 0
		dist = TILE_UNITS - (pointX & TILE_MASK);
		break;
	case 11:	// xdiff == 1, ydiff == 1
		tx = TILE_UNITS - (pointX & TILE_MASK);
		ty = TILE_UNITS - (pointY & TILE_MASK);
		dist = tx > ty ? tx + ty/2 : tx/2 + ty;
		break;
	default:
		ASSERT( FALSE, "fpathDistToTile: unexpected point relationship" );
		dist = TILE_UNITS;
		break;
	}

	return dist;
}

// Variables for the callback
static SDWORD	finalX,finalY, vectorX,vectorY;
static SDWORD	clearX,clearY;
static BOOL		obstruction;

// callback to find the first clear tile before an obstructed target
BOOL fpathEndPointCallback(SDWORD x, SDWORD y, SDWORD dist)
{
	SDWORD	vx,vy;

	dist = dist;

	// See if this point is past the final point (dot product)
	vx = x - finalX;
	vy = y - finalY;
	if (vx*vectorX + vy*vectorY <=0)
	{
		return FALSE;
	}

	// note the last clear tile
	if (!fpathBlockingTile(x>>TILE_SHIFT,y>>TILE_SHIFT))
	{
		clearX = (x & ~TILE_MASK) + TILE_UNITS/2;
		clearY = (y & ~TILE_MASK) + TILE_UNITS/2;
	}
	else
	{
		obstruction = TRUE;
	}

	return TRUE;
}

void fpathSetDirectRoute( BASE_OBJECT *psObj, SDWORD targetX, SDWORD targetY )
{
	MOVE_CONTROL *psMoveCntl;

	ASSERT( PTRVALID(psObj, sizeof(BASE_OBJECT)),
			"fpathSetDirectRoute: invalid object pointer\n" );

	if ( psObj->type == OBJ_DROID )
	{
		psMoveCntl = &((DROID *) psObj)->sMove;

		/* set global pointer for object being routed - GJ hack */
		fpathSetCurrentObject( psObj );

		psMoveCntl->DestinationX = targetX;
		psMoveCntl->DestinationY = targetY;
//		psMoveCntl->MovementList[0].XCoordinate = targetX;
//		psMoveCntl->MovementList[0].YCoordinate = targetY;
//		psMoveCntl->MovementList[1].XCoordinate = -1;
//		psMoveCntl->MovementList[1].YCoordinate = -1;
		psMoveCntl->numPoints = 1;
		psMoveCntl->asPath[0].x = (UBYTE)(targetX >> TILE_SHIFT);
		psMoveCntl->asPath[0].y = (UBYTE)(targetY >> TILE_SHIFT);
	}
}


// append an astar route onto a move-control route
void fpathAppendRoute( MOVE_CONTROL *psMoveCntl, ASTAR_ROUTE *psAStarRoute )
{
	SDWORD		mi, ai;

	// find the end of the last route
//	for(mi=0; psMoveCntl->MovementList[mi].XCoordinate != -1; mi += 1)
//		;

	mi = psMoveCntl->numPoints;
	ai = 0;
	while ((mi < TRAVELSIZE) && (ai < psAStarRoute->numPoints))
	{
//		psMoveCntl->MovementList[mi].XCoordinate =
		psMoveCntl->asPath[mi].x = (UBYTE)(psAStarRoute->asPos[ai].x);
//			(UBYTE)(psAStarRoute->asPos[ai].x >> TILE_SHIFT);
//		psMoveCntl->MovementList[mi].YCoordinate =
		psMoveCntl->asPath[mi].y = (UBYTE)(psAStarRoute->asPos[ai].y);
//			(UBYTE)(psAStarRoute->asPos[ai].y >> TILE_SHIFT);

		ai += 1;
		mi += 1;
	}

//	psMoveCntl->MovementList[mi].XCoordinate = -1;
//	psMoveCntl->MovementList[mi].YCoordinate = -1;
	psMoveCntl->numPoints = (UBYTE)(psMoveCntl->numPoints + ai);
	psMoveCntl->DestinationX = (psAStarRoute->finalX << TILE_SHIFT) + TILE_UNITS/2;
	psMoveCntl->DestinationY = (psAStarRoute->finalY << TILE_SHIFT) + TILE_UNITS/2;
}


// set the routing block flags for the gateways
/*void fpathSetGatewayBlock(void)
{
	GATEWAY		*psCurr;
	SDWORD		pos;
	MAPTILE		*psTile;

	for(psCurr=psGateways; psCurr; psCurr=psCurr->psNext)
	{
		if (!(psCurr->flags & GWR_INROUTE))
		{
			if (psCurr->x1 == psCurr->x2)
			{
				for(pos = psCurr->y1; pos <= psCurr->y2; pos += 1)
				{
					psTile = mapTile(psCurr->x1, pos);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
			else
			{
				for(pos = psCurr->x1; pos <= psCurr->x2; pos += 1)
				{
					psTile = mapTile(pos, psCurr->y1);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
		}
	}
}*/


// clear the routing block flags for the gateways
/*void fpathClearGatewayBlock(void)
{
	GATEWAY		*psCurr;
	SDWORD		pos;
	MAPTILE		*psTile;

	for(psCurr=psGateways; psCurr; psCurr=psCurr->psNext)
	{
//		if (!(psCurr->flags & GWR_INROUTE))
		{
			if (psCurr->x1 == psCurr->x2)
			{
				for(pos = psCurr->y1; pos <= psCurr->y2; pos += 1)
				{
					psTile = mapTile(psCurr->x1, pos);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
			else
			{
				for(pos = psCurr->x1; pos <= psCurr->x2; pos += 1)
				{
					psTile = mapTile(pos, psCurr->y1);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
		}
	}
}*/


// check whether a WORLD coordinate point is within a gateway's tiles
BOOL fpathPointInGateway(SDWORD x, SDWORD y, GATEWAY *psGate)
{
	x = x >> TILE_SHIFT;
	y = y >> TILE_SHIFT;

	if ((x >= psGate->x1) && (x <= psGate->x2) &&
		(y >= psGate->y1) && (y <= psGate->y2))
	{
		return TRUE;
	}

	return FALSE;
}


// set blocking flags for all gateways around a zone
void fpathSetGatewayBlock(SDWORD zone, GATEWAY *psLast, GATEWAY *psNext)
{
	GATEWAY		*psCurr;
	SDWORD		pos, tx,ty, blockZone;
	MAPTILE		*psTile;

	for(psCurr=psGateways; psCurr; psCurr=psCurr->psNext)
	{
		if ((psCurr != psLast) &&
			(psCurr != psNext) &&
			!(psCurr->flags & GWR_WATERLINK) &&
			((psCurr->zone1 == zone) || (psCurr->zone2 == zone)) )
		{
			if (psCurr->x1 == psCurr->x2)
			{
				for(pos = psCurr->y1; pos <= psCurr->y2; pos += 1)
				{
					psTile = mapTile(psCurr->x1, pos);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
			else
			{
				for(pos = psCurr->x1; pos <= psCurr->x2; pos += 1)
				{
					psTile = mapTile(pos, psCurr->y1);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
		}
	}

	// now set the blocking flags next to the two gateways that the route
	// is going through
	DBP2(("Blocking gateways for zones :"));
	if (psLast != NULL)
	{
		blockZone = (psLast->flags & GWR_ZONE1) ? psLast->zone1 : psLast->zone2;
		DBP2((" %d", blockZone));
		for(tx = psLast->x1 - 1; tx <= psLast->x2 + 1; tx ++)
		{
			for(ty = psLast->y1 - 1; ty <= psLast->y2 + 1; ty ++)
			{
				if (!fpathPointInGateway(tx << TILE_SHIFT, ty << TILE_SHIFT, psLast) &&
					tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
				{
					psTile = mapTile(tx, ty);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
		}
	}
	if (psNext != NULL)
	{
		blockZone = (psNext->flags & GWR_ZONE1) ? psNext->zone2 : psNext->zone1;
		DBP2((" %d", blockZone));
		for(tx = psNext->x1 - 1; tx <= psNext->x2 + 1; tx ++)
		{
			for(ty = psNext->y1 - 1; ty <= psNext->y2 + 1; ty ++)
			{
				if (!fpathPointInGateway(tx << TILE_SHIFT, ty << TILE_SHIFT, psNext) &&
					tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
				{
					psTile = mapTile(tx, ty);
					psTile->tileInfoBits |= BITS_FPATHBLOCK;
				}
			}
		}
	}
	DBP2(("\n"));
}


// clear blocking flags for all gateways around a zone
void fpathClearGatewayBlock(SDWORD zone, GATEWAY *psLast, GATEWAY *psNext)
{
	GATEWAY		*psCurr;
	SDWORD		pos, tx,ty, blockZone;
	MAPTILE		*psTile;

	for(psCurr=psGateways; psCurr; psCurr=psCurr->psNext)
	{
		if (!(psCurr->flags & GWR_WATERLINK) &&
			((psCurr->zone1 == zone) || (psCurr->zone2 == zone)) )
		{
			if (psCurr->x1 == psCurr->x2)
			{
				for(pos = psCurr->y1; pos <= psCurr->y2; pos += 1)
				{
					psTile = mapTile(psCurr->x1, pos);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
			else
			{
				for(pos = psCurr->x1; pos <= psCurr->x2; pos += 1)
				{
					psTile = mapTile(pos, psCurr->y1);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
		}
	}
	// clear the flags around the route gateways
	if (psLast != NULL)
	{
		blockZone = (psLast->flags & GWR_ZONE1) ? psLast->zone1 : psLast->zone2;
		for(tx = psLast->x1 - 1; tx <= psLast->x2 + 1; tx ++)
		{
			for(ty = psLast->y1 - 1; ty <= psLast->y2 + 1; ty ++)
			{
				if (!fpathPointInGateway(tx << TILE_SHIFT, ty << TILE_SHIFT, psLast) &&
					tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
				{
					psTile = mapTile(tx, ty);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
		}
	}
	if (psNext != NULL)
	{
		blockZone = (psNext->flags & GWR_ZONE1) ? psNext->zone2 : psNext->zone1;
		for(tx = psNext->x1 - 1; tx <= psNext->x2 + 1; tx ++)
		{
			for(ty = psNext->y1 - 1; ty <= psNext->y2 + 1; ty ++)
			{
				if (!fpathPointInGateway(tx << TILE_SHIFT, ty << TILE_SHIFT, psNext) &&
					tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
				{
					psTile = mapTile(tx, ty);
					psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
				}
			}
		}
	}
}


// clear the routing ignore flags for the gateways
void fpathClearIgnore(void)
{
	GATEWAY		*psCurr;
	SDWORD		link, numLinks;

	for(psCurr=psGateways; psCurr; psCurr=psCurr->psNext)
	{
		psCurr->flags &= ~GWR_IGNORE;
		numLinks = psCurr->zone1Links + psCurr->zone2Links;
		for (link = 0; link < numLinks; link += 1)
		{
			psCurr->psLinks[link].flags &= ~ GWRL_BLOCKED;
		}
	}
}

// find a clear tile on a gateway to route to
void fpathGatewayCoords(GATEWAY *psGate, SDWORD *px, SDWORD *py)
{
	SDWORD	x = 0, y = 0, dist, mx, my, pos;

	// find the clear tile nearest to the middle
	mx = (psGate->x1 + psGate->x2)/2;
	my = (psGate->y1 + psGate->y2)/2;
	dist = SDWORD_MAX;
	if (psGate->x1 == psGate->x2)
	{
		for(pos=psGate->y1;pos <= psGate->y2; pos+=1)
		{
			if (!fpathBlockingTile(psGate->x1,pos) &&
				(abs(pos - my) < dist))
			{
				x = psGate->x1;
				y = pos;
				dist = abs(pos - my);
			}
		}
	}
	else
	{
		for(pos=psGate->x1;pos <= psGate->x2; pos+=1)
		{
			if (!fpathBlockingTile(pos, psGate->y1) &&
				(abs(pos - mx) < dist))
			{
				x = pos;
				y = psGate->y1;
				dist = abs(pos - mx);
			}
		}
	}

	// if no clear tile is found just return the middle
	if (dist == SDWORD_MAX)
	{
		x = mx;
		y = my;
	}

	*px = (x * TILE_UNITS) + TILE_UNITS/2;
	*py = (y * TILE_UNITS) + TILE_UNITS/2;
}

// create a final route from a gateway route
#if 0
SDWORD fpathGatewayRouteOld(BASE_OBJECT *psObj, SDWORD routeMode, SDWORD GWTerrain,
						 SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy,
						 MOVE_CONTROL *psMoveCntl)
{
	static SDWORD	linkx,linky, gwx,gwy, asret, routex,routey;
	ASTAR_ROUTE		sAStarRoute;
	SDWORD			retval = FPR_OK, gwRet;
	static GATEWAY	*psCurrRoute, *psGWRoute, *psLastGW;
	BOOL			bRouting = TRUE;
	BOOL			firstRoute = TRUE;

	// keep trying gateway routes until out of options
	while (bRouting)
	{

		if (routeMode == ASR_NEWROUTE)
		{
			firstRoute = FALSE;

			DBP2(("Gateway route - droid %d\n", psObj->id));
			gwRet = gwrAStarRoute(psObj->player, GWTerrain,
								  sx,sy, fx,fy, &psGWRoute);
			switch (gwRet)
			{
			case GWR_OK:
				// initialise the move control structure
				psMoveCntl->numPoints = 0;
				break;
			case GWR_NEAREST:
				if (firstRoute)
				{
					// first time a route has been generated
					// initialise the move control structure
					psMoveCntl->numPoints = 0;
				}
				else
				{
					DBP2(("   GW route returned GWR_NEAREST for second route\n"));
					// can't find a better route than the last one
					if (psMoveCntl->numPoints > 0)
					{
						// return the last route as it got as near as you can
						retval = FPR_OK;
					}
					else
					{
						DBP2(("     no points - route failed\n"));
						retval = FPR_FAILED;
					}
					goto exit;
				}
				break;
			case GWR_NOZONE:
			case GWR_SAMEZONE:
				// no zone information - try a normal route
				psGWRoute = NULL;
				break;
			case GWR_FAILED:
				DBP2(("   Gateway route failed\n"));
				retval = FPR_FAILED;
				goto exit;
				break;
			}
		}


		// stop routing through any gateways which are not in the route
		fpathSetGatewayBlock();

		if (routeMode == ASR_NEWROUTE)
		{
			// if the start of the route is on the first gateway, skip it
			if ((psGWRoute != NULL) && fpathPointInGateway(sx,sy, psGWRoute))
			{
				psGWRoute = psGWRoute->psRoute;
			}

			linkx = sx;
			linky = sy;
			psCurrRoute = psGWRoute;
			psLastGW = NULL;
		}

		// now generate the route
		bRouting = FALSE;
		while (psCurrRoute != NULL)
		{
			// if the end of the route is on the last gateway, skip it
			if ((psCurrRoute->psRoute == NULL) && fpathPointInGateway(fx,fy, psCurrRoute))
			{
				break;
			}

	/*		gwx = (psCurrRoute->x1 + psCurrRoute->x2)/2;
			gwy = (psCurrRoute->y1 + psCurrRoute->y2)/2;

			gwx = gwx * TILE_UNITS + TILE_UNITS/2;
			gwy = gwy * TILE_UNITS + TILE_UNITS/2;*/
			fpathGatewayCoords(psCurrRoute, &gwx, &gwy);

			DBP2(("   astar route : (%d,%d) -> (%d,%d)\n",
				linkx>>TILE_SHIFT, linky>>TILE_SHIFT,
				gwx>>TILE_SHIFT, gwy>>TILE_SHIFT));
			asret = fpathAStarRoute(routeMode, &sAStarRoute, linkx,linky, gwx,gwy);
			routeMode = ASR_NEWROUTE;
			if ((asret == ASR_NEAREST) &&
				actionRouteBlockingPos((DROID *)psObj, sAStarRoute.finalX,sAStarRoute.finalY))
			{
				// found a blocking wall - route to that
				fpathAppendRoute(psMoveCntl, &sAStarRoute);
				retval = FPR_OK;
				goto exit;
			}
			else if ((asret == ASR_FAILED) ||
					 (asret == ASR_NEAREST))
			{
				// no route found - try ditching this gateway
				// and trying a new gateway route
				DBP2(("   Route failed - trying new gatway route\n"));
				psCurrRoute->flags |= GWR_IGNORE;
				bRouting = TRUE;
				fpathClearGatewayBlock();
				break;
			}
			else if (asret == ASR_PARTIAL)
			{
				// routing hasn't finished yet
				DBP2(("   Reschedule\n"));
				retval = FPR_WAIT;
				goto exit;
			}

			fpathAppendRoute(psMoveCntl, &sAStarRoute);

			linkx = gwx;
			linky = gwy;

			psLastGW = psCurrRoute;
			psCurrRoute = psCurrRoute->psRoute;
		}

		// only finish off if no new gateway route is going to be generated
		if (!bRouting)
		{
			asret = fpathAStarRoute(routeMode,&sAStarRoute, linkx,linky, fx,fy);
			routeMode = ASR_NEWROUTE;
			if ((asret == ASR_NEAREST) &&
				actionRouteBlockingPos((DROID *)psObj, sAStarRoute.finalX,sAStarRoute.finalY))
			{
				// found a blocking wall - route to that
				fpathAppendRoute(psMoveCntl, &sAStarRoute);
				retval = FPR_OK;
				goto exit;
			}
			else if ((psLastGW != NULL) &&
				((asret == ASR_FAILED) ||
				 (asret == ASR_NEAREST)))
			{
				// no route found - try ditching the last gateway
				// and trying a new gateway route
				DBP2(("   Route failed - trying new gatway route\n"));
				psLastGW->flags |= GWR_IGNORE;
				bRouting = TRUE;
				fpathClearGatewayBlock();
			}
			else if (asret == ASR_FAILED)
			{
				DBP2(("   Final route failed\n"));
				retval = FPR_FAILED;
				goto exit;
			}
			else if (asret == ASR_PARTIAL)
			{
				// routing hasn't finished yet
				DBP2(("   Reschedule\n"));
				retval = FPR_WAIT;
				goto exit;
			}

			fpathAppendRoute(psMoveCntl, &sAStarRoute);
		}
	}

exit:
	// reset the routing block flags
	fpathClearGatewayBlock();
	if (retval != FPR_WAIT)
	{
		fpathClearIgnore();
	}

	return retval;
}
#endif

void fpathBlockGatewayLink(GATEWAY *psLast, GATEWAY *psCurr)
{
	SDWORD	link, numLinks;

	if ((psLast == NULL) && (psCurr != NULL))
	{
		DBP2(("   Blocking first gateway\n"));
		psCurr->flags |= GWR_IGNORE;
	}
	else if ((psCurr == NULL) && (psLast != NULL))
	{
		DBP2(("   Blocking last gateway\n"));
		psLast->flags |= GWR_IGNORE;
	}
	else if ((psLast != NULL) && (psCurr != NULL))
	{
		DBP2(("   Blocking link between gateways"));
		numLinks = psLast->zone1Links + psLast->zone2Links;
		for(link = 0; link < numLinks; link += 1)
		{
//			if (psLast->psLinks[link].psGateway == psCurr)
			if (psLast->psLinks[link].flags & GWRL_CHILD)
			{
				DBP2((" last link %d", link));
				psLast->psLinks[link].flags |= GWRL_BLOCKED;
			}
		}
		numLinks = psCurr->zone1Links + psCurr->zone2Links;
		for(link = 0; link < numLinks; link += 1)
		{
//			if (psCurr->psLinks[link].psGateway == psLast)
			if (psCurr->psLinks[link].flags & GWRL_PARENT)
			{
				DBP2((" curr link %d", link));
				psCurr->psLinks[link].flags |= GWRL_BLOCKED;
			}
		}
		DBP2(("\n"));
	}
}


// check if a new route is closer to the target than the one stored in
// the droid
BOOL fpathRouteCloser(MOVE_CONTROL *psMoveCntl, ASTAR_ROUTE *psAStarRoute, SDWORD tx,SDWORD ty)
{
	SDWORD	xdiff,ydiff, prevDist, nextDist;

	if (psAStarRoute->numPoints == 0)
	{
		// no route to copy do nothing
		return FALSE;
	}

	if (psMoveCntl->numPoints == 0)
	{
		// no previous route - this has to be better
		return TRUE;
	}

	// see which route is closest to the final destination
	xdiff = (psMoveCntl->asPath[psMoveCntl->numPoints - 1].x << TILE_SHIFT) + TILE_UNITS/2 - tx;
	ydiff = (psMoveCntl->asPath[psMoveCntl->numPoints - 1].y << TILE_SHIFT) + TILE_UNITS/2 - ty;
	prevDist = xdiff*xdiff + ydiff*ydiff;

	xdiff = (psAStarRoute->finalX << TILE_SHIFT) + TILE_UNITS/2 - tx;
	ydiff = (psAStarRoute->finalY << TILE_SHIFT) + TILE_UNITS/2 - ty;
	nextDist = xdiff*xdiff + ydiff*ydiff;

	if (nextDist < prevDist)
	{
		return TRUE;
	}

	return FALSE;
}

// create a final route from a gateway route
SDWORD fpathGatewayRoute(BASE_OBJECT *psObj, SDWORD routeMode, SDWORD GWTerrain,
						 SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy,
						 MOVE_CONTROL *psMoveCntl)
{
	static SDWORD	linkx, linky, gwx, gwy, asret, matchPoints;
	static ASTAR_ROUTE		sAStarRoute;
	SDWORD			retval = FPR_OK, gwRet, zone;
	static GATEWAY	*psCurrRoute, *psGWRoute, *psLastGW;
	BOOL			bRouting, bFinished;
	static BOOL		bFirstRoute;

	if (routeMode == ASR_NEWROUTE)
	{
		fpathClearIgnore();
		// initialise the move control structures
		psMoveCntl->numPoints = 0;
		sAStarRoute.numPoints = 0;
		bFirstRoute = TRUE;
	}

	// keep trying gateway routes until out of options
	bRouting = TRUE;
	while (bRouting)
	{
		if (routeMode == ASR_NEWROUTE)
		{
			DBP2(("Gateway route - droid %d\n", psObj->id));
			gwRet = gwrAStarRoute(psObj->player, GWTerrain,
								  sx,sy, fx,fy, &psGWRoute);
			switch (gwRet)
			{
			case GWR_OK:
				break;
			case GWR_NEAREST:
				// need to deal with this case for retried routing - only accept this if no previous route?
				if (!bFirstRoute)
				{
					if (psMoveCntl->numPoints > 0)
					{
						DBP2(("   Gateway route nearest - Use previous route\n"));
						retval = FPR_OK;
						goto exit;
					}
					else
					{
						DBP2(("   Gateway route nearest - No points - failed\n"));
						retval = FPR_FAILED;
						goto exit;
					}
				}
				break;
			case GWR_NOZONE:
			case GWR_SAMEZONE:
				// no zone information - try a normal route
				psGWRoute = NULL;
				break;
			case GWR_FAILED:
				DBP2(("   Gateway route failed\n"));
				if ((psObj->type == OBJ_DROID) && vtolDroid((DROID *)psObj))
				{
					// just fail for VTOLs - they can set a direct route
					retval = FPR_FAILED;
					goto exit;
				}
				else
				{
					psGWRoute = NULL;
				}
				break;
			}

			// reset matchPoints so that routing between gateways generated
			// by the previous gateway route can be reused
			matchPoints = 0;
			sAStarRoute.numPoints = 0;
		}
		bFirstRoute = FALSE;

		if (routeMode == ASR_NEWROUTE)
		{
			// if the start of the route is on the first gateway, skip it
			if ((psGWRoute != NULL) && fpathPointInGateway(sx,sy, psGWRoute))
			{
				psGWRoute = psGWRoute->psRoute;
			}

			linkx = sx;
			linky = sy;
			psCurrRoute = psGWRoute;
			psLastGW = NULL;
		}

		// now generate the route
		bRouting = FALSE;
		bFinished = FALSE;
		while (!bFinished)
		{
			if ((psCurrRoute == NULL) ||
				((psCurrRoute->psRoute == NULL) && fpathPointInGateway(fx,fy, psCurrRoute)))
			{
				// last stretch on the route is not to a gatway but to
				// the final route coordinates
				gwx = fx;
				gwy = fy;
				zone = gwGetZone(fx >> TILE_SHIFT, fy >> TILE_SHIFT);
			}
			else
			{
				fpathGatewayCoords(psCurrRoute, &gwx, &gwy);
				zone = psCurrRoute->flags & GWR_ZONE1 ? psCurrRoute->zone1 : psCurrRoute->zone2;
			}

			// only route between the gateways if it wasn't done on a previous route
//			if (!fpathCheckRouteMatch(psMoveCntl, &matchPoints, gwx,gwy))
//			if (1)
			{
//				psMoveCntl->numPoints = (UBYTE)matchPoints;

				DBP2(("   astar route : (%d,%d) -> (%d,%d) zone %d\n",
					linkx>>TILE_SHIFT, linky>>TILE_SHIFT,
					gwx>>TILE_SHIFT, gwy>>TILE_SHIFT, zone));
				fpathSetGatewayBlock(zone, psLastGW, psCurrRoute);
				asret = fpathAStarRoute(routeMode, &sAStarRoute, linkx,linky, gwx,gwy);
				fpathClearGatewayBlock(zone, psLastGW, psCurrRoute);
				if (asret == ASR_PARTIAL)
				{
					// routing hasn't finished yet
					DBP2(("   Reschedule\n"));
					retval = FPR_WAIT;
					goto exit;
				}
/*				else if (asret != ASR_FAILED)
				{
					fpathAppendRoute(psMoveCntl, &sAStarRoute);
					matchPoints = psMoveCntl->numPoints;
				}*/
				routeMode = ASR_NEWROUTE;

				if ((asret == ASR_NEAREST) &&
					actionRouteBlockingPos((DROID *)psObj, sAStarRoute.finalX,sAStarRoute.finalY))
				{
					// found a blocking wall - route to that
					DBP2(("   Got blocking wall\n"));
					retval = FPR_OK;
					goto exit;
				}
				else if ((asret == ASR_NEAREST) && (psGWRoute == NULL))
				{
					// all routing was in one zone - this is as good as it's going to be
					DBP2(("   Nearest route in same zone\n"));
					if (fpathRouteCloser(psMoveCntl, &sAStarRoute, fx,fy))
					{
						psMoveCntl->numPoints = 0;
						fpathAppendRoute(psMoveCntl, &sAStarRoute);
					}
					retval = FPR_OK;
					goto exit;
				}
				else if ((asret == ASR_FAILED) && (psGWRoute == NULL))
				{
					// all routing was in one zone - can't retry
					DBP2(("   Failed route in same zone\n"));
					retval = FPR_FAILED;
					goto exit;
				}
				else if ((asret == ASR_FAILED) ||
						 (asret == ASR_NEAREST))
				{
					// no route found - try ditching this gateway
					// and trying a new gateway route
					DBP2(("   Route failed - ignore gateway/link and reroute\n"));
					if (fpathRouteCloser(psMoveCntl, &sAStarRoute, fx,fy))
					{
						psMoveCntl->numPoints = 0;
						fpathAppendRoute(psMoveCntl, &sAStarRoute);
					}
					fpathBlockGatewayLink(psLastGW, psCurrRoute);
					bRouting = TRUE;
					break;
				}
			}
#ifdef DEBUG_GROUP2
/*			else
			{
				DBP2(("   matched previous route : (%d,%d) -> (%d,%d)\n",
					linkx>>TILE_SHIFT, linky>>TILE_SHIFT,
					gwx>>TILE_SHIFT, gwy>>TILE_SHIFT));
			}*/
#endif

			linkx = gwx;
			linky = gwy;

			psLastGW = psCurrRoute;
			if (psCurrRoute != NULL)
			{
				psCurrRoute = psCurrRoute->psRoute;
			}
			else
			{
				bFinished = TRUE;
			}
		}
	}

	if (fpathRouteCloser(psMoveCntl, &sAStarRoute, fx,fy))
	{
		psMoveCntl->numPoints = 0;
		fpathAppendRoute(psMoveCntl, &sAStarRoute);
	}

exit:
	// reset the routing block flags
	if (retval != FPR_WAIT)
	{
		fpathClearIgnore();
	}

	return retval;
}

/* set pointer for current fpath object - GJ hack */
void fpathSetCurrentObject( BASE_OBJECT *psObj )
{
	g_psObjRoute = psObj;
}

// set the correct blocking tile function
void fpathSetBlockingTile( UBYTE ubPropulsionType )
{
#ifndef TEST_BED
	switch ( ubPropulsionType )
	{
	case HOVER:
		fpathBlockingTile = fpathHoverBlockingTile;
		break;
	case LIFT:
		fpathBlockingTile = fpathLiftBlockingTile;
		break;
	default:
		fpathBlockingTile = fpathGroundBlockingTile;
	}
#else
	fpathBlockingTile = fpathGroundBlockingTile;
#endif
}


// Find a route for an object to a location
FPATH_RETVAL fpathRoute(BASE_OBJECT *psObj, MOVE_CONTROL *psMoveCntl,
						SDWORD tX, SDWORD tY)
{
	SDWORD				startX,startY, targetX,targetY;
	SDWORD				x,y;
	SDWORD				dir, nearestDir, minDist, tileDist;
	FPATH_RETVAL		retVal = FPR_OK;
//	DROID				*psCurr;
	DROID				*psDroid = NULL;
	PROPULSION_STATS	*psPropStats;
	UDWORD				GWTerrain;


	/* set global pointer for object being routed - GJ hack */
	fpathSetCurrentObject( psObj );

	if ((psPartialRouteObj == NULL) ||
		(psPartialRouteObj != psObj))
	{
		targetX = tX;
		targetY = tY;
		startX = (SDWORD)psObj->x;
		startY = (SDWORD)psObj->y;
	}
	else if (psObj->type == OBJ_DROID &&
			 ((DROID *)psObj)->sMove.Status == MOVEWAITROUTE &&
			 (((DROID *)psObj)->sMove.DestinationX != tX ||
			  ((DROID *)psObj)->sMove.DestinationX != tX))
	{
		psPartialRouteObj = NULL;
		targetX = tX;
		targetY = tY;
		startX = (SDWORD)psObj->x;
		startY = (SDWORD)psObj->y;
	}
	else
	{
		// continuing routing for the object
		startX = partialSX;
		startY = partialSY;
		targetX = partialTX;
		targetY = partialTY;
	}

	// don't have to do anything if already there
	if (startX == targetX && startY == targetY)
	{
		// return failed to stop them moving anywhere
// 		debug( LOG_NEVER, "Unit %d: route failed (same pos)\n", psDroid->id );
		return FPR_FAILED;
	}

	// set the correct blocking tile function and gateway terrain flag
	if (psObj->type == OBJ_DROID)
	{
		psDroid = (DROID *)psObj;
		psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
		ASSERT( PTRVALID(psPropStats, sizeof(PROPULSION_STATS)),
			"fpathRoute: invalid propulsion stats pointer" );

		fpathSetBlockingTile( psPropStats->propulsionType );

		/* set gateway terrain flag */
		switch ( psPropStats->propulsionType )
		{
			case HOVER:
				GWTerrain = GWR_TER_ALL;
				break;

			case LIFT:
				GWTerrain = GWR_TER_ALL;
				break;

			default:
				GWTerrain = GWR_TER_LAND;
				break;
		}
	}
	else
	{
		GWTerrain = GWR_TER_LAND;
	}

#ifndef TEST_BED
	// set all the flags for stationary droids
/*	for(psCurr = apsDroidLists[psObj->player]; psCurr; psCurr = psCurr->psNext)
	{
		if (psCurr != (DROID *)psObj &&
			!psCurr->selected &&
			psCurr->sMove.Status == MOVEINACTIVE)
		{
			psTile = mapTile(psCurr->x >> TILE_SHIFT, psCurr->y >> TILE_SHIFT);
			psTile->tileInfoBits |= BITS_FPATHBLOCK;
		}
	}*/
#endif

	if ((psPartialRouteObj == NULL) ||
		(psPartialRouteObj != psObj))
	{
		// check whether the start point of the route
		// is a blocking tile and find an alternative if it is
		if (fpathBlockingTile(startX >> TILE_SHIFT, startY >> TILE_SHIFT))
		{
			// find the nearest non blocking tile to the object
			minDist = SDWORD_MAX;
			nearestDir = NUM_DIR;
			for(dir=0; dir<NUM_DIR; dir++)
			{
				x = (startX>>TILE_SHIFT) + aDirOffset[dir].x;
				y = (startY>>TILE_SHIFT) + aDirOffset[dir].y;
				if (!fpathBlockingTile(x,y))
				{
					tileDist = fpathDistToTile(x,y, startX,startY);
					if (tileDist < minDist)
					{
						minDist = tileDist;
						nearestDir = dir;
					}
				}
			}

			if (nearestDir == NUM_DIR)
			{
				// surrounded by blocking tiles, give up
				retVal = FPR_FAILED;
// 				debug( LOG_NEVER, "Unit %d: route failed (surrouned by blocking)\n", psDroid->id );
				goto exit;
			}
			else
			{
				startX = (((startX>>TILE_SHIFT) + aDirOffset[nearestDir].x) << TILE_SHIFT)
							+ TILE_SHIFT/2;
				startY = (((startY>>TILE_SHIFT) + aDirOffset[nearestDir].y) << TILE_SHIFT)
							+ TILE_SHIFT/2;
			}
		}

		// initialise the raycast - if there is los to the target, no routing necessary
		finalX = targetX & ~TILE_MASK;
		finalX += TILE_UNITS/2;
		finalY = targetY & ~TILE_MASK;
		finalY += TILE_UNITS/2;

		clearX = finalX; clearY = finalY;
		vectorX = startX - finalX;
		vectorY = startY - finalY;
		obstruction = FALSE;

		// cast the ray to find the last clear tile before the obstruction
		rayCast(startX,startY, rayPointsToAngle(startX,startY, finalX, finalY),
				RAY_MAXLEN, fpathEndPointCallback);

		if (!obstruction)
		{
			// no obstructions - trivial route
			fpathSetDirectRoute( psObj, targetX, targetY );
			retVal = FPR_OK;
// 			debug( LOG_NEVER, "Unit %d: trivial route\n", psDroid->id );
			if (psPartialRouteObj != NULL)
			{
// 				debug( LOG_NEVER, "Unit %d: trivial route during multi-frame route\n" );
			}
			goto exit;
		}

		// check whether the end point of the route
		// is a blocking tile and find an alternative if it is
		if (fpathBlockingTile(targetX >> TILE_SHIFT, targetY >> TILE_SHIFT))
		{
			// route to the last clear tile found by the raycast
			targetX = clearX;
			targetY = clearY;
		}

		// see if there is another unit with a usable route
		if (fpathFindRoute((DROID *)psDroid, startX,startY, targetX,targetY))
		{
// 			debug( LOG_NEVER, "Unit %d: found route\n", psDroid->id );
			if (psPartialRouteObj != NULL)
			{
// 				debug( LOG_NEVER, "Unit %d: found route during multi-frame route\n" );
			}
			goto exit;
		}
	}

	ASSERT( startX >= 0 && startX < (SDWORD)mapWidth*TILE_UNITS &&
			startY >= 0 && startY < (SDWORD)mapHeight*TILE_UNITS,
			"fpathRoute: start coords off map" );
	ASSERT( targetX >= 0 && targetX < (SDWORD)mapWidth*TILE_UNITS &&
			targetY >= 0 && targetY < (SDWORD)mapHeight*TILE_UNITS,
			"fpathRoute: target coords off map" );
	ASSERT( fpathBlockingTile == fpathGroundBlockingTile ||
			fpathBlockingTile == fpathHoverBlockingTile ||
			fpathBlockingTile == fpathLiftBlockingTile,
			"fpathRoute: invalid blocking function" );

	if (astarInner > FPATH_LOOP_LIMIT)
	{
		if (psPartialRouteObj == psObj)
		{
			retVal = FPR_WAIT;
			goto exit;
		}
		else
		{
// 			debug( LOG_NEVER, "Unit %d: reschedule\n" );
			retVal = FPR_RESCHEDULE;
			goto exit;
		}
	}
	else if ( ((psPartialRouteObj != NULL) &&
			   (psPartialRouteObj != psObj)) ||
			  ((psPartialRouteObj != psObj) &&
			   (psNextRouteDroid != NULL) &&
			   (psNextRouteDroid != (DROID *)psObj)) )
	{
		retVal = FPR_RESCHEDULE;
		goto exit;
	}

// 	debug( LOG_NEVER, "Unit %d: ", psObj->id );
	if (psPartialRouteObj == NULL)
	{
		retVal = fpathGatewayRoute(psObj, ASR_NEWROUTE, GWTerrain,
						startX,startY, targetX,targetY, psMoveCntl);
	}
	else
	{
//		DBPRINTF(("Partial Route: %d\n", psDroid->id));
		psPartialRouteObj = NULL;
		retVal = fpathGatewayRoute(psObj, ASR_CONTINUE, GWTerrain,
						startX,startY, targetX,targetY, psMoveCntl);
	}
	if (retVal == FPR_WAIT)
	{
		psPartialRouteObj = psObj;
		lastPartialFrame = frameGetFrameNumber();
		partialSX = startX;
		partialSY = startY;
		partialTX = targetX;
		partialTY = targetY;
	}
	else if ((retVal == FPR_FAILED) &&
			 (psObj->type == OBJ_DROID) && vtolDroid((DROID *)psObj))
	{
		fpathSetDirectRoute( psObj, targetX, targetY );
		retVal = FPR_OK;
	}

exit:

#ifndef TEST_BED
	// reset all the droid flags
/*	for(psCurr = apsDroidLists[psObj->player]; psCurr; psCurr = psCurr->psNext)
	{
		if (psCurr->sMove.Status == MOVEINACTIVE)
		{
			psTile = mapTile(psCurr->x >> TILE_SHIFT, psCurr->y >> TILE_SHIFT);
			psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
		}
	}*/
#endif

	// reset the blocking tile function
	fpathBlockingTile = fpathGroundBlockingTile;

	/* reset global pointer for object being routed */
	fpathSetCurrentObject( NULL );

#if defined(JOHN) && defined(DEBUG)
	{
		MAPTILE				*psTile;

		psTile = psMapTiles;
		for(x=0; x<(SDWORD)(mapWidth*mapHeight); x+= 1)
		{
			if (psTile->tileInfoBits & BITS_FPATHBLOCK)
			{
				ASSERT( FALSE,"fpathRoute: blocking flags still in the map" );
			}
			psTile += 1;
		}
	}
#endif

#ifdef DEBUG_GROUP1
	{
		SDWORD	pos;

		DBP1(("Waypoints:"));

		for(pos = 0; pos < psMoveCntl->numPoints; pos += 1)
		{
			DBP1(("  (%d,%d)",
				psMoveCntl->asPath[pos].x,
				psMoveCntl->asPath[pos].y));
		}
		DBP1(("\n"));
	}
#endif

	return retVal;
}


// find the first point on the route which has both droids on the same side of it
BOOL fpathFindFirstRoutePoint(MOVE_CONTROL *psMove, SDWORD *pIndex, SDWORD x1,SDWORD y1, SDWORD x2,SDWORD y2)
{
	SDWORD	vx1,vy1, vx2,vy2;

	for(*pIndex = 0; *pIndex < psMove->numPoints; (*pIndex) ++)
	{
		vx1 = x1 - psMove->asPath[ *pIndex ].x;
		vy1 = y1 - psMove->asPath[ *pIndex ].y;
		vx2 = x2 - psMove->asPath[ *pIndex ].x;
		vy2 = y2 - psMove->asPath[ *pIndex ].y;

		// found it if the dot products have the same sign
		if ( (vx1 * vx2 + vy1 * vy2) < 0 )
		{
			return TRUE;
		}
	}

	return FALSE;
}

// See if there is another unit on your side that has a route this unit can use
BOOL fpathFindRoute(DROID *psDroid, SDWORD sX,SDWORD sY, SDWORD tX,SDWORD tY)
{
	FORMATION	*psFormation;
	DROID		*psCurr;
	SDWORD		i, startX,startY, index;

	if (!formationFind(&psFormation, tX,tY))
	{
		return FALSE;
	}

	// now look for a unit in this formation with a route that can be used
	for(psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
	{
		if ((psCurr != psDroid) &&
			(psCurr != (DROID *)psPartialRouteObj) &&
			(psCurr->sMove.psFormation == psFormation) &&
			(psCurr->sMove.numPoints > 0))
		{
			// find the first route point
			if (!fpathFindFirstRoutePoint(&psCurr->sMove, &index, sX,sY, (SDWORD)psCurr->x, (SDWORD)psCurr->y))
			{
				continue;
			}

			// initialise the raycast - if there is los to the start of the route
			startX = (sX & ~TILE_MASK) + TILE_UNITS/2;
			startY = (sY & ~TILE_MASK) + TILE_UNITS/2;
			finalX = (psCurr->sMove.asPath[index].x * TILE_UNITS) + TILE_UNITS/2;
			finalY = (psCurr->sMove.asPath[index].y * TILE_UNITS) + TILE_UNITS/2;

			clearX = finalX; clearY = finalY;
			vectorX = startX - finalX;
			vectorY = startY - finalY;
			obstruction = FALSE;

			// cast the ray to find the last clear tile before the obstruction
			rayCast(startX,startY, rayPointsToAngle(startX,startY, finalX, finalY),
					RAY_MAXLEN, fpathEndPointCallback);

			if (!obstruction)
			{
				// This route is OK, copy it over
				for(i=index; i<psCurr->sMove.numPoints; i++)
				{
					psDroid->sMove.asPath[i] = psCurr->sMove.asPath[i];
				}
				psDroid->sMove.numPoints = psCurr->sMove.numPoints;

				// now see if the route

				return TRUE;
			}
		}
	}


	return FALSE;
}
