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