warzone2100/src/fpath.c

1353 lines
33 KiB
C

/*
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
*
*/
#include "lib/framework/frame.h"
#include "objects.h"
#include "map.h"
#include "raycast.h"
#include "geometry.h"
#include "hci.h"
#include "order.h"
#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 Vector2i 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;
static 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;
}
}
#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;
/* check VTOL limits if not routing */
{
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)) ||
(terrainType(psTile) == TER_CLIFFFACE) ||
(terrainType(psTile) == TER_WATER))
{
return TRUE;
}
return FALSE;
}
// 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)) ||
(terrainType(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( g_psObjRoute != NULL,
"fpathLiftBlockingTile: invalid object pointer" );
ASSERT( psDroid != NULL,
"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 ( terrainType(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(world_coord(x), world_coord(y)) -
(SDWORD) map_Height( g_psObjRoute->pos.x, g_psObjRoute->pos.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;
}
}
// Calculate the distance to a tile from a point
static 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 - map_coord(pointX);
ydiff = tileY - map_coord(pointY);
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
static 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(map_coord(x), map_coord(y)))
{
clearX = (x & ~TILE_MASK) + TILE_UNITS/2;
clearY = (y & ~TILE_MASK) + TILE_UNITS/2;
}
else
{
obstruction = TRUE;
}
return TRUE;
}
/* To plan a path from psObj's current position to 2D position Vector(targetX,targetY)
without taking obstructions in to consideration */
void fpathSetDirectRoute( BASE_OBJECT *psObj, SDWORD targetX, SDWORD targetY )
{
MOVE_CONTROL *psMoveCntl;
ASSERT( psObj != NULL,
"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 = map_coord(targetX);
psMoveCntl->asPath[0].y = map_coord(targetY);
}
}
// append an astar route onto a move-control route
static 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);
// map_coord(psAStarRoute->asPos[ai].x);
// psMoveCntl->MovementList[mi].YCoordinate =
psMoveCntl->asPath[mi].y = (UBYTE)(psAStarRoute->asPos[ai].y);
// map_coord(psAStarRoute->asPos[ai].y);
ai += 1;
mi += 1;
}
// psMoveCntl->MovementList[mi].XCoordinate = -1;
// psMoveCntl->MovementList[mi].YCoordinate = -1;
psMoveCntl->numPoints = (UBYTE)(psMoveCntl->numPoints + ai);
psMoveCntl->DestinationX = world_coord(psAStarRoute->finalX) + TILE_UNITS/2;
psMoveCntl->DestinationY = world_coord(psAStarRoute->finalY) + 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
static BOOL fpathPointInGateway(SDWORD x, SDWORD y, GATEWAY *psGate)
{
x = map_coord(x);
y = map_coord(y);
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
static 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
debug( LOG_MOVEMENT, "Blocking gateways for zones :");
if (psLast != NULL)
{
blockZone = (psLast->flags & GWR_ZONE1) ? psLast->zone1 : psLast->zone2;
debug( LOG_MOVEMENT, " %d", blockZone);
for(tx = psLast->x1 - 1; tx <= psLast->x2 + 1; tx ++)
{
for(ty = psLast->y1 - 1; ty <= psLast->y2 + 1; ty ++)
{
if (!fpathPointInGateway(world_coord(tx), world_coord(ty), 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;
debug( LOG_MOVEMENT, " %d", blockZone);
for(tx = psNext->x1 - 1; tx <= psNext->x2 + 1; tx ++)
{
for(ty = psNext->y1 - 1; ty <= psNext->y2 + 1; ty ++)
{
if (!fpathPointInGateway(world_coord(tx), world_coord(ty), psNext) &&
tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
{
psTile = mapTile(tx, ty);
psTile->tileInfoBits |= BITS_FPATHBLOCK;
}
}
}
}
debug( LOG_MOVEMENT, "\n");
}
// clear blocking flags for all gateways around a zone
static 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(world_coord(tx), world_coord(ty), 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(world_coord(tx), world_coord(ty), psNext) &&
tileOnMap(tx,ty) && gwGetZone(tx,ty) == blockZone)
{
psTile = mapTile(tx, ty);
psTile->tileInfoBits &= ~BITS_FPATHBLOCK;
}
}
}
}
}
// clear the routing ignore flags for the gateways
static 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
static 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;
}
static void fpathBlockGatewayLink(GATEWAY *psLast, GATEWAY *psCurr)
{
SDWORD link, numLinks;
if ((psLast == NULL) && (psCurr != NULL))
{
debug( LOG_MOVEMENT, " Blocking first gateway\n");
psCurr->flags |= GWR_IGNORE;
}
else if ((psCurr == NULL) && (psLast != NULL))
{
debug( LOG_MOVEMENT, " Blocking last gateway\n");
psLast->flags |= GWR_IGNORE;
}
else if ((psLast != NULL) && (psCurr != NULL))
{
debug( LOG_MOVEMENT, " Blocking link between gateways");
numLinks = psLast->zone1Links + psLast->zone2Links;
for(link = 0; link < numLinks; link += 1)
{
if (psLast->psLinks[link].flags & GWRL_CHILD)
{
debug( LOG_MOVEMENT, " 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].flags & GWRL_PARENT)
{
debug( LOG_MOVEMENT, " curr link %d", link);
psCurr->psLinks[link].flags |= GWRL_BLOCKED;
}
}
debug( LOG_MOVEMENT, "\n");
}
}
// check if a new route is closer to the target than the one stored in
// the droid
static 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 = world_coord(psMoveCntl->asPath[psMoveCntl->numPoints - 1].x) + TILE_UNITS/2 - tx;
ydiff = world_coord(psMoveCntl->asPath[psMoveCntl->numPoints - 1].y) + TILE_UNITS/2 - ty;
prevDist = xdiff*xdiff + ydiff*ydiff;
xdiff = world_coord(psAStarRoute->finalX) + TILE_UNITS/2 - tx;
ydiff = world_coord(psAStarRoute->finalY) + TILE_UNITS/2 - ty;
nextDist = xdiff*xdiff + ydiff*ydiff;
if (nextDist < prevDist)
{
return TRUE;
}
return FALSE;
}
// create a final route from a gateway route
static FPATH_RETVAL 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;
FPATH_RETVAL retval = FPR_OK;
SDWORD 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)
{
debug( LOG_MOVEMENT, "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)
{
debug( LOG_MOVEMENT, " Gateway route nearest - Use previous route\n");
retval = FPR_OK;
goto exit;
}
else
{
debug( LOG_MOVEMENT, " 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:
debug( LOG_MOVEMENT, " 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(map_coord(fx), map_coord(fy));
}
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
{
debug( LOG_MOVEMENT, " astar route : (%d,%d) -> (%d,%d) zone %d\n",
map_coord(linkx), map_coord(linky),
map_coord(gwx), map_coord(gwy), 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
debug( LOG_MOVEMENT, " Reschedule\n");
retval = FPR_WAIT;
goto exit;
}
routeMode = ASR_NEWROUTE;
if ((asret == ASR_NEAREST) &&
actionRouteBlockingPos((DROID *)psObj, sAStarRoute.finalX,sAStarRoute.finalY))
{
// found a blocking wall - route to that
debug( LOG_MOVEMENT, " 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
debug( LOG_MOVEMENT, " 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
debug( LOG_MOVEMENT, " 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
debug( LOG_MOVEMENT, " 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;
}
}
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 )
{
switch ( ubPropulsionType )
{
case HOVER:
fpathBlockingTile = fpathHoverBlockingTile;
break;
case LIFT:
fpathBlockingTile = fpathLiftBlockingTile;
break;
default:
fpathBlockingTile = fpathGroundBlockingTile;
}
}
// 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 *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->pos.x;
startY = (SDWORD)psObj->pos.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->pos.x;
startY = (SDWORD)psObj->pos.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( psPropStats != NULL,
"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;
}
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(map_coord(startX), map_coord(startY)))
{
// find the nearest non blocking tile to the object
minDist = SDWORD_MAX;
nearestDir = NUM_DIR;
for(dir=0; dir<NUM_DIR; dir++)
{
x = map_coord(startX) + aDirOffset[dir].x;
y = map_coord(startY) + 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 = world_coord(map_coord(startX) + aDirOffset[nearestDir].x)
+ TILE_SHIFT / 2;
startY = world_coord(map_coord(startY) + aDirOffset[nearestDir].y)
+ 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(map_coord(targetX), map_coord(targetY)))
{
// 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:
// reset the blocking tile function
fpathBlockingTile = fpathGroundBlockingTile;
/* reset global pointer for object being routed */
fpathSetCurrentObject( NULL );
#ifdef 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
return retVal;
}
// find the first point on the route which has both droids on the same side of it
static 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
static 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->pos.x, (SDWORD)psCurr->pos.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;
}