You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1176 lines
36 KiB
1176 lines
36 KiB
/* |
|
* |
|
* 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. |
|
* |
|
*/ |
|
|
|
#ifndef NAV_AREA_H |
|
#define NAV_AREA_H |
|
#ifdef _WIN32 |
|
#pragma once |
|
#endif |
|
|
|
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 CUtlLinkedList<NavConnect, int> NavConnectList; |
|
|
|
enum LadderDirectionType |
|
{ |
|
LADDER_UP = 0, |
|
LADDER_DOWN, |
|
NUM_LADDER_DIRECTIONS |
|
}; |
|
|
|
class CNavLadder |
|
{ |
|
public: |
|
CNavLadder() |
|
{ |
|
m_topForwardArea = NULL; |
|
m_topRightArea = NULL; |
|
m_topLeftArea = NULL; |
|
m_topBehindArea = NULL; |
|
m_bottomArea = NULL; |
|
m_entity = NULL; |
|
} |
|
|
|
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; |
|
|
|
/* <4c2dc0> ../game_shared/bot/nav_area.h:88 */ |
|
void OnDestroyNotify(CNavArea *dead) |
|
{ |
|
if (dead == m_topForwardArea) |
|
m_topForwardArea = NULL; |
|
|
|
if (dead == m_topLeftArea) |
|
m_topLeftArea = NULL; |
|
|
|
if (dead == m_topRightArea) |
|
m_topRightArea = NULL; |
|
|
|
if (dead == m_topBehindArea) |
|
m_topBehindArea = NULL; |
|
|
|
if (dead == m_bottomArea) |
|
m_bottomArea = NULL; |
|
} |
|
}; |
|
|
|
typedef CUtlLinkedList<CNavLadder *, int> NavLadderList; |
|
|
|
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 CUtlLinkedList<HidingSpot *, int> HidingSpotList; |
|
|
|
struct SpotOrder |
|
{ |
|
float t; |
|
union |
|
{ |
|
HidingSpot *spot; |
|
unsigned int id; |
|
}; |
|
}; |
|
|
|
typedef CUtlLinkedList<SpotOrder, int> 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 CUtlLinkedList<SpotEncounter *, int> SpotEncounterList; |
|
typedef CUtlLinkedList<CNavArea *, int> 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 = NULL) 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].Count(); } // 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 |
|
bool IsVisible (const Vector &eye, Vector *visSpot = NULL) const; // return true if area is visible from the given eyepoint, return visible spot |
|
|
|
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 = NULL, CNavArea **outBeta = NULL); // 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].AddToTail(ladder); } |
|
void AddLadderDown(CNavLadder *ladder) { m_ladder[LADDER_DOWN].AddToTail (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 |
|
{ |
|
int iter; |
|
for (iter = m_connect[dir].Head (); iter != m_connect[dir].InvalidIndex (); iter = m_connect[dir].Next (iter)) |
|
{ |
|
if (i == 0) |
|
return m_connect[dir][iter].area; |
|
--i; |
|
} |
|
return NULL; |
|
} |
|
|
|
inline bool CNavArea::IsOpen() const |
|
{ |
|
return (m_openMarker == m_masterMarker) ? true : false; |
|
} |
|
|
|
inline bool CNavArea::IsOpenListEmpty() |
|
{ |
|
return (m_openList != NULL) ? false : true; |
|
} |
|
|
|
inline CNavArea *CNavArea::PopOpenList() |
|
{ |
|
if (m_openList) |
|
{ |
|
CNavArea *area = m_openList; |
|
|
|
// disconnect from list |
|
area->RemoveFromOpenList(); |
|
return area; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
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; |
|
|
|
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 == NULL) |
|
{ |
|
// 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 = NULL) |
|
{ |
|
if (closestArea) |
|
*closestArea = NULL; |
|
|
|
if (startArea == NULL) |
|
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 == NULL && goalPos == NULL) |
|
{ |
|
return false; |
|
} |
|
|
|
startArea->SetParent(NULL); |
|
|
|
// if we are already in the goal area, build trivial path |
|
if (startArea == goalArea) |
|
{ |
|
goalArea->SetParent(NULL); |
|
|
|
if (closestArea) |
|
*closestArea = goalArea; |
|
|
|
return true; |
|
} |
|
|
|
// determine actual goal position |
|
Vector actualGoalPos = (goalPos != NULL) ? (*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()); |
|
|
|
float initCost = costFunc(startArea, NULL, NULL); |
|
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); |
|
int floorIter = floorList->Head (); |
|
|
|
bool ladderUp = true; |
|
const NavLadderList *ladderList = NULL; |
|
int ladderIter = NavLadderList::InvalidIndex (); |
|
enum |
|
{ |
|
AHEAD = 0, |
|
LEFT, |
|
RIGHT, |
|
BEHIND, |
|
NUM_TOP_DIRECTIONS |
|
}; |
|
|
|
int ladderTopDir; |
|
while (true) |
|
{ |
|
CNavArea *newArea; |
|
NavTraverseType how; |
|
const CNavLadder *ladder = NULL; |
|
|
|
// 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->InvalidIndex ()) |
|
{ |
|
++dir; |
|
|
|
if (dir == NUM_DIRECTIONS) |
|
{ |
|
// checked all directions on floor - check ladders next |
|
searchFloor = false; |
|
|
|
ladderList = area->GetLadderList(LADDER_UP); |
|
ladderIter = ladderList->Head (); |
|
ladderTopDir = AHEAD; |
|
} |
|
else |
|
{ |
|
// start next direction |
|
floorList = area->GetAdjacentList((NavDirType)dir); |
|
floorIter = floorList->Head(); |
|
} |
|
continue; |
|
} |
|
|
|
newArea = floorList->Element (floorIter).area; |
|
how = (NavTraverseType)dir; |
|
floorIter = floorList->Next (floorIter); |
|
} |
|
// search ladders |
|
else |
|
{ |
|
if (ladderIter == ladderList->InvalidIndex ()) |
|
{ |
|
if (!ladderUp) |
|
{ |
|
// checked both ladder directions - done |
|
break; |
|
} |
|
else |
|
{ |
|
// check down ladders |
|
ladderUp = false; |
|
ladderList = area->GetLadderList(LADDER_DOWN); |
|
ladderIter = ladderList->Head (); |
|
} |
|
continue; |
|
} |
|
|
|
if (ladderUp) |
|
{ |
|
ladder = ladderList->Element (ladderIter); |
|
|
|
// cannot use this ladder if the ladder bottom is hanging above our head |
|
if (ladder->m_isDangling) |
|
{ |
|
ladderIter = ladderList->Next (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 = ladderList->Next (ladderIter); |
|
continue; |
|
} |
|
|
|
how = GO_LADDER_UP; |
|
++ladderTopDir; |
|
} |
|
else |
|
{ |
|
newArea = ladderList->Element (ladderIter)->m_bottomArea; |
|
how = GO_LADDER_DOWN; |
|
ladder = ladderList->Element (ladderIter); |
|
ladderIter = ladderList->Next (ladderIter); |
|
} |
|
|
|
if (newArea == NULL) |
|
continue; |
|
} |
|
|
|
// don't backtrack |
|
if (newArea == area) |
|
continue; |
|
|
|
float 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 |
|
float 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> |
|
float NavAreaTravelDistance(CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc) |
|
{ |
|
if (startArea == NULL) |
|
return -1.0f; |
|
|
|
if (endArea == NULL) |
|
return -1.0f; |
|
|
|
if (startArea == endArea) |
|
return 0.0f; |
|
|
|
// compute path between areas using given cost heuristic |
|
if (NavAreaBuildPath(startArea, endArea, NULL, costFunc) == false) |
|
return -1.0f; |
|
|
|
// compute distance along path |
|
float 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 == NULL || startPos == NULL || goalPos == NULL) |
|
return -1.0f; |
|
|
|
// compute path between areas using given cost heuristic |
|
CNavArea *goalArea = NULL; |
|
if (NavAreaBuildPath(startArea, TheNavAreaGrid.GetNearestNavArea(goalPos), goalPos, costFunc, &goalArea) == false) |
|
return -1.0f; |
|
|
|
if (goalArea == NULL) |
|
return -1.0f; |
|
|
|
// compute distance along path |
|
if (goalArea->GetParent() == NULL) |
|
{ |
|
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 == NULL) |
|
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 == NULL || startPos == NULL) |
|
return; |
|
|
|
CNavArea::MakeNewMarker(); |
|
CNavArea::ClearSearchLists(); |
|
|
|
startArea->AddToOpenList(); |
|
startArea->SetTotalCost(0.0f); |
|
startArea->SetCostSoFar(0.0f); |
|
startArea->SetParent(NULL); |
|
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 |
|
|
|
// check up ladders |
|
const NavLadderList *ladderList = area->GetLadderList (LADDER_UP); |
|
if (ladderList) |
|
{ |
|
FOR_EACH_LL ((*ladderList), it) |
|
{ |
|
const CNavLadder *ladder = (*ladderList)[it]; |
|
|
|
// 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_EACH_LL ((*ladderList), it) |
|
{ |
|
const CNavLadder *ladder = (*ladderList)[it]; |
|
|
|
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) |
|
{ |
|
FOR_EACH_LL (TheNavAreaList, it) |
|
{ |
|
CNavArea *area = TheNavAreaList[it]; |
|
if (func(area) == false) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// Fuctor that returns lowest cost for farthest away areas |
|
// For use with FindMinimumCostArea() |
|
NOXREF 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; |
|
|
|
FOR_EACH_LL (TheNavAreaList, it) |
|
{ |
|
CNavArea *area = TheNavAreaList[it]; |
|
|
|
// 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, NULL); |
|
|
|
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.Count(); |
|
int which = RANDOM_LONG(0, numAreas - 1); |
|
|
|
FOR_EACH_LL (TheNavAreaList, it) |
|
{ |
|
if (which-- == 0) |
|
return TheNavAreaList[it]; |
|
} |
|
|
|
return cheapAreaSet[RANDOM_LONG (0, cheapAreaSetCount - 1)].area; |
|
} |
|
} |
|
|
|
bool IsHidingSpotInCover(const Vector *spot); |
|
void ClassifySniperSpot(HidingSpot *spot); |
|
void DestroyHidingSpots(); |
|
void EditNavAreas(NavEditCmdType cmd); |
|
bool GetGroundHeight(const Vector *pos, float *height, Vector *normal = NULL); |
|
bool GetSimpleGroundHeight(const Vector *pos, float *height, Vector *normal = NULL); |
|
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 = NULL, int ignoreTeam = 0); |
|
const Vector *FindRandomHidingSpot(CBaseEntity *me, Place place, bool isSniper = false); |
|
HidingSpot *GetHidingSpotByID(unsigned int id); |
|
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(); |
|
|
|
extern NavLadderList TheNavLadderList; |
|
extern HidingSpotList TheHidingSpotList; |
|
extern NavAreaList TheNavAreaList; |
|
extern CNavAreaGrid TheNavAreaGrid; |
|
|
|
#endif // NAV_AREA_H
|
|
|