codemp/game/NPC_senses.c

Go to the documentation of this file.
00001 //NPC_senses.cpp
00002 
00003 #include "b_local.h"
00004 
00005 extern int eventClearTime;
00006 /*
00007 qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask)
00008 
00009 returns true if can see from point 1 to 2, even through glass (1 pane)- doesn't work with portals
00010 */
00011 qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask)
00012 {
00013         trace_t         tr;
00014         gentity_t       *hit;
00015 
00016         trap_Trace ( &tr, point1, NULL, NULL, point2, ignore, clipmask );
00017         if ( tr.fraction == 1.0 ) 
00018         {
00019                 return qtrue;
00020         }
00021 
00022         hit = &g_entities[ tr.entityNum ];
00023         if(EntIsGlass(hit))
00024         {
00025                 vec3_t  newpoint1;
00026                 VectorCopy(tr.endpos, newpoint1);
00027                 trap_Trace (&tr, newpoint1, NULL, NULL, point2, hit->s.number, clipmask );
00028 
00029                 if ( tr.fraction == 1.0 ) 
00030                 {
00031                         return qtrue;
00032                 }
00033         }
00034 
00035         return qfalse;
00036 }
00037 
00038 /*
00039 CanSee
00040 determine if NPC can see an entity
00041 
00042 This is a straight line trace check.  This function does not look at PVS or FOV,
00043 or take any AI related factors (for example, the NPC's reaction time) into account
00044 
00045 FIXME do we need fat and thin version of this?
00046 */
00047 qboolean CanSee ( gentity_t *ent ) 
00048 {
00049         trace_t         tr;
00050         vec3_t          eyes;
00051         vec3_t          spot;
00052 
00053         CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
00054 
00055         CalcEntitySpot( ent, SPOT_ORIGIN, spot );
00056         trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
00057         ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
00058         if ( tr.fraction == 1.0 ) 
00059         {
00060                 return qtrue;
00061         }
00062 
00063         CalcEntitySpot( ent, SPOT_HEAD, spot );
00064         trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
00065         ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
00066         if ( tr.fraction == 1.0 ) 
00067         {
00068                 return qtrue;
00069         }
00070 
00071         CalcEntitySpot( ent, SPOT_LEGS, spot );
00072         trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
00073         ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
00074         if ( tr.fraction == 1.0 ) 
00075         {
00076                 return qtrue;
00077         }
00078 
00079         return qfalse;
00080 }
00081 
00082 qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold )
00083 {
00084         vec3_t  dir, forward, angles;
00085         float   dot;
00086 
00087         VectorSubtract( spot, from, dir );
00088         dir[2] = 0;
00089         VectorNormalize( dir );
00090 
00091         VectorCopy( fromAngles, angles );
00092         angles[0] = 0;
00093         AngleVectors( angles, forward, NULL, NULL );
00094 
00095         dot = DotProduct( dir, forward );
00096 
00097         return (dot > threshHold);
00098 }
00099 
00100 /*
00101 InFOV
00102 
00103 IDEA: further off to side of FOV range, higher chance of failing even if technically in FOV,
00104         keep core of 50% to sides as always succeeding
00105 */
00106 
00107 //Position compares
00108 
00109 qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV )
00110 {
00111         vec3_t  deltaVector, angles, deltaAngles;
00112 
00113         VectorSubtract ( spot, from, deltaVector );
00114         vectoangles ( deltaVector, angles );
00115         
00116         deltaAngles[PITCH]      = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
00117         deltaAngles[YAW]        = AngleDelta ( fromAngles[YAW], angles[YAW] );
00118 
00119         if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) 
00120         {
00121                 return qtrue;
00122         }
00123 
00124         return qfalse;
00125 }
00126 
00127 //NPC to position
00128 
00129 qboolean InFOV2( vec3_t origin, gentity_t *from, int hFOV, int vFOV ) 
00130 {
00131         vec3_t  fromAngles, eyes;
00132 
00133         if( from->client )
00134         {
00135                 VectorCopy(from->client->ps.viewangles, fromAngles);
00136         }
00137         else
00138         {
00139                 VectorCopy(from->s.angles, fromAngles);
00140         }
00141 
00142         CalcEntitySpot( from, SPOT_HEAD, eyes );
00143 
00144         return InFOV3( origin, eyes, fromAngles, hFOV, vFOV );
00145 }
00146 
00147 //Entity to entity
00148 
00149 qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ) 
00150 {
00151         vec3_t  eyes;
00152         vec3_t  spot;
00153         vec3_t  deltaVector;
00154         vec3_t  angles, fromAngles;
00155         vec3_t  deltaAngles;
00156 
00157         if( from->client )
00158         {
00159                 if( !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) )
00160                 {//Actual facing of tag_head!
00161                         //NOTE: Stasis aliens may have a problem with this?
00162                         VectorCopy( from->client->renderInfo.eyeAngles, fromAngles );
00163                 }
00164                 else
00165                 {
00166                         VectorCopy( from->client->ps.viewangles, fromAngles );
00167                 }
00168         }
00169         else
00170         {
00171                 VectorCopy(from->s.angles, fromAngles);
00172         }
00173 
00174         CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes );
00175 
00176         CalcEntitySpot( ent, SPOT_ORIGIN, spot );
00177         VectorSubtract ( spot, eyes, deltaVector);
00178 
00179         vectoangles ( deltaVector, angles );
00180         deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
00181         deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
00182         if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) 
00183         {
00184                 return qtrue;
00185         }
00186 
00187         CalcEntitySpot( ent, SPOT_HEAD, spot );
00188         VectorSubtract ( spot, eyes, deltaVector);
00189         vectoangles ( deltaVector, angles );
00190         deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
00191         deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
00192         if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) 
00193         {
00194                 return qtrue;
00195         }
00196 
00197         CalcEntitySpot( ent, SPOT_LEGS, spot );
00198         VectorSubtract ( spot, eyes, deltaVector);
00199         vectoangles ( deltaVector, angles );
00200         deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
00201         deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
00202         if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) 
00203         {
00204                 return qtrue;
00205         }
00206 
00207         return qfalse;
00208 }
00209 
00210 qboolean InVisrange ( gentity_t *ent ) 
00211 {//FIXME: make a calculate visibility for ents that takes into account
00212         //lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc.
00213         vec3_t  eyes;
00214         vec3_t  spot;
00215         vec3_t  deltaVector;
00216         float   visrange = (NPCInfo->stats.visrange*NPCInfo->stats.visrange);
00217 
00218         CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
00219 
00220         CalcEntitySpot( ent, SPOT_ORIGIN, spot );
00221         VectorSubtract ( spot, eyes, deltaVector);
00222 
00223         /*if(ent->client)
00224         {
00225                 float   vel, avel;
00226                 if(ent->client->ps.velocity[0] || ent->client->ps.velocity[1] || ent->client->ps.velocity[2])
00227                 {
00228                         vel = VectorLength(ent->client->ps.velocity);
00229                         if(vel > 128)
00230                         {
00231                                 visrange += visrange * (vel/256);
00232                         }
00233                 }
00234 
00235                 if(ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
00236                 {//FIXME: shouldn't they need to have line of sight to you to detect this?
00237                         avel = VectorLength(ent->avelocity);
00238                         if(avel > 15)
00239                         {
00240                                 visrange += visrange * (avel/60);
00241                         }
00242                 }
00243         }*/
00244 
00245         if(VectorLengthSquared(deltaVector) > visrange)
00246         {
00247                 return qfalse;
00248         }
00249 
00250         return qtrue;
00251 }
00252 
00253 /*
00254 NPC_CheckVisibility
00255 */
00256 
00257 visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ) 
00258 {
00259         // flags should never be 0
00260         if ( !flags ) 
00261         {
00262                 return VIS_NOT;
00263         }
00264 
00265         // check PVS
00266         if ( flags & CHECK_PVS ) 
00267         {
00268                 if ( !trap_InPVS ( ent->r.currentOrigin, NPC->r.currentOrigin ) ) 
00269                 {
00270                         return VIS_NOT;
00271                 }
00272         }
00273         if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) ) 
00274         {
00275                 return VIS_PVS;
00276         }
00277 
00278         // check within visrange
00279         if (flags & CHECK_VISRANGE)
00280         {
00281                 if( !InVisrange ( ent ) ) 
00282                 {
00283                         return VIS_PVS;
00284                 }
00285         }
00286 
00287         // check 360 degree visibility
00288         //Meaning has to be a direct line of site
00289         if ( flags & CHECK_360 ) 
00290         {
00291                 if ( !CanSee ( ent ) ) 
00292                 {
00293                         return VIS_PVS;
00294                 }
00295         }
00296         if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) ) 
00297         {
00298                 return VIS_360;
00299         }
00300 
00301         // check FOV
00302         if ( flags & CHECK_FOV ) 
00303         {
00304                 if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) ) 
00305                 {
00306                         return VIS_360;
00307                 }
00308         }
00309 
00310         if ( !(flags & CHECK_SHOOT) ) 
00311         {
00312                 return VIS_FOV;
00313         }
00314 
00315         // check shootability
00316         if ( flags & CHECK_SHOOT ) 
00317         {
00318                 if ( !CanShoot ( ent, NPC ) ) 
00319                 {
00320                         return VIS_FOV;
00321                 }
00322         }
00323 
00324         return VIS_SHOOT;
00325 }
00326 
00327 /*
00328 -------------------------
00329 NPC_CheckSoundEvents
00330 -------------------------
00331 */
00332 static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel )
00333 {
00334         int     bestEvent = -1;
00335         int bestAlert = -1;
00336         int     bestTime = -1;
00337         int i;
00338         float dist, radius;
00339 
00340         maxHearDist *= maxHearDist;
00341 
00342         for ( i = 0; i < level.numAlertEvents; i++ )
00343         {
00344                 //are we purposely ignoring this alert?
00345                 if ( i == ignoreAlert )
00346                         continue;
00347                 //We're only concerned about sounds
00348                 if ( level.alertEvents[i].type != AET_SOUND )
00349                         continue;
00350                 //must be at least this noticable
00351                 if ( level.alertEvents[i].level < minAlertLevel )
00352                         continue;
00353                 //must have an owner?
00354                 if ( mustHaveOwner && !level.alertEvents[i].owner )
00355                         continue;
00356                 //Must be within range
00357                 dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin );
00358 
00359                 //can't hear it
00360                 if ( dist > maxHearDist )
00361                         continue;
00362 
00363                 radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
00364                 if ( dist > radius )
00365                         continue;
00366 
00367                 if ( level.alertEvents[i].addLight )
00368                 {//a quiet sound, must have LOS to hear it
00369                         if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse )
00370                         {//no LOS, didn't hear it
00371                                 continue;
00372                         }
00373                 }
00374 
00375                 //See if this one takes precedence over the previous one
00376                 if ( level.alertEvents[i].level >= bestAlert //higher alert level
00377                         || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer
00378                 {//NOTE: equal is better because it's later in the array
00379                         bestEvent = i;
00380                         bestAlert = level.alertEvents[i].level;
00381                         bestTime = level.alertEvents[i].timestamp;
00382                 }
00383         }
00384 
00385         return bestEvent;
00386 }
00387 
00388 float G_GetLightLevel( vec3_t pos, vec3_t fromDir )
00389 {
00390         /*
00391         vec3_t  ambient={0}, directed, lightDir;
00392 
00393         cgi_R_GetLighting( pos, ambient, directed, lightDir );
00394         lightLevel = VectorLength( ambient ) + (VectorLength( directed )*DotProduct( lightDir, fromDir ));
00395         */
00396         float   lightLevel;
00397         //rwwFIXMEFIXME: ...this is evil. We can possibly read from the server BSP data, or load the lightmap along
00398         //with collision data and whatnot, but is it worth it?
00399         lightLevel = 255;
00400 
00401         return lightLevel;
00402 }
00403 /*
00404 -------------------------
00405 NPC_CheckSightEvents
00406 -------------------------
00407 */
00408 static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel )
00409 {
00410         int     bestEvent = -1;
00411         int bestAlert = -1;
00412         int     bestTime = -1;
00413         int i;
00414         float   dist, radius;
00415 
00416         maxSeeDist *= maxSeeDist;
00417         for ( i = 0; i < level.numAlertEvents; i++ )
00418         {
00419                 //are we purposely ignoring this alert?
00420                 if ( i == ignoreAlert )
00421                         continue;
00422                 //We're only concerned about sounds
00423                 if ( level.alertEvents[i].type != AET_SIGHT )
00424                         continue;
00425                 //must be at least this noticable
00426                 if ( level.alertEvents[i].level < minAlertLevel )
00427                         continue;
00428                 //must have an owner?
00429                 if ( mustHaveOwner && !level.alertEvents[i].owner )
00430                         continue;
00431 
00432                 //Must be within range
00433                 dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin );
00434 
00435                 //can't see it
00436                 if ( dist > maxSeeDist )
00437                         continue;
00438 
00439                 radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
00440                 if ( dist > radius )
00441                         continue;
00442 
00443                 //Must be visible
00444                 if ( InFOV2( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse )
00445                         continue;
00446 
00447                 if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse )
00448                         continue;
00449 
00450                 //FIXME: possibly have the light level at this point affect the 
00451                 //                      visibility/alert level of this event?  Would also
00452                 //                      need to take into account how bright the event
00453                 //                      itself is.  A lightsaber would stand out more
00454                 //                      in the dark... maybe pass in a light level that
00455                 //                      is added to the actual light level at this position?
00456 
00457                 //See if this one takes precedence over the previous one
00458                 if ( level.alertEvents[i].level >= bestAlert //higher alert level
00459                         || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer
00460                 {//NOTE: equal is better because it's later in the array
00461                         bestEvent = i;
00462                         bestAlert = level.alertEvents[i].level;
00463                         bestTime = level.alertEvents[i].timestamp;
00464                 }
00465         }
00466 
00467         return bestEvent;
00468 }
00469 
00470 /*
00471 -------------------------
00472 NPC_CheckAlertEvents
00473 
00474     NOTE: Should all NPCs create alertEvents too so they can detect each other?
00475 -------------------------
00476 */
00477 
00478 int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel )
00479 {
00480         int bestSoundEvent = -1;
00481         int bestSightEvent = -1;
00482         int bestSoundAlert = -1;
00483         int bestSightAlert = -1;
00484 
00485         if ( &g_entities[0] == NULL || g_entities[0].health <= 0 )
00486         {
00487                 //player is dead
00488                 return -1;
00489         }
00490 
00491         //get sound event
00492         bestSoundEvent = G_CheckSoundEvents( self, maxHearDist, ignoreAlert, mustHaveOwner, minAlertLevel );
00493         //get sound event alert level
00494         if ( bestSoundEvent >= 0 )
00495         {
00496                 bestSoundAlert = level.alertEvents[bestSoundEvent].level;
00497         }
00498 
00499         //get sight event
00500         if ( self->NPC )
00501         {
00502                 bestSightEvent = G_CheckSightEvents( self, self->NPC->stats.hfov, self->NPC->stats.vfov, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );
00503         }
00504         else
00505         {
00506                 bestSightEvent = G_CheckSightEvents( self, 80, 80, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );//FIXME: look at cg_view to get more accurate numbers?
00507         }
00508         //get sight event alert level
00509         if ( bestSightEvent >= 0 )
00510         {
00511                 bestSightAlert = level.alertEvents[bestSightEvent].level;
00512         }
00513 
00514         //return the one that has a higher alert (or sound if equal)
00515         //FIXME:        This doesn't take the distance of the event into account
00516 
00517         if ( bestSightEvent >= 0 && bestSightAlert > bestSoundAlert )
00518         {//valid best sight event, more important than the sound event
00519                 //get the light level of the alert event for this checker
00520                 vec3_t  eyePoint, sightDir;
00521                 //get eye point
00522                 CalcEntitySpot( self, SPOT_HEAD_LEAN, eyePoint );
00523                 VectorSubtract( level.alertEvents[bestSightEvent].position, eyePoint, sightDir );
00524                 level.alertEvents[bestSightEvent].light = level.alertEvents[bestSightEvent].addLight + G_GetLightLevel( level.alertEvents[bestSightEvent].position, sightDir );
00525                 //return the sight event
00526                 return bestSightEvent;
00527         }
00528         //return the sound event
00529         return bestSoundEvent;
00530 }
00531 
00532 int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel )
00533 {
00534         return G_CheckAlertEvents( NPC, checkSight, checkSound, NPCInfo->stats.visrange, NPCInfo->stats.earshot, ignoreAlert, mustHaveOwner, minAlertLevel );
00535 }
00536 
00537 qboolean G_CheckForDanger( gentity_t *self, int alertEvent )
00538 {//FIXME: more bStates need to call this?
00539         if ( alertEvent == -1 )
00540         {
00541                 return qfalse;
00542         }
00543 
00544         if ( level.alertEvents[alertEvent].level >= AEL_DANGER )
00545         {//run away!
00546                 if ( !level.alertEvents[alertEvent].owner || !level.alertEvents[alertEvent].owner->client || (level.alertEvents[alertEvent].owner!=self&&level.alertEvents[alertEvent].owner->client->playerTeam!=self->client->playerTeam) )
00547                 {
00548                         if ( self->NPC )
00549                         {
00550                                 if ( self->NPC->scriptFlags & SCF_DONT_FLEE )
00551                                 {//can't flee
00552                                         return qfalse;
00553                                 }
00554                                 else
00555                                 {
00556                                         NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 3000, 6000 );
00557                                         return qtrue;
00558                                 }
00559                         }
00560                         else
00561                         {
00562                                 return qtrue;
00563                         }
00564                 }
00565         }
00566         return qfalse;
00567 }
00568 qboolean NPC_CheckForDanger( int alertEvent )
00569 {//FIXME: more bStates need to call this?
00570         return G_CheckForDanger( NPC, alertEvent );
00571 }
00572 
00573 /*
00574 -------------------------
00575 AddSoundEvent
00576 -------------------------
00577 */
00578 qboolean RemoveOldestAlert( void );
00579 void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS )
00580 {
00581         //FIXME: Handle this in another manner?
00582         if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
00583         {
00584                 if ( !RemoveOldestAlert() )
00585                 {//how could that fail?
00586                         return;
00587                 }
00588         }
00589         
00590         if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts
00591                 return;
00592 
00593         //FIXME: if owner is not a player or player ally, and there are no player allies present,
00594         //                      perhaps we don't need to store the alert... unless we want the player to
00595         //                      react to enemy alert events in some way?
00596 
00597         VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position );
00598 
00599         level.alertEvents[ level.numAlertEvents ].radius        = radius;
00600         level.alertEvents[ level.numAlertEvents ].level         = alertLevel;
00601         level.alertEvents[ level.numAlertEvents ].type          = AET_SOUND;
00602         level.alertEvents[ level.numAlertEvents ].owner         = owner;
00603         if ( needLOS )
00604         {//a very low-level sound, when check this sound event, check for LOS
00605                 level.alertEvents[ level.numAlertEvents ].addLight      = 1;    //will force an LOS trace on this sound
00606         }
00607         else
00608         {
00609                 level.alertEvents[ level.numAlertEvents ].addLight      = 0;    //will force an LOS trace on this sound
00610         }
00611         level.alertEvents[ level.numAlertEvents ].ID            = level.curAlertID++;
00612         level.alertEvents[ level.numAlertEvents ].timestamp     = level.time;
00613 
00614         level.numAlertEvents++;
00615 }
00616 
00617 /*
00618 -------------------------
00619 AddSightEvent
00620 -------------------------
00621 */
00622 
00623 void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight )
00624 {
00625         //FIXME: Handle this in another manner?
00626         if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
00627         {
00628                 if ( !RemoveOldestAlert() )
00629                 {//how could that fail?
00630                         return;
00631                 }
00632         }
00633 
00634         if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts
00635                 return;
00636 
00637         //FIXME: if owner is not a player or player ally, and there are no player allies present,
00638         //                      perhaps we don't need to store the alert... unless we want the player to
00639         //                      react to enemy alert events in some way?
00640 
00641         VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position );
00642 
00643         level.alertEvents[ level.numAlertEvents ].radius        = radius;
00644         level.alertEvents[ level.numAlertEvents ].level         = alertLevel;
00645         level.alertEvents[ level.numAlertEvents ].type          = AET_SIGHT;
00646         level.alertEvents[ level.numAlertEvents ].owner         = owner;                
00647         level.alertEvents[ level.numAlertEvents ].addLight      = addLight;     //will get added to actual light at that point when it's checked
00648         level.alertEvents[ level.numAlertEvents ].ID            = level.curAlertID++;
00649         level.alertEvents[ level.numAlertEvents ].timestamp     = level.time;
00650 
00651         level.numAlertEvents++;
00652 }
00653 
00654 /*
00655 -------------------------
00656 ClearPlayerAlertEvents
00657 -------------------------
00658 */
00659 
00660 void ClearPlayerAlertEvents( void )
00661 {
00662         int curNumAlerts = level.numAlertEvents;
00663         int i;
00664         //loop through them all (max 32)
00665         for ( i = 0; i < curNumAlerts; i++ )
00666         {
00667                 //see if the event is old enough to delete
00668                 if ( level.alertEvents[i].timestamp && level.alertEvents[i].timestamp + ALERT_CLEAR_TIME < level.time )
00669                 {//this event has timed out
00670                         //drop the count
00671                         level.numAlertEvents--;
00672                         //shift the rest down
00673                         if ( level.numAlertEvents > 0 )
00674                         {//still have more in the array
00675                                 if ( (i+1) < MAX_ALERT_EVENTS )
00676                                 {
00677                                         memmove( &level.alertEvents[i], &level.alertEvents[i+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(i+1) ) );
00678                                 }
00679                         }
00680                         else
00681                         {//just clear this one... or should we clear the whole array?
00682                                 memset( &level.alertEvents[i], 0, sizeof( alertEvent_t ) );
00683                         }
00684                 }
00685         }
00686         //make sure this never drops below zero... if it does, something very very bad happened
00687         assert( level.numAlertEvents >= 0 );
00688 
00689         if ( eventClearTime < level.time )
00690         {//this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms
00691                 eventClearTime = level.time + ALERT_CLEAR_TIME;
00692         }
00693 }
00694 
00695 qboolean RemoveOldestAlert( void )
00696 {
00697         int     oldestEvent = -1, oldestTime = Q3_INFINITE;
00698         int i;
00699         //loop through them all (max 32)
00700         for ( i = 0; i < level.numAlertEvents; i++ )
00701         {
00702                 //see if the event is old enough to delete
00703                 if ( level.alertEvents[i].timestamp < oldestTime )
00704                 {
00705                         oldestEvent = i;
00706                         oldestTime = level.alertEvents[i].timestamp;
00707                 }
00708         }
00709         if ( oldestEvent != -1 )
00710         {
00711                 //drop the count
00712                 level.numAlertEvents--;
00713                 //shift the rest down
00714                 if ( level.numAlertEvents > 0 )
00715                 {//still have more in the array
00716                         if ( (oldestEvent+1) < MAX_ALERT_EVENTS )
00717                         {
00718                                 memmove( &level.alertEvents[oldestEvent], &level.alertEvents[oldestEvent+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(oldestEvent+1) ) );
00719                         }
00720                 }
00721                 else
00722                 {//just clear this one... or should we clear the whole array?
00723                         memset( &level.alertEvents[oldestEvent], 0, sizeof( alertEvent_t ) );
00724                 }
00725         }
00726         //make sure this never drops below zero... if it does, something very very bad happened
00727         assert( level.numAlertEvents >= 0 );
00728         //return true is have room for one now
00729         return (level.numAlertEvents<MAX_ALERT_EVENTS);
00730 }
00731 
00732 /*
00733 -------------------------
00734 G_ClearLOS
00735 -------------------------
00736 */
00737 
00738 // Position to position
00739 qboolean G_ClearLOS( gentity_t *self, const vec3_t start, const vec3_t end )
00740 {
00741         trace_t         tr;
00742         int                     traceCount = 0;
00743         
00744         //FIXME: ENTITYNUM_NONE ok?
00745         trap_Trace ( &tr, start, NULL, NULL, end, ENTITYNUM_NONE, CONTENTS_OPAQUE/*CONTENTS_SOLID*//*(CONTENTS_SOLID|CONTENTS_MONSTERCLIP)*/ );
00746         while ( tr.fraction < 1.0 && traceCount < 3 )
00747         {//can see through 3 panes of glass
00748                 if ( tr.entityNum < ENTITYNUM_WORLD )
00749                 {
00750                         if ( &g_entities[tr.entityNum] != NULL && (g_entities[tr.entityNum].r.svFlags&SVF_GLASS_BRUSH) )
00751                         {//can see through glass, trace again, ignoring me
00752                                 trap_Trace ( &tr, tr.endpos, NULL, NULL, end, tr.entityNum, MASK_OPAQUE );
00753                                 traceCount++;
00754                                 continue;
00755                         }
00756                 }
00757                 return qfalse;
00758         }
00759 
00760         if ( tr.fraction == 1.0 ) 
00761                 return qtrue;
00762 
00763         return qfalse;
00764 }
00765 
00766 //Entity to position
00767 qboolean G_ClearLOS2( gentity_t *self, gentity_t *ent, const vec3_t end )
00768 {
00769         vec3_t  eyes;
00770 
00771         CalcEntitySpot( ent, SPOT_HEAD_LEAN, eyes );
00772 
00773         return G_ClearLOS( self, eyes, end );
00774 }
00775 
00776 //Position to entity
00777 qboolean G_ClearLOS3( gentity_t *self, const vec3_t start, gentity_t *ent )
00778 {
00779         vec3_t          spot;
00780 
00781         //Look for the chest first
00782         CalcEntitySpot( ent, SPOT_ORIGIN, spot );
00783 
00784         if ( G_ClearLOS( self, start, spot ) )
00785                 return qtrue;
00786 
00787         //Look for the head next
00788         CalcEntitySpot( ent, SPOT_HEAD_LEAN, spot );
00789 
00790         if ( G_ClearLOS( self, start, spot ) )
00791                 return qtrue;
00792 
00793         return qfalse;
00794 }
00795 
00796 //NPC's eyes to entity
00797 qboolean G_ClearLOS4( gentity_t *self, gentity_t *ent ) 
00798 {
00799         vec3_t  eyes;
00800 
00801         //Calculate my position
00802         CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
00803         
00804         return G_ClearLOS3( self, eyes, ent );
00805 }
00806 
00807 //NPC's eyes to position
00808 qboolean G_ClearLOS5( gentity_t *self, const vec3_t end )
00809 {
00810         vec3_t  eyes;
00811 
00812         //Calculate the my position
00813         CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
00814         
00815         return G_ClearLOS( self, eyes, end );
00816 }
00817 
00818 /*
00819 -------------------------
00820 NPC_GetFOVPercentage
00821 -------------------------
00822 */
00823 
00824 float NPC_GetHFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float hFOV )
00825 {
00826         vec3_t  deltaVector, angles;
00827         float   delta;
00828 
00829         VectorSubtract ( spot, from, deltaVector );
00830 
00831         vectoangles ( deltaVector, angles );
00832         
00833         delta = fabs( AngleDelta ( facing[YAW], angles[YAW] ) );
00834 
00835         if ( delta > hFOV )
00836                 return 0.0f; 
00837 
00838         return ( ( hFOV - delta ) / hFOV );
00839 }
00840 
00841 /*
00842 -------------------------
00843 NPC_GetVFOVPercentage
00844 -------------------------
00845 */
00846 
00847 float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV )
00848 {
00849         vec3_t  deltaVector, angles;
00850         float   delta;
00851 
00852         VectorSubtract ( spot, from, deltaVector );
00853 
00854         vectoangles ( deltaVector, angles );
00855         
00856         delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) );
00857 
00858         if ( delta > vFOV )
00859                 return 0.0f; 
00860 
00861         return ( ( vFOV - delta ) / vFOV );
00862 }
00863 
00864 #define MAX_INTEREST_DIST       ( 256 * 256 )
00865 /*
00866 -------------------------
00867 NPC_FindLocalInterestPoint 
00868 -------------------------
00869 */
00870 
00871 int G_FindLocalInterestPoint( gentity_t *self )
00872 {
00873         int             i, bestPoint = ENTITYNUM_NONE;
00874         float   dist, bestDist = Q3_INFINITE;
00875         vec3_t  diffVec, eyes;
00876 
00877         CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
00878         for ( i = 0; i < level.numInterestPoints; i++ )
00879         {
00880                 //Don't ignore portals?  If through a portal, need to look at portal!
00881                 if ( trap_InPVS( level.interestPoints[i].origin, eyes ) )
00882                 {
00883                         VectorSubtract( level.interestPoints[i].origin, eyes, diffVec );
00884                         if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 &&
00885                                 fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 )
00886                         {//Too close to look so far up or down
00887                                 continue;
00888                         }
00889                         dist = VectorLengthSquared( diffVec );
00890                         //Some priority to more interesting points
00891                         //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5);
00892                         if ( dist < MAX_INTEREST_DIST && dist < bestDist )
00893                         {
00894                                 if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) )
00895                                 {
00896                                         bestDist = dist;
00897                                         bestPoint = i;
00898                                 }
00899                         }
00900                 }
00901         }
00902         if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target )
00903         {
00904                 G_UseTargets2( self, self, level.interestPoints[bestPoint].target );
00905         }
00906         return bestPoint;
00907 }
00908 
00909 /*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4)
00910 A point that a squadmate will look at if standing still
00911 
00912 target - thing to fire when someone looks at this thing
00913 */
00914 
00915 void SP_target_interest( gentity_t *self )
00916 {//FIXME: rename point_interest
00917         if(level.numInterestPoints >= MAX_INTEREST_POINTS)
00918         {
00919                 Com_Printf("ERROR:  Too many interest points, limit is %d\n", MAX_INTEREST_POINTS);
00920                 G_FreeEntity(self);
00921                 return;
00922         }
00923 
00924         VectorCopy(self->r.currentOrigin, level.interestPoints[level.numInterestPoints].origin);
00925 
00926         if(self->target && self->target[0])
00927         {
00928                 level.interestPoints[level.numInterestPoints].target = G_NewString( self->target );
00929         }
00930 
00931         level.numInterestPoints++;
00932 
00933         G_FreeEntity(self);
00934 }