mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-25 13:41:34 +00:00
5508 lines
153 KiB
C++
5508 lines
153 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
// RAVEN SOFTWARE - STAR WARS: JK II
|
|
// (c) 2002 Activision
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#include "g_headers.h"
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// HFile Bindings
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool HFILEopen_read(int& handle, const char* filepath) {gi.FS_FOpenFile(filepath, &handle, FS_READ); return (handle!=0);}
|
|
bool HFILEopen_write(int& handle, const char* filepath) {gi.FS_FOpenFile(filepath, &handle, FS_WRITE); return (handle!=0);}
|
|
bool HFILEread(int& handle, void* data, int size) {return (gi.FS_Read(data, size, handle)!=0);}
|
|
bool HFILEwrite(int& handle, const void* data, int size) {return (gi.FS_Write(data, size, handle)!=0);}
|
|
bool HFILEclose(int& handle) {gi.FS_FCloseFile(handle); return true;}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Externs
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
extern gentity_t* G_FindDoorTrigger( gentity_t *ent );
|
|
extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
|
|
extern qboolean G_CheckInSolidTeleport (const vec3_t& teleportPos, gentity_t *self);
|
|
|
|
extern cvar_t* g_nav1;
|
|
extern cvar_t* g_nav2;
|
|
extern cvar_t* g_developer;
|
|
extern int delayedShutDown;
|
|
extern vec3_t playerMinsStep;
|
|
extern vec3_t playerMaxs;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Includes
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#include "b_local.h"
|
|
#include "g_navigator.h"
|
|
#if !defined(RAGL_GRAPH_VS_INC)
|
|
#include "../Ragl/graph_vs.h"
|
|
#endif
|
|
#if !defined(RATL_GRAPH_REGION_INC)
|
|
#include "../Ragl/graph_region.h"
|
|
#endif
|
|
//#if !defined(RATL_GRAPH_TRIANGULATE_INC)
|
|
// #include "..\Ragl\graph_triangulate.h"
|
|
//#endif
|
|
#if !defined(RATL_VECTOR_VS_INC)
|
|
#include "../Ratl/vector_vs.h"
|
|
#endif
|
|
#if !defined(RUFL_HSTRING_INC)
|
|
#include "../Rufl/hstring.h"
|
|
#endif
|
|
#if !defined(RUFL_HFILE_INC)
|
|
#include "../Rufl/hfile.h"
|
|
#endif
|
|
#if !defined(RAVL_BOUNDS_INC)
|
|
#include "../Ravl/CBounds.h"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Defines
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#define NAV_VERSION 1.3f
|
|
#define NEIGHBORING_DIST 200.0f
|
|
#define SAFE_NEIGHBORINGPOINT_DIST 400.0f
|
|
#define SAFE_AT_NAV_DIST_SQ 6400.0f //80*80
|
|
#define SAFE_GOTO_DIST_SQ 19600.0f //140*140
|
|
|
|
namespace NAV
|
|
{
|
|
enum
|
|
{
|
|
#ifdef _XBOX
|
|
// now 11 bytes each
|
|
NUM_NODES = 1024, // Question for VV- is this big enough for all the levels? if so, we should use it too...
|
|
#else
|
|
NUM_NODES = 1024,
|
|
#endif
|
|
// now 5 bytes each
|
|
NUM_EDGES = 3*NUM_NODES,
|
|
NUM_EDGES_PER_NODE = 20,
|
|
|
|
|
|
NUM_REGIONS = NUM_NODES/3, // Had to raise this up for bounty
|
|
NUM_CELLS = 32, // should be the square root of NUM_NODES
|
|
NUM_NODES_PER_CELL = 60, // had to raise this for t3_bounty
|
|
NUM_TARGETS = 5, // max number of outgoing edges from a given node
|
|
|
|
CELL_RANGE = 1000,
|
|
VIEW_RANGE = 550,
|
|
|
|
BIAS_NONWAYPOINT = 500,
|
|
BIAS_DANGER = 8000,
|
|
BIAS_TOOSMALL = 10000,
|
|
|
|
NULL_PATH_USER_INDEX= -1,
|
|
#ifdef _XBOX
|
|
// This may not be safe, but I REALLY need memory. Better testing will reveal that
|
|
// these don't work, but in quick tests these numbers were sufficient.
|
|
MAX_PATH_USERS = 60,
|
|
MAX_PATH_SIZE = 50,
|
|
#else
|
|
MAX_PATH_USERS = 100,
|
|
MAX_PATH_SIZE = NUM_NODES/7,
|
|
#endif
|
|
|
|
Z_CULL_OFFSET = 60,
|
|
|
|
MAX_NODES_PER_NAME = 30,
|
|
MAX_EDGE_SEG_LEN = 100,
|
|
MAX_EDGE_FLOOR_DIST = 60,
|
|
MAX_EDGE_AUTO_LEN = 500,
|
|
|
|
MAX_EDGES_PER_ENT = 10,
|
|
MAX_BLOCKING_ENTS = 100,
|
|
MAX_ALERTS_PER_AGENT= 10,
|
|
MAX_ALERT_TIME = 10000,
|
|
|
|
MIN_WAY_NEIGHBORS = 4,
|
|
MAX_NONWP_NEIGHBORS = 1,
|
|
|
|
|
|
// Human Sized
|
|
//-------------
|
|
SC_MEDIUM_RADIUS = 20,
|
|
SC_MEDIUM_HEIGHT = 60,
|
|
|
|
// Rancor Sized
|
|
//--------------
|
|
SC_LARGE_RADIUS = 60,
|
|
SC_LARGE_HEIGHT = 120,
|
|
|
|
|
|
SAVE_LOAD = 0,
|
|
|
|
CHECK_JUMP = 0,
|
|
CHECK_START_OPEN = 1,
|
|
CHECK_START_SOLID = 1,
|
|
};
|
|
}
|
|
|
|
namespace STEER
|
|
{
|
|
enum
|
|
{
|
|
NULL_STEER_USER_INDEX= -1,
|
|
MAX_NEIGHBORS = 20,
|
|
Z_CULL_OFFSET = 60,
|
|
SIDE_LOCKED_TIMER = 2000,
|
|
NEIGHBOR_RANGE = 60,
|
|
};
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Total Memory - 11 Bytes (can save 5 bytes by removing Name and Targets)
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
class CWayNode
|
|
{
|
|
public:
|
|
CVec3 mPoint;
|
|
float mRadius;
|
|
NAV::EPointType mType;
|
|
hstring mName; // TODO OPTIMIZATION: Remove This?
|
|
hstring mTargets[NAV::NUM_TARGETS]; // TODO OPTIMIZATION: Remove This
|
|
enum EWayNodeFlags
|
|
{
|
|
WN_NONE = 0,
|
|
WN_ISLAND,
|
|
WN_FLOATING,
|
|
WN_DROPTOFLOOR,
|
|
WN_NOAUTOCONNECT,
|
|
WN_MAX
|
|
};
|
|
ratl::bits_vs<WN_MAX> mFlags;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Access Operator (For Cells)(For Triangulation)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float operator[](int dimension)
|
|
{
|
|
return mPoint[dimension];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Left Right Test (For Triangulation)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual ESide LRTest(const CWayNode& A, const CWayNode& B) const
|
|
{
|
|
return (mPoint.LRTest(A.mPoint, B.mPoint));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Point In Circle (For Triangulation)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual bool InCircle(const CWayNode& A, const CWayNode& B, const CWayNode& C) const
|
|
{
|
|
return (mPoint.PtInCircle(A.mPoint, B.mPoint, C.mPoint));
|
|
}
|
|
};
|
|
const CWayNode& GetNode(int Handle);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Total Memory - 5 bytes
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
class CWayEdge
|
|
{
|
|
public:
|
|
int mNodeA; // DO NOT REMOVE THIS: Handles are full ints because upper bits are used
|
|
int mNodeB; // DO NOT REMOVE THIS: Handles are full ints because upper bits are used
|
|
float mDistance; // DO NOT REMOVE THIS: It's a serious runtime optimization for A*
|
|
|
|
unsigned short mOwnerNum; // Converted to short. Largest entity number is 1024
|
|
unsigned short mEntityNum; // Converted to short. Largest entity number is 1024
|
|
enum EWayEdgeFlags
|
|
{
|
|
WE_NONE = 0,
|
|
|
|
WE_SIZE_MEDIUM,
|
|
WE_SIZE_LARGE,
|
|
|
|
WE_BLOCKING_DOOR,
|
|
WE_BLOCKING_WALL,
|
|
WE_BLOCKING_BREAK,
|
|
|
|
WE_VALID,
|
|
WE_ONHULL,
|
|
WE_FLYING,
|
|
WE_JUMPING,
|
|
WE_CANBEINVAL,
|
|
WE_DESIGNERPLACED,
|
|
|
|
WE_MAX
|
|
};
|
|
ratl::bits_vs<WE_MAX> mFlags; // Should be only one int
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Size Function
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
inline int Blocking()
|
|
{
|
|
if (mFlags.get_bit(WE_BLOCKING_BREAK))
|
|
{
|
|
return WE_BLOCKING_BREAK;
|
|
}
|
|
if (mFlags.get_bit(WE_BLOCKING_WALL))
|
|
{
|
|
return WE_BLOCKING_WALL;
|
|
}
|
|
if (mFlags.get_bit(WE_BLOCKING_DOOR))
|
|
{
|
|
return WE_BLOCKING_DOOR;
|
|
}
|
|
return 0;
|
|
}
|
|
inline bool BlockingBreakable() {return (mFlags.get_bit(WE_BLOCKING_BREAK));}
|
|
inline bool BlockingWall() {return (mFlags.get_bit(WE_BLOCKING_WALL));}
|
|
inline bool BlockingDoor() {return (mFlags.get_bit(WE_BLOCKING_DOOR));}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Size Function
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
inline int Size() const
|
|
{
|
|
return (mFlags.get_bit(WE_SIZE_MEDIUM)?(WE_SIZE_MEDIUM):(WE_SIZE_LARGE));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Access Operator (For Cells)(For Triangulation)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float operator[](int dimension) const
|
|
{
|
|
CVec3 Half(GetNode(mNodeA).mPoint + GetNode(mNodeB).mPoint);
|
|
return (Half[dimension] * 0.5f);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Point - returns the center of the edge
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void Point(CVec3& Half) const
|
|
{
|
|
Half = GetNode(mNodeA).mPoint;
|
|
Half += GetNode(mNodeB).mPoint;
|
|
Half *= 0.5f;
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// GetPoint A
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
const CVec3& PointA() const
|
|
{
|
|
return GetNode(mNodeA).mPoint;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// GetPoint B
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
const CVec3& PointB() const
|
|
{
|
|
return GetNode(mNodeB).mPoint;
|
|
}
|
|
|
|
};
|
|
const CWayEdge& GetEdge(int Handle);
|
|
|
|
|
|
struct SNodeSort
|
|
{
|
|
NAV::TNodeHandle mHandle;
|
|
float mDistance;
|
|
bool mInRadius;
|
|
|
|
|
|
bool operator < (const SNodeSort& other) const
|
|
{
|
|
return (mDistance<other.mDistance);
|
|
}
|
|
};
|
|
|
|
struct SEntSize
|
|
{
|
|
float mRadius;
|
|
float mHeight;
|
|
};
|
|
|
|
struct SDangerAlert
|
|
{
|
|
int mHandle;
|
|
float mDanger;
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Defines
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
typedef ragl::graph_vs <CWayNode, NAV::NUM_NODES, CWayEdge, NAV::NUM_EDGES, NAV::NUM_EDGES_PER_NODE> TGraph;
|
|
typedef ragl::graph_region <CWayNode, NAV::NUM_NODES, CWayEdge, NAV::NUM_EDGES, NAV::NUM_EDGES_PER_NODE, NAV::NUM_REGIONS, NAV::NUM_REGIONS> TGraphRegion;
|
|
typedef TGraph::cells <NAV::NUM_NODES_PER_CELL, NAV::NUM_CELLS, NAV::NUM_CELLS> TGraphCells;
|
|
typedef ratl::vector_vs <SNodeSort, NAV::NUM_NODES_PER_CELL> TNearestNavSort;
|
|
|
|
typedef ratl::array_vs <SDangerAlert, NAV::MAX_ALERTS_PER_AGENT> TAlertList;
|
|
typedef ratl::array_vs <TAlertList, MAX_GENTITIES> TEntityAlertList;
|
|
typedef ratl::vector_vs <NAV::TNodeHandle, NAV::MAX_NODES_PER_NAME> TNamedNodeList;
|
|
typedef ratl::map_vs <hstring, TNamedNodeList, NAV::NUM_NODES> TNameToNodeMap;
|
|
|
|
typedef ratl::vector_vs <NAV::TEdgeHandle, NAV::MAX_EDGES_PER_ENT> TEdgesPerEnt;
|
|
typedef ratl::map_vs <int, TEdgesPerEnt, NAV::MAX_BLOCKING_ENTS> TEntEdgeMap;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Path Point
|
|
//
|
|
// This is actual vector and speed location (as well as node handle of that the agent
|
|
// has planned to go to.
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
struct SPathPoint
|
|
{
|
|
CVec3 mPoint;
|
|
float mSpeed;
|
|
float mSlowingRadius;
|
|
float mReachedRadius;
|
|
float mDist;
|
|
float mETA;
|
|
NAV::TNodeHandle mNode;
|
|
};
|
|
typedef ratl::vector_vs <SPathPoint, NAV::MAX_PATH_SIZE> TPath;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Path User
|
|
//
|
|
// This is the cached path for a given actor
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
struct SPathUser
|
|
{
|
|
int mEnd;
|
|
bool mSuccess;
|
|
int mLastUseTime;
|
|
int mLastAStarTime;
|
|
TPath mPath;
|
|
};
|
|
typedef ratl::pool_vs<SPathUser, NAV::MAX_PATH_USERS> TPathUsers;
|
|
typedef ratl::array_vs<int, MAX_GENTITIES> TPathUserIndex;
|
|
|
|
|
|
typedef ratl::vector_vs<gentity_t*, STEER::MAX_NEIGHBORS> TNeighbors;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Steer User
|
|
//
|
|
// This is the cached steering data for a given actor
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
struct SSteerUser
|
|
{
|
|
// Constant Values In Entity
|
|
//---------------------------
|
|
float mMaxForce;
|
|
float mMaxSpeed;
|
|
float mRadius;
|
|
float mMass;
|
|
|
|
|
|
// Current Values
|
|
//----------------
|
|
TNeighbors mNeighbors;
|
|
|
|
CVec3 mOrientation;
|
|
CVec3 mPosition;
|
|
|
|
CVec3 mVelocity;
|
|
float mSpeed;
|
|
|
|
|
|
// Values Projected From Current Values
|
|
//--------------------------------------
|
|
CVec3 mProjectFwd;
|
|
CVec3 mProjectSide;
|
|
CVec3 mProjectPath;
|
|
|
|
|
|
// Temporary Values
|
|
//------------------
|
|
CVec3 mDesiredVelocity;
|
|
float mDesiredSpeed;
|
|
float mDistance;
|
|
CVec3 mSeekLocation;
|
|
|
|
int mIgnoreEntity;
|
|
|
|
bool mBlocked;
|
|
int mBlockedTgtEntity;
|
|
CVec3 mBlockedTgtPosition;
|
|
|
|
// Steering
|
|
//----------
|
|
CVec3 mSteering;
|
|
float mNewtons;
|
|
};
|
|
|
|
typedef ratl::pool_vs<SSteerUser, 4> TSteerUsers;
|
|
typedef ratl::array_vs<int, MAX_GENTITIES> TSteerUserIndex;
|
|
typedef ratl::bits_vs<MAX_GENTITIES> TEntBits;
|
|
|
|
|
|
TAlertList& GetAlerts(gentity_t* actor);
|
|
TGraph& GetGraph();
|
|
int GetAirRegion();
|
|
int GetIslandRegion();
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// The Graph User
|
|
//
|
|
// Here we define our own user class, which can invalidate edges and generate unique
|
|
// costs for node traversal based on the nodes, and possibly the actor attempting to
|
|
// cross the nodes at any given time.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
class CGraphUser : public TGraph::user
|
|
{
|
|
private:
|
|
gentity_t* mActor;
|
|
int mActorSize;
|
|
|
|
CVec3 mDangerSpot;
|
|
float mDangerSpotRadiusSq;
|
|
|
|
|
|
public:
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void SetActor(gentity_t* actor)
|
|
{
|
|
mActor = actor;
|
|
mActorSize = NAV::ClassifyEntSize(actor);
|
|
mDangerSpotRadiusSq = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void ClearActor()
|
|
{
|
|
mActor = 0;
|
|
mActorSize = 0;
|
|
mDangerSpotRadiusSq = 0;
|
|
}
|
|
|
|
gentity_t* GetActor()
|
|
{
|
|
return mActor;
|
|
}
|
|
|
|
void SetDangerSpot(const CVec3& Spot, float RadiusSq)
|
|
{
|
|
mDangerSpot = Spot;
|
|
mDangerSpotRadiusSq = RadiusSq;
|
|
}
|
|
void ClearDangerSpot()
|
|
{
|
|
mDangerSpotRadiusSq = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual bool can_be_invalid(const CWayEdge& Edge) const
|
|
{
|
|
return (Edge.mFlags.get_bit(CWayEdge::WE_CANBEINVAL));
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual bool is_valid(CWayEdge& Edge, int EndPoint=0) const
|
|
{
|
|
// If The Actor Can't Fly, But This Is A Flying Edge, It's Invalid
|
|
//-----------------------------------------------------------------
|
|
if (mActor && Edge.mFlags.get_bit(CWayEdge::WE_FLYING) && mActor->NPC && !(mActor->NPC->scriptFlags&SCF_NAV_CAN_FLY))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If The Actor Can't Fly, But This Is A Flying Edge, It's Invalid
|
|
//-----------------------------------------------------------------
|
|
if (mActor && Edge.mFlags.get_bit(CWayEdge::WE_JUMPING) && mActor->NPC && !(mActor->NPC->scriptFlags&SCF_NAV_CAN_JUMP))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// If The Actor Is Too Big, This Is Not A Valid Edge For Him
|
|
//-----------------------------------------------------------
|
|
if (mActor && Edge.Size()<mActorSize &&
|
|
(EndPoint!=-1) /*&& // Don't count the last edge on the path as invalid because of size
|
|
(!EndPoint || (EndPoint!=Edge.mNodeA && EndPoint!=Edge.mNodeB))*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Edge.mEntityNum!=ENTITYNUM_NONE)
|
|
{
|
|
gentity_t* ent = &g_entities[Edge.mEntityNum];
|
|
if (ent)
|
|
{
|
|
// Can The Actor Navigate Through The Breakable Entity?
|
|
//------------------------------------------------------
|
|
if ((mActor) &&
|
|
(mActor->NPC) &&
|
|
(mActor->NPC->aiFlags&NPCAI_NAV_THROUGH_BREAKABLES) &&
|
|
(Edge.BlockingBreakable()) &&
|
|
(G_EntIsBreakable(Edge.mEntityNum, mActor))
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Is This A Door?
|
|
//-----------------
|
|
if (Edge.BlockingDoor())
|
|
{
|
|
bool StartOpen = (ent->spawnflags & 1);
|
|
bool Closed = (StartOpen)?(ent->moverState==MOVER_POS2):(ent->moverState==MOVER_POS1);
|
|
|
|
// If It Is Closed, We Want To Check If It Will Auto Open For Us
|
|
//---------------------------------------------------------------
|
|
if (Closed)
|
|
{
|
|
gentity_t* owner = &g_entities[Edge.mOwnerNum];
|
|
if (owner)
|
|
{
|
|
// Check To See If The Owner Is Inactive Or Locked, Or Unavailable To The NPC
|
|
//----------------------------------------------------------------------------
|
|
if ((owner->svFlags & SVF_INACTIVE) ||
|
|
(owner==ent && (owner->spawnflags & (MOVER_PLAYER_USE|MOVER_FORCE_ACTIVATE|MOVER_LOCKED))) ||
|
|
(owner!=ent && (owner->spawnflags & (1 /*PLAYERONLY*/|4 /*USE_BOTTON*/))))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// Look For A Key
|
|
//----------------
|
|
if (mActor!=0 && (owner->spawnflags & MOVER_GOODIE))
|
|
{
|
|
int key = INV_GoodieKeyCheck(mActor);
|
|
if (!key)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No Owner? This Must Be A Scripted Door Or Other Contraption
|
|
//--------------------------------------------------------------
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// If This Is A Wall, Check If It Has Contents Now
|
|
//-------------------------------------------------
|
|
else if (Edge.BlockingWall())
|
|
{
|
|
return !(ent->contents&CONTENTS_SOLID);
|
|
}
|
|
}
|
|
}
|
|
else if ( Edge.BlockingBreakable())
|
|
{//we had a breakable in our way, now it's gone, see if there is anything else in the way
|
|
if ( NAV::TestEdge( Edge.mNodeA, Edge.mNodeB, false ) )
|
|
{//clear it
|
|
Edge.mFlags.clear_bit(CWayEdge::WE_BLOCKING_BREAK);
|
|
}
|
|
//NOTE: if this fails with the SC_LARGE size
|
|
}
|
|
|
|
return (Edge.mFlags.get_bit(CWayEdge::WE_VALID));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// This is the cost estimate from any node to any other node (usually the goal)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual float cost(const CWayNode& A, const CWayNode& B) const
|
|
{
|
|
return (A.mPoint.Dist(B.mPoint));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// This is the cost estimate for traversing a particular edge
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual float cost(const CWayEdge& Edge, const CWayNode& B) const
|
|
{
|
|
float DangerBias = 0.0f;
|
|
if (mActor)
|
|
{
|
|
int eHandle = GetGraph().edge_index(Edge);
|
|
TAlertList& al = GetAlerts(mActor);
|
|
for (int alIndex=0; alIndex<TAlertList::CAPACITY; alIndex++)
|
|
{
|
|
if (al[alIndex].mHandle==eHandle && al[alIndex].mDanger>0.0f)
|
|
{
|
|
DangerBias += (al[alIndex].mDanger*NAV::BIAS_DANGER);
|
|
}
|
|
}
|
|
|
|
// If The Actor Is Too Big, Bias This Edge For Him
|
|
//-------------------------------------------------
|
|
if (Edge.Size()<mActorSize)
|
|
{
|
|
//DangerBias += NAV::BIAS_TOOSMALL;
|
|
}
|
|
}
|
|
|
|
if (mDangerSpotRadiusSq > mDangerSpot.DistToLine2(Edge.PointA(), Edge.PointB()))
|
|
{
|
|
DangerBias += NAV::BIAS_DANGER;
|
|
}
|
|
|
|
|
|
if (B.mType==NAV::PT_WAYNODE)
|
|
{
|
|
return (Edge.mDistance + DangerBias);
|
|
}
|
|
return ((Edge.mDistance + DangerBias) + NAV::BIAS_NONWAYPOINT);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual bool on_same_floor(const CWayNode& A, const CWayNode& B) const
|
|
{
|
|
return (fabsf(A.mPoint[2] - B.mPoint[2])<100.0f);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// setup the edge (For Triangulation)
|
|
//
|
|
// This function is here because it evaluates the base cost from NodeA to NodeB, which
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
virtual void setup_edge(CWayEdge& Edge, int A, int B, bool OnHull, const CWayNode& NodeA, const CWayNode& NodeB, bool CanBeInvalid=false)
|
|
{
|
|
Edge.mNodeA = A;
|
|
Edge.mNodeB = B;
|
|
Edge.mDistance = NodeA.mPoint.Dist(NodeB.mPoint);
|
|
Edge.mEntityNum = ENTITYNUM_NONE;
|
|
Edge.mOwnerNum = ENTITYNUM_NONE;
|
|
Edge.mFlags.clear();
|
|
Edge.mFlags.set_bit(CWayEdge::WE_VALID);
|
|
if (CanBeInvalid)
|
|
{
|
|
Edge.mFlags.set_bit(CWayEdge::WE_CANBEINVAL);
|
|
}
|
|
if (OnHull)
|
|
{
|
|
Edge.mFlags.set_bit(CWayEdge::WE_ONHULL);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// The Global Public Objects
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
TGraph mGraph;
|
|
TGraphRegion mRegion(mGraph);
|
|
TGraphCells mCells(mGraph);
|
|
|
|
TGraph::search mSearch;
|
|
CGraphUser mUser;
|
|
|
|
|
|
TNameToNodeMap mNodeNames;
|
|
TEntEdgeMap mEntEdgeMap;
|
|
|
|
TNearestNavSort mNearestNavSort;
|
|
|
|
TPathUsers mPathUsers;
|
|
TPathUserIndex mPathUserIndex;
|
|
SPathUser mPathUserMaster;
|
|
|
|
TSteerUsers mSteerUsers;
|
|
TSteerUserIndex mSteerUserIndex;
|
|
|
|
TEntityAlertList mEntityAlertList;
|
|
|
|
vec3_t mZeroVec;
|
|
trace_t mMoveTrace;
|
|
trace_t mViewTrace;
|
|
|
|
int mMoveTraceCount = 0;
|
|
int mViewTraceCount = 0;
|
|
int mConnectTraceCount = 0;
|
|
int mConnectTime = 0;
|
|
int mIslandCount = 0;
|
|
int mIslandRegion = 0;
|
|
int mAirRegion = 0;
|
|
char mLocStringA[256] = {0};
|
|
char mLocStringB[256] = {0};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
TAlertList& GetAlerts(gentity_t* actor)
|
|
{
|
|
return mEntityAlertList[actor->s.number];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
TGraph& GetGraph()
|
|
{
|
|
return mGraph;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
const CWayNode& GetNode(int Handle)
|
|
{
|
|
return mGraph.get_node(Handle);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
const CWayEdge& GetEdge(int Handle)
|
|
{
|
|
return mGraph.get_edge(Handle);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
int GetAirRegion()
|
|
{
|
|
return mAirRegion;
|
|
}
|
|
int GetIslandRegion()
|
|
{
|
|
return mIslandRegion;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Helper Function : View Trace
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool ViewTrace(const CVec3& a, const CVec3& b)
|
|
{
|
|
int contents = (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP);
|
|
|
|
mViewTraceCount++;
|
|
gi.trace(&mViewTrace, a.v, 0, 0, b.v, ENTITYNUM_NONE, contents);
|
|
|
|
if ((mViewTrace.allsolid==qfalse) && (mViewTrace.startsolid==qfalse ) && (mViewTrace.fraction==1.0f))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Helper Function : View Trace
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool ViewNavTrace(const CVec3& a, const CVec3& b)
|
|
{
|
|
int contents = (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP);
|
|
|
|
mViewTraceCount++;
|
|
gi.trace(&mViewTrace, a.v, 0, 0, b.v, ENTITYNUM_NONE, contents);
|
|
|
|
if ((mViewTrace.allsolid==qfalse) && (mViewTrace.startsolid==qfalse ) && (mViewTrace.fraction==1.0f))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Helper Function : Move Trace
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool MoveTrace(const CVec3& Start, const CVec3& Stop, const CVec3& Mins, const CVec3& Maxs,
|
|
int IgnoreEnt=0,
|
|
bool CheckForDoNotEnter=false,
|
|
bool RetryIfStartInDoNotEnter=true,
|
|
bool IgnoreAllEnts=false,
|
|
int OverrideContents=0)
|
|
{
|
|
int contents = (MASK_NPCSOLID);
|
|
if (OverrideContents)
|
|
{
|
|
contents = OverrideContents;
|
|
}
|
|
if (CheckForDoNotEnter)
|
|
{
|
|
contents |= CONTENTS_BOTCLIP;
|
|
}
|
|
if (IgnoreAllEnts)
|
|
{
|
|
contents &= ~CONTENTS_BODY;
|
|
}
|
|
|
|
|
|
// Run The Trace
|
|
//---------------
|
|
mMoveTraceCount++;
|
|
gi.trace(&mMoveTrace, Start.v, Mins.v, Maxs.v, Stop.v, IgnoreEnt, contents);
|
|
|
|
|
|
// Did It Make It?
|
|
//-----------------
|
|
if ((mMoveTrace.allsolid==qfalse) && (mMoveTrace.startsolid==qfalse ) && (mMoveTrace.fraction==1.0f))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If We Started In Solid, Try Removing The "Do Not Enter" Contents Type, And Trace Again
|
|
//----------------------------------------------------------------------------------------
|
|
if (CheckForDoNotEnter && RetryIfStartInDoNotEnter && ((mMoveTrace.allsolid==qtrue) || (mMoveTrace.startsolid==qtrue)))
|
|
{
|
|
contents &= ~CONTENTS_BOTCLIP;
|
|
|
|
// Run The Trace
|
|
//---------------
|
|
mMoveTraceCount++;
|
|
gi.trace(&mMoveTrace, Start.v, Mins.v, Maxs.v, Stop.v, IgnoreEnt, contents);
|
|
|
|
// Did It Make It?
|
|
//-----------------
|
|
if ((mMoveTrace.allsolid==qfalse) && (mMoveTrace.startsolid==qfalse ) && (mMoveTrace.fraction==1.0f))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Helper Function : Move Trace (with actor)
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool MoveTrace(gentity_t* actor, const CVec3& goalPosition, bool IgnoreAllEnts=false)
|
|
{
|
|
assert(actor!=0);
|
|
CVec3 Mins(actor->mins);
|
|
CVec3 Maxs(actor->maxs);
|
|
|
|
Mins[2] += (STEPSIZE*1);
|
|
|
|
return MoveTrace(actor->currentOrigin, goalPosition, Mins, Maxs, actor->s.number, true, true, IgnoreAllEnts/*, actor->contents*/);
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// GoTo
|
|
//
|
|
// This Function serves as a master control for finding, updating, and following a path
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::GoTo(gentity_t* actor, TNodeHandle target, float MaxDangerLevel)
|
|
{
|
|
assert(actor!=0 && actor->client!=0);
|
|
|
|
// Check If We Already Have A Path
|
|
//---------------------------------
|
|
bool HasPath = NAV::HasPath(actor);
|
|
|
|
// If So Update It
|
|
//-----------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = NAV::UpdatePath(actor, target, MaxDangerLevel);
|
|
}
|
|
|
|
// If No Path, Try To Find One
|
|
//-----------------------------
|
|
if (!HasPath)
|
|
{
|
|
HasPath = NAV::FindPath(actor, target, MaxDangerLevel);
|
|
}
|
|
|
|
// If We Have A Path, Now Try To Follow It
|
|
//-----------------------------------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = (STEER::Path(actor)!=0.0f);
|
|
if (HasPath)
|
|
{
|
|
if (STEER::AvoidCollisions(actor, actor->client->leader))
|
|
{
|
|
STEER::Blocked(actor, NAV::NextPosition(actor));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
STEER::Blocked(actor, NAV::GetNodePosition(target));
|
|
}
|
|
}
|
|
|
|
// Nope, No Path At All... Bad
|
|
//------------------------------
|
|
else
|
|
{
|
|
STEER::Blocked(actor, NAV::GetNodePosition(target));
|
|
}
|
|
|
|
return HasPath;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::GoTo(gentity_t* actor, gentity_t* target, float MaxDangerLevel)
|
|
{
|
|
assert(actor!=0 && actor->client!=0);
|
|
|
|
bool HasPath = false;
|
|
TNodeHandle targetNode = GetNearestNode(target, true);
|
|
|
|
// If The Target Has No Nearest Nav Point, Go To His Last Valid Waypoint Instead
|
|
//-------------------------------------------------------------------------------
|
|
if (targetNode==0)
|
|
{
|
|
targetNode = target->lastWaypoint;
|
|
}
|
|
|
|
// Has He EVER Had A Valid Waypoint?
|
|
//-----------------------------------
|
|
if (targetNode!=0)
|
|
{
|
|
// If On An Edge, Pick The Safest Of The Two Points
|
|
//--------------------------------------------------
|
|
if (targetNode<0)
|
|
{
|
|
targetNode = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(targetNode)).mNodeA):(mGraph.get_edge(abs(targetNode)).mNodeB);
|
|
}
|
|
|
|
// Check If We Already Have A Path
|
|
//---------------------------------
|
|
HasPath = NAV::HasPath(actor);
|
|
|
|
// If So Update It
|
|
//-----------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = NAV::UpdatePath(actor, targetNode, MaxDangerLevel);
|
|
}
|
|
|
|
// If No Path, Try To Find One
|
|
//-----------------------------
|
|
if (!HasPath)
|
|
{
|
|
HasPath = NAV::FindPath(actor, targetNode, MaxDangerLevel);
|
|
}
|
|
|
|
// If We Have A Path, Now Try To Follow It
|
|
//-----------------------------------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = (STEER::Path(actor)!=0.0f);
|
|
if (HasPath)
|
|
{
|
|
// Attempt To Avoid Collisions Along The Path
|
|
//--------------------------------------------
|
|
if (STEER::AvoidCollisions(actor, actor->client->leader))
|
|
{
|
|
// Have A Path, Currently Blocked By Something
|
|
//---------------------------------------------
|
|
STEER::Blocked(actor, NAV::NextPosition(actor));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
STEER::Blocked(actor, target);
|
|
}
|
|
}
|
|
|
|
// Nope, No Path At All... Bad
|
|
//------------------------------
|
|
else
|
|
{
|
|
STEER::Blocked(actor, target);
|
|
}
|
|
}
|
|
|
|
// No Waypoint Near
|
|
//------------------
|
|
else
|
|
{
|
|
STEER::Blocked(actor, target);
|
|
}
|
|
|
|
return HasPath;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::GoTo(gentity_t* actor, const vec3_t& position, float MaxDangerLevel)
|
|
{
|
|
assert(actor!=0 && actor->client!=0);
|
|
|
|
bool HasPath = false;
|
|
TNodeHandle targetNode = GetNearestNode(position);
|
|
if (targetNode!=0)
|
|
{
|
|
// If On An Edge, Pick The Safest Of The Two Points
|
|
//--------------------------------------------------
|
|
if (targetNode<0)
|
|
{
|
|
targetNode = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(targetNode)).mNodeA):(mGraph.get_edge(abs(targetNode)).mNodeB);
|
|
}
|
|
|
|
// Check If We Already Have A Path
|
|
//---------------------------------
|
|
HasPath = NAV::HasPath(actor);
|
|
|
|
// If So Update It
|
|
//-----------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = NAV::UpdatePath(actor, targetNode, MaxDangerLevel);
|
|
}
|
|
|
|
// If No Path, Try To Find One
|
|
//-----------------------------
|
|
if (!HasPath)
|
|
{
|
|
HasPath = NAV::FindPath(actor, targetNode, MaxDangerLevel);
|
|
}
|
|
|
|
// If We Have A Path, Now Try To Follow It
|
|
//-----------------------------------------
|
|
if (HasPath)
|
|
{
|
|
HasPath = (STEER::Path(actor)!=0.0f);
|
|
if (HasPath)
|
|
{
|
|
// Attempt To Avoid Collisions Along The Path
|
|
//--------------------------------------------
|
|
if (STEER::AvoidCollisions(actor, actor->client->leader))
|
|
{
|
|
// Have A Path, Currently Blocked By Something
|
|
//---------------------------------------------
|
|
STEER::Blocked(actor, NAV::NextPosition(actor));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
STEER::Blocked(actor, NAV::NextPosition(actor));
|
|
}
|
|
}
|
|
|
|
// Nope, No Path At All... Bad
|
|
//------------------------------
|
|
else
|
|
{
|
|
STEER::Blocked(actor, position);
|
|
}
|
|
}
|
|
|
|
// No Waypoint Near
|
|
//------------------
|
|
else
|
|
{
|
|
STEER::Blocked(actor, position);
|
|
}
|
|
|
|
return HasPath;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// This function exists as a wrapper so that the graph can write to the gi.Printf()
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void stupid_print(const char* data)
|
|
{
|
|
gi.Printf(data);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::LoadFromFile(const char *filename, int checksum)
|
|
{
|
|
mZeroVec[0] = 0;
|
|
mZeroVec[1] = 0;
|
|
mZeroVec[2] = 0;
|
|
|
|
mPathUserIndex.fill(NULL_PATH_USER_INDEX);
|
|
mSteerUserIndex.fill(STEER::NULL_STEER_USER_INDEX);
|
|
|
|
mMoveTraceCount = 0;
|
|
mViewTraceCount = 0;
|
|
mConnectTraceCount = 0;
|
|
mConnectTime = 0;
|
|
mIslandCount = 0;
|
|
mIslandRegion = 0;
|
|
mAirRegion = 0;
|
|
|
|
memset(&mEntityAlertList, 0, sizeof(mEntityAlertList));
|
|
|
|
#if !defined(FINAL_BUILD)
|
|
ratl::ratl_base::OutputPrint = stupid_print;
|
|
#endif
|
|
|
|
mGraph.clear();
|
|
mRegion.clear();
|
|
mCells.clear();
|
|
mNodeNames.clear();
|
|
mNearestNavSort.clear();
|
|
|
|
#ifndef _XBOX
|
|
if (SAVE_LOAD)
|
|
{
|
|
hfile navFile(va("maps/%s.navNEW"));
|
|
if (!navFile.open_read(NAV_VERSION, checksum))
|
|
{
|
|
return false;
|
|
}
|
|
navFile.load(&mGraph, sizeof(mGraph));
|
|
navFile.load(&mRegion, sizeof(mRegion));
|
|
navFile.load(&mCells, sizeof(mCells));
|
|
navFile.close();
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::TestEdge( TNodeHandle NodeA, TNodeHandle NodeB, qboolean IsDebugEdge )
|
|
{
|
|
int atHandle = mGraph.get_edge_across( NodeA, NodeB );
|
|
CWayEdge& at = mGraph.get_edge(atHandle);
|
|
CWayNode& a = mGraph.get_node(at.mNodeA);
|
|
CWayNode& b = mGraph.get_node(at.mNodeB);
|
|
CVec3 Mins(-15.0f, -15.0f, 0.0f); // These were the old "sizeless" defaults
|
|
CVec3 Maxs(15.0f, 15.0f, 40.0f);
|
|
bool CanGo = false;
|
|
bool HitCharacter = false;
|
|
int EntHit = ENTITYNUM_NONE;
|
|
int i = (int)(at.Size());
|
|
|
|
a.mPoint.ToStr(mLocStringA);
|
|
b.mPoint.ToStr(mLocStringB);
|
|
|
|
const char* aName = (a.mName.empty())?(mLocStringA):(a.mName.c_str());
|
|
const char* bName = (b.mName.empty())?(mLocStringB):(b.mName.c_str());
|
|
|
|
float radius = (at.Size()==CWayEdge::WE_SIZE_LARGE)?(SC_LARGE_RADIUS):(SC_MEDIUM_RADIUS);
|
|
float height = (at.Size()==CWayEdge::WE_SIZE_LARGE)?(SC_LARGE_HEIGHT):(SC_MEDIUM_HEIGHT);
|
|
|
|
Mins[0] = Mins[1] = (radius) * -1.0f;
|
|
Maxs[0] = Maxs[1] = (radius);
|
|
Maxs[2] = (height);
|
|
|
|
|
|
// If Either Start Or End Points Are Too Small, Don' Bother At This Size
|
|
//-----------------------------------------------------------------------
|
|
if ((a.mType==PT_WAYNODE && a.mRadius<radius) ||
|
|
(b.mType==PT_WAYNODE && b.mRadius<radius))
|
|
{
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Size Too Big\n", aName, bName, i);
|
|
}
|
|
CanGo = false;
|
|
return CanGo;
|
|
}
|
|
|
|
|
|
// Try It
|
|
//--------
|
|
CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, 0, true, false);
|
|
EntHit = mMoveTrace.entityNum;
|
|
|
|
// Check For A Flying Edge
|
|
//-------------------------
|
|
if (a.mFlags.get_bit(CWayNode::WN_FLOATING) || b.mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_FLYING);
|
|
if (!a.mFlags.get_bit(CWayNode::WN_FLOATING) || !b.mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_CANBEINVAL);
|
|
}
|
|
}
|
|
|
|
|
|
// Well, It' Can't Go, But Possibly If We Hit An Entity And Remove That Entity, We Can Go?
|
|
//-----------------------------------------------------------------------------------------
|
|
if (!CanGo &&
|
|
!mMoveTrace.startsolid &&
|
|
EntHit!=ENTITYNUM_WORLD &&
|
|
EntHit!=ENTITYNUM_NONE &&
|
|
(&g_entities[EntHit])!=0)
|
|
{
|
|
gentity_t* ent = &g_entities[EntHit];
|
|
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Hit Entity Type (%s), TargetName (%s)\n", aName, bName, ent->classname, ent->targetname);
|
|
}
|
|
|
|
|
|
// Find Out What Type Of Entity This Is
|
|
//--------------------------------------
|
|
if (!Q_stricmp("func_door", ent->classname))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_BLOCKING_DOOR);
|
|
}
|
|
else if (
|
|
!Q_stricmp("func_wall", ent->classname) ||
|
|
!Q_stricmp("func_static", ent->classname) ||
|
|
!Q_stricmp("func_usable", ent->classname))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_BLOCKING_WALL);
|
|
}
|
|
else if (
|
|
!Q_stricmp("func_glass", ent->classname) ||
|
|
!Q_stricmp("func_breakable", ent->classname) ||
|
|
!Q_stricmp("misc_model_breakable", ent->classname))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_BLOCKING_BREAK);
|
|
}
|
|
else if (ent->NPC || ent->s.number==0)
|
|
{
|
|
HitCharacter = true;
|
|
}
|
|
|
|
// Don't Care About Any Other Entity Types
|
|
//-----------------------------------------
|
|
else
|
|
{
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Unable To Ignore Ent, Going A Size Down\n", aName, bName);
|
|
}
|
|
return CanGo; // Go To Next Size Down
|
|
}
|
|
|
|
|
|
// If It Is A Door, Try Opening The Door To See If We Can Get In
|
|
//---------------------------------------------------------------
|
|
if (at.BlockingDoor())
|
|
{
|
|
// Find The Master
|
|
//-----------------
|
|
gentity_t *master = ent;
|
|
while (master && master->teammaster && (master->flags&FL_TEAMSLAVE))
|
|
{
|
|
master = master->teammaster;
|
|
}
|
|
bool DoorIsStartOpen = master->spawnflags&1;
|
|
|
|
// Open The Chain
|
|
//----------------
|
|
gentity_t *slave = master;
|
|
while (slave)
|
|
{
|
|
VectorCopy((DoorIsStartOpen)?(slave->pos1):(slave->pos2), slave->currentOrigin);
|
|
gi.linkentity(slave);
|
|
slave = slave->teamchain;
|
|
}
|
|
|
|
// Try The Trace
|
|
//---------------
|
|
CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, 0, true, false);
|
|
if (CanGo)
|
|
{
|
|
ent = master;
|
|
EntHit = master->s.number;
|
|
}
|
|
else
|
|
{
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Unable Pass Through Door Even When Open, Going A Size Down\n", aName, bName);
|
|
}
|
|
}
|
|
|
|
// Close The Door
|
|
//----------------
|
|
slave = master;
|
|
while (slave)
|
|
{
|
|
VectorCopy((DoorIsStartOpen)?(slave->pos2):(slave->pos1), slave->currentOrigin);
|
|
gi.linkentity(slave);
|
|
slave = slave->teamchain;
|
|
}
|
|
}
|
|
|
|
// Assume Breakable Walls Will Be Clear Later
|
|
//-----------------------------------------------------
|
|
else if (at.BlockingBreakable())
|
|
{//we'll do the trace again later if this ent gets broken
|
|
CanGo = true;
|
|
}
|
|
|
|
// Otherwise, Try It, Pretending The Ent is Not There
|
|
//----------------------------------------------------
|
|
else
|
|
{
|
|
CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, EntHit, true, false);
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Unable Pass Through Even If Entity Was Gone, Going A Size Down\n", aName, bName);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// If We Can Now Go, Ignoring The Entity, Remember That (But Don't Remember Characters - They Won't Stay Anyway)
|
|
//---------------------------------------------------------------------------------------------------------------
|
|
if (CanGo && !HitCharacter)
|
|
{
|
|
ent->wayedge = atHandle;
|
|
at.mEntityNum = EntHit;
|
|
at.mFlags.set_bit(CWayEdge::WE_CANBEINVAL);
|
|
|
|
// Add It To The Edge Map
|
|
//------------------------
|
|
TEntEdgeMap::iterator eemiter = mEntEdgeMap.find(EntHit);
|
|
if (eemiter==mEntEdgeMap.end())
|
|
{
|
|
TEdgesPerEnt EdgesPerEnt;
|
|
EdgesPerEnt.push_back(atHandle);
|
|
mEntEdgeMap.insert(EntHit, EdgesPerEnt);
|
|
}
|
|
else
|
|
{
|
|
if (!eemiter->full())
|
|
{
|
|
eemiter->push_back(atHandle);
|
|
}
|
|
else
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
assert("Max Edges Perh Handle Reached, Unable To Add To Edge Map!"==0);
|
|
gi.Printf("WARNING: Too many nav edges pass through entity %d (%s)\n", EntHit, g_entities[EntHit].targetname);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Check For Special Conditions For The Different Types
|
|
//-------------------------------------------------------
|
|
if (at.BlockingDoor())
|
|
{
|
|
// Doors Need To Know Their "owner" Entity - The Thing That Controlls
|
|
// When The Door Is Open (or can "auto open")
|
|
//
|
|
// We'll start by assuming the door is it's own master
|
|
//-----------------------------------------------------
|
|
at.mOwnerNum = ent->s.number;
|
|
gentity_t* owner = 0;
|
|
|
|
// If There Is A Target Name, See If This Thing Is Controlled From A Switch Or Something
|
|
//---------------------------------------------------------------------------------------
|
|
if (ent->targetname)
|
|
{
|
|
// Try Target
|
|
//------------
|
|
owner = G_Find(owner, FOFS(target), ent->targetname);
|
|
if (owner &&
|
|
(!Q_stricmp("trigger_multiple", owner->classname) || !Q_stricmp("trigger_once", owner->classname)))
|
|
{
|
|
at.mOwnerNum = owner->s.number;
|
|
}
|
|
else
|
|
{
|
|
// Try Target2
|
|
//-------------
|
|
owner = G_Find(owner, FOFS(target2), ent->targetname);
|
|
if (owner &&
|
|
(!Q_stricmp("trigger_multiple", owner->classname) || !Q_stricmp("trigger_once", owner->classname)))
|
|
{
|
|
at.mOwnerNum = owner->s.number;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, See If There Is An Auto Door Opener Trigger
|
|
//--------------------------------------------------------
|
|
else
|
|
{
|
|
owner = G_FindDoorTrigger(ent);
|
|
if (owner)
|
|
{
|
|
at.mOwnerNum = owner->s.number;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Breakable Walls Are Not Valid Until Broken. Period
|
|
//-----------------------------------------------------
|
|
else if (at.BlockingBreakable())
|
|
{//we'll do the trace again later if this ent gets broken
|
|
at.mFlags.clear_bit(CWayEdge::WE_VALID);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now Search For Any Holes In The Ground
|
|
//----------------------------------------
|
|
if (CHECK_JUMP && CanGo)
|
|
{
|
|
CVec3 Mins(-15.0f, -15.0f, 0.0f); // These were the old "sizeless" defaults
|
|
CVec3 Maxs(15.0f, 15.0f, 40.0f);
|
|
|
|
CVec3 AtoB(b.mPoint - a.mPoint);
|
|
float AtoBDist = AtoB.SafeNorm();
|
|
int AtoBSegs = (AtoBDist / MAX_EDGE_SEG_LEN);
|
|
|
|
AtoB *= MAX_EDGE_SEG_LEN;
|
|
CVec3 Start(a.mPoint);
|
|
CVec3 Stop;
|
|
|
|
for (int curSeg=1; (curSeg<AtoBSegs && CanGo); curSeg++)
|
|
{
|
|
Start += AtoB;
|
|
Stop = Start;
|
|
Stop[2] -= MAX_EDGE_FLOOR_DIST;
|
|
|
|
CanGo = !MoveTrace(Start, Stop, Mins, Maxs, EntHit, true, false);
|
|
}
|
|
}
|
|
|
|
return CanGo;
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::LoadFromEntitiesAndSaveToFile(const char *filename, int checksum)
|
|
{
|
|
if (mGraph.size_nodes()<=3)
|
|
{
|
|
return true;
|
|
}
|
|
mUser.ClearActor();
|
|
|
|
mMoveTraceCount = 0;
|
|
mViewTraceCount = 0;
|
|
mConnectTraceCount = 0;
|
|
mConnectTime = gi.Milliseconds();
|
|
|
|
// PHASE 0: SCAN ALL ENTITIES AND TEMPORARLY CLOSE / TURN THEM ON
|
|
//================================================================
|
|
TEntBits Doors;
|
|
TEntBits Walls;
|
|
TEntBits NPCs;
|
|
if (CHECK_START_OPEN)
|
|
{
|
|
for (int curEnt=0; curEnt<MAX_GENTITIES; curEnt++)
|
|
{
|
|
gentity_t* ent = &g_entities[curEnt];
|
|
|
|
if (!ent || !ent->inuse)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Is It An NPC or Vehicle?
|
|
//--------------------------------------
|
|
if (ent->NPC || (ent->NPC_type && Q_stricmp(ent->NPC_type, "atst")==0))
|
|
{
|
|
NPCs.set_bit(curEnt);
|
|
ent->lastMoveTime = ent->contents;
|
|
ent->contents = 0;
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
|
|
|
|
// Is This Ent "Start Off" or "Start Open"?
|
|
//------------------------------------------
|
|
if (!(ent->spawnflags&1))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// Is It A Door? Then Close It
|
|
//--------------------------------------
|
|
if (!Q_stricmp("func_door", ent->classname))
|
|
{
|
|
Doors.set_bit(curEnt);
|
|
VectorCopy(ent->pos2, ent->currentOrigin);
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
// Is It A Wall? Then Turn It On
|
|
//--------------------------------------
|
|
else if (!Q_stricmp("func_wall", ent->classname) || !Q_stricmp("func_usable", ent->classname))
|
|
{
|
|
Walls.set_bit(curEnt);
|
|
ent->contents = ent->spawnContents;
|
|
gi.linkentity(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// PHASE I: TRIANGULATE ALL NODES IN THE GRAPH
|
|
//=============================================
|
|
/* TGraphTriang *Triang = new TGraphTriang(mGraph);
|
|
Triang->clear();
|
|
Triang->insertion_hull();
|
|
Triang->delaunay_edge_flip();
|
|
Triang->floor_shape(mUser, 1000.0f);
|
|
Triang->alpha_shape(mUser, 1000.0f);
|
|
Triang->finish(mUser);
|
|
delete Triang;
|
|
*/
|
|
mCells.fill_cells_nodes(NAV::CELL_RANGE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PHASE II: SCAN THROUGH EXISTING NODES AND GENERATE EDGES TO OTHER NODES WAY POINTS
|
|
//====================================================================================
|
|
CWayNode* at;
|
|
int atHandle;
|
|
const char* atNameStr;
|
|
int tgtNum;
|
|
int tgtHandle;
|
|
hstring tgtName;
|
|
const char* tgtNameStr;
|
|
|
|
CWayEdge atToTgt;
|
|
CVec3 atFloor;
|
|
CVec3 atRoof;
|
|
bool atOnFloor;
|
|
|
|
|
|
TNameToNodeMap::iterator nameFinder;
|
|
TGraph::TNodes::iterator nodeIter;
|
|
TGraph::TEdges::iterator edgeIter;
|
|
|
|
ratl::ratl_compare closestNbrs[MIN_WAY_NEIGHBORS];
|
|
|
|
// Drop To Floor And Mark Floating
|
|
//---------------------------------
|
|
for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++)
|
|
{
|
|
at = &(*nodeIter);
|
|
atRoof = at->mPoint;
|
|
atFloor = at->mPoint;
|
|
if (at->mFlags.get_bit(CWayNode::WN_DROPTOFLOOR))
|
|
{
|
|
atFloor[2] -= MAX_EDGE_FLOOR_DIST;
|
|
}
|
|
atFloor[2] -= (MAX_EDGE_FLOOR_DIST * 1.5f);
|
|
atOnFloor = !ViewTrace(atRoof, atFloor);
|
|
if (at->mFlags.get_bit(CWayNode::WN_DROPTOFLOOR))
|
|
{
|
|
at->mPoint = mViewTrace.endpos;
|
|
at->mPoint[2] += 5.0f;
|
|
}
|
|
else if (!atOnFloor && (at->mType==PT_WAYNODE || at->mType==PT_GOALNODE))
|
|
{
|
|
at->mFlags.set_bit(CWayNode::WN_FLOATING);
|
|
}
|
|
}
|
|
|
|
|
|
for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++)
|
|
{
|
|
nodeIter->mPoint.ToStr(mLocStringA);
|
|
|
|
at = &(*nodeIter);
|
|
atHandle = (nodeIter.index());
|
|
atNameStr = (at->mName.empty())?(mLocStringA):(at->mName.c_str());
|
|
|
|
// Connect To Hand Designed Targets
|
|
//----------------------------------
|
|
for (tgtNum=0; tgtNum<NAV::NUM_TARGETS; tgtNum++)
|
|
{
|
|
// Does This Target Name Exist (or is it "")?
|
|
//--------------------------------------------
|
|
tgtName = at->mTargets[tgtNum];
|
|
if (!tgtName || tgtName.empty())
|
|
{
|
|
continue;
|
|
}
|
|
tgtNameStr = tgtName.c_str();
|
|
|
|
// Clear The Name In The Array, So Save Is Not Corrupted
|
|
//-------------------------------------------------------
|
|
at->mTargets[tgtNum] = 0;
|
|
|
|
|
|
// Try To Find The Node This Target Name Refers To
|
|
//-------------------------------------------------
|
|
nameFinder = mNodeNames.find(tgtName);
|
|
if (nameFinder==mNodeNames.end())
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf(S_COLOR_YELLOW"WARNING: nav unable to locate target (%s) from node (%s)\n", tgtNameStr, atNameStr);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// For Each One
|
|
//--------------
|
|
for (int tgtNameIndex=0; tgtNameIndex<(*nameFinder).size(); tgtNameIndex++)
|
|
{
|
|
// Connect The Two Nodes In The Graph
|
|
//------------------------------------
|
|
tgtHandle = (*nameFinder)[tgtNameIndex];
|
|
|
|
// Only If The Target Is NOT The Same As The Source
|
|
//--------------------------------------------------
|
|
if (atHandle!=tgtHandle)
|
|
{
|
|
int edge = mGraph.get_edge_across(atHandle, tgtHandle);
|
|
|
|
// If The Edge Already Exists, Just Make Sure To Add Any Flags
|
|
//-------------------------------------------------------------
|
|
if (edge)
|
|
{
|
|
// If It Is The Jump Edge (Last Target), Mark It
|
|
//-----------------------------------------------
|
|
if (tgtNum == (NAV::NUM_TARGETS-1))
|
|
{
|
|
mGraph.get_edge(edge).mFlags.set_bit(CWayEdge::WE_JUMPING);
|
|
}
|
|
mGraph.get_edge(edge).mFlags.set_bit(CWayEdge::WE_DESIGNERPLACED);
|
|
continue;
|
|
}
|
|
|
|
// Setup The Edge
|
|
//----------------
|
|
mUser.setup_edge(atToTgt, atHandle, tgtHandle, false, mGraph.get_node(atHandle), mGraph.get_node(tgtHandle), false);
|
|
atToTgt.mFlags.set_bit(CWayEdge::WE_DESIGNERPLACED);
|
|
|
|
// If It Is The Jump Edge (Last Target), Mark It
|
|
//-----------------------------------------------
|
|
if (tgtNum == (NAV::NUM_TARGETS-1))
|
|
{
|
|
atToTgt.mFlags.set_bit(CWayEdge::WE_JUMPING);
|
|
}
|
|
|
|
// Now Tell The Graph Which Edge Index To Store Between The Two Points
|
|
//---------------------------------------------------------------------
|
|
mGraph.connect_node(atToTgt, atHandle, tgtHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If It Is A Combat Or Goal Nav, Try To "Auto Connect" To A Few Nearby Way Points
|
|
//---------------------------------------------------------------------------------
|
|
if (!at->mFlags.get_bit(CWayNode::WN_NOAUTOCONNECT) &&
|
|
(at->mType==NAV::PT_COMBATNODE || at->mType==NAV::PT_GOALNODE))
|
|
{
|
|
// Get The List Of Nodes For This Cell Of The Map
|
|
//------------------------------------------------
|
|
TGraphCells::SCell& Cell = mCells.get_cell(at->mPoint[0], at->mPoint[1]);
|
|
|
|
// Create A Closest Neighbors Array And Initialize It Empty
|
|
//----------------------------------------------------------
|
|
for (int i=0; i<MIN_WAY_NEIGHBORS; i++)
|
|
{
|
|
closestNbrs[i].mHandle = 0;
|
|
closestNbrs[i].mCost = 0;
|
|
}
|
|
int highestCost=0;
|
|
int nonWPCount=0;
|
|
|
|
for (int cellNode=0; cellNode<Cell.mNodes.size(); cellNode++)
|
|
{
|
|
tgtHandle = Cell.mNodes[cellNode];
|
|
|
|
// If It Is The Same Node, Or We Already Connect To It, Ignore
|
|
//-------------------------------------------------------------
|
|
if (tgtHandle==atHandle || mGraph.get_edge_across(atHandle, tgtHandle))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If The Target Is Another Combat Point Or Goal Node, Ignore It
|
|
//--------------------------------------------------------------
|
|
CWayNode& node = mGraph.get_node(tgtHandle);
|
|
|
|
|
|
// Ignore Ones That Are A Floor Above Or Below
|
|
//---------------------------------------------
|
|
if (fabsf(node.mPoint[2] - at->mPoint[2])>NAV::MAX_EDGE_FLOOR_DIST)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore Ones That Are Too Far
|
|
//------------------------------
|
|
float cost = node.mPoint.Dist(at->mPoint);
|
|
if (cost>NAV::MAX_EDGE_AUTO_LEN)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Connecting To Another Combat Point Or Goal Node It Must Be Half The Max Connect Distance
|
|
//------------------------------------------------------------------------------------------
|
|
if (node.mType==NAV::PT_COMBATNODE || node.mType==NAV::PT_GOALNODE)
|
|
{
|
|
nonWPCount++;
|
|
if (nonWPCount>NAV::MAX_NONWP_NEIGHBORS)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (cost>(NAV::MAX_EDGE_AUTO_LEN/2.0f))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If We Already Have Points, Ignore Anything Farther Than The Current Farthest
|
|
//------------------------------------------------------------------------------
|
|
if (closestNbrs[highestCost].mHandle!=0 && closestNbrs[highestCost].mCost<cost)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore Anything Not In The PVS
|
|
//--------------------------------
|
|
if (!(gi.inPVS(node.mPoint.v, at->mPoint.v)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// Now Record This Point Over The One With The Highest Cost
|
|
//----------------------------------------------------------
|
|
closestNbrs[highestCost].mHandle = tgtHandle;
|
|
closestNbrs[highestCost].mCost = node.mPoint.Dist(at->mPoint);
|
|
|
|
// Find The New Highest Cost
|
|
//---------------------------
|
|
for (int i=0; i<MIN_WAY_NEIGHBORS; i++)
|
|
{
|
|
if (closestNbrs[i].mHandle==0)
|
|
{
|
|
highestCost = i;
|
|
break;
|
|
}
|
|
|
|
if (closestNbrs[i].mCost>closestNbrs[highestCost].mCost)
|
|
{
|
|
highestCost = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now Connect All The Closest Neighbors
|
|
//---------------------------------------
|
|
for (int i=0; i<MIN_WAY_NEIGHBORS; i++)
|
|
{
|
|
if (closestNbrs[i].mHandle)
|
|
{
|
|
mUser.setup_edge(atToTgt, atHandle, closestNbrs[i].mHandle, false, (*nodeIter), mGraph.get_node(closestNbrs[i].mHandle), false);
|
|
mGraph.connect_node(atToTgt, atHandle, closestNbrs[i].mHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PHASE III: SCAN EDGES AND RUN TRACES FOR VALID CONNECTIONS & DOORS
|
|
//==================================================================
|
|
ratl::vector_vs<int, NAV::NUM_EDGES> *ToBeRemoved = new ratl::vector_vs<int, NAV::NUM_EDGES>;
|
|
for (edgeIter=mGraph.edges_begin(); edgeIter!=mGraph.edges_end(); edgeIter++)
|
|
{
|
|
CWayEdge& at = (*edgeIter);
|
|
int atHandle = edgeIter.index();
|
|
CWayNode& a = mGraph.get_node(at.mNodeA);
|
|
CWayNode& b = mGraph.get_node(at.mNodeB);
|
|
|
|
mGraph.get_node(at.mNodeA).mPoint.ToStr(mLocStringA);
|
|
mGraph.get_node(at.mNodeB).mPoint.ToStr(mLocStringB);
|
|
|
|
const char* aName = (a.mName.empty())?(mLocStringA):(a.mName.c_str());
|
|
const char* bName = (b.mName.empty())?(mLocStringB):(b.mName.c_str());
|
|
|
|
|
|
if (at.mFlags.get_bit(CWayEdge::WE_JUMPING))
|
|
{
|
|
at.mFlags.set_bit(CWayEdge::WE_SIZE_LARGE);
|
|
at.mFlags.set_bit(CWayEdge::WE_CANBEINVAL);
|
|
at.mFlags.set_bit(CWayEdge::WE_DESIGNERPLACED);
|
|
continue;
|
|
}
|
|
|
|
|
|
// Cycle through the different sizes, starting with the largest
|
|
//--------------------------------------------------------------
|
|
bool CanGo = false;
|
|
bool IsDebugEdge =
|
|
(g_nav1->string[0] && g_nav2->string[0] &&
|
|
(!Q_stricmp(*(a.mName), g_nav1->string) || !Q_stricmp(*(b.mName), g_nav1->string)) &&
|
|
(!Q_stricmp(*(a.mName), g_nav2->string) || !Q_stricmp(*(b.mName), g_nav2->string)));
|
|
|
|
// For debugging a connection between two known points:
|
|
//------------------------------------------------------
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("===============================\n");
|
|
gi.Printf("Nav(%s)<->(%s): DEBUGGING START\n", aName, bName);
|
|
assert(0); // Break Here
|
|
}
|
|
|
|
// Try Large
|
|
//-----------
|
|
at.mFlags.set_bit(CWayEdge::WE_SIZE_LARGE);
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Attempting Size Large...\n", aName, bName);
|
|
}
|
|
|
|
// Try Medium
|
|
//------------
|
|
CanGo = TestEdge( at.mNodeA, at.mNodeB, IsDebugEdge );
|
|
if (!CanGo)
|
|
{
|
|
at.mFlags.clear_bit(CWayEdge::WE_SIZE_LARGE);
|
|
at.mFlags.set_bit(CWayEdge::WE_SIZE_MEDIUM);
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Attempting Size Medium...\n", aName, bName);
|
|
}
|
|
CanGo = TestEdge( at.mNodeA, at.mNodeB, IsDebugEdge );
|
|
}
|
|
|
|
// If This Edge Can't Go At Any Size, Dump It
|
|
//--------------------------------------------
|
|
if (!CanGo)
|
|
{
|
|
ToBeRemoved->push_back(atHandle);
|
|
if (IsDebugEdge)
|
|
{
|
|
CVec3 ContactNormal(mMoveTrace.plane.normal);
|
|
CVec3 ContactPoint( mMoveTrace.endpos);
|
|
|
|
char cpointstr[256] = {0};
|
|
char cnormstr[256] = {0};
|
|
|
|
ContactNormal.ToStr(cnormstr);
|
|
ContactPoint.ToStr(cpointstr);
|
|
|
|
gi.Printf("Nav(%s)<->(%s): FAILED, NO SMALLER SIZE POSSIBLE\n", aName, bName);
|
|
gi.Printf("Nav(%s)<->(%s): The last trace hit:\n", aName, bName);
|
|
gi.Printf("Nav(%s)<->(%s): at %s,\n", aName, bName, cpointstr);
|
|
gi.Printf("Nav(%s)<->(%s): normal %s\n", aName, bName, cnormstr);
|
|
if (mMoveTrace.entityNum!=ENTITYNUM_WORLD)
|
|
{
|
|
gentity_t* ent = &g_entities[mMoveTrace.entityNum];
|
|
gi.Printf("Nav(%s)<->(%s): on entity Type (%s), TargetName (%s)\n", aName, bName, ent->classname, ent->targetname);
|
|
}
|
|
if ((mMoveTrace.contents)&CONTENTS_MONSTERCLIP)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): with contents BLOCKNPC\n", aName, bName);
|
|
}
|
|
else if ((mMoveTrace.contents)&CONTENTS_BOTCLIP)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): with contents DONOTENTER\n", aName, bName);
|
|
}
|
|
else if ((mMoveTrace.contents)&CONTENTS_SOLID)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): with contents SOLID\n", aName, bName);
|
|
}
|
|
else if ((mMoveTrace.contents)&CONTENTS_WATER)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): with contents WATER\n", aName, bName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): Success!\n", aName, bName);
|
|
}
|
|
}
|
|
|
|
if (IsDebugEdge)
|
|
{
|
|
gi.Printf("Nav(%s)<->(%s): DEBUGGING END\n", aName, bName);
|
|
gi.Printf("===============================\n");
|
|
}
|
|
}
|
|
|
|
// Now Go Ahead And Remove Dead Edges
|
|
//------------------------------------
|
|
for (int RemIndex=0;RemIndex<ToBeRemoved->size(); RemIndex++)
|
|
{
|
|
CWayEdge& at = mGraph.get_edge((*ToBeRemoved)[RemIndex]);
|
|
if (at.mFlags.get_bit(CWayEdge::WE_DESIGNERPLACED))
|
|
{
|
|
hstring aHstr = mGraph.get_node(at.mNodeA).mName;
|
|
hstring bHstr = mGraph.get_node(at.mNodeB).mName;
|
|
|
|
mGraph.get_node(at.mNodeA).mPoint.ToStr(mLocStringA);
|
|
mGraph.get_node(at.mNodeB).mPoint.ToStr(mLocStringB);
|
|
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( S_COLOR_RED"ERROR: Nav connect failed: %s@%s <-> %s@%s\n", aHstr.c_str(), mLocStringA, bHstr.c_str(), mLocStringB);
|
|
#endif
|
|
delayedShutDown = level.time + 100;
|
|
}
|
|
mGraph.remove_edge(at.mNodeA, at.mNodeB);
|
|
}
|
|
|
|
delete ToBeRemoved;
|
|
|
|
// Detect Point Islands
|
|
//----------------------
|
|
for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++)
|
|
{
|
|
at = &(*nodeIter);
|
|
atHandle = nodeIter.index();
|
|
|
|
if (!mGraph.node_has_neighbors(atHandle))
|
|
{
|
|
at->mPoint.ToStr(mLocStringA);
|
|
|
|
at->mFlags.set_bit(CWayNode::WN_ISLAND);
|
|
mIslandCount++;
|
|
if (at->mType==NAV::PT_COMBATNODE)
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( S_COLOR_RED"ERROR: Combat Point %s@%s Is Not Connected To Anything\n", at->mName.c_str(), mLocStringA);
|
|
delayedShutDown = level.time + 100;
|
|
#endif
|
|
}
|
|
if (at->mType==NAV::PT_GOALNODE)
|
|
{
|
|
// Try To Trace Down, If We Don't Hit Any Ground, Assume This Is An "Air Point"
|
|
//------------------------------------------------------------------------------
|
|
CVec3 Down(at->mPoint);
|
|
Down[2] -= 100;
|
|
if (!ViewTrace(at->mPoint, Down))
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( S_COLOR_RED"ERROR: Nav Goal %s@%s Is Not Connected To Anything\n", at->mName.c_str(), mLocStringA);
|
|
delayedShutDown = level.time + 100;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// PHASE IV: SCAN EDGES FOR REGIONS
|
|
//==================================
|
|
mRegion.clear();
|
|
|
|
mIslandRegion = mRegion.reserve();
|
|
// mAirRegion = mRegion.reserve();
|
|
for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++)
|
|
{
|
|
at = &(*nodeIter);
|
|
if (at->mFlags.get_bit(CWayNode::WN_ISLAND))
|
|
{
|
|
mRegion.assign_region(nodeIter.index(), mIslandRegion);
|
|
}
|
|
// else if (at->mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
// {
|
|
// mRegion.assign_region(nodeIter.index(), mAirRegion);
|
|
// }
|
|
}
|
|
if (!mRegion.find_regions(mUser))
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( S_COLOR_RED"ERROR: Too Many Regions!\n");
|
|
delayedShutDown = level.time + 100;
|
|
#endif
|
|
}
|
|
if (!mRegion.find_region_edges())
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( S_COLOR_RED"ERROR: Too Many Region Edges!\n");
|
|
delayedShutDown = level.time + 100;
|
|
#endif
|
|
}
|
|
|
|
|
|
// PHASE V: SCAN NODES AND FILL CELLS
|
|
//===================================
|
|
mCells.fill_cells_edges(NAV::CELL_RANGE);
|
|
|
|
|
|
// PHASE VI: SCAN ALL ENTITIES AND RE OPEN / TURN THEM OFF
|
|
//=========================================================
|
|
if (CHECK_START_OPEN)
|
|
{
|
|
for (int curEnt=0; curEnt<MAX_GENTITIES; curEnt++)
|
|
{
|
|
// Is This Ent "Start Off" or "Start Open"?
|
|
//------------------------------------------
|
|
gentity_t* ent = &g_entities[curEnt];
|
|
if (!ent || !ent->inuse)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// Is It A Door?
|
|
//--------------------------------------
|
|
if (Doors.get_bit(curEnt))
|
|
{
|
|
VectorCopy(ent->pos1, ent->currentOrigin);
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
// Is It A Wall?
|
|
//--------------------------------------
|
|
else if (Walls.get_bit(curEnt))
|
|
{
|
|
ent->contents = 0;
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
// Is It An NPC?
|
|
//--------------------------------------
|
|
else if (NPCs.get_bit(curEnt))
|
|
{
|
|
ent->contents = ent->lastMoveTime;
|
|
ent->lastMoveTime = 0;
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
mConnectTraceCount = mMoveTraceCount;
|
|
mMoveTraceCount = 0;
|
|
|
|
mConnectTime = gi.Milliseconds() - mConnectTime;
|
|
|
|
|
|
// PHASE VI: SAVE TO FILE
|
|
//========================
|
|
#ifndef _XBOX
|
|
if (SAVE_LOAD)
|
|
{
|
|
hfile navFile(va("maps/%s.navNEW"));
|
|
if (!navFile.open_write(NAV_VERSION, checksum))
|
|
{
|
|
return false;
|
|
}
|
|
navFile.save(&mGraph, sizeof(mGraph));
|
|
navFile.save(&mRegion, sizeof(mRegion));
|
|
navFile.save(&mCells, sizeof(mCells));
|
|
navFile.close();
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::DecayDangerSenses()
|
|
{
|
|
float PerFrameDecay = (50.0f) / (float)(NAV::MAX_ALERT_TIME);
|
|
for (int entIndex=0; entIndex<TEntityAlertList::CAPACITY; entIndex++)
|
|
{
|
|
TAlertList& ae = mEntityAlertList[entIndex];
|
|
for (int alertIndex=0; alertIndex<TAlertList::CAPACITY; alertIndex++)
|
|
{
|
|
if (ae[alertIndex].mHandle!=0)
|
|
{
|
|
ae[alertIndex].mDanger -= PerFrameDecay;
|
|
|
|
// If It Just Decayed To Nothing, Clear It
|
|
//-----------------------------------------
|
|
if (ae[alertIndex].mDanger<=0.0f)
|
|
{
|
|
ae[alertIndex].mHandle = 0;
|
|
ae[alertIndex].mDanger = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::RegisterDangerSense(gentity_t* actor, int alertEventIndex)
|
|
{
|
|
if (actor==0 || alertEventIndex<0)
|
|
{
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
//If there are no waypoints, don't bother trying to register danger on them
|
|
if (mGraph.size_nodes()<1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get The Alert List For This Ent And The Alert Itself
|
|
//------------------------------------------------------
|
|
TAlertList& al = mEntityAlertList[actor->s.number];
|
|
alertEvent_t& ae = level.alertEvents[alertEventIndex];
|
|
|
|
if (ae.radius<=0.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// DEBUG GRAPHICS
|
|
//=====================================================
|
|
if (NAVDEBUG_showRadius)
|
|
{
|
|
CG_DrawRadius(ae.position, ae.radius, NODE_GOAL);
|
|
}
|
|
//=====================================================
|
|
|
|
|
|
CVec3 DangerPoint(ae.position);
|
|
|
|
// Look Through The Nearby Edges And Record Any That Are Affected
|
|
//----------------------------------------------------------------
|
|
TGraphCells::TCellNodes& cellEdges = mCells.get_cell(DangerPoint[0], DangerPoint[1]).mEdges;
|
|
for (int cellEdgeIndex=0; cellEdgeIndex<cellEdges.size(); cellEdgeIndex++)
|
|
{
|
|
int edgeHandle = cellEdges[cellEdgeIndex];
|
|
CWayEdge& edge = mGraph.get_edge(edgeHandle);
|
|
float edgeDanger = ((ae.radius - DangerPoint.DistToLine(edge.PointA(), edge.PointB()))/(ae.radius));
|
|
if (edgeDanger>0.0f)
|
|
{
|
|
|
|
// Record The Square, So That Danger Drops Off Quadradically Rather Than Linearly
|
|
//--------------------------------------------------------------------------------
|
|
edgeDanger *= edgeDanger;
|
|
|
|
|
|
// Now Find The Index We Will "Replace" With This New Information
|
|
//----------------------------------------------------------------
|
|
int replaceIndex = -1;
|
|
for (int alIndex=0; alIndex<TAlertList::CAPACITY; alIndex++)
|
|
{
|
|
|
|
// If It's The Same Edge Handle, Then Replace This One!
|
|
//------------------------------------------------------
|
|
if (al[alIndex].mHandle==edgeHandle || al[alIndex].mHandle==0)
|
|
{
|
|
replaceIndex = alIndex;
|
|
break;
|
|
}
|
|
|
|
// Otherwise, Remember The Least Dangerous One
|
|
//---------------------------------------------
|
|
if (replaceIndex==-1 || al[alIndex].mDanger<al[replaceIndex].mDanger)
|
|
{
|
|
replaceIndex = alIndex;
|
|
}
|
|
}
|
|
|
|
// Now Go Ahead And Record It
|
|
//----------------------------
|
|
al[replaceIndex].mHandle = edgeHandle;
|
|
al[replaceIndex].mDanger = edgeDanger;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::WayEdgesNowClear(gentity_t* ent)
|
|
{
|
|
if (ent)
|
|
{
|
|
ent->wayedge = 0;
|
|
|
|
int EdgeHandle;
|
|
int EntNum = ent->s.number;
|
|
|
|
TEntEdgeMap::iterator finder = mEntEdgeMap.find(EntNum);
|
|
if (finder!=mEntEdgeMap.end())
|
|
{
|
|
for (int i=0; i<finder->size(); i++)
|
|
{
|
|
EdgeHandle = (*finder)[i];
|
|
if (EdgeHandle!=0)
|
|
{
|
|
CWayEdge& edge = mGraph.get_edge(EdgeHandle);
|
|
edge.mFlags.set_bit(CWayEdge::WE_VALID);
|
|
edge.mEntityNum = ENTITYNUM_NONE;
|
|
edge.mOwnerNum = ENTITYNUM_NONE;
|
|
}
|
|
}
|
|
mEntEdgeMap.erase(EntNum);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::SpawnedPoint(gentity_t* ent, NAV::EPointType type)
|
|
{
|
|
if (mGraph.size_nodes()>=NUM_NODES)
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
gi.Printf( "SpawnedPoint: Max Nav points reached (%d)!\n",NUM_NODES );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
CVec3 Mins;
|
|
CVec3 Maxs;
|
|
|
|
|
|
Mins[0] = Mins[1] = (SC_MEDIUM_RADIUS) * -1.0f;
|
|
Maxs[0] = Maxs[1] = (SC_MEDIUM_RADIUS);
|
|
Mins[2] = 0.0f;
|
|
Maxs[2] = SC_MEDIUM_HEIGHT;
|
|
|
|
|
|
CVec3 Start(ent->currentOrigin);
|
|
CVec3 Stop(ent->currentOrigin);
|
|
Stop[2] += 5.0f;
|
|
|
|
Start.ToStr(mLocStringA);
|
|
const char* pointName = (ent->targetname && ent->targetname[0])?(ent->targetname):"?";
|
|
|
|
if (CHECK_START_SOLID)
|
|
{
|
|
// Try It
|
|
//--------
|
|
if (!MoveTrace(Start, Stop, Mins, Maxs, 0, true, false))
|
|
{
|
|
assert("ERROR: Nav in solid!"==0);
|
|
gi.Printf( S_COLOR_RED"ERROR: Nav(%d) in solid: %s@%s\n", type, pointName, mLocStringA);
|
|
delayedShutDown = level.time + 100;
|
|
return;
|
|
}
|
|
}
|
|
|
|
CWayNode node;
|
|
|
|
node.mPoint = ent->currentOrigin;
|
|
node.mRadius = ent->radius;
|
|
node.mType = type;
|
|
node.mFlags.clear();
|
|
if (type==NAV::PT_WAYNODE && (ent->spawnflags & 2))
|
|
{
|
|
node.mFlags.set_bit(CWayNode::WN_DROPTOFLOOR);
|
|
}
|
|
if (ent->spawnflags & 4)
|
|
{
|
|
node.mFlags.set_bit(CWayNode::WN_NOAUTOCONNECT);
|
|
}
|
|
|
|
// TO AVOID PROBLEMS WITH THE TRIANGULATION, WE MOVE THE POINTS AROUND JUST A BIT
|
|
//================================================================================
|
|
//node.mPoint += CVec3(Q_flrand(-RANDOM_PERMUTE, RANDOM_PERMUTE), Q_flrand(-RANDOM_PERMUTE, RANDOM_PERMUTE), 0.0f);
|
|
//================================================================================
|
|
|
|
// Validate That The New Location Is Still Safe
|
|
//----------------------------------------------
|
|
if (false && CHECK_START_SOLID)
|
|
{
|
|
Start = (node.mPoint);
|
|
Stop = (node.mPoint);
|
|
Stop[2] += 5.0f;
|
|
|
|
// Try It Again
|
|
//--------------
|
|
if (!MoveTrace(Start, Stop, Mins, Maxs, 0, true, false))
|
|
{
|
|
gi.Printf( S_COLOR_YELLOW"WARNING: Nav Moved To Solid, Resetting: (%s)\n", pointName);
|
|
assert("WARNING: Nav Moved To Solid, Resetting!"==0);
|
|
node.mPoint = ent->currentOrigin;
|
|
}
|
|
}
|
|
|
|
node.mTargets[0] = ent->target;
|
|
node.mTargets[1] = ent->target2;
|
|
node.mTargets[2] = ent->target3;
|
|
node.mTargets[3] = ent->target4;
|
|
node.mTargets[4] = ent->targetJump;
|
|
node.mName = ent->targetname;
|
|
|
|
int NodeHandle = mGraph.insert_node(node);
|
|
|
|
ent->waypoint = NodeHandle;
|
|
|
|
mCells.expand_bounds(NodeHandle);
|
|
|
|
if (!node.mName.empty())
|
|
{
|
|
TNameToNodeMap::iterator nameFinder = mNodeNames.find(node.mName);
|
|
|
|
if (nameFinder==mNodeNames.end())
|
|
{
|
|
TNamedNodeList list;
|
|
list.clear();
|
|
list.push_back(NodeHandle);
|
|
mNodeNames.insert(node.mName, list);
|
|
}
|
|
else
|
|
{
|
|
(*nameFinder).push_back(NodeHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::GetNearestNode(gentity_t* ent, bool forceRecalcNow, NAV::TNodeHandle goal)
|
|
{
|
|
if (!ent)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (ent->waypoint==WAYPOINT_NONE || forceRecalcNow || (level.time>ent->noWaypointTime))
|
|
{
|
|
if (ent->waypoint)
|
|
{
|
|
ent->lastWaypoint = ent->waypoint;
|
|
}
|
|
ent->waypoint =
|
|
GetNearestNode(
|
|
ent->currentOrigin,
|
|
ent->waypoint,
|
|
goal,
|
|
ent->s.number,
|
|
(ent->client && ent->client->moveType==MT_FLYSWIM));
|
|
ent->noWaypointTime = level.time + 1000; // Don't Erase This Result For 5 Seconds
|
|
}
|
|
|
|
return ent->waypoint;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::GetNearestNode(const vec3_t& position, NAV::TNodeHandle previous, NAV::TNodeHandle goal, int ignoreEnt, bool allowZOffset)
|
|
{
|
|
if (mGraph.size_edges()>0)
|
|
{
|
|
// Get The List Of Nodes For This Cell Of The Map
|
|
//------------------------------------------------
|
|
TGraphCells::SCell& Cell = mCells.get_cell(position[0], position[1]);
|
|
if (Cell.mNodes.empty() && Cell.mEdges.empty())
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
if (g_developer->value)
|
|
{
|
|
gi.Printf("WARNING: Failure To Find A Node Here, Examine Cell Layout\n");
|
|
}
|
|
#endif
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
CVec3 Pos(position);
|
|
SNodeSort NodeSort;
|
|
|
|
// PHASE I - TEST NAV POINTS
|
|
//===========================
|
|
{
|
|
mNearestNavSort.clear();
|
|
for (int i=0; i<Cell.mNodes.size() && !mNearestNavSort.full(); i++)
|
|
{
|
|
CWayNode& node = mGraph.get_node(Cell.mNodes[i]);
|
|
|
|
NodeSort.mHandle = Cell.mNodes[i];
|
|
NodeSort.mDistance = node.mPoint.Dist2(Pos);
|
|
NodeSort.mInRadius = (NodeSort.mDistance<(node.mRadius*node.mRadius));
|
|
|
|
// Severly Bias Points That Are Not On The Same Z Height As The Pos
|
|
//------------------------------------------------------------------
|
|
if (!allowZOffset)
|
|
{
|
|
float ZOff = fabsf(node.mPoint[2] - Pos[2]);
|
|
if (ZOff>(VIEW_RANGE / 4))
|
|
{
|
|
continue;
|
|
}
|
|
if (ZOff>30.0f)
|
|
{
|
|
NodeSort.mDistance += (ZOff*ZOff);
|
|
}
|
|
}
|
|
|
|
// Ignore Points That Are Too Far
|
|
//--------------------------------
|
|
if (NodeSort.mDistance>(NAV::VIEW_RANGE*NAV::VIEW_RANGE))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Bias Points That Are Not Connected To Anything
|
|
//------------------------------------------------
|
|
if (node.mFlags.get_bit(CWayNode::WN_ISLAND))
|
|
{
|
|
NodeSort.mDistance *= 3.0f;
|
|
}
|
|
|
|
if (previous && previous!=NodeSort.mHandle && !NAV::InSameRegion(previous, NodeSort.mHandle))
|
|
{
|
|
NodeSort.mDistance += (100.0f*100.0f);
|
|
}
|
|
if (previous>0 && previous!=NodeSort.mHandle && !mGraph.get_edge_across(previous, NodeSort.mHandle))
|
|
{
|
|
NodeSort.mDistance += (200.0f*200.0f);
|
|
}
|
|
if (goal && goal!=NodeSort.mHandle && !NAV::InSameRegion(goal, NodeSort.mHandle))
|
|
{
|
|
NodeSort.mDistance += (300.0f*300.0f);
|
|
}
|
|
|
|
|
|
// Bias Combat And Goal Nodes Some
|
|
//---------------------------------
|
|
// if (node.mType==NAV::PT_COMBATNODE || node.mType==NAV::PT_GOALNODE)
|
|
// {
|
|
// NodeSort.mDistance += 50.0f;
|
|
// }
|
|
|
|
mNearestNavSort.push_back(NodeSort);
|
|
}
|
|
|
|
// Sort Them By Distance
|
|
//-----------------------
|
|
mNearestNavSort.sort();
|
|
|
|
// Now , Run Through Each Of The Sorted Nodes, Starting With The Closest One
|
|
//---------------------------------------------------------------------------
|
|
for (int j=0; j<mNearestNavSort.size(); j++)
|
|
{
|
|
// If In The Radius Of This Point, It Is Safe To Return It
|
|
//---------------------------------------------------------
|
|
if (mNearestNavSort[j].mInRadius)
|
|
{
|
|
return mNearestNavSort[j].mHandle;
|
|
}
|
|
|
|
// Otherwise, We Need To Trace To It
|
|
//-----------------------------------
|
|
else if (ViewNavTrace(Pos, mGraph.get_node(mNearestNavSort[j].mHandle).mPoint))
|
|
{
|
|
return mNearestNavSort[j].mHandle;
|
|
}
|
|
}
|
|
}
|
|
|
|
// PHASE II: TEST NAV EDGES
|
|
//==========================
|
|
{
|
|
CVec3 Point;
|
|
CVec3 PointOnEdge;
|
|
float PointOnEdgeRange;
|
|
mNearestNavSort.clear();
|
|
for (int i=0; i<Cell.mEdges.size() && !mNearestNavSort.full(); i++)
|
|
{
|
|
CWayEdge& edge = mGraph.get_edge(Cell.mEdges[i]);
|
|
|
|
edge.Point(Point);
|
|
|
|
NodeSort.mHandle = Cell.mEdges[i];
|
|
NodeSort.mDistance = Point.Dist2(Pos);
|
|
|
|
// Severly Bias Points That Are Not On The Same Z Height As The Pos
|
|
//------------------------------------------------------------------
|
|
if (!allowZOffset)
|
|
{
|
|
float ZOff = fabsf(Point[2] - Pos[2]);
|
|
if (ZOff>(VIEW_RANGE / 4))
|
|
{
|
|
continue;
|
|
}
|
|
if (ZOff>30.0f)
|
|
{
|
|
NodeSort.mDistance += (ZOff*ZOff);
|
|
}
|
|
}
|
|
|
|
// Ignore Points That Are Too Far
|
|
//--------------------------------
|
|
if (NodeSort.mDistance>(NAV::VIEW_RANGE*NAV::VIEW_RANGE))
|
|
{
|
|
continue;
|
|
}
|
|
mNearestNavSort.push_back(NodeSort);
|
|
}
|
|
|
|
// Sort Them By Distance
|
|
//-----------------------
|
|
mNearestNavSort.sort();
|
|
|
|
// Now , Run Through Each Of The Sorted Edges, Starting With The Closest One
|
|
//---------------------------------------------------------------------------
|
|
for (int j=0; j<mNearestNavSort.size(); j++)
|
|
{
|
|
CWayEdge& edge = mGraph.get_edge(mNearestNavSort[j].mHandle);
|
|
PointOnEdge = Pos;
|
|
|
|
PointOnEdgeRange = PointOnEdge.ProjectToLine(edge.PointA(), edge.PointB());
|
|
|
|
if (PointOnEdgeRange>0.0f && PointOnEdgeRange<1.0f)
|
|
{
|
|
// Otherwise, We Need To Trace To It
|
|
//-----------------------------------
|
|
if (ViewNavTrace(Pos, PointOnEdge))
|
|
{
|
|
return (mNearestNavSort[j].mHandle * -1); // "Edges" have negative IDs
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::ChooseRandomNeighbor(NAV::TNodeHandle NodeHandle)
|
|
{
|
|
if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0)
|
|
{
|
|
TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle);
|
|
if (neighbors.size()>0)
|
|
{
|
|
return (neighbors[Q_irand(0, neighbors.size()-1)].mNode);
|
|
}
|
|
}
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::ChooseRandomNeighbor(TNodeHandle NodeHandle, const vec3_t& position, float maxDistance)
|
|
{
|
|
if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0)
|
|
{
|
|
CVec3 Pos(position);
|
|
|
|
TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle);
|
|
|
|
// Remove All Neighbors That Are Too Far
|
|
//---------------------------------------
|
|
for (int i=0; i<neighbors.size(); i++)
|
|
{
|
|
if (mGraph.get_node(neighbors[i].mNode).mPoint.Dist(Pos)>maxDistance)
|
|
{
|
|
neighbors.erase_swap(i);
|
|
i--;
|
|
if (neighbors.empty())
|
|
{
|
|
return WAYPOINT_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now, Randomly Pick From What Is Left
|
|
//--------------------------------------
|
|
if (neighbors.size()>0)
|
|
{
|
|
return (neighbors[Q_irand(0, neighbors.size()-1)].mNode);
|
|
}
|
|
}
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::ChooseClosestNeighbor(NAV::TNodeHandle NodeHandle, const vec3_t& position)
|
|
{
|
|
if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0)
|
|
{
|
|
CVec3 pos(position);
|
|
TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle);
|
|
|
|
NAV::TNodeHandle Cur = WAYPOINT_NONE;
|
|
float CurDist = 0.0f;
|
|
NAV::TNodeHandle Best = NodeHandle;
|
|
float BestDist = mGraph.get_node(Cur).mPoint.Dist2(pos);
|
|
|
|
for (int i=0; i<neighbors.size(); i++)
|
|
{
|
|
Cur = neighbors[i].mNode;
|
|
CurDist = mGraph.get_node(Cur).mPoint.Dist2(pos);
|
|
if (Best==WAYPOINT_NONE || BestDist<CurDist)
|
|
{
|
|
Best = Cur;
|
|
BestDist = CurDist;
|
|
}
|
|
}
|
|
return Best;
|
|
}
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::ChooseFarthestNeighbor(NAV::TNodeHandle NodeHandle, const vec3_t& position)
|
|
{
|
|
if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0)
|
|
{
|
|
CVec3 pos(position);
|
|
TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle);
|
|
|
|
NAV::TNodeHandle Cur = WAYPOINT_NONE;
|
|
float CurDist = 0.0f;
|
|
NAV::TNodeHandle Best = NodeHandle;
|
|
float BestDist = mGraph.get_node(Cur).mPoint.Dist2(pos);
|
|
|
|
for (int i=0; i<neighbors.size(); i++)
|
|
{
|
|
Cur = neighbors[i].mNode;
|
|
CurDist = mGraph.get_node(Cur).mPoint.Dist2(pos);
|
|
if (Best==WAYPOINT_NONE || BestDist>CurDist)
|
|
{
|
|
Best = Cur;
|
|
BestDist = CurDist;
|
|
}
|
|
}
|
|
return Best;
|
|
}
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
NAV::TNodeHandle NAV::ChooseFarthestNeighbor(gentity_t* actor, const vec3_t& target, float maxSafeDot)
|
|
{
|
|
CVec3 actorPos(actor->currentOrigin);
|
|
CVec3 targetPos(target);
|
|
CVec3 actorToTgt(targetPos - actorPos);
|
|
float actorToTgtDist = actorToTgt.Norm();
|
|
|
|
NAV::TNodeHandle cur = GetNearestNode(actor);
|
|
|
|
// If Not Anywhere, Give Up
|
|
//--------------------------
|
|
if (cur==WAYPOINT_NONE)
|
|
{
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
// If On An Edge, Pick The Safest Of The Two Points
|
|
//--------------------------------------------------
|
|
if (cur<0)
|
|
{
|
|
CWayEdge& edge = mGraph.get_edge(abs(cur));
|
|
if (edge.PointA().Dist2(targetPos)>edge.PointA().Dist2(actorPos))
|
|
{
|
|
return edge.mNodeA;
|
|
}
|
|
return edge.mNodeB;
|
|
}
|
|
|
|
CVec3 curPos(mGraph.get_node(cur).mPoint);
|
|
CVec3 curToTgt(targetPos - curPos);
|
|
float curDist = curToTgt.SafeNorm();
|
|
// float curDot = curToTgt.Dot(actorToTgt);
|
|
NAV::TNodeHandle best = WAYPOINT_NONE;
|
|
float bestDist = 0.0f;
|
|
TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(cur);
|
|
|
|
|
|
// If The Actor's Current Point Is Valid, Initialize The Best One To That
|
|
//------------------------------------------------------------------------
|
|
if (curDist>actorToTgtDist && actorPos.Dist(curPos)>300.0f)//curDot<maxSafeDot
|
|
{
|
|
best = cur;
|
|
bestDist = curDist;
|
|
}
|
|
|
|
|
|
for (int i=0; i<neighbors.size(); i++)
|
|
{
|
|
cur = neighbors[i].mNode;
|
|
curPos = (mGraph.get_node(cur).mPoint);
|
|
curToTgt = (targetPos - curPos);
|
|
curDist = curToTgt.SafeNorm();
|
|
// curDot = curToTgt.Dot(actorToTgt);
|
|
|
|
if (curDist>bestDist && curDist>actorToTgtDist)//curDot<maxSafeDot
|
|
{
|
|
best = cur;
|
|
bestDist = curDist;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::FindPath(gentity_t* actor, NAV::TNodeHandle target, float MaxDangerLevel)
|
|
{
|
|
mUser.ClearActor();
|
|
|
|
// If Either Start Or End Is Invalid, We Can't Do Any Pathing
|
|
//------------------------------------------------------------
|
|
if (target==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
NAV::TNodeHandle start = GetNearestNode(actor, true, target);
|
|
if (start==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Convert Edges To Points
|
|
//------------------------
|
|
if (start<0)
|
|
{
|
|
start = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(start)).mNodeA):(mGraph.get_edge(abs(start)).mNodeB);
|
|
}
|
|
if (target<0)
|
|
{
|
|
target = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(target)).mNodeA):(mGraph.get_edge(abs(target)).mNodeB);
|
|
}
|
|
|
|
mUser.SetActor(actor);
|
|
|
|
// First Step: Find The Actor And Make Sure He Has A Path User Struct
|
|
//--------------------------------------------------------------------
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
if (mPathUsers.full())
|
|
{
|
|
assert("NAV: No more unused path users, possibly change MAX_PATH_USERS"==0);
|
|
return false;
|
|
}
|
|
|
|
pathUserNum = mPathUsers.alloc();
|
|
mPathUsers[pathUserNum].mEnd = WAYPOINT_NONE;
|
|
mPathUsers[pathUserNum].mSuccess = false;
|
|
mPathUsers[pathUserNum].mLastAStarTime = 0;
|
|
mPathUserIndex[actor->s.number] = pathUserNum;
|
|
}
|
|
SPathUser& puser = mPathUsers[pathUserNum];
|
|
puser.mLastUseTime = level.time;
|
|
|
|
|
|
// Now, Check To See If He Already Has Found A Path To This Target
|
|
//-----------------------------------------------------------------
|
|
if (puser.mEnd==target && level.time<puser.mLastAStarTime)
|
|
{
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
|
|
// Setup The Search
|
|
//------------------
|
|
mSearch.mStart = start;
|
|
mSearch.mEnd = target;
|
|
puser.mEnd = target;
|
|
|
|
|
|
// First Check The Region
|
|
//------------------------
|
|
if (mRegion.size()>0 && !mRegion.has_valid_edge(mSearch.mStart, mSearch.mEnd, mUser))
|
|
{
|
|
puser.mSuccess = false;
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
|
|
// Now, Run A*
|
|
//-------------
|
|
if (actor->enemy && actor->enemy->client)
|
|
{
|
|
if (actor->enemy->client->ps.weapon==WP_SABER)
|
|
{
|
|
mUser.SetDangerSpot(actor->enemy->currentOrigin, 200.0f);
|
|
}
|
|
else if (
|
|
actor->enemy->client->NPC_class==CLASS_RANCOR ||
|
|
actor->enemy->client->NPC_class==CLASS_WAMPA)
|
|
{
|
|
mUser.SetDangerSpot(actor->enemy->currentOrigin, 400.0f);
|
|
}
|
|
}
|
|
mGraph.astar(mSearch, mUser);
|
|
mUser.ClearDangerSpot();
|
|
|
|
puser.mLastAStarTime = level.time + Q_irand(3000, 6000);
|
|
puser.mSuccess = mSearch.success();
|
|
if (!puser.mSuccess)
|
|
{
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
// Grab A Couple "Current Conditions"
|
|
//------------------------------------
|
|
CVec3 At(actor->currentOrigin);
|
|
float AtTime = level.time;
|
|
float AtSpeed = actor->NPC->stats.runSpeed;
|
|
if (!(actor->NPC->scriptFlags&SCF_RUNNING) &&
|
|
((actor->NPC->scriptFlags&SCF_WALKING) ||
|
|
(actor->NPC->aiFlags&NPCAI_WALKING) ||
|
|
(ucmd.buttons&BUTTON_WALKING)
|
|
))
|
|
{
|
|
AtSpeed = actor->NPC->stats.walkSpeed;
|
|
}
|
|
|
|
AtSpeed *= 0.001f; // Convert units/sec to units/millisec for comparison against level.time
|
|
AtSpeed *= 0.25; // Cut the speed in half to account for accel & decel & some slop
|
|
|
|
|
|
|
|
// Get The Size Of This Actor
|
|
//----------------------------
|
|
float minRadius = Min(actor->mins[0], actor->mins[1]);
|
|
float maxRadius = Max(actor->maxs[0], actor->maxs[1]);
|
|
float radius = Max(fabsf(minRadius), maxRadius);
|
|
|
|
if (radius>20.0f)
|
|
{
|
|
radius = 20.0f;
|
|
}
|
|
|
|
|
|
// Copy The Search Results Into The Path
|
|
//---------------------------------------
|
|
{
|
|
SPathPoint PPoint;
|
|
puser.mPath.clear();
|
|
for (mSearch.path_begin(); !mSearch.path_end() && !puser.mPath.full(); mSearch.path_inc())
|
|
{
|
|
if (puser.mPath.full())
|
|
{
|
|
// NAV_TODO: If the path is longer than the max length, we want to store the first valid ones, instead of returning false
|
|
assert("This Is A Test To See If We Hit This Condition Anymore... It Should Be Handled Properly"==0);
|
|
mPathUsers.free(pathUserNum);
|
|
mPathUserIndex[actor->s.number] = NULL_PATH_USER_INDEX;
|
|
return false;
|
|
}
|
|
|
|
PPoint.mNode = mSearch.path_at();
|
|
PPoint.mPoint = mGraph.get_node(PPoint.mNode).mPoint;
|
|
PPoint.mSpeed = AtSpeed;
|
|
PPoint.mSlowingRadius = 0.0f;
|
|
PPoint.mReachedRadius = Max(radius*3.0f, (mGraph.get_node(PPoint.mNode).mRadius * 0.40f));
|
|
if (mGraph.get_node(PPoint.mNode).mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
PPoint.mReachedRadius = 20.0f;
|
|
}
|
|
PPoint.mReachedRadius *= PPoint.mReachedRadius; // squared, for faster checks later
|
|
PPoint.mDist = 0.0f;
|
|
PPoint.mETA = 0.0f;
|
|
|
|
puser.mPath.push_back(PPoint);
|
|
}
|
|
assert(puser.mPath.size()>0);
|
|
|
|
|
|
// Last Point On The Path Always Gets A Slowing Radius
|
|
//-----------------------------------------------------
|
|
puser.mPath[0].mSlowingRadius = Max(70.0f, (mGraph.get_node(PPoint.mNode).mRadius * 0.75f));
|
|
puser.mPath[0].mReachedRadius = Max(radius, (mGraph.get_node(PPoint.mNode).mRadius * 0.15f));
|
|
puser.mPath[0].mReachedRadius *= puser.mPath[0].mReachedRadius; // squared, for faster checks later
|
|
}
|
|
|
|
int numEdges = (puser.mPath.size()-1);
|
|
|
|
|
|
// Trim Out Backtracking Edges
|
|
//-----------------------------
|
|
for (int edge=0; edge<numEdges; edge++)
|
|
{
|
|
CVec3 PointA(puser.mPath[edge].mPoint);
|
|
CVec3 PointB(puser.mPath[edge+1].mPoint);
|
|
|
|
CVec3 AtOnEdge(At);
|
|
float AtOnEdgeScale = AtOnEdge.ProjectToLineSeg(PointA, PointB);
|
|
float AtDistToEdge = AtOnEdge.Dist(At);
|
|
|
|
if (AtOnEdgeScale>0.1f && AtOnEdgeScale<0.9f)
|
|
{
|
|
if (AtDistToEdge<(radius) || (AtDistToEdge<(radius*20.0f) && MoveTrace(At, AtOnEdge, actor->mins, actor->maxs, actor->s.number, true, true, false)))
|
|
{
|
|
puser.mPath.resize(edge+2); // +2 because every edge needs at least 2 points
|
|
puser.mPath[edge+1].mPoint = AtOnEdge;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// For All Points On The Path, Compute ETA, And Check For Sharp Corners
|
|
//----------------------------------------------------------------------
|
|
CVec3 AtToNext;
|
|
CVec3 NextToBeyond;
|
|
float NextToBeyondDistance;
|
|
float NextToBeyondDot;
|
|
|
|
for (int i=puser.mPath.size()-1; i>-1; i--)
|
|
{
|
|
SPathPoint& PPoint = puser.mPath[i]; // For Debugging And A Tad Speed Improvement, Get A Ref Directly
|
|
|
|
AtToNext = (PPoint.mPoint - At);
|
|
if (fabsf(AtToNext[2])>Z_CULL_OFFSET)
|
|
{
|
|
AtToNext[2] = 0.0f;
|
|
}
|
|
|
|
PPoint.mDist = AtToNext.Norm(); // Get The Distance And Norm The Direction
|
|
PPoint.mETA = (PPoint.mDist / PPoint.mSpeed); // Estimate Our Eta By Distance/Speed
|
|
PPoint.mETA += AtTime;
|
|
|
|
|
|
// Check To See If This Is The Apex Of A Sharp Turn
|
|
//--------------------------------------------------
|
|
if (i!=0 && //is there a next point?
|
|
!mGraph.get_node(PPoint.mNode).mFlags.get_bit(CWayNode::WN_FLOATING)
|
|
)
|
|
{
|
|
NextToBeyond = (puser.mPath[i-1].mPoint - PPoint.mPoint);
|
|
if (fabsf(NextToBeyond[2])>Z_CULL_OFFSET)
|
|
{
|
|
NextToBeyond[2] = 0.0f;
|
|
}
|
|
NextToBeyondDistance = NextToBeyond.Norm();
|
|
NextToBeyondDot = NextToBeyond.Dot(AtToNext);
|
|
|
|
if ((NextToBeyondDistance>150.0f && PPoint.mDist>150.0f && NextToBeyondDot<0.64f) ||
|
|
(NextToBeyondDistance>30.0f && NextToBeyondDot<0.5f))
|
|
{
|
|
PPoint.mSlowingRadius = Max(40.0f, mGraph.get_node(PPoint.mNode).mRadius); // Force A Stop Here
|
|
}
|
|
}
|
|
|
|
// Update Our Time And Location For The Next Point
|
|
//-------------------------------------------------
|
|
AtTime = PPoint.mETA;
|
|
At = PPoint.mPoint;
|
|
}
|
|
|
|
// Failed To Find An Acceptibly Safe Path
|
|
//----------------------------------------
|
|
if (MaxDangerLevel!=1.0f && NAV::PathDangerLevel(NPC)>MaxDangerLevel)
|
|
{
|
|
puser.mSuccess = false;
|
|
}
|
|
|
|
|
|
assert(puser.mPath.size()>0);
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::SafePathExists(const CVec3& startVec, const CVec3& stopVec, const CVec3& danger, float dangerDistSq)
|
|
{
|
|
mUser.ClearActor();
|
|
|
|
|
|
// If Either Start Or End Is Invalid, We Can't Do Any Pathing
|
|
//------------------------------------------------------------
|
|
NAV::TNodeHandle target = GetNearestNode(stopVec.v, 0, 0, 0, true);
|
|
if (target==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
NAV::TNodeHandle start = GetNearestNode(startVec.v, 0, target, 0, true);
|
|
if (start==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Convert Edges To Points
|
|
//------------------------
|
|
if (start<0)
|
|
{
|
|
start = mGraph.get_edge(abs(start)).mNodeA;
|
|
}
|
|
if (target<0)
|
|
{
|
|
target = mGraph.get_edge(abs(target)).mNodeA;
|
|
}
|
|
if (start==target)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
// First Step: Find The Actor And Make Sure He Has A Path User Struct
|
|
//--------------------------------------------------------------------
|
|
SPathUser& puser = mPathUserMaster;
|
|
puser.mLastUseTime = level.time;
|
|
|
|
|
|
// Now, Check To See If He Already Has Found A Path To This Target
|
|
//-----------------------------------------------------------------
|
|
if (puser.mEnd==target && level.time<puser.mLastAStarTime)
|
|
{
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
|
|
// Setup The Search
|
|
//------------------
|
|
mSearch.mStart = start;
|
|
mSearch.mEnd = target;
|
|
puser.mEnd = target;
|
|
|
|
|
|
// First Check The Region
|
|
//------------------------
|
|
if (mRegion.size()>0 && !mRegion.has_valid_edge(mSearch.mStart, mSearch.mEnd, mUser))
|
|
{
|
|
puser.mSuccess = false;
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
// Now, Run A*
|
|
//-------------
|
|
// mUser.SetDangerSpot(danger, dangerDistSq);
|
|
mGraph.astar(mSearch, mUser);
|
|
// mUser.ClearDangerSpot();
|
|
|
|
puser.mLastAStarTime = level.time + Q_irand(3000, 6000);;
|
|
puser.mSuccess = mSearch.success();
|
|
if (!puser.mSuccess)
|
|
{
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
// Failed To Find An Acceptibly Safe Path
|
|
//----------------------------------------
|
|
CVec3 Prev(stopVec);
|
|
CVec3 Next;
|
|
for (mSearch.path_begin(); !mSearch.path_end(); mSearch.path_inc())
|
|
{
|
|
Next = mGraph.get_node(mSearch.path_at()).mPoint;
|
|
if (dangerDistSq > danger.DistToLine2(Next, Prev))
|
|
{
|
|
puser.mSuccess = false;
|
|
break;
|
|
}
|
|
Prev = Next;
|
|
}
|
|
if (puser.mSuccess)
|
|
{
|
|
Next = startVec;
|
|
if (dangerDistSq > danger.DistToLine2(Prev, Next))
|
|
{
|
|
puser.mSuccess = false;
|
|
}
|
|
}
|
|
|
|
return puser.mSuccess;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
float NAV::EstimateCostToGoal(const vec3_t& position, TNodeHandle Goal)
|
|
{
|
|
if (Goal!=0 && Goal!=WAYPOINT_NONE)
|
|
{
|
|
return (Distance(position, GetNodePosition(Goal)));
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
float NAV::EstimateCostToGoal(TNodeHandle Start, TNodeHandle Goal)
|
|
{
|
|
mUser.ClearActor();
|
|
if (Goal!=0 && Goal!=WAYPOINT_NONE && Start!=0 && Start!=WAYPOINT_NONE)
|
|
{
|
|
return (Distance(GetNodePosition(Start), GetNodePosition(Goal)));
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::OnSamePoint(gentity_t* actor, gentity_t* target)
|
|
{
|
|
return (GetNearestNode(actor)==GetNearestNode(target));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::InSameRegion(gentity_t* actor, gentity_t* target)
|
|
{
|
|
mUser.ClearActor();
|
|
if (mRegion.size()>0)
|
|
{
|
|
NAV::TNodeHandle actNode = GetNearestNode(actor);
|
|
NAV::TNodeHandle tgtNode = GetNearestNode(target);
|
|
if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
if (actNode==tgtNode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (actNode<0)
|
|
{
|
|
actNode = mGraph.get_edge(abs(actNode)).mNodeA;
|
|
}
|
|
if (tgtNode<0)
|
|
{
|
|
tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA;
|
|
}
|
|
|
|
mUser.SetActor(actor);
|
|
|
|
return (mRegion.has_valid_edge(actNode, tgtNode, mUser));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::InSameRegion(gentity_t* actor, const vec3_t& position)
|
|
{
|
|
mUser.ClearActor();
|
|
if (mRegion.size()>0)
|
|
{
|
|
NAV::TNodeHandle actNode = GetNearestNode(actor);
|
|
NAV::TNodeHandle tgtNode = GetNearestNode(position);
|
|
|
|
if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
if (actNode==tgtNode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (actNode<0)
|
|
{
|
|
actNode = mGraph.get_edge(abs(actNode)).mNodeA;
|
|
}
|
|
if (tgtNode<0)
|
|
{
|
|
tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA;
|
|
}
|
|
|
|
mUser.SetActor(actor);
|
|
|
|
return (mRegion.has_valid_edge(actNode, tgtNode, mUser));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::InSameRegion(NAV::TNodeHandle A, NAV::TNodeHandle B)
|
|
{
|
|
if (mRegion.size()>0)
|
|
{
|
|
NAV::TNodeHandle actNode = A;
|
|
NAV::TNodeHandle tgtNode = B;
|
|
|
|
if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
if (actNode==tgtNode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (actNode<0)
|
|
{
|
|
actNode = mGraph.get_edge(abs(actNode)).mNodeA;
|
|
}
|
|
if (tgtNode<0)
|
|
{
|
|
tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA;
|
|
}
|
|
|
|
gentity_t* tempActor = mUser.GetActor();
|
|
mUser.ClearActor();
|
|
bool hasEdge = mRegion.has_valid_edge(actNode, tgtNode, mUser);
|
|
if (tempActor)
|
|
{
|
|
mUser.SetActor(tempActor);
|
|
}
|
|
return hasEdge;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::OnNeighboringPoints(TNodeHandle A, TNodeHandle B)
|
|
{
|
|
if (A==B)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (A<=0 || B<=0)
|
|
{
|
|
return false;
|
|
}
|
|
int edgeNum = mGraph.get_edge_across(A, B);
|
|
if (edgeNum &&
|
|
!mGraph.get_edge(edgeNum).mFlags.get_bit(CWayEdge::WE_JUMPING) &&
|
|
!mGraph.get_edge(edgeNum).mFlags.get_bit(CWayEdge::WE_FLYING) &&
|
|
mGraph.get_edge(edgeNum).mDistance<SAFE_NEIGHBORINGPOINT_DIST)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::OnNeighboringPoints(gentity_t* actor, gentity_t* target)
|
|
{
|
|
if (OnNeighboringPoints(GetNearestNode(actor), GetNearestNode(target)))
|
|
{
|
|
if (Distance(actor->currentOrigin, target->currentOrigin)<NEIGHBORING_DIST)
|
|
{
|
|
// if (ViewNavTrace(actor->currentOrigin, target->currentOrigin))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::OnNeighboringPoints(gentity_t* actor, const vec3_t& position)
|
|
{
|
|
if (OnNeighboringPoints(GetNearestNode(actor), GetNearestNode(position)))
|
|
{
|
|
if (Distance(actor->currentOrigin, position)<NEIGHBORING_DIST)
|
|
{
|
|
// if (ViewNavTrace(actor->currentOrigin, position))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Is The Position (at) within the safe radius of the atNode, targetNode, or the edge
|
|
// between?
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::InSafeRadius(CVec3 at, TNodeHandle atNode, TNodeHandle targetNode)
|
|
{
|
|
// Uh, No
|
|
//--------
|
|
if (atNode<=0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If In The Radius Of Nearest Nav Point
|
|
//---------------------------------------
|
|
if (Distance(at.v, GetNodePosition(atNode))<mGraph.get_node(atNode).mRadius)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If We Have A Target Node, We May Be On The Edge That Connects Between The Two
|
|
//-------------------------------------------------------------------------------
|
|
if (targetNode>0 && atNode!=targetNode)
|
|
{
|
|
// If In The Radius Of Target Nav Point
|
|
//---------------------------------------
|
|
if (Distance(at.v, GetNodePosition(targetNode))<mGraph.get_node(targetNode).mRadius)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Does The Edge Exist?
|
|
//----------------------
|
|
int atToTargetEdgeIndex = mGraph.get_edge_across(atNode, targetNode);
|
|
if (atToTargetEdgeIndex!=0)
|
|
{
|
|
CWayEdge& atToTargetEdge = mGraph.get_edge(atToTargetEdgeIndex);
|
|
|
|
// Is The Edge Valid?
|
|
//--------------------
|
|
if (!atToTargetEdge.mFlags.get_bit(CWayEdge::WE_FLYING) &&
|
|
!atToTargetEdge.mFlags.get_bit(CWayEdge::WE_JUMPING) &&
|
|
mUser.is_valid(atToTargetEdge, targetNode))
|
|
{
|
|
float atDistToEdge = at.DistToLine(atToTargetEdge.PointA(), atToTargetEdge.PointB());
|
|
|
|
// Now Are We Close Enough To The Edge
|
|
//-------------------------------------
|
|
if (atToTargetEdge.mFlags.get_bit(CWayEdge::WE_SIZE_LARGE))
|
|
{
|
|
return (atDistToEdge<SC_LARGE_RADIUS);
|
|
}
|
|
return (atDistToEdge<SC_MEDIUM_RADIUS);
|
|
}// not valid edge
|
|
}// no edge
|
|
}// no valid target
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::FindPath(gentity_t* actor, gentity_t* target, float MaxDangerLevel)
|
|
{
|
|
assert(target!=0 && actor!=0);
|
|
if (target!=0 && actor!=0)
|
|
{
|
|
if (target->waypoint==WAYPOINT_NONE)
|
|
{
|
|
GetNearestNode(target);
|
|
}
|
|
if (target->waypoint!=WAYPOINT_NONE)
|
|
{
|
|
return FindPath(actor, target->waypoint, MaxDangerLevel);
|
|
}
|
|
else if (target->lastWaypoint!=WAYPOINT_NONE)
|
|
{
|
|
return FindPath(actor, target->lastWaypoint, MaxDangerLevel);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::FindPath(gentity_t* actor, const vec3_t& position, float MaxDangerLevel)
|
|
{
|
|
return FindPath(actor, GetNearestNode(position), MaxDangerLevel);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
const vec3_t& NAV::NextPosition(gentity_t* actor)
|
|
{
|
|
assert(HasPath(actor));
|
|
TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath;
|
|
|
|
return (path[path.size()-1].mPoint.v);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::NextPosition(gentity_t* actor, CVec3& Position)
|
|
{
|
|
assert(HasPath(actor));
|
|
TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath;
|
|
Position = path[path.size()-1].mPoint;
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::NextPosition(gentity_t* actor, CVec3& Position, float& SlowingRadius, bool& Fly, bool& Jump)
|
|
{
|
|
assert(HasPath(actor));
|
|
TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath;
|
|
SPathPoint& next = path[path.size()-1];
|
|
CWayNode& node = mGraph.get_node(next.mNode);
|
|
int curNodeIndex = NAV::GetNearestNode(actor);
|
|
int edgeIndex = (curNodeIndex>0)?(mGraph.get_edge_across(curNodeIndex, next.mNode)):(abs(curNodeIndex));
|
|
|
|
SlowingRadius = next.mSlowingRadius;
|
|
Position = next.mPoint;
|
|
Fly = node.mFlags.get_bit(CWayNode::WN_FLOATING);
|
|
|
|
if (edgeIndex)
|
|
{
|
|
Jump = mGraph.get_edge(edgeIndex).mFlags.get_bit(CWayEdge::WE_JUMPING);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// This function completely removes any pathfinding information related to this agent
|
|
// and frees up this path user structure for someone else to use.
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::ClearPath(gentity_t* actor)
|
|
{
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mPathUsers.free(pathUserNum);
|
|
mPathUserIndex[actor->s.number] = NULL_PATH_USER_INDEX;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Update Path
|
|
//
|
|
// Removes points that have been reached, and frees the user if no points remain.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::UpdatePath(gentity_t* actor, TNodeHandle target, float MaxDangerLevel)
|
|
{
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
return false;
|
|
}
|
|
if (!mPathUsers[pathUserNum].mSuccess)
|
|
{
|
|
return false;
|
|
}
|
|
if (!mPathUsers[pathUserNum].mPath.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// Remove Any Points We Have Reached
|
|
//-----------------------------------
|
|
CVec3 At(actor->currentOrigin);
|
|
TPath& path = mPathUsers[pathUserNum].mPath;
|
|
bool InReachedRadius = false;
|
|
bool ReachedAnything = false;
|
|
assert(path.size()>0);
|
|
|
|
do
|
|
{
|
|
SPathPoint& PPoint = path[path.size()-1];
|
|
CVec3 Dir(PPoint.mPoint - At);
|
|
if (fabsf(At[2] - PPoint.mPoint[2])<Z_CULL_OFFSET)
|
|
{
|
|
Dir[2] = 0.0f;
|
|
}
|
|
|
|
InReachedRadius = (Dir.Len2()<PPoint.mReachedRadius);
|
|
if (InReachedRadius)
|
|
{
|
|
ReachedAnything = true;
|
|
path.pop_back();
|
|
}
|
|
} while (InReachedRadius && path.size()>0);
|
|
|
|
|
|
// If We've Reached The End Of The Path, Return With no path!
|
|
//------------------------------------------------------------
|
|
if (path.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ReachedAnything)
|
|
{
|
|
if (target!=PT_NONE && mPathUsers[pathUserNum].mEnd!=target)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If Not, Time To Double Check To See If The Path Is Still Valid
|
|
//----------------------------------------------------------------
|
|
if (path[path.size()-1].mETA<level.time || (MaxDangerLevel!=1.0f && PathDangerLevel(NPC)>MaxDangerLevel))
|
|
{
|
|
// Hmmm. Should Have Reached This Point By Now, Or Too Dangerous. Try To Recompute The Path
|
|
//--------------------------------------------------------------------------------------------
|
|
NAV::TNodeHandle target = mPathUsers[pathUserNum].mEnd; // Remember The End As Our Target
|
|
if (target==WAYPOINT_NONE)
|
|
{
|
|
ClearPath(actor);
|
|
return false;
|
|
}
|
|
mPathUsers[pathUserNum].mEnd = WAYPOINT_NONE; // Clear Out The Old End
|
|
if (!FindPath(actor, target, MaxDangerLevel))
|
|
{
|
|
mPathUsers[pathUserNum].mEnd = target;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Ok, We Have A Path, And Are On-Route
|
|
//--------------------------------------
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
bool NAV::HasPath(gentity_t* actor, TNodeHandle target)
|
|
{
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
return false;
|
|
}
|
|
if (!mPathUsers[pathUserNum].mSuccess)
|
|
{
|
|
return false;
|
|
}
|
|
if (!mPathUsers[pathUserNum].mPath.size())
|
|
{
|
|
return false;
|
|
}
|
|
if (target!=PT_NONE && mPathUsers[pathUserNum].mEnd!=target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
float NAV::PathDangerLevel(gentity_t* actor)
|
|
{
|
|
if (!actor)
|
|
{
|
|
assert("No Actor!!!"==0);
|
|
return 0.0f;
|
|
}
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
TPath& Path = mPathUsers[pathUserNum].mPath;
|
|
|
|
// If it only has one point, let's say it's not dangerous
|
|
//--------------------------------------------------------
|
|
if (Path.size()<2)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
float DangerLevel = 0.0f;
|
|
CVec3 enemyPos;
|
|
float enemySafeDist = 0.0f;
|
|
float enemyDangerLevel = 0.0f;
|
|
TEdgeHandle curEdge = 0;
|
|
int curPathAt = Path.size()-1;
|
|
TAlertList& al = mEntityAlertList[actor->s.number];
|
|
int alIndex = 0;
|
|
TNodeHandle prevNode = (GetNearestNode(actor));
|
|
CVec3 prevPoint(actor->currentOrigin);
|
|
|
|
|
|
// Some Special Enemies Always Cause Persistant Danger
|
|
//-----------------------------------------------------
|
|
if (actor->enemy && actor->enemy->client)
|
|
{
|
|
if (actor->enemy->client->ps.weapon==WP_SABER ||
|
|
actor->enemy->client->NPC_class==CLASS_RANCOR ||
|
|
actor->enemy->client->NPC_class==CLASS_WAMPA)
|
|
{
|
|
enemyPos = (actor->enemy->currentOrigin);
|
|
enemySafeDist = actor->enemy->radius * 10.0f;
|
|
}
|
|
}
|
|
|
|
|
|
// Go Through All Remaining Points On The Path
|
|
//---------------------------------------------
|
|
for (; curPathAt>=0; curPathAt--)
|
|
{
|
|
SPathPoint& PPoint = Path[curPathAt];
|
|
|
|
// If Any Edges On The Path Have Been Registered As Dangerous
|
|
//-------------------------------------------------------------
|
|
if (prevNode<0 || mGraph.get_edge_across(prevNode, PPoint.mNode))
|
|
{
|
|
if (prevNode<0)
|
|
{
|
|
curEdge = prevNode;
|
|
}
|
|
else
|
|
{
|
|
curEdge = mGraph.get_edge_across(prevNode, PPoint.mNode);
|
|
}
|
|
for (alIndex=0; alIndex<TAlertList::CAPACITY; alIndex++)
|
|
{
|
|
if (al[alIndex].mHandle==curEdge && al[alIndex].mDanger>DangerLevel)
|
|
{
|
|
DangerLevel = al[alIndex].mDanger;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check For Enemy Position Proximity To This Next Edge (using actual Edge Locations)
|
|
//------------------------------------------------------------------------------------
|
|
if (enemySafeDist!=0.0f)
|
|
{
|
|
enemyDangerLevel = enemyPos.DistToLine(prevPoint, PPoint.mPoint)/enemySafeDist;
|
|
if (enemyDangerLevel>DangerLevel)
|
|
{
|
|
DangerLevel = enemyDangerLevel;
|
|
}
|
|
}
|
|
|
|
prevNode = PPoint.mNode;
|
|
prevPoint = PPoint.mPoint;
|
|
}
|
|
return DangerLevel;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
int NAV::PathNodesRemaining(gentity_t* actor)
|
|
{
|
|
int pathUserNum = mPathUserIndex[actor->s.number];
|
|
if (pathUserNum==NULL_PATH_USER_INDEX)
|
|
{
|
|
return false;
|
|
}
|
|
return mPathUsers[pathUserNum].mPath.size();
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
const vec3_t& NAV::GetNodePosition(TNodeHandle NodeHandle)
|
|
{
|
|
if (NodeHandle!=0)
|
|
{
|
|
if (NodeHandle>0)
|
|
{
|
|
return (mGraph.get_node(NodeHandle).mPoint.v);
|
|
}
|
|
else
|
|
{
|
|
return (mGraph.get_edge(abs(NodeHandle)).PointA().v);
|
|
}
|
|
}
|
|
return mZeroVec;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::GetNodePosition(TNodeHandle NodeHandle, vec3_t& position)
|
|
{
|
|
if (NodeHandle!=0)
|
|
{
|
|
if (NodeHandle>0)
|
|
{
|
|
VectorCopy(mGraph.get_node(NodeHandle).mPoint.v, position);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(mGraph.get_edge(abs(NodeHandle)).PointA().v, position);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Call This function to get the size classification for a given entity
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
unsigned int NAV::ClassifyEntSize(gentity_t* ent)
|
|
{
|
|
if (ent)
|
|
{
|
|
float minRadius = Min(ent->mins[0], ent->mins[1]);
|
|
float maxRadius = Max(ent->maxs[0], ent->maxs[1]);
|
|
float radius = Max(fabsf(minRadius), maxRadius);
|
|
float height = ent->maxs[2];
|
|
|
|
if ((radius > SC_MEDIUM_RADIUS) || height > (SC_MEDIUM_HEIGHT))
|
|
{
|
|
return CWayEdge::WE_SIZE_LARGE;
|
|
}
|
|
return CWayEdge::WE_SIZE_MEDIUM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::ShowDebugInfo(const vec3_t& PlayerPosition, int PlayerWaypoint)
|
|
{
|
|
mUser.ClearActor();
|
|
CVec3 atEnd;
|
|
|
|
// Show Nodes
|
|
//------------
|
|
if (NAVDEBUG_showNodes || NAVDEBUG_showCombatPoints || NAVDEBUG_showNavGoals)
|
|
{
|
|
for (TGraph::TNodes::iterator atIter=mGraph.nodes_begin(); atIter!=mGraph.nodes_end(); atIter++)
|
|
{
|
|
CWayNode& at = (*atIter);
|
|
atEnd = at.mPoint;
|
|
atEnd[2] += 30.0f;
|
|
|
|
|
|
if (gi.inPVS(PlayerPosition, at.mPoint.v))
|
|
{
|
|
if (at.mType==NAV::PT_WAYNODE && NAVDEBUG_showNodes)
|
|
{
|
|
if (NAVDEBUG_showPointLines)
|
|
{
|
|
if (at.mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_FLOATING );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_NORMAL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (at.mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
CG_DrawNode(at.mPoint.v, NODE_FLOATING );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawNode(at.mPoint.v, NODE_NORMAL );
|
|
}
|
|
}
|
|
if (NAVDEBUG_showRadius && at.mPoint.Dist2(PlayerPosition)<(at.mRadius*at.mRadius))
|
|
{
|
|
if (at.mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
CG_DrawRadius(at.mPoint.v, at.mRadius, NODE_FLOATING );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawRadius(at.mPoint.v, at.mRadius, NODE_NORMAL );
|
|
}
|
|
}
|
|
}
|
|
else if (at.mType==NAV::PT_COMBATNODE && NAVDEBUG_showCombatPoints)
|
|
{
|
|
if (NAVDEBUG_showPointLines)
|
|
{
|
|
CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_COMBAT );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawCombatPoint(at.mPoint.v, 0);
|
|
}
|
|
}
|
|
else if (at.mType==NAV::PT_GOALNODE && NAVDEBUG_showNavGoals)
|
|
{
|
|
if (NAVDEBUG_showPointLines)
|
|
{
|
|
CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_GOAL );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawNode(at.mPoint.v, NODE_NAVGOAL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Show Edges
|
|
//------------
|
|
if (NAVDEBUG_showEdges)
|
|
{
|
|
for (TGraph::TEdges::iterator atIter=mGraph.edges_begin(); atIter!=mGraph.edges_end(); atIter++)
|
|
{
|
|
CWayEdge& at = (*atIter);
|
|
CWayNode& a = mGraph.get_node(at.mNodeA);
|
|
CWayNode& b = mGraph.get_node(at.mNodeB);
|
|
CVec3 AvePos = a.mPoint + b.mPoint;
|
|
AvePos *= 0.5f;
|
|
|
|
if (AvePos.Dist2(PlayerPosition)<(500.0f*500.0f) && gi.inPVS(PlayerPosition, AvePos.v))
|
|
{
|
|
if (mUser.is_valid(at))
|
|
{
|
|
if (at.mFlags.get_bit(CWayEdge::WE_JUMPING))
|
|
{
|
|
CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_JUMP);
|
|
}
|
|
else if (at.mFlags.get_bit(CWayEdge::WE_FLYING))
|
|
{
|
|
CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_FLY);
|
|
}
|
|
else if (at.Size()==CWayEdge::WE_SIZE_LARGE)
|
|
{
|
|
CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_LARGE);
|
|
}
|
|
else
|
|
{
|
|
CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_NORMAL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_BLOCKED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (NAVDEBUG_showGrid)
|
|
{
|
|
float x1, y1;
|
|
float x2, y2;
|
|
float z = 0.0f;
|
|
|
|
for (int x=0; x<TGraphCells::SIZEX; x++)
|
|
{
|
|
for (int y=0; y<TGraphCells::SIZEY; y++)
|
|
{
|
|
TGraphCells::TCellNodes& nodes = mCells.get_cell(x, y).mNodes;
|
|
mCells.get_cell_upperleft(x,y, x1, y1);
|
|
mCells.get_cell_lowerright(x,y, x2, y2);
|
|
|
|
int type = EDGE_CELL_EMPTY;
|
|
if (nodes.size())
|
|
{
|
|
type = EDGE_CELL;
|
|
z = mGraph.get_node(nodes[0])[2] - 10.0f;
|
|
}
|
|
|
|
|
|
CVec3 upleft(x1, y1, z);
|
|
CVec3 upright(x2, y1, z);
|
|
CVec3 downright(x2, y2, z);
|
|
CVec3 downleft(x1, y2, z);
|
|
CVec3 center = (upleft + downright) * 0.5f;
|
|
if (center.Dist(PlayerPosition)<10000.0f)
|
|
{
|
|
CG_DrawEdge(upleft.v, upright.v, type);
|
|
CG_DrawEdge(upright.v, downright.v, type);
|
|
// CG_DrawEdge(downright.v, downleft.v, type);
|
|
// CG_DrawEdge(downleft.v, upleft.v, type);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( NAVDEBUG_showTestPath )
|
|
{
|
|
// NAV_TODO: Allow Test Paths
|
|
}
|
|
|
|
if ( NAVDEBUG_showNearest && player && (player->waypoint!=0 || player->lastWaypoint!=0))
|
|
{
|
|
PlayerWaypoint = (player->waypoint)?(player->waypoint):(player->lastWaypoint);
|
|
CVec3 PPos(PlayerPosition);
|
|
if (PlayerWaypoint>0)
|
|
{
|
|
CWayNode& node = mGraph.get_node(PlayerWaypoint);
|
|
|
|
CG_DrawEdge(PPos.v, node.mPoint.v, (player->waypoint)?(EDGE_NEARESTVALID):(EDGE_NEARESTINVALID));
|
|
}
|
|
else
|
|
{
|
|
CWayEdge& edge = mGraph.get_edge(abs(PlayerWaypoint));
|
|
CVec3 PosOnLine(PlayerPosition);
|
|
PosOnLine.ProjectToLineSeg(edge.PointA(), edge.PointB());
|
|
|
|
CG_DrawEdge(PPos.v, PosOnLine.v, (player->waypoint)?(EDGE_NEARESTVALID):(EDGE_NEARESTINVALID));
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Show Stats
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::ShowStats()
|
|
{
|
|
#if !defined(FINAL_BUILD)
|
|
mGraph.ProfileSpew();
|
|
mRegion.ProfileSpew();
|
|
|
|
mGraph.ProfilePrint("Point Islands: (%d)", mIslandCount);
|
|
mGraph.ProfilePrint("");
|
|
mGraph.ProfilePrint("");
|
|
mGraph.ProfilePrint("--------------------------------------------------------");
|
|
mGraph.ProfilePrint(" Star Wars - Jedi Academy ");
|
|
mGraph.ProfilePrint(" Additional Statistics ");
|
|
mGraph.ProfilePrint("--------------------------------------------------------");
|
|
mGraph.ProfilePrint("");
|
|
mGraph.ProfilePrint("MEMORY CONSUMPTION (In Bytes)");
|
|
mGraph.ProfilePrint("Cells : (%d)", (sizeof(mCells)));
|
|
mGraph.ProfilePrint("Path : (%d)", (sizeof(mPathUsers)+sizeof(mPathUserIndex)));
|
|
mGraph.ProfilePrint("Steer : (%d)", (sizeof(mSteerUsers)+sizeof(mSteerUserIndex)));
|
|
mGraph.ProfilePrint("Alerts : (%d)", (sizeof(mEntityAlertList)));
|
|
float totalBytes = (
|
|
sizeof(mCells)+
|
|
sizeof(mGraph)+
|
|
sizeof(mRegion)+
|
|
sizeof(mPathUsers)+
|
|
sizeof(mPathUserIndex)+
|
|
sizeof(mSteerUsers)+
|
|
sizeof(mSteerUserIndex)+
|
|
sizeof(mEntityAlertList));
|
|
|
|
mGraph.ProfilePrint("TOTAL : (KiloBytes): (%5.3f) MeggaBytes(%3.3f)",
|
|
((float)(totalBytes)/1024.0f),
|
|
((float)(totalBytes)/1048576.0f)
|
|
);
|
|
mGraph.ProfilePrint("");
|
|
|
|
|
|
mGraph.ProfilePrint("Connect Stats: Milliseconds(%d) Traces(%d)", mConnectTime, mConnectTraceCount);
|
|
mGraph.ProfilePrint("");
|
|
mGraph.ProfilePrint("Move Trace: Count(%d) PerFrame(%f)", mMoveTraceCount, (float)(mMoveTraceCount)/(float)(level.time));
|
|
mGraph.ProfilePrint("View Trace: Count(%d) PerFrame(%f)", mViewTraceCount, (float)(mViewTraceCount)/(float)(level.time));
|
|
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// TeleportTo
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::TeleportTo(gentity_t* actor, const char* pointName)
|
|
{
|
|
assert(actor!=0);
|
|
hstring nName(pointName);
|
|
TNameToNodeMap::iterator nameFinder= mNodeNames.find(nName);
|
|
if (nameFinder!=mNodeNames.end())
|
|
{
|
|
if ((*nameFinder).size()>1)
|
|
{
|
|
gi.Printf("WARNING: More than one point named (%s). Going to first one./n", pointName);
|
|
}
|
|
TeleportPlayer(actor, mGraph.get_node((*nameFinder)[0]).mPoint.v, actor->currentAngles);
|
|
return;
|
|
}
|
|
gi.Printf("Unable To Locate Point (%s)\n", pointName);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// TeleportTo
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void NAV::TeleportTo(gentity_t* actor, int pointNum)
|
|
{
|
|
assert(actor!=0);
|
|
TeleportPlayer(actor, mGraph.get_node(pointNum).mPoint.v, actor->currentAngles);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Activate
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void STEER::Activate(gentity_t* actor)
|
|
{
|
|
assert(!Active(actor) && actor && actor->client && actor->NPC); // Can't Activate If Already Active
|
|
|
|
|
|
// PHASE I - ACTIVATE THE STEER USER FOR THIS ACTOR
|
|
//==================================================
|
|
if (mSteerUsers.full())
|
|
{
|
|
assert("STEER: No more unused steer users, possibly change size"==0);
|
|
return;
|
|
}
|
|
|
|
// Get A Steer User From The Pool
|
|
//--------------------------------
|
|
int steerUserNum = mSteerUsers.alloc();
|
|
mSteerUserIndex[actor->s.number] = steerUserNum;
|
|
SSteerUser& suser = mSteerUsers[steerUserNum];
|
|
|
|
|
|
// PHASE II - Copy Data For This Actor Into The SUser
|
|
//====================================================
|
|
suser.mPosition = actor->currentOrigin;
|
|
suser.mOrientation = actor->currentAngles;
|
|
suser.mVelocity = actor->client->ps.velocity;
|
|
suser.mSpeed = suser.mVelocity.Len();
|
|
|
|
suser.mBlocked = false;
|
|
|
|
suser.mMaxSpeed = actor->NPC->stats.runSpeed;
|
|
suser.mRadius = RadiusFromBounds(actor->mins, actor->maxs);
|
|
suser.mMaxForce = 150.0f; //STEER_TODO: Get From actor Somehow
|
|
suser.mMass = 1.0f; //STEER_TODO: Get From actor Somehow
|
|
if (!(actor->NPC->scriptFlags&SCF_RUNNING) &&
|
|
((actor->NPC->scriptFlags&SCF_WALKING) ||
|
|
(actor->NPC->aiFlags&NPCAI_WALKING) ||
|
|
(ucmd.buttons&BUTTON_WALKING)
|
|
))
|
|
{
|
|
suser.mMaxSpeed = actor->NPC->stats.walkSpeed;
|
|
}
|
|
|
|
assert(suser.mPosition.IsFinite());
|
|
assert(suser.mOrientation.IsFinite());
|
|
assert(suser.mVelocity.IsFinite());
|
|
|
|
|
|
// Find Our Neighbors
|
|
//--------------------
|
|
suser.mNeighbors.clear();
|
|
float RangeSize = suser.mRadius + STEER::NEIGHBOR_RANGE;
|
|
|
|
CVec3 Range(RangeSize, RangeSize, (actor->client->moveType==MT_FLYSWIM)?(RangeSize):(suser.mRadius*2.0f));
|
|
CVec3 Mins(suser.mPosition - Range);
|
|
CVec3 Maxs(suser.mPosition + Range);
|
|
|
|
gentity_t* EntityList[MAX_GENTITIES];
|
|
gentity_t* neighbor = 0;
|
|
|
|
int numFound = gi.EntitiesInBox(Mins.v, Maxs.v, EntityList, MAX_GENTITIES);
|
|
for (int i=0; i<numFound; i++)
|
|
{
|
|
neighbor = EntityList[i];
|
|
assert(neighbor!=0);
|
|
|
|
if (neighbor->s.number==actor->s.number || neighbor==actor->enemy || !neighbor->client || neighbor->health<=0 || !neighbor->inuse)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
suser.mNeighbors.push_back(neighbor);
|
|
}
|
|
|
|
|
|
// Clear Out Steering, So If No STEER Operations Are Called, Net Effect Is Zero
|
|
//------------------------------------------------------------------------------
|
|
suser.mSteering.Clear();
|
|
suser.mNewtons = 0.0f;
|
|
|
|
VectorClear(actor->client->ps.moveDir);
|
|
actor->client->ps.speed = 0;
|
|
|
|
|
|
// PHASE III - Project The Current Velocity Forward, To The Side, And Onto The Path
|
|
//==================================================================================
|
|
suser.mProjectFwd = suser.mPosition + (suser.mVelocity * 1.0f);
|
|
suser.mProjectSide = (suser.mVelocity * 0.3f);
|
|
suser.mProjectSide.Reposition(suser.mPosition, (actor->NPC->avoidSide==Side_Left)?(40.0f):(-40.0f));
|
|
|
|
|
|
// STEER_TODO: Project The Point The Path (If The character has one)
|
|
//-------------------------------------------------------------------
|
|
//suser.mProjectPath = ;
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// DeActivate
|
|
//
|
|
// This function first scales back the composite steering vector to
|
|
// the max force range, and if there is any steering left, it
|
|
// applies the steering to the velocity. Velocity is also truncated
|
|
// to be less than Max Speed.
|
|
//
|
|
// Finaly, the results are copied onto the entity and the steer user
|
|
// struct is freed for use by another entity.
|
|
//////////////////////////////////////////////////////////////////////
|
|
void STEER::DeActivate(gentity_t* actor, usercmd_t* ucmd)
|
|
{
|
|
assert(Active(actor) && actor && actor->client && actor->NPC); // Can't Deactivate If Never Activated
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
|
|
assert(suser.mPosition.IsFinite());
|
|
assert(suser.mOrientation.IsFinite());
|
|
assert(suser.mSteering.IsFinite());
|
|
assert(suser.mMass!=0.0f);
|
|
|
|
|
|
|
|
// PHASE I - TRUNCATE STEERING AND APPLY TO VELOCITY
|
|
//===================================================
|
|
suser.mNewtons = suser.mSteering.Truncate(suser.mMaxForce);
|
|
if (suser.mNewtons>1E-10)
|
|
{
|
|
suser.mSteering /= suser.mMass;
|
|
suser.mVelocity += suser.mSteering;
|
|
suser.mSpeed = suser.mVelocity.Truncate(suser.mMaxSpeed);
|
|
|
|
// DEBUG GRAPHICS
|
|
//=================================================================
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CVec3 EndThrust(suser.mPosition+suser.mSteering);
|
|
CVec3 EndVelocity(suser.mPosition+suser.mVelocity);
|
|
|
|
CG_DrawEdge(suser.mPosition.v, EndThrust.v, EDGE_THRUST);
|
|
CG_DrawEdge(suser.mPosition.v, EndVelocity.v, EDGE_VELOCITY);
|
|
}
|
|
//=================================================================
|
|
}
|
|
if (suser.mSpeed<10.0f)
|
|
{
|
|
suser.mSpeed = 0.0f;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PHASE II - CONVERT VELOCITY TO MOVE DIRECTION & ANGLES
|
|
//========================================================
|
|
if (!NPC_Jumping())
|
|
{
|
|
CVec3 MoveDir(suser.mVelocity);
|
|
CVec3 Angles(actor->NPC->lastPathAngles);
|
|
|
|
if (suser.mSpeed>0.0f && MoveDir!=CVec3::mZero)
|
|
{
|
|
MoveDir.Norm();
|
|
CVec3 NewAngles(suser.mVelocity);
|
|
NewAngles.VecToAng();
|
|
|
|
Angles = NewAngles;//((Angles + NewAngles)*0.75f);
|
|
}
|
|
assert(MoveDir.IsFinite());
|
|
assert(Angles.IsFinite());
|
|
|
|
|
|
|
|
// PHASE III - ASSIGN ALL THIS DATA TO THE ACTOR ENTITY
|
|
//======================================================
|
|
actor->NPC->aiFlags |= NPCAI_NO_SLOWDOWN;
|
|
|
|
VectorCopy(MoveDir.v,
|
|
actor->client->ps.moveDir);
|
|
actor->client->ps.speed = suser.mSpeed;
|
|
|
|
VectorCopy(Angles.v,
|
|
actor->NPC->lastPathAngles);
|
|
actor->NPC->desiredPitch = 0.0f;
|
|
actor->NPC->desiredYaw = AngleNormalize360(Angles[YAW]);
|
|
|
|
|
|
// Convert Movement To User Command
|
|
//----------------------------------
|
|
if (suser.mSpeed > 0.0f)
|
|
{
|
|
vec3_t forward, right, up;
|
|
AngleVectors(actor->currentAngles, forward, right, up); // Use Current Angles
|
|
|
|
float fDot = Com_Clamp(-127.0f, 127.0f, DotProduct(forward, MoveDir.v)*127.0f);
|
|
float rDot = Com_Clamp(-127.0f, 127.0f, DotProduct(right, MoveDir.v)*127.0f);
|
|
|
|
ucmd->forwardmove = floor(fDot);
|
|
ucmd->rightmove = floor(rDot);
|
|
ucmd->upmove = 0.0f;
|
|
|
|
if (suser.mSpeed<(actor->NPC->stats.walkSpeed + 5.0f))
|
|
{
|
|
ucmd->buttons |= BUTTON_WALKING;
|
|
}
|
|
else
|
|
{
|
|
ucmd->buttons &= ~BUTTON_WALKING;
|
|
}
|
|
|
|
// Handle Fly Swim Movement
|
|
//--------------------------
|
|
if (actor->client->moveType==MT_FLYSWIM)
|
|
{
|
|
ucmd->forwardmove = 0.0f;
|
|
ucmd->rightmove = 0.0f;
|
|
|
|
VectorCopy(suser.mVelocity.v, actor->client->ps.velocity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ucmd->forwardmove = 0.0f;
|
|
ucmd->rightmove = 0.0f;
|
|
ucmd->upmove = 0.0f;
|
|
|
|
// Handle Fly Swim Movement
|
|
//--------------------------
|
|
if (actor->client->moveType==MT_FLYSWIM)
|
|
{
|
|
VectorClear(actor->client->ps.velocity);
|
|
}
|
|
}
|
|
|
|
// Slow Down If Going Backwards
|
|
//------------------------------
|
|
if (ucmd->forwardmove<0.0f)
|
|
{
|
|
client->ps.speed *= 0.75f;
|
|
suser.mSpeed *= 0.75f;
|
|
}
|
|
|
|
|
|
// PHASE IV - UPDATE BLOCKING INFORMATION
|
|
//========================================
|
|
if (suser.mBlocked)
|
|
{
|
|
// If Not Previously Blocked, Record The Start Time And Any Entites Involved
|
|
//---------------------------------------------------------------------------
|
|
if (!(actor->NPC->aiFlags&NPCAI_BLOCKED))
|
|
{
|
|
actor->NPC->aiFlags |= NPCAI_BLOCKED;
|
|
actor->NPC->blockedDebounceTime = level.time;
|
|
}
|
|
|
|
// If Navigation Or Steering Had A Target Entity (Trying To Go To), Record That Here
|
|
//-----------------------------------------------------------------------------------
|
|
actor->NPC->blockedTargetEntity = 0;
|
|
if (suser.mBlockedTgtEntity!=ENTITYNUM_NONE)
|
|
{
|
|
actor->NPC->blockedTargetEntity = &g_entities[suser.mBlockedTgtEntity];
|
|
}
|
|
VectorCopy(suser.mBlockedTgtPosition.v, actor->NPC->blockedTargetPosition);
|
|
}
|
|
else
|
|
{
|
|
// Nothing Blocking, So Turn Off The Blocked Stuff If It Is There
|
|
//----------------------------------------------------------------
|
|
if (actor->NPC->aiFlags&NPCAI_BLOCKED)
|
|
{
|
|
actor->NPC->aiFlags &= ~NPCAI_BLOCKED;
|
|
actor->NPC->blockedDebounceTime = 0;
|
|
actor->NPC->blockedTargetEntity = 0;
|
|
}
|
|
}
|
|
|
|
// If We've Been Blocked For More Than 2 Seconds, May Want To Take Corrective Action
|
|
//-----------------------------------------------------------------------------------
|
|
if (STEER::HasBeenBlockedFor(actor, 2000))
|
|
{
|
|
if (NAVDEBUG_showEnemyPath)
|
|
{
|
|
CG_DrawEdge(actor->currentOrigin, actor->NPC->blockedTargetPosition, EDGE_PATHBLOCKED);
|
|
if (actor->waypoint)
|
|
{
|
|
CVec3 Dumb(NAV::GetNodePosition(actor->waypoint));
|
|
CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_BLOCKED);
|
|
}
|
|
}
|
|
|
|
// If He Can Fly Or Jump, Try That
|
|
//---------------------------------
|
|
if ((actor->NPC->scriptFlags&SCF_NAV_CAN_FLY) ||
|
|
(actor->NPC->scriptFlags&SCF_NAV_CAN_JUMP)
|
|
)
|
|
{
|
|
// Ok, Well, How About Jumping To The Last Waypoint We Had That Was Valid
|
|
//------------------------------------------------------------------------
|
|
if ((STEER::HasBeenBlockedFor(actor, 8000)) &&
|
|
(!actor->waypoint || Distance(NAV::GetNodePosition(actor->waypoint), actor->currentOrigin)>150.0f) &&
|
|
(actor->lastWaypoint))
|
|
{
|
|
if (player &&
|
|
(STEER::HasBeenBlockedFor(actor, 15000)) &&
|
|
!gi.inPVS(player->currentOrigin, NAV::GetNodePosition(actor->lastWaypoint)) &&
|
|
!gi.inPVS(player->currentOrigin, actor->currentOrigin) &&
|
|
!G_CheckInSolidTeleport(NAV::GetNodePosition(actor->lastWaypoint), actor)
|
|
)
|
|
{
|
|
G_SetOrigin(actor, NAV::GetNodePosition(actor->lastWaypoint));
|
|
G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" );
|
|
}
|
|
else
|
|
{
|
|
NPC_TryJump(NAV::GetNodePosition(actor->lastWaypoint));
|
|
}
|
|
}
|
|
|
|
// First, Try Jumping Directly To Our Target Desired Location
|
|
//------------------------------------------------------------
|
|
else
|
|
{
|
|
// If We Had A Target Entity, Try Jumping There
|
|
//----------------------------------------------
|
|
if (NPCInfo->blockedTargetEntity)
|
|
{
|
|
NPC_TryJump(NPCInfo->blockedTargetEntity);
|
|
}
|
|
|
|
// Otherwise Try Jumping To The Target Position
|
|
//----------------------------------------------
|
|
else
|
|
{
|
|
NPC_TryJump(NPCInfo->blockedTargetPosition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PHASE V - FREE UP THE STEER USER
|
|
//===================================
|
|
mSteerUsers.free(mSteerUserIndex[actor->s.number]);
|
|
mSteerUserIndex[actor->s.number] = NULL_STEER_USER_INDEX;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Active?
|
|
//
|
|
// Simple way for external systemm to check if this object is active
|
|
// already.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
bool STEER::Active(gentity_t* actor)
|
|
{
|
|
return (mSteerUserIndex[actor->s.number]!=NULL_STEER_USER_INDEX);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// SafeToGoTo - returns true if it is safe for the actor to steer toward the target
|
|
// position
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::SafeToGoTo(gentity_t* actor, const vec3_t& targetPosition, int targetNode)
|
|
{
|
|
int actorNode = NAV::GetNearestNode(actor, true, targetNode);
|
|
float actorToTargetDistance = Distance(actor->currentOrigin, targetPosition);
|
|
|
|
|
|
// Are They Close Enough To Just Go There
|
|
//----------------------------------------
|
|
if (actorToTargetDistance<110.0f && fabsf(targetPosition[2]-actor->currentOrigin[2])<50.0f)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Are They Both Within The Radius Of Their Nearest Nav Point?
|
|
//-------------------------------------------------------------
|
|
if (actorToTargetDistance<500.0f)
|
|
{
|
|
// Are Both Actor And Target In Safe Radius?
|
|
//-------------------------------------------
|
|
if (NAV::OnNeighboringPoints(actorNode, targetNode) &&
|
|
NAV::InSafeRadius(actor->currentOrigin, actorNode, targetNode) &&
|
|
NAV::InSafeRadius(targetPosition, targetNode, actorNode))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If Close Enough, We May Be Able To Go Anyway, So Try A Trace
|
|
//--------------------------------------------------------------
|
|
if (actorToTargetDistance<400.0f)//250.0f)
|
|
{
|
|
if (!TIMER_Done(actor, "SafeToGoToDURATION"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (TIMER_Done(actor, "SafeToGoToCHECK"))
|
|
{
|
|
TIMER_Set( actor, "SafeToGoToCHECK", 1500); // Check Every 1.5 Seconds
|
|
if (MoveTrace(actor, targetPosition, true))
|
|
{
|
|
TIMER_Set(actor, "SafeToGoToDURATION", 2000); // Safe For 2 Seconds
|
|
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CVec3 Dumb(targetPosition);
|
|
CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_WHITE_TWOSECOND);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CVec3 Dumb(targetPosition);
|
|
CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_RED_TWOSECOND);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Master Functions
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::GoTo(gentity_t* actor, gentity_t* target, float reachedRadius, bool avoidCollisions)
|
|
{
|
|
// Can't Steer To A Guy In The Air
|
|
//---------------------------------
|
|
if (!target)
|
|
{
|
|
NAV::ClearPath(actor);
|
|
STEER::Stop(actor);
|
|
return true;
|
|
}
|
|
|
|
// If Within Reached Radius, Just Stop Right Here
|
|
//------------------------------------------------
|
|
if (STEER::Reached(actor, target->currentOrigin, reachedRadius, (actor->client && actor->client->moveType==MT_FLYSWIM)))
|
|
{
|
|
NAV::ClearPath(actor);
|
|
STEER::Stop(actor);
|
|
return true;
|
|
}
|
|
|
|
// Check To See If It Is Safe To Attempt Steering Toward The Target Position
|
|
//---------------------------------------------------------------------------
|
|
if (
|
|
//(target->client && target->client->ps.groundEntityNum==ENTITYNUM_NONE) ||
|
|
!STEER::SafeToGoTo(actor, target->currentOrigin, NAV::GetNearestNode(target))
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Ok, It Is, So Clear The Path, And Go Toward Our Target
|
|
//--------------------------------------------------------
|
|
NAV::ClearPath(actor);
|
|
STEER::Persue(actor, target, reachedRadius*4.0f);
|
|
if (avoidCollisions)
|
|
{
|
|
if (STEER::AvoidCollisions(actor, actor->client->leader)!=0.0f)
|
|
{
|
|
STEER::Blocked(actor, target); // Collision Detected
|
|
}
|
|
}
|
|
|
|
if (NAVDEBUG_showEnemyPath)
|
|
{
|
|
CG_DrawEdge(actor->currentOrigin, target->currentOrigin, EDGE_FOLLOWPOS);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Master Function- GoTo
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::GoTo(gentity_t* actor, const vec3_t& position, float reachedRadius, bool avoidCollisions)
|
|
{
|
|
// If Within Reached Radius, Just Stop Right Here
|
|
//------------------------------------------------
|
|
if (STEER::Reached(actor, position, reachedRadius, (actor->client && actor->client->moveType==MT_FLYSWIM)))
|
|
{
|
|
NAV::ClearPath(actor);
|
|
STEER::Stop(actor);
|
|
return true;
|
|
}
|
|
|
|
// Check To See If It Is Safe To Attempt Steering Toward The Target Position
|
|
//---------------------------------------------------------------------------
|
|
if (!STEER::SafeToGoTo(actor, position, NAV::GetNearestNode(position)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Ok, It Is, So Clear The Path, And Go Toward Our Target
|
|
//--------------------------------------------------------
|
|
NAV::ClearPath(actor);
|
|
STEER::Seek(actor, position, reachedRadius*2.0f);
|
|
if (avoidCollisions)
|
|
{
|
|
if (STEER::AvoidCollisions(actor, actor->client->leader)!=0.0f)
|
|
{
|
|
STEER::Blocked(actor, position); // Collision Detected
|
|
}
|
|
}
|
|
|
|
if (NAVDEBUG_showEnemyPath)
|
|
{
|
|
CVec3 Dumb(position);
|
|
CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_FOLLOWPOS);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Blocked Recorder
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void STEER::Blocked(gentity_t* actor, gentity_t* target)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mBlocked = true;
|
|
suser.mBlockedTgtEntity = target->s.number;
|
|
suser.mBlockedTgtPosition = target->currentOrigin;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Blocked Recorder
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
void STEER::Blocked(gentity_t* actor, const vec3_t& target)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mBlocked = true;
|
|
suser.mBlockedTgtEntity = ENTITYNUM_NONE;
|
|
suser.mBlockedTgtPosition = target;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Blocked Recorder
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::HasBeenBlockedFor(gentity_t* actor, int duration)
|
|
{
|
|
return (
|
|
(actor->NPC->aiFlags&NPCAI_BLOCKED) &&
|
|
(level.time - actor->NPC->blockedDebounceTime)>duration
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Stop
|
|
//
|
|
// Just Decelerate.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
float STEER::Stop(gentity_t* actor, float weight)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mDesiredVelocity.Clear();
|
|
suser.mDistance = 0.0f;
|
|
suser.mDesiredSpeed = 0.0f;
|
|
suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight);
|
|
|
|
if (actor->NPC->aiFlags&NPCAI_FLY)
|
|
{
|
|
int nearestNode = NAV::GetNearestNode(actor);
|
|
if (nearestNode>0 && !mGraph.get_node(nearestNode).mFlags.get_bit(CWayNode::WN_FLOATING))
|
|
{
|
|
actor->NPC->aiFlags &= ~NPCAI_FLY;
|
|
}
|
|
}
|
|
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
float STEER::MatchSpeed(gentity_t* actor, float speed, float weight)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mDesiredVelocity = suser.mVelocity;
|
|
suser.mDesiredVelocity.Truncate(speed);
|
|
suser.mDistance = 0.0f;
|
|
suser.mDesiredSpeed = 0.0f;
|
|
suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight);
|
|
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Seek
|
|
//
|
|
// The most basic of all steering operations, this function applies a change to
|
|
// the steering vector
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Seek(gentity_t* actor, const CVec3& pos, float slowingDistance, float weight, float desiredSpeed)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mSeekLocation = pos;
|
|
suser.mDesiredVelocity = suser.mSeekLocation - suser.mPosition;
|
|
|
|
//If The Difference In Height Is Small Enough, Just Kill It
|
|
//----------------------------------------------------------
|
|
if (fabsf(suser.mDesiredVelocity[2]) < 10.0f)
|
|
{
|
|
suser.mDesiredVelocity[2] = 0.0f;
|
|
}
|
|
|
|
|
|
suser.mDistance = suser.mDesiredVelocity.SafeNorm();
|
|
if (suser.mDistance>0.0f)
|
|
{
|
|
suser.mDesiredSpeed = (desiredSpeed!=0.0f)?(desiredSpeed):(suser.mMaxSpeed);
|
|
if (slowingDistance!=0.0f && suser.mDistance < slowingDistance)
|
|
{
|
|
suser.mDesiredSpeed *= (suser.mDistance/slowingDistance);
|
|
}
|
|
suser.mDesiredVelocity *= suser.mDesiredSpeed;
|
|
}
|
|
else
|
|
{
|
|
suser.mDesiredSpeed = 0.0f;
|
|
suser.mDesiredVelocity.Clear();
|
|
}
|
|
|
|
suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight);
|
|
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
return suser.mDistance;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Flee
|
|
//
|
|
// Similar to seek, except there is no concept of a slowing distance. Adds the
|
|
// position vectors instead of subtracting them to obtain a desired velocity away
|
|
// from the target.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Flee(gentity_t* actor, const CVec3& pos, float weight)
|
|
{
|
|
assert(Active(actor));
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mDesiredVelocity = suser.mPosition - pos;
|
|
suser.mDistance = suser.mDesiredVelocity.SafeNorm();
|
|
suser.mDesiredSpeed = suser.mMaxSpeed;
|
|
suser.mDesiredVelocity *= suser.mDesiredSpeed;
|
|
suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight);
|
|
suser.mSeekLocation = pos + suser.mDesiredVelocity;
|
|
|
|
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
return suser.mDistance;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Persue
|
|
//
|
|
// Predict the target's position and seek that.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Persue(gentity_t* actor, gentity_t* target, float slowingDistance)
|
|
{
|
|
assert(Active(actor) && target);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
CVec3 ProjectedTargetPosition(target->currentOrigin);
|
|
|
|
if (target->client)
|
|
{
|
|
float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition);
|
|
|
|
CVec3 TargetVelocity(target->client->ps.velocity);
|
|
float TargetSpeed = TargetVelocity.SafeNorm();
|
|
if (TargetSpeed>0.0f)
|
|
{
|
|
TargetVelocity *= (DistToTarget + 5.0f);
|
|
ProjectedTargetPosition += TargetVelocity;
|
|
}
|
|
}
|
|
|
|
return Seek(actor, ProjectedTargetPosition, slowingDistance);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Persue
|
|
//
|
|
// Predict the target's position and seek that.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Persue(gentity_t* actor, gentity_t* target, float slowingDistance, float offsetForward, float offsetRight, float offsetUp, bool relativeToTargetFacing)
|
|
{
|
|
assert(Active(actor) && target);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
CVec3 ProjectedTargetPosition(target->currentOrigin);
|
|
|
|
// If Target Is A Client, Take His Velocity Into Account And Project His Position
|
|
//--------------------------------------------------------------------------------
|
|
if (target->client)
|
|
{
|
|
float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition);
|
|
|
|
CVec3 TargetVelocity(target->client->ps.velocity);
|
|
float TargetSpeed = TargetVelocity.SafeNorm();
|
|
if (TargetSpeed>0.0f)
|
|
{
|
|
TargetVelocity[2] *= 0.1f;
|
|
TargetVelocity *= (DistToTarget + 5.0f);
|
|
ProjectedTargetPosition += TargetVelocity;
|
|
}
|
|
}
|
|
|
|
// Get The Direction Toward The Target
|
|
//-------------------------------------
|
|
CVec3 DirectionToTarget(ProjectedTargetPosition);
|
|
DirectionToTarget -= suser.mPosition;
|
|
DirectionToTarget.SafeNorm();
|
|
|
|
|
|
CVec3 ProjectForward(DirectionToTarget);
|
|
CVec3 ProjectRight;
|
|
CVec3 ProjectUp;
|
|
|
|
if (relativeToTargetFacing)
|
|
{
|
|
AngleVectors(target->currentAngles, ProjectForward.v, ProjectRight.v, ProjectUp.v);
|
|
if (ProjectRight.Dot(DirectionToTarget)>0.0f)
|
|
{
|
|
ProjectRight *= -1.0f; // If Going In Same Direction As Target Right, Project Toward Target Left
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MakeNormalVectors(ProjectForward.v, ProjectRight.v, ProjectUp.v);
|
|
}
|
|
|
|
ProjectedTargetPosition.ScaleAdd(ProjectForward, offsetForward);
|
|
ProjectedTargetPosition.ScaleAdd(ProjectRight, offsetRight);
|
|
ProjectedTargetPosition.ScaleAdd(ProjectUp, offsetUp);
|
|
|
|
return Seek(actor, ProjectedTargetPosition, slowingDistance);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Evade
|
|
//
|
|
// Predict the target's position and flee that.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Evade(gentity_t* actor, gentity_t* target)
|
|
{
|
|
assert(Active(actor) && target);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
CVec3 ProjectedTargetPosition(target->currentOrigin);
|
|
|
|
if (target->client)
|
|
{
|
|
float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition);
|
|
|
|
|
|
CVec3 TargetVelocity(target->client->ps.velocity);
|
|
float TargetSpeed = TargetVelocity.SafeNorm();
|
|
if (TargetSpeed>0.0f)
|
|
{
|
|
TargetVelocity *= (DistToTarget + 5.0f);
|
|
ProjectedTargetPosition += TargetVelocity;
|
|
}
|
|
}
|
|
|
|
return Flee(actor, ProjectedTargetPosition);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Separation
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Separation(gentity_t* actor, float Scale)
|
|
{
|
|
assert(Active(actor) && actor);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
if (!suser.mNeighbors.empty())
|
|
{
|
|
for (int i=0; i<suser.mNeighbors.size(); i++)
|
|
{
|
|
if (suser.mNeighbors[i]->s.number>actor->s.number)
|
|
{
|
|
CVec3 NbrPos(suser.mNeighbors[i]->currentOrigin);
|
|
CVec3 NbrToAct(suser.mPosition - NbrPos);
|
|
float NbrToActDist = NbrToAct.Len2();
|
|
if (NbrToActDist>1.0f)
|
|
{
|
|
NbrToActDist = (1.0f/NbrToActDist);
|
|
NbrToAct *= NbrToActDist * (suser.mMaxSpeed * 10.0f) * Scale;
|
|
suser.mSteering += NbrToAct;
|
|
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CVec3 Prj(suser.mPosition + NbrToAct);
|
|
CG_DrawEdge(suser.mPosition.v, Prj.v, EDGE_IMPACT_POSSIBLE); // Separation
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Alignment
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Alignment(gentity_t* actor, float Scale)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Cohesion
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Cohesion(gentity_t* actor, float Scale)
|
|
{
|
|
assert(Active(actor) && actor);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
if (!suser.mNeighbors.empty())
|
|
{
|
|
CVec3 AvePosition;
|
|
for (int i=0; i<suser.mNeighbors.size(); i++)
|
|
{
|
|
AvePosition += CVec3(suser.mNeighbors[i]->currentOrigin);
|
|
}
|
|
AvePosition *= 1.0f/suser.mNeighbors.size();
|
|
return Seek(actor, AvePosition);
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Find Nearest Leader
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
gentity_t* STEER::SelectLeader(gentity_t* actor)
|
|
{
|
|
assert(Active(actor) && actor);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
for (int i=0; i<suser.mNeighbors.size(); i++)
|
|
{
|
|
if (suser.mNeighbors[i]->s.number>actor->s.number && !Q_stricmp(suser.mNeighbors[i]->NPC_type, actor->NPC_type ))
|
|
{
|
|
return suser.mNeighbors[i];
|
|
}
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Path - Seek to the next position on the path (or jump)
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Path(gentity_t* actor)
|
|
{
|
|
if (NAV::HasPath(actor))
|
|
{
|
|
CVec3 NextPosition;
|
|
float NextSlowingRadius;
|
|
bool Fly = false;
|
|
bool Jump = false;
|
|
|
|
if (!NAV::NextPosition(actor, NextPosition, NextSlowingRadius, Fly, Jump))
|
|
{
|
|
assert("STEER: Unable to obtain the next path position"==0);
|
|
return 0.0f;
|
|
}
|
|
|
|
// Start Flying If Next Point Is In The Air
|
|
//------------------------------------------
|
|
if (Fly)
|
|
{
|
|
actor->NPC->aiFlags |= NPCAI_FLY;
|
|
}
|
|
|
|
// Otherwise, If Next Point Is On The Ground, No Need To Fly Any Longer
|
|
//----------------------------------------------------------------------
|
|
else if (actor->NPC->aiFlags&NPCAI_FLY)
|
|
{
|
|
actor->NPC->aiFlags &= ~NPCAI_FLY;
|
|
}
|
|
|
|
// Start Jumping If Next Point Is A Jump
|
|
//---------------------------------------
|
|
if (Jump)
|
|
{
|
|
if (NPC_TryJump(NextPosition.v))
|
|
{
|
|
actor->NPC->aiFlags |= NPCAI_JUMP;
|
|
return 1.0f;
|
|
}
|
|
}
|
|
actor->NPC->aiFlags &=~NPCAI_JUMP;
|
|
|
|
|
|
// Preview His Path
|
|
//------------------
|
|
if (NAVDEBUG_showEnemyPath)
|
|
{
|
|
CVec3 Prev(actor->currentOrigin);
|
|
TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath;
|
|
for (int i=path.size()-1; i>=0; i--)
|
|
{
|
|
CG_DrawEdge(Prev.v, path[i].mPoint.v, EDGE_PATH);
|
|
Prev = path[i].mPoint;
|
|
}
|
|
}
|
|
|
|
// If Jump Was On, But We Reached This Point, It Must Have Failed
|
|
//----------------------------------------------------------------
|
|
if (Jump)
|
|
{
|
|
Stop(actor);
|
|
return 0.0f; // We've Failed!
|
|
}
|
|
|
|
// Otherwise, Go!
|
|
//----------------
|
|
return Seek(actor, NextPosition, NextSlowingRadius);
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Wander
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::Wander(gentity_t* actor)
|
|
{
|
|
assert(Active(actor) && actor);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
|
|
CVec3 Direction(CVec3::mX);
|
|
|
|
if (suser.mSpeed>0.1f)
|
|
{
|
|
Direction = suser.mVelocity;
|
|
Direction.VecToAng();
|
|
Direction[2] += Q_irand(-5, 5);
|
|
Direction.AngToVec();
|
|
}
|
|
|
|
Direction *= 70.0f;
|
|
Seek(actor, suser.mPosition+Direction);
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Follow A Leader Entity
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::FollowLeader(gentity_t* actor, gentity_t* leader, float dist)
|
|
{
|
|
assert(Active(actor) && actor && leader && leader->client);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
|
|
float LeaderSpeed = leader->resultspeed;
|
|
int TimeRemaining = (leader->followPosRecalcTime - level.time);
|
|
|
|
if (TimeRemaining<0 || (LeaderSpeed>0.0f && TimeRemaining>1000))
|
|
{
|
|
CVec3 LeaderPosition(leader->currentOrigin);
|
|
CVec3 LeaderDirection(leader->currentAngles);
|
|
LeaderDirection.pitch() = 0;
|
|
LeaderDirection.AngToVec();
|
|
|
|
if (!actor->enemy && !leader->enemy)
|
|
{
|
|
LeaderDirection = (LeaderPosition - suser.mPosition);
|
|
LeaderDirection.Norm();
|
|
}
|
|
|
|
CVec3 FollowPosition(LeaderDirection);
|
|
FollowPosition *= (-1.0f * (fabsf(dist)+suser.mRadius));
|
|
FollowPosition += LeaderPosition;
|
|
|
|
MoveTrace(leader, FollowPosition, true);
|
|
if (mMoveTrace.fraction>0.1)
|
|
{
|
|
FollowPosition = mMoveTrace.endpos;
|
|
FollowPosition += (LeaderDirection * suser.mRadius);
|
|
|
|
VectorCopy(FollowPosition.v, leader->followPos);
|
|
leader->followPosWaypoint = NAV::GetNearestNode(leader->followPos, leader->waypoint, 0, leader->s.number);
|
|
}
|
|
|
|
float MaxSpeed = (g_speed->value);
|
|
if (LeaderSpeed>MaxSpeed)
|
|
{
|
|
MaxSpeed = LeaderSpeed;
|
|
}
|
|
float SpeedScale = (1.0f - (LeaderSpeed / MaxSpeed));
|
|
|
|
leader->followPosRecalcTime =
|
|
(level.time) +
|
|
(Q_irand(50, 500)) +
|
|
(SpeedScale * Q_irand(3000, 8000)) +
|
|
((!actor->enemy && !leader->enemy)?(Q_irand(8000, 15000)):(0));
|
|
}
|
|
|
|
if (NAVDEBUG_showEnemyPath)
|
|
{
|
|
CG_DrawEdge(leader->currentOrigin, leader->followPos, EDGE_FOLLOWPOS);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Test For A Collision
|
|
//
|
|
// This is a helper function used by collision avoidance.
|
|
//
|
|
// Returns true when a collision is detected (not safe)
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
bool TestCollision(gentity_t* actor, SSteerUser& suser, const CVec3& ProjectVelocity, float ProjectSpeed, ESide Side, float weight=1.0f)
|
|
{
|
|
// Test To See If The Projected Position Is Safe
|
|
//-----------------------------------------------
|
|
bool Safe = (Side==Side_None)?(MoveTrace(actor, suser.mProjectFwd)):(MoveTrace(actor, suser.mProjectSide));
|
|
if (mMoveTrace.entityNum!=ENTITYNUM_NONE && mMoveTrace.entityNum!=ENTITYNUM_WORLD)
|
|
{
|
|
// The Ignore Entity Is Safe
|
|
//---------------------------
|
|
if (mMoveTrace.entityNum==suser.mIgnoreEntity)
|
|
{
|
|
Safe = true;
|
|
}
|
|
|
|
// Doors Are Always Safe
|
|
//-----------------------
|
|
if (g_entities[mMoveTrace.entityNum].classname &&
|
|
Q_stricmp(g_entities[mMoveTrace.entityNum].classname, "func_door")==0)
|
|
{
|
|
Safe = true;
|
|
}
|
|
|
|
// If It's Breakable And We Can Go Through It, Then That's Safe Too
|
|
//------------------------------------------------------------------
|
|
if ((actor->NPC->aiFlags&NPCAI_NAV_THROUGH_BREAKABLES) &&
|
|
G_EntIsBreakable(mMoveTrace.entityNum, actor))
|
|
{
|
|
Safe = true;
|
|
}
|
|
|
|
// TODO: Put Other Ignore Cases Below
|
|
//------------------------------------
|
|
}
|
|
|
|
// Need These Vectors To Draw The Lines Below
|
|
//--------------------------------------------
|
|
CVec3 ContactNormal(mMoveTrace.plane.normal);
|
|
CVec3 ContactPoint( mMoveTrace.endpos);
|
|
int ContactNum = mMoveTrace.entityNum;
|
|
|
|
|
|
if (!Safe && Side==Side_None)
|
|
{
|
|
// Did We Hit A Client?
|
|
//----------------------
|
|
if (ContactNum!=ENTITYNUM_WORLD && ContactNum!=ENTITYNUM_NONE && g_entities[ContactNum].client!=0)
|
|
{
|
|
gentity_t* Contact = &g_entities[ContactNum];
|
|
//bool ContactIsPlayer = (Contact->client->ps.clientNum==0);
|
|
CVec3 ContactVelocity (Contact->client->ps.velocity);
|
|
CVec3 ContactPosition (Contact->currentOrigin);
|
|
float ContactSpeed = (ContactVelocity.Len());
|
|
|
|
// If He Is Moving, We Might Be Able To Just Slow Down Some And Stay Behind Him
|
|
//------------------------------------------------------------------------------
|
|
if (ContactSpeed>0.01f)
|
|
{
|
|
if (ContactSpeed<ProjectSpeed)
|
|
{
|
|
CVec3 MyDirection(ProjectVelocity);
|
|
CVec3 ContactDirection(ContactVelocity);
|
|
|
|
ContactDirection.Norm();
|
|
MyDirection.Norm();
|
|
|
|
float DirectionSimilarity = fabsf(MyDirection.Dot(ContactDirection));
|
|
if (DirectionSimilarity>0.5)
|
|
{
|
|
// Match Speed
|
|
//-------------
|
|
suser.mDesiredVelocity = suser.mVelocity;
|
|
suser.mDesiredVelocity.Truncate(ContactSpeed);
|
|
suser.mSteering += ((suser.mDesiredVelocity - ProjectVelocity) * DirectionSimilarity);
|
|
suser.mIgnoreEntity = ContactNum; // So The Side Trace Does Not Care About This Guy
|
|
assert(suser.mSteering.IsFinite());
|
|
Safe = true; // We'll Say It's Safe For Now
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ok, He's Just Standing There...
|
|
//---------------------------------
|
|
else
|
|
{
|
|
CVec3 Next(suser.mSeekLocation);
|
|
if (NAV::HasPath(actor))
|
|
{
|
|
Next = CVec3(NAV::NextPosition(actor));
|
|
}
|
|
|
|
CVec3 AbsMin(Contact->absmin);
|
|
CVec3 AbsMax(Contact->absmax);
|
|
|
|
// Is The Contact Standing Over Our Next Position?
|
|
//-------------------------------------------------
|
|
if (Next>AbsMin && Next<AbsMax)
|
|
{
|
|
// Ok, Just Give Up And Stop For Now
|
|
//-----------------------------------
|
|
suser.mSteering -= ProjectVelocity;
|
|
suser.mIgnoreEntity = ContactNum;
|
|
assert(suser.mSteering.IsFinite());
|
|
|
|
Safe = true; // We say it is "Safe" because We Don't Want To Try And Steer Around
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ignore Shallow Slopes
|
|
//-----------------------
|
|
if (!Safe && ContactNormal[2]>0.0f && ContactNormal[2]<MIN_WALK_NORMAL)
|
|
{
|
|
Safe = true;
|
|
}
|
|
|
|
// If Still Not Safe
|
|
//-------------------
|
|
if (!Safe)
|
|
{
|
|
// Normalize Projected Velocity And Dot It With The Contact Normal
|
|
//-----------------------------------------------------------------
|
|
CVec3 ProjectDirection(ProjectVelocity);
|
|
ProjectDirection.Norm();
|
|
|
|
// Cross The Contact Normal With Z In Order To Get A Parallel Vector
|
|
//-------------------------------------------------------------------
|
|
ContactNormal.Cross(CVec3::mZ);
|
|
|
|
// Only Force A Particular Steering Side For A Max Of 2 Seconds, Then Retest Dot Product
|
|
//---------------------------------------------------------------------------------------
|
|
if (actor->NPC->lastAvoidSteerSide!=Side_None && actor->NPC->lastAvoidSteerSideDebouncer<level.time)
|
|
{
|
|
actor->NPC->lastAvoidSteerSide = Side_None;
|
|
actor->NPC->lastAvoidSteerSideDebouncer = level.time + Q_irand(500, STEER::SIDE_LOCKED_TIMER);
|
|
}
|
|
|
|
// Make Sure The Normal Is Going The Same Way As The Velocity
|
|
//-----------------------------------------------------------
|
|
if (((ESide)(actor->NPC->lastAvoidSteerSide)==Side_Right) ||
|
|
((ESide)(actor->NPC->lastAvoidSteerSide)==Side_None && ContactNormal.Dot(ProjectDirection)<0.0f))
|
|
{
|
|
ContactNormal *= -1.0f;
|
|
actor->NPC->lastAvoidSteerSide = Side_Right;
|
|
}
|
|
else
|
|
{
|
|
actor->NPC->lastAvoidSteerSide = Side_Left;
|
|
}
|
|
ContactNormal[2] = 0.0f;
|
|
|
|
// Scale Up The Normal A Bit And Translate The Contact Point Out
|
|
//---------------------------------------------------------------
|
|
ContactNormal *= (ProjectSpeed * weight * 0.5);
|
|
ContactPoint += ContactNormal;
|
|
|
|
// Move Toward The Contact Point
|
|
//-------------------------------
|
|
STEER::Seek(actor, ContactPoint, weight);
|
|
}
|
|
|
|
// If It Was Safe, Reset Our Avoid Side Data
|
|
//-------------------------------------------
|
|
else if (Side==Side_None)
|
|
{
|
|
actor->NPC->lastAvoidSteerSide = Side_None;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DEBUG GRAPHICS
|
|
//=================================================================
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CVec3 Prj((Side==Side_None)?(suser.mProjectFwd):(suser.mProjectSide));
|
|
|
|
if (Safe)
|
|
{
|
|
CG_DrawEdge(suser.mPosition.v, Prj.v, EDGE_IMPACT_SAFE); // WHITE LINE
|
|
}
|
|
else
|
|
{
|
|
CG_DrawEdge(suser.mPosition.v, mMoveTrace.endpos, EDGE_IMPACT_POSSIBLE); // RED LINE
|
|
CG_DrawEdge(mMoveTrace.endpos, ContactPoint.v, EDGE_IMPACT_POSSIBLE); // RED LINE
|
|
}
|
|
}
|
|
//=================================================================
|
|
|
|
return !Safe;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Collision Avoidance
|
|
//
|
|
// Usually the last steering operation to call before finialization, this operation
|
|
// attempts to avoid collisions with nearby entities and architecture by thrusing
|
|
// away from them.
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
float STEER::AvoidCollisions(gentity_t* actor, gentity_t* leader)
|
|
{
|
|
assert(Active(actor) && actor && actor->client);
|
|
SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]];
|
|
|
|
suser.mIgnoreEntity = -5;
|
|
|
|
|
|
// Simulate The Results Of Any Current Steering To The Velocity
|
|
//--------------------------------------------------------------
|
|
CVec3 ProjectedSteering(suser.mSteering);
|
|
CVec3 ProjectedVelocity(suser.mVelocity);
|
|
float ProjectedSpeed = suser.mSpeed;
|
|
float Newtons;
|
|
|
|
Newtons = ProjectedSteering.Truncate(suser.mMaxForce);
|
|
if (Newtons>1E-10)
|
|
{
|
|
ProjectedSteering /= suser.mMass;
|
|
ProjectedVelocity += ProjectedSteering;
|
|
ProjectedSpeed = ProjectedVelocity.Truncate(suser.mMaxSpeed);
|
|
}
|
|
|
|
|
|
// Select An Ignore Entity
|
|
//-------------------------
|
|
if (actor->NPC->behaviorState!=BS_CINEMATIC)
|
|
{
|
|
if (actor->NPC->greetEnt && actor->NPC->greetEnt->owner==NPC)
|
|
{
|
|
suser.mIgnoreEntity = actor->NPC->greetEnt->s.clientNum;
|
|
}
|
|
else if (actor->enemy)
|
|
{
|
|
suser.mIgnoreEntity = actor->enemy->s.clientNum;
|
|
}
|
|
else if (leader)
|
|
{
|
|
suser.mIgnoreEntity = leader->s.clientNum;
|
|
}
|
|
}
|
|
|
|
|
|
// If Moving
|
|
//-----------
|
|
if (ProjectedSpeed>0.01f)
|
|
{
|
|
CVec3 ProjectVelocitySide(ProjectedVelocity);
|
|
ProjectVelocitySide.Reposition(CVec3::mZero, (actor->NPC->avoidSide==Side_Left)?(40.0f):(-40.0f));
|
|
|
|
// Project Based On Our ProjectedVelocity
|
|
//-----------------------------------------
|
|
suser.mProjectFwd = suser.mPosition + (ProjectedVelocity * 1.0f);
|
|
suser.mProjectSide = suser.mPosition + (ProjectVelocitySide * 0.3f);
|
|
|
|
// Test For Collisions In The Front And On The Sides
|
|
//---------------------------------------------------
|
|
bool HitFront = TestCollision(actor, suser, ProjectedVelocity, ProjectedSpeed, Side_None, 1.0f);
|
|
bool HitSide = TestCollision(actor, suser, ProjectedVelocity, ProjectedSpeed, (ESide)actor->NPC->avoidSide, 0.5f);
|
|
if (!HitSide)
|
|
{
|
|
// If The Side Is Clear, Try The Other Side
|
|
//------------------------------------------
|
|
actor->NPC->avoidSide = (actor->NPC->avoidSide==Side_Left)?(Side_Right):(Side_Left);
|
|
}
|
|
|
|
// Hit Something!
|
|
//----------------
|
|
if (HitFront || HitSide)
|
|
{
|
|
return ProjectedSpeed;
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Reached
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::Reached(gentity_t* actor, gentity_t* target, float targetRadius, bool flying)
|
|
{
|
|
if (!actor || !target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CVec3 ActorPos(actor->currentOrigin);
|
|
CVec3 ActorMins(actor->absmin);
|
|
CVec3 ActorMaxs(actor->absmax);
|
|
|
|
CVec3 TargetPos(target->currentOrigin);
|
|
|
|
if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPos<ActorMaxs))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Reached
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::Reached(gentity_t* actor, NAV::TNodeHandle target, float targetRadius, bool flying)
|
|
{
|
|
if (!actor || !target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CVec3 ActorPos(actor->currentOrigin);
|
|
CVec3 ActorMins(actor->absmin);
|
|
CVec3 ActorMaxs(actor->absmax);
|
|
|
|
CVec3 TargetPos(NAV::GetNodePosition(target));
|
|
|
|
if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPos<ActorMaxs))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
// Reached
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
bool STEER::Reached(gentity_t* actor, const vec3_t& target, float targetRadius, bool flying)
|
|
{
|
|
if (!actor || !target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CVec3 ActorPos(actor->currentOrigin);
|
|
CVec3 ActorMins(actor->absmin);
|
|
CVec3 ActorMaxs(actor->absmax);
|
|
|
|
CVec3 TargetPos(target);
|
|
|
|
if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPos<ActorMaxs))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if (target->client && target->client->ps.groundEntityNum == ENTITYNUM_NONE)
|
|
// {
|
|
// TargetPos -= ActorPos;
|
|
// if (fabsf(TargetPos[2]<(targetRadius*8)))
|
|
// {
|
|
// TargetPos[2] = 0.0f;
|
|
// if (TargetPos.Len2()<((targetRadius*2.0f)*(targetRadius*2.0f)))
|
|
// {
|
|
// return true;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// Clean up all of the krufty structures that only grow, never shrink, eventually
|
|
// causing asserts and subsequent memory trashing.
|
|
void ClearAllNavStructures(void)
|
|
{
|
|
TEntEdgeMap::iterator i = mEntEdgeMap.begin();
|
|
for ( ; i != mEntEdgeMap.end(); i++)
|
|
{
|
|
i->clear();
|
|
}
|
|
mEntEdgeMap.clear();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|