ReGameDLL_CS/regamedll/game_shared/bot/nav_area.h
2024-01-31 18:33:34 +07:00

1169 lines
36 KiB
C++

/*
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#pragma once
#include <list>
class CNavArea;
void DestroyHidingSpots();
void StripNavigationAreas();
bool SaveNavigationMap(const char *filename);
NavErrorType LoadNavigationMap();
void DestroyNavigationMap();
enum NavEditCmdType
{
EDIT_NONE,
EDIT_DELETE, // delete current area
EDIT_SPLIT, // split current area
EDIT_MERGE, // merge adjacent areas
EDIT_JOIN, // define connection between areas
EDIT_BREAK, // break connection between areas
EDIT_MARK, // mark an area for further operations
EDIT_ATTRIB_CROUCH, // toggle crouch attribute on current area
EDIT_ATTRIB_JUMP, // toggle jump attribute on current area
EDIT_ATTRIB_PRECISE, // toggle precise attribute on current area
EDIT_ATTRIB_NO_JUMP, // toggle inhibiting discontinuity jumping in current area
EDIT_BEGIN_AREA, // begin creating a new nav area
EDIT_END_AREA, // end creation of the new nav area
EDIT_CONNECT, // connect marked area to selected area
EDIT_DISCONNECT, // disconnect marked area from selected area
EDIT_SPLICE, // create new area in between marked and selected areas
EDIT_TOGGLE_PLACE_MODE, // switch between normal and place editing
EDIT_TOGGLE_PLACE_PAINTING, // switch between "painting" places onto areas
EDIT_PLACE_FLOODFILL, // floodfill areas out from current area
EDIT_PLACE_PICK, // "pick up" the place at the current area
EDIT_MARK_UNNAMED, // mark an unnamed area for further operations
EDIT_WARP_TO_MARK, // warp a spectating local player to the selected mark
EDIT_SELECT_CORNER, // select a corner on the current area
EDIT_RAISE_CORNER, // raise a corner on the current area
EDIT_LOWER_CORNER, // lower a corner on the current area
};
enum RouteType
{
FASTEST_ROUTE,
SAFEST_ROUTE,
};
union NavConnect
{
unsigned int id;
CNavArea *area;
bool operator==(const NavConnect &other) const { return (area == other.area) ? true : false; }
};
typedef std::list<NavConnect> NavConnectList;
enum LadderDirectionType
{
LADDER_UP = 0,
LADDER_DOWN,
NUM_LADDER_DIRECTIONS
};
class CNavLadder
{
public:
CNavLadder()
{
m_topForwardArea = nullptr;
m_topRightArea = nullptr;
m_topLeftArea = nullptr;
m_topBehindArea = nullptr;
m_bottomArea = nullptr;
m_entity = nullptr;
}
Vector m_top;
Vector m_bottom;
float m_length;
NavDirType m_dir;
Vector2D m_dirVector;
CBaseEntity *m_entity;
CNavArea *m_topForwardArea;
CNavArea *m_topLeftArea;
CNavArea *m_topRightArea;
CNavArea *m_topBehindArea;
CNavArea *m_bottomArea;
bool m_isDangling;
void OnDestroyNotify(CNavArea *dead)
{
if (dead == m_topForwardArea)
m_topForwardArea = nullptr;
if (dead == m_topLeftArea)
m_topLeftArea = nullptr;
if (dead == m_topRightArea)
m_topRightArea = nullptr;
if (dead == m_topBehindArea)
m_topBehindArea = nullptr;
if (dead == m_bottomArea)
m_bottomArea = nullptr;
}
};
typedef std::list<CNavLadder *> NavLadderList;
extern NavLadderList TheNavLadderList;
class HidingSpot
{
public:
HidingSpot();
HidingSpot(const Vector *pos, unsigned char flags);
enum
{
IN_COVER = 0x01,
GOOD_SNIPER_SPOT = 0x02,
IDEAL_SNIPER_SPOT = 0x04
};
bool HasGoodCover() const { return (m_flags & IN_COVER) ? true : false; }
bool IsGoodSniperSpot() const { return (m_flags & GOOD_SNIPER_SPOT) ? true : false; }
bool IsIdealSniperSpot() const { return (m_flags & IDEAL_SNIPER_SPOT) ? true : false; }
void SetFlags(unsigned char flags) { m_flags |= flags; }
unsigned char GetFlags() const { return m_flags; }
void Save(int fd, unsigned int version) const;
void Load(SteamFile *file, unsigned int version);
const Vector *GetPosition() const { return &m_pos; }
unsigned int GetID() const { return m_id; }
void Mark() { m_marker = m_masterMarker; }
bool IsMarked() const { return (m_marker == m_masterMarker) ? true : false; }
static void ChangeMasterMarker() { m_masterMarker++; }
private:
friend void DestroyHidingSpots();
Vector m_pos;
unsigned int m_id;
unsigned int m_marker;
unsigned char m_flags;
static unsigned int m_nextID;
static unsigned int m_masterMarker;
};
typedef std::list<HidingSpot *> HidingSpotList;
extern HidingSpotList TheHidingSpotList;
struct SpotOrder
{
float t;
union
{
HidingSpot *spot;
unsigned int id;
};
};
typedef std::list<SpotOrder> SpotOrderList;
struct SpotEncounter
{
NavConnect from;
NavDirType fromDir;
NavConnect to;
NavDirType toDir;
Ray path; // the path segment
SpotOrderList spotList; // list of spots to look at, in order of occurrence
};
typedef std::list<SpotEncounter> SpotEncounterList;
typedef std::list<CNavArea *> NavAreaList;
// A CNavArea is a rectangular region defining a walkable area in the map
class CNavArea
{
public:
CNavArea(CNavNode *nwNode, CNavNode *neNode, CNavNode *seNode, CNavNode *swNode);
CNavArea();
CNavArea(const Vector *corner, const Vector *otherCorner);
CNavArea(const Vector *nwCorner, const Vector *neCorner, const Vector *seCorner, const Vector *swCorner);
~CNavArea();
void ConnectTo(CNavArea *area, NavDirType dir); // connect this area to given area in given direction
void Disconnect(CNavArea *area); // disconnect this area from given area
void Save(FILE *fp) const;
void Save(int fd, unsigned int version);
void Load(SteamFile *file, unsigned int version);
NavErrorType PostLoad();
unsigned int GetID() const { return m_id; }
void SetAttributes(unsigned char bits) { m_attributeFlags = bits; }
unsigned char GetAttributes() const { return m_attributeFlags; }
void SetPlace(Place place) { m_place = place; } // set place descriptor
Place GetPlace() const { return m_place; } // get place descriptor
bool IsOverlapping(const Vector *pos) const; // return true if 'pos' is within 2D extents of area
bool IsOverlapping(const CNavArea *area) const; // return true if 'area' overlaps our 2D extents
bool IsOverlappingX(const CNavArea *area) const; // return true if 'area' overlaps our X extent
bool IsOverlappingY(const CNavArea *area) const; // return true if 'area' overlaps our Y extent
int GetPlayerCount(int teamID = 0, CBasePlayer *ignore = nullptr) const; // return number of players with given teamID in this area (teamID == 0 means any/all)
float GetZ(const Vector *pos) const; // return Z of area at (x,y) of 'pos'
float GetZ(float x, float y) const; // return Z of area at (x,y) of 'pos'
bool Contains(const Vector *pos) const; // return true if given point is on or above this area, but no others
bool IsCoplanar(const CNavArea *area) const; // return true if this area and given area are approximately co-planar
void GetClosestPointOnArea(const Vector *pos, Vector *close) const; // return closest point to 'pos' on this area - returned point in 'close'
float GetDistanceSquaredToPoint(const Vector *pos) const; // return shortest distance between point and this area
bool IsDegenerate() const; // return true if this area is badly formed
bool IsEdge(NavDirType dir) const; // return true if there are no bi-directional links on the given side
int GetAdjacentCount(NavDirType dir) const { return m_connect[dir].size(); } // return number of connected areas in given direction
CNavArea *GetAdjacentArea(NavDirType dir, int i) const; // return the i'th adjacent area in the given direction
CNavArea *GetRandomAdjacentArea(NavDirType dir) const;
const NavConnectList *GetAdjacentList(NavDirType dir) const { return &m_connect[dir]; }
bool IsConnected(const CNavArea *area, NavDirType dir) const; // return true if given area is connected in given direction
float ComputeHeightChange(const CNavArea *area); // compute change in height from this area to given area
const NavLadderList *GetLadderList(LadderDirectionType dir) const { return &m_ladder[dir]; }
void ComputePortal(const CNavArea *to, NavDirType dir, Vector *center, float *halfWidth) const; // compute portal to adjacent area
void ComputeClosestPointInPortal(const CNavArea *to, NavDirType dir, const Vector *fromPos, Vector *closePos) const; // compute closest point within the "portal" between to adjacent areas
NavDirType ComputeDirection(Vector *point) const; // return direction from this area to the given point
// for hunting algorithm
void SetClearedTimestamp(int teamID) { m_clearedTimestamp[teamID] = gpGlobals->time; } // set this area's "clear" timestamp to now
float GetClearedTimestamp(int teamID) { return m_clearedTimestamp[teamID]; } // get time this area was marked "clear"
// hiding spots
const HidingSpotList *GetHidingSpotList() const { return &m_hidingSpotList; }
void ComputeHidingSpots(); // analyze local area neighborhood to find "hiding spots" in this area - for map learning
void ComputeSniperSpots(); // analyze local area neighborhood to find "sniper spots" in this area - for map learning
SpotEncounter *GetSpotEncounter(const CNavArea *from, const CNavArea *to); // given the areas we are moving between, return the spots we will encounter
void ComputeSpotEncounters(); // compute spot encounter data - for map learning
// danger
void IncreaseDanger(int teamID, float amount); // increase the danger of this area for the given team
float GetDanger(int teamID); // return the danger of this area (decays over time)
float GetSizeX() const { return m_extent.hi.x - m_extent.lo.x; }
float GetSizeY() const { return m_extent.hi.y - m_extent.lo.y; }
const Extent *GetExtent() const { return &m_extent; }
const Vector *GetCenter() const { return &m_center; }
const Vector *GetCorner(NavCornerType corner) const;
// approach areas
struct ApproachInfo
{
NavConnect here; // the approach area
NavConnect prev; // the area just before the approach area on the path
NavTraverseType prevToHereHow;
NavConnect next; // the area just after the approach area on the path
NavTraverseType hereToNextHow;
};
const ApproachInfo *GetApproachInfo(int i) const { return &m_approach[i]; }
int GetApproachInfoCount() const { return m_approachCount; }
void ComputeApproachAreas(); // determine the set of "approach areas" - for map learning
// A* pathfinding algorithm
static void MakeNewMarker()
{
if (++m_masterMarker == 0)
m_masterMarker = 1;
}
void Mark() { m_marker = m_masterMarker; }
BOOL IsMarked() const { return (m_marker == m_masterMarker) ? true : false; }
void SetParent(CNavArea *parent, NavTraverseType how = NUM_TRAVERSE_TYPES) { m_parent = parent; m_parentHow = how; }
CNavArea *GetParent() const { return m_parent; }
NavTraverseType GetParentHow() const { return m_parentHow; }
bool IsOpen() const; // true if on "open list"
void AddToOpenList(); // add to open list in decreasing value order
void UpdateOnOpenList(); // a smaller value has been found, update this area on the open list
void RemoveFromOpenList();
static bool IsOpenListEmpty();
static CNavArea *PopOpenList(); // remove and return the first element of the open list
bool IsClosed() const; // true if on "closed list"
void AddToClosedList(); // add to the closed list
void RemoveFromClosedList();
static void ClearSearchLists(); // clears the open and closed lists for a new search
void SetTotalCost(float value) { m_totalCost = value; }
float GetTotalCost() const { return m_totalCost; }
void SetCostSoFar(float value) { m_costSoFar = value; }
float GetCostSoFar() const { return m_costSoFar; }
// editing
void Draw(byte red, byte green, byte blue, int duration = 50); // draw area for debugging & editing
void DrawConnectedAreas();
void DrawMarkedCorner(NavCornerType corner, byte red, byte green, byte blue, int duration = 50);
bool SplitEdit(bool splitAlongX, float splitEdge, CNavArea **outAlpha = nullptr, CNavArea **outBeta = nullptr); // split this area into two areas at the given edge
bool MergeEdit(CNavArea *adj); // merge this area and given adjacent area
bool SpliceEdit(CNavArea *other); // create a new area between this area and given area
void RaiseCorner(NavCornerType corner, int amount); // raise/lower a corner (or all corners if corner == NUM_CORNERS)
// ladders
void AddLadderUp(CNavLadder *ladder) { m_ladder[LADDER_UP].push_back(ladder); }
void AddLadderDown(CNavLadder *ladder) { m_ladder[LADDER_DOWN].push_back(ladder); }
private:
friend void ConnectGeneratedAreas();
friend void MergeGeneratedAreas();
friend void MarkJumpAreas();
friend bool SaveNavigationMap(const char *filename);
friend NavErrorType LoadNavigationMap();
friend void DestroyNavigationMap();
friend void DestroyHidingSpots();
friend void StripNavigationAreas();
friend class CNavAreaGrid;
friend class CCSBotManager;
void Initialize(); // to keep constructors consistent
static bool m_isReset; // if true, don't bother cleaning up in destructor since everything is going away
static unsigned int m_nextID; // used to allocate unique IDs
unsigned int m_id; // unique area ID
Extent m_extent; // extents of area in world coords (NOTE: lo.z is not necessarily the minimum Z, but corresponds to Z at point (lo.x, lo.y), etc
Vector m_center; // centroid of area
unsigned char m_attributeFlags; // set of attribute bit flags (see NavAttributeType)
Place m_place; // place descriptor
// height of the implicit corners
float m_neZ;
float m_swZ;
enum { MAX_AREA_TEAMS = 2 };
// for hunting
float m_clearedTimestamp[MAX_AREA_TEAMS]; // time this area was last "cleared" of enemies
// danger
float m_danger[MAX_AREA_TEAMS]; // danger of this area, allowing bots to avoid areas where they died in the past - zero is no danger
float m_dangerTimestamp[MAX_AREA_TEAMS]; // time when danger value was set - used for decaying
void DecayDanger();
// hiding spots
HidingSpotList m_hidingSpotList;
bool IsHidingSpotCollision(const Vector *pos) const; // returns true if an existing hiding spot is too close to given position
// encounter spots
SpotEncounterList m_spotEncounterList; // list of possible ways to move thru this area, and the spots to look at as we do
void AddSpotEncounters(const CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir);
// approach areas
enum { MAX_APPROACH_AREAS = 16 };
ApproachInfo m_approach[MAX_APPROACH_AREAS];
unsigned char m_approachCount;
void Strip(); // remove "analyzed" data from nav area
// A* pathfinding algorithm
static unsigned int m_masterMarker;
unsigned int m_marker; // used to flag the area as visited
CNavArea *m_parent; // the area just prior to this on in the search path
NavTraverseType m_parentHow; // how we get from parent to us
float m_totalCost; // the distance so far plus an estimate of the distance left
float m_costSoFar; // distance travelled so far
static CNavArea *m_openList;
CNavArea *m_nextOpen, *m_prevOpen; // only valid if m_openMarker == m_masterMarker
unsigned int m_openMarker; // if this equals the current marker value, we are on the open list
// connections to adjacent areas
NavConnectList m_connect[NUM_DIRECTIONS]; // a list of adjacent areas for each direction
NavLadderList m_ladder[NUM_LADDER_DIRECTIONS]; // list of ladders leading up and down from this area
CNavNode *m_node[NUM_CORNERS]; // nav nodes at each corner of the area
void FinishMerge(CNavArea *adjArea); // recompute internal data once nodes have been adjusted during merge
void MergeAdjacentConnections(CNavArea *adjArea); // for merging with "adjArea" - pick up all of "adjArea"s connections
void AssignNodes(CNavArea *area); // assign internal nodes to the given area
void FinishSplitEdit(CNavArea *newArea, NavDirType ignoreEdge); // given the portion of the original area, update its internal data
NavAreaList m_overlapList; // list of areas that overlap this area
void OnDestroyNotify(CNavArea *dead); // invoked when given area is going away
CNavArea *m_prevHash, *m_nextHash; // for hash table in CNavAreaGrid
};
extern NavAreaList TheNavAreaList;
inline bool CNavArea::IsDegenerate() const
{
return (m_extent.lo.x >= m_extent.hi.x || m_extent.lo.y >= m_extent.hi.y);
}
inline CNavArea *CNavArea::GetAdjacentArea(NavDirType dir, int i) const
{
for (auto &con : m_connect[dir])
{
if (i == 0)
return con.area;
i--;
}
return nullptr;
}
inline bool CNavArea::IsOpen() const
{
return (m_openMarker == m_masterMarker) ? true : false;
}
inline bool CNavArea::IsOpenListEmpty()
{
return m_openList ? false : true;
}
inline CNavArea *CNavArea::PopOpenList()
{
if (m_openList)
{
CNavArea *area = m_openList;
// disconnect from list
area->RemoveFromOpenList();
return area;
}
return nullptr;
}
inline bool CNavArea::IsClosed() const
{
if (IsMarked() && !IsOpen())
return true;
return false;
}
inline void CNavArea::AddToClosedList()
{
Mark();
}
inline void CNavArea::RemoveFromClosedList()
{
// since "closed" is defined as visited (marked) and not on open list, do nothing
}
// The CNavAreaGrid is used to efficiently access navigation areas by world position
// Each cell of the grid contains a list of areas that overlap it
// Given a world position, the corresponding grid cell is ( x/cellsize, y/cellsize )
class CNavAreaGrid
{
public:
CNavAreaGrid();
~CNavAreaGrid();
void Reset(); // clear the grid to empty
void Initialize(float minX, float maxX, float minY, float maxY); // clear and reset the grid to the given extents
void AddNavArea(CNavArea *area); // add an area to the grid
void RemoveNavArea(CNavArea *area); // remove an area from the grid
unsigned int GetNavAreaCount() const { return m_areaCount; } // return total number of nav areas
CNavArea *GetNavArea(const Vector *pos, float beneathLimt = 120.0f) const; // given a position, return the nav area that IsOverlapping and is *immediately* beneath it
CNavArea *GetNavAreaByID(unsigned int id) const;
CNavArea *GetNearestNavArea(const Vector *pos, bool anyZ = false) const;
bool IsValid() const;
Place GetPlace(const Vector *pos) const; // return radio chatter place for given coordinate
private:
const float m_cellSize;
NavAreaList *m_grid;
int m_gridSizeX;
int m_gridSizeY;
float m_minX;
float m_minY;
unsigned int m_areaCount; // total number of nav areas
enum { HASH_TABLE_SIZE = 256 };
CNavArea *m_hashTable[HASH_TABLE_SIZE]; // hash table to optimize lookup by ID
inline int ComputeHashKey(unsigned int id) const // returns a hash key for the given nav area ID
{
return id & 0xFF;
}
inline int WorldToGridX(float wx) const
{
int x = (wx - m_minX) / m_cellSize;
if (x < 0)
x = 0;
else if (x >= m_gridSizeX)
x = m_gridSizeX - 1;
return x;
}
inline int WorldToGridY(float wy) const
{
int y = (wy - m_minY) / m_cellSize;
if (y < 0)
y = 0;
else if (y >= m_gridSizeY)
y = m_gridSizeY - 1;
return y;
}
};
extern CNavAreaGrid TheNavAreaGrid;
class ShortestPathCost
{
public:
float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
{
if (!fromArea)
{
// first area in path, no cost
return 0.0f;
}
else
{
// compute distance travelled along path so far
float dist;
if (ladder)
dist = ladder->m_length;
else
dist = (*area->GetCenter() - *fromArea->GetCenter()).Length();
float cost = dist + fromArea->GetCostSoFar();
// if this is a "crouch" area, add penalty
if (area->GetAttributes() & NAV_CROUCH)
{
const float crouchPenalty = 20.0f;
cost += crouchPenalty * dist;
}
// if this is a "jump" area, add penalty
if (area->GetAttributes() & NAV_JUMP)
{
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
return cost;
}
}
};
// Find path from startArea to goalArea via an A* search, using supplied cost heuristic.
// If cost functor returns -1 for an area, that area is considered a dead end.
// This doesn't actually build a path, but the path is defined by following parent
// pointers back from goalArea to startArea.
// If 'closestArea' is non-NULL, the closest area to the goal is returned (useful if the path fails).
// If 'goalArea' is NULL, will compute a path as close as possible to 'goalPos'.
// If 'goalPos' is NULL, will use the center of 'goalArea' as the goal position.
// Returns true if a path exists.
template <typename CostFunctor>
bool NavAreaBuildPath(CNavArea *startArea, CNavArea *goalArea, const Vector *goalPos, CostFunctor &costFunc, CNavArea **closestArea = nullptr)
{
if (closestArea)
*closestArea = nullptr;
if (!startArea)
return false;
// If goalArea is NULL, this function will return the closest area to the goal.
// However, if there is also no goal, we can't do anything.
if (!goalArea && !goalPos)
{
return false;
}
startArea->SetParent(nullptr);
// if we are already in the goal area, build trivial path
if (startArea == goalArea)
{
goalArea->SetParent(nullptr);
if (closestArea)
*closestArea = goalArea;
return true;
}
// determine actual goal position
Vector actualGoalPos = (goalPos != nullptr) ? (*goalPos) : (*goalArea->GetCenter());
// start search
CNavArea::ClearSearchLists();
// compute estimate of path length
// TODO: Cost might work as "manhattan distance"
startArea->SetTotalCost((*startArea->GetCenter() - actualGoalPos).Length());
real_t initCost = costFunc(startArea, nullptr, nullptr);
if (initCost < 0.0f)
return false;
startArea->SetCostSoFar(initCost);
startArea->AddToOpenList();
// keep track of the area we visit that is closest to the goal
if (closestArea)
*closestArea = startArea;
float closestAreaDist = startArea->GetTotalCost();
// do A* search
while (!CNavArea::IsOpenListEmpty())
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
// check if we have found the goal area
if (area == goalArea)
{
if (closestArea)
*closestArea = goalArea;
return true;
}
// search adjacent areas
bool searchFloor = true;
int dir = NORTH;
const NavConnectList *floorList = area->GetAdjacentList(NORTH);
auto floorIter = floorList->begin();
bool ladderUp = true;
const NavLadderList *ladderList = nullptr;
NavLadderList::const_iterator ladderIter;
enum
{
AHEAD = 0,
LEFT,
RIGHT,
BEHIND,
NUM_TOP_DIRECTIONS
};
int ladderTopDir;
while (true)
{
CNavArea *newArea = nullptr;
NavTraverseType how;
const CNavLadder *ladder = nullptr;
// Get next adjacent area - either on floor or via ladder
if (searchFloor)
{
// if exhausted adjacent connections in current direction, begin checking next direction
if (floorIter == floorList->end())
{
dir++;
if (dir >= NUM_DIRECTIONS)
{
// checked all directions on floor - check ladders next
searchFloor = false;
ladderList = area->GetLadderList(LADDER_UP);
ladderIter = ladderList->begin();
ladderTopDir = AHEAD;
}
else
{
// start next direction
floorList = area->GetAdjacentList((NavDirType)dir);
floorIter = floorList->begin();
}
continue;
}
newArea = (*floorIter).area;
how = (NavTraverseType)dir;
floorIter++;
DbgAssert(newArea);
if (!newArea)
continue;
}
// search ladders
else
{
if (ladderIter == ladderList->end())
{
if (!ladderUp)
{
// checked both ladder directions - done
break;
}
else
{
// check down ladders
ladderUp = false;
ladderList = area->GetLadderList(LADDER_DOWN);
ladderIter = ladderList->begin();
}
continue;
}
if (ladderUp)
{
ladder = (*ladderIter);
// cannot use this ladder if the ladder bottom is hanging above our head
if (ladder->m_isDangling)
{
ladderIter++;
continue;
}
// do not use BEHIND connection, as its very hard to get to when going up a ladder
if (ladderTopDir == AHEAD)
newArea = ladder->m_topForwardArea;
else if (ladderTopDir == LEFT)
newArea = ladder->m_topLeftArea;
else if (ladderTopDir == RIGHT)
newArea = ladder->m_topRightArea;
else
{
ladderIter++;
continue;
}
how = GO_LADDER_UP;
ladderTopDir++;
}
else
{
newArea = (*ladderIter)->m_bottomArea;
how = GO_LADDER_DOWN;
ladder = (*ladderIter);
ladderIter++;
}
if (!newArea)
continue;
}
// don't backtrack
if (newArea == area)
continue;
real_t newCostSoFar = costFunc(newArea, area, ladder);
// check if cost functor says this area is a dead-end
if (newCostSoFar < 0.0f)
continue;
if ((newArea->IsOpen() || newArea->IsClosed()) && newArea->GetCostSoFar() <= newCostSoFar)
{
// this is a worse path - skip it
continue;
}
else
{
// compute estimate of distance left to go
real_t newCostRemaining = (*newArea->GetCenter() - actualGoalPos).Length();
// track closest area to goal in case path fails
if (closestArea && newCostRemaining < closestAreaDist)
{
*closestArea = newArea;
closestAreaDist = newCostRemaining;
}
newArea->SetParent(area, how);
newArea->SetCostSoFar(newCostSoFar);
newArea->SetTotalCost(newCostSoFar + newCostRemaining);
if (newArea->IsClosed())
newArea->RemoveFromClosedList();
if (newArea->IsOpen())
{
// area already on open list, update the list order to keep costs sorted
newArea->UpdateOnOpenList();
}
else
newArea->AddToOpenList();
}
}
// we have searched this area
area->AddToClosedList();
}
return false;
}
// Compute distance between two areas. Return -1 if can't reach 'endArea' from 'startArea'.
template <typename CostFunctor>
real_t NavAreaTravelDistance(CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc)
{
if (!startArea)
return -1.0f;
if (!endArea)
return -1.0f;
if (startArea == endArea)
return 0.0f;
// compute path between areas using given cost heuristic
if (NavAreaBuildPath(startArea, endArea, nullptr, costFunc) == false)
return -1.0f;
// compute distance along path
real_t distance = 0.0f;
for (CNavArea *area = endArea; area->GetParent(); area = area->GetParent())
{
distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length();
}
return distance;
}
// Compute distance from area to position. Return -1 if can't reach position.
template <typename CostFunctor>
float NavAreaTravelDistance(const Vector *startPos, CNavArea *startArea, const Vector *goalPos, CostFunctor &costFunc)
{
if (!startArea || !startPos || !goalPos)
return -1.0f;
// compute path between areas using given cost heuristic
CNavArea *goalArea = nullptr;
if (NavAreaBuildPath(startArea, TheNavAreaGrid.GetNearestNavArea(goalPos), goalPos, costFunc, &goalArea) == false)
return -1.0f;
if (!goalArea)
return -1.0f;
// compute distance along path
if (!goalArea->GetParent())
{
return (*goalPos - *startPos).Length();
}
else
{
CNavArea *area = goalArea->GetParent();
float distance = (*goalPos - *area->GetCenter()).Length();
for (; area->GetParent(); area = area->GetParent())
{
distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length();
}
return distance;
}
}
// Do a breadth-first search, invoking functor on each area.
// If functor returns 'true', continue searching from this area.
// If functor returns 'false', the area's adjacent areas are not explored (dead end).
// If 'maxRange' is 0 or less, no range check is done (all areas will be examined).
// NOTE: Returns all areas that overlap range, even partially
// TODO: Use ladder connections
//
// helper function
inline void AddAreaToOpenList(CNavArea *area, CNavArea *parent, const Vector *startPos, float maxRange)
{
if (!area)
return;
if (!area->IsMarked())
{
area->Mark();
area->SetTotalCost(0.0f);
area->SetParent(parent);
if (maxRange > 0.0f)
{
// make sure this area overlaps range
Vector closePos;
area->GetClosestPointOnArea(startPos, &closePos);
if ((closePos - *startPos).Make2D().IsLengthLessThan(maxRange))
{
// compute approximate distance along path to limit travel range, too
float distAlong = parent->GetCostSoFar();
distAlong += (*area->GetCenter() - *parent->GetCenter()).Length();
area->SetCostSoFar(distAlong);
// allow for some fudge due to large size areas
if (distAlong <= 1.5f * maxRange)
area->AddToOpenList();
}
}
else
{
// infinite range
area->AddToOpenList();
}
}
}
template <typename Functor>
void SearchSurroundingAreas(CNavArea *startArea, const Vector *startPos, Functor &func, float maxRange = -1.0f)
{
if (!startArea || !startPos)
return;
CNavArea::MakeNewMarker();
CNavArea::ClearSearchLists();
startArea->AddToOpenList();
startArea->SetTotalCost(0.0f);
startArea->SetCostSoFar(0.0f);
startArea->SetParent(nullptr);
startArea->Mark();
while (!CNavArea::IsOpenListEmpty())
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
// invoke functor on area
if (func(area))
{
// explore adjacent floor areas
for (int dir = 0; dir < NUM_DIRECTIONS; dir++)
{
int count = area->GetAdjacentCount((NavDirType)dir);
for (int i = 0; i < count; i++)
{
CNavArea *adjArea = area->GetAdjacentArea((NavDirType)dir, i);
AddAreaToOpenList(adjArea, area, startPos, maxRange);
}
}
// explore adjacent areas connected by ladders
NavLadderList::const_iterator ladderIt;
// check up ladders
const NavLadderList *ladderList = area->GetLadderList(LADDER_UP);
if (ladderList)
{
for (ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ladderIt++)
{
const CNavLadder *ladder = *ladderIt;
// cannot use this ladder if the ladder bottom is hanging above our head
if (ladder->m_isDangling)
{
continue;
}
// do not use BEHIND connection, as its very hard to get to when going up a ladder
AddAreaToOpenList(ladder->m_topForwardArea, area, startPos, maxRange);
AddAreaToOpenList(ladder->m_topLeftArea, area, startPos, maxRange);
AddAreaToOpenList(ladder->m_topRightArea, area, startPos, maxRange);
}
}
// check down ladders
ladderList = area->GetLadderList(LADDER_DOWN);
if (ladderList)
{
for (ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ladderIt++)
{
const CNavLadder *ladder = *ladderIt;
AddAreaToOpenList(ladder->m_bottomArea, area, startPos, maxRange);
}
}
}
}
}
// Apply the functor to all navigation areas
// If functor returns false, stop processing and return false.
template <typename Functor>
bool ForAllAreas(Functor &func)
{
NavAreaList::iterator iter;
for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++)
{
CNavArea *area = (*iter);
if (func(area) == false)
return false;
}
return true;
}
// Fuctor that returns lowest cost for farthest away areas
// For use with FindMinimumCostArea()
class FarAwayFunctor
{
public:
float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
{
if (area == fromArea)
return 9999999.9f;
return 1.0f / (*fromArea->GetCenter() - *area->GetCenter()).Length();
}
};
// Fuctor that returns lowest cost for farthest away areas
// For use with FindMinimumCostArea()
class FarAwayFromPositionFunctor
{
public:
FarAwayFromPositionFunctor(const Vector *pos) { m_pos = pos; }
float operator() (CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
{
return 1.0f / (*m_pos - *area->GetCenter()).Length();
}
private:
const Vector *m_pos;
};
// Pick a low-cost area of "decent" size
template <typename CostFunctor>
CNavArea *FindMinimumCostArea(CNavArea *startArea, CostFunctor &costFunc)
{
const float minSize = 150.0f;
// collect N low-cost areas of a decent size
enum { NUM_CHEAP_AREAS = 32 };
struct
{
CNavArea *area;
float cost;
}
cheapAreaSet[NUM_CHEAP_AREAS];
int cheapAreaSetCount = 0;
NavAreaList::iterator iter;
for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++)
{
CNavArea *area = (*iter);
// skip the small areas
const Extent *extent = area->GetExtent();
if (extent->hi.x - extent->lo.x < minSize || extent->hi.y - extent->lo.y < minSize)
continue;
// compute cost of this area
float cost = costFunc(area, startArea, nullptr);
if (cheapAreaSetCount < NUM_CHEAP_AREAS)
{
cheapAreaSet[cheapAreaSetCount].area = area;
cheapAreaSet[cheapAreaSetCount++].cost = cost;
}
else
{
// replace most expensive cost if this is cheaper
int expensive = 0;
for (int i = 1; i < NUM_CHEAP_AREAS; i++)
{
if (cheapAreaSet[i].cost > cheapAreaSet[expensive].cost)
expensive = i;
}
if (cheapAreaSet[expensive].cost > cost)
{
cheapAreaSet[expensive].area = area;
cheapAreaSet[expensive].cost = cost;
}
}
}
if (cheapAreaSetCount)
{
// pick one of the areas at random
return cheapAreaSet[RANDOM_LONG(0, cheapAreaSetCount - 1)].area;
}
else
{
// degenerate case - no decent sized areas - pick a random area
int numAreas = TheNavAreaList.size();
int which = RANDOM_LONG(0, numAreas - 1);
NavAreaList::iterator iter;
for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); iter++)
{
if (which-- == 0)
break;
}
return (*iter);
}
}
bool IsHidingSpotInCover(const Vector *spot);
void ClassifySniperSpot(HidingSpot *spot);
void DestroyHidingSpots();
void EditNavAreas(NavEditCmdType cmd);
bool GetGroundHeight(const Vector *pos, float *height, Vector *normal = nullptr);
bool GetSimpleGroundHeight(const Vector *pos, float *height, Vector *normal = nullptr);
CNavArea *GetMarkedArea();
void EditNavAreasReset();
void DrawHidingSpots(const CNavArea *area);
void IncreaseDangerNearby(int teamID, float amount, CNavArea *startArea, const Vector *pos, float maxRadius);
void DrawDanger();
bool IsSpotOccupied(CBaseEntity *me, const Vector *pos); // if a player is at the given spot, return true
const Vector *FindNearbyHidingSpot(CBaseEntity *me, const Vector *pos, CNavArea *startArea, float maxRange = 1000.0f, bool isSniper = false, bool useNearest = false);
const Vector *FindNearbyRetreatSpot(CBaseEntity *me, const Vector *start, CNavArea *startArea, float maxRange = 1000.0f, int avoidTeam = 0, bool useCrouchAreas = true);
// return true if moving from "start" to "finish" will cross a player's line of fire
bool IsCrossingLineOfFire(const Vector &start, const Vector &finish, CBaseEntity *ignore = nullptr, int ignoreTeam = 0);
const Vector *FindRandomHidingSpot(CBaseEntity *me, Place place, bool isSniper = false);
HidingSpot *GetHidingSpotByID(unsigned int id);
void ApproachAreaAnalysisPrep();
void CleanupApproachAreaAnalysisPrep();
void DestroyLadders();
void DestroyNavigationMap();
void StripNavigationAreas();
CNavArea *FindFirstAreaInDirection(const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore, Vector *closePos);
bool testJumpDown(const Vector *fromPos, const Vector *toPos);
void ConnectGeneratedAreas();
void MergeGeneratedAreas();
bool IsAreaRoughlySquare(const CNavArea *area);
void SplitX(CNavArea *area);
void SplitY(CNavArea *area);
void SquareUpAreas();
bool TestArea(CNavNode *node, int width, int height);
int BuildArea(CNavNode *node, int width, int height);
void BuildLadders();
void MarkJumpAreas();
void GenerateNavigationAreaMesh();