1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 //===========================================================================
30 //
31 // Name: ai_cast_sight.c
32 // Function: Wolfenstein AI Character Visiblity
33 // Programmer: Ridah
34 // Tab Size: 4 (real tabs)
35 //===========================================================================
36
37 #include "g_local.h"
38 #include "../qcommon/q_shared.h"
39 #include "../botlib/botlib.h" //bot lib interface
40 #include "../botlib/be_aas.h"
41 #include "../botlib/be_ea.h"
42 #include "../botlib/be_ai_gen.h"
43 #include "../botlib/be_ai_goal.h"
44 #include "../botlib/be_ai_move.h"
45 #include "../botlib/botai.h" //bot ai interface
46
47 #include "ai_cast.h"
48
49 /*
50 Does sight checking for Cast AI's.
51 */
52
53 static float aiStateFovScales[] =
54 {
55 1.0, // relaxed
56 1.5, // query
57 1.5, // alert
58 2.0, // combat
59 };
60
61 orientation_t clientHeadTags[MAX_CLIENTS];
62 int clientHeadTagTimes[MAX_CLIENTS];
63
64 /*
65 ==============
66 AICast_InFieldOfVision
67 ==============
68 */
AICast_InFieldOfVision(vec3_t viewangles,float fov,vec3_t angles)69 qboolean AICast_InFieldOfVision( vec3_t viewangles, float fov, vec3_t angles ) {
70 int i;
71 float diff, angle;
72
73 for ( i = 0; i < 2; i++ )
74 {
75 angle = AngleMod( viewangles[i] );
76 angles[i] = AngleMod( angles[i] );
77 diff = angles[i] - angle;
78 if ( angles[i] > angle ) {
79 if ( diff > 180.0 ) {
80 diff -= 360.0;
81 }
82 } else
83 {
84 if ( diff < -180.0 ) {
85 diff += 360.0;
86 }
87 }
88 if ( diff > 0 ) {
89 if ( diff > fov * 0.5 ) {
90 return qfalse;
91 }
92 } else
93 {
94 if ( diff < -fov * 0.5 ) {
95 return qfalse;
96 }
97 }
98 }
99 return qtrue;
100 }
101
102 /*
103 ==============
104 AICast_VisibleFromPos
105 ==============
106 */
AICast_VisibleFromPos(vec3_t srcpos,int srcnum,vec3_t destpos,int destnum,qboolean updateVisPos)107 qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum,
108 vec3_t destpos, int destnum, qboolean updateVisPos ) {
109 int i, contents_mask, passent, hitent;
110 trace_t trace;
111 vec3_t start, end, middle, eye;
112 cast_state_t *cs = NULL;
113 int srcviewheight;
114 vec3_t destmins, destmaxs;
115 vec3_t right, vec;
116 qboolean inPVS;
117
118 if ( g_entities[destnum].flags & FL_NOTARGET ) {
119 return qfalse;
120 }
121
122 if ( srcnum < aicast_maxclients ) {
123 cs = AICast_GetCastState( srcnum );
124 }
125 //
126 if ( cs && cs->bs ) {
127 srcviewheight = cs->bs->cur_ps.viewheight;
128 } else if ( g_entities[srcnum].client ) {
129 srcviewheight = g_entities[srcnum].client->ps.viewheight;
130 } else {
131 srcviewheight = 0;
132 }
133 //
134 VectorCopy( g_entities[destnum].r.mins, destmins );
135 VectorCopy( g_entities[destnum].r.maxs, destmaxs );
136 //
137 //calculate middle of bounding box
138 VectorAdd( destmins, destmaxs, middle );
139 VectorScale( middle, 0.5, middle );
140 VectorAdd( destpos, middle, middle );
141 // calculate eye position
142 VectorCopy( srcpos, eye );
143 eye[2] += srcviewheight;
144 //
145 // set the right vector
146 VectorSubtract( middle, eye, vec );
147 VectorNormalize( vec );
148 right[0] = vec[1];
149 right[1] = vec[0];
150 right[2] = 0;
151 //
152 inPVS = qfalse;
153 //
154 for ( i = 0; i < 5; i++ )
155 {
156 if ( cs && updateVisPos ) { // if it's a grenade or something, PVS checks don't work very well
157 //if the point is not in potential visible sight
158 if ( i < 3 ) { // don't do PVS check for left/right checks
159 if ( !trap_InPVS( eye, middle ) ) {
160 continue;
161 } else {
162 inPVS = qtrue;
163 }
164 } else if ( !inPVS ) {
165 break; // wasn't in potential view in either of the previous tests
166 } // so don't bother doing left/right
167 }
168 //
169 contents_mask = MASK_AISIGHT; //(MASK_SHOT | CONTENTS_AI_NOSIGHT) & ~(CONTENTS_BODY); // we can see anything that a bullet can pass through
170 passent = srcnum;
171 hitent = destnum;
172 VectorCopy( eye, start );
173 VectorCopy( middle, end );
174 //if the entity is in water, lava or slime
175 if ( trap_PointContents( middle, destnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) {
176 contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
177 } //end if
178 //if eye is in water, lava or slime
179 if ( trap_PointContents( eye, srcnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) {
180 if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) {
181 passent = destnum;
182 hitent = srcnum;
183 VectorCopy( middle, start );
184 VectorCopy( eye, end );
185 } //end if
186 contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
187 } //end if
188 //trace from start to end
189 trap_Trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE /*passent*/, contents_mask );
190 //if water was hit
191 if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) {
192 //if the water surface is translucent
193 // if (trace.surface.flags & (SURF_TRANS33|SURF_TRANS66))
194 {
195 //trace through the water
196 contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
197 trap_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask );
198 } //end if
199 } //end if
200 //if a full trace or the hitent was hit
201 if ( trace.fraction >= 1 || trace.entityNum == hitent ) {
202 return qtrue;
203 }
204 //check bottom and top of bounding box as well
205 if ( i == 0 ) {
206 middle[2] -= ( destmaxs[2] - destmins[2] ) * 0.5;
207 } else if ( i == 1 ) {
208 middle[2] += destmaxs[2] - destmins[2];
209 } else if ( i == 2 ) { // right side
210 middle[2] -= ( destmaxs[2] - destmins[2] ) / 2.0;
211 VectorMA( eye, destmaxs[0] - 0.5, right, eye );
212 } else if ( i == 3 ) { // left side
213 VectorMA( eye, -2.0 * ( destmaxs[0] - 0.5 ), right, eye );
214 }
215 } //end for
216
217 return qfalse;
218 }
219
220 /*
221 ==============
222 AICast_CheckVisibility
223 ==============
224 */
AICast_CheckVisibility(gentity_t * srcent,gentity_t * destent)225 qboolean AICast_CheckVisibility( gentity_t *srcent, gentity_t *destent ) {
226 vec3_t dir, entangles, middle, eye, viewangles;
227 cast_state_t *cs;
228 float fov, dist;
229 int viewer, ent;
230 cast_visibility_t *vis;
231 orientation_t or;
232
233 if ( destent->flags & FL_NOTARGET ) {
234 return qfalse;
235 }
236 //
237 viewer = srcent->s.number;
238 ent = destent->s.number;
239 //
240 cs = AICast_GetCastState( viewer );
241 AICast_GetCastState( ent );
242 //
243 vis = &cs->vislist[ent];
244 //
245 // if the destent is the client, and they have just loaded a savegame, ignore them temporarily
246 if ( !destent->aiCharacter && level.lastLoadTime && ( level.lastLoadTime > level.time - 2000 ) && !vis->visible_timestamp ) {
247 return qfalse;
248 }
249 // set the FOV
250 fov = cs->attributes[FOV] * aiStateFovScales[cs->aiState];
251 if ( !fov ) { // assume it's a player, give them a generic fov
252 fov = 180;
253 }
254 if ( cs->aiFlags & AIFL_ZOOMING ) {
255 fov *= 0.8;
256 } else {
257 if ( cs->lastEnemy >= 0 ) { // they've already been in a fight, so give them a very large fov
258 if ( fov < 270 ) {
259 fov = 270;
260 }
261 }
262 }
263 // RF, if they were visible last check, then give us a full FOV, since we are aware of them
264 if ( cs->aiState >= AISTATE_ALERT && vis->visible_timestamp == vis->lastcheck_timestamp ) {
265 fov = 360;
266 }
267 //calculate middle of bounding box
268 VectorAdd( destent->r.mins, destent->r.maxs, middle );
269 VectorScale( middle, 0.5, middle );
270 VectorAdd( destent->client->ps.origin, middle, middle );
271 // calculate eye position
272 if ( ( level.lastLoadTime < level.time - 4000 ) && ( srcent->r.svFlags & SVF_CASTAI ) ) {
273 if ( clientHeadTagTimes[srcent->s.number] == level.time ) {
274 // use the actual direction the head is facing
275 vectoangles( clientHeadTags[srcent->s.number].axis[0], viewangles );
276 // and the actual position of the head
277 VectorCopy( clientHeadTags[srcent->s.number].origin, eye );
278 } else if ( trap_GetTag( srcent->s.number, "tag_head", &or ) ) {
279 // use the actual direction the head is facing
280 vectoangles( or.axis[0], viewangles );
281 // and the actual position of the head
282 VectorCopy( or.origin, eye );
283 VectorMA( eye, 12, or.axis[2], eye );
284 // save orientation data
285 memcpy( &clientHeadTags[srcent->s.number], &or, sizeof( orientation_t ) );
286 clientHeadTagTimes[srcent->s.number] = level.time;
287 } else {
288 VectorCopy( srcent->client->ps.origin, eye );
289 eye[2] += srcent->client->ps.viewheight;
290 VectorCopy( srcent->client->ps.viewangles, viewangles );
291 // save orientation data (so we dont keep checking for a tag when it doesn't exist)
292 VectorCopy( eye, clientHeadTags[srcent->s.number].origin );
293 AnglesToAxis( viewangles, clientHeadTags[srcent->s.number].axis );
294 clientHeadTagTimes[srcent->s.number] = level.time;
295 }
296 } else {
297 VectorCopy( srcent->client->ps.origin, eye );
298 eye[2] += srcent->client->ps.viewheight;
299 VectorCopy( srcent->client->ps.viewangles, viewangles );
300 }
301 //check if entity is within field of vision
302 VectorSubtract( middle, eye, dir );
303 vectoangles( dir, entangles );
304 //
305 dist = VectorLength( dir );
306 //
307 // alertness is visible range
308 if ( cs->bs && dist > cs->attributes[ALERTNESS] ) {
309 return qfalse;
310 }
311 // check FOV
312 if ( !AICast_InFieldOfVision( viewangles, fov, entangles ) ) {
313 return qfalse;
314 }
315 //
316 if ( !AICast_VisibleFromPos( srcent->client->ps.origin, srcent->s.number, destent->client->ps.origin, destent->s.number, qtrue ) ) {
317 return qfalse;
318 }
319 //
320 return qtrue;
321 }
322
323 /*
324 ==============
325 AICast_UpdateVisibility
326 ==============
327 */
AICast_UpdateVisibility(gentity_t * srcent,gentity_t * destent,qboolean shareVis,qboolean directview)328 void AICast_UpdateVisibility( gentity_t *srcent, gentity_t *destent, qboolean shareVis, qboolean directview ) {
329 cast_visibility_t *vis, *ovis, *svis, oldvis;
330 cast_state_t *cs, *ocs;
331 qboolean shareRange;
332 int cnt, i;
333
334 if ( destent->flags & FL_NOTARGET ) {
335 return;
336 }
337
338 cs = AICast_GetCastState( srcent->s.number );
339 ocs = AICast_GetCastState( destent->s.number );
340
341 if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) {
342 return; // absolutely no sight (or hear) information allowed
343
344 }
345 shareRange = ( VectorDistance( srcent->client->ps.origin, destent->client->ps.origin ) < AIVIS_SHARE_RANGE );
346
347 vis = &cs->vislist[destent->s.number];
348
349 vis->chase_marker_count = 0;
350
351 if ( aicast_debug.integer == 1 ) {
352 if ( !vis->visible_timestamp || vis->visible_timestamp < level.time - 5000 ) {
353 if ( directview ) {
354 G_Printf( "SIGHT (direct): %s sees %s\n", srcent->aiName, destent->aiName );
355 } else {
356 G_Printf( "SIGHT (non-direct/audible): %s sees %s\n", srcent->aiName, destent->aiName );
357 }
358 }
359 }
360
361 // trigger the sight event
362 AICast_Sight( srcent, destent, vis->visible_timestamp );
363
364 // update the values
365 vis->lastcheck_timestamp = level.time;
366 vis->visible_timestamp = level.time;
367 VectorCopy( destent->client->ps.origin, vis->visible_pos );
368 VectorCopy( destent->client->ps.velocity, vis->visible_vel );
369 vis->lastcheck_health = destent->health - 1;
370
371 // we may need to process this visibility at some point, even after they become not visible again
372 vis->flags |= AIVIS_PROCESS_SIGHTING;
373
374 if ( directview ) {
375 vis->real_visible_timestamp = level.time;
376 VectorCopy( destent->client->ps.origin, vis->real_visible_pos );
377 vis->real_update_timestamp = level.time;
378 }
379
380 // if we are on fire, then run away from anything we see
381 if ( cs->attributes[AGGRESSION] < 1.0 && srcent->s.onFireEnd > level.time && ( !destent->s.number || cs->dangerEntityValidTime < level.time + 2000 ) && !( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) {
382 cs->dangerEntity = destent->s.number;
383 VectorCopy( destent->r.currentOrigin, cs->dangerEntityPos );
384 cs->dangerEntityValidTime = level.time + 5000;
385 cs->dangerDist = 99999;
386 cs->dangerEntityTimestamp = level.time;
387 }
388
389 // Look for reasons to make this character an enemy of ours
390
391 // if they are an enemy and inside the detection radius, go hostile
392 if ( !( vis->flags & AIVIS_ENEMY ) && !AICast_SameTeam( cs, destent->s.number ) ) {
393 float idr;
394
395 idr = cs->attributes[INNER_DETECTION_RADIUS];
396 if ( cs->aiFlags & AIFL_ZOOMING ) {
397 idr *= 10;
398 }
399 if ( !( vis->flags & AIVIS_ENEMY ) && VectorDistance( vis->visible_pos, g_entities[cs->entityNum].r.currentOrigin ) < idr ) {
400 // RF, moved them over to AICast_ScanForEnemies()
401 //AICast_ScriptEvent( cs, "enemysight", destent->aiName );
402 vis->flags |= AIVIS_ENEMY;
403 }
404 // if we are in (or above) ALERT mode, then we now know this is an enemy
405 else if ( cs->aiState >= AISTATE_ALERT ) {
406 // RF, moved them over to AICast_ScanForEnemies()
407 //AICast_ScriptEvent( cs, "enemysight", destent->aiName );
408 vis->flags |= AIVIS_ENEMY;
409 }
410 }
411
412 // if they are friendly, then we should help them out if they are in trouble
413 if ( AICast_SameTeam( cs, destent->s.number ) && ( srcent->aiTeam == AITEAM_ALLIES || srcent->aiTeam == AITEAM_NAZI ) ) {
414 // if they are dead, we should check them out
415 if ( destent->health <= 0 ) {
416 // if we haven't already checked them out
417 if ( !( vis->flags & AIVIS_INSPECTED ) ) {
418 vis->flags |= AIVIS_INSPECT;
419 }
420 // if they are mad, we should help, or at least act concerned
421 } else if ( cs->aiState < AISTATE_COMBAT && ocs->aiState >= AISTATE_COMBAT && ocs->bs && ( ocs->enemyNum >= 0 ) ) {
422 // if we haven't already checked them out
423 if ( !( vis->flags & AIVIS_INSPECTED ) ) {
424 vis->flags |= AIVIS_INSPECT;
425 }
426 // if they are alert, we should also go alert
427 } else if ( cs->aiState < AISTATE_ALERT && ocs->aiState == AISTATE_ALERT && ocs->bs ) {
428 AICast_StateChange( cs, AISTATE_ALERT );
429 }
430 }
431
432 // if this is a friendly, then check them for hostile's that we currently haven't upgraded so
433
434 if ( ( destent->health > 0 ) &&
435 ( srcent->aiTeam == destent->aiTeam ) && // only share with exact same team, and non-neutrals
436 ( srcent->aiTeam != AITEAM_NEUTRAL ) ) {
437 ocs = AICast_GetCastState( destent->s.number );
438 cnt = 0;
439 //
440 for ( i = 0; i < aicast_maxclients && cnt < level.numPlayingClients; i++ ) {
441 if ( !g_entities[i].inuse ) {
442 continue;
443 }
444 //
445 cnt++;
446 //
447 if ( i == srcent->s.number ) {
448 continue;
449 }
450 if ( i == destent->s.number ) {
451 continue;
452 }
453 //
454 ovis = &ocs->vislist[i];
455 svis = &cs->vislist[i];
456 //
457 // if we are close to the friendly, then we should share their visibility info
458 if ( destent->health > 0 && shareRange ) {
459 oldvis = *svis;
460 // if they have seen this character more recently than us, share
461 if ( ( ovis->visible_timestamp > svis->visible_timestamp ) ||
462 ( ( ovis->visible_timestamp > level.time - 5000 ) && ( ovis->flags & AIVIS_ENEMY ) && !( svis->flags & AIVIS_ENEMY ) ) ) {
463 // trigger an EVENT
464
465 // trigger the sight event
466 AICast_Sight( srcent, destent, ovis->visible_timestamp );
467
468 // we may need to process this visibility at some point, even after they become not visible again
469 svis->flags |= AIVIS_PROCESS_SIGHTING;
470
471 // if we are sharing information about an enemy, then trigger a scripted event
472 if ( !svis->real_visible_timestamp && ovis->real_visible_timestamp && ( ovis->flags & AIVIS_ENEMY ) ) {
473 // setup conditions
474 BG_UpdateConditionValue( ocs->entityNum, ANIM_COND_ENEMY_TEAM, g_entities[i].aiTeam, qfalse );
475 // call the event
476 BG_AnimScriptEvent( &g_entities[ocs->entityNum].client->ps, ANIM_ET_INFORM_FRIENDLY_OF_ENEMY, qfalse, qfalse );
477 }
478 // copy the whole structure
479 *svis = *ovis;
480 // minus the flags
481 svis->flags = oldvis.flags;
482 // keep our sight time if it's sooner
483 if ( oldvis.visible_timestamp > ovis->visible_timestamp ) {
484 svis->visible_timestamp = oldvis.visible_timestamp;
485 }
486 // check to see if we just made this character an enemy of ours
487 if ( /*(cs->aiState == AISTATE_COMBAT) &&*/ ( ovis->flags & AIVIS_ENEMY ) && !( oldvis.flags & AIVIS_ENEMY ) ) {
488 svis->flags |= AIVIS_ENEMY;
489 if ( !( cs->vislist[i].flags & AIVIS_SIGHT_SCRIPT_CALLED ) ) {
490 AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName );
491 cs->vislist[i].flags |= AIVIS_SIGHT_SCRIPT_CALLED;
492 if ( !( cs->aiFlags & AIFL_DENYACTION ) ) {
493 G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[SIGHTSOUNDSCRIPT] ) );
494 }
495 }
496 }
497 }
498 } else {
499 // if either of us haven't seen this character yet, then ignore it
500 if ( !svis->visible_timestamp || !ovis->visible_timestamp ) {
501 continue;
502 }
503 }
504 //
505 // if they have marked this character as hostile, then we should also
506 if ( ( cs->aiState == AISTATE_COMBAT ) && AICast_HostileEnemy( ocs, i ) && !AICast_HostileEnemy( cs, i ) ) {
507 if ( !( cs->vislist[i].flags & AIVIS_SIGHT_SCRIPT_CALLED ) ) {
508 AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName );
509 cs->vislist[i].flags |= AIVIS_SIGHT_SCRIPT_CALLED;
510 if ( !( cs->aiFlags & AIFL_DENYACTION ) ) {
511 G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[SIGHTSOUNDSCRIPT] ) );
512 }
513 }
514 svis->flags |= AIVIS_ENEMY;
515 }
516 }
517 }
518 }
519
520 /*
521 ==============
522 AICast_UpdateNonVisibility
523 ==============
524 */
AICast_UpdateNonVisibility(gentity_t * srcent,gentity_t * destent,qboolean directview)525 void AICast_UpdateNonVisibility( gentity_t *srcent, gentity_t *destent, qboolean directview ) {
526 cast_visibility_t *vis;
527 cast_state_t *cs;
528
529 cs = AICast_GetCastState( srcent->s.number );
530
531 vis = &cs->vislist[destent->s.number];
532
533 // update the values
534 vis->lastcheck_timestamp = level.time;
535 vis->notvisible_timestamp = level.time;
536
537 if ( directview ) {
538 vis->real_update_timestamp = level.time;
539 vis->real_notvisible_timestamp = level.time;
540 }
541
542 // if enough time has passed, and still within chase period, drop a marker
543 if ( vis->chase_marker_count < MAX_CHASE_MARKERS ) {
544 if ( ( level.time - vis->visible_timestamp ) > ( vis->chase_marker_count + 1 ) * CHASE_MARKER_INTERVAL ) {
545 VectorCopy( destent->client->ps.origin, vis->chase_marker[vis->chase_marker_count] );
546 vis->chase_marker_count++;
547 }
548 }
549 }
550
551 /*
552 ==============
553 AICast_SightUpdate
554 ==============
555 */
556 static int lastsrc = 0, lastdest = 0;
557
AICast_SightUpdate(int numchecks)558 void AICast_SightUpdate( int numchecks ) {
559 int count = 0, destcount, srccount;
560 int src = 0, dest = 0;
561 gentity_t *srcent, *destent;
562 cast_state_t *cs, *dcs;
563 //static int lastNumUpdated; // TTimo: unused
564 cast_visibility_t *vis;
565 #define SIGHT_MIN_DELAY 200
566
567 if ( numchecks < 5 ) {
568 numchecks = 5;
569 }
570
571 if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) {
572 return;
573 }
574
575 if ( saveGamePending ) {
576 return;
577 }
578
579 // First, check all REAL clients, so sighting player is only effected by reaction_time, not
580 // effected by framerate also
581 for ( srccount = 0, src = 0, srcent = &g_entities[0];
582 src < aicast_maxclients && srccount < level.numPlayingClients;
583 src++, srcent++ )
584 {
585 if ( !srcent->inuse ) {
586 continue;
587 }
588
589 srccount++;
590
591 if ( srcent->aiInactive ) {
592 continue;
593 }
594 if ( srcent->health <= 0 ) {
595 continue;
596 }
597 if ( !( srcent->r.svFlags & SVF_CASTAI ) ) { // only source check AI Cast
598 continue;
599 }
600
601 cs = AICast_GetCastState( src );
602
603 if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) {
604 continue;
605 }
606
607 // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
608 trap_AAS_SetCurrentWorld( cs->aasWorldIndex );
609
610 for ( destcount = 0, dest = 0, destent = g_entities;
611 //dest < aicast_maxclients && destcount < level.numPlayingClients;
612 destent == g_entities; // only check the player
613 dest++, destent++ )
614 {
615 if ( !destent->inuse ) {
616 continue;
617 }
618
619 destcount++;
620
621 if ( destent->health <= 0 ) {
622 continue;
623 }
624 if ( destent->r.svFlags & SVF_CASTAI ) { // only dest check REAL clients
625 continue;
626 }
627
628 if ( src == dest ) {
629 continue;
630 }
631
632 vis = &cs->vislist[destent->s.number];
633
634 // OPTIMIZATION: if we have seen the player, abort checking each frame
635 //if (vis->real_visible_timestamp && cs->aiState > AISTATE_QUERY && AICast_HostileEnemy(cs, destent->s.number))
636 // continue;
637
638 // if we saw them last frame, skip this test, so we only check initial sightings each frame
639 if ( vis->lastcheck_timestamp == vis->real_visible_timestamp ) {
640 continue;
641 }
642
643 // if we recently checked this vis, skip
644 if ( vis->lastcheck_timestamp >= level.time - ( 40 + rand() % 40 ) ) {
645 continue;
646 }
647
648 // check for visibility
649 if ( !( destent->flags & FL_NOTARGET )
650 && ( AICast_CheckVisibility( srcent, destent ) ) ) {
651 // record the sighting
652 AICast_UpdateVisibility( srcent, destent, qtrue, qtrue );
653 } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp)
654 {
655 AICast_UpdateNonVisibility( srcent, destent, qtrue );
656 }
657 }
658 }
659
660 // Now do the normal timeslice checks
661 for ( srccount = 0, src = lastsrc, srcent = &g_entities[lastsrc];
662 src < aicast_maxclients; // && srccount < level.numPlayingClients;
663 src++, srcent++ )
664 {
665 if ( !srcent->inuse ) {
666 continue;
667 }
668
669 srccount++;
670
671 if ( srcent->aiInactive ) {
672 continue;
673 }
674 if ( srcent->health <= 0 ) {
675 continue;
676 }
677
678 cs = AICast_GetCastState( src );
679
680 if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) {
681 continue;
682 }
683
684 // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
685 trap_AAS_SetCurrentWorld( cs->aasWorldIndex );
686
687 if ( lastdest < 0 ) {
688 lastdest = 0;
689 }
690
691 for ( destcount = 0, dest = lastdest, destent = &g_entities[lastdest];
692 dest < aicast_maxclients; // && destcount < level.numPlayingClients;
693 dest++, destent++ )
694 {
695 if ( !destent->inuse ) {
696 continue;
697 }
698
699 destcount++;
700
701 if ( destent->aiInactive ) {
702 continue;
703 }
704 if ( src == dest ) {
705 continue;
706 }
707
708 dcs = AICast_GetCastState( destent->s.number );
709
710 vis = &cs->vislist[destent->s.number];
711
712 // don't check too often
713 if ( vis->lastcheck_timestamp > ( level.time - SIGHT_MIN_DELAY ) ) {
714 continue;
715 }
716
717 if ( destent->health <= 0 ) {
718 // only check dead guys until they are sighted
719 if ( vis->lastcheck_health < 0 ) {
720 continue;
721 }
722 }
723
724 if ( vis->lastcheck_timestamp > level.time ) {
725 continue; // let the loadgame settle down
726
727 }
728 // if they are friends, only check very infrequently
729 if ( AICast_SameTeam( cs, destent->s.number ) && ( vis->lastcheck_timestamp == vis->visible_timestamp )
730 && ( destent->health == vis->lastcheck_health + 1 ) ) {
731 if ( dcs->aiState < AISTATE_COMBAT ) {
732 if ( vis->lastcheck_timestamp > ( level.time - ( 2000 + rand() % 1000 ) ) ) {
733 continue; // dont check too often
734 }
735 } else { // check a little more frequently
736 if ( vis->lastcheck_timestamp > ( level.time - ( 500 + rand() % 500 ) ) ) {
737 continue; // dont check too often
738 }
739 }
740 }
741
742 // check for visibility
743 if ( !( destent->flags & FL_NOTARGET )
744 && ( AICast_CheckVisibility( srcent, destent ) ) ) {
745 // make sure they are still with us
746 if ( destent->inuse ) {
747 // record the sighting
748 AICast_UpdateVisibility( srcent, destent, qtrue, qtrue );
749 }
750 } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp)
751 {
752 AICast_UpdateNonVisibility( srcent, destent, qtrue );
753 }
754
755 // break if we've processed the maximum visibilities
756 if ( ++count > numchecks ) {
757 dest++;
758 if ( dest >= aicast_maxclients ) {
759 src++;
760 }
761 goto escape;
762 }
763 }
764
765 lastdest = 0;
766 }
767
768 escape:
769
770 if ( src >= aicast_maxclients ) {
771 src = 0;
772 }
773 lastsrc = src;
774 if ( dest >= aicast_maxclients ) {
775 dest = 0;
776 }
777 lastdest = dest;
778 }
779