stvoy-sp-sdk/game/NPC_senses.cpp

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 );
}