2013-04-19 02:52:48 +00:00
//NPC_senses.cpp
// leave this line at the top for all NPC_xxxx.cpp files...
# include "g_headers.h"
# include "b_local.h"
# ifdef _DEBUG
# include <float.h>
# endif
extern int eventClearTime ;
/*
qboolean G_ClearLineOfSight ( const vec3_t point1 , const vec3_t point2 , int ignore , int clipmask )
returns true if can see from point 1 to 2 , even through glass ( 1 pane ) - doesn ' t work with portals
*/
qboolean G_ClearLineOfSight ( const vec3_t point1 , const vec3_t point2 , int ignore , int clipmask )
{
trace_t tr ;
gi . trace ( & tr , point1 , NULL , NULL , point2 , ignore , clipmask ) ;
if ( tr . fraction = = 1.0 )
{
return qtrue ;
}
gentity_t * hit = & g_entities [ tr . entityNum ] ;
if ( EntIsGlass ( hit ) )
{
vec3_t newpoint1 ;
VectorCopy ( tr . endpos , newpoint1 ) ;
gi . trace ( & tr , newpoint1 , NULL , NULL , point2 , hit - > s . number , clipmask ) ;
if ( tr . fraction = = 1.0 )
{
return qtrue ;
}
}
return qfalse ;
}
/*
CanSee
determine if NPC can see an entity
This is a straight line trace check . This function does not look at PVS or FOV ,
or take any AI related factors ( for example , the NPC ' s reaction time ) into account
FIXME do we need fat and thin version of this ?
*/
qboolean CanSee ( gentity_t * ent )
{
trace_t tr ;
vec3_t eyes ;
vec3_t spot ;
CalcEntitySpot ( NPC , SPOT_HEAD_LEAN , eyes ) ;
CalcEntitySpot ( ent , SPOT_ORIGIN , spot ) ;
gi . trace ( & tr , eyes , NULL , NULL , spot , NPC - > s . number , MASK_OPAQUE ) ;
ShotThroughGlass ( & tr , ent , spot , MASK_OPAQUE ) ;
if ( tr . fraction = = 1.0 )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_HEAD , spot ) ;
gi . trace ( & tr , eyes , NULL , NULL , spot , NPC - > s . number , MASK_OPAQUE ) ;
ShotThroughGlass ( & tr , ent , spot , MASK_OPAQUE ) ;
if ( tr . fraction = = 1.0 )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_LEGS , spot ) ;
gi . trace ( & tr , eyes , NULL , NULL , spot , NPC - > s . number , MASK_OPAQUE ) ;
ShotThroughGlass ( & tr , ent , spot , MASK_OPAQUE ) ;
if ( tr . fraction = = 1.0 )
{
return qtrue ;
}
return qfalse ;
}
qboolean InFront ( vec3_t spot , vec3_t from , vec3_t fromAngles , float threshHold = 0.0f )
{
vec3_t dir , forward , angles ;
float dot ;
VectorSubtract ( spot , from , dir ) ;
dir [ 2 ] = 0 ;
VectorNormalize ( dir ) ;
VectorCopy ( fromAngles , angles ) ;
angles [ 0 ] = 0 ;
AngleVectors ( angles , forward , NULL , NULL ) ;
dot = DotProduct ( dir , forward ) ;
return ( dot > threshHold ) ;
}
float DotToSpot ( vec3_t spot , vec3_t from , vec3_t fromAngles )
{
vec3_t dir , forward , angles ;
float dot ;
VectorSubtract ( spot , from , dir ) ;
dir [ 2 ] = 0 ;
VectorNormalize ( dir ) ;
VectorCopy ( fromAngles , angles ) ;
angles [ 0 ] = 0 ;
AngleVectors ( angles , forward , NULL , NULL ) ;
dot = DotProduct ( dir , forward ) ;
return dot ;
}
/*
InFOV
IDEA : further off to side of FOV range , higher chance of failing even if technically in FOV ,
keep core of 50 % to sides as always succeeding
*/
//Position compares
qboolean InFOV ( vec3_t spot , vec3_t from , vec3_t fromAngles , int hFOV , int vFOV )
{
vec3_t deltaVector , angles , deltaAngles ;
VectorSubtract ( spot , from , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
return qfalse ;
}
//NPC to position
qboolean InFOV ( vec3_t origin , gentity_t * from , int hFOV , int vFOV )
{
vec3_t fromAngles , eyes ;
if ( from - > client )
{
VectorCopy ( from - > client - > ps . viewangles , fromAngles ) ;
}
else
{
VectorCopy ( from - > s . angles , fromAngles ) ;
}
CalcEntitySpot ( from , SPOT_HEAD , eyes ) ;
return InFOV ( origin , eyes , fromAngles , hFOV , vFOV ) ;
}
//Entity to entity
qboolean InFOVFromPlayerView ( gentity_t * ent , int hFOV , int vFOV )
{
vec3_t eyes ;
vec3_t spot ;
vec3_t deltaVector ;
vec3_t angles , fromAngles ;
vec3_t deltaAngles ;
if ( ! player | | ! player - > client )
{
return qfalse ;
}
if ( cg . time )
{
VectorCopy ( cg . refdefViewAngles , fromAngles ) ;
}
else
{
VectorCopy ( player - > client - > ps . viewangles , fromAngles ) ;
}
if ( cg . time )
{
VectorCopy ( cg . refdef . vieworg , eyes ) ;
}
else
{
CalcEntitySpot ( player , SPOT_HEAD_LEAN , eyes ) ;
}
CalcEntitySpot ( ent , SPOT_ORIGIN , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_HEAD , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_LEGS , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
return qfalse ;
}
qboolean InFOV ( gentity_t * ent , gentity_t * from , int hFOV , int vFOV )
{
vec3_t eyes ;
vec3_t spot ;
vec3_t deltaVector ;
vec3_t angles , fromAngles ;
vec3_t deltaAngles ;
if ( from - > client )
{
if ( from - > client - > NPC_class ! = CLASS_RANCOR
& & from - > client - > NPC_class ! = CLASS_WAMPA
& & ! VectorCompare ( from - > client - > renderInfo . eyeAngles , vec3_origin ) )
{ //Actual facing of tag_head!
//NOTE: Stasis aliens may have a problem with this?
VectorCopy ( from - > client - > renderInfo . eyeAngles , fromAngles ) ;
}
else
{
VectorCopy ( from - > client - > ps . viewangles , fromAngles ) ;
}
}
else
{
VectorCopy ( from - > s . angles , fromAngles ) ;
}
CalcEntitySpot ( from , SPOT_HEAD_LEAN , eyes ) ;
CalcEntitySpot ( ent , SPOT_ORIGIN , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_HEAD , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
CalcEntitySpot ( ent , SPOT_LEGS , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
deltaAngles [ PITCH ] = AngleDelta ( fromAngles [ PITCH ] , angles [ PITCH ] ) ;
deltaAngles [ YAW ] = AngleDelta ( fromAngles [ YAW ] , angles [ YAW ] ) ;
if ( fabs ( deltaAngles [ PITCH ] ) < = vFOV & & fabs ( deltaAngles [ YAW ] ) < = hFOV )
{
return qtrue ;
}
return qfalse ;
}
qboolean InVisrange ( gentity_t * ent )
{ //FIXME: make a calculate visibility for ents that takes into account
//lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc.
vec3_t eyes ;
vec3_t spot ;
vec3_t deltaVector ;
float visrange = ( NPCInfo - > stats . visrange * NPCInfo - > stats . visrange ) ;
CalcEntitySpot ( NPC , SPOT_HEAD_LEAN , eyes ) ;
CalcEntitySpot ( ent , SPOT_ORIGIN , spot ) ;
VectorSubtract ( spot , eyes , deltaVector ) ;
/*if(ent->client)
{
float vel , avel ;
if ( ent - > client - > ps . velocity [ 0 ] | | ent - > client - > ps . velocity [ 1 ] | | ent - > client - > ps . velocity [ 2 ] )
{
vel = VectorLength ( ent - > client - > ps . velocity ) ;
if ( vel > 128 )
{
visrange + = visrange * ( vel / 256 ) ;
}
}
if ( ent - > avelocity [ 0 ] | | ent - > avelocity [ 1 ] | | ent - > avelocity [ 2 ] )
{ //FIXME: shouldn't they need to have line of sight to you to detect this?
avel = VectorLength ( ent - > avelocity ) ;
if ( avel > 15 )
{
visrange + = visrange * ( avel / 60 ) ;
}
}
} */
if ( VectorLengthSquared ( deltaVector ) > visrange )
{
return qfalse ;
}
return qtrue ;
}
/*
NPC_CheckVisibility
*/
visibility_t NPC_CheckVisibility ( gentity_t * ent , int flags )
{
// flags should never be 0
if ( ! flags )
{
return VIS_NOT ;
}
// check PVS
if ( flags & CHECK_PVS )
{
if ( ! gi . inPVS ( ent - > currentOrigin , NPC - > currentOrigin ) )
{
return VIS_NOT ;
}
}
if ( ! ( flags & ( CHECK_360 | CHECK_FOV | CHECK_SHOOT ) ) )
{
return VIS_PVS ;
}
// check within visrange
if ( flags & CHECK_VISRANGE )
{
if ( ! InVisrange ( ent ) )
{
return VIS_PVS ;
}
}
// check 360 degree visibility
//Meaning has to be a direct line of site
if ( flags & CHECK_360 )
{
if ( ! CanSee ( ent ) )
{
return VIS_PVS ;
}
}
if ( ! ( flags & ( CHECK_FOV | CHECK_SHOOT ) ) )
{
return VIS_360 ;
}
// check FOV
if ( flags & CHECK_FOV )
{
if ( ! InFOV ( ent , NPC , NPCInfo - > stats . hfov , NPCInfo - > stats . vfov ) )
{
return VIS_360 ;
}
}
if ( ! ( flags & CHECK_SHOOT ) )
{
return VIS_FOV ;
}
// check shootability
if ( flags & CHECK_SHOOT )
{
if ( ! CanShoot ( ent , NPC ) )
{
return VIS_FOV ;
}
}
return VIS_SHOOT ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckSoundEvents
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int G_CheckSoundEvents ( gentity_t * self , float maxHearDist , int ignoreAlert , qboolean mustHaveOwner , int minAlertLevel , qboolean onGroundOnly )
{
int bestEvent = - 1 ;
int bestAlert = - 1 ;
int bestTime = - 1 ;
float dist , radius ;
maxHearDist * = maxHearDist ;
for ( int i = 0 ; i < level . numAlertEvents ; i + + )
{
//are we purposely ignoring this alert?
if ( level . alertEvents [ i ] . ID = = ignoreAlert )
continue ;
//We're only concerned about sounds
if ( level . alertEvents [ i ] . type ! = AET_SOUND )
continue ;
//must be at least this noticable
if ( level . alertEvents [ i ] . level < minAlertLevel )
continue ;
//must have an owner?
if ( mustHaveOwner & & ! level . alertEvents [ i ] . owner )
continue ;
//must be on the ground?
if ( onGroundOnly & & ! level . alertEvents [ i ] . onGround )
continue ;
//Must be within range
dist = DistanceSquared ( level . alertEvents [ i ] . position , self - > currentOrigin ) ;
//can't hear it
if ( dist > maxHearDist )
continue ;
if ( self - > client & & self - > client - > NPC_class ! = CLASS_SAND_CREATURE )
{ //sand creatures hear all in within their earshot, regardless of quietness and alert sound radius!
radius = level . alertEvents [ i ] . radius * level . alertEvents [ i ] . radius ;
if ( dist > radius )
continue ;
if ( level . alertEvents [ i ] . addLight )
{ //a quiet sound, must have LOS to hear it
if ( G_ClearLOS ( self , level . alertEvents [ i ] . position ) = = qfalse )
{ //no LOS, didn't hear it
continue ;
}
}
}
//See if this one takes precedence over the previous one
if ( level . alertEvents [ i ] . level > = bestAlert //higher alert level
| | ( level . alertEvents [ i ] . level = = bestAlert & & level . alertEvents [ i ] . timestamp > = bestTime ) ) //same alert level, but this one is newer
{ //NOTE: equal is better because it's later in the array
bestEvent = i ;
bestAlert = level . alertEvents [ i ] . level ;
bestTime = level . alertEvents [ i ] . timestamp ;
}
}
return bestEvent ;
}
float G_GetLightLevel ( vec3_t pos , vec3_t fromDir )
{
vec3_t ambient = { 0 } , directed , lightDir ;
float lightLevel ;
cgi_R_GetLighting ( pos , ambient , directed , lightDir ) ;
lightLevel = VectorLength ( ambient ) + ( VectorLength ( directed ) * DotProduct ( lightDir , fromDir ) ) ;
return lightLevel ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckSightEvents
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int G_CheckSightEvents ( gentity_t * self , int hFOV , int vFOV , float maxSeeDist , int ignoreAlert , qboolean mustHaveOwner , int minAlertLevel )
{
int bestEvent = - 1 ;
int bestAlert = - 1 ;
int bestTime = - 1 ;
float dist , radius ;
maxSeeDist * = maxSeeDist ;
for ( int i = 0 ; i < level . numAlertEvents ; i + + )
{
//are we purposely ignoring this alert?
if ( level . alertEvents [ i ] . ID = = ignoreAlert )
continue ;
//We're only concerned about sounds
if ( level . alertEvents [ i ] . type ! = AET_SIGHT )
continue ;
//must be at least this noticable
if ( level . alertEvents [ i ] . level < minAlertLevel )
continue ;
//must have an owner?
if ( mustHaveOwner & & ! level . alertEvents [ i ] . owner )
continue ;
//Must be within range
dist = DistanceSquared ( level . alertEvents [ i ] . position , self - > currentOrigin ) ;
//can't see it
if ( dist > maxSeeDist )
continue ;
radius = level . alertEvents [ i ] . radius * level . alertEvents [ i ] . radius ;
if ( dist > radius )
continue ;
//Must be visible
if ( InFOV ( level . alertEvents [ i ] . position , self , hFOV , vFOV ) = = qfalse )
continue ;
if ( G_ClearLOS ( self , level . alertEvents [ i ] . position ) = = qfalse )
continue ;
//FIXME: possibly have the light level at this point affect the
// visibility/alert level of this event? Would also
// need to take into account how bright the event
// itself is. A lightsaber would stand out more
// in the dark... maybe pass in a light level that
// is added to the actual light level at this position?
//See if this one takes precedence over the previous one
if ( level . alertEvents [ i ] . level > = bestAlert //higher alert level
| | ( level . alertEvents [ i ] . level = = bestAlert & & level . alertEvents [ i ] . timestamp > = bestTime ) ) //same alert level, but this one is newer
{ //NOTE: equal is better because it's later in the array
bestEvent = i ;
bestAlert = level . alertEvents [ i ] . level ;
bestTime = level . alertEvents [ i ] . timestamp ;
}
}
return bestEvent ;
}
qboolean G_RememberAlertEvent ( gentity_t * self , int alertIndex )
{
if ( ! self | | ! self - > NPC )
{ //not a valid ent
return qfalse ;
}
if ( alertIndex = = - 1 )
{ //not a valid event
return qfalse ;
}
// Get The Event Struct
//----------------------
alertEvent_t & at = level . alertEvents [ alertIndex ] ;
if ( at . ID = = self - > NPC - > lastAlertID )
{ //already know this one
return qfalse ;
}
if ( at . owner = = self )
{ //don't care about events that I made
return false ;
}
self - > NPC - > lastAlertID = at . ID ;
// Now, If It Is Dangerous Enough, We Want To Register This With The Pathfinding System
//--------------------------------------------------------------------------------------
bool IsDangerous = ( at . level > = AEL_DANGER ) ;
bool IsFromNPC = ( at . owner & & at . owner - > client ) ;
bool IsFromEnemy = ( IsFromNPC & & at . owner - > client - > playerTeam ! = self - > client - > playerTeam ) ;
if ( IsDangerous & & ( ! IsFromNPC | | IsFromEnemy ) )
{
NAV : : RegisterDangerSense ( self , alertIndex ) ;
}
return qtrue ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_CheckAlertEvents
NOTE : Should all NPCs create alertEvents too so they can detect each other ?
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
int G_CheckAlertEvents ( gentity_t * self , qboolean checkSight , qboolean checkSound , float maxSeeDist , float maxHearDist , int ignoreAlert , qboolean mustHaveOwner , int minAlertLevel , qboolean onGroundOnly )
{
if ( & g_entities [ 0 ] = = NULL | | g_entities [ 0 ] . health < = 0 )
{
//player is dead
return - 1 ;
}
int bestSoundEvent = - 1 ;
int bestSightEvent = - 1 ;
int bestSoundAlert = - 1 ;
int bestSightAlert = - 1 ;
if ( checkSound )
{
//get sound event
bestSoundEvent = G_CheckSoundEvents ( self , maxHearDist , ignoreAlert , mustHaveOwner , minAlertLevel , onGroundOnly ) ;
//get sound event alert level
if ( bestSoundEvent > = 0 )
{
bestSoundAlert = level . alertEvents [ bestSoundEvent ] . level ;
}
}
if ( checkSight )
{
//get sight event
if ( self - > NPC )
{
bestSightEvent = G_CheckSightEvents ( self , self - > NPC - > stats . hfov , self - > NPC - > stats . vfov , maxSeeDist , ignoreAlert , mustHaveOwner , minAlertLevel ) ;
}
else
{
bestSightEvent = G_CheckSightEvents ( self , 80 , 80 , maxSeeDist , ignoreAlert , mustHaveOwner , minAlertLevel ) ; //FIXME: look at cg_view to get more accurate numbers?
}
//get sight event alert level
if ( bestSightEvent > = 0 )
{
bestSightAlert = level . alertEvents [ bestSightEvent ] . level ;
}
//return the one that has a higher alert (or sound if equal)
//FIXME: This doesn't take the distance of the event into account
if ( bestSightEvent > = 0 & & bestSightAlert > bestSoundAlert )
{ //valid best sight event, more important than the sound event
//get the light level of the alert event for this checker
vec3_t eyePoint , sightDir ;
//get eye point
CalcEntitySpot ( self , SPOT_HEAD_LEAN , eyePoint ) ;
VectorSubtract ( level . alertEvents [ bestSightEvent ] . position , eyePoint , sightDir ) ;
level . alertEvents [ bestSightEvent ] . light = level . alertEvents [ bestSightEvent ] . addLight + G_GetLightLevel ( level . alertEvents [ bestSightEvent ] . position , sightDir ) ;
//return the sight event
if ( G_RememberAlertEvent ( self , bestSightEvent ) )
{
return bestSightEvent ;
}
}
}
//return the sound event
if ( G_RememberAlertEvent ( self , bestSoundEvent ) )
{
return bestSoundEvent ;
}
//no event or no new event
return - 1 ;
}
int NPC_CheckAlertEvents ( qboolean checkSight , qboolean checkSound , int ignoreAlert , qboolean mustHaveOwner , int minAlertLevel , qboolean onGroundOnly )
{
return G_CheckAlertEvents ( NPC , checkSight , checkSound , NPCInfo - > stats . visrange , NPCInfo - > stats . earshot , ignoreAlert , mustHaveOwner , minAlertLevel , onGroundOnly ) ;
}
extern void WP_ForcePowerStop ( gentity_t * self , forcePowers_t forcePower ) ;
qboolean G_CheckForDanger ( gentity_t * self , int alertEvent )
{ //FIXME: more bStates need to call this?
if ( alertEvent = = - 1 )
{
return qfalse ;
}
if ( level . alertEvents [ alertEvent ] . level > = AEL_DANGER )
{ //run away!
if ( ! level . alertEvents [ alertEvent ] . owner | | ! level . alertEvents [ alertEvent ] . owner - > client | | ( level . alertEvents [ alertEvent ] . owner ! = self & & level . alertEvents [ alertEvent ] . owner - > client - > playerTeam ! = self - > client - > playerTeam ) )
{
if ( self - > NPC )
{
if ( self - > NPC - > scriptFlags & SCF_DONT_FLEE )
{ //can't flee
return qfalse ;
}
else
{
if ( level . alertEvents [ alertEvent ] . level > = AEL_DANGER_GREAT | | self - > s . weapon = = WP_NONE | | self - > s . weapon = = WP_MELEE )
{ //flee for a longer period of time
NPC_StartFlee ( level . alertEvents [ alertEvent ] . owner , level . alertEvents [ alertEvent ] . position , level . alertEvents [ alertEvent ] . level , 3000 , 6000 ) ;
}
else if ( ! Q_irand ( 0 , 10 ) ) //FIXME: base on rank? aggression?
{ //just normal danger and I have a weapon, so just a 25% chance of fleeing only for a few seconds
//FIXME: used to just find a better combat point, need that functionality back
NPC_StartFlee ( level . alertEvents [ alertEvent ] . owner , level . alertEvents [ alertEvent ] . position , level . alertEvents [ alertEvent ] . level , 1000 , 3000 ) ;
}
else
{ //didn't flee
TIMER_Set ( NPC , " duck " , 2000 ) ; // something dangerous going on...
return qfalse ;
}
return qtrue ;
}
}
else
{
return qtrue ;
}
}
}
return qfalse ;
}
qboolean NPC_CheckForDanger ( int alertEvent )
{ //FIXME: more bStates need to call this?
return G_CheckForDanger ( NPC , alertEvent ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
AddSoundEvent
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
qboolean RemoveOldestAlert ( void ) ;
void AddSoundEvent ( gentity_t * owner , vec3_t position , float radius , alertEventLevel_e alertLevel , qboolean needLOS , qboolean onGround )
{
//FIXME: Handle this in another manner?
if ( level . numAlertEvents > = MAX_ALERT_EVENTS )
{
if ( ! RemoveOldestAlert ( ) )
{ //how could that fail?
return ;
}
}
if ( owner = = NULL & & alertLevel < AEL_DANGER ) //allows un-owned danger alerts
return ;
//FIXME: why are Sand creatures suddenly crashlanding?
if ( owner & & owner - > client & & owner - > client - > NPC_class = = CLASS_SAND_CREATURE )
{
return ;
}
//FIXME: if owner is not a player or player ally, and there are no player allies present,
// perhaps we don't need to store the alert... unless we want the player to
// react to enemy alert events in some way?
# ifdef _DEBUG
2013-04-22 11:30:27 +00:00
assert ( ! Q_isnan ( position [ 0 ] ) & & ! Q_isnan ( position [ 1 ] ) & & ! Q_isnan ( position [ 2 ] ) ) ;
2013-04-19 02:52:48 +00:00
# endif
VectorCopy ( position , level . alertEvents [ level . numAlertEvents ] . position ) ;
level . alertEvents [ level . numAlertEvents ] . radius = radius ;
level . alertEvents [ level . numAlertEvents ] . level = alertLevel ;
level . alertEvents [ level . numAlertEvents ] . type = AET_SOUND ;
level . alertEvents [ level . numAlertEvents ] . owner = owner ;
if ( needLOS )
{ //a very low-level sound, when check this sound event, check for LOS
level . alertEvents [ level . numAlertEvents ] . addLight = 1 ; //will force an LOS trace on this sound
}
else
{
level . alertEvents [ level . numAlertEvents ] . addLight = 0 ; //will force an LOS trace on this sound
}
level . alertEvents [ level . numAlertEvents ] . onGround = onGround ;
level . alertEvents [ level . numAlertEvents ] . ID = + + level . curAlertID ;
level . alertEvents [ level . numAlertEvents ] . timestamp = level . time ;
level . numAlertEvents + + ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
AddSightEvent
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void AddSightEvent ( gentity_t * owner , vec3_t position , float radius , alertEventLevel_e alertLevel , float addLight )
{
//FIXME: Handle this in another manner?
if ( level . numAlertEvents > = MAX_ALERT_EVENTS )
{
if ( ! RemoveOldestAlert ( ) )
{ //how could that fail?
return ;
}
}
if ( owner = = NULL & & alertLevel < AEL_DANGER ) //allows un-owned danger alerts
return ;
//FIXME: if owner is not a player or player ally, and there are no player allies present,
// perhaps we don't need to store the alert... unless we want the player to
// react to enemy alert events in some way?
# ifdef _DEBUG
2013-04-22 11:30:27 +00:00
assert ( ! Q_isnan ( position [ 0 ] ) & & ! Q_isnan ( position [ 1 ] ) & & ! Q_isnan ( position [ 2 ] ) ) ;
2013-04-19 02:52:48 +00:00
# endif
VectorCopy ( position , level . alertEvents [ level . numAlertEvents ] . position ) ;
level . alertEvents [ level . numAlertEvents ] . radius = radius ;
level . alertEvents [ level . numAlertEvents ] . level = alertLevel ;
level . alertEvents [ level . numAlertEvents ] . type = AET_SIGHT ;
level . alertEvents [ level . numAlertEvents ] . owner = owner ;
level . alertEvents [ level . numAlertEvents ] . addLight = addLight ; //will get added to actual light at that point when it's checked
level . alertEvents [ level . numAlertEvents ] . ID = level . curAlertID + + ;
level . alertEvents [ level . numAlertEvents ] . timestamp = level . time ;
level . numAlertEvents + + ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
ClearPlayerAlertEvents
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
void ClearPlayerAlertEvents ( void )
{
int curNumAlerts = level . numAlertEvents ;
//loop through them all (max 32)
for ( int i = 0 ; i < curNumAlerts ; i + + )
{
//see if the event is old enough to delete
if ( level . alertEvents [ i ] . timestamp & & level . alertEvents [ i ] . timestamp + ALERT_CLEAR_TIME < level . time )
{ //this event has timed out
//drop the count
level . numAlertEvents - - ;
//shift the rest down
if ( level . numAlertEvents > 0 )
{ //still have more in the array
if ( ( i + 1 ) < MAX_ALERT_EVENTS )
{
memmove ( & level . alertEvents [ i ] , & level . alertEvents [ i + 1 ] , sizeof ( alertEvent_t ) * ( MAX_ALERT_EVENTS - ( i + 1 ) ) ) ;
}
}
else
{ //just clear this one... or should we clear the whole array?
memset ( & level . alertEvents [ i ] , 0 , sizeof ( alertEvent_t ) ) ;
}
}
}
//make sure this never drops below zero... if it does, something very very bad happened
assert ( level . numAlertEvents > = 0 ) ;
if ( eventClearTime < level . time )
{ //this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms
eventClearTime = level . time + ALERT_CLEAR_TIME ;
}
}
qboolean RemoveOldestAlert ( void )
{
int oldestEvent = - 1 , oldestTime = Q3_INFINITE ;
//loop through them all (max 32)
for ( int i = 0 ; i < level . numAlertEvents ; i + + )
{
//see if the event is old enough to delete
if ( level . alertEvents [ i ] . timestamp < oldestTime )
{
oldestEvent = i ;
oldestTime = level . alertEvents [ i ] . timestamp ;
}
}
if ( oldestEvent ! = - 1 )
{
//drop the count
level . numAlertEvents - - ;
//shift the rest down
if ( level . numAlertEvents > 0 )
{ //still have more in the array
if ( ( oldestEvent + 1 ) < MAX_ALERT_EVENTS )
{
memmove ( & level . alertEvents [ oldestEvent ] , & level . alertEvents [ oldestEvent + 1 ] , sizeof ( alertEvent_t ) * ( MAX_ALERT_EVENTS - ( oldestEvent + 1 ) ) ) ;
}
}
else
{ //just clear this one... or should we clear the whole array?
memset ( & level . alertEvents [ oldestEvent ] , 0 , sizeof ( alertEvent_t ) ) ;
}
}
//make sure this never drops below zero... if it does, something very very bad happened
assert ( level . numAlertEvents > = 0 ) ;
//return true is have room for one now
return ( level . numAlertEvents < MAX_ALERT_EVENTS ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
G_ClearLOS
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
// Position to position
qboolean G_ClearLOS ( gentity_t * self , const vec3_t start , const vec3_t end )
{
trace_t tr ;
int traceCount = 0 ;
//FIXME: ENTITYNUM_NONE ok?
gi . trace ( & tr , start , NULL , NULL , end , ENTITYNUM_NONE , CONTENTS_OPAQUE /*CONTENTS_SOLID*/ /*(CONTENTS_SOLID|CONTENTS_MONSTERCLIP)*/ ) ;
while ( tr . fraction < 1.0 & & traceCount < 3 )
{ //can see through 3 panes of glass
if ( tr . entityNum < ENTITYNUM_WORLD )
{
if ( & g_entities [ tr . entityNum ] ! = NULL & & ( g_entities [ tr . entityNum ] . svFlags & SVF_GLASS_BRUSH ) )
{ //can see through glass, trace again, ignoring me
gi . trace ( & tr , tr . endpos , NULL , NULL , end , tr . entityNum , MASK_OPAQUE ) ;
traceCount + + ;
continue ;
}
}
return qfalse ;
}
if ( tr . fraction = = 1.0 )
return qtrue ;
return qfalse ;
}
//Entity to position
qboolean G_ClearLOS ( gentity_t * self , gentity_t * ent , const vec3_t end )
{
vec3_t eyes ;
CalcEntitySpot ( ent , SPOT_HEAD_LEAN , eyes ) ;
return G_ClearLOS ( self , eyes , end ) ;
}
//Position to entity
qboolean G_ClearLOS ( gentity_t * self , const vec3_t start , gentity_t * ent )
{
vec3_t spot ;
//Look for the chest first
CalcEntitySpot ( ent , SPOT_ORIGIN , spot ) ;
if ( G_ClearLOS ( self , start , spot ) )
return qtrue ;
//Look for the head next
CalcEntitySpot ( ent , SPOT_HEAD_LEAN , spot ) ;
if ( G_ClearLOS ( self , start , spot ) )
return qtrue ;
return qfalse ;
}
//NPC's eyes to entity
qboolean G_ClearLOS ( gentity_t * self , gentity_t * ent )
{
vec3_t eyes ;
//Calculate my position
CalcEntitySpot ( self , SPOT_HEAD_LEAN , eyes ) ;
return G_ClearLOS ( self , eyes , ent ) ;
}
//NPC's eyes to position
qboolean G_ClearLOS ( gentity_t * self , const vec3_t end )
{
vec3_t eyes ;
//Calculate the my position
CalcEntitySpot ( self , SPOT_HEAD_LEAN , eyes ) ;
return G_ClearLOS ( self , eyes , end ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_GetFOVPercentage
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
float NPC_GetHFOVPercentage ( vec3_t spot , vec3_t from , vec3_t facing , float hFOV )
{
vec3_t deltaVector , angles ;
float delta ;
VectorSubtract ( spot , from , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
delta = fabs ( AngleDelta ( facing [ YAW ] , angles [ YAW ] ) ) ;
if ( delta > hFOV )
return 0.0f ;
return ( ( hFOV - delta ) / hFOV ) ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_GetVFOVPercentage
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
float NPC_GetVFOVPercentage ( vec3_t spot , vec3_t from , vec3_t facing , float vFOV )
{
vec3_t deltaVector , angles ;
float delta ;
VectorSubtract ( spot , from , deltaVector ) ;
vectoangles ( deltaVector , angles ) ;
delta = fabs ( AngleDelta ( facing [ PITCH ] , angles [ PITCH ] ) ) ;
if ( delta > vFOV )
return 0.0f ;
return ( ( vFOV - delta ) / vFOV ) ;
}
# define MAX_INTEREST_DIST ( 256 * 256 )
/*
- - - - - - - - - - - - - - - - - - - - - - - - -
NPC_FindLocalInterestPoint
- - - - - - - - - - - - - - - - - - - - - - - - -
*/
int G_FindLocalInterestPoint ( gentity_t * self )
{
int i , bestPoint = ENTITYNUM_NONE ;
float dist , bestDist = Q3_INFINITE ;
vec3_t diffVec , eyes ;
CalcEntitySpot ( self , SPOT_HEAD_LEAN , eyes ) ;
for ( i = 0 ; i < level . numInterestPoints ; i + + )
{
//Don't ignore portals? If through a portal, need to look at portal!
if ( gi . inPVS ( level . interestPoints [ i ] . origin , eyes ) )
{
VectorSubtract ( level . interestPoints [ i ] . origin , eyes , diffVec ) ;
if ( ( fabs ( diffVec [ 0 ] ) + fabs ( diffVec [ 1 ] ) ) / 2 < 48 & &
fabs ( diffVec [ 2 ] ) > ( fabs ( diffVec [ 0 ] ) + fabs ( diffVec [ 1 ] ) ) / 2 )
{ //Too close to look so far up or down
continue ;
}
dist = VectorLengthSquared ( diffVec ) ;
//Some priority to more interesting points
//dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5);
if ( dist < MAX_INTEREST_DIST & & dist < bestDist )
{
if ( G_ClearLineOfSight ( eyes , level . interestPoints [ i ] . origin , self - > s . number , MASK_OPAQUE ) )
{
bestDist = dist ;
bestPoint = i ;
}
}
}
}
if ( bestPoint ! = ENTITYNUM_NONE & & level . interestPoints [ bestPoint ] . target )
{
G_UseTargets2 ( self , self , level . interestPoints [ bestPoint ] . target ) ;
}
return bestPoint ;
}
/*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4)
A point that a squadmate will look at if standing still
target - thing to fire when someone looks at this thing
*/
void SP_target_interest ( gentity_t * self )
{ //FIXME: rename point_interest
if ( level . numInterestPoints > = MAX_INTEREST_POINTS )
{
gi . Printf ( " ERROR: Too many interest points, limit is %d \n " , MAX_INTEREST_POINTS ) ;
G_FreeEntity ( self ) ;
return ;
}
VectorCopy ( self - > currentOrigin , level . interestPoints [ level . numInterestPoints ] . origin ) ;
if ( self - > target & & self - > target [ 0 ] )
{
level . interestPoints [ level . numInterestPoints ] . target = G_NewString ( self - > target ) ;
}
level . numInterestPoints + + ;
G_FreeEntity ( self ) ;
}