623 lines
14 KiB
C++
623 lines
14 KiB
C++
//NPC_senses.cpp
|
|
#include "b_local.h"
|
|
|
|
/*
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
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 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( VectorLengthSquared( from->client->renderInfo.eyeAngles ) )
|
|
{//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
|
|
-------------------------
|
|
*/
|
|
|
|
int NPC_CheckSoundEvents( void )
|
|
{
|
|
int bestEvent = -1;
|
|
int bestAlert = -1;
|
|
|
|
for ( int i = 0; i < level.numAlertEvents; i++ )
|
|
{
|
|
//We're only concerned about sounds
|
|
if ( level.alertEvents[i].type != AET_SOUND )
|
|
continue;
|
|
|
|
//Must be within range
|
|
int radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
|
|
|
|
if ( DistanceSquared( level.alertEvents[i].position, NPC->currentOrigin ) > radius )
|
|
continue;
|
|
|
|
//See if this one takes precedence over the previous one
|
|
if ( level.alertEvents[i].level > bestAlert )
|
|
{
|
|
bestEvent = i;
|
|
bestAlert = level.alertEvents[i].level;
|
|
}
|
|
}
|
|
|
|
return bestEvent;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_CheckSightEvents
|
|
-------------------------
|
|
*/
|
|
|
|
int NPC_CheckSightEvents( void )
|
|
{
|
|
int bestEvent = -1;
|
|
int bestAlert = -1;
|
|
|
|
for ( int i = 0; i < level.numAlertEvents; i++ )
|
|
{
|
|
//We're only concerned about sounds
|
|
if ( level.alertEvents[i].type != AET_SIGHT )
|
|
continue;
|
|
|
|
//Must be within range
|
|
int radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
|
|
|
|
if ( DistanceSquared( level.alertEvents[i].position, NPC->currentOrigin ) > radius )
|
|
continue;
|
|
|
|
//Must be visible
|
|
if ( InFOV( level.alertEvents[i].position, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
|
|
continue;
|
|
|
|
if ( NPC_ClearLOS( level.alertEvents[i].position ) == qfalse )
|
|
continue;
|
|
|
|
//See if this one takes precedence over the previous one
|
|
if ( level.alertEvents[i].level > bestAlert )
|
|
{
|
|
bestEvent = i;
|
|
bestAlert = level.alertEvents[i].level;
|
|
}
|
|
}
|
|
|
|
return bestEvent;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_CheckAlertEvents
|
|
|
|
NOTE: Should all NPCs create alertEvents too so they can detect each other?
|
|
-------------------------
|
|
*/
|
|
|
|
int NPC_CheckAlertEvents( void )
|
|
{
|
|
int bestSoundEvent = -1;
|
|
int bestSightEvent = -1;
|
|
|
|
bestSoundEvent = NPC_CheckSoundEvents();
|
|
bestSightEvent = NPC_CheckSightEvents();
|
|
|
|
//FIXME: This doesn't take the relavence of the event into account,
|
|
// so an important event far away will override a lesser one close up!
|
|
|
|
return ( bestSoundEvent >= bestSightEvent ) ? bestSoundEvent : bestSightEvent;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
AddSoundEvent
|
|
-------------------------
|
|
*/
|
|
|
|
void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel )
|
|
{
|
|
//FIXME: Handle this in another manner?
|
|
if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
|
|
return;
|
|
|
|
if ( owner == NULL )
|
|
return;
|
|
|
|
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;
|
|
|
|
level.numAlertEvents++;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
AddSightEvent
|
|
-------------------------
|
|
*/
|
|
|
|
void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel )
|
|
{
|
|
//FIXME: Handle this in another manner?
|
|
if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
|
|
return;
|
|
|
|
if ( owner == NULL )
|
|
return;
|
|
|
|
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.numAlertEvents++;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ClearPlayerAlertEvents
|
|
-------------------------
|
|
*/
|
|
|
|
void ClearPlayerAlertEvents( void )
|
|
{
|
|
level.numAlertEvents = 0;
|
|
}
|
|
|
|
//NOTENOTE: Sigh... the lengths I go to making things simple...
|
|
/*
|
|
-------------------------
|
|
NPC_ClearLOS
|
|
-------------------------
|
|
*/
|
|
|
|
// Position to position
|
|
|
|
qboolean NPC_ClearLOS( vec3_t start, vec3_t end )
|
|
{
|
|
trace_t tr;
|
|
|
|
//FIXME: ENTITYNUM_NONE ok?
|
|
gi.trace ( &tr, start, NULL, NULL, end, ENTITYNUM_NONE, CONTENTS_SOLID/*(CONTENTS_SOLID|CONTENTS_MONSTERCLIP)*/ );
|
|
|
|
if ( tr.fraction == 1.0 )
|
|
return qtrue;
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//Entity to position
|
|
|
|
qboolean NPC_ClearLOS( gentity_t *ent, vec3_t end )
|
|
{
|
|
vec3_t eyes;
|
|
|
|
CalcEntitySpot( ent, SPOT_HEAD, eyes );
|
|
|
|
return NPC_ClearLOS( eyes, end );
|
|
}
|
|
|
|
//Position to entity
|
|
|
|
qboolean NPC_ClearLOS( vec3_t start, gentity_t *ent )
|
|
{
|
|
vec3_t spot;
|
|
|
|
//Look for the chest first
|
|
CalcEntitySpot( ent, SPOT_ORIGIN, spot );
|
|
|
|
if ( NPC_ClearLOS( start, spot ) )
|
|
return qtrue;
|
|
|
|
//Look for the head next
|
|
CalcEntitySpot( ent, SPOT_HEAD, spot );
|
|
|
|
if ( NPC_ClearLOS( start, spot ) )
|
|
return qtrue;
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//NPC's eyes to entity
|
|
|
|
qboolean NPC_ClearLOS( gentity_t *ent )
|
|
{
|
|
vec3_t eyes;
|
|
|
|
//Calculate the NPC's position
|
|
CalcEntitySpot( NPC, SPOT_HEAD, eyes );
|
|
|
|
return NPC_ClearLOS( eyes, ent );
|
|
}
|
|
|
|
//NPC's eyes to position
|
|
|
|
qboolean NPC_ClearLOS( vec3_t end )
|
|
{
|
|
vec3_t eyes;
|
|
|
|
//Calculate the NPC's position
|
|
CalcEntitySpot( NPC, SPOT_HEAD, eyes );
|
|
|
|
return NPC_ClearLOS( eyes, end );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_ClearShot
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NPC_ClearShot( gentity_t *ent )
|
|
{
|
|
if ( ( NPC == NULL ) || ( ent == NULL ) )
|
|
return qfalse;
|
|
|
|
vec3_t muzzle;
|
|
trace_t tr;
|
|
|
|
CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
|
|
|
|
//Hack for scavenger aim error
|
|
if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
|
|
{
|
|
vec3_t mins = { -2, -2, -2 };
|
|
vec3_t maxs = { 2, 2, 2 };
|
|
|
|
gi.trace ( &tr, muzzle, mins, maxs, ent->currentOrigin, NPC->s.number, MASK_SHOT );
|
|
}
|
|
else
|
|
{
|
|
gi.trace ( &tr, muzzle, NULL, NULL, ent->currentOrigin, NPC->s.number, MASK_SHOT );
|
|
}
|
|
|
|
if ( tr.entityNum == ent->s.number )
|
|
return qtrue;
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
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 );
|
|
}
|