682 lines
17 KiB
C
682 lines
17 KiB
C
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2009 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
|
|
*/
|
|
/**
|
|
* @file fpath.c
|
|
*
|
|
* Interface to the routing functions.
|
|
*
|
|
*/
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_thread.h>
|
|
|
|
#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 "action.h"
|
|
#include "formation.h"
|
|
|
|
#include "fpath.h"
|
|
|
|
// If the path finding system is shutdown or not
|
|
static volatile bool fpathQuit = false;
|
|
|
|
/* Beware: Enabling this will cause significant slow-down. */
|
|
#undef DEBUG_MAP
|
|
|
|
/* 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
|
|
|
|
typedef struct _jobNode
|
|
{
|
|
PROPULSION_TYPE propulsion;
|
|
DROID_TYPE droidType;
|
|
int destX, destY;
|
|
int origX, origY;
|
|
UDWORD droidID;
|
|
struct _jobNode *next;
|
|
} PATHJOB;
|
|
|
|
typedef struct _jobDone
|
|
{
|
|
UDWORD droidID; ///< Unique droid ID.
|
|
MOVE_CONTROL sMove; ///< New movement values for the droid.
|
|
struct _jobDone *next; ///< Next result in the list.
|
|
FPATH_RETVAL retval; ///< Result value from path-finding.
|
|
bool done; ///< If the result is finished and ready for use.
|
|
} PATHRESULT;
|
|
|
|
|
|
#define NUM_BASIC 8
|
|
#define NUM_DIR 24
|
|
|
|
// Convert a direction into an offset, spanning two tiles
|
|
static const Vector2i aDirOffset[NUM_DIR] =
|
|
{
|
|
{ 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 },
|
|
{ -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 }, { 2, -1 }, { -2, 0 },
|
|
{ 2, 0 }, { -2, 1 }, { 2, 1 }, { -2, 2 }, { -1, 2 }, { 0, 2 }, { 1, 2 }, { 2, 2 },
|
|
};
|
|
|
|
// threading stuff
|
|
static SDL_Thread *fpathThread = NULL;
|
|
static SDL_sem *fpathSemaphore = NULL;
|
|
static PATHJOB *firstJob = NULL;
|
|
static PATHRESULT *firstResult = NULL;
|
|
|
|
|
|
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult);
|
|
|
|
|
|
/** Find the length of the job queue. Function is thread-safe. */
|
|
static int fpathJobQueueLength(void)
|
|
{
|
|
PATHJOB *psJob;
|
|
int count = 0;
|
|
|
|
SDL_SemWait(fpathSemaphore);
|
|
psJob = firstJob;
|
|
while (psJob)
|
|
{
|
|
count++;
|
|
psJob = psJob->next;
|
|
}
|
|
SDL_SemPost(fpathSemaphore);
|
|
return count;
|
|
}
|
|
|
|
|
|
/** Find the length of the result queue, excepting future results. Function is thread-safe. */
|
|
static int fpathResultQueueLength(void)
|
|
{
|
|
PATHRESULT *psResult;
|
|
int count = 0;
|
|
|
|
SDL_SemWait(fpathSemaphore);
|
|
psResult = firstResult;
|
|
while (psResult)
|
|
{
|
|
if (psResult->done)
|
|
{
|
|
count++;
|
|
}
|
|
psResult = psResult->next;
|
|
}
|
|
SDL_SemPost(fpathSemaphore);
|
|
return count;
|
|
}
|
|
|
|
|
|
/** This runs in a separate thread */
|
|
static int fpathThreadFunc(WZ_DECL_UNUSED void *data)
|
|
{
|
|
SDL_SemWait(fpathSemaphore);
|
|
|
|
while (!fpathQuit)
|
|
{
|
|
PATHJOB job;
|
|
PATHRESULT *psResult, result;
|
|
bool gotWork = false;
|
|
|
|
// Pop the first job off the queue
|
|
if (firstJob)
|
|
{
|
|
PATHJOB *next = firstJob->next;
|
|
|
|
job = *firstJob; // struct copy
|
|
job.next = NULL;
|
|
free(firstJob);
|
|
firstJob = next;
|
|
gotWork = true;
|
|
}
|
|
|
|
if (!gotWork)
|
|
{
|
|
SDL_SemPost(fpathSemaphore);
|
|
SDL_Delay(100);
|
|
SDL_SemWait(fpathSemaphore);
|
|
continue;
|
|
}
|
|
|
|
// Create future result
|
|
psResult = malloc(sizeof(*psResult));
|
|
psResult->done = false;
|
|
psResult->droidID = job.droidID;
|
|
psResult->sMove.asPath = NULL;
|
|
psResult->retval = FPR_FAILED;
|
|
|
|
// Add to beginning of result list
|
|
psResult->next = firstResult;
|
|
firstResult = psResult;
|
|
psResult = NULL; // now hands off
|
|
|
|
SDL_SemPost(fpathSemaphore);
|
|
|
|
// Execute path-finding for this job using our local temporaries
|
|
memset(&result, 0, sizeof(result));
|
|
result.sMove.asPath = NULL;
|
|
fpathExecute(&job, &result);
|
|
|
|
SDL_SemWait(fpathSemaphore);
|
|
|
|
// Find our result again, and replace it with our local temporary
|
|
// We do it this way to avoid a race condition where a droid dies
|
|
// while we are generating its path, and we never free the result.
|
|
psResult = firstResult;
|
|
while (psResult && psResult->droidID != job.droidID)
|
|
{
|
|
psResult = psResult->next;
|
|
}
|
|
if (psResult)
|
|
{
|
|
psResult->sMove = result.sMove; // struct copy
|
|
psResult->retval = result.retval;
|
|
psResult->done = true;
|
|
}
|
|
}
|
|
SDL_SemPost(fpathSemaphore);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// initialise the findpath module
|
|
BOOL fpathInitialise(void)
|
|
{
|
|
// The path system is up
|
|
fpathQuit = false;
|
|
|
|
if (!fpathThread)
|
|
{
|
|
fpathSemaphore = SDL_CreateSemaphore(1);
|
|
fpathThread = SDL_CreateThread(fpathThreadFunc, NULL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void fpathShutdown()
|
|
{
|
|
// Signal the path finding thread to quit
|
|
fpathQuit = true;
|
|
|
|
fpathHardTableReset();
|
|
if (fpathThread)
|
|
{
|
|
SDL_WaitThread(fpathThread, NULL);
|
|
fpathThread = NULL;
|
|
SDL_DestroySemaphore(fpathSemaphore);
|
|
fpathSemaphore = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the pathfinding system.
|
|
* @ingroup pathfinding
|
|
*/
|
|
void fpathUpdate(void)
|
|
{
|
|
// Nothing now
|
|
}
|
|
|
|
|
|
// Check if the map tile at a location blocks a droid
|
|
BOOL fpathBlockingTile(SDWORD x, SDWORD y, PROPULSION_TYPE propulsion)
|
|
{
|
|
MAPTILE *psTile;
|
|
|
|
/* All tiles outside of the map and on map border are blocking. */
|
|
if (x < 1
|
|
|| y < 1
|
|
|| x > mapWidth - 1
|
|
|| y > mapHeight - 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* Check scroll limits (used in campaign to partition the map. */
|
|
if (propulsion != PROPULSION_TYPE_LIFT && (x < scrollMinX + 1 || y < scrollMinY + 1 || x >= scrollMaxX - 1 || y >= scrollMaxY - 1))
|
|
{
|
|
// coords off map - auto blocking tile
|
|
return true;
|
|
}
|
|
|
|
psTile = mapTile(x, y);
|
|
|
|
// Only tall structures are blocking VTOL now
|
|
if (propulsion == PROPULSION_TYPE_LIFT && !TileHasTallStructure(psTile))
|
|
{
|
|
return false;
|
|
}
|
|
else if (propulsion == PROPULSION_TYPE_LIFT)
|
|
{
|
|
return true;
|
|
}
|
|
else if (propulsion == PROPULSION_TYPE_PROPELLOR && terrainType(psTile) != TER_WATER)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (psTile->tileInfoBits & BITS_FPATHBLOCK || (TileIsOccupied(psTile) && !TileIsNotBlocking(psTile))
|
|
|| terrainType(psTile) == TER_CLIFFFACE
|
|
|| (terrainType(psTile) == TER_WATER && propulsion != PROPULSION_TYPE_HOVER && propulsion != PROPULSION_TYPE_PROPELLOR))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/** Calculate the distance to a tile from a point
|
|
*
|
|
* @ingroup pathfinding
|
|
*/
|
|
static inline int fpathDistToTile(int tileX, int tileY, int pointX, int pointY)
|
|
{
|
|
// get the difference in world coords
|
|
int xdiff = world_coord(tileX) - pointX;
|
|
int ydiff = world_coord(tileY) - pointY;
|
|
|
|
if (xdiff == 0 && ydiff == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
return trigIntSqrt(xdiff * xdiff + ydiff * ydiff);
|
|
}
|
|
|
|
|
|
static void fpathSetMove(MOVE_CONTROL *psMoveCntl, SDWORD targetX, SDWORD targetY)
|
|
{
|
|
psMoveCntl->asPath = realloc(psMoveCntl->asPath, sizeof(*psMoveCntl->asPath));
|
|
psMoveCntl->DestinationX = targetX;
|
|
psMoveCntl->DestinationY = targetY;
|
|
psMoveCntl->numPoints = 1;
|
|
psMoveCntl->asPath[0].x = map_coord(targetX);
|
|
psMoveCntl->asPath[0].y = map_coord(targetY);
|
|
}
|
|
|
|
|
|
void fpathSetDirectRoute(DROID *psDroid, SDWORD targetX, SDWORD targetY)
|
|
{
|
|
fpathSetMove(&psDroid->sMove, targetX, targetY);
|
|
}
|
|
|
|
|
|
void fpathRemoveDroidData(int id)
|
|
{
|
|
PATHJOB *psJob;
|
|
PATHJOB *psPrevJob = NULL;
|
|
PATHRESULT *psResult;
|
|
PATHRESULT *psPrevResult = NULL;
|
|
|
|
SDL_SemWait(fpathSemaphore);
|
|
|
|
psJob = firstJob;
|
|
psResult = firstResult;
|
|
|
|
while (psJob)
|
|
{
|
|
if (psJob->droidID == id)
|
|
{
|
|
if (psPrevJob)
|
|
{
|
|
psPrevJob->next = psJob->next;
|
|
free(psJob);
|
|
psJob = psPrevJob->next;
|
|
}
|
|
else
|
|
{
|
|
firstJob = psJob->next;
|
|
free(psJob);
|
|
psJob = firstJob;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
psPrevJob = psJob;
|
|
psJob = psJob->next;
|
|
}
|
|
}
|
|
while (psResult)
|
|
{
|
|
if (psResult->droidID == id)
|
|
{
|
|
if (psPrevResult)
|
|
{
|
|
psPrevResult->next = psResult->next;
|
|
free(psResult->sMove.asPath);
|
|
free(psResult);
|
|
psResult = psPrevResult->next;
|
|
}
|
|
else
|
|
{
|
|
firstResult = psResult->next;
|
|
free(psResult->sMove.asPath);
|
|
free(psResult);
|
|
psResult = firstResult;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
psPrevResult = psResult;
|
|
psResult = psResult->next;
|
|
}
|
|
}
|
|
SDL_SemPost(fpathSemaphore);
|
|
}
|
|
|
|
|
|
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType, DROID_TYPE droidType)
|
|
{
|
|
PATHJOB *psJob = NULL;
|
|
|
|
objTrace(id, "called(,%d,%d,%d,%d,%d,,)", id, startX, startY, tX, tY);
|
|
|
|
// don't have to do anything if already there
|
|
if (startX == tX && startY == tY)
|
|
{
|
|
// return failed to stop them moving anywhere
|
|
objTrace(id, "Tried to move nowhere");
|
|
return FPR_FAILED;
|
|
}
|
|
|
|
// Check if waiting for a result
|
|
if (psMove->Status == MOVEWAITROUTE)
|
|
{
|
|
PATHRESULT *psPrev = NULL, *psNext = firstResult;
|
|
|
|
objTrace(id, "Checking if we have a path yet");
|
|
SDL_SemWait(fpathSemaphore);
|
|
|
|
while (psNext)
|
|
{
|
|
if (psNext->droidID == id && psNext->done)
|
|
{
|
|
FPATH_RETVAL retval;
|
|
|
|
ASSERT(psNext->retval != FPR_OK || psNext->sMove.asPath, "Ok result but no path in list");
|
|
|
|
// Remove it from the result list
|
|
if (psPrev)
|
|
{
|
|
psPrev->next = psNext->next;
|
|
}
|
|
else
|
|
{
|
|
firstResult = psNext->next;
|
|
}
|
|
|
|
// Copy over select fields - preserve others
|
|
psMove->DestinationX = psNext->sMove.DestinationX;
|
|
psMove->DestinationY = psNext->sMove.DestinationY;
|
|
psMove->numPoints = psNext->sMove.numPoints;
|
|
psMove->Position = 0;
|
|
psMove->Status = MOVEROUTE;
|
|
if (psMove->asPath)
|
|
{
|
|
free(psMove->asPath);
|
|
}
|
|
psMove->asPath = psNext->sMove.asPath;
|
|
retval = psNext->retval;
|
|
ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy");
|
|
free(psNext);
|
|
SDL_SemPost(fpathSemaphore);
|
|
objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", (int)psMove->DestinationX,
|
|
(int)psMove->DestinationY, (int)psMove->numPoints, (int)retval);
|
|
return retval;
|
|
}
|
|
psPrev = psNext;
|
|
psNext = psNext->next;
|
|
}
|
|
SDL_SemPost(fpathSemaphore);
|
|
objTrace(id, "No path yet. Waiting.");
|
|
return FPR_WAIT; // keep waiting
|
|
}
|
|
|
|
// We were not waiting for a result, and found no trivial path, so create new job and start waiting
|
|
psJob = malloc(sizeof(*psJob));
|
|
ASSERT(psJob, "Out of memory");
|
|
if (!psJob)
|
|
{
|
|
return FPR_FAILED;
|
|
}
|
|
psJob->origX = startX;
|
|
psJob->origY = startY;
|
|
psJob->droidID = id;
|
|
psJob->destX = tX;
|
|
psJob->destY = tY;
|
|
psJob->next = NULL;
|
|
psJob->droidType = droidType;
|
|
psJob->propulsion = propulsionType;
|
|
|
|
// Clear any results or jobs waiting already. It is a vital assumption that there is only one
|
|
// job or result for each droid in the system at any time.
|
|
fpathRemoveDroidData(id);
|
|
|
|
SDL_SemWait(fpathSemaphore);
|
|
|
|
// Add to end of list
|
|
if (!firstJob)
|
|
{
|
|
firstJob = psJob;
|
|
}
|
|
else
|
|
{
|
|
PATHJOB *psNext = firstJob;
|
|
|
|
while (psNext->next != NULL)
|
|
{
|
|
psNext = psNext->next;
|
|
}
|
|
|
|
psNext->next = psJob;
|
|
}
|
|
|
|
SDL_SemPost(fpathSemaphore);
|
|
|
|
objTrace(id, "Queued up a path-finding request to (%d, %d)", tX, tY);
|
|
return FPR_WAIT; // wait while polling result queue
|
|
}
|
|
|
|
|
|
// Find a route for an DROID to a location in world coordinates
|
|
FPATH_RETVAL fpathDroidRoute(DROID* psDroid, SDWORD tX, SDWORD tY)
|
|
{
|
|
PROPULSION_STATS *psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
|
|
|
|
ASSERT(psPropStats != NULL, "invalid propulsion stats pointer");
|
|
ASSERT(psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!");
|
|
if (psDroid->type != OBJ_DROID || !psPropStats)
|
|
{
|
|
return FPR_FAILED;
|
|
}
|
|
// check whether the end point of the route
|
|
// is a blocking tile and find an alternative if it is
|
|
if (psDroid->sMove.Status != MOVEWAITROUTE && fpathBlockingTile(map_coord(tX), map_coord(tY), psPropStats->propulsionType))
|
|
{
|
|
// find the nearest non blocking tile to the DROID
|
|
int minDist = SDWORD_MAX;
|
|
int nearestDir = NUM_DIR;
|
|
int dir;
|
|
|
|
objTrace(psDroid->id, "BLOCKED(%d,%d) - trying workaround", map_coord(tX), map_coord(tY));
|
|
for (dir = 0; dir < NUM_DIR; dir++)
|
|
{
|
|
int x = map_coord(tX) + aDirOffset[dir].x;
|
|
int y = map_coord(tY) + aDirOffset[dir].y;
|
|
|
|
if (tileOnMap(x, y) && !fpathBlockingTile(x, y, psPropStats->propulsionType))
|
|
{
|
|
// pick the adjacent tile closest to our starting point
|
|
int tileDist = fpathDistToTile(x, y, psDroid->pos.x, psDroid->pos.y);
|
|
|
|
if (tileDist < minDist)
|
|
{
|
|
minDist = tileDist;
|
|
nearestDir = dir;
|
|
}
|
|
}
|
|
|
|
if (dir == NUM_BASIC - 1 && nearestDir != NUM_DIR)
|
|
{
|
|
break; // found a solution without checking at greater distance
|
|
}
|
|
}
|
|
|
|
if (nearestDir == NUM_DIR)
|
|
{
|
|
// surrounded by blocking tiles, give up
|
|
objTrace(psDroid->id, "route to (%d, %d) failed - target blocked", map_coord(tX), map_coord(tY));
|
|
return FPR_FAILED;
|
|
}
|
|
else
|
|
{
|
|
tX = world_coord(map_coord(tX) + aDirOffset[nearestDir].x) + TILE_SHIFT / 2;
|
|
tY = world_coord(map_coord(tY) + aDirOffset[nearestDir].y) + TILE_SHIFT / 2;
|
|
objTrace(psDroid->id, "Workaround found at (%d, %d)", map_coord(tX), map_coord(tY));
|
|
}
|
|
}
|
|
return fpathRoute(&psDroid->sMove, psDroid->id, psDroid->pos.x, psDroid->pos.y, tX, tY, psPropStats->propulsionType, psDroid->droidType);
|
|
}
|
|
|
|
// Run only from path thread
|
|
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult)
|
|
{
|
|
FPATH_RETVAL retval = fpathAStarRoute(&psResult->sMove, psJob->origX, psJob->origY, psJob->destX, psJob->destY, psJob->propulsion);
|
|
|
|
ASSERT(retval != ASR_OK || psResult->sMove.asPath, "Ok result but no path in result");
|
|
switch (retval)
|
|
{
|
|
case ASR_NEAREST:
|
|
objTrace(psJob->droidID, "** Nearest route **");
|
|
psResult->retval = FPR_OK;
|
|
break;
|
|
case ASR_FAILED:
|
|
objTrace(psJob->droidID, "** Failed route **");
|
|
// Is this really a good idea? Was in original code.
|
|
if (psJob->propulsion == PROPULSION_TYPE_LIFT && psJob->droidType != DROID_TRANSPORTER)
|
|
{
|
|
objTrace(psJob->droidID, "Doing fallback for non-transport VTOL");
|
|
fpathSetMove(&psResult->sMove, psJob->destX, psJob->destY);
|
|
psResult->retval = FPR_OK;
|
|
}
|
|
else
|
|
{
|
|
psResult->retval = FPR_FAILED;
|
|
}
|
|
break;
|
|
default:
|
|
objTrace(psJob->droidID, "Got route of length %d", psResult->sMove.numPoints);
|
|
psResult->retval = FPR_OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void fpathTest(int x, int y, int x2, int y2)
|
|
{
|
|
MOVE_CONTROL sMove;
|
|
FPATH_RETVAL r;
|
|
int i;
|
|
|
|
// On non-debug builds prevent warnings about defining but not using fpathJobQueueLength
|
|
(void)fpathJobQueueLength;
|
|
|
|
/* Check initial state */
|
|
assert(fpathThread != NULL);
|
|
assert(fpathSemaphore != NULL);
|
|
assert(firstJob == NULL);
|
|
assert(firstResult == NULL);
|
|
assert(fpathJobQueueLength() == 0);
|
|
assert(fpathResultQueueLength() == 0);
|
|
fpathRemoveDroidData(0); // should not crash
|
|
|
|
/* This should not leak memory */
|
|
sMove.asPath = NULL;
|
|
for (i = 0; i < 100; i++) fpathSetMove(&sMove, 1, 1);
|
|
free(sMove.asPath);
|
|
sMove.asPath = NULL;
|
|
|
|
/* Test one path */
|
|
sMove.Status = MOVEINACTIVE;
|
|
r = fpathRoute(&sMove, 1, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
|
|
assert(r == FPR_WAIT);
|
|
sMove.Status = MOVEWAITROUTE;
|
|
assert(fpathJobQueueLength() == 1 || fpathResultQueueLength() == 1);
|
|
fpathRemoveDroidData(2); // should not crash, nor remove our path
|
|
assert(fpathJobQueueLength() == 1 || fpathResultQueueLength() == 1);
|
|
while (fpathResultQueueLength() == 0) SDL_Delay(10);
|
|
assert(fpathJobQueueLength() == 0);
|
|
assert(fpathResultQueueLength() == 1);
|
|
r = fpathRoute(&sMove, 1, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
|
|
assert(r == FPR_OK);
|
|
assert(sMove.numPoints > 0 && sMove.asPath);
|
|
assert(sMove.asPath[sMove.numPoints - 1].x == map_coord(x2));
|
|
assert(sMove.asPath[sMove.numPoints - 1].y == map_coord(y2));
|
|
assert(fpathResultQueueLength() == 0);
|
|
|
|
/* Let one hundred paths flower! */
|
|
sMove.Status = MOVEINACTIVE;
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
|
|
assert(r == FPR_WAIT);
|
|
}
|
|
while (fpathResultQueueLength() != 100) SDL_Delay(10);
|
|
assert(fpathJobQueueLength() == 0);
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
sMove.Status = MOVEWAITROUTE;
|
|
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
|
|
assert(r == FPR_OK);
|
|
assert(sMove.numPoints > 0 && sMove.asPath);
|
|
assert(sMove.asPath[sMove.numPoints - 1].x == map_coord(x2));
|
|
assert(sMove.asPath[sMove.numPoints - 1].y == map_coord(y2));
|
|
}
|
|
assert(fpathResultQueueLength() == 0);
|
|
|
|
/* Kill a hundred flowers */
|
|
sMove.Status = MOVEINACTIVE;
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
|
|
assert(r == FPR_WAIT);
|
|
}
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
fpathRemoveDroidData(i);
|
|
}
|
|
assert(fpathJobQueueLength() == 0);
|
|
assert(fpathResultQueueLength() == 0);
|
|
assert(firstJob == NULL);
|
|
assert(firstResult == NULL);
|
|
}
|