692 lines
22 KiB
C++
692 lines
22 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 Warzone 2100 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 "lib/framework/frame.h"
|
|
#include "lib/framework/crc.h"
|
|
#include "lib/netplay/netplay.h"
|
|
|
|
#include "lib/framework/wzapp.h"
|
|
|
|
#include "objects.h"
|
|
#include "map.h"
|
|
#include "raycast.h"
|
|
#include "geometry.h"
|
|
#include "hci.h"
|
|
#include "order.h"
|
|
#include "multiplay.h"
|
|
#include "astar.h"
|
|
#include "action.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
|
|
|
|
struct PATHRESULT
|
|
{
|
|
UDWORD droidID; ///< Unique droid ID.
|
|
MOVE_CONTROL sMove; ///< New movement values for the droid.
|
|
FPATH_RETVAL retval; ///< Result value from path-finding.
|
|
Vector2i originalDest; ///< Used to check if the pathfinding job is to the right destination.
|
|
};
|
|
|
|
|
|
// threading stuff
|
|
static WZ_THREAD *fpathThread = NULL;
|
|
static WZ_MUTEX *fpathMutex = NULL;
|
|
static WZ_SEMAPHORE *fpathSemaphore = NULL;
|
|
static std::list<PATHJOB> pathJobs;
|
|
static std::list<PATHRESULT> pathResults;
|
|
|
|
static bool waitingForResult = false;
|
|
static uint32_t waitingForResultId;
|
|
static WZ_SEMAPHORE *waitingForResultSemaphore = NULL;
|
|
|
|
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult);
|
|
|
|
|
|
/** This runs in a separate thread */
|
|
static int fpathThreadFunc(void *)
|
|
{
|
|
wzMutexLock(fpathMutex);
|
|
|
|
while (!fpathQuit)
|
|
{
|
|
if (pathJobs.empty())
|
|
{
|
|
ASSERT(!waitingForResult, "Waiting for a result (id %u) that doesn't exist.", waitingForResultId);
|
|
wzMutexUnlock(fpathMutex);
|
|
wzSemaphoreWait(fpathSemaphore); // Go to sleep until needed.
|
|
wzMutexLock(fpathMutex);
|
|
continue;
|
|
}
|
|
|
|
// Copy the first job from the queue. Don't pop yet, since the main thread may want to set .deleted = true.
|
|
PATHJOB job = pathJobs.front();
|
|
|
|
wzMutexUnlock(fpathMutex);
|
|
|
|
// Execute path-finding for this job using our local temporaries
|
|
PATHRESULT result;
|
|
result.droidID = job.droidID;
|
|
memset(&result.sMove, 0, sizeof(result.sMove));
|
|
result.retval = FPR_FAILED;
|
|
result.originalDest = Vector2i(job.destX, job.destY);
|
|
|
|
fpathExecute(&job, &result);
|
|
|
|
wzMutexLock(fpathMutex);
|
|
|
|
ASSERT(pathJobs.front().droidID == job.droidID, "Bug"); // The front of pathJobs may have .deleted set to true, but should not otherwise have been modified or deleted.
|
|
if (!pathJobs.front().deleted)
|
|
{
|
|
pathResults.push_back(result);
|
|
}
|
|
pathJobs.pop_front();
|
|
|
|
// Unblock the main thread, if it was waiting for this particular result.
|
|
if (waitingForResult && waitingForResultId == job.droidID)
|
|
{
|
|
waitingForResult = false;
|
|
objTrace(waitingForResultId, "These are the droids you are looking for.");
|
|
wzSemaphorePost(waitingForResultSemaphore);
|
|
}
|
|
}
|
|
wzMutexUnlock(fpathMutex);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// initialise the findpath module
|
|
bool fpathInitialise(void)
|
|
{
|
|
// The path system is up
|
|
fpathQuit = false;
|
|
|
|
if (!fpathThread)
|
|
{
|
|
fpathMutex = wzMutexCreate();
|
|
fpathSemaphore = wzSemaphoreCreate(0);
|
|
waitingForResultSemaphore = wzSemaphoreCreate(0);
|
|
fpathThread = wzThreadCreate(fpathThreadFunc, NULL);
|
|
wzThreadStart(fpathThread);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void fpathShutdown()
|
|
{
|
|
// Signal the path finding thread to quit
|
|
fpathQuit = true;
|
|
wzSemaphorePost(fpathSemaphore); // Wake up thread.
|
|
|
|
if (fpathThread)
|
|
{
|
|
wzThreadJoin(fpathThread);
|
|
fpathThread = NULL;
|
|
wzMutexDestroy(fpathMutex);
|
|
fpathMutex = NULL;
|
|
wzSemaphoreDestroy(fpathSemaphore);
|
|
fpathSemaphore = NULL;
|
|
wzSemaphoreDestroy(waitingForResultSemaphore);
|
|
waitingForResultSemaphore = NULL;
|
|
}
|
|
fpathHardTableReset();
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the pathfinding system.
|
|
* @ingroup pathfinding
|
|
*/
|
|
void fpathUpdate(void)
|
|
{
|
|
// Nothing now
|
|
}
|
|
|
|
|
|
bool fpathIsEquivalentBlocking(PROPULSION_TYPE propulsion1, int player1, FPATH_MOVETYPE moveType1,
|
|
PROPULSION_TYPE propulsion2, int player2, FPATH_MOVETYPE moveType2)
|
|
{
|
|
int domain1, domain2;
|
|
switch (propulsion1)
|
|
{
|
|
default: domain1 = 0; break; // Land
|
|
case PROPULSION_TYPE_LIFT: domain1 = 1; break; // Air
|
|
case PROPULSION_TYPE_PROPELLOR: domain1 = 2; break; // Water
|
|
case PROPULSION_TYPE_HOVER: domain1 = 3; break; // Land and water
|
|
}
|
|
switch (propulsion2)
|
|
{
|
|
default: domain2 = 0; break; // Land
|
|
case PROPULSION_TYPE_LIFT: domain2 = 1; break; // Air
|
|
case PROPULSION_TYPE_PROPELLOR: domain2 = 2; break; // Water
|
|
case PROPULSION_TYPE_HOVER: domain2 = 3; break; // Land and water
|
|
}
|
|
|
|
if (domain1 != domain2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (domain1 == 1)
|
|
{
|
|
return true; // Air units ignore move type and player.
|
|
}
|
|
|
|
if (moveType1 != moveType2 || player1 != player2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint8_t prop2bits(PROPULSION_TYPE propulsion)
|
|
{
|
|
uint8_t bits;
|
|
|
|
switch (propulsion)
|
|
{
|
|
case PROPULSION_TYPE_LIFT:
|
|
bits = AIR_BLOCKED;
|
|
break;
|
|
case PROPULSION_TYPE_HOVER:
|
|
bits = FEATURE_BLOCKED;
|
|
break;
|
|
case PROPULSION_TYPE_PROPELLOR:
|
|
bits = FEATURE_BLOCKED | LAND_BLOCKED;
|
|
break;
|
|
default:
|
|
bits = FEATURE_BLOCKED | WATER_BLOCKED;
|
|
break;
|
|
}
|
|
return bits;
|
|
}
|
|
|
|
// Check if the map tile at a location blocks a droid
|
|
bool fpathBaseBlockingTile(SDWORD x, SDWORD y, PROPULSION_TYPE propulsion, int mapIndex, FPATH_MOVETYPE moveType)
|
|
{
|
|
/* 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;
|
|
}
|
|
unsigned aux = auxTile(x, y, mapIndex);
|
|
|
|
int auxMask = 0;
|
|
switch (moveType)
|
|
{
|
|
case FMT_MOVE: auxMask = AUXBITS_NONPASSABLE; break; // do not wish to shoot our way through enemy buildings, but want to go through friendly gates (without shooting them)
|
|
case FMT_ATTACK: auxMask = AUXBITS_OUR_BUILDING; break; // move blocked by friendly building, assuming we do not want to shoot it up en route
|
|
case FMT_BLOCK: auxMask = AUXBITS_BLOCKING; break; // Do not wish to tunnel through closed gates or buildings.
|
|
}
|
|
|
|
unsigned unitbits = prop2bits(propulsion); // TODO - cache prop2bits to psDroid, and pass in instead of propulsion type
|
|
if ((unitbits & FEATURE_BLOCKED) != 0 && (aux & auxMask) != 0)
|
|
{
|
|
return true; // move blocked by building, and we cannot or do not want to shoot our way through anything
|
|
}
|
|
|
|
// the MAX hack below is because blockTile() range does not include player-specific versions...
|
|
return (blockTile(x, y, MAX(0, mapIndex - MAX_PLAYERS)) & unitbits) != 0; // finally check if move is blocked by propulsion related factors
|
|
}
|
|
|
|
bool fpathDroidBlockingTile(DROID *psDroid, int x, int y, FPATH_MOVETYPE moveType)
|
|
{
|
|
return fpathBaseBlockingTile(x, y, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType);
|
|
}
|
|
|
|
// Check if the map tile at a location blocks a droid
|
|
bool fpathBlockingTile(SDWORD x, SDWORD y, PROPULSION_TYPE propulsion)
|
|
{
|
|
return fpathBaseBlockingTile(x, y, propulsion, 0, FMT_BLOCK); // with FMT_BLOCK, it is irrelevant which player is passed in
|
|
}
|
|
|
|
|
|
// Returns the closest non-blocking tile to pos, or returns pos if no non-blocking tiles are present within a 2 tile distance.
|
|
static Position findNonblockingPosition(Position pos, PROPULSION_TYPE propulsion, int player = 0, FPATH_MOVETYPE moveType = FMT_BLOCK)
|
|
{
|
|
Vector2i centreTile = map_coord(removeZ(pos));
|
|
if (!fpathBaseBlockingTile(centreTile.x, centreTile.y, propulsion, player, moveType))
|
|
{
|
|
return pos; // Fast case, pos is not on a blocking tile.
|
|
}
|
|
|
|
Vector2i bestTile = centreTile;
|
|
int bestDistSq = INT32_MAX;
|
|
for (int y = -2; y <= 2; ++y)
|
|
for (int x = -2; x <= 2; ++x)
|
|
{
|
|
Vector2i tile = centreTile + Vector2i(x, y);
|
|
Vector2i diff = world_coord(tile) + Vector2i(TILE_UNITS/2, TILE_UNITS/2) - removeZ(pos);
|
|
int distSq = diff*diff;
|
|
if (distSq < bestDistSq && !fpathBaseBlockingTile(tile.x, tile.y, propulsion, player, moveType))
|
|
{
|
|
bestTile = tile;
|
|
bestDistSq = distSq;
|
|
}
|
|
}
|
|
|
|
// Return point on tile closest to the original pos.
|
|
Vector2i minCoord = world_coord(bestTile);
|
|
Vector2i maxCoord = minCoord + Vector2i(TILE_UNITS - 1, TILE_UNITS - 1);
|
|
return Position(std::min(std::max(pos.x, minCoord.x), maxCoord.x), std::min(std::max(pos.y, minCoord.y), maxCoord.y), pos.z);
|
|
}
|
|
|
|
|
|
|
|
static void fpathSetMove(MOVE_CONTROL *psMoveCntl, SDWORD targetX, SDWORD targetY)
|
|
{
|
|
psMoveCntl->asPath = (Vector2i *)realloc(psMoveCntl->asPath, sizeof(*psMoveCntl->asPath));
|
|
psMoveCntl->destination = Vector2i(targetX, targetY);
|
|
psMoveCntl->numPoints = 1;
|
|
psMoveCntl->asPath[0] = Vector2i(targetX, targetY);
|
|
}
|
|
|
|
|
|
void fpathSetDirectRoute(DROID *psDroid, SDWORD targetX, SDWORD targetY)
|
|
{
|
|
fpathSetMove(&psDroid->sMove, targetX, targetY);
|
|
}
|
|
|
|
|
|
void fpathRemoveDroidData(int id)
|
|
{
|
|
wzMutexLock(fpathMutex);
|
|
|
|
for (std::list<PATHJOB>::iterator psJob = pathJobs.begin(); psJob != pathJobs.end(); ++psJob)
|
|
{
|
|
if (psJob->droidID == id)
|
|
{
|
|
psJob->deleted = true; // Don't delete the job, since job execution order matters, so tell it to throw away the result after executing, instead.
|
|
}
|
|
}
|
|
for (std::list<PATHRESULT>::iterator psResult = pathResults.begin(); psResult != pathResults.end(); )
|
|
{
|
|
if (psResult->droidID == id)
|
|
{
|
|
psResult = pathResults.erase(psResult);
|
|
}
|
|
else
|
|
{
|
|
++psResult;
|
|
}
|
|
}
|
|
|
|
wzMutexUnlock(fpathMutex);
|
|
}
|
|
|
|
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType,
|
|
DROID_TYPE droidType, FPATH_MOVETYPE moveType, int owner, bool acceptNearest, StructureBounds const &dstStructure)
|
|
{
|
|
objTrace(id, "called(*,id=%d,sx=%d,sy=%d,ex=%d,ey=%d,prop=%d,type=%d,move=%d,owner=%d)", id, startX, startY, tX, tY, (int)propulsionType, (int)droidType, (int)moveType, owner);
|
|
|
|
if (!worldOnMap(startX, startY) || !worldOnMap(tX, tY))
|
|
{
|
|
debug(LOG_ERROR, "Droid trying to find path to/from invalid location (%d %d) -> (%d %d).", startX, startY, tX, tY);
|
|
objTrace(id, "Invalid start/end.");
|
|
syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner);
|
|
return FPR_FAILED;
|
|
}
|
|
|
|
// 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");
|
|
syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner);
|
|
return FPR_FAILED;
|
|
}
|
|
|
|
// Check if waiting for a result
|
|
while (psMove->Status == MOVEWAITROUTE)
|
|
{
|
|
objTrace(id, "Checking if we have a path yet");
|
|
wzMutexLock(fpathMutex);
|
|
|
|
for (std::list<PATHRESULT>::iterator psResult = pathResults.begin(); psResult != pathResults.end(); ++psResult)
|
|
{
|
|
if (psResult->droidID != id)
|
|
{
|
|
continue; // Wrong result, try next one.
|
|
}
|
|
|
|
ASSERT(psResult->retval != FPR_OK || psResult->sMove.asPath, "Ok result but no path in list");
|
|
|
|
// Copy over select fields - preserve others
|
|
psMove->destination = psResult->sMove.destination;
|
|
psMove->numPoints = psResult->sMove.numPoints;
|
|
bool correctDestination = tX == psResult->originalDest.x && tY == psResult->originalDest.y;
|
|
psMove->pathIndex = 0;
|
|
psMove->Status = MOVENAVIGATE;
|
|
free(psMove->asPath);
|
|
psMove->asPath = psResult->sMove.asPath;
|
|
FPATH_RETVAL retval = psResult->retval;
|
|
ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy");
|
|
ASSERT(retval != FPR_OK || psMove->numPoints > 0, "Ok result but path empty after copy");
|
|
|
|
// Remove it from the result list
|
|
pathResults.erase(psResult);
|
|
|
|
wzMutexUnlock(fpathMutex);
|
|
|
|
objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", psMove->destination.x, psMove->destination.y, psMove->numPoints, (int)retval);
|
|
syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = %d, path[%d] = %08X->(%d, %d)", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner, retval, psMove->numPoints, ~crcSumVector2i(0, psMove->asPath, psMove->numPoints), psMove->destination.x, psMove->destination.y);
|
|
|
|
if (!correctDestination)
|
|
{
|
|
goto queuePathfinding; // Seems we got the result of an old pathfinding job for this droid, so need to pathfind again.
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
objTrace(id, "No path yet. Waiting.");
|
|
waitingForResult = true;
|
|
waitingForResultId = id;
|
|
wzMutexUnlock(fpathMutex);
|
|
wzSemaphoreWait(waitingForResultSemaphore); // keep waiting
|
|
}
|
|
queuePathfinding:
|
|
|
|
// We were not waiting for a result, and found no trivial path, so create new job and start waiting
|
|
PATHJOB job;
|
|
job.origX = startX;
|
|
job.origY = startY;
|
|
job.droidID = id;
|
|
job.destX = tX;
|
|
job.destY = tY;
|
|
job.dstStructure = dstStructure;
|
|
job.droidType = droidType;
|
|
job.propulsion = propulsionType;
|
|
job.moveType = moveType;
|
|
job.owner = owner;
|
|
job.acceptNearest = acceptNearest;
|
|
job.deleted = false;
|
|
fpathSetBlockingMap(&job);
|
|
|
|
// 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);
|
|
|
|
wzMutexLock(fpathMutex);
|
|
|
|
// Add to end of list
|
|
bool isFirstJob = pathJobs.empty();
|
|
pathJobs.push_back(job);
|
|
if (isFirstJob)
|
|
{
|
|
wzSemaphorePost(fpathSemaphore); // Wake up processing thread.
|
|
}
|
|
|
|
wzMutexUnlock(fpathMutex);
|
|
|
|
objTrace(id, "Queued up a path-finding request to (%d, %d), at least %d items earlier in queue", tX, tY, isFirstJob);
|
|
syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_WAIT", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner);
|
|
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, FPATH_MOVETYPE moveType)
|
|
{
|
|
bool acceptNearest;
|
|
PROPULSION_STATS *psPropStats = getPropulsionStats(psDroid);
|
|
|
|
// override for AI to blast our way through stuff
|
|
if (!isHumanPlayer(psDroid->player) && moveType == FMT_MOVE)
|
|
{
|
|
moveType = (psDroid->asWeaps[0].nStat == 0) ? FMT_MOVE : FMT_ATTACK;
|
|
}
|
|
|
|
ASSERT_OR_RETURN(FPR_FAILED, psPropStats != NULL, "invalid propulsion stats pointer");
|
|
ASSERT_OR_RETURN(FPR_FAILED, psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!");
|
|
|
|
// Check whether the start and end points of the route are blocking tiles and find an alternative if they are.
|
|
Position startPos = psDroid->pos;
|
|
Position endPos = Position(tX, tY, 0);
|
|
StructureBounds dstStructure = getStructureBounds(worldTile(endPos)->psObject);
|
|
startPos = findNonblockingPosition(startPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType);
|
|
if (!dstStructure.valid()) // If there's a structure over the destination, ignore it, otherwise pathfind from somewhere around the obstruction.
|
|
{
|
|
endPos = findNonblockingPosition(endPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType);
|
|
}
|
|
objTrace(psDroid->id, "Want to go to (%d, %d) -> (%d, %d), going (%d, %d) -> (%d, %d)", map_coord(psDroid->pos.x), map_coord(psDroid->pos.y), map_coord(tX), map_coord(tY), map_coord(startPos.x), map_coord(startPos.y), map_coord(endPos.x), map_coord(endPos.y));
|
|
switch (psDroid->order.type)
|
|
{
|
|
case DORDER_BUILD:
|
|
case DORDER_LINEBUILD: // build a number of structures in a row (walls + bridges)
|
|
dstStructure = getStructureBounds(psDroid->order.psStats, psDroid->order.pos, psDroid->order.direction); // Just need to get close enough to build (can be diagonally), do not need to reach the destination tile.
|
|
// Continue, do not break.
|
|
case DORDER_HELPBUILD: // help to build a structure
|
|
case DORDER_DEMOLISH: // demolish a structure
|
|
case DORDER_REPAIR:
|
|
acceptNearest = false;
|
|
break;
|
|
default:
|
|
acceptNearest = true;
|
|
break;
|
|
}
|
|
return fpathRoute(&psDroid->sMove, psDroid->id, startPos.x, startPos.y, endPos.x, endPos.y, psPropStats->propulsionType,
|
|
psDroid->droidType, moveType, psDroid->player, acceptNearest, dstStructure);
|
|
}
|
|
|
|
// Run only from path thread
|
|
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult)
|
|
{
|
|
ASR_RETVAL retval = fpathAStarRoute(&psResult->sMove, psJob);
|
|
|
|
ASSERT(retval != ASR_OK || psResult->sMove.asPath, "Ok result but no path in result");
|
|
ASSERT(retval == ASR_FAILED || psResult->sMove.numPoints > 0, "Ok result but no length of path in result");
|
|
switch (retval)
|
|
{
|
|
case ASR_NEAREST:
|
|
if (psJob->acceptNearest)
|
|
{
|
|
objTrace(psJob->droidID, "** Nearest route -- accepted **");
|
|
psResult->retval = FPR_OK;
|
|
}
|
|
else
|
|
{
|
|
objTrace(psJob->droidID, "** Nearest route -- rejected **");
|
|
psResult->retval = FPR_FAILED;
|
|
}
|
|
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 && psJob->droidType != DROID_SUPERTRANSPORTER))
|
|
{
|
|
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;
|
|
case ASR_OK:
|
|
objTrace(psJob->droidID, "Got route of length %d", psResult->sMove.numPoints);
|
|
psResult->retval = FPR_OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Find the length of the job queue. Function is thread-safe. */
|
|
static int fpathJobQueueLength(void)
|
|
{
|
|
int count = 0;
|
|
|
|
wzMutexLock(fpathMutex);
|
|
count = pathJobs.size(); // O(N) function call for std::list. .empty() is faster, but this function isn't used except in tests.
|
|
wzMutexUnlock(fpathMutex);
|
|
return count;
|
|
}
|
|
|
|
|
|
/** Find the length of the result queue, excepting future results. Function is thread-safe. */
|
|
static int fpathResultQueueLength(void)
|
|
{
|
|
int count = 0;
|
|
|
|
wzMutexLock(fpathMutex);
|
|
count = pathResults.size(); // O(N) function call for std::list. .empty() is faster, but this function isn't used except in tests.
|
|
wzMutexUnlock(fpathMutex);
|
|
return count;
|
|
}
|
|
|
|
|
|
// Only used by fpathTest.
|
|
static FPATH_RETVAL fpathSimpleRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY)
|
|
{
|
|
return fpathRoute(psMove, id, startX, startY, tX, tY, PROPULSION_TYPE_WHEELED, DROID_WEAPON, FMT_BLOCK, 0, true, getStructureBounds((BASE_OBJECT *)NULL));
|
|
}
|
|
|
|
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(fpathMutex != NULL);
|
|
assert(fpathSemaphore != NULL);
|
|
assert(pathJobs.empty());
|
|
assert(pathResults.empty());
|
|
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 = fpathSimpleRoute(&sMove, 1, x, y, x2, y2);
|
|
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) wzYieldCurrentThread();
|
|
assert(fpathJobQueueLength() == 0);
|
|
assert(fpathResultQueueLength() == 1);
|
|
r = fpathSimpleRoute(&sMove, 1, x, y, x2, y2);
|
|
assert(r == FPR_OK);
|
|
assert(sMove.numPoints > 0 && sMove.asPath);
|
|
assert(sMove.asPath[sMove.numPoints - 1].x == x2);
|
|
assert(sMove.asPath[sMove.numPoints - 1].y == y2);
|
|
assert(fpathResultQueueLength() == 0);
|
|
|
|
/* Let one hundred paths flower! */
|
|
sMove.Status = MOVEINACTIVE;
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
r = fpathSimpleRoute(&sMove, i, x, y, x2, y2);
|
|
assert(r == FPR_WAIT);
|
|
}
|
|
while (fpathResultQueueLength() != 100) wzYieldCurrentThread();
|
|
assert(fpathJobQueueLength() == 0);
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
sMove.Status = MOVEWAITROUTE;
|
|
r = fpathSimpleRoute(&sMove, i, x, y, x2, y2);
|
|
assert(r == FPR_OK);
|
|
assert(sMove.numPoints > 0 && sMove.asPath);
|
|
assert(sMove.asPath[sMove.numPoints - 1].x == x2);
|
|
assert(sMove.asPath[sMove.numPoints - 1].y == y2);
|
|
}
|
|
assert(fpathResultQueueLength() == 0);
|
|
|
|
/* Kill a hundred flowers */
|
|
sMove.Status = MOVEINACTIVE;
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
r = fpathSimpleRoute(&sMove, i, x, y, x2, y2);
|
|
assert(r == FPR_WAIT);
|
|
}
|
|
for (i = 1; i <= 100; i++)
|
|
{
|
|
fpathRemoveDroidData(i);
|
|
}
|
|
//assert(pathJobs.empty()); // can now be marked .deleted as well
|
|
assert(pathResults.empty());
|
|
(void)r; // Squelch unused-but-set warning.
|
|
}
|
|
|
|
bool fpathCheck(Position orig, Position dest, PROPULSION_TYPE propulsion)
|
|
{
|
|
// We have to be careful with this check because it is called on
|
|
// load when playing campaign on droids that are on the other
|
|
// map during missions, and those maps are usually larger.
|
|
if (!worldOnMap(removeZ(orig)) || !worldOnMap(removeZ(dest)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MAPTILE *origTile = worldTile(removeZ(findNonblockingPosition(orig, propulsion)));
|
|
MAPTILE *destTile = worldTile(removeZ(findNonblockingPosition(dest, propulsion)));
|
|
|
|
ASSERT(propulsion != PROPULSION_TYPE_NUM, "Bad propulsion type");
|
|
ASSERT(origTile != NULL && destTile != NULL, "Bad tile parameter");
|
|
|
|
switch (propulsion)
|
|
{
|
|
case PROPULSION_TYPE_PROPELLOR:
|
|
case PROPULSION_TYPE_WHEELED:
|
|
case PROPULSION_TYPE_TRACKED:
|
|
case PROPULSION_TYPE_LEGGED:
|
|
case PROPULSION_TYPE_HALF_TRACKED:
|
|
return origTile->limitedContinent == destTile->limitedContinent;
|
|
case PROPULSION_TYPE_HOVER:
|
|
return origTile->hoverContinent == destTile->hoverContinent;
|
|
case PROPULSION_TYPE_LIFT:
|
|
return true; // assume no map uses skyscrapers to isolate areas
|
|
case PROPULSION_TYPE_NUM:
|
|
break;
|
|
}
|
|
return true; // should never get here
|
|
}
|