123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107 |
- //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
- assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) );
- #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
- assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) );
- #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);
- }
|