mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
3827 lines
72 KiB
C++
3827 lines
72 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2015 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// navigate.cpp: C++ implementation of the A* search algorithm.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
#include "navigate.h"
|
|
#include "misc.h"
|
|
#include "doors.h"
|
|
#include "actor.h"
|
|
#include "player.h"
|
|
#include "debuglines.h"
|
|
#include "scriptexception.h"
|
|
|
|
#define PATHFILE_VERSION 103
|
|
|
|
int path_checkthisframe;
|
|
cvar_t *ai_showroutes;
|
|
cvar_t *ai_showroutes_distance;
|
|
cvar_t *ai_shownodenums;
|
|
cvar_t *ai_shownode;
|
|
cvar_t *ai_showallnode;
|
|
cvar_t *ai_showpath;
|
|
cvar_t *ai_fallheight;
|
|
cvar_t *ai_debugpath;
|
|
cvar_t *ai_pathchecktime;
|
|
cvar_t *ai_pathcheckdist;
|
|
|
|
static float *path_start;
|
|
static float *path_end;
|
|
static PathNode *Node;
|
|
static float path_totaldir[ 2 ];
|
|
static float path_p[ 3 ];
|
|
static float path_startdir[ 2 ];
|
|
|
|
static Entity *IgnoreObjects[ MAX_GENTITIES ];
|
|
static int NumIgnoreObjects;
|
|
|
|
static qboolean pathnodesinitialized = false;
|
|
static qboolean loadingarchive = false;
|
|
static qboolean pathnodescalculated = false;
|
|
int ai_maxnode;
|
|
|
|
MapCell PathSearch::PathMap[ PATHMAP_GRIDSIZE ][ PATHMAP_GRIDSIZE ];
|
|
PathNode *PathSearch::open;
|
|
int PathSearch::findFrame;
|
|
qboolean PathSearch::m_bNodesloaded;
|
|
qboolean PathSearch::m_NodeCheckFailed;
|
|
int PathSearch::m_LoadIndex;
|
|
|
|
PathNode *PathSearch::pathnodes[ MAX_PATHNODES ];
|
|
int PathSearch::nodecount;
|
|
float PathSearch::total_dist;
|
|
const char *PathSearch::last_error;
|
|
|
|
byte *bulkNavMemory = NULL;
|
|
byte *startBulkNavMemory = NULL;
|
|
|
|
Vector PLAYER_BASE_MIN( -15.5f, -15.5f, 0 );
|
|
Vector PLAYER_BASE_MAX( 15.5f, 15.5f, 0 );
|
|
Vector testpos[ 200 ];
|
|
Vector ai_startpath;
|
|
Vector ai_endpath;
|
|
|
|
float NODE_MINS[ 3 ] = { -15, -15, 0 };
|
|
float NODE_MAXS[ 3 ] = { 15, 15, 94 };
|
|
float COLOR_PATHNODE_ERROR[ 3 ] = { 0, 0, 0 };
|
|
float COLOR_PATHNODE_COVER[ 3 ] = { 0, 1, 0 };
|
|
float COLOR_PATHNODE_CORNER_LEFT[ 3 ] = { 1, 1, 0 };
|
|
float COLOR_PATHNODE_CORNER_RIGHT[ 3 ] = { 0.7f, 1, 0 };
|
|
float COLOR_PATHNODE_SNIPER[ 3 ] = { 1, 0, 0 };
|
|
float COLOR_PATHNODE_CRATE[ 3 ] = { 3, 0, 0 };
|
|
float COLOR_PATHNODE_CONCEALMENT[ 3 ] = { 0, 0, 1 };
|
|
float COLOR_PATHNODE_DUCK[ 3 ] = { 0, 1, 1 };
|
|
float COLOR_PATHNODE_DEFAULT[ 3 ] = { 1, 0, 1 };
|
|
|
|
int testcount = 0;
|
|
static ActorPath *test_path = NULL;
|
|
|
|
struct {
|
|
float fMinRangeSquared;
|
|
float fMaxRangeSquared;
|
|
float fMinAngle;
|
|
float fMaxAngle;
|
|
}
|
|
|
|
g_AttackParms[] =
|
|
{
|
|
{ 64 * 64, 2048 * 2048, 150.0f, 210.0f },
|
|
{ 64 * 64, 2048 * 2048, 150.0f, 210.0f },
|
|
{ 96 * 96, 2048 * 2048, 320.0f, 40.0f },
|
|
};
|
|
|
|
PathSearch PathManager;
|
|
|
|
int path_checksthisframe;
|
|
|
|
void AI_AddNode
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
int i = PathSearch::nodecount;
|
|
|
|
assert( node );
|
|
|
|
if( i < MAX_PATHNODES )
|
|
{
|
|
if( i > ai_maxnode )
|
|
{
|
|
ai_maxnode = i;
|
|
}
|
|
PathSearch::pathnodes[ i ] = node;
|
|
node->nodenum = i;
|
|
PathSearch::nodecount++;
|
|
return;
|
|
}
|
|
|
|
gi.Error( ERR_DROP, "Exceeded MAX_PATHNODES!\n" );
|
|
}
|
|
|
|
qboolean CheckMove
|
|
(
|
|
Vector &origin,
|
|
Vector &pos,
|
|
short int *path_fallheight,
|
|
float size
|
|
)
|
|
{
|
|
mmove_t mm;
|
|
int i;
|
|
float air_z;
|
|
float fallheight;
|
|
float test_fallheight;
|
|
float error;
|
|
trace_t trace;
|
|
vec3_t dir;
|
|
vec3_t end;
|
|
|
|
memset( &mm, 0, sizeof( mmove_t ) );
|
|
|
|
VectorClear( mm.velocity );
|
|
VectorCopy( origin, mm.origin );
|
|
mm.desired_speed = 150.0f;
|
|
mm.entityNum = ENTITYNUM_NONE;
|
|
mm.tracemask = MASK_PATHSOLID;
|
|
mm.frametime = 0.1f;
|
|
mm.desired_dir[ 0 ] = pos[ 0 ] - origin[ 0 ];
|
|
mm.desired_dir[ 1 ] = pos[ 1 ] - origin[ 1 ];
|
|
VectorNormalize2D( mm.desired_dir );
|
|
|
|
mm.groundPlane = qfalse;
|
|
mm.walking = qfalse;
|
|
|
|
mm.mins[ 0 ] = -size;
|
|
mm.mins[ 1 ] = -size;
|
|
mm.mins[ 2 ] = 0;
|
|
mm.maxs[ 0 ] = size;
|
|
mm.maxs[ 1 ] = size;
|
|
mm.maxs[ 2 ] = 94.0f;
|
|
|
|
testcount = 0;
|
|
fallheight = 0.0f;
|
|
air_z = mm.origin[ 2 ];
|
|
|
|
for( i = 200; i != 1; i-- )
|
|
{
|
|
testpos[ i - 1 ] = mm.origin;
|
|
testcount++;
|
|
|
|
MmoveSingle( &mm );
|
|
|
|
if( mm.groundPlane )
|
|
{
|
|
test_fallheight = air_z - mm.origin[ 2 ];
|
|
|
|
if( test_fallheight > fallheight )
|
|
{
|
|
if( test_fallheight > 1024.0f )
|
|
return false;
|
|
|
|
fallheight = test_fallheight;
|
|
}
|
|
|
|
air_z = mm.origin[ 2 ];
|
|
}
|
|
|
|
dir[ 0 ] = pos[ 0 ] - mm.origin[ 0 ];
|
|
dir[ 1 ] = pos[ 1 ] - mm.origin[ 1 ];
|
|
|
|
if( DotProduct2D( dir, mm.desired_dir ) <= 0.1f )
|
|
{
|
|
error = mm.origin[ 2 ] - pos[ 2 ];
|
|
|
|
gi.Printf( "error = %f\n", error );
|
|
|
|
*path_fallheight = ( short )fallheight;
|
|
if( fabs( error ) > 94.0f )
|
|
{
|
|
if( mm.groundPlane )
|
|
return false;
|
|
|
|
mm.desired_dir[ 0 ] = dir[ 0 ];
|
|
mm.desired_dir[ 1 ] = dir[ 1 ];
|
|
VectorNormalize2D( mm.desired_dir );
|
|
}
|
|
else if( error > 0.0f && !mm.groundPlane )
|
|
{
|
|
end[ 0 ] = mm.origin[ 0 ];
|
|
end[ 1 ] = mm.origin[ 1 ];
|
|
end[ 2 ] = pos[ 2 ];
|
|
|
|
trace = G_Trace( mm.origin,
|
|
mm.mins,
|
|
mm.maxs,
|
|
end,
|
|
NULL,
|
|
MASK_PATHSOLID,
|
|
true,
|
|
"CheckMove" );
|
|
|
|
test_fallheight = mm.origin[ 2 ] - trace.endpos[ 2 ];
|
|
|
|
if( test_fallheight <= 18.0f )
|
|
{
|
|
*path_fallheight = ( short )test_fallheight + fallheight;
|
|
return test_fallheight + fallheight <= 1024.0f;
|
|
}
|
|
|
|
if( mm.groundPlane )
|
|
return false;
|
|
|
|
mm.desired_dir[ 0 ] = dir[ 0 ];
|
|
mm.desired_dir[ 1 ] = dir[ 1 ];
|
|
VectorNormalize2D( mm.desired_dir );
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( mm.hit_obstacle )
|
|
{
|
|
gi.DPrintf( "obstacle hit\n" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_pathnode (1 0 0) (-24 -24 0) (24 24 32) FLEE DUCK COVER DOOR JUMP LADDER
|
|
|
|
FLEE marks the node as a safe place to flee to. Actor will be removed when it reaches a flee node and is not visible to a player.
|
|
|
|
DUCK marks the node as a good place to duck behind during weapon fire.
|
|
|
|
COVER marks the node as a good place to hide behind during weapon fire.
|
|
|
|
DOOR marks the node as a door node. If an adjacent node has DOOR marked as well, the actor will only use the path if the door in between them is unlocked.
|
|
|
|
JUMP marks the node as one to jump from when going to the node specified by target.
|
|
"target" the pathnode to jump to.
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_Path_SetNodeFlags
|
|
(
|
|
"spawnflags",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"node_flags",
|
|
"Sets the path nodes flags.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION( SimpleEntity, PathNode, "info_pathnode" )
|
|
{
|
|
{ &EV_Path_SetNodeFlags, &PathNode::SetNodeFlags },
|
|
{ &EV_IsTouching, &PathNode::IsTouching },
|
|
{ &EV_Delete, &PathNode::Remove },
|
|
{ &EV_Remove, &PathNode::Remove },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static Vector pathNodesChecksum;
|
|
static int numLoadNodes = 0;
|
|
static int numNodes = 0;
|
|
|
|
void *PathNode::operator new( size_t size )
|
|
{
|
|
return PathManager.AllocPathNode();
|
|
}
|
|
|
|
void PathNode::operator delete( void *ptr )
|
|
{
|
|
return PathManager.FreePathNode( ptr );
|
|
}
|
|
|
|
PathNode::PathNode()
|
|
{
|
|
entflags |= EF_PATHNODE;
|
|
findCount = 0;
|
|
numChildren = 0;
|
|
iAvailableTime = -1;
|
|
|
|
if( !loadingarchive )
|
|
{
|
|
// our archive function will take care of this stuff
|
|
AI_AddNode( this );
|
|
nodeflags = 0;
|
|
virtualNumChildren = 0;
|
|
iAvailableTime = -1;
|
|
Child = NULL;
|
|
}
|
|
}
|
|
|
|
PathNode::~PathNode()
|
|
{
|
|
entflags &= ~EF_PATHNODE;
|
|
}
|
|
|
|
void PathNode::Claim
|
|
(
|
|
Entity *pClaimer
|
|
)
|
|
{
|
|
pLastClaimer = pClaimer;
|
|
iAvailableTime = 0;
|
|
}
|
|
|
|
const_str PathNode::GetSpecialAttack
|
|
(
|
|
Actor *pActor
|
|
)
|
|
{
|
|
int iSpecialAttack;
|
|
const_str csAnimation;
|
|
float fRangeSquared;
|
|
float vDelta[ 2 ];
|
|
float fMinAngle;
|
|
float fMaxAngle;
|
|
|
|
if( nodeflags & AI_CORNER_LEFT )
|
|
{
|
|
iSpecialAttack = 0;
|
|
csAnimation = STRING_ANIM_CORNERLEFT_SCR;
|
|
}
|
|
else if( nodeflags & AI_CORNER_RIGHT )
|
|
{
|
|
iSpecialAttack = 1;
|
|
csAnimation = STRING_ANIM_CORNERRIGHT_SCR;
|
|
}
|
|
else
|
|
{
|
|
if( nodeflags >= 0 )
|
|
return STRING_NULL;
|
|
|
|
iSpecialAttack = 2;
|
|
csAnimation = STRING_ANIM_OVERATTACK_SCR;
|
|
}
|
|
|
|
if( pActor->m_Enemy )
|
|
{
|
|
vDelta[ 0 ] = pActor->m_Enemy->origin[ 0 ] - origin[ 0 ];
|
|
vDelta[ 1 ] = pActor->m_Enemy->origin[ 1 ] - origin[ 1 ];
|
|
}
|
|
else
|
|
{
|
|
vDelta[ 0 ] = pActor->m_vLastEnemyPos[ 0 ] - origin[ 0 ];
|
|
vDelta[ 1 ] = pActor->m_vLastEnemyPos[ 1 ] - origin[ 1 ];
|
|
}
|
|
|
|
fRangeSquared = vDelta[ 0 ] * vDelta[ 0 ] + vDelta[ 1 ] * vDelta[ 1 ];
|
|
|
|
if( fRangeSquared < g_AttackParms[ iSpecialAttack ].fMinRangeSquared || fRangeSquared > g_AttackParms[ iSpecialAttack ].fMaxRangeSquared )
|
|
return STRING_NULL;
|
|
|
|
fMinAngle = atan2( vDelta[ 0 ], vDelta[ 1 ] ) * ( 180.0f / M_PI ) - angles[ 1 ];
|
|
|
|
if( fMinAngle > -360.0f )
|
|
{
|
|
if( fMinAngle >= 0.0f )
|
|
{
|
|
if( fMinAngle >= 720.0f )
|
|
fMaxAngle = fMinAngle - 720.0f;
|
|
else if( fMinAngle >= 360.0f )
|
|
fMaxAngle = fMinAngle - 360.0f;
|
|
else
|
|
fMaxAngle = fMinAngle;
|
|
}
|
|
else
|
|
{
|
|
fMaxAngle = fMinAngle + 360.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fMaxAngle = fMinAngle + 720.0f;
|
|
}
|
|
|
|
if( g_AttackParms[ iSpecialAttack ].fMinAngle <= g_AttackParms[ iSpecialAttack ].fMaxAngle )
|
|
{
|
|
if( g_AttackParms[ iSpecialAttack ].fMinAngle > fMaxAngle )
|
|
return STRING_NULL;
|
|
}
|
|
else
|
|
{
|
|
if( g_AttackParms[ iSpecialAttack ].fMinAngle <= fMaxAngle )
|
|
return STRING_NULL;
|
|
}
|
|
|
|
if( fMaxAngle > g_AttackParms[ iSpecialAttack ].fMaxAngle )
|
|
return STRING_NULL;
|
|
|
|
return csAnimation;
|
|
}
|
|
|
|
Entity *PathNode::GetClaimHolder
|
|
(
|
|
void
|
|
) const
|
|
{
|
|
if( iAvailableTime )
|
|
return NULL;
|
|
else
|
|
return pLastClaimer;
|
|
}
|
|
|
|
bool PathNode::IsClaimedByOther
|
|
(
|
|
Entity *pPossibleClaimer
|
|
) const
|
|
{
|
|
if( pLastClaimer == pPossibleClaimer )
|
|
return false;
|
|
|
|
if( iAvailableTime )
|
|
{
|
|
return ( level.inttime < iAvailableTime );
|
|
}
|
|
else
|
|
{
|
|
return ( pLastClaimer != NULL );
|
|
}
|
|
}
|
|
|
|
qboolean PathNode::IsTouching
|
|
(
|
|
Entity *e1
|
|
)
|
|
{
|
|
return e1->absmin[ 0 ] <= origin[ 0 ] + 15.5f &&
|
|
e1->absmin[ 1 ] <= origin[ 1 ] + 15.5f &&
|
|
e1->absmin[ 0 ] <= origin[ 2 ] + 94.0f &&
|
|
origin[ 0 ] - 15.5f <= e1->absmax[ 0 ] &&
|
|
origin[ 1 ] - 15.5f <= e1->absmax[ 1 ] &&
|
|
origin[ 2 ] + 0.0f <= e1->absmax[ 2 ];
|
|
}
|
|
|
|
void PathNode::SetNodeFlags
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
nodeflags = ev->GetInteger( 1 );
|
|
}
|
|
|
|
void PathNode::IsTouching
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
Entity *ent = ev->GetEntity( 1 );
|
|
|
|
if( !ent )
|
|
{
|
|
ScriptError( "IsTouching used with a NULL entity.\n" );
|
|
}
|
|
|
|
ev->AddInteger( IsTouching( ev->GetEntity( 1 ) ) );
|
|
}
|
|
|
|
void PathNode::Remove
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
// Pathnodes mustn't be removed
|
|
ScriptError( "Not allowed to delete a path node" );
|
|
}
|
|
|
|
void PathNode::setOriginEvent
|
|
(
|
|
Vector org
|
|
)
|
|
{
|
|
if( !PathManager.m_bNodesloaded )
|
|
{
|
|
origin = org;
|
|
centroid = org;
|
|
}
|
|
}
|
|
|
|
void PathNode::Archive
|
|
(
|
|
Archiver &arc
|
|
)
|
|
{
|
|
}
|
|
|
|
void PathNode::ArchiveDynamic
|
|
(
|
|
Archiver &arc
|
|
)
|
|
{
|
|
SimpleEntity::SimpleArchive( arc );
|
|
|
|
arc.ArchiveObjectPosition( this );
|
|
arc.ArchiveSafePointer( &pLastClaimer );
|
|
arc.ArchiveInteger( &iAvailableTime );
|
|
arc.ArchiveInteger( &numChildren );
|
|
|
|
if( numChildren != virtualNumChildren )
|
|
{
|
|
for( int i = 0; i < virtualNumChildren; i++ )
|
|
{
|
|
arc.ArchiveByte( &Child[ i ].numBlockers );
|
|
arc.ArchiveShort( &Child[ i ].node );
|
|
arc.ArchiveShort( &Child[ i ].fallheight );
|
|
arc.ArchiveFloat( &Child[ i ].dist );
|
|
arc.ArchiveVec2( Child[ i ].dir );
|
|
arc.ArchiveVec3( Child[ i ].pos1 );
|
|
arc.ArchiveVec3( Child[ i ].pos2 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathNode::ArchiveStatic
|
|
(
|
|
Archiver &arc
|
|
)
|
|
{
|
|
arc.ArchiveVector( &origin );
|
|
arc.ArchiveVector( ¢roid );
|
|
arc.ArchiveInteger( &nodeflags );
|
|
arc.ArchiveInteger( &virtualNumChildren );
|
|
|
|
numChildren = virtualNumChildren;
|
|
|
|
if( arc.Loading() )
|
|
{
|
|
bulkNavMemory -= virtualNumChildren * sizeof( pathway_t ) * sizeof( pathway_t * );
|
|
Child = virtualNumChildren ? ( pathway_t * )bulkNavMemory : NULL;
|
|
}
|
|
|
|
for( int i = 0; i < virtualNumChildren; i++ )
|
|
{
|
|
arc.ArchiveShort( &Child[ i ].node );
|
|
arc.ArchiveShort( &Child[ i ].fallheight );
|
|
arc.ArchiveFloat( &Child[ i ].dist );
|
|
arc.ArchiveVec2( Child[ i ].dir );
|
|
arc.ArchiveVec3( Child[ i ].pos1 );
|
|
arc.ArchiveVec3( Child[ i ].pos2 );
|
|
|
|
if( arc.Loading() )
|
|
Child[ i ].numBlockers = 0;
|
|
}
|
|
}
|
|
|
|
void PathNode::ConnectChild
|
|
(
|
|
int i
|
|
)
|
|
{
|
|
int j;
|
|
pathway_t child = Child[ i ];
|
|
|
|
for( j = i - 1; j >= numChildren; j-- )
|
|
{
|
|
Child[ j + 1 ] = Child[ j ];
|
|
}
|
|
|
|
Child[ numChildren ] = child;
|
|
numChildren++;
|
|
}
|
|
|
|
void PathNode::DisconnectChild
|
|
(
|
|
int i
|
|
)
|
|
{
|
|
int j;
|
|
pathway_t child = Child[ i ];
|
|
|
|
for( j = i + 1; j < numChildren; j++ )
|
|
{
|
|
Child[ j - 1 ] = Child[ j ];
|
|
}
|
|
|
|
numChildren--;
|
|
Child[ numChildren ] = child;
|
|
}
|
|
|
|
void PathNode::ConnectTo
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
Child[ virtualNumChildren ].node = nodenum;
|
|
Child[ virtualNumChildren ].numBlockers = 0;
|
|
virtualNumChildren++;
|
|
numChildren++;
|
|
}
|
|
|
|
bool PathNode::CheckPathTo
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
|
|
if( virtualNumChildren < NUM_PATHSPERNODE )
|
|
{
|
|
CheckPathToDefault( node, &Child[ virtualNumChildren ] );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Com_Printf( "^~^~^ %d paths per node at (%.2f %.2f %.2f) exceeded\n - use DONT_LINK on some nodes to conserve cpu and memory usage\n", NUM_PATHSPERNODE, node->origin[ 0 ], node->origin[ 1 ], node->origin[ 2 ] );
|
|
PathSearch::m_NodeCheckFailed = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void PathNode::CheckPathToDefault
|
|
(
|
|
PathNode *node,
|
|
pathway_t *pathway
|
|
)
|
|
{
|
|
float dist;
|
|
float delta[ 2 ];
|
|
Vector start;
|
|
Vector end;
|
|
|
|
delta[ 0 ] = node->origin[ 0 ] - origin[ 0 ];
|
|
delta[ 1 ] = node->origin[ 1 ] - origin[ 1 ];
|
|
|
|
dist = VectorNormalize2D( delta );
|
|
|
|
if( dist >= 384.0f )
|
|
return;
|
|
|
|
start = origin + Vector( 0, 0, 36.0f );
|
|
end = start - Vector( 0, 0, 2048.0f );
|
|
|
|
trace_t trace = G_Trace(
|
|
start,
|
|
PLAYER_BASE_MIN,
|
|
PLAYER_BASE_MAX,
|
|
end,
|
|
NULL,
|
|
MASK_PATHSOLID,
|
|
qfalse,
|
|
"droptofloor" );
|
|
|
|
start = node->origin + Vector( 0, 0, 36.0f );
|
|
end = start - Vector( 0, 0, 2048.0f );
|
|
|
|
trace_t trace2 = G_Trace(
|
|
start,
|
|
PLAYER_BASE_MIN,
|
|
PLAYER_BASE_MAX,
|
|
end,
|
|
NULL,
|
|
MASK_PATHSOLID,
|
|
qfalse,
|
|
"droptofloor" );
|
|
|
|
start = trace.endpos;
|
|
end = trace2.endpos;
|
|
|
|
if( CheckMove( start, end, &pathway->fallheight, 15.5f ) )
|
|
{
|
|
pathway->dist = dist;
|
|
pathway->dir[ 0 ] = delta[ 0 ];
|
|
pathway->dir[ 1 ] = delta[ 1 ];
|
|
VectorCopy( start, pathway->pos1 );
|
|
VectorCopy( end, pathway->pos2 );
|
|
Child[ virtualNumChildren ].node = node->nodenum;
|
|
Child[ virtualNumChildren ].numBlockers = 0;
|
|
|
|
virtualNumChildren++;
|
|
numChildren++;
|
|
}
|
|
}
|
|
|
|
void PathNode::MarkTemporarilyBad
|
|
(
|
|
void
|
|
)
|
|
{
|
|
iAvailableTime = level.inttime + 5000;
|
|
pLastClaimer = NULL;
|
|
}
|
|
|
|
void PathNode::Relinquish
|
|
(
|
|
void
|
|
)
|
|
{
|
|
iAvailableTime = level.inttime + 4000;
|
|
}
|
|
|
|
void PathNode::DrawConnections
|
|
(
|
|
void
|
|
)
|
|
{
|
|
int i;
|
|
pathway_t *path;
|
|
PathNode *node;
|
|
|
|
for( i = 0; i < numChildren; i++ )
|
|
{
|
|
path = &Child[ i ];
|
|
node = PathSearch::pathnodes[ path->node ];
|
|
|
|
G_DebugLine( origin + Vector( "0 0 24" ), node->origin + Vector( "0 0 24" ), 0.7f, 0.7f, 0, 1 );
|
|
}
|
|
}
|
|
|
|
static bool IsValidPathnode
|
|
(
|
|
int spawnflags
|
|
)
|
|
{
|
|
if( ( spawnflags & AI_DUCK ) && ( spawnflags & AI_COVERFLAGS2 ) )
|
|
return false;
|
|
|
|
if( ( spawnflags & AI_CONCEALMENT ) && ( spawnflags & AI_SNIPERFLAGS ) )
|
|
return false;
|
|
|
|
if( ( spawnflags & AI_CORNER_LEFT ) && ( spawnflags & AI_COVER_LEFT_FLAGS ) )
|
|
return false;
|
|
|
|
if( ( spawnflags & AI_CORNER_RIGHT ) && ( spawnflags & AI_COVER_RIGHT_FLAGS ) )
|
|
return false;
|
|
|
|
if( ( spawnflags & AI_SNIPER ) && ( spawnflags & AI_CRATEFLAGS ) )
|
|
return false;
|
|
|
|
if( ( spawnflags & AI_ALL ) && ( spawnflags & AI_COVERFLAGS3 ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void GetPathnodeColor
|
|
(
|
|
int spawnflags,
|
|
vec3_t color
|
|
)
|
|
{
|
|
if( IsValidPathnode( spawnflags ) )
|
|
{
|
|
if( spawnflags & AI_CORNER_LEFT )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_CORNER_LEFT, color );
|
|
}
|
|
else if( spawnflags & AI_CORNER_RIGHT )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_CORNER_RIGHT, color );
|
|
}
|
|
else if( spawnflags & AI_DUCK )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_DUCK, color );
|
|
}
|
|
else if( spawnflags & AI_SNIPER )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_SNIPER, color );
|
|
}
|
|
else if( spawnflags & AI_CONCEALMENT )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_CONCEALMENT, color );
|
|
}
|
|
else if( spawnflags & AI_COVER )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_COVER, color );
|
|
}
|
|
else if( spawnflags & AI_CRATE )
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_CRATE, color );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_DEFAULT, color );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( COLOR_PATHNODE_ERROR, color );
|
|
}
|
|
}
|
|
|
|
void DrawNode
|
|
(
|
|
int iNodeCount
|
|
)
|
|
{
|
|
Vector down;
|
|
Vector up;
|
|
Vector dir;
|
|
Vector p1;
|
|
Vector p2;
|
|
Vector p3;
|
|
Vector p4;
|
|
Vector q1;
|
|
Vector q2;
|
|
Vector q3;
|
|
Vector q4;
|
|
Vector playerorigin;
|
|
Vector aStart;
|
|
Vector aEnd;
|
|
PathNode *node;
|
|
PathNode *nodelist[ 4096 ];
|
|
Vector end;
|
|
Vector start;
|
|
Vector p;
|
|
vec3_t color;
|
|
|
|
playerorigin = g_entities[ 0 ].client->ps.origin;
|
|
|
|
if( iNodeCount > 4096 )
|
|
iNodeCount = 4096;
|
|
|
|
if( ai_showallnode->integer )
|
|
iNodeCount = PathSearch::DebugNearestNodeList2( playerorigin, nodelist, iNodeCount );
|
|
else
|
|
iNodeCount = PathSearch::DebugNearestNodeList( playerorigin, nodelist, iNodeCount );
|
|
|
|
if( iNodeCount )
|
|
{
|
|
for( int i = 0; i < iNodeCount; i++ )
|
|
{
|
|
node = nodelist[ i ];
|
|
GetPathnodeColor( node->nodeflags, color );
|
|
|
|
p1.x = PLAYER_BASE_MAX.x + node->origin.x;
|
|
p1.y = PLAYER_BASE_MAX.y + node->origin.y;
|
|
|
|
p2.x = PLAYER_BASE_MAX.x + node->origin.x;
|
|
p2.y = PLAYER_BASE_MIN.y + node->origin.y;
|
|
|
|
p3.x = PLAYER_BASE_MIN.x + node->origin.x;
|
|
p3.y = PLAYER_BASE_MIN.y + node->origin.y;
|
|
|
|
p4.x = PLAYER_BASE_MIN.x + node->origin.x;
|
|
p4.y = PLAYER_BASE_MAX.y + node->origin.y;
|
|
|
|
start = node->origin + Vector( 0, 0, 18 );
|
|
end = node->origin + Vector( 0, 0, 18 );
|
|
|
|
aStart = start;
|
|
aEnd[ 0 ] = node->origin[ 0 ] + cos( M_PI / 180.0f * node->angles[ 1 ] ) * 16.0f;
|
|
aEnd[ 1 ] = node->origin[ 1 ] + sin( M_PI / 180.0f * node->angles[ 1 ] ) * 16.0f;
|
|
aEnd[ 2 ] = end[ 2 ];
|
|
|
|
G_DebugLine( aStart, aEnd, 1, 1, 1, 1 );
|
|
|
|
p1.z = node->origin.z + 36.0f;
|
|
p2.z = node->origin.z + 36.0f;
|
|
p3.z = node->origin.z + 36.0f;
|
|
p4.z = node->origin.z + 36.0f;
|
|
|
|
G_DebugLine( p1, p2, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p2, p3, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p3, p4, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p4, p1, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
|
|
q1 = p1;
|
|
q2 = p2;
|
|
q3 = p3;
|
|
q4 = p4;
|
|
|
|
q1.z = node->origin.z;
|
|
q2.z = node->origin.z;
|
|
q3.z = node->origin.z;
|
|
q4.z = node->origin.z;
|
|
|
|
G_DebugLine( q1, q2, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( q2, q3, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( q3, q4, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( q4, q1, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
|
|
G_DebugLine( p1, q1, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p2, q2, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p3, q3, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
G_DebugLine( p4, q4, color[ 0 ], color[ 1 ], color[ 2 ], 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_DebugCircle( playerorigin + Vector( 0, 0, 48 ), 128, 1, 0, 0, 1, true );
|
|
}
|
|
}
|
|
|
|
void DrawAllConnections
|
|
(
|
|
void
|
|
)
|
|
{
|
|
pathway_t *path;
|
|
pathway_t *path2;
|
|
PathNode *node;
|
|
PathNode *to;
|
|
Vector down;
|
|
Vector up;
|
|
Vector dir;
|
|
Vector p1;
|
|
Vector p2;
|
|
Vector p3;
|
|
Vector p4;
|
|
Vector playerorigin;
|
|
qboolean showroutes;
|
|
qboolean shownums;
|
|
bool reverse;
|
|
|
|
showroutes = ( ai_showroutes->integer != 0 );
|
|
shownums = ( ai_shownodenums->integer != 0 );
|
|
|
|
// Figure out where the camera is
|
|
|
|
if( !g_entities[ 0 ].client )
|
|
return;
|
|
|
|
playerorigin.x = g_entities[ 0 ].client->ps.origin[ 0 ];
|
|
playerorigin.y = g_entities[ 0 ].client->ps.origin[ 1 ];
|
|
playerorigin.z = g_entities[ 0 ].client->ps.origin[ 2 ];
|
|
|
|
playerorigin[ 2 ] += g_entities[ 0 ].client->ps.viewheight;
|
|
|
|
for( int i = 0; i < PathSearch::nodecount; i++ )
|
|
{
|
|
node = PathSearch::pathnodes[ i ];
|
|
|
|
if( Vector( node->origin - playerorigin ).length() > ai_showroutes_distance->integer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( shownums )
|
|
{
|
|
G_DrawDebugNumber( node->origin + Vector( 0, 0, 14 ), node->nodenum, 1.5, 1, 1, 0 );
|
|
}
|
|
|
|
for( int j = 0; j < node->numChildren; j++ )
|
|
{
|
|
path = &node->Child[ j ];
|
|
|
|
if( path->fallheight > ai_fallheight->integer )
|
|
continue;
|
|
|
|
reverse = false;
|
|
to = PathSearch::pathnodes[ path->node ];
|
|
|
|
for( int k = to->numChildren - 1; k >= 0; k-- )
|
|
{
|
|
path2 = &to->Child[ k ];
|
|
|
|
if( path2->fallheight < ai_fallheight->integer && PathSearch::pathnodes[ path2->node ] == node )
|
|
{
|
|
reverse = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
p1 = path->pos1 + Vector( 0, 0, 36 );
|
|
p2 = path->pos2 + Vector( 0, 0, 36 );
|
|
|
|
if( node->nodenum < to->nodenum || !reverse )
|
|
{
|
|
// draw connected lines in green
|
|
G_DebugLine( p1, p2, 0, 1, 0, 1 );
|
|
|
|
if( !reverse )
|
|
{
|
|
dir = Vector( path->pos2 ) - Vector( path->pos1 );
|
|
dir.z = 0;
|
|
VectorNormalize( dir );
|
|
|
|
p3 = dir * 8.0f + dir * 8.0f;
|
|
p4 = dir * 8.0f;
|
|
p4.z += 8.0f;
|
|
|
|
G_DebugLine( p1 + p3 + up , p1 + p3 + up - p4, 1, 0, 0, 1 );
|
|
|
|
p4.z -= 16.0f;
|
|
|
|
G_DebugLine( p1 + p3 + down, p1 + p3 + down - p4, 1, 0, 0, 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !node->numChildren )
|
|
{
|
|
// Put a little X where the node is to show that it had no connections
|
|
p1 = node->origin;
|
|
p1.z += 2;
|
|
|
|
if( node->nodeflags & PATH_DONT_LINK )
|
|
{
|
|
G_DebugCircle( p1, 12, 0, 0, 1, 1, true );
|
|
}
|
|
else
|
|
{
|
|
p2 = Vector( 12, 12, 0 );
|
|
G_DebugLine( p1 - p2, p1 + p2, 1, 0, 0, 1 );
|
|
|
|
p2.x = -12;
|
|
G_DebugLine( p1 - p2, p1 + p2, 1, 0, 0, 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MapCell::MapCell()
|
|
{
|
|
numnodes = 0;
|
|
nodes = NULL;
|
|
}
|
|
|
|
MapCell::~MapCell()
|
|
{
|
|
numnodes = 0;
|
|
nodes = NULL;
|
|
}
|
|
qboolean MapCell::AddNode
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
nodes[ numnodes ] = ( short )node->nodenum;
|
|
numnodes++;
|
|
|
|
return true;
|
|
}
|
|
|
|
int MapCell::NumNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
return numnodes;
|
|
}
|
|
|
|
|
|
/* All
|
|
work and no play
|
|
makes Jim a dull boy. All
|
|
work and no play makes Jim a
|
|
dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work and
|
|
no play makes Jim a dull boy. All work and
|
|
no play makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no play makes
|
|
Jim a dull boy. All work and no play makes Jim a
|
|
dull boy. All work and no play makes Jim a dull boy.
|
|
All work and no play makes Jim a dull boy. All work
|
|
and no play makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no play makes Jim a
|
|
dull boy. All work and no play makes Jim a dull boy. All
|
|
work and no play makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work and no play makes Jim
|
|
a dull boy. All work and no play makes Jim a dull boy.
|
|
All work and no play makes Jim a dull boy. All work and
|
|
no play makes Jim a dull boy. All work and no play makes
|
|
Jim a dull boy. All work and no play makes Jim a dull
|
|
boy. All work and no play makes Jim a dull boy. All work
|
|
and no play makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no play makes Jim a
|
|
dull boy. All work and no play makes Jim a dull boy. All
|
|
work and no play makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work and no play makes
|
|
Jim a dull boy. All work and no play makes Jim a dull
|
|
boy. All work and no play makes Jim a dull boy. All
|
|
work and no play makes Jim a dull boy. All work and
|
|
no play makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no play
|
|
makes Jim a dull boy. All work and no
|
|
play makes Jim a dull boy. All work
|
|
and no play makes Jim a dull boy.
|
|
All work and no play makes
|
|
Jim a dull boy. All work
|
|
and no play makes
|
|
Jim a
|
|
*/
|
|
|
|
CLASS_DECLARATION( Class, PathSearch, NULL )
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
|
|
PathSearch::PathSearch()
|
|
{
|
|
memset( pathnodes, 0, sizeof( pathnodes ) );
|
|
open = 0;
|
|
findFrame = 0;
|
|
}
|
|
|
|
PathSearch::~PathSearch()
|
|
{
|
|
ResetNodes();
|
|
}
|
|
|
|
void PathSearch::AddToGrid
|
|
(
|
|
PathNode *node,
|
|
int x,
|
|
int y
|
|
)
|
|
{
|
|
MapCell *cell;
|
|
|
|
if( x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE )
|
|
cell = NULL;
|
|
else
|
|
cell = &PathMap[ x ][ y ];
|
|
|
|
if( !cell )
|
|
return;
|
|
|
|
if( cell->NumNodes() >= PATHMAP_NODES )
|
|
Com_Printf( "^~^~^ PathSearch::AddToGrid: Node overflow at ( %d, %d )\n", x, y );
|
|
else
|
|
cell->AddNode( node );
|
|
}
|
|
|
|
int PathSearch::NodeCoordinate
|
|
(
|
|
float coord
|
|
)
|
|
{
|
|
float c;
|
|
|
|
//return ( ( int )coord + MAX_MAP_BOUNDS - ( PATHMAP_CELLSIZE / 2 ) ) / PATHMAP_CELLSIZE;
|
|
|
|
c = coord + MAX_MAP_BOUNDS - ( PATHMAP_CELLSIZE / 2 );
|
|
|
|
if( c < 0 )
|
|
c = coord + MAX_MAP_BOUNDS + ( PATHMAP_CELLSIZE / 2 ) - 1;
|
|
|
|
return ( int )c >> 8;
|
|
}
|
|
|
|
int PathSearch::GridCoordinate
|
|
(
|
|
float coord
|
|
)
|
|
{
|
|
float c;
|
|
|
|
//return ( ( int )coord + MAX_MAP_BOUNDS ) / PATHMAP_CELLSIZE;
|
|
|
|
c = coord + MAX_MAP_BOUNDS;
|
|
|
|
if( c < 0 )
|
|
c = coord + MAX_MAP_BOUNDS + PATHMAP_CELLSIZE - 1;
|
|
|
|
return ( int )c >> 8;
|
|
}
|
|
|
|
void PathSearch::AddNode
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
int x;
|
|
int y;
|
|
|
|
assert( node );
|
|
|
|
x = NodeCoordinate( node->origin[ 0 ] );
|
|
y = NodeCoordinate( node->origin[ 1 ] );
|
|
|
|
AddToGrid( node, x, y );
|
|
AddToGrid( node, x + 1, y );
|
|
AddToGrid( node, x, y + 1 );
|
|
AddToGrid( node, x + 1, y + 1 );
|
|
}
|
|
|
|
void PathSearch::LoadAddToGrid
|
|
(
|
|
int x,
|
|
int y
|
|
)
|
|
{
|
|
MapCell *cell;
|
|
|
|
if( x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE )
|
|
cell = NULL;
|
|
else
|
|
cell = &PathMap[ x ][ y ];
|
|
|
|
if( cell )
|
|
cell->numnodes++;
|
|
}
|
|
|
|
void PathSearch::LoadAddToGrid2
|
|
(
|
|
PathNode *node,
|
|
int x,
|
|
int y
|
|
)
|
|
{
|
|
MapCell *cell;
|
|
|
|
if( x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE )
|
|
cell = NULL;
|
|
else
|
|
cell = &PathMap[ x ][ y ];
|
|
|
|
if( cell )
|
|
cell->AddNode( node );
|
|
}
|
|
|
|
MapCell *PathSearch::GetNodesInCell
|
|
(
|
|
int x,
|
|
int y
|
|
)
|
|
{
|
|
if( ( x < 0 ) || ( x >= PATHMAP_GRIDSIZE ) || ( y < 0 ) || ( y >= PATHMAP_GRIDSIZE ) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return &PathMap[ x ][ y ];
|
|
}
|
|
|
|
MapCell *PathSearch::GetNodesInCell
|
|
(
|
|
float *pos
|
|
)
|
|
{
|
|
int x;
|
|
int y;
|
|
|
|
x = GridCoordinate( pos[ 0 ] );
|
|
y = GridCoordinate( pos[ 1 ] );
|
|
|
|
return GetNodesInCell( x, y );
|
|
}
|
|
|
|
|
|
PathNode *PathSearch::DebugNearestStartNode
|
|
(
|
|
float *pos,
|
|
Entity *ent
|
|
)
|
|
{
|
|
PathNode *node = NULL;
|
|
int i;
|
|
MapCell *cell;
|
|
int nodes[ 128 ];
|
|
vec3_t deltas[ 128 ];
|
|
vec3_t start;
|
|
vec3_t end;
|
|
|
|
cell = GetNodesInCell( pos );
|
|
|
|
if( !cell )
|
|
return NULL;
|
|
|
|
int node_count = NearestNodeSetup( pos, cell, nodes, deltas );
|
|
|
|
if( !node_count )
|
|
return NULL;
|
|
|
|
VectorCopy( pos, start );
|
|
start[ 2 ] += 32.0f;
|
|
|
|
for( i = 0; i < node_count; i++ )
|
|
{
|
|
node = pathnodes[ cell->nodes[ nodes[ i ] ] ];
|
|
|
|
VectorCopy( start, end );
|
|
VectorAdd( end, deltas[ nodes[ i ] ], end );
|
|
|
|
Vector vStart = start;
|
|
Vector vMins = Vector( -15, -15, 0 );
|
|
Vector vMaxs = Vector( 15, 15, 62 );
|
|
Vector vEnd = end;
|
|
|
|
if( G_SightTrace(
|
|
vStart,
|
|
vMins,
|
|
vMaxs,
|
|
vEnd,
|
|
ent,
|
|
NULL,
|
|
MASK_TARGETPATH,
|
|
qtrue,
|
|
"PathSearch::DebugNearestStartNode" ) )
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return pathnodes[ cell->nodes[ nodes[ 0 ] ] ];
|
|
}
|
|
|
|
PathNode *PathSearch::NearestStartNode
|
|
(
|
|
float *pos,
|
|
SimpleActor *ent
|
|
)
|
|
{
|
|
PathNode *node = NULL;
|
|
int i;
|
|
MapCell *cell;
|
|
int nodes[128];
|
|
vec3_t deltas[128];
|
|
vec3_t start;
|
|
vec3_t end;
|
|
|
|
cell = GetNodesInCell(pos);
|
|
|
|
if (!cell)
|
|
return NULL;
|
|
|
|
int node_count = NearestNodeSetup(pos, cell, nodes, deltas);
|
|
int n = 0;
|
|
int j = 0;
|
|
|
|
VectorCopy(pos, start);
|
|
start[2] += 32.0f;
|
|
|
|
Vector vMins = Vector(-15, -15, 0);
|
|
Vector vMaxs = Vector(15, 15, 62);
|
|
|
|
for (i = 0; i < node_count; i++)
|
|
{
|
|
node = pathnodes[cell->nodes[nodes[i]]];
|
|
|
|
VectorAdd(start, deltas[nodes[i]], end);
|
|
|
|
Vector vStart = start;
|
|
Vector vEnd = end;
|
|
|
|
if (G_SightTrace(
|
|
vStart,
|
|
vMins,
|
|
vMaxs,
|
|
vEnd,
|
|
(gentity_t *)NULL,
|
|
(gentity_t *)NULL,
|
|
1107437825, //FIXME: macro
|
|
qtrue,
|
|
"PathSearch::NearestStartNode 1"))
|
|
{
|
|
ent->m_NearestNode = node;
|
|
ent->m_vNearestNodePos = end;
|
|
return node;
|
|
}
|
|
}
|
|
|
|
if ((ent->m_NearestNode &&
|
|
(G_SightTrace(
|
|
Vector(start),
|
|
vMins,
|
|
vMaxs,
|
|
ent->m_vNearestNodePos,
|
|
(gentity_t*)ent,
|
|
(gentity_t*)NULL,
|
|
1073883393, //FIXME: macro
|
|
qtrue,
|
|
"PathSearch::NearestStartNode 2")))
|
|
|| node_count <= 0
|
|
)
|
|
{
|
|
node = ent->m_NearestNode;
|
|
}
|
|
else
|
|
{
|
|
node = pathnodes[cell->nodes[nodes[0]]];
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
PathNode *PathSearch::NearestEndNode
|
|
(
|
|
float *pos
|
|
)
|
|
{
|
|
PathNode *node = NULL;
|
|
int i;
|
|
MapCell *cell;
|
|
int nodes[ 128 ];
|
|
vec3_t deltas[ 128 ];
|
|
vec3_t start;
|
|
vec3_t end;
|
|
|
|
cell = GetNodesInCell( pos );
|
|
|
|
if( !cell )
|
|
return NULL;
|
|
|
|
int node_count = NearestNodeSetup( pos, cell, nodes, deltas );
|
|
int n = 0;
|
|
int j = 0;
|
|
|
|
if( !node_count )
|
|
return NULL;
|
|
|
|
VectorCopy( pos, start );
|
|
start[ 2 ] += 32.0f;
|
|
|
|
for( i = 0; i < node_count; i++ )
|
|
{
|
|
node = pathnodes[ cell->nodes[ nodes[ i ] ] ];
|
|
|
|
VectorAdd(start, deltas[nodes[i]], end);
|
|
|
|
Vector vStart = start;
|
|
Vector vMins = Vector( -15, -15, 0 );
|
|
Vector vMaxs = Vector( 15, 15, 62 );
|
|
Vector vEnd = end;
|
|
|
|
if( G_SightTrace(
|
|
vStart,
|
|
vMins,
|
|
vMaxs,
|
|
vEnd,
|
|
( gentity_t * )NULL,
|
|
( gentity_t * )NULL,
|
|
MASK_TARGETPATH,
|
|
qtrue,
|
|
"PathSearch::NearestEndNode" ) )
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return pathnodes[ cell->nodes[ nodes[ 0 ] ] ];
|
|
}
|
|
|
|
int PathSearch::DebugNearestNodeList
|
|
(
|
|
float *pos,
|
|
PathNode **nodelist,
|
|
int iMaxNodes
|
|
)
|
|
{
|
|
PathNode *node;
|
|
int i;
|
|
MapCell *cell;
|
|
int nodes[ 128 ];
|
|
vec3_t deltas[ 128 ];
|
|
vec3_t start;
|
|
vec3_t end;
|
|
|
|
cell = GetNodesInCell( pos );
|
|
|
|
if( !cell )
|
|
return 0;
|
|
|
|
int node_count = NearestNodeSetup( pos, cell, nodes, deltas );
|
|
int n = 0;
|
|
int j = 0;
|
|
|
|
for( i = 0; i < node_count && j < iMaxNodes; i++ )
|
|
{
|
|
node = pathnodes[ cell->nodes[ nodes[ i ] ] ];
|
|
|
|
VectorCopy( pos, start );
|
|
VectorCopy( pos, end );
|
|
|
|
VectorAdd( end, deltas[ i ], end );
|
|
|
|
Vector vStart = start;
|
|
Vector vMins = Vector( -15, -15, 0 );
|
|
Vector vMaxs = Vector( 15, 15, 62 );
|
|
Vector vEnd = end;
|
|
|
|
if( G_SightTrace(
|
|
vStart,
|
|
vMins,
|
|
vMaxs,
|
|
vEnd,
|
|
( gentity_t *)NULL,
|
|
( gentity_t * )NULL,
|
|
MASK_PATHSOLID,
|
|
qtrue,
|
|
"PathSearch::DebugNearestNodeList" ) )
|
|
{
|
|
nodelist[ n ] = node;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
if( !n && node_count )
|
|
{
|
|
nodelist[ 0 ] = pathnodes[ cell->nodes[ nodes[ 0 ] ] ];
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PathSearch::DebugNearestNodeList2
|
|
(
|
|
float *pos,
|
|
PathNode **nodelist,
|
|
int iMaxNodes
|
|
)
|
|
{
|
|
vec3_t delta;
|
|
PathNode *node;
|
|
float dist;
|
|
int n = 0;
|
|
int i;
|
|
int j;
|
|
static float node_dist[ MAX_PATHNODES ];
|
|
int node_count;
|
|
|
|
node_count = nodecount;
|
|
|
|
for( i = 0; i < node_count; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
if( pos[ 2 ] > node->origin[ 2 ] + 94.0f )
|
|
continue;
|
|
|
|
if( node->origin[ 2 ] > pos[ 2 ] + 94.0f )
|
|
continue;
|
|
|
|
delta[ 0 ] = node->origin[ 0 ] - pos[ 0 ];
|
|
delta[ 1 ] = node->origin[ 1 ] - pos[ 1 ];
|
|
delta[ 2 ] = node->origin[ 2 ] - pos[ 2 ];
|
|
|
|
dist = VectorLengthSquared( delta );
|
|
|
|
for( j = n; j > 0; j-- )
|
|
{
|
|
if( dist >= node_dist[ j - 1 ] )
|
|
break;
|
|
|
|
node_dist[ j ] = node_dist[ j - 1 ];
|
|
nodelist[ j ] = nodelist[ j - 1 ];
|
|
}
|
|
|
|
n++;
|
|
nodelist[ j ] = node;
|
|
node_dist[ j ] = dist;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
void PathSearch::ArchiveStaticLoad
|
|
(
|
|
Archiver& arc
|
|
)
|
|
{
|
|
int i;
|
|
PathNode *node;
|
|
int total_nodes;
|
|
int total_children;
|
|
int x;
|
|
int y;
|
|
int size;
|
|
|
|
loadingarchive = true;
|
|
|
|
arc.ArchiveInteger( &nodecount );
|
|
arc.ArchiveInteger( &total_nodes );
|
|
arc.ArchiveInteger( &total_children );
|
|
|
|
size = total_nodes + total_children * ( sizeof( pathway_t ) * 2 ) + nodecount * ( sizeof( PathNode ) / 2 );
|
|
size *= sizeof(void*) / 2;
|
|
|
|
gi.DPrintf( "%d memory allocated for navigation.\n", size );
|
|
|
|
if( size )
|
|
startBulkNavMemory = ( byte * )gi.Malloc( size );
|
|
else
|
|
startBulkNavMemory = NULL;
|
|
|
|
bulkNavMemory = startBulkNavMemory + size;
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = new PathNode;
|
|
|
|
arc.ArchiveObjectPosition( node );
|
|
node->ArchiveStatic( arc );
|
|
node->nodenum = i;
|
|
|
|
pathnodes[ i ] = node;
|
|
|
|
if( !( node->nodeflags & PATH_DONT_LINK ) )
|
|
{
|
|
x = NodeCoordinate( node->origin[ 0 ] );
|
|
y = NodeCoordinate( node->origin[ 1 ] );
|
|
|
|
LoadAddToGrid( x, y );
|
|
LoadAddToGrid( x + 1, y );
|
|
LoadAddToGrid( x, y + 1 );
|
|
LoadAddToGrid( x + 1, y + 1 );
|
|
}
|
|
}
|
|
|
|
for( x = 0; x < PATHMAP_GRIDSIZE; x++ )
|
|
{
|
|
for( y = 0; y < PATHMAP_GRIDSIZE; y++ )
|
|
{
|
|
bulkNavMemory -= PathMap[ x ][ y ].numnodes * sizeof( short );
|
|
|
|
PathMap[ x ][ y ].nodes = PathMap[ x ][ y ].numnodes ? ( short * )bulkNavMemory : NULL;
|
|
PathMap[ x ][ y ].numnodes = 0;
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
if( !( node->nodeflags & PATH_DONT_LINK ) )
|
|
{
|
|
x = NodeCoordinate( node->origin[ 0 ] );
|
|
y = NodeCoordinate( node->origin[ 1 ] );
|
|
|
|
LoadAddToGrid2( node, x, y );
|
|
LoadAddToGrid2( node, x + 1, y );
|
|
LoadAddToGrid2( node, x, y + 1 );
|
|
LoadAddToGrid2( node, x + 1, y + 1 );
|
|
}
|
|
}
|
|
|
|
loadingarchive = false;
|
|
}
|
|
|
|
void PathSearch::ArchiveStaticSave
|
|
(
|
|
Archiver& arc
|
|
)
|
|
{
|
|
int i;
|
|
PathNode *node;
|
|
int total_nodes = 0;
|
|
int total_children = 0;
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
for( x = 0; x < PATHMAP_GRIDSIZE; x++ )
|
|
{
|
|
for( y = 0; y < PATHMAP_GRIDSIZE; y++ )
|
|
{
|
|
total_nodes += PathMap[ x ][ y ].NumNodes();
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
total_children += node->virtualNumChildren;
|
|
}
|
|
|
|
arc.ArchiveInteger( &nodecount );
|
|
arc.ArchiveInteger( &total_nodes );
|
|
arc.ArchiveInteger( &total_children );
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
arc.ArchiveObjectPosition( node );
|
|
node->ArchiveStatic( arc );
|
|
}
|
|
}
|
|
|
|
void PathSearch::ArchiveLoadNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
Archiver arc;
|
|
qboolean success;
|
|
|
|
success = qfalse;
|
|
if( arc.Read( level.m_pathfile, false ) )
|
|
{
|
|
int file_version;
|
|
str maptime;
|
|
|
|
// get file values
|
|
arc.ArchiveInteger( &file_version );
|
|
if( file_version != PATHFILE_VERSION )
|
|
{
|
|
Com_Printf( "Expecting version %d path file. Path file is version %d.\n", PATHFILE_VERSION, file_version );
|
|
arc.Close();
|
|
return;
|
|
}
|
|
|
|
arc.ArchiveString( &maptime );
|
|
if( gi.MapTime() == maptime && gi.FS_FileNewer( level.m_mapfile.c_str(), level.m_pathfile.c_str() ) <= 0 )
|
|
{
|
|
arc.ArchiveInteger( &m_NodeCheckFailed );
|
|
|
|
if( !g_nodecheck->integer || !m_NodeCheckFailed )
|
|
{
|
|
ArchiveStaticLoad( arc );
|
|
m_bNodesloaded = arc.NoErrors();
|
|
}
|
|
else
|
|
{
|
|
gi.Printf( "Rebuilding pathnodes to view node errors.\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.Printf( "Pathnodes have changed, rebuilding.\n" );
|
|
}
|
|
}
|
|
|
|
arc.Close();
|
|
}
|
|
|
|
qboolean PathSearch::ArchiveSaveNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
Archiver arc;
|
|
str maptime;
|
|
int tempInt;
|
|
|
|
arc.Create( level.m_pathfile );
|
|
tempInt = PATHFILE_VERSION;
|
|
arc.ArchiveInteger( &tempInt );
|
|
maptime = gi.MapTime();
|
|
arc.ArchiveString( &maptime );
|
|
arc.ArchiveInteger( &m_NodeCheckFailed );
|
|
ArchiveStaticSave( arc );
|
|
arc.Close();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PathSearch::Connect
|
|
(
|
|
PathNode *node
|
|
)
|
|
{
|
|
int x;
|
|
int y;
|
|
|
|
findFrame++;
|
|
node->findCount = findFrame;
|
|
|
|
x = NodeCoordinate( node->origin[ 0 ] );
|
|
y = NodeCoordinate( node->origin[ 1 ] );
|
|
|
|
if( Connect( node, x - 1, y - 1 ) )
|
|
{
|
|
if( Connect( node, x - 1, y ) )
|
|
{
|
|
if( Connect( node, x - 1, y + 1 ) )
|
|
{
|
|
if( Connect( node, x, y - 1 ) )
|
|
{
|
|
if( Connect( node, x, y ) )
|
|
{
|
|
if( Connect( node, x, y + 1 ) )
|
|
{
|
|
if( Connect( node, x + 1, y - 1 ) )
|
|
{
|
|
if( Connect( node, x + 1, y ) )
|
|
{
|
|
Connect( node, x + 1, y + 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PathSearch::Connect
|
|
(
|
|
PathNode *node,
|
|
int x,
|
|
int y
|
|
)
|
|
{
|
|
MapCell *cell;
|
|
int i;
|
|
PathNode *node2;
|
|
|
|
if( x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE )
|
|
cell = NULL;
|
|
else
|
|
cell = &PathMap[ x ][ y ];
|
|
|
|
if( !cell )
|
|
return true;
|
|
|
|
if( cell->numnodes <= 0 )
|
|
return true;
|
|
|
|
for( i = 0; i < cell->numnodes; i++ )
|
|
{
|
|
node2 = pathnodes[ cell->nodes[ i ] ];
|
|
|
|
if( node2->findCount != findFrame )
|
|
{
|
|
node2->findCount = findFrame;
|
|
|
|
if( !node->CheckPathTo( node2 ) )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PathSearch::ShowNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
if( g_entities->client )
|
|
{
|
|
if( ai_shownode->integer )
|
|
{
|
|
DrawNode( ai_shownode->integer );
|
|
}
|
|
if( ai_showroutes->integer || ai_shownodenums->integer )
|
|
{
|
|
DrawAllConnections();
|
|
}
|
|
}
|
|
|
|
if( ai_showpath->integer )
|
|
{
|
|
if( !test_path )
|
|
test_path = new ActorPath;
|
|
|
|
if( ai_showpath->integer == 1 )
|
|
{
|
|
ai_startpath = g_entities[ 0 ].entity->origin;
|
|
}
|
|
if( ai_showpath->integer == 2 )
|
|
{
|
|
ai_endpath = g_entities[ 0 ].entity->origin;
|
|
}
|
|
if( ai_showpath->integer <= 2 )
|
|
{
|
|
test_path->SetFallHeight( ai_fallheight->integer );
|
|
test_path->FindPath( ai_startpath, ai_endpath, NULL, 0, NULL, 0 );
|
|
}
|
|
if( ai_showpath->integer == 3 )
|
|
{
|
|
if( test_path->CurrentNode() )
|
|
{
|
|
test_path->UpdatePos( g_entities[ 0 ].entity->origin );
|
|
|
|
Vector vStart = g_entities[ 0 ].entity->origin + Vector( 0, 0, 32 );
|
|
Vector vEnd = g_entities[ 0 ].entity->origin + test_path->CurrentDelta() + Vector( 0, 0, 32 );
|
|
|
|
G_DebugLine( vStart, vEnd, 1, 1, 0, 1 );
|
|
}
|
|
}
|
|
|
|
G_DebugLine( ai_startpath, ai_endpath, 0, 0, 1, 1 );
|
|
|
|
if( test_path->CurrentNode() )
|
|
{
|
|
PathInfo *pos = test_path->CurrentNode();
|
|
|
|
while( pos != test_path->LastNode() )
|
|
{
|
|
Vector vStart = pos->point + Vector( 0, 0, 32 );
|
|
|
|
pos--;
|
|
|
|
Vector vEnd = pos->point + Vector( 0, 0, 32 );
|
|
|
|
G_DebugLine( vStart, vEnd, 1, 0, 0, 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathSearch::LoadNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
ai_showroutes = gi.Cvar_Get( "ai_showroutes", "0", 0 );
|
|
ai_showroutes_distance = gi.Cvar_Get( "ai_showroutes_distance", "1000", 0 );
|
|
ai_shownodenums = gi.Cvar_Get( "ai_shownodenums", "0", 0 );
|
|
ai_shownode = gi.Cvar_Get( "ai_shownode", "0", 0 );
|
|
ai_showallnode = gi.Cvar_Get( "ai_showallnode", "0", 0 );
|
|
ai_showpath = gi.Cvar_Get( "ai_showpath", "0", 0 );
|
|
ai_fallheight = gi.Cvar_Get( "ai_fallheight", "96", 0 );
|
|
ai_debugpath = gi.Cvar_Get( "ai_debugpath", "0", 0 );
|
|
ai_pathchecktime = gi.Cvar_Get( "ai_pathchecktime", "1.5", CVAR_CHEAT );
|
|
ai_pathcheckdist = gi.Cvar_Get( "ai_pathcheckdist", "4096", CVAR_CHEAT );
|
|
|
|
ArchiveLoadNodes();
|
|
}
|
|
|
|
void PathSearch::CreatePaths
|
|
(
|
|
void
|
|
)
|
|
{
|
|
int i;
|
|
int j;
|
|
int x;
|
|
int y;
|
|
PathNode *node;
|
|
Vector start;
|
|
Vector end;
|
|
gentity_t *ent;
|
|
int t1;
|
|
int t2;
|
|
|
|
if( m_bNodesloaded )
|
|
return;
|
|
|
|
if( !nodecount )
|
|
{
|
|
m_bNodesloaded = true;
|
|
return;
|
|
}
|
|
|
|
m_NodeCheckFailed = false;
|
|
|
|
gi.DPrintf( "***********************************\n"
|
|
"***********************************\n"
|
|
"\n"
|
|
"Creating paths...\n"
|
|
"\n"
|
|
"***********************************\n"
|
|
"***********************************\n" );
|
|
|
|
t1 = gi.Milliseconds();
|
|
|
|
for( i = 0, ent = g_entities; i < game.maxentities; i++, ent++ )
|
|
{
|
|
if( ent->entity && ent->entity->IsSubclassOfDoor() )
|
|
gi.UnlinkEntity( ent );
|
|
}
|
|
|
|
for( x = 0; x < PATHMAP_GRIDSIZE; x++ )
|
|
{
|
|
for( y = 0; y < PATHMAP_GRIDSIZE; y++ )
|
|
{
|
|
MapCell *cell = &PathMap[ x ][ y ];
|
|
|
|
cell->nodes = ( short * )gi.Malloc( PATHMAP_CELLSIZE );
|
|
cell->numnodes = 0;
|
|
memset( cell->nodes, 0, PATHMAP_CELLSIZE );
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
start = node->origin + Vector( 0, 0, 36.0f );
|
|
end = node->origin - Vector( 0, 0, 2048.0f );
|
|
|
|
trace_t trace = G_Trace( start,
|
|
PLAYER_BASE_MIN,
|
|
PLAYER_BASE_MAX,
|
|
end,
|
|
NULL,
|
|
MASK_PATHSOLID,
|
|
qfalse,
|
|
"droptofloor" );
|
|
|
|
node->origin = trace.endpos;
|
|
node->centroid = trace.endpos;
|
|
|
|
if( !( node->nodeflags & PATH_DONT_LINK ) )
|
|
{
|
|
for( j = i - 1; j >= 0; j-- )
|
|
{
|
|
PathNode *node2 = pathnodes[ j ];
|
|
|
|
if( node2->origin == node->origin )
|
|
{
|
|
Com_Printf( "^~^~^ Duplicate node at (%.2f %.2f %.2f) not linked\n", node->origin[ 0 ], node->origin[ 1 ], node->origin[ 2 ] );
|
|
node->nodeflags |= PATH_DONT_LINK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !( node->nodeflags & PATH_DONT_LINK ) )
|
|
node->Child = ( pathway_t * )gi.Malloc( sizeof( pathway_t ) * PATHMAP_NODES );
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
if( node->nodeflags & PATH_DONT_LINK )
|
|
continue;
|
|
|
|
AddNode( node );
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
if( node->nodeflags & PATH_DONT_LINK )
|
|
continue;
|
|
|
|
Connect( node );
|
|
}
|
|
|
|
for( i = 0, ent = g_entities; i < game.maxentities; i++, ent++ )
|
|
{
|
|
if( ent->entity && ent->entity->IsSubclassOfDoor() )
|
|
ent->entity->link();
|
|
}
|
|
|
|
gi.DPrintf( "\nSaving path nodes to '%s'\n", level.m_pathfile.c_str() );
|
|
Com_Printf( "Archiving\n" );
|
|
ArchiveSaveNodes();
|
|
m_bNodesloaded = true;
|
|
Com_Printf( "done.\n" );
|
|
|
|
t2 = gi.Milliseconds();
|
|
Com_Printf( "Path connection: %5.2f seconds\n", ( t2 - t1 ) / 1000.0f );
|
|
Com_Printf( "Number of nodes: %d\n", nodecount );
|
|
gi.ClearResource();
|
|
|
|
if( g_nodecheck->integer && m_NodeCheckFailed )
|
|
{
|
|
gi.Error( ERR_DROP, "Node check failed" );
|
|
}
|
|
}
|
|
|
|
void *PathSearch::AllocPathNode
|
|
(
|
|
void
|
|
)
|
|
{
|
|
if( bulkNavMemory && !m_bNodesloaded )
|
|
{
|
|
bulkNavMemory -= sizeof( PathNode );
|
|
return bulkNavMemory;
|
|
}
|
|
else
|
|
{
|
|
return gi.Malloc( sizeof( PathNode ) );
|
|
}
|
|
}
|
|
|
|
void PathSearch::FreePathNode
|
|
(
|
|
void *ptr
|
|
)
|
|
{
|
|
if( !bulkNavMemory || m_bNodesloaded )
|
|
{
|
|
gi.Free( ptr );
|
|
}
|
|
}
|
|
|
|
void PathSearch::ResetNodes
|
|
(
|
|
void
|
|
)
|
|
{
|
|
int i;
|
|
int x;
|
|
int y;
|
|
|
|
m_LoadIndex = -1;
|
|
m_bNodesloaded = false;
|
|
|
|
if( !startBulkNavMemory )
|
|
{
|
|
for( x = PATHMAP_GRIDSIZE - 1; x >= 0; x-- )
|
|
{
|
|
for( y = PATHMAP_GRIDSIZE - 1; y >= 0; y-- )
|
|
{
|
|
if( PathMap[ x ][ y ].nodes )
|
|
gi.Free( PathMap[ x ][ y ].nodes );
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
if( pathnodes[ i ]->Child )
|
|
gi.Free( pathnodes[ i ]->Child );
|
|
}
|
|
}
|
|
|
|
for( x = PATHMAP_GRIDSIZE - 1; x >= 0; x-- )
|
|
{
|
|
for( y = PATHMAP_GRIDSIZE - 1; y >= 0; y-- )
|
|
{
|
|
PathMap[ x ][ y ].numnodes = 0;
|
|
PathMap[ x ][ y ].nodes = NULL;
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
delete pathnodes[ i ];
|
|
pathnodes[ i ] = NULL;
|
|
}
|
|
|
|
nodecount = 0;
|
|
|
|
// Free the bulk nav' memory
|
|
if( startBulkNavMemory )
|
|
{
|
|
gi.Free( startBulkNavMemory );
|
|
bulkNavMemory = NULL;
|
|
startBulkNavMemory = NULL;
|
|
}
|
|
}
|
|
|
|
PathInfo *PathSearch::GeneratePath
|
|
(
|
|
PathInfo *path
|
|
)
|
|
{
|
|
PathNode *ParentNode;
|
|
pathway_t *pathway;
|
|
float dist;
|
|
float dir[ 2 ];
|
|
PathInfo *current_path;
|
|
|
|
current_path = path;
|
|
|
|
dir[ 0 ] = path_end[ 0 ] - Node->m_PathPos[ 0 ];
|
|
dir[ 1 ] = path_end[ 1 ] - Node->m_PathPos[ 1 ];
|
|
|
|
dist = VectorNormalize2D( dir );
|
|
|
|
total_dist = dist + Node->g;
|
|
|
|
path->point[ 0 ] = path_end[ 0 ];
|
|
path->point[ 1 ] = path_end[ 1 ];
|
|
path->point[ 2 ] = path_end[ 2 ];
|
|
|
|
ParentNode = Node->Parent;
|
|
if( ParentNode )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
|
|
path->dir[ 0 ] = path_end[ 0 ] - pathway->pos2[ 0 ];
|
|
path->dir[ 1 ] = path_end[ 1 ] - pathway->pos2[ 1 ];
|
|
path->dist = VectorNormalize2D( path->dir );
|
|
|
|
if( path->dist )
|
|
{
|
|
path->bAccurate = false;
|
|
current_path = path + 1;
|
|
}
|
|
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, current_path->point );
|
|
current_path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
current_path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
current_path->dist = pathway->dist;
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
|
|
for( Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL; Node = ParentNode, ParentNode = ParentNode->Parent )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, current_path->point );
|
|
current_path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
current_path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
current_path->dist = pathway->dist;
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
}
|
|
|
|
VectorCopy( pathway->pos1, current_path->point );
|
|
current_path->dir[ 0 ] = path_startdir[ 0 ];
|
|
current_path->dir[ 1 ] = path_startdir[ 1 ];
|
|
current_path->dist = Node->g;
|
|
}
|
|
else
|
|
{
|
|
path->dir[ 0 ] = path_totaldir[ 0 ];
|
|
path->dir[ 1 ] = path_totaldir[ 1 ];
|
|
path->dist = Node->h;
|
|
}
|
|
|
|
if( current_path->dist )
|
|
{
|
|
current_path->bAccurate = false;
|
|
current_path++;
|
|
|
|
VectorCopy( path_start, current_path->point );
|
|
current_path->dist = 0;
|
|
VectorClear2D( current_path->dir );
|
|
}
|
|
|
|
current_path->bAccurate = false;
|
|
return current_path;
|
|
}
|
|
|
|
PathInfo *PathSearch::GeneratePathNear
|
|
(
|
|
PathInfo *path
|
|
)
|
|
{
|
|
PathInfo *current_path = path;
|
|
pathway_t *pathway;
|
|
PathNode *ParentNode;
|
|
|
|
total_dist = Node->g;
|
|
VectorCopy( Node->m_PathPos, path->point );
|
|
|
|
ParentNode = Node->Parent;
|
|
if( ParentNode )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, path->point );
|
|
path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
path->dist = pathway->dist;
|
|
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
|
|
for( Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL; Node = ParentNode, ParentNode = ParentNode->Parent )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, current_path->point );
|
|
current_path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
current_path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
current_path->dist = pathway->dist;
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
}
|
|
|
|
VectorCopy( pathway->pos1, current_path->point );
|
|
current_path->dir[ 0 ] = path_startdir[ 0 ];
|
|
current_path->dir[ 1 ] = path_startdir[ 1 ];
|
|
|
|
current_path->dist = Node->g;
|
|
}
|
|
else
|
|
{
|
|
path->dir[ 0 ] = path_totaldir[ 0 ];
|
|
path->dir[ 1 ] = path_totaldir[ 1 ];
|
|
path->dist = Node->h;
|
|
}
|
|
|
|
if( current_path->dist )
|
|
{
|
|
current_path->bAccurate = false;
|
|
current_path++;
|
|
|
|
VectorCopy( path_start, current_path->point );
|
|
current_path->dist = 0;
|
|
VectorClear2D( current_path->dir );
|
|
}
|
|
|
|
current_path->bAccurate = false;
|
|
return current_path;
|
|
}
|
|
|
|
PathInfo *PathSearch::GeneratePathAway
|
|
(
|
|
PathInfo *path
|
|
)
|
|
{
|
|
PathInfo *current_path = path;
|
|
pathway_t *pathway;
|
|
PathNode *ParentNode;
|
|
|
|
VectorCopy( Node->m_PathPos, path->point );
|
|
|
|
ParentNode = Node->Parent;
|
|
if( ParentNode )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, path->point );
|
|
path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
path->dist = pathway->dist;
|
|
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
|
|
for( Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL; Node = ParentNode, ParentNode = ParentNode->Parent )
|
|
{
|
|
pathway = &ParentNode->Child[ Node->pathway ];
|
|
if( pathway->dist )
|
|
{
|
|
VectorCopy( pathway->pos2, current_path->point );
|
|
current_path->dir[ 0 ] = pathway->dir[ 0 ];
|
|
current_path->dir[ 1 ] = pathway->dir[ 1 ];
|
|
current_path->dist = pathway->dist;
|
|
current_path->bAccurate = true;
|
|
current_path++;
|
|
}
|
|
}
|
|
|
|
VectorCopy( pathway->pos1, current_path->point );
|
|
current_path->dir[ 0 ] = path_startdir[ 0 ];
|
|
current_path->dir[ 1 ] = path_startdir[ 1 ];
|
|
|
|
current_path->dist = Node->g;
|
|
|
|
if( Node->g )
|
|
{
|
|
current_path->bAccurate = false;
|
|
current_path++;
|
|
VectorCopy( path_start, current_path->point );
|
|
current_path->dist = 0;
|
|
VectorClear2D( current_path->dir );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorClear2D( path->dir );
|
|
path->dist = 0;
|
|
}
|
|
|
|
current_path->bAccurate = false;
|
|
return current_path;
|
|
}
|
|
|
|
PathNode *PathSearch::GetSpawnNode
|
|
(
|
|
ClassDef *cls
|
|
)
|
|
{
|
|
if( m_bNodesloaded )
|
|
{
|
|
return pathnodes[ m_LoadIndex++ ];
|
|
}
|
|
else
|
|
{
|
|
// Otherwise create a new node
|
|
return ( PathNode * )cls->newInstance();
|
|
}
|
|
}
|
|
|
|
int PathSearch::FindPath
|
|
(
|
|
float *start,
|
|
float *end,
|
|
Entity *ent,
|
|
float maxPath,
|
|
float *vLeashHome,
|
|
float fLeashDistSquared,
|
|
int fallheight
|
|
)
|
|
{
|
|
int i;
|
|
int g;
|
|
PathNode *NewNode;
|
|
pathway_t *pathway;
|
|
PathNode *prev;
|
|
PathNode *next;
|
|
int f;
|
|
vec2_t delta;
|
|
PathNode *to;
|
|
|
|
if( ent )
|
|
{
|
|
if( ent->IsSubclassOfActor() )
|
|
{
|
|
Node = NearestStartNode( start, ( SimpleActor * )ent );
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start, ent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start );
|
|
}
|
|
|
|
if( !Node )
|
|
{
|
|
last_error = "couldn't find start node";
|
|
return 0;
|
|
}
|
|
|
|
to = NearestEndNode( end );
|
|
if( !to )
|
|
{
|
|
last_error = "couldn't find end node";
|
|
return 0;
|
|
}
|
|
|
|
total_dist = 9e+11f;
|
|
|
|
if( !maxPath )
|
|
maxPath = 9e+11f;
|
|
|
|
findFrame++;
|
|
open = NULL;
|
|
|
|
path_startdir[ 0 ] = Node->origin[ 0 ] - start[ 0 ];
|
|
path_startdir[ 1 ] = Node->origin[ 1 ] - start[ 1 ];
|
|
|
|
Node->g = VectorNormalize2D( path_startdir );
|
|
|
|
path_totaldir[ 0 ] = end[ 0 ] - start[ 0 ];
|
|
path_totaldir[ 1 ] = end[ 1 ] - start[ 1 ];
|
|
|
|
Node->h = VectorNormalize2D( path_totaldir );
|
|
Node->inopen = true;
|
|
Node->Parent = NULL;
|
|
Node->m_Depth = 3;
|
|
Node->m_PathPos = start;
|
|
Node->findCount = findFrame;
|
|
Node->PrevNode = NULL;
|
|
Node->NextNode = NULL;
|
|
|
|
open = Node;
|
|
|
|
while( 1 )
|
|
{
|
|
Node = open;
|
|
Node->inopen = false;
|
|
|
|
open = Node->NextNode;
|
|
if( open )
|
|
open->PrevNode = NULL;
|
|
|
|
if( Node == to )
|
|
break;
|
|
|
|
for( i = Node->numChildren - 1; i >= 0; i-- )
|
|
{
|
|
vec2_t vDist;
|
|
|
|
pathway = &Node->Child[ i ];
|
|
|
|
NewNode = pathnodes[ pathway->node ];
|
|
|
|
if( vLeashHome )
|
|
{
|
|
vDist[ 0 ] = pathway->pos2[ 0 ] - vLeashHome[ 0 ];
|
|
vDist[ 1 ] = pathway->pos2[ 1 ] - vLeashHome[ 1 ];
|
|
}
|
|
|
|
if( !vLeashHome || VectorLength2DSquared( vDist ) <= fLeashDistSquared )
|
|
{
|
|
g = ( int )( pathway->dist + Node->g + 1.0f );
|
|
|
|
if( NewNode->findCount == findFrame )
|
|
{
|
|
if( g >= NewNode->g )
|
|
continue;
|
|
|
|
if( NewNode->inopen )
|
|
{
|
|
NewNode->inopen = false;
|
|
next = NewNode->NextNode;
|
|
prev = NewNode->PrevNode;
|
|
|
|
if( next )
|
|
next->PrevNode = prev;
|
|
|
|
if( prev )
|
|
prev->NextNode = next;
|
|
else
|
|
open = next;
|
|
}
|
|
}
|
|
|
|
delta[ 0 ] = end[ 0 ] - pathway->pos2[ 0 ];
|
|
delta[ 1 ] = end[ 1 ] - pathway->pos2[ 1 ];
|
|
|
|
NewNode->h = VectorLength2D( delta );
|
|
|
|
f = ( int )( ( float )g + NewNode->h );
|
|
|
|
if( f >= maxPath )
|
|
{
|
|
last_error = "specified path distance exceeded";
|
|
return 0;
|
|
}
|
|
|
|
if( pathway->fallheight <= fallheight )
|
|
{
|
|
NewNode->f = f;
|
|
NewNode->pathway = i;
|
|
NewNode->g = g;
|
|
NewNode->Parent = Node;
|
|
NewNode->m_Depth = Node->m_Depth + 1;
|
|
NewNode->inopen = true;
|
|
NewNode->m_PathPos = pathway->pos2;
|
|
NewNode->findCount = findFrame;
|
|
|
|
if( !open )
|
|
{
|
|
NewNode->NextNode = NULL;
|
|
NewNode->PrevNode = NULL;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
if( open->f >= f )
|
|
{
|
|
NewNode->PrevNode = NULL;
|
|
NewNode->NextNode = open;
|
|
|
|
open->PrevNode = NewNode;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
prev = open;
|
|
next = open->NextNode;
|
|
while( next && next->f < f )
|
|
{
|
|
prev = next;
|
|
next = next->NextNode;
|
|
}
|
|
|
|
NewNode->NextNode = next;
|
|
if( next )
|
|
next->PrevNode = NewNode;
|
|
prev->NextNode = NewNode;
|
|
NewNode->PrevNode = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !open )
|
|
{
|
|
last_error = "unreachable path";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
path_start = start;
|
|
path_end = end;
|
|
|
|
return Node->m_Depth;
|
|
}
|
|
|
|
int PathSearch::FindPathAway
|
|
(
|
|
float *start,
|
|
float *avoid,
|
|
float *vPreferredDir,
|
|
Entity *ent,
|
|
float fMinSafeDist,
|
|
float *vLeashHome,
|
|
float fLeashDistSquared,
|
|
int fallheight
|
|
)
|
|
{
|
|
int i;
|
|
int g;
|
|
PathNode *NewNode;
|
|
pathway_t *pathway;
|
|
PathNode *prev;
|
|
PathNode *next;
|
|
int f;
|
|
float fBias;
|
|
vec2_t delta;
|
|
float fMinSafeDistSquared = fMinSafeDist * fMinSafeDist;
|
|
|
|
if( ent )
|
|
{
|
|
if( ent->IsSubclassOfActor() )
|
|
{
|
|
Node = NearestStartNode( start, ( SimpleActor * )ent );
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start, ent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start );
|
|
}
|
|
|
|
if( !Node )
|
|
{
|
|
last_error = "couldn't find start node";
|
|
return 0;
|
|
}
|
|
|
|
findFrame++;
|
|
open = NULL;
|
|
|
|
path_startdir[ 0 ] = Node->origin[ 0 ] - start[ 0 ];
|
|
path_startdir[ 1 ] = Node->origin[ 1 ] - start[ 1 ];
|
|
|
|
delta[ 0 ] = start[ 0 ] - avoid[ 0 ];
|
|
delta[ 1 ] = start[ 1 ] - avoid[ 1 ];
|
|
|
|
fBias = VectorLength2D( vPreferredDir );
|
|
|
|
Node->inopen = true;
|
|
Node->g = VectorNormalize2D( path_startdir );
|
|
Node->h = fMinSafeDist - VectorNormalize2D( delta );
|
|
Node->h += fBias - DotProduct2D( vPreferredDir, delta );
|
|
Node->Parent = NULL;
|
|
Node->m_Depth = 2;
|
|
Node->findCount = findFrame;
|
|
Node->PrevNode = NULL;
|
|
Node->NextNode = NULL;
|
|
Node->m_PathPos = start;
|
|
|
|
open = Node;
|
|
|
|
while( 1 )
|
|
{
|
|
Node = open;
|
|
Node->inopen = false;
|
|
|
|
open = Node->NextNode;
|
|
|
|
if( open )
|
|
open->PrevNode = NULL;
|
|
|
|
delta[ 0 ] = Node->m_PathPos[ 0 ] - avoid[ 0 ];
|
|
delta[ 1 ] = Node->m_PathPos[ 1 ] - avoid[ 1 ];
|
|
|
|
if( VectorLength2DSquared( delta ) >= fMinSafeDistSquared )
|
|
break;
|
|
|
|
for( i = Node->numChildren - 1; i >= 0; i-- )
|
|
{
|
|
vec2_t vDist;
|
|
|
|
pathway = &Node->Child[ i ];
|
|
NewNode = pathnodes[ pathway->node ];
|
|
|
|
if( vLeashHome )
|
|
{
|
|
vDist[ 0 ] = pathway->pos2[ 0 ] - vLeashHome[ 0 ];
|
|
vDist[ 1 ] = pathway->pos2[ 1 ] - vLeashHome[ 1 ];
|
|
}
|
|
|
|
if( !vLeashHome || VectorLength2DSquared( vDist ) <= fLeashDistSquared )
|
|
{
|
|
g = ( int )( pathway->dist + Node->g + 1.0f );
|
|
|
|
if( NewNode->findCount == findFrame )
|
|
{
|
|
if( g >= NewNode->g )
|
|
continue;
|
|
|
|
if( NewNode->inopen )
|
|
{
|
|
NewNode->inopen = false;
|
|
next = NewNode->NextNode;
|
|
prev = NewNode->PrevNode;
|
|
|
|
if( next )
|
|
next->PrevNode = prev;
|
|
|
|
if( prev )
|
|
prev->NextNode = next;
|
|
else
|
|
open = next;
|
|
}
|
|
}
|
|
|
|
delta[ 0 ] = pathway->pos2[ 0 ] - avoid[ 0 ];
|
|
delta[ 1 ] = pathway->pos2[ 1 ] - avoid[ 1 ];
|
|
|
|
NewNode->h = fMinSafeDist - VectorNormalize2D( delta );
|
|
f = NewNode->h + fBias - DotProduct2D( vPreferredDir, delta );
|
|
NewNode->h += f;
|
|
|
|
if( pathway->fallheight <= fallheight )
|
|
{
|
|
NewNode->Parent = Node;
|
|
NewNode->m_Depth = Node->m_Depth + 1;
|
|
NewNode->g = g;
|
|
NewNode->pathway = i;
|
|
NewNode->inopen = true;
|
|
NewNode->m_PathPos = pathway->pos2;
|
|
NewNode->findCount = findFrame;
|
|
NewNode->f = f;
|
|
|
|
if( !open )
|
|
{
|
|
NewNode->NextNode = NULL;
|
|
NewNode->PrevNode = NULL;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
if( open->f >= f )
|
|
{
|
|
NewNode->PrevNode = NULL;
|
|
NewNode->NextNode = open;
|
|
|
|
open->PrevNode = NewNode;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
prev = open;
|
|
next = open->NextNode;
|
|
while( next && next->f < f )
|
|
{
|
|
prev = next;
|
|
next = next->NextNode;
|
|
}
|
|
|
|
NewNode->NextNode = next;
|
|
if( next )
|
|
next->PrevNode = NewNode;
|
|
prev->NextNode = NewNode;
|
|
NewNode->PrevNode = prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !open )
|
|
{
|
|
last_error = "unreachable path";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
path_start = start;
|
|
return Node->m_Depth;
|
|
}
|
|
|
|
int PathSearch::FindPathNear
|
|
(
|
|
float *start,
|
|
float *end,
|
|
Entity *ent,
|
|
float maxPath,
|
|
float fRadiusSquared,
|
|
float *vLeashHome,
|
|
float fLeashDistSquared,
|
|
int fallheight
|
|
)
|
|
{
|
|
int i;
|
|
int g;
|
|
PathNode *NewNode;
|
|
pathway_t *pathway;
|
|
PathNode *prev;
|
|
PathNode *next;
|
|
int f;
|
|
vec2_t dir;
|
|
vec2_t delta;
|
|
|
|
if( ent )
|
|
{
|
|
if( ent->IsSubclassOfActor() )
|
|
{
|
|
Node = NearestStartNode( start, ( SimpleActor * )ent );
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start, ent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Node = DebugNearestStartNode( start );
|
|
}
|
|
|
|
if( !Node )
|
|
{
|
|
last_error = "no start node";
|
|
return 0;
|
|
}
|
|
|
|
total_dist = 9e11f;
|
|
|
|
if( !maxPath )
|
|
maxPath = 9e11f;
|
|
|
|
findFrame++;
|
|
open = NULL;
|
|
|
|
path_startdir[ 0 ] = Node->origin[ 0 ] - start[ 0 ];
|
|
path_startdir[ 1 ] = Node->origin[ 1 ] - start[ 1 ];
|
|
|
|
dir[ 0 ] = end[ 0 ] - start[ 0 ];
|
|
dir[ 1 ] = end[ 1 ] - start[ 1 ];
|
|
|
|
Node->inopen = true;
|
|
Node->g = VectorNormalize2D( path_startdir );
|
|
Node->h = VectorNormalize2D( delta );
|
|
Node->Parent = NULL;
|
|
Node->m_Depth = 3;
|
|
Node->findCount = findFrame;
|
|
Node->PrevNode = NULL;
|
|
Node->NextNode = NULL;
|
|
Node->m_PathPos = start;
|
|
|
|
open = Node;
|
|
|
|
while( 1 )
|
|
{
|
|
Node = open;
|
|
Node->inopen = false;
|
|
|
|
open = Node->NextNode;
|
|
|
|
if( open )
|
|
open->PrevNode = NULL;
|
|
|
|
delta[ 0 ] = end[ 0 ] - Node->m_PathPos[ 0 ];
|
|
delta[ 1 ] = end[ 1 ] - Node->m_PathPos[ 1 ];
|
|
|
|
if( fRadiusSquared >= VectorLength2DSquared( delta ) )
|
|
break;
|
|
|
|
for( i = Node->numChildren - 1; i >= 0; i-- )
|
|
{
|
|
pathway = &Node->Child[ i ];
|
|
NewNode = pathnodes[ pathway->node ];
|
|
|
|
g = ( int )( pathway->dist + Node->g + 1.0f );
|
|
|
|
if( NewNode->findCount == findFrame )
|
|
{
|
|
if( g >= NewNode->g )
|
|
continue;
|
|
|
|
if( NewNode->inopen )
|
|
{
|
|
NewNode->inopen = false;
|
|
next = NewNode->NextNode;
|
|
prev = NewNode->PrevNode;
|
|
|
|
if( next )
|
|
next->PrevNode = prev;
|
|
|
|
if( prev )
|
|
prev->NextNode = next;
|
|
else
|
|
open = next;
|
|
}
|
|
}
|
|
|
|
delta[ 0 ] = end[ 0 ] - pathway->pos2[ 0 ];
|
|
delta[ 1 ] = end[ 1 ] - pathway->pos2[ 1 ];
|
|
|
|
NewNode->h = VectorLength2D( delta );
|
|
f = ( int )( ( float )g + NewNode->h );
|
|
|
|
if( f >= maxPath )
|
|
{
|
|
last_error = "specified path distance exceeded";
|
|
return 0;
|
|
}
|
|
|
|
if( pathway->fallheight <= fallheight )
|
|
{
|
|
NewNode->f = f;
|
|
NewNode->pathway = i;
|
|
NewNode->g = g;
|
|
NewNode->Parent = Node;
|
|
NewNode->m_Depth = Node->m_Depth + 1;
|
|
NewNode->inopen = true;
|
|
NewNode->m_PathPos = pathway->pos2;
|
|
NewNode->findCount = findFrame;
|
|
|
|
if( !open )
|
|
{
|
|
NewNode->NextNode = NULL;
|
|
NewNode->PrevNode = NULL;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
if( open->f >= f )
|
|
{
|
|
NewNode->PrevNode = NULL;
|
|
NewNode->NextNode = open;
|
|
|
|
open->PrevNode = NewNode;
|
|
open = NewNode;
|
|
continue;
|
|
}
|
|
|
|
prev = open;
|
|
next = open->NextNode;
|
|
while( next && next->f < f )
|
|
{
|
|
prev = next;
|
|
next = next->NextNode;
|
|
}
|
|
|
|
NewNode->NextNode = next;
|
|
if( next )
|
|
next->PrevNode = NewNode;
|
|
prev->NextNode = NewNode;
|
|
NewNode->PrevNode = prev;
|
|
}
|
|
}
|
|
|
|
if( !open )
|
|
{
|
|
last_error = "unreachable path";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
path_start = start;
|
|
path_end = end;
|
|
return Node->m_Depth;
|
|
}
|
|
|
|
int node_compare(const void *pe1, const void *pe2)
|
|
{
|
|
nodeinfo *Pe1 = (nodeinfo *) pe1;
|
|
nodeinfo *Pe2 = (nodeinfo *) pe2;
|
|
int iConcealment;
|
|
|
|
iConcealment = (Pe1->pNode->nodeflags & 0xB0) != 0;
|
|
if (Pe2->pNode->nodeflags & 0xB0)
|
|
--iConcealment;
|
|
return ((Pe1->fDistSquared)
|
|
+ (iConcealment << 23)
|
|
+ (((Pe1->pNode->nodeflags & 8) - (Pe2->pNode->nodeflags & 8)) << 21)
|
|
- (Pe2->fDistSquared));
|
|
|
|
}
|
|
PathNode *PathSearch::FindCornerNodeForWall
|
|
(
|
|
float *start,
|
|
float *end,
|
|
SimpleActor *ent,
|
|
float maxPath,
|
|
float *plane
|
|
)
|
|
{
|
|
PathNode *ParentNode;
|
|
PathNode *OpenNode;
|
|
int i;
|
|
|
|
Node = NearestStartNode(start, ent);
|
|
if (!Node)
|
|
{
|
|
last_error = "couldn't find start node";
|
|
return NULL;
|
|
}
|
|
|
|
if (DotProduct(start, plane) - plane[3] < 0.0)
|
|
{
|
|
last_error = "starting point is already behind the wall";
|
|
return NULL;
|
|
}
|
|
|
|
if (DotProduct(plane, end) - plane[3] > 0.0)
|
|
{
|
|
last_error = "end point is in front of the wall";
|
|
return NULL;
|
|
}
|
|
|
|
total_dist = 1.0e12f;
|
|
|
|
if (maxPath == 0.0)
|
|
maxPath = 1.0e12f;
|
|
|
|
findFrame++;
|
|
open = NULL;
|
|
|
|
VectorSub2D(Node->origin, start, path_startdir);
|
|
Node->g = VectorNormalize2D(path_startdir);
|
|
|
|
VectorSub2D(end, start, path_totaldir);
|
|
Node->h = VectorNormalize2D(path_totaldir);
|
|
|
|
Node->inopen = true;
|
|
|
|
Node->Parent = NULL;
|
|
|
|
Node->m_Depth = 3;
|
|
Node->m_PathPos = start;
|
|
Node->findCount = findFrame;
|
|
Node->PrevNode = 0;
|
|
Node->NextNode = 0;
|
|
|
|
OpenNode = Node;
|
|
open = Node;
|
|
|
|
if (!open)
|
|
{
|
|
last_error = "unreachable path";
|
|
return NULL;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
Node = OpenNode;
|
|
|
|
Node->inopen = false;
|
|
|
|
open = Node->NextNode;
|
|
if (open)
|
|
{
|
|
open->PrevNode = NULL;
|
|
}
|
|
|
|
ParentNode = Node->Parent;
|
|
|
|
if (ParentNode
|
|
&& DotProduct(plane, Node->m_PathPos) - plane[3] < 0.0)
|
|
{
|
|
vec2_t delta;
|
|
VectorSub2D(ParentNode->m_PathPos, start, delta);
|
|
if (VectorLength2DSquared(delta) < Square(16))
|
|
ParentNode = Node;
|
|
return ParentNode;
|
|
}
|
|
|
|
i = Node->numChildren;
|
|
if (i)
|
|
break;
|
|
weird_lbl:
|
|
OpenNode = open;
|
|
if (!OpenNode)
|
|
{
|
|
last_error = "unreachable path";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int f, g, h;
|
|
while (true)
|
|
{
|
|
pathway_t *pathway = &Node->Child[--i];
|
|
PathNode *pSomeNode = PathSearch::pathnodes[pathway->node];
|
|
g = (pathway->dist + Node->g + 1.0);
|
|
if (pSomeNode->findCount == PathSearch::findFrame)
|
|
{
|
|
if (g >= pSomeNode->g)
|
|
{
|
|
if (i == 0)
|
|
goto weird_lbl;
|
|
|
|
continue;
|
|
}
|
|
if (pSomeNode->inopen)
|
|
{
|
|
pSomeNode->inopen = false;
|
|
|
|
if (pSomeNode->NextNode)
|
|
pSomeNode->NextNode->PrevNode = pSomeNode->PrevNode;
|
|
if (pSomeNode->PrevNode)
|
|
pSomeNode->PrevNode->NextNode = pSomeNode->NextNode;
|
|
else
|
|
PathSearch::open = pSomeNode->NextNode;
|
|
}
|
|
}
|
|
|
|
vec2_t vDelta2;
|
|
VectorSub2D(end, pathway->pos1, vDelta2);
|
|
pSomeNode->h = h = VectorLength2D(vDelta2);
|
|
|
|
f = (h + g);
|
|
|
|
if (f >= maxPath)
|
|
break;
|
|
|
|
pSomeNode->f = f;
|
|
pSomeNode->pathway = i;
|
|
pSomeNode->g = g;
|
|
pSomeNode->Parent = Node;
|
|
pSomeNode->inopen = true;
|
|
pSomeNode->m_PathPos = pathway->pos2;
|
|
pSomeNode->findCount = PathSearch::findFrame;
|
|
pSomeNode->m_Depth = Node->m_Depth + 1;
|
|
|
|
if (!PathSearch::open)
|
|
{
|
|
pSomeNode->NextNode = NULL;
|
|
pSomeNode->PrevNode = NULL;
|
|
PathSearch::open = pSomeNode;
|
|
if (i == 0)
|
|
goto weird_lbl;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (PathSearch::open->f == f)
|
|
{
|
|
pSomeNode->PrevNode = NULL;
|
|
pSomeNode->NextNode = PathSearch::open;
|
|
PathSearch::open->PrevNode = pSomeNode;
|
|
PathSearch::open = pSomeNode;
|
|
if (i == 0)
|
|
goto weird_lbl;
|
|
|
|
continue;
|
|
}
|
|
PathNode * pNextOpenNode = PathSearch::open->NextNode;
|
|
if (pNextOpenNode)
|
|
{
|
|
if (f > pNextOpenNode->f)
|
|
{
|
|
do
|
|
{
|
|
pNextOpenNode = pNextOpenNode->NextNode;
|
|
if (!pNextOpenNode)
|
|
break;
|
|
|
|
} while (f > pNextOpenNode->f);
|
|
}
|
|
}
|
|
|
|
pSomeNode->NextNode = pNextOpenNode;
|
|
|
|
if (pNextOpenNode)
|
|
pNextOpenNode->PrevNode = pSomeNode;
|
|
|
|
pNextOpenNode->NextNode = pSomeNode;
|
|
pSomeNode->PrevNode = pNextOpenNode;
|
|
|
|
if (i == 0)
|
|
goto weird_lbl;
|
|
}
|
|
PathSearch::last_error = "specified path distance exceeded";
|
|
return NULL;
|
|
}
|
|
|
|
PathNode *PathSearch::FindCornerNodeForExactPath
|
|
(
|
|
SimpleActor *pSelf,
|
|
Sentient *enemy,
|
|
float fMaxPath
|
|
)
|
|
{
|
|
PathNode *pPathNode[4096];
|
|
|
|
if (!PathSearch::FindPath(enemy->origin, pSelf->origin, pSelf, fMaxPath, 0, 0.0, 100))
|
|
return NULL;
|
|
|
|
size_t iDepth = 0;
|
|
for (PathNode* pParentNode = Node->Parent; pParentNode; pParentNode = pParentNode->Parent, iDepth++)
|
|
{
|
|
pPathNode[iDepth] = pParentNode;
|
|
}
|
|
|
|
Node = pPathNode[iDepth -1];
|
|
|
|
|
|
if (iDepth == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
size_t i;
|
|
for (i = 0; i < iDepth; i += 2)
|
|
{
|
|
if (!G_SightTrace(
|
|
pSelf->EyePosition(),
|
|
vec_zero,
|
|
vec_zero,
|
|
pSelf->EyePosition() - pSelf->origin + pPathNode[i]->m_PathPos,
|
|
pSelf,
|
|
enemy,
|
|
0x2040B19,
|
|
0,
|
|
"FindCornerNodeFoExactPath 1"))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
size_t index = i - 1;
|
|
if (index < iDepth)
|
|
{
|
|
if (index)
|
|
{
|
|
if (!G_SightTrace(
|
|
pSelf->EyePosition(),
|
|
vec_zero,
|
|
vec_zero,
|
|
pSelf->EyePosition() - pSelf->origin + pPathNode[i]->m_PathPos,
|
|
pSelf,
|
|
enemy,
|
|
0x2040B19,
|
|
0,
|
|
"FindCornerNodeFoExactPath 2"))
|
|
{
|
|
index--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
index = iDepth - 1;
|
|
}
|
|
|
|
return pPathNode[index];
|
|
}
|
|
|
|
int PathSearch::FindPotentialCover
|
|
(
|
|
SimpleActor *pEnt,
|
|
Vector& vPos,
|
|
Entity *pEnemy,
|
|
PathNode **ppFoundNodes,
|
|
int iMaxFind
|
|
)
|
|
{
|
|
Actor *pSelf = (Actor *)pEnt;
|
|
PathNode *pNode;
|
|
Vector delta;
|
|
int nNodes = 0;
|
|
nodeinfo nodes[MAX_PATHNODES];
|
|
|
|
for (int i = 0; i < nodecount; i++)
|
|
{
|
|
pNode = pathnodes[i];
|
|
if (pNode && pNode->nodeflags & AI_SNIPER)
|
|
{
|
|
if (pNode->pLastClaimer == pSelf || (pNode->iAvailableTime && level.inttime >= pNode->iAvailableTime) || (pNode->pLastClaimer == NULL))
|
|
{
|
|
delta = pNode->origin - pSelf->m_vHome;
|
|
if (delta.lengthSquared() <= pSelf->m_fLeashSquared)
|
|
{
|
|
delta = pNode->origin - pEnemy->origin;
|
|
if (delta.lengthSquared() >= pSelf->m_fMinDistanceSquared && delta.lengthSquared() <= pSelf->m_fMaxDistanceSquared)
|
|
{
|
|
delta = pNode->origin - pSelf->origin;
|
|
nodes[nNodes].pNode = pNode;
|
|
nodes[nNodes].fDistSquared = delta.lengthSquared();
|
|
nNodes++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nNodes)
|
|
{
|
|
qsort(nodes, nNodes, sizeof(nodeinfo), node_compare);
|
|
|
|
if (nNodes > iMaxFind)
|
|
nNodes = iMaxFind;
|
|
|
|
if (nNodes > 0)
|
|
{
|
|
pNode = ppFoundNodes[nNodes - 1];
|
|
|
|
for (int i = 0; i < nNodes; i++)
|
|
{
|
|
pNode = nodes[i].pNode;
|
|
pNode--;
|
|
}
|
|
}
|
|
|
|
}
|
|
return nNodes;
|
|
}
|
|
|
|
void PathSearch::PlayerCover
|
|
(
|
|
Player *pPlayer
|
|
)
|
|
{
|
|
int i;
|
|
PathNode *node;
|
|
Vector delta;
|
|
Entity *pOwner;
|
|
|
|
for( i = 0; i < nodecount; i++ )
|
|
{
|
|
node = pathnodes[ i ];
|
|
|
|
if( !node || !( node->nodeflags & AI_COVERFLAGS ) )
|
|
continue;
|
|
|
|
pOwner = node->GetClaimHolder();
|
|
|
|
delta = node->origin - pPlayer->origin;
|
|
|
|
// Check if we need to cover
|
|
if( VectorLengthSquared( delta ) > 2304.0f )
|
|
{
|
|
if( pOwner == pPlayer )
|
|
node->Relinquish();
|
|
|
|
continue;
|
|
}
|
|
|
|
if( pOwner != pPlayer )
|
|
{
|
|
if( pOwner )
|
|
pOwner->PathnodeClaimRevoked( node );
|
|
|
|
// Player claim the node
|
|
node->Claim( pPlayer );
|
|
}
|
|
}
|
|
}
|
|
|
|
PathNode *PathSearch::FindNearestCover
|
|
(
|
|
SimpleActor *pEnt,
|
|
Vector& vPos,
|
|
Entity *pEnemy
|
|
)
|
|
{
|
|
// not found in ida
|
|
return NULL;
|
|
}
|
|
|
|
PathNode *PathSearch::FindNearestSniperNode
|
|
(
|
|
SimpleActor *pEnt,
|
|
Vector& vPos,
|
|
Entity *pEnemy
|
|
)
|
|
{
|
|
Actor *pSelf = (Actor *)pEnt;
|
|
PathNode *pNode;
|
|
Vector delta;
|
|
int nNodes = 0;
|
|
nodeinfo nodes[MAX_PATHNODES];
|
|
|
|
for (int i = 0; i < nodecount; i++)
|
|
{
|
|
pNode = pathnodes[i];
|
|
if (pNode && pNode->nodeflags & AI_SNIPER)
|
|
{
|
|
if (pNode->pLastClaimer == pSelf || (pNode->iAvailableTime && level.inttime >= pNode->iAvailableTime) || (pNode->pLastClaimer == NULL))
|
|
{
|
|
delta = pNode->origin - pSelf->m_vHome;
|
|
if (delta.lengthSquared() <= pSelf->m_fLeashSquared)
|
|
{
|
|
delta = pNode->origin - pEnemy->origin;
|
|
if (delta.lengthSquared() >= pSelf->m_fMinDistanceSquared && delta.lengthSquared() <= pSelf->m_fMaxDistanceSquared)
|
|
{
|
|
delta = pNode->origin - pSelf->origin;
|
|
nodes[nNodes].pNode = pNode;
|
|
nodes[nNodes].fDistSquared = delta.lengthSquared();
|
|
nNodes++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nNodes == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
qsort(nodes, nNodes, sizeof(nodeinfo), node_compare);
|
|
|
|
if (nNodes <= 0)
|
|
return NULL;
|
|
|
|
for (int i = 0; i < nNodes; i++)
|
|
{
|
|
pNode = nodes[i].pNode;
|
|
if (pSelf->CanSeeFrom(pSelf->EyePosition() + pNode->origin, pEnemy))
|
|
{
|
|
return pNode;
|
|
}
|
|
|
|
pNode->iAvailableTime = level.inttime + 5000;
|
|
pNode->pLastClaimer = NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int PathSearch::NearestNodeSetup
|
|
(
|
|
vec3_t pos,
|
|
MapCell *cell,
|
|
int *nodes,
|
|
vec3_t *deltas
|
|
)
|
|
{
|
|
vec3_t delta;
|
|
PathNode *node;
|
|
float dist;
|
|
int n = 0;
|
|
int i;
|
|
int j;
|
|
float node_dist[ 128 ];
|
|
int node_count;
|
|
|
|
node_count = cell->numnodes;
|
|
|
|
for( i = 0; i < node_count; i++ )
|
|
{
|
|
node = pathnodes[ cell->nodes[ i ] ];
|
|
|
|
if( pos[ 2 ] > node->origin[ 2 ] + 94.0f )
|
|
continue;
|
|
|
|
if( node->origin[ 2 ] > pos[ 2 ] + 94.0f )
|
|
continue;
|
|
|
|
delta[ 0 ] = node->origin[ 0 ] - pos[ 0 ];
|
|
delta[ 1 ] = node->origin[ 1 ] - pos[ 1 ];
|
|
delta[ 2 ] = node->origin[ 2 ] - pos[ 2 ];
|
|
|
|
VectorCopy( delta, deltas[ i ] );
|
|
|
|
dist = VectorLengthSquared( delta );
|
|
|
|
for( j = n; j > 0; j-- )
|
|
{
|
|
if( dist >= node_dist[ j - 1 ] )
|
|
break;
|
|
|
|
node_dist[ j ] = node_dist[ j - 1 ];
|
|
nodes[ j ] = nodes[ j - 1 ];
|
|
}
|
|
|
|
n++;
|
|
nodes[ j ] = i;
|
|
node_dist[ j ] = dist;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
void PathSearch::Init
|
|
(
|
|
void
|
|
)
|
|
{
|
|
ai_showroutes = gi.Cvar_Get( "ai_showroutes", "0", 0 );
|
|
ai_showroutes_distance = gi.Cvar_Get( "ai_showroutes_distance", "1000", 0 );
|
|
ai_shownodenums = gi.Cvar_Get( "ai_shownodenums", "0", 0 );
|
|
ai_shownode = gi.Cvar_Get( "ai_shownode", "0", 0 );
|
|
ai_showallnode = gi.Cvar_Get( "ai_showallnode", "0", 0 );
|
|
ai_showpath = gi.Cvar_Get( "ai_showpath", "0", 0 );
|
|
ai_fallheight = gi.Cvar_Get( "ai_fallheight", "4096", 0 );
|
|
ai_debugpath = gi.Cvar_Get( "ai_debugpath", "0", 0 );
|
|
ai_pathchecktime = gi.Cvar_Get( "ai_pathchecktime", "1.5", CVAR_CHEAT );
|
|
ai_pathcheckdist = gi.Cvar_Get( "ai_pathcheckdist", "4096", CVAR_CHEAT );
|
|
}
|
|
|
|
Event EV_AttractiveNode_GetPriority
|
|
(
|
|
"priority",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Get the node priority",
|
|
EV_GETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetPriority
|
|
(
|
|
"priority",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"priority",
|
|
"Set the node priority",
|
|
EV_SETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_GetDistance
|
|
(
|
|
"max_dist",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Get the max distance for this node",
|
|
EV_GETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetDistance
|
|
(
|
|
"max_dist",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"max_dist",
|
|
"Set the max distance for this node to be attracted, -1 for unlimited distance.",
|
|
EV_SETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_GetStayTime
|
|
(
|
|
"stay_time",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Get the max stay time for this node",
|
|
EV_GETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetStayTime
|
|
(
|
|
"stay_time",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"stay_time",
|
|
"Set the maximum stay time AI will stay on this node",
|
|
EV_SETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_GetRespawnTime
|
|
(
|
|
"respawn_time",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Get the how much time will this node re-attract already attracted AIs",
|
|
EV_GETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetRespawnTime
|
|
(
|
|
"respawn_time",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"respawn_time",
|
|
"Set the how much time will this node re-attract already attracted AIs. The minimum required value is 1, otherwise AI will get stuck.",
|
|
EV_SETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_GetTeam
|
|
(
|
|
"team",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Get the attractive node team. 'none' for no team.",
|
|
EV_GETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetTeam
|
|
(
|
|
"team",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"team",
|
|
"Set the attractive node team. 'none' for no team.",
|
|
EV_SETTER
|
|
);
|
|
|
|
Event EV_AttractiveNode_SetUse
|
|
(
|
|
"setuse",
|
|
EV_DEFAULT,
|
|
"b",
|
|
"use",
|
|
"Set if AI should use or not"
|
|
);
|
|
|
|
CLASS_DECLARATION( SimpleArchivedEntity, AttractiveNode, NULL )
|
|
{
|
|
{ &EV_AttractiveNode_GetPriority, &AttractiveNode::GetPriority },
|
|
{ &EV_AttractiveNode_SetPriority, &AttractiveNode::SetPriority },
|
|
{ &EV_AttractiveNode_GetDistance, &AttractiveNode::GetDistance },
|
|
{ &EV_AttractiveNode_SetDistance, &AttractiveNode::SetDistance },
|
|
{ &EV_AttractiveNode_GetStayTime, &AttractiveNode::GetStayTime },
|
|
{ &EV_AttractiveNode_SetStayTime, &AttractiveNode::SetStayTime },
|
|
{ &EV_AttractiveNode_GetRespawnTime, &AttractiveNode::GetRespawnTime },
|
|
{ &EV_AttractiveNode_SetRespawnTime, &AttractiveNode::SetRespawnTime },
|
|
{ &EV_AttractiveNode_GetTeam, &AttractiveNode::GetTeam },
|
|
{ &EV_AttractiveNode_SetTeam, &AttractiveNode::SetTeam },
|
|
{ &EV_AttractiveNode_SetUse, &AttractiveNode::SetUse },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Container< AttractiveNode * > attractiveNodes;
|
|
|
|
AttractiveNode::AttractiveNode()
|
|
{
|
|
|
|
m_iPriority = 0; // set to default 0
|
|
m_fMaxStayTime = 0; // set to default 0, could be just a pickup
|
|
m_fMaxDistance = 1024;
|
|
m_fMaxDistanceSquared = m_fMaxDistance * m_fMaxDistance;
|
|
m_fRespawnTime = 15.0f; // set to default 15 seconds
|
|
m_bUse = false;
|
|
m_csTeam = STRING_NONE;
|
|
m_iTeam = TEAM_NONE;
|
|
|
|
attractiveNodes.AddObject( this );
|
|
}
|
|
|
|
AttractiveNode::~AttractiveNode()
|
|
{
|
|
attractiveNodes.RemoveObject( this );
|
|
}
|
|
|
|
bool AttractiveNode::CheckTeam
|
|
(
|
|
Sentient *sent
|
|
)
|
|
{
|
|
if( !m_iTeam )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if( sent->IsSubclassOfPlayer() )
|
|
{
|
|
Player *p = ( Player * )sent;
|
|
|
|
if( ( m_iTeam == TEAM_FREEFORALL && g_gametype->integer >= GT_TEAM ) ||
|
|
p->GetTeam() != m_iTeam )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( m_iTeam == TEAM_ALLIES && sent->m_Team != TEAM_AMERICAN )
|
|
{
|
|
return false;
|
|
}
|
|
else if( m_iTeam == TEAM_AXIS && sent->m_Team != TEAM_GERMAN )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AttractiveNode::setMaxDist
|
|
(
|
|
float dist
|
|
)
|
|
{
|
|
m_fMaxDistance = dist;
|
|
|
|
if( dist < 0 )
|
|
{
|
|
m_fMaxDistanceSquared = -1;
|
|
}
|
|
else
|
|
{
|
|
m_fMaxDistanceSquared = dist * dist;
|
|
}
|
|
}
|
|
|
|
void AttractiveNode::GetPriority
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
ev->AddInteger( m_iPriority );
|
|
}
|
|
|
|
void AttractiveNode::SetPriority
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
m_iPriority = ev->GetInteger( 1 );
|
|
}
|
|
|
|
void AttractiveNode::GetDistance
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
ev->AddFloat( m_fMaxDistance );
|
|
}
|
|
|
|
void AttractiveNode::SetDistance
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
setMaxDist( ev->GetFloat( 1 ) );
|
|
}
|
|
|
|
void AttractiveNode::GetStayTime
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
ev->AddFloat( m_fMaxStayTime );
|
|
}
|
|
|
|
void AttractiveNode::SetStayTime
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
m_fMaxStayTime = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void AttractiveNode::GetRespawnTime
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
ev->AddFloat( m_fRespawnTime );
|
|
}
|
|
|
|
void AttractiveNode::SetRespawnTime
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
m_fRespawnTime = ev->GetFloat( 1 );
|
|
if( m_fRespawnTime < 1.0f )
|
|
{
|
|
m_fRespawnTime = 1.0f;
|
|
}
|
|
}
|
|
|
|
void AttractiveNode::GetTeam
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
ev->AddConstString( m_csTeam );
|
|
}
|
|
|
|
void AttractiveNode::SetTeam
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
if( ev->IsNilAt( 1 ) )
|
|
{
|
|
m_csTeam = STRING_NONE;
|
|
m_iTeam = TEAM_NONE;
|
|
return;
|
|
}
|
|
|
|
m_csTeam = ev->GetConstString( 1 );
|
|
|
|
switch( m_csTeam )
|
|
{
|
|
case STRING_NONE:
|
|
m_iTeam = TEAM_NONE;
|
|
break;
|
|
case STRING_SPECTATOR:
|
|
m_iTeam = TEAM_SPECTATOR;
|
|
break;
|
|
case STRING_FREEFORALL:
|
|
m_iTeam = TEAM_FREEFORALL;
|
|
break;
|
|
case STRING_ALLIES:
|
|
case STRING_AMERICAN:
|
|
m_iTeam = TEAM_ALLIES;
|
|
break;
|
|
case STRING_AXIS:
|
|
case STRING_GERMAN:
|
|
m_iTeam = TEAM_AXIS;
|
|
break;
|
|
default:
|
|
m_iTeam = TEAM_NONE;
|
|
ScriptError( "Invalid team %s\n", ev->GetString( 1 ).c_str() );
|
|
}
|
|
}
|
|
|
|
void AttractiveNode::SetUse
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
m_bUse = ev->GetBoolean( 1 );
|
|
}
|