MegaGlest/source/glest_game/ai/route_planner.cpp

1150 lines
36 KiB
C++

// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2001-2008 Martio Figueroa
// 2009-2010 James McCulloch
//
// You can redistribute this code 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
// ==============================================================
#include "route_planner.h"
#include "node_pool.h"
#include "config.h"
#include "map.h"
#include "game.h"
#include "unit.h"
#include "unit_type.h"
#include "leak_dumper.h"
#include <iostream>
#define PF_DEBUG(x) { \
stringstream _ss; \
_ss << x << endl; \
SystemFlags::OutputDebug(SystemFlags::debugPathFinder, _ss.str().c_str()); \
}
//#define PF_DEBUG(x) std::cout << x << endl
#if _GAE_DEBUG_EDITION_
# include "debug_renderer.h"
#endif
#ifndef PATHFINDER_DEBUG_MESSAGES
# define PATHFINDER_DEBUG_MESSAGES 0
#endif
#if PATHFINDER_DEBUG_MESSAGES
# define CONSOLE_LOG(x) {g_console.addLine(x); g_logger.add(x);}
#else
# define CONSOLE_LOG(x) {}
#endif
#define _PROFILE_PATHFINDER()
//#define _PROFILE_PATHFINDER() _PROFILE_FUNCTION()
using namespace Shared::Graphics;
using namespace Shared::Util;
namespace Glest { namespace Game {
#if _GAE_DEBUG_EDITION_
template <typename NodeStorage>
void collectOpenClosed(NodeStorage *ns) {
list<Vec2i> *nodes = ns->getOpenNodes();
list<Vec2i>::iterator nit = nodes->begin();
for ( ; nit != nodes->end(); ++nit )
g_debugRenderer.getPFCallback().openSet.insert(*nit);
delete nodes;
nodes = ns->getClosedNodes();
for ( nit = nodes->begin(); nit != nodes->end(); ++nit )
g_debugRenderer.getPFCallback().closedSet.insert(*nit);
delete nodes;
}
void collectPath(const Unit *unit) {
const UnitPath &path = *unit->getPath();
for (UnitPath::const_iterator pit = path.begin(); pit != path.end(); ++pit)
g_debugRenderer.getPFCallback().pathSet.insert(*pit);
}
void collectWaypointPath(const Unit *unit) {
const WaypointPath &path = *unit->getWaypointPath();
g_debugRenderer.clearWaypoints();
WaypointPath::const_iterator it = path.begin();
for ( ; it != path.end(); ++it) {
Vec3f vert = g_world.getMap()->getTile(Map::toTileCoords(*it))->getVertex();
vert.x += it->x % GameConstants::cellScale + 0.5f;
vert.z += it->y % GameConstants::cellScale + 0.5f;
vert.y += 0.15f;
g_debugRenderer.addWaypoint(vert);
}
}
void clearOpenClosed(const Vec2i &start, const Vec2i &target) {
g_debugRenderer.getPFCallback().pathStart = start;
g_debugRenderer.getPFCallback().pathDest = target;
g_debugRenderer.getPFCallback().pathSet.clear();
g_debugRenderer.getPFCallback().openSet.clear();
g_debugRenderer.getPFCallback().closedSet.clear();
}
#endif // _GAE_DEBUG_EDITION_
// =====================================================
// class RoutePlanner
// =====================================================
// ===================== PUBLIC ========================
/** Construct RoutePlanner object */
RoutePlanner::RoutePlanner(World *world)
: world(world)
, nsgSearchEngine(NULL)
, nodeStore(NULL)
, tSearchEngine(NULL)
, tNodeStore(NULL) {
const int &w = world->getMap()->getW();
const int &h = world->getMap()->getH();
nodeStore = new NodePool(w, h);
GridNeighbours gNeighbours(w, h);
nsgSearchEngine = new SearchEngine<NodePool>(gNeighbours, nodeStore, true);
nsgSearchEngine->setInvalidKey(Vec2i(-1));
nsgSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap);
int numNodes = w * h / 4096 * 250; // 250 nodes for every 16 clusters
tNodeStore = new TransitionNodeStore(numNodes);
TransitionNeighbours tNeighbours;
tSearchEngine = new TransitionSearchEngine(tNeighbours, tNodeStore, true);
tSearchEngine->setInvalidKey(NULL);
}
/** delete SearchEngine objects */
RoutePlanner::~RoutePlanner() {
delete nsgSearchEngine;
delete tSearchEngine;
}
/** Determine legality of a proposed move for a unit. This function is the absolute last say
* in such matters.
* @param unit the unit attempting to move
* @param pos2 the position unit desires to move to
* @return true if move may proceed, false if not legal.
*/
bool RoutePlanner::isLegalMove(Unit *unit, const Vec2i &pos2) const {
assert(world->getMap()->isInside(pos2));
assert(unit->getPos().dist(pos2) < 1.5);
float d = unit->getPos().dist(pos2);
if (d > 1.5 || d < 0.9f) {
// path is invalid, this shouldn't happen... but it does.
static_cast<UnitPath*>(unit->getPath())->clear();
unit->getWaypointPath()->clear();
return false;
}
const Vec2i &pos1 = unit->getPos();
const int &size = unit->getType()->getSize();
const Field &field = unit->getCurrField();
AnnotatedMap *annotatedMap = world->getCartographer()->getMasterMap();
if (!annotatedMap->canOccupy(pos2, size, field)) {
return false; // obstruction in field
}
if ( pos1.x != pos2.x && pos1.y != pos2.y ) {
// Proposed move is diagonal, check if cells either 'side' are free.
// eg.. XXXX
// X1FX The Cells marked 'F' must both be free
// XF2X for the move 1->2 to be legit
// XXXX
Vec2i diag1, diag2;
getDiags(pos1, pos2, size, diag1, diag2);
if (!annotatedMap->canOccupy(diag1, 1, field) || !annotatedMap->canOccupy(diag2, 1, field)
|| !world->getMap()->getCell(diag1)->isFree(field)
|| !world->getMap()->getCell(diag2)->isFree(field)) {
return false; // obstruction, can not move to pos2
}
}
for (int i = pos2.x; i < unit->getType()->getSize() + pos2.x; ++i) {
for (int j = pos2.y; j < unit->getType()->getSize() + pos2.y; ++j) {
if (world->getMap()->getCell(i,j)->getUnit(field) != unit
&& !world->getMap()->isFreeCell(Vec2i(i, j), field)) {
return false; // blocked by another unit
}
}
}
// pos2 is free, and nothing is in the way
return true;
}
string log_prelude(World *w, Unit *u) {
stringstream ss;
ss << "Frame: " << w->getFrameCount() << ", Unit: " << u->getId() << ", ";
return ss.str();
}
ostream& operator<<(ostream &stream, const list<Vec2i> &posList) {
list<Vec2i>::const_iterator itBegin = posList.begin();
list<Vec2i>::const_iterator itEnd = posList.end();
list<Vec2i>::const_iterator it = itBegin;
stream << " [ ";
for ( ; it != itEnd; ++it) {
if (it != itBegin) {
stream << ", ";
}
stream << *it;
}
stream << " ] ";
return stream;
}
TravelState RoutePlanner::findPathToResource(Unit *unit, const Vec2i &targetPos, const ResourceType *rt) {
PF_TRACE();
assert(rt && (rt->getClass() == rcTech || rt->getClass() == rcTileset));
ResourceMapKey mapKey(rt, unit->getCurrField(), unit->getType()->getSize());
PMap1Goal goal(world->getCartographer()->getResourceMap(mapKey));
PF_DEBUG(log_prelude(world, unit) << "findPathToResource() targetPos: " << targetPos
<< ", resource type: " << rt->getName());
return findPathToGoal(unit, goal, targetPos);
}
TravelState RoutePlanner::findPathToStore(Unit *unit, const Unit *store) {
PF_TRACE();
Vec2i target = store->getCenteredPos();
PMap1Goal goal(world->getCartographer()->getStoreMap(store, unit));
PF_DEBUG(log_prelude(world, unit) << "findPathToStore() store: " << store->getId());
return findPathToGoal(unit, goal, target);
}
TravelState RoutePlanner::findPathToBuildSite(Unit *unit, const UnitType *bType,
const Vec2i &bPos, CardinalDir bFacing) {
PF_TRACE();
PatchMap<1> *pMap = world->getCartographer()->getSiteMap(bType, bPos, bFacing, unit);
PMap1Goal goal(pMap);
PF_DEBUG(log_prelude(world, unit) << "findPathToBuildSite() " << "building type: " << bType->getName()
<< ", building pos: " << bPos << ", building facing = " << bFacing);
return findPathToGoal(unit, goal, bPos + Vec2i(bType->getSize() / 2));
}
float RoutePlanner::quickSearch(Field field, int size, const Vec2i &start, const Vec2i &dest) {
PF_TRACE();
// setup search
MoveCost moveCost(field, size, world->getCartographer()->getMasterMap());
DiagonalDistance heuristic(dest);
nsgSearchEngine->setStart(start, heuristic(start));
PosGoal goal(dest);
AStarResult r = nsgSearchEngine->aStar(goal, moveCost, heuristic);
if (r == asrComplete && nsgSearchEngine->getGoalPos() == dest) {
return nsgSearchEngine->getCostTo(dest);
}
return -1.f;
}
HAAStarResult RoutePlanner::setupHierarchicalOpenList(Unit *unit, const Vec2i &target) {
// get Transitions for start cluster
Transitions transitions;
Vec2i startCluster = ClusterMap::cellToCluster(unit->getPos());
ClusterMap *clusterMap = world->getCartographer()->getClusterMap();
clusterMap->getTransitions(startCluster, unit->getCurrField(), transitions);
DiagonalDistance dd(target);
nsgSearchEngine->getNeighbourFunc().setSearchCluster(startCluster);
bool startTrap = true;
// attempt quick path from unit->pos to each transition,
// if successful add transition to open list
AnnotatedMap *aMap = world->getCartographer()->getMasterMap();
aMap->annotateLocal(unit);
for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) {
float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), (*it)->nwPos);
if (cost != -1.f) {
tSearchEngine->setOpen(*it, dd((*it)->nwPos), cost);
startTrap = false;
}
}
aMap->clearLocalAnnotations(unit);
if (startTrap) {
// do again, without annnotations, return TRAPPED if all else goes well
bool locked = true;
for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) {
float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), (*it)->nwPos);
if (cost != -1.f) {
tSearchEngine->setOpen(*it, dd((*it)->nwPos), cost);
locked = false;
}
}
if (locked) {
return hsrFailed;
}
return hsrStartTrap;
}
return hsrComplete;
}
HAAStarResult RoutePlanner::setupHierarchicalSearch(Unit *unit, const Vec2i &dest, TransitionGoal &goalFunc) {
PF_TRACE();
// set-up open list
HAAStarResult res = setupHierarchicalOpenList(unit, dest);
if (res == hsrFailed) {
return hsrFailed;
}
bool startTrap = res == hsrStartTrap;
// transitions to goal
Transitions transitions;
Vec2i cluster = ClusterMap::cellToCluster(dest);
world->getCartographer()->getClusterMap()->getTransitions(cluster, unit->getCurrField(), transitions);
nsgSearchEngine->getNeighbourFunc().setSearchCluster(cluster);
bool goalTrap = true;
// attempt quick path from dest to each transition,
// if successful add transition to goal set
for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) {
float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), dest, (*it)->nwPos);
if (cost != -1.f) {
goalFunc.goalTransitions().insert(*it);
goalTrap = false;
}
}
return startTrap ? hsrStartTrap
: goalTrap ? hsrGoalTrap
: hsrComplete;
}
HAAStarResult RoutePlanner::findWaypointPath(Unit *unit, const Vec2i &dest, WaypointPath &waypoints) {
PF_TRACE();
TransitionGoal goal;
HAAStarResult setupResult = setupHierarchicalSearch(unit, dest, goal);
nsgSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap);
if (setupResult == hsrFailed) {
return hsrFailed;
}
TransitionCost cost(unit->getCurrField(), unit->getType()->getSize());
TransitionHeuristic heuristic(dest);
AStarResult res = tSearchEngine->aStar(goal,cost,heuristic);
if (res == asrComplete) {
WaypointPath &wpPath = *unit->getWaypointPath();
wpPath.clear();
waypoints.push(dest);
const Transition *t = tSearchEngine->getGoalPos();
while (t) {
waypoints.push(t->nwPos);
t = tSearchEngine->getPreviousPos(t);
}
return setupResult;
}
return hsrFailed;
}
/** goal function for search on cluster map when goal position is unexplored */
class UnexploredGoal {
private:
set<const Transition*> potentialGoals;
int team;
Map *map;
public:
UnexploredGoal(Map *map, int teamIndex) : team(teamIndex), map(map) {}
bool operator()(const Transition *t, const float d) const {
Edges::const_iterator it = t->edges.begin();
for ( ; it != t->edges.end(); ++it) {
if (!map->getSurfaceCell(Map::toSurfCoords((*it)->transition()->nwPos))->isExplored(team)) {
// leads to unexplored area, is a potential goal
const_cast<UnexploredGoal*>(this)->potentialGoals.insert(t);
return false;
}
}
// always 'fails', is used to build a list of possible 'avenues of exploration'
return false;
}
/** returns the best 'potential goal' transition found, or null if no potential goals */
const Transition* getBestSeen(const Vec2i &currPos, const Vec2i &target) {
const Transition *best = 0;
float distToBest = Shared::Graphics::infinity;//1e6f//numeric_limits<float>::infinity();
set<const Transition*>::iterator itEnd = potentialGoals.end();
for (set<const Transition*>::iterator it = potentialGoals.begin(); it != itEnd; ++it) {
float myDist = (*it)->nwPos.dist(target) + (*it)->nwPos.dist(currPos);
if (myDist < distToBest) {
best = *it;
distToBest = myDist;
}
}
return best;
}
};
/** cost function for searching cluster map with an unexplored target */
class UnexploredCost {
Field field;
int size;
int team;
Map *map;
public:
UnexploredCost(Field f, int s, int team, Map *map) : field(f), size(s), team(team), map(map) {}
float operator()(const Transition *t, const Transition *t2) const {
Edges::const_iterator it = t->edges.begin();
for ( ; it != t->edges.end(); ++it) {
if ((*it)->transition() == t2) {
break;
}
}
assert(it != t->edges.end());
if ((*it)->maxClear() >= size
&& map->getSurfaceCell(Map::toSurfCoords((*it)->transition()->nwPos))->isExplored(team)) {
return (*it)->cost(size);
}
return -1.f;
}
};
HAAStarResult RoutePlanner::findWaypointPathUnExplored(Unit *unit, const Vec2i &dest, WaypointPath &waypoints) {
// set-up open list
HAAStarResult res = setupHierarchicalOpenList(unit, dest);
nsgSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap);
if (res == hsrFailed) {
return hsrFailed;
}
bool startTrap = res == hsrStartTrap;
UnexploredGoal goal(world->getMap(), unit->getTeam());
UnexploredCost cost(unit->getCurrField(), unit->getType()->getSize(), unit->getTeam(), world->getMap());
TransitionHeuristic heuristic(dest);
tSearchEngine->aStar(goal, cost, heuristic);
const Transition *t = goal.getBestSeen(unit->getPos(), dest);
if (!t) {
return hsrFailed;
}
WaypointPath &wpPath = *unit->getWaypointPath();
wpPath.clear();
while (t) {
waypoints.push(t->nwPos);
t = tSearchEngine->getPreviousPos(t);
}
return res; // return setup res (in case of start trap)
}
/** refine waypoint path, extend low level path to next waypoint.
* @return true if successful, in which case waypoint will have been popped.
* false on failure, in which case waypoint will not be popped. */
bool RoutePlanner::refinePath(Unit *unit) {
PF_TRACE();
WaypointPath &wpPath = *unit->getWaypointPath();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
assert(!wpPath.empty());
const Vec2i &startPos = path.empty() ? unit->getPos() : path.back();
const Vec2i &destPos = wpPath.front();
AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit);
MoveCost cost(unit, aMap);
DiagonalDistance dd(destPos);
PosGoal posGoal(destPos);
nsgSearchEngine->setStart(startPos, dd(startPos));
AStarResult res = nsgSearchEngine->aStar(posGoal, cost, dd);
if (res != asrComplete) {
return false;
}
// IF_DEBUG_EDITION( collectOpenClosed<NodePool>(nsgSearchEngine->getStorage()); )
// extract path
Vec2i pos = nsgSearchEngine->getGoalPos();
assert(pos == destPos);
list<Vec2i>::iterator it = path.end();
while (pos.x != -1) {
it = path.insert(it, pos);
pos = nsgSearchEngine->getPreviousPos(pos);
}
// erase start point (already on path or is start pos)
it = path.erase(it);
// pop waypoint
wpPath.pop();
return true;
}
#undef max
void RoutePlanner::smoothPath(Unit *unit) {
PF_TRACE();
UnitPathInterface *path = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(path);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
if (advPath->size() < 3) {
return;
}
AnnotatedMap* const &aMap = world->getCartographer()->getMasterMap();
int min_x = 1 << 17,
max_x = -1,
min_y = 1 << 17,
max_y = -1;
set<Vec2i> onPath;
UnitPath::iterator it = advPath->begin();
for ( ; it != advPath->end(); ++it) {
if (it->x < min_x) min_x = it->x;
if (it->x > max_x) max_x = it->x;
if (it->y < min_y) min_y = it->y;
if (it->y > max_y) max_y = it->y;
onPath.insert(*it);
}
Rect2i bounds(min_x, min_y, max_x + 1, max_y + 1);
it = advPath->begin();
UnitPath::iterator nit = it;
++nit;
while (nit != advPath->end()) {
onPath.erase(*it);
Vec2i sp = *it;
for (int d = 0; d < odCount; ++d) {
Vec2i p = *it + OrdinalOffsets[d];
if (p == *nit) continue;
Vec2i intersect(-1);
while (bounds.isInside(p)) {
if (!aMap->canOccupy(p, unit->getType()->getSize(), unit->getCurrField())) {
break;
}
if (d % 2 == 1) { // diagonal
Vec2i off1, off2;
getDiags(p - OrdinalOffsets[d], p, unit->getType()->getSize(), off1, off2);
if (!aMap->canOccupy(off1, 1, unit->getCurrField())
|| !aMap->canOccupy(off2, 1, unit->getCurrField())) {
break;
}
}
if (onPath.find(p) != onPath.end()) {
intersect = p;
break;
}
p += OrdinalOffsets[d];
}
if (intersect != Vec2i(-1)) {
UnitPath::iterator eit = nit;
while (*eit != intersect) {
onPath.erase(*eit++);
}
nit = advPath->erase(nit, eit);
sp += OrdinalOffsets[d];
while (sp != intersect) {
advPath->insert(nit, sp);
onPath.insert(sp); // do we need this? Can these get us further hits ??
sp += OrdinalOffsets[d];
}
break; // break foreach direction
} // if shortcut
} // foreach direction
nit = ++it;
++nit;
}
}
TravelState RoutePlanner::doRouteCache(Unit *unit) {
PF_TRACE();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
WaypointPath &wpPath = *unit->getWaypointPath();
assert(unit->getPos().dist(path.front()) < 1.5f);
if (attemptMove(unit)) {
if (!wpPath.empty() && path.size() < 12) {
// if there are less than 12 steps left on this path, and there are more waypoints
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), wpPath.back()); )
while (!wpPath.empty() && path.size() < 24) {
// refine path to at least 24 steps (or end of path)
if (!refinePath(unit)) {
CONSOLE_LOG( "refinePath() failed. [route cache]" )
wpPath.clear(); // path has become invalid
break;
}
}
smoothPath(unit);
PF_DEBUG(log_prelude(world, unit) << " MOVING [route cahce hit] from " << unit->getPos()
<< " to " << unit->getTargetPos());
PF_DEBUG(log_prelude(world, unit) << "[low-level path further refined]");
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
// IF_DEBUG_EDITION( collectPath(unit); )
} else {
PF_DEBUG(log_prelude(world, unit) << " MOVING [route cahce hit] from " << unit->getPos()
<< " to " << unit->getTargetPos());
}
return tsMoving;
}
// path blocked, quickSearch to next waypoint...
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), wpPath.empty() ? path.back() : wpPath.front()); )
if (repairPath(unit) && attemptMove(unit)) {
// IF_DEBUG_EDITION( collectPath(unit); )
PF_DEBUG(log_prelude(world, unit) << " MOVING [cached path repaired] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
}
unit->setCurrSkill(scStop);
return tsBlocked;
}
TravelState RoutePlanner::doQuickPathSearch(Unit *unit, const Vec2i &target) {
PF_TRACE();
AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit);
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); )
aMap->annotateLocal(unit);
float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), target);
aMap->clearLocalAnnotations(unit);
// IF_DEBUG_EDITION( collectOpenClosed<NodePool>(nodeStore); )
if (cost != -1.f) {
Vec2i pos = nsgSearchEngine->getGoalPos();
while (pos.x != -1) {
path.push_front(pos);
pos = nsgSearchEngine->getPreviousPos(pos);
}
if (path.size() > 1) {
path.pop();
if (attemptMove(unit)) {
// IF_DEBUG_EDITION( collectPath(unit); )
PF_DEBUG(log_prelude(world, unit) << " MOVING [doQuickPathSearch() Ok.] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
}
}
path.clear();
}
PF_DEBUG(log_prelude(world, unit) << "doQuickPathSearch() : Failed.");
return tsBlocked;
}
TravelState RoutePlanner::findAerialPath(Unit *unit, const Vec2i &targetPos) {
PF_TRACE();
AnnotatedMap *aMap = world->getCartographer()->getMasterMap();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
PosGoal goal(targetPos);
MoveCost cost(unit, aMap);
DiagonalDistance dd(targetPos);
nsgSearchEngine->setNodeLimit(256);
nsgSearchEngine->setStart(unit->getPos(), dd(unit->getPos()));
aMap->annotateLocal(unit);
AStarResult res = nsgSearchEngine->aStar(goal, cost, dd);
aMap->clearLocalAnnotations(unit);
if (res == asrComplete || res == asrNodeLimit) {
Vec2i pos = nsgSearchEngine->getGoalPos();
while (pos.x != -1) {
path.push_front(pos);
pos = nsgSearchEngine->getPreviousPos(pos);
}
if (path.size() > 1) {
path.pop();
if (attemptMove(unit)) {
PF_DEBUG(log_prelude(world, unit) << " MOVING [aerial path search Ok.] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
}
} else {
path.clear();
}
}
PF_DEBUG(log_prelude(world, unit) << " BLOCKED [aerial path search failed.]");
path.incBlockCount();
unit->setCurrSkill(scStop);
return tsBlocked;
}
/** Find a path to a location.
* @param unit the unit requesting the path
* @param finalPos the position the unit desires to go to
* @return ARRIVED, MOVING, BLOCKED or IMPOSSIBLE
*/
TravelState RoutePlanner::findPathToLocation(Unit *unit, const Vec2i &finalPos) {
PF_TRACE();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
WaypointPath &wpPath = *unit->getWaypointPath();
// if arrived (where we wanted to go)
if(finalPos == unit->getPos()) {
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() : ARRIVED [1]");
return tsArrived;
}
// route cache
if (!path.empty()) {
if (doRouteCache(unit) == tsMoving) {
return tsMoving;
}
}
// route cache miss or blocked
const Vec2i &target = computeNearestFreePos(unit, finalPos);
// if arrived (as close as we can get to it)
if (target == unit->getPos()) {
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() : ARRIVED [2]");
return tsArrived;
}
path.clear();
wpPath.clear();
if (unit->getCurrField() == fAir) {
return findAerialPath(unit, target);
}
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() " << "target pos: " << finalPos);
// QuickSearch if close to target
Vec2i startCluster = ClusterMap::cellToCluster(unit->getPos());
Vec2i destCluster = ClusterMap::cellToCluster(target);
if (startCluster.dist(destCluster) < 3.f) {
if (doQuickPathSearch(unit, target) == tsMoving) {
return tsMoving;
}
}
PF_TRACE();
// Hierarchical Search
tSearchEngine->reset();
Map *map = world->getMap();
HAAStarResult res;
if (map->getSurfaceCell(Map::toSurfCoords(target))->isExplored(unit->getTeam())) {
res = findWaypointPath(unit, target, wpPath);
} else {
res = findWaypointPathUnExplored(unit, target, wpPath);
}
if (res == hsrFailed) {
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() IMPOSSIBLE");
if (unit->getFaction()->getThisFaction()) {
world->getGame()->getConsole()->addLine(Lang::getInstance().get("InvalidPosition"));
}
return tsImpossible;
} else if (res == hsrStartTrap) {
if (wpPath.size() < 2) {
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() BLOCKED [START_TRAP]");
return tsBlocked;
}
}
PF_TRACE();
// IF_DEBUG_EDITION( collectWaypointPath(unit); )
if (wpPath.size() > 1) {
wpPath.pop();
}
assert(!wpPath.empty());
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); )
// refine path, to at least 20 steps (or end of path)
AnnotatedMap *aMap = world->getCartographer()->getMasterMap();
aMap->annotateLocal(unit);
wpPath.condense();
while (!wpPath.empty() && path.size() < 20) {
if (!refinePath(unit)) {
aMap->clearLocalAnnotations(unit);
path.incBlockCount();
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() BLOCKED [refinePath() failed]");
return tsBlocked;
}
}
smoothPath(unit);
aMap->clearLocalAnnotations(unit);
// IF_DEBUG_EDITION( collectPath(unit); )
if (path.empty()) {
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() : BLOCKED ... [post hierarchical search failure, path empty.]" );
unit->setCurrSkill(scStop);
return tsBlocked;
}
if (attemptMove(unit)) {
PF_DEBUG(log_prelude(world, unit) << " MOVING [Hierarchical Search Ok.] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
}
PF_DEBUG(log_prelude(world, unit) << "findPathToLocation() : BLOCKED ... [possible invalid path?]");
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
unit->setCurrSkill(scStop);
path.incBlockCount();
return tsBlocked;
}
TravelState RoutePlanner::customGoalSearch(PMap1Goal &goal, Unit *unit, const Vec2i &target) {
PF_TRACE();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
WaypointPath &wpPath = *unit->getWaypointPath();
const Vec2i &start = unit->getPos();
// setup search
AnnotatedMap *aMap = world->getCartographer()->getMasterMap();
MoveCost moveCost(unit->getCurrField(), unit->getType()->getSize(), aMap);
DiagonalDistance heuristic(target);
nsgSearchEngine->setNodeLimit(512);
nsgSearchEngine->setStart(start, heuristic(start));
aMap->annotateLocal(unit);
AStarResult r = nsgSearchEngine->aStar(goal, moveCost, heuristic);
aMap->clearLocalAnnotations(unit);
PF_TRACE();
if (r == asrComplete) {
Vec2i pos = nsgSearchEngine->getGoalPos();
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), pos); )
// IF_DEBUG_EDITION( collectOpenClosed<NodePool>(nsgSearchEngine->getStorage()); )
while (pos.x != -1) {
path.push_front(pos);
pos = nsgSearchEngine->getPreviousPos(pos);
}
if (!path.empty()) path.pop();
// IF_DEBUG_EDITION( collectPath(unit); )
if (!path.empty() && attemptMove(unit)) {
return tsMoving;
}
path.clear();
}
return tsBlocked;
}
TravelState RoutePlanner::findPathToGoal(Unit *unit, PMap1Goal &goal, const Vec2i &target) {
PF_TRACE();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
WaypointPath &wpPath = *unit->getWaypointPath();
// if at goal
if (goal(unit->getPos(), 0.f)) {
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "ARRIVED.");
return tsArrived;
}
// route chache
if (!path.empty()) {
if (doRouteCache(unit) == tsMoving) {
return tsMoving;
}
path.clear();
wpPath.clear();
}
// try customGoalSearch if close to target
if (unit->getPos().dist(target) < 50.f) {
if (customGoalSearch(goal, unit, target) == tsMoving) {
PF_DEBUG(log_prelude(world, unit) << " MOVING [customGoalSearch() Ok.] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
} else {
PF_DEBUG(log_prelude(world, unit) << "BLOCKED. [customGoalSearch() Failed.]");
unit->setCurrSkill(scStop);
return tsBlocked;
}
}
PF_TRACE();
// Hierarchical Search
tSearchEngine->reset();
if (!findWaypointPath(unit, target, wpPath)) {
PF_DEBUG(log_prelude(world, unit) << "IMPOSSIBLE.");
if (unit->getFaction()->getThisFaction()) {
//console.add(lang.get("Unreachable"));
}
return tsImpossible;
}
// IF_DEBUG_EDITION( collectWaypointPath(unit); )
assert(wpPath.size() > 1);
wpPath.pop();
// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); )
// cull destination and waypoints close to it, when we get to the last remaining
// waypoint we'll do a 'customGoalSearch' to the target
while (wpPath.size() > 1 && wpPath.back().dist(target) < 32.f) {
wpPath.pop_back();
}
// refine path, to at least 20 steps (or end of path)
AnnotatedMap *aMap = world->getCartographer()->getMasterMap();
aMap->annotateLocal(unit);
while (!wpPath.empty() && path.size() < 20) {
if (!refinePath(unit)) {
aMap->clearLocalAnnotations(unit);
unit->setCurrSkill(scStop);
PF_DEBUG(log_prelude(world, unit) << "BLOCKED [refinePath() failed].");
return tsBlocked;
}
}
smoothPath(unit);
aMap->clearLocalAnnotations(unit);
// IF_DEBUG_EDITION( collectPath(unit); )
if (attemptMove(unit)) {
PF_DEBUG(log_prelude(world, unit) << " MOVING [Hierarchical Search Ok.] from " << unit->getPos()
<< " to " << unit->getTargetPos());
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
return tsMoving;
}
PF_DEBUG(log_prelude(world, unit) << "BLOCKED [hierarchical search, possible invalid path].");
CONSOLE_LOG( "Hierarchical refined path blocked ? valid ?!? [Custom Goal Search]" )
if (!wpPath.empty()) {
PF_DEBUG("Waypoint Path: " << wpPath);
}
if (!path.empty()) {
PF_DEBUG("LowLevel Path: " << path);
}
unit->setCurrSkill(scStop);
return tsBlocked;
}
/** repair a blocked path
* @param unit unit whose path is blocked
* @return true if repair succeeded */
bool RoutePlanner::repairPath(Unit *unit) {
PF_TRACE();
UnitPathInterface *unitpath = unit->getPath();
UnitPath *advPath = dynamic_cast<UnitPath *>(unitpath);
if(advPath == NULL) {
throw runtime_error("Invalid or NULL unit path pointer!");
}
UnitPath &path = *advPath;
WaypointPath &wpPath = *unit->getWaypointPath();
Vec2i dest;
if (path.size() < 10 && !wpPath.empty()) {
dest = wpPath.front();
} else {
dest = path.back();
}
path.clear();
AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit);
aMap->annotateLocal(unit);
if (quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), dest) != -1.f) {
Vec2i pos = nsgSearchEngine->getGoalPos();
while (pos.x != -1) {
path.push_front(pos);
pos = nsgSearchEngine->getPreviousPos(pos);
}
if (path.size() > 2) {
path.pop();
if (!wpPath.empty() && wpPath.front() == path.back()) {
wpPath.pop();
}
} else {
path.clear();
}
}
aMap->clearLocalAnnotations(unit);
if (!path.empty()) {
// IF_DEBUG_EDITION (
// collectOpenClosed<NodePool>(nsgSearchEngine->getStorage());
// collectPath(unit);
// )
return true;
}
return false;
}
#if _GAE_DEBUG_EDITION_
TravelState RoutePlanner::doFullLowLevelAStar(Unit *unit, const Vec2i &dest) {
UnitPath &path = *unit->getPath();
WaypointPath &wpPath = *unit->getWaypointPath();
const Vec2i &target = computeNearestFreePos(unit, dest);
// if arrived (as close as we can get to it)
if (target == unit->getPos()) {
unit->setCurrSkill(scStop);
return tsArrived;
}
path.clear();
wpPath.clear();
// Low Level Search with NodeMap (this is for testing purposes only, not node limited)
// this is the 'straight' A* using the NodeMap (no node limit)
AnnotatedMap *aMap = g_world.getCartographer()->getAnnotatedMap(unit);
SearchEngine<NodeMap> *se = g_world.getCartographer()->getSearchEngine();
DiagonalDistance dd(target);
MoveCost cost(unit, aMap);
PosGoal goal(target);
se->setNodeLimit(-1);
se->setStart(unit->getPos(), dd(unit->getPos()));
AStarResult res = se->aStar(goal,cost,dd);
list<Vec2i>::iterator it;
IF_DEBUG_EDITION (
list<Vec2i> *nodes = NULL;
NodeMap* nm = se->getStorage();
)
Vec2i goalPos, pos;
switch (res) {
case asrComplete:
goalPos = se->getGoalPos();
pos = goalPos;
while (pos.x != -1) {
path.push(pos);
pos = se->getPreviousPos(pos);
}
if (!path.empty()) path.pop();
IF_DEBUG_EDITION (
collectOpenClosed<NodeMap>(se->getStorage());
collectPath(unit);
)
break; // case asrComplete
case AStarResult::FAILURE:
return tsImpossible;
default:
throw runtime_error("Something that shouldn't have happened, did happen :(");
}
if (path.empty()) {
unit->setCurrSkill(scStop);
return tsArrived;
}
if (attemptMove(unit)) return tsMoving; // should always succeed (if local annotations were applied)
unit->setCurrSkill(scStop);
path.incBlockCount();
return tsBlocked;
}
#endif // _GAE_DEBUG_EDITION_
// ==================== PRIVATE ====================
// return finalPos if free, else a nearest free pos within maxFreeSearchRadius
// cells, or unit's current position if none found
//
/** find nearest free position a unit can occupy
* @param unit the unit in question
* @param finalPos the position unit wishes to be
* @return finalPos if free and occupyable by unit, else the closest such position, or the unit's
* current position if none found
* @todo reimplement with Dijkstra search
*/
Vec2i RoutePlanner::computeNearestFreePos(const Unit *unit, const Vec2i &finalPos) {
PF_TRACE();
//unit data
Vec2i unitPos= unit->getPos();
int size= unit->getType()->getSize();
Field field = unit->getCurrField();
int teamIndex= unit->getTeam();
//if finalPos is free return it
if (world->getMap()->isAproxFreeCells(finalPos, size, field, teamIndex)) {
return finalPos;
}
//find nearest pos
Vec2i nearestPos = unitPos;
float nearestDist = unitPos.dist(finalPos);
for (int i = -maxFreeSearchRadius; i <= maxFreeSearchRadius; ++i) {
for (int j = -maxFreeSearchRadius; j <= maxFreeSearchRadius; ++j) {
Vec2i currPos = finalPos + Vec2i(i, j);
if (world->getMap()->isAproxFreeCells(currPos, size, field, teamIndex)) {
float dist = currPos.dist(finalPos);
//if nearer from finalPos
if (dist < nearestDist) {
nearestPos = currPos;
nearestDist = dist;
//if the distance is the same compare distance to unit
} else if (dist == nearestDist) {
if (currPos.dist(unitPos) < nearestPos.dist(unitPos)) {
nearestPos = currPos;
}
}
}
}
}
return nearestPos;
}
}} // end namespace Glest::Game::Search