NPC_senses.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. //NPC_senses.cpp
  2. // leave this line at the top for all NPC_xxxx.cpp files...
  3. #include "g_headers.h"
  4. #include "b_local.h"
  5. #ifdef _DEBUG
  6. #include <float.h>
  7. #endif
  8. extern int eventClearTime;
  9. /*
  10. qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask)
  11. returns true if can see from point 1 to 2, even through glass (1 pane)- doesn't work with portals
  12. */
  13. qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask)
  14. {
  15. trace_t tr;
  16. gi.trace ( &tr, point1, NULL, NULL, point2, ignore, clipmask );
  17. if ( tr.fraction == 1.0 )
  18. {
  19. return qtrue;
  20. }
  21. gentity_t *hit = &g_entities[ tr.entityNum ];
  22. if(EntIsGlass(hit))
  23. {
  24. vec3_t newpoint1;
  25. VectorCopy(tr.endpos, newpoint1);
  26. gi.trace (&tr, newpoint1, NULL, NULL, point2, hit->s.number, clipmask );
  27. if ( tr.fraction == 1.0 )
  28. {
  29. return qtrue;
  30. }
  31. }
  32. return qfalse;
  33. }
  34. /*
  35. CanSee
  36. determine if NPC can see an entity
  37. This is a straight line trace check. This function does not look at PVS or FOV,
  38. or take any AI related factors (for example, the NPC's reaction time) into account
  39. FIXME do we need fat and thin version of this?
  40. */
  41. qboolean CanSee ( gentity_t *ent )
  42. {
  43. trace_t tr;
  44. vec3_t eyes;
  45. vec3_t spot;
  46. CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
  47. CalcEntitySpot( ent, SPOT_ORIGIN, spot );
  48. gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
  49. ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
  50. if ( tr.fraction == 1.0 )
  51. {
  52. return qtrue;
  53. }
  54. CalcEntitySpot( ent, SPOT_HEAD, spot );
  55. gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
  56. ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
  57. if ( tr.fraction == 1.0 )
  58. {
  59. return qtrue;
  60. }
  61. CalcEntitySpot( ent, SPOT_LEGS, spot );
  62. gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE );
  63. ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE);
  64. if ( tr.fraction == 1.0 )
  65. {
  66. return qtrue;
  67. }
  68. return qfalse;
  69. }
  70. qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f )
  71. {
  72. vec3_t dir, forward, angles;
  73. float dot;
  74. VectorSubtract( spot, from, dir );
  75. dir[2] = 0;
  76. VectorNormalize( dir );
  77. VectorCopy( fromAngles, angles );
  78. angles[0] = 0;
  79. AngleVectors( angles, forward, NULL, NULL );
  80. dot = DotProduct( dir, forward );
  81. return (dot > threshHold);
  82. }
  83. float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles )
  84. {
  85. vec3_t dir, forward, angles;
  86. float dot;
  87. VectorSubtract( spot, from, dir );
  88. dir[2] = 0;
  89. VectorNormalize( dir );
  90. VectorCopy( fromAngles, angles );
  91. angles[0] = 0;
  92. AngleVectors( angles, forward, NULL, NULL );
  93. dot = DotProduct( dir, forward );
  94. return dot;
  95. }
  96. /*
  97. InFOV
  98. IDEA: further off to side of FOV range, higher chance of failing even if technically in FOV,
  99. keep core of 50% to sides as always succeeding
  100. */
  101. //Position compares
  102. qboolean InFOV( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV )
  103. {
  104. vec3_t deltaVector, angles, deltaAngles;
  105. VectorSubtract ( spot, from, deltaVector );
  106. vectoangles ( deltaVector, angles );
  107. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  108. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  109. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  110. {
  111. return qtrue;
  112. }
  113. return qfalse;
  114. }
  115. //NPC to position
  116. qboolean InFOV( vec3_t origin, gentity_t *from, int hFOV, int vFOV )
  117. {
  118. vec3_t fromAngles, eyes;
  119. if( from->client )
  120. {
  121. VectorCopy(from->client->ps.viewangles, fromAngles);
  122. }
  123. else
  124. {
  125. VectorCopy(from->s.angles, fromAngles);
  126. }
  127. CalcEntitySpot( from, SPOT_HEAD, eyes );
  128. return InFOV( origin, eyes, fromAngles, hFOV, vFOV );
  129. }
  130. //Entity to entity
  131. qboolean InFOVFromPlayerView ( gentity_t *ent, int hFOV, int vFOV )
  132. {
  133. vec3_t eyes;
  134. vec3_t spot;
  135. vec3_t deltaVector;
  136. vec3_t angles, fromAngles;
  137. vec3_t deltaAngles;
  138. if ( !player || !player->client )
  139. {
  140. return qfalse;
  141. }
  142. if ( cg.time )
  143. {
  144. VectorCopy( cg.refdefViewAngles, fromAngles );
  145. }
  146. else
  147. {
  148. VectorCopy( player->client->ps.viewangles, fromAngles );
  149. }
  150. if( cg.time )
  151. {
  152. VectorCopy( cg.refdef.vieworg, eyes );
  153. }
  154. else
  155. {
  156. CalcEntitySpot( player, SPOT_HEAD_LEAN, eyes );
  157. }
  158. CalcEntitySpot( ent, SPOT_ORIGIN, spot );
  159. VectorSubtract ( spot, eyes, deltaVector);
  160. vectoangles ( deltaVector, angles );
  161. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  162. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  163. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  164. {
  165. return qtrue;
  166. }
  167. CalcEntitySpot( ent, SPOT_HEAD, spot );
  168. VectorSubtract ( spot, eyes, deltaVector);
  169. vectoangles ( deltaVector, angles );
  170. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  171. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  172. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  173. {
  174. return qtrue;
  175. }
  176. CalcEntitySpot( ent, SPOT_LEGS, spot );
  177. VectorSubtract ( spot, eyes, deltaVector);
  178. vectoangles ( deltaVector, angles );
  179. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  180. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  181. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  182. {
  183. return qtrue;
  184. }
  185. return qfalse;
  186. }
  187. qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV )
  188. {
  189. vec3_t eyes;
  190. vec3_t spot;
  191. vec3_t deltaVector;
  192. vec3_t angles, fromAngles;
  193. vec3_t deltaAngles;
  194. if( from->client )
  195. {
  196. if( from->client->NPC_class != CLASS_RANCOR
  197. && from->client->NPC_class != CLASS_WAMPA
  198. && !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) )
  199. {//Actual facing of tag_head!
  200. //NOTE: Stasis aliens may have a problem with this?
  201. VectorCopy( from->client->renderInfo.eyeAngles, fromAngles );
  202. }
  203. else
  204. {
  205. VectorCopy( from->client->ps.viewangles, fromAngles );
  206. }
  207. }
  208. else
  209. {
  210. VectorCopy(from->s.angles, fromAngles);
  211. }
  212. CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes );
  213. CalcEntitySpot( ent, SPOT_ORIGIN, spot );
  214. VectorSubtract ( spot, eyes, deltaVector);
  215. vectoangles ( deltaVector, angles );
  216. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  217. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  218. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  219. {
  220. return qtrue;
  221. }
  222. CalcEntitySpot( ent, SPOT_HEAD, spot );
  223. VectorSubtract ( spot, eyes, deltaVector);
  224. vectoangles ( deltaVector, angles );
  225. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  226. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  227. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  228. {
  229. return qtrue;
  230. }
  231. CalcEntitySpot( ent, SPOT_LEGS, spot );
  232. VectorSubtract ( spot, eyes, deltaVector);
  233. vectoangles ( deltaVector, angles );
  234. deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] );
  235. deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] );
  236. if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV )
  237. {
  238. return qtrue;
  239. }
  240. return qfalse;
  241. }
  242. qboolean InVisrange ( gentity_t *ent )
  243. {//FIXME: make a calculate visibility for ents that takes into account
  244. //lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc.
  245. vec3_t eyes;
  246. vec3_t spot;
  247. vec3_t deltaVector;
  248. float visrange = (NPCInfo->stats.visrange*NPCInfo->stats.visrange);
  249. CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
  250. CalcEntitySpot( ent, SPOT_ORIGIN, spot );
  251. VectorSubtract ( spot, eyes, deltaVector);
  252. /*if(ent->client)
  253. {
  254. float vel, avel;
  255. if(ent->client->ps.velocity[0] || ent->client->ps.velocity[1] || ent->client->ps.velocity[2])
  256. {
  257. vel = VectorLength(ent->client->ps.velocity);
  258. if(vel > 128)
  259. {
  260. visrange += visrange * (vel/256);
  261. }
  262. }
  263. if(ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
  264. {//FIXME: shouldn't they need to have line of sight to you to detect this?
  265. avel = VectorLength(ent->avelocity);
  266. if(avel > 15)
  267. {
  268. visrange += visrange * (avel/60);
  269. }
  270. }
  271. }*/
  272. if(VectorLengthSquared(deltaVector) > visrange)
  273. {
  274. return qfalse;
  275. }
  276. return qtrue;
  277. }
  278. /*
  279. NPC_CheckVisibility
  280. */
  281. visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags )
  282. {
  283. // flags should never be 0
  284. if ( !flags )
  285. {
  286. return VIS_NOT;
  287. }
  288. // check PVS
  289. if ( flags & CHECK_PVS )
  290. {
  291. if ( !gi.inPVS ( ent->currentOrigin, NPC->currentOrigin ) )
  292. {
  293. return VIS_NOT;
  294. }
  295. }
  296. if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) )
  297. {
  298. return VIS_PVS;
  299. }
  300. // check within visrange
  301. if (flags & CHECK_VISRANGE)
  302. {
  303. if( !InVisrange ( ent ) )
  304. {
  305. return VIS_PVS;
  306. }
  307. }
  308. // check 360 degree visibility
  309. //Meaning has to be a direct line of site
  310. if ( flags & CHECK_360 )
  311. {
  312. if ( !CanSee ( ent ) )
  313. {
  314. return VIS_PVS;
  315. }
  316. }
  317. if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) )
  318. {
  319. return VIS_360;
  320. }
  321. // check FOV
  322. if ( flags & CHECK_FOV )
  323. {
  324. if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) )
  325. {
  326. return VIS_360;
  327. }
  328. }
  329. if ( !(flags & CHECK_SHOOT) )
  330. {
  331. return VIS_FOV;
  332. }
  333. // check shootability
  334. if ( flags & CHECK_SHOOT )
  335. {
  336. if ( !CanShoot ( ent, NPC ) )
  337. {
  338. return VIS_FOV;
  339. }
  340. }
  341. return VIS_SHOOT;
  342. }
  343. /*
  344. -------------------------
  345. NPC_CheckSoundEvents
  346. -------------------------
  347. */
  348. static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly )
  349. {
  350. int bestEvent = -1;
  351. int bestAlert = -1;
  352. int bestTime = -1;
  353. float dist, radius;
  354. maxHearDist *= maxHearDist;
  355. for ( int i = 0; i < level.numAlertEvents; i++ )
  356. {
  357. //are we purposely ignoring this alert?
  358. if ( level.alertEvents[i].ID == ignoreAlert )
  359. continue;
  360. //We're only concerned about sounds
  361. if ( level.alertEvents[i].type != AET_SOUND )
  362. continue;
  363. //must be at least this noticable
  364. if ( level.alertEvents[i].level < minAlertLevel )
  365. continue;
  366. //must have an owner?
  367. if ( mustHaveOwner && !level.alertEvents[i].owner )
  368. continue;
  369. //must be on the ground?
  370. if ( onGroundOnly && !level.alertEvents[i].onGround )
  371. continue;
  372. //Must be within range
  373. dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin );
  374. //can't hear it
  375. if ( dist > maxHearDist )
  376. continue;
  377. if ( self->client && self->client->NPC_class != CLASS_SAND_CREATURE )
  378. {//sand creatures hear all in within their earshot, regardless of quietness and alert sound radius!
  379. radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
  380. if ( dist > radius )
  381. continue;
  382. if ( level.alertEvents[i].addLight )
  383. {//a quiet sound, must have LOS to hear it
  384. if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse )
  385. {//no LOS, didn't hear it
  386. continue;
  387. }
  388. }
  389. }
  390. //See if this one takes precedence over the previous one
  391. if ( level.alertEvents[i].level >= bestAlert //higher alert level
  392. || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer
  393. {//NOTE: equal is better because it's later in the array
  394. bestEvent = i;
  395. bestAlert = level.alertEvents[i].level;
  396. bestTime = level.alertEvents[i].timestamp;
  397. }
  398. }
  399. return bestEvent;
  400. }
  401. float G_GetLightLevel( vec3_t pos, vec3_t fromDir )
  402. {
  403. vec3_t ambient={0}, directed, lightDir;
  404. float lightLevel;
  405. cgi_R_GetLighting( pos, ambient, directed, lightDir );
  406. lightLevel = VectorLength( ambient ) + (VectorLength( directed )*DotProduct( lightDir, fromDir ));
  407. return lightLevel;
  408. }
  409. /*
  410. -------------------------
  411. NPC_CheckSightEvents
  412. -------------------------
  413. */
  414. static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel )
  415. {
  416. int bestEvent = -1;
  417. int bestAlert = -1;
  418. int bestTime = -1;
  419. float dist, radius;
  420. maxSeeDist *= maxSeeDist;
  421. for ( int i = 0; i < level.numAlertEvents; i++ )
  422. {
  423. //are we purposely ignoring this alert?
  424. if ( level.alertEvents[i].ID == ignoreAlert )
  425. continue;
  426. //We're only concerned about sounds
  427. if ( level.alertEvents[i].type != AET_SIGHT )
  428. continue;
  429. //must be at least this noticable
  430. if ( level.alertEvents[i].level < minAlertLevel )
  431. continue;
  432. //must have an owner?
  433. if ( mustHaveOwner && !level.alertEvents[i].owner )
  434. continue;
  435. //Must be within range
  436. dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin );
  437. //can't see it
  438. if ( dist > maxSeeDist )
  439. continue;
  440. radius = level.alertEvents[i].radius * level.alertEvents[i].radius;
  441. if ( dist > radius )
  442. continue;
  443. //Must be visible
  444. if ( InFOV( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse )
  445. continue;
  446. if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse )
  447. continue;
  448. //FIXME: possibly have the light level at this point affect the
  449. // visibility/alert level of this event? Would also
  450. // need to take into account how bright the event
  451. // itself is. A lightsaber would stand out more
  452. // in the dark... maybe pass in a light level that
  453. // is added to the actual light level at this position?
  454. //See if this one takes precedence over the previous one
  455. if ( level.alertEvents[i].level >= bestAlert //higher alert level
  456. || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer
  457. {//NOTE: equal is better because it's later in the array
  458. bestEvent = i;
  459. bestAlert = level.alertEvents[i].level;
  460. bestTime = level.alertEvents[i].timestamp;
  461. }
  462. }
  463. return bestEvent;
  464. }
  465. qboolean G_RememberAlertEvent( gentity_t *self, int alertIndex )
  466. {
  467. if ( !self || !self->NPC )
  468. {//not a valid ent
  469. return qfalse;
  470. }
  471. if ( alertIndex == -1 )
  472. {//not a valid event
  473. return qfalse;
  474. }
  475. // Get The Event Struct
  476. //----------------------
  477. alertEvent_t& at = level.alertEvents[alertIndex];
  478. if ( at.ID == self->NPC->lastAlertID )
  479. {//already know this one
  480. return qfalse;
  481. }
  482. if (at.owner==self)
  483. {//don't care about events that I made
  484. return false;
  485. }
  486. self->NPC->lastAlertID = at.ID;
  487. // Now, If It Is Dangerous Enough, We Want To Register This With The Pathfinding System
  488. //--------------------------------------------------------------------------------------
  489. bool IsDangerous = (at.level >= AEL_DANGER);
  490. bool IsFromNPC = (at.owner && at.owner->client);
  491. bool IsFromEnemy = (IsFromNPC && at.owner->client->playerTeam!=self->client->playerTeam);
  492. if (IsDangerous && (!IsFromNPC || IsFromEnemy))
  493. {
  494. NAV::RegisterDangerSense(self, alertIndex);
  495. }
  496. return qtrue;
  497. }
  498. /*
  499. -------------------------
  500. NPC_CheckAlertEvents
  501. NOTE: Should all NPCs create alertEvents too so they can detect each other?
  502. -------------------------
  503. */
  504. int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly )
  505. {
  506. if ( &g_entities[0] == NULL || g_entities[0].health <= 0 )
  507. {
  508. //player is dead
  509. return -1;
  510. }
  511. int bestSoundEvent = -1;
  512. int bestSightEvent = -1;
  513. int bestSoundAlert = -1;
  514. int bestSightAlert = -1;
  515. if ( checkSound )
  516. {
  517. //get sound event
  518. bestSoundEvent = G_CheckSoundEvents( self, maxHearDist, ignoreAlert, mustHaveOwner, minAlertLevel, onGroundOnly );
  519. //get sound event alert level
  520. if ( bestSoundEvent >= 0 )
  521. {
  522. bestSoundAlert = level.alertEvents[bestSoundEvent].level;
  523. }
  524. }
  525. if ( checkSight )
  526. {
  527. //get sight event
  528. if ( self->NPC )
  529. {
  530. bestSightEvent = G_CheckSightEvents( self, self->NPC->stats.hfov, self->NPC->stats.vfov, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );
  531. }
  532. else
  533. {
  534. bestSightEvent = G_CheckSightEvents( self, 80, 80, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );//FIXME: look at cg_view to get more accurate numbers?
  535. }
  536. //get sight event alert level
  537. if ( bestSightEvent >= 0 )
  538. {
  539. bestSightAlert = level.alertEvents[bestSightEvent].level;
  540. }
  541. //return the one that has a higher alert (or sound if equal)
  542. //FIXME: This doesn't take the distance of the event into account
  543. if ( bestSightEvent >= 0 && bestSightAlert > bestSoundAlert )
  544. {//valid best sight event, more important than the sound event
  545. //get the light level of the alert event for this checker
  546. vec3_t eyePoint, sightDir;
  547. //get eye point
  548. CalcEntitySpot( self, SPOT_HEAD_LEAN, eyePoint );
  549. VectorSubtract( level.alertEvents[bestSightEvent].position, eyePoint, sightDir );
  550. level.alertEvents[bestSightEvent].light = level.alertEvents[bestSightEvent].addLight + G_GetLightLevel( level.alertEvents[bestSightEvent].position, sightDir );
  551. //return the sight event
  552. if ( G_RememberAlertEvent( self, bestSightEvent ) )
  553. {
  554. return bestSightEvent;
  555. }
  556. }
  557. }
  558. //return the sound event
  559. if ( G_RememberAlertEvent( self, bestSoundEvent ) )
  560. {
  561. return bestSoundEvent;
  562. }
  563. //no event or no new event
  564. return -1;
  565. }
  566. int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly )
  567. {
  568. return G_CheckAlertEvents( NPC, checkSight, checkSound, NPCInfo->stats.visrange, NPCInfo->stats.earshot, ignoreAlert, mustHaveOwner, minAlertLevel, onGroundOnly );
  569. }
  570. extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
  571. qboolean G_CheckForDanger( gentity_t *self, int alertEvent )
  572. {//FIXME: more bStates need to call this?
  573. if ( alertEvent == -1 )
  574. {
  575. return qfalse;
  576. }
  577. if ( level.alertEvents[alertEvent].level >= AEL_DANGER )
  578. {//run away!
  579. if ( !level.alertEvents[alertEvent].owner || !level.alertEvents[alertEvent].owner->client || (level.alertEvents[alertEvent].owner!=self&&level.alertEvents[alertEvent].owner->client->playerTeam!=self->client->playerTeam) )
  580. {
  581. if ( self->NPC )
  582. {
  583. if ( self->NPC->scriptFlags & SCF_DONT_FLEE )
  584. {//can't flee
  585. return qfalse;
  586. }
  587. else
  588. {
  589. if ( level.alertEvents[alertEvent].level >= AEL_DANGER_GREAT || self->s.weapon == WP_NONE || self->s.weapon == WP_MELEE )
  590. {//flee for a longer period of time
  591. NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 3000, 6000 );
  592. }
  593. else if ( !Q_irand( 0, 10 ) )//FIXME: base on rank? aggression?
  594. {//just normal danger and I have a weapon, so just a 25% chance of fleeing only for a few seconds
  595. //FIXME: used to just find a better combat point, need that functionality back
  596. NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 1000, 3000 );
  597. }
  598. else
  599. {//didn't flee
  600. TIMER_Set( NPC, "duck", 2000); // something dangerous going on...
  601. return qfalse;
  602. }
  603. return qtrue;
  604. }
  605. }
  606. else
  607. {
  608. return qtrue;
  609. }
  610. }
  611. }
  612. return qfalse;
  613. }
  614. qboolean NPC_CheckForDanger( int alertEvent )
  615. {//FIXME: more bStates need to call this?
  616. return G_CheckForDanger( NPC, alertEvent );
  617. }
  618. /*
  619. -------------------------
  620. AddSoundEvent
  621. -------------------------
  622. */
  623. qboolean RemoveOldestAlert( void );
  624. void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS, qboolean onGround )
  625. {
  626. //FIXME: Handle this in another manner?
  627. if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
  628. {
  629. if ( !RemoveOldestAlert() )
  630. {//how could that fail?
  631. return;
  632. }
  633. }
  634. if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts
  635. return;
  636. //FIXME: why are Sand creatures suddenly crashlanding?
  637. if ( owner && owner->client && owner->client->NPC_class == CLASS_SAND_CREATURE )
  638. {
  639. return;
  640. }
  641. //FIXME: if owner is not a player or player ally, and there are no player allies present,
  642. // perhaps we don't need to store the alert... unless we want the player to
  643. // react to enemy alert events in some way?
  644. #ifdef _DEBUG
  645. assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) );
  646. #endif
  647. VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position );
  648. level.alertEvents[ level.numAlertEvents ].radius = radius;
  649. level.alertEvents[ level.numAlertEvents ].level = alertLevel;
  650. level.alertEvents[ level.numAlertEvents ].type = AET_SOUND;
  651. level.alertEvents[ level.numAlertEvents ].owner = owner;
  652. if ( needLOS )
  653. {//a very low-level sound, when check this sound event, check for LOS
  654. level.alertEvents[ level.numAlertEvents ].addLight = 1; //will force an LOS trace on this sound
  655. }
  656. else
  657. {
  658. level.alertEvents[ level.numAlertEvents ].addLight = 0; //will force an LOS trace on this sound
  659. }
  660. level.alertEvents[ level.numAlertEvents ].onGround = onGround;
  661. level.alertEvents[ level.numAlertEvents ].ID = ++level.curAlertID;
  662. level.alertEvents[ level.numAlertEvents ].timestamp = level.time;
  663. level.numAlertEvents++;
  664. }
  665. /*
  666. -------------------------
  667. AddSightEvent
  668. -------------------------
  669. */
  670. void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight )
  671. {
  672. //FIXME: Handle this in another manner?
  673. if ( level.numAlertEvents >= MAX_ALERT_EVENTS )
  674. {
  675. if ( !RemoveOldestAlert() )
  676. {//how could that fail?
  677. return;
  678. }
  679. }
  680. if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts
  681. return;
  682. //FIXME: if owner is not a player or player ally, and there are no player allies present,
  683. // perhaps we don't need to store the alert... unless we want the player to
  684. // react to enemy alert events in some way?
  685. #ifdef _DEBUG
  686. assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) );
  687. #endif
  688. VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position );
  689. level.alertEvents[ level.numAlertEvents ].radius = radius;
  690. level.alertEvents[ level.numAlertEvents ].level = alertLevel;
  691. level.alertEvents[ level.numAlertEvents ].type = AET_SIGHT;
  692. level.alertEvents[ level.numAlertEvents ].owner = owner;
  693. level.alertEvents[ level.numAlertEvents ].addLight = addLight; //will get added to actual light at that point when it's checked
  694. level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++;
  695. level.alertEvents[ level.numAlertEvents ].timestamp = level.time;
  696. level.numAlertEvents++;
  697. }
  698. /*
  699. -------------------------
  700. ClearPlayerAlertEvents
  701. -------------------------
  702. */
  703. void ClearPlayerAlertEvents( void )
  704. {
  705. int curNumAlerts = level.numAlertEvents;
  706. //loop through them all (max 32)
  707. for ( int i = 0; i < curNumAlerts; i++ )
  708. {
  709. //see if the event is old enough to delete
  710. if ( level.alertEvents[i].timestamp && level.alertEvents[i].timestamp + ALERT_CLEAR_TIME < level.time )
  711. {//this event has timed out
  712. //drop the count
  713. level.numAlertEvents--;
  714. //shift the rest down
  715. if ( level.numAlertEvents > 0 )
  716. {//still have more in the array
  717. if ( (i+1) < MAX_ALERT_EVENTS )
  718. {
  719. memmove( &level.alertEvents[i], &level.alertEvents[i+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(i+1) ) );
  720. }
  721. }
  722. else
  723. {//just clear this one... or should we clear the whole array?
  724. memset( &level.alertEvents[i], 0, sizeof( alertEvent_t ) );
  725. }
  726. }
  727. }
  728. //make sure this never drops below zero... if it does, something very very bad happened
  729. assert( level.numAlertEvents >= 0 );
  730. if ( eventClearTime < level.time )
  731. {//this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms
  732. eventClearTime = level.time + ALERT_CLEAR_TIME;
  733. }
  734. }
  735. qboolean RemoveOldestAlert( void )
  736. {
  737. int oldestEvent = -1, oldestTime = Q3_INFINITE;
  738. //loop through them all (max 32)
  739. for ( int i = 0; i < level.numAlertEvents; i++ )
  740. {
  741. //see if the event is old enough to delete
  742. if ( level.alertEvents[i].timestamp < oldestTime )
  743. {
  744. oldestEvent = i;
  745. oldestTime = level.alertEvents[i].timestamp;
  746. }
  747. }
  748. if ( oldestEvent != -1 )
  749. {
  750. //drop the count
  751. level.numAlertEvents--;
  752. //shift the rest down
  753. if ( level.numAlertEvents > 0 )
  754. {//still have more in the array
  755. if ( (oldestEvent+1) < MAX_ALERT_EVENTS )
  756. {
  757. memmove( &level.alertEvents[oldestEvent], &level.alertEvents[oldestEvent+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(oldestEvent+1) ) );
  758. }
  759. }
  760. else
  761. {//just clear this one... or should we clear the whole array?
  762. memset( &level.alertEvents[oldestEvent], 0, sizeof( alertEvent_t ) );
  763. }
  764. }
  765. //make sure this never drops below zero... if it does, something very very bad happened
  766. assert( level.numAlertEvents >= 0 );
  767. //return true is have room for one now
  768. return (level.numAlertEvents<MAX_ALERT_EVENTS);
  769. }
  770. /*
  771. -------------------------
  772. G_ClearLOS
  773. -------------------------
  774. */
  775. // Position to position
  776. qboolean G_ClearLOS( gentity_t *self, const vec3_t start, const vec3_t end )
  777. {
  778. trace_t tr;
  779. int traceCount = 0;
  780. //FIXME: ENTITYNUM_NONE ok?
  781. gi.trace ( &tr, start, NULL, NULL, end, ENTITYNUM_NONE, CONTENTS_OPAQUE/*CONTENTS_SOLID*//*(CONTENTS_SOLID|CONTENTS_MONSTERCLIP)*/ );
  782. while ( tr.fraction < 1.0 && traceCount < 3 )
  783. {//can see through 3 panes of glass
  784. if ( tr.entityNum < ENTITYNUM_WORLD )
  785. {
  786. if ( &g_entities[tr.entityNum] != NULL && (g_entities[tr.entityNum].svFlags&SVF_GLASS_BRUSH) )
  787. {//can see through glass, trace again, ignoring me
  788. gi.trace ( &tr, tr.endpos, NULL, NULL, end, tr.entityNum, MASK_OPAQUE );
  789. traceCount++;
  790. continue;
  791. }
  792. }
  793. return qfalse;
  794. }
  795. if ( tr.fraction == 1.0 )
  796. return qtrue;
  797. return qfalse;
  798. }
  799. //Entity to position
  800. qboolean G_ClearLOS( gentity_t *self, gentity_t *ent, const vec3_t end )
  801. {
  802. vec3_t eyes;
  803. CalcEntitySpot( ent, SPOT_HEAD_LEAN, eyes );
  804. return G_ClearLOS( self, eyes, end );
  805. }
  806. //Position to entity
  807. qboolean G_ClearLOS( gentity_t *self, const vec3_t start, gentity_t *ent )
  808. {
  809. vec3_t spot;
  810. //Look for the chest first
  811. CalcEntitySpot( ent, SPOT_ORIGIN, spot );
  812. if ( G_ClearLOS( self, start, spot ) )
  813. return qtrue;
  814. //Look for the head next
  815. CalcEntitySpot( ent, SPOT_HEAD_LEAN, spot );
  816. if ( G_ClearLOS( self, start, spot ) )
  817. return qtrue;
  818. return qfalse;
  819. }
  820. //NPC's eyes to entity
  821. qboolean G_ClearLOS( gentity_t *self, gentity_t *ent )
  822. {
  823. vec3_t eyes;
  824. //Calculate my position
  825. CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
  826. return G_ClearLOS( self, eyes, ent );
  827. }
  828. //NPC's eyes to position
  829. qboolean G_ClearLOS( gentity_t *self, const vec3_t end )
  830. {
  831. vec3_t eyes;
  832. //Calculate the my position
  833. CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
  834. return G_ClearLOS( self, eyes, end );
  835. }
  836. /*
  837. -------------------------
  838. NPC_GetFOVPercentage
  839. -------------------------
  840. */
  841. float NPC_GetHFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float hFOV )
  842. {
  843. vec3_t deltaVector, angles;
  844. float delta;
  845. VectorSubtract ( spot, from, deltaVector );
  846. vectoangles ( deltaVector, angles );
  847. delta = fabs( AngleDelta ( facing[YAW], angles[YAW] ) );
  848. if ( delta > hFOV )
  849. return 0.0f;
  850. return ( ( hFOV - delta ) / hFOV );
  851. }
  852. /*
  853. -------------------------
  854. NPC_GetVFOVPercentage
  855. -------------------------
  856. */
  857. float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV )
  858. {
  859. vec3_t deltaVector, angles;
  860. float delta;
  861. VectorSubtract ( spot, from, deltaVector );
  862. vectoangles ( deltaVector, angles );
  863. delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) );
  864. if ( delta > vFOV )
  865. return 0.0f;
  866. return ( ( vFOV - delta ) / vFOV );
  867. }
  868. #define MAX_INTEREST_DIST ( 256 * 256 )
  869. /*
  870. -------------------------
  871. NPC_FindLocalInterestPoint
  872. -------------------------
  873. */
  874. int G_FindLocalInterestPoint( gentity_t *self )
  875. {
  876. int i, bestPoint = ENTITYNUM_NONE;
  877. float dist, bestDist = Q3_INFINITE;
  878. vec3_t diffVec, eyes;
  879. CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
  880. for ( i = 0; i < level.numInterestPoints; i++ )
  881. {
  882. //Don't ignore portals? If through a portal, need to look at portal!
  883. if ( gi.inPVS( level.interestPoints[i].origin, eyes ) )
  884. {
  885. VectorSubtract( level.interestPoints[i].origin, eyes, diffVec );
  886. if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 &&
  887. fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 )
  888. {//Too close to look so far up or down
  889. continue;
  890. }
  891. dist = VectorLengthSquared( diffVec );
  892. //Some priority to more interesting points
  893. //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5);
  894. if ( dist < MAX_INTEREST_DIST && dist < bestDist )
  895. {
  896. if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) )
  897. {
  898. bestDist = dist;
  899. bestPoint = i;
  900. }
  901. }
  902. }
  903. }
  904. if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target )
  905. {
  906. G_UseTargets2( self, self, level.interestPoints[bestPoint].target );
  907. }
  908. return bestPoint;
  909. }
  910. /*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4)
  911. A point that a squadmate will look at if standing still
  912. target - thing to fire when someone looks at this thing
  913. */
  914. void SP_target_interest( gentity_t *self )
  915. {//FIXME: rename point_interest
  916. if(level.numInterestPoints >= MAX_INTEREST_POINTS)
  917. {
  918. gi.Printf("ERROR: Too many interest points, limit is %d\n", MAX_INTEREST_POINTS);
  919. G_FreeEntity(self);
  920. return;
  921. }
  922. VectorCopy(self->currentOrigin, level.interestPoints[level.numInterestPoints].origin);
  923. if(self->target && self->target[0])
  924. {
  925. level.interestPoints[level.numInterestPoints].target = G_NewString( self->target );
  926. }
  927. level.numInterestPoints++;
  928. G_FreeEntity(self);
  929. }