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_fight.c
32 // Function:		Wolfenstein AI Character Fighting/Combat
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/be_ai_weap.h"
46 #include "../botlib/botai.h"          //bot ai interface
47 
48 #include "ai_cast.h"
49 
50 /*
51 Support routines for the Decision Making layer.
52 */
53 
54 // FIXME: go through here and convert all weapon/character parameters to #define's
55 // and move them to a seperate header file for easy modification
56 
57 /*
58 =================
59 AICast_StateChange
60 
61   returns qfalse if scripting has denied the action
62 =================
63 */
AICast_StateChange(cast_state_t * cs,aistateEnum_t newaistate)64 qboolean AICast_StateChange( cast_state_t *cs, aistateEnum_t newaistate ) {
65 	gentity_t *ent;
66 	int result, scriptIndex;
67 	aistateEnum_t oldstate;
68 
69 	ent = &g_entities[cs->entityNum];
70 
71 	oldstate = cs->aiState;
72 	cs->aiState = newaistate;
73 
74 	// RF, if the state is the same, ignore
75 	if ( oldstate == newaistate ) {
76 		return qtrue;
77 	}
78 
79 	// if moving from query mode, kill the anim and pausetime
80 	if ( oldstate == AISTATE_QUERY ) {
81 		// stop playing the animation
82 		ent->client->ps.torsoTimer = 0;
83 		ent->client->ps.legsTimer = 0;
84 		cs->pauseTime = 0;
85 	}
86 
87 	// if moving to combat mode, default back to normal movetype (fast)
88 	if ( newaistate == AISTATE_COMBAT ) {
89 		cs->movestate = MS_DEFAULT;
90 		cs->movestateType = MSTYPE_NONE;
91 	}
92 
93 	scriptIndex = cs->scriptCallIndex;
94 
95 	// check scripting to see if this event should be ignored (no anim or handling)
96 	cs->aiFlags &= ~AIFL_DENYACTION;
97 	AICast_ScriptEvent( cs, "statechange", va( "%s %s", animStateStr[oldstate].string, animStateStr[newaistate].string ) );
98 
99 	if ( !( cs->aiFlags & AIFL_DENYACTION ) ) {
100 		// if no script was found, try enemysight
101 		if ( newaistate == AISTATE_COMBAT && cs->scriptCallIndex == scriptIndex &&
102 			 !( cs->vislist[cs->enemyNum].flags & AIVIS_SIGHT_SCRIPT_CALLED ) ) { // no script was found, so default back to enemysight
103 			AICast_ScriptEvent( cs, "enemysight", g_entities[cs->enemyNum].aiName );
104 			cs->vislist[cs->enemyNum].flags |= AIVIS_SIGHT_SCRIPT_CALLED;
105 			if ( !( cs->aiFlags & AIFL_DENYACTION ) ) {
106 				G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[SIGHTSOUNDSCRIPT] ) );
107 			}
108 
109 			if ( cs->aiFlags & AIFL_DENYACTION ) {
110 				// don't run any dynamic handling or default anims
111 				return qfalse;
112 			}
113 		}
114 
115 		// look for an animation
116 		result = BG_AnimScriptStateChange( &ent->client->ps, newaistate, oldstate );
117 
118 		if ( result > 0 ) {
119 			// pause while the animation plays
120 			cs->pauseTime = level.time + result;
121 		}
122 	}
123 
124 	// set query mode fields
125 	if ( newaistate == AISTATE_QUERY ) {
126 		cs->queryStartTime = level.time;
127 		if ( cs->queryCountValidTime < level.time ) {
128 			cs->queryCount = 0;
129 		} else {
130 			cs->queryCount++;
131 		}
132 		cs->queryCountValidTime = level.time + 60000;   // one minute
133 		switch ( cs->queryCount ) {
134 		case 0:
135 			cs->queryAlertSightTime = level.time + 1000;
136 			break;
137 		case 1:
138 			cs->queryAlertSightTime = level.time + 500;
139 			break;
140 		default:
141 			cs->queryAlertSightTime = -1;   // IMMEDIATE COMBAT MODE
142 			break;
143 		}
144 	}
145 
146 	return qtrue;
147 }
148 
149 /*
150 =================
151 AICast_ScanForEnemies
152 
153   returns the number of enemies visible, filling the "enemies" list before exiting
154 
155   if we only found queryEnemies (possibly hostile, but not sure) while relaxed,
156   then a negative count is returned
157 =================
158 */
AICast_ScanForEnemies(cast_state_t * cs,int * enemies)159 int AICast_ScanForEnemies( cast_state_t *cs, int *enemies ) {
160 	int i, j, enemyCount, queryCount, friendlyAlertCount;
161 	static float distances[MAX_CLIENTS];
162 	static int sortedEnemies[MAX_CLIENTS];
163 	float lastDist;
164 	int best, oldEnemy;
165 	cast_state_t *ocs;
166 
167 	if ( cs->castScriptStatus.scriptAttackEnt >= 0 ) {
168 		if ( g_entities[cs->castScriptStatus.scriptAttackEnt].health <= 0 ) {
169 			cs->castScriptStatus.scriptAttackEnt = -1;
170 		} else {
171 			// if we are not in combat mode, then an enemy should trigger a state change straight to combat mode
172 			if ( cs->aiState < AISTATE_COMBAT ) {
173 				AICast_StateChange( cs, AISTATE_COMBAT );   // just go straight to combat mode
174 			}
175 			enemies[0] = cs->castScriptStatus.scriptAttackEnt;
176 			return 1;
177 		}
178 	}
179 
180 	if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) {
181 		return qfalse;
182 	}
183 
184 	if ( cs->noAttackTime >= level.time ) {
185 		return qfalse;
186 	}
187 
188 	if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) {
189 		return qfalse;
190 	}
191 
192 	if ( cs->pauseTime > level.time ) {
193 		return qfalse;
194 	}
195 
196 	enemyCount = 0;
197 	queryCount = 0;
198 	friendlyAlertCount = 0;
199 
200 	// while we're here, may as well check for some baddies
201 	for ( i = 0; i < g_maxclients.integer; i++ )
202 	{
203 		if ( g_entities[i].inuse ) {
204 			// try not to commit suicide
205 			if ( i != cs->entityNum ) {
206 				if ( AICast_EntityVisible( cs, i, qfalse ) ) {
207 					// how should we deal with them?
208 					if ( ( g_entities[i].health > 0 ) && AICast_HostileEnemy( cs, i ) ) { // visible and a baddy!
209 						enemies[enemyCount] = i;
210 						enemyCount++;
211 						queryCount = 0;
212 						friendlyAlertCount = 0;
213 					} else if ( !enemyCount && ( g_entities[i].health > 0 ) && AICast_QueryEnemy( cs, i ) && ( cs->vislist[i].flags & AIVIS_PROCESS_SIGHTING ) ) {
214 						enemies[queryCount] = i;
215 						queryCount++;
216 						friendlyAlertCount = 0;
217 					} else if ( !queryCount && !enemyCount && ( cs->vislist[i].flags & AIVIS_INSPECT ) ) {
218 						enemies[friendlyAlertCount] = i;
219 						friendlyAlertCount++;
220 					}
221 					// the sighting has been processed
222 					cs->vislist[i].flags &= ~AIVIS_PROCESS_SIGHTING;
223 				}
224 			}
225 		}
226 	}
227 
228 	if ( !enemyCount ) {
229 		if ( queryCount ) {
230 			enemyCount = queryCount;
231 		} else if ( friendlyAlertCount ) {
232 			enemyCount = friendlyAlertCount;
233 		}
234 	}
235 
236 	if ( !enemyCount ) {  // nothing worth doing anything about
237 		// look for audible events that we should investigate
238 		if ( cs->audibleEventTime && cs->audibleEventTime < level.time && cs->audibleEventTime > level.time - 2000 ) {
239 			return -4;
240 		}
241 		// look for bullet impacts that have occured recently
242 		if ( cs->bulletImpactTime && cs->bulletImpactTime < level.time && cs->bulletImpactTime > level.time - 1000 ) {
243 			return -3;
244 		}
245 		return 0;
246 	}
247 
248 	// sort the enemies by distance
249 	for ( i = 0; i < enemyCount; i++ ) {
250 		distances[i] = Distance( cs->bs->origin, g_entities[enemies[i]].client->ps.origin );
251 		if ( !distances[i] ) {
252 			G_Printf( "WARNING: zero distance between enemies:\n%s at %s, %s at %s\n", g_entities[cs->entityNum].aiName, vtos( cs->bs->origin ), g_entities[enemies[i]].aiName, vtos( g_entities[enemies[i]].client->ps.origin ) );
253 			distances[i] = 999998;  // try to ignore them (HACK)
254 		}
255 	}
256 	for ( j = 0; j < enemyCount; j++ ) {
257 		lastDist = 999999;
258 		best = -1;
259 		for ( i = 0; i < enemyCount; i++ ) {
260 			if ( distances[i] && distances[i] < lastDist ) {
261 				lastDist = distances[i];
262 				best = i;
263 			}
264 		}
265 		if ( best < 0 ) {
266 			G_Error( "error sorting enemies by distance\n" );
267 		}
268 		sortedEnemies[j] = enemies[best];
269 		distances[best] = -1;
270 	}
271 	memcpy( enemies, sortedEnemies, sizeof( int ) * enemyCount );
272 
273 	// if we are not in combat mode, then an enemy should trigger a state change straight to combat mode
274 	if ( !queryCount && !friendlyAlertCount && enemyCount && cs->aiState < AISTATE_COMBAT ) {
275 		// face them while making the transition
276 		oldEnemy = cs->enemyNum;
277 		// set it temporarily
278 		cs->enemyNum = enemies[0];
279 		//
280 		AICast_AimAtEnemy( cs );
281 		AICast_StateChange( cs, AISTATE_COMBAT );   // just go straight to combat mode
282 		// set it back
283 		cs->enemyNum = oldEnemy;
284 	}
285 
286 	// if we are in relaxed state, and we see a query enemy, then go into query mode
287 	if ( queryCount ) {
288 		if ( cs->aiState == AISTATE_RELAXED ) {
289 			// go into query mode
290 			if ( AICast_StateChange( cs, AISTATE_QUERY ) ) {
291 				cs->enemyNum = enemies[0];  // lock onto the closest potential enemy
292 				return -1;
293 			}
294 			return 0;   // scripting obviously doesn't want us to progress from relaxed just yet
295 		}
296 		// else ignore the query mode, since we are already above relaxed mode
297 		return 0;
298 	}
299 	if ( friendlyAlertCount ) {
300 		// call a script event
301 		if ( g_entities[enemies[0]].health <= 0 ) {
302 			AICast_ForceScriptEvent( cs, "inspectbodystart", g_entities[enemies[0]].aiName );
303 			if ( cs->aiFlags & AIFL_DENYACTION ) {
304 				// ignore this friendly
305 				cs->vislist[enemies[0]].flags |= AIVIS_INSPECTED;   // they have been notified
306 				cs->vislist[enemies[0]].flags &= ~AIVIS_INSPECT;        // they have been notified
307 				return 0;
308 			}
309 		}
310 		//
311 		if ( cs->aiState < AISTATE_COMBAT ) {
312 			// go into alert mode, and return this entity so we can inspect it or something,
313 			// but let dynamic AI sort out what it wants to do
314 			if ( cs->aiState == AISTATE_ALERT || AICast_StateChange( cs, AISTATE_ALERT ) ) {
315 				// only return the entity if they are in combat mode or dead
316 				ocs = AICast_GetCastState( enemies[0] );
317 				if ( ( g_entities[enemies[0]].health <= 0 ) || ( ocs->aiState >= AISTATE_COMBAT ) ) {
318 					return -2;
319 				}
320 			}
321 			return 0;   // scripting failed or they're not worth physically inspecting
322 		}
323 		// ignore the friendly, we have our hands full
324 		return 0;
325 	}
326 
327 	// must be hostile enemy(s) found, so return them
328 	return enemyCount;
329 }
330 
331 /*
332 ==================
333 AICast_EntityVisible
334 ==================
335 */
AICast_EntityVisible(cast_state_t * cs,int enemynum,qboolean directview)336 qboolean AICast_EntityVisible( cast_state_t *cs, int enemynum, qboolean directview ) {
337 	cast_visibility_t *vis;
338 	int last_visible;
339 	int reactionTime;
340 	float dist;
341 
342 	if ( enemynum >= MAX_CLIENTS ) {
343 		return qtrue;           // FIXME: do a visibility calculation on non-client entities?
344 
345 	}
346 	vis = &cs->vislist[enemynum];
347 
348 	if ( !vis->visible_timestamp && !vis->real_visible_timestamp ) {
349 		return qfalse;  // they are not visible at all
350 
351 	}
352 	if ( directview ) {
353 		last_visible = vis->real_visible_timestamp;
354 	} else {
355 		last_visible = vis->visible_timestamp;
356 	}
357 
358 	reactionTime = (int)( 1000 * cs->attributes[REACTION_TIME] );
359 	if ( cs->startAttackCount > 1 ) {
360 		// we recently saw them, so we are more "aware" of their presence
361 		reactionTime /= 2;
362 	}
363 
364 	// if they are close, we should react faster
365 	if ( cs->bs && enemynum == cs->enemyNum ) {
366 		dist = cs->enemyDist;
367 	} else {
368 		dist = VectorDistance( g_entities[cs->entityNum].client->ps.origin, cs->vislist[enemynum].visible_pos );
369 	}
370 	if ( dist < 384 ) {
371 		reactionTime *= 0.5 + 0.5 * ( dist / 384 );
372 	}
373 
374 	if ( vis->notvisible_timestamp < ( level.time - reactionTime ) ) {
375 		// make sure we've seen them since we've last not seen them (since the visibility checking is spread amongst server frames)
376 		if ( vis->notvisible_timestamp < last_visible ) {
377 			return qtrue;
378 		}
379 	}
380 
381 	// we can't directly see them, but if they've just left our sight, pretend we can see them for another second or so
382 
383 	if ( !directview && last_visible ) {
384 		if ( vis->notvisible_timestamp > last_visible ) {
385 			if ( vis->notvisible_timestamp < ( last_visible + 5000 ) ) {
386 				return qtrue;
387 			}
388 		}
389 	}
390 
391 
392 	return qfalse;
393 }
394 
395 /*
396 ==================
397 AICast_HostileEnemy
398 
399   returns qtrue if the entity is hostile
400 ==================
401 */
AICast_HostileEnemy(cast_state_t * cs,int enemynum)402 qboolean AICast_HostileEnemy( cast_state_t *cs, int enemynum ) {
403 	// if we hate them, they are an enemy
404 	if ( cs->vislist[enemynum].flags & AIVIS_ENEMY ) {
405 		return qtrue;
406 	} else {
407 		return qfalse;
408 	}
409 }
410 
411 /*
412 ==================
413 AICast_QueryEnemy
414 
415   returns qtrue if the entity can become hostile (they hurt us or we recognize them)
416 ==================
417 */
AICast_QueryEnemy(cast_state_t * cs,int enemynum)418 qboolean AICast_QueryEnemy( cast_state_t *cs, int enemynum ) {
419 
420 	if ( g_entities[cs->entityNum].aiTeam != g_entities[enemynum].aiTeam ) {
421 		if ( g_entities[cs->entityNum].aiTeam == AITEAM_MONSTER || g_entities[enemynum].aiTeam == AITEAM_MONSTER ) {
422 			// monsters hate all non-monsters and vice-versa
423 			return qtrue;
424 		} else {
425 			// neutral's can only possibly become hostile if they hurt us, otherwise they are assumed to be harmless
426 			if ( g_entities[cs->entityNum].aiTeam == AITEAM_NEUTRAL || g_entities[enemynum].aiTeam == AITEAM_NEUTRAL ) {
427 				return qfalse;  // one of us is neutral, assume harmless
428 			}
429 			return qtrue;
430 		}
431 	} else {    // same team
432 		return qfalse;  // be cool bitch
433 	}
434 
435 }
436 
437 /*
438 ==================
439 AICast_SameTeam
440 
441   the player is always team 1, AI's default to team 0
442 
443   MONSTER's hate everyone else except other MONSTER's
444 
445   NEUTRAL's are cool with everyone that hasn't hurt them
446 ==================
447 */
AICast_SameTeam(cast_state_t * cs,int enemynum)448 qboolean AICast_SameTeam( cast_state_t *cs, int enemynum ) {
449 
450 	if ( g_entities[cs->entityNum].aiTeam != g_entities[enemynum].aiTeam ) {
451 		if ( g_entities[cs->entityNum].aiTeam == AITEAM_NEUTRAL || g_entities[enemynum].aiTeam == AITEAM_NEUTRAL ) {
452 			// if we hate them, they are an enemy
453 			if ( cs->vislist[enemynum].flags & AIVIS_ENEMY ) {
454 				return qfalse;
455 			} else {
456 				return qtrue;
457 			}
458 		} else {
459 			return qfalse;  // they are an enemy
460 		}
461 	} else {
462 		return qtrue;   // be cool bitch
463 	}
464 
465 }
466 
467 /*
468 ==================
469 AICast_WeaponRange
470 ==================
471 */
AICast_WeaponRange(cast_state_t * cs,int weaponnum)472 float AICast_WeaponRange( cast_state_t *cs, int weaponnum ) {
473 	switch ( weaponnum ) {
474 	case WP_TESLA:
475 		switch ( cs->aiCharacter ) {
476 		case AICHAR_SUPERSOLDIER:   // BOSS2
477 			// if they have a panzer, give this weapon a shorter range
478 			if ( !COM_BitCheck( cs->bs->cur_ps.weapons, WP_PANZERFAUST ) ) {
479 				return TESLA_SUPERSOLDIER_RANGE;
480 			}
481 		}
482 		return ( TESLA_RANGE * 0.9 ) - 50;  // allow for bounding box
483 	case WP_FLAMETHROWER:
484 		return ( FLAMETHROWER_RANGE * 0.5 ) - 50;   // allow for bounding box
485 	case WP_PANZERFAUST:
486 		return 8000;
487 
488 	case WP_GRENADE_LAUNCHER:
489 	case WP_GRENADE_PINEAPPLE:
490 		return 800;
491 	case WP_MONSTER_ATTACK1:
492 		switch ( cs->aiCharacter ) {
493 		case AICHAR_HEINRICH:
494 			if ( cs->weaponFireTimes[weaponnum] < level.time - 8000 ) {
495 				return 500;     // lots of room for stomping
496 			} else {
497 				return 120;     // come in real close
498 			}
499 		case AICHAR_HELGA:  // helga BOSS1 melee
500 			return 80;
501 		case AICHAR_WARZOMBIE:
502 			return 80;      // make it larger so we can start swinging early, and move in while swinging
503 		case AICHAR_LOPER:  // close attack, head-butt, fist
504 			return 60;
505 		case AICHAR_BLACKGUARD:
506 			return BLACKGUARD_MELEE_RANGE;
507 		case AICHAR_STIMSOLDIER3:
508 			return TESLA_RANGE;
509 		case AICHAR_ZOMBIE: // zombie flaming attack
510 			return ZOMBIE_FLAME_RADIUS - 50;      // get well within range before starting
511 		}
512 		break;
513 	case WP_MONSTER_ATTACK2:
514 		switch ( cs->aiCharacter ) {
515 		case AICHAR_HEINRICH:
516 			return 8000;
517 		case AICHAR_ZOMBIE: // zombie spirit attack
518 			return 1000;
519 		case AICHAR_HELGA:  // zombie spirit attack
520 			return 1900;
521 		case AICHAR_LOPER:  // loper leap attack
522 			return 8000;    // use it to gain on them also
523 		}
524 		break;
525 	case WP_MONSTER_ATTACK3:
526 		switch ( cs->aiCharacter ) {
527 		case AICHAR_HEINRICH:   // spirits
528 			return 50000;
529 		case AICHAR_LOPER:  // loper ground attack
530 			return LOPER_GROUND_RANGE;
531 		case AICHAR_WARZOMBIE:  // warzombie defense
532 			return 2000;
533 		case AICHAR_ZOMBIE:
534 			return 44;
535 		}
536 		break;
537 
538 		// Rafael added these changes as per Mikes request
539 	case WP_MAUSER:
540 	case WP_GARAND:
541 	case WP_SNIPERRIFLE:
542 	case WP_SNOOPERSCOPE:
543 		return 8000;
544 		break;
545 
546 
547 	}
548 	// default range
549 	return 3000;
550 }
551 
552 /*
553 ==================
554 AICast_CheckAttack_real
555 ==================
556 */
AICast_CheckAttack_real(cast_state_t * cs,int enemy,qboolean allowHitWorld)557 qboolean AICast_CheckAttack_real( cast_state_t *cs, int enemy, qboolean allowHitWorld ) {
558 	//float points;
559 	vec3_t forward, right, start, end, dir, up, angles;
560 	weaponinfo_t wi;
561 	trace_t trace;
562 	float traceDist;
563 	static vec3_t smins = {-6, -6, -6}, smaxs = {6, 6, 6};
564 	static vec3_t fmins = {-30, -30, -24}, fmaxs = {30, 30, 24};
565 	float *mins, *maxs;
566 	float halfHeight;
567 	int traceMask;
568 	int fuzzyCount, i;
569 	gentity_t *ent, *enemyEnt;
570 	float dist;
571 	int passEnt;
572 	int weapnum;
573 	//
574 	if ( enemy < 0 ) {
575 		return qfalse;
576 	}
577 	ent = &g_entities[cs->entityNum];
578 	enemyEnt = &g_entities[enemy];
579 	//
580 	if ( cs->bs ) {
581 		weapnum = cs->weaponNum;
582 	} else {
583 		weapnum = ent->client->ps.weapon;
584 	}
585 	//
586 	if ( !weapnum ) {
587 		return qfalse;
588 	}
589 	//
590 	// don't attack while in air (like on a ladder)
591 	if ( !ent->waterlevel && ent->client->ps.groundEntityNum == ENTITYNUM_NONE && !ent->active ) {
592 		// stim is allowed to fire while in air for flying attack
593 		if ( !ent->client->ps.powerups[PW_FLIGHT] ) {
594 			return qfalse;
595 		}
596 	}
597 	//
598 	if ( ent->health <= 0 ) {
599 		return qfalse;
600 	}
601 	// can't attack without any ammo
602 	if ( cs->bs ) {
603 		if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
604 			return qfalse;
605 		}
606 	}
607 	// special case: warzombie should play laughing anim at first sight
608 	if ( cs->aiCharacter == AICHAR_WARZOMBIE && weapnum == WP_MONSTER_ATTACK2 ) {
609 		return qtrue;
610 	}
611 	//
612 	//if the enemy isn't directly visible
613 	if ( !allowHitWorld && cs->vislist[enemy].real_visible_timestamp != cs->vislist[enemy].real_update_timestamp ) {
614 		return qfalse;
615 	}
616 	//
617 	//get the weapon info (FIXME: hard-code the weapon info?)
618 	memset( &wi, 0, sizeof( weaponinfo_t ) );
619 	//
620 	traceMask = MASK_SHOT;  // FIXME: assign mask's to different weapons
621 	//end point aiming at
622 	if ( !ent->active ) {
623 		//get the start point shooting from
624 		VectorCopy( enemyEnt->r.currentOrigin, start );
625 		start[2] += enemyEnt->client->ps.viewheight;
626 		VectorCopy( ent->r.currentOrigin, end );
627 		end[2] += ent->client->ps.viewheight;
628 		VectorSubtract( start, end, dir );
629 		vectoangles( dir, angles );
630 		AngleVectors( angles, forward, right, up );
631 		CalcMuzzlePoint( &g_entities[cs->entityNum], weapnum, forward, right, up, start );
632 
633 		traceDist = AICast_WeaponRange( cs, weapnum );
634 		switch ( weapnum ) {
635 		case WP_GAUNTLET:
636 			mins = NULL;
637 			maxs = NULL;
638 			break;
639 		case WP_DYNAMITE:
640 		case WP_PANZERFAUST:
641 		case WP_GRENADE_LAUNCHER:
642 		case WP_GRENADE_PINEAPPLE:
643 			traceMask = MASK_MISSILESHOT;
644 			mins = smins;
645 			maxs = smaxs;
646 			break;
647 		case WP_FLAMETHROWER:
648 			mins = fmins;
649 			maxs = fmaxs;
650 			break;
651 		default:
652 			mins = smins;
653 			maxs = smaxs;
654 			break;
655 		}
656 		passEnt = cs->entityNum;
657 
658 		// don't try too far
659 		dist = Distance( start, enemyEnt->r.currentOrigin );
660 		fuzzyCount = 6;
661 		if ( traceDist > dist ) {
662 			traceDist = dist;
663 		} else {
664 			dist -= enemyEnt->r.maxs[0];    // subtract distance to edge of bounding box edge
665 			if ( traceDist < dist ) {
666 				return qfalse;
667 			}
668 		}
669 	} else {
670 		gentity_t *mg42;
671 		// we are mounted on a weapon
672 		mg42 = &g_entities[cs->mountedEntity];
673 		VectorCopy( enemyEnt->r.currentOrigin, start );
674 		start[2] += enemyEnt->client->ps.viewheight;
675 		VectorCopy( mg42->r.currentOrigin, end );
676 		VectorSubtract( start, end, dir );
677 		vectoangles( dir, angles );
678 		AngleVectors( angles, forward, right, up );
679 
680 		VectorCopy( mg42->r.currentOrigin, start );
681 		VectorMA( start, 16, forward, start );
682 		VectorMA( start, 16, up, start );
683 		// snap to integer coordinates for more efficient network bandwidth usage
684 		SnapVector( start );
685 
686 		traceDist = 8192;
687 		mins = NULL;
688 		maxs = NULL;
689 		if ( mg42->mg42BaseEnt >= 0 ) {
690 			passEnt = mg42->mg42BaseEnt;
691 		} else {
692 			passEnt = cs->entityNum;
693 		}
694 
695 		// don't try too far
696 		dist = Distance( start, enemyEnt->r.currentOrigin );
697 		if ( traceDist > dist ) {
698 			traceDist = dist;
699 			fuzzyCount = 6;
700 		} else { //if (dist > traceDist - 32) {
701 			return qfalse;
702 		}
703 		/*} else {
704 			fuzzyCount = 0;
705 		}*/
706 	}
707 
708 	for ( i = 0; i <= fuzzyCount; i++ ) {
709 		VectorMA( start, traceDist, forward, end );
710 
711 		// fuzzy end point
712 		if ( i > 0 ) {
713 			VectorMA( end, enemyEnt->r.maxs[0] * 0.9 * (float)( ( i % 2 ) * 2 - 1 ), right, end );
714 			halfHeight = ( enemyEnt->r.maxs[2] - enemyEnt->r.mins[2] ) / 2.0;
715 			end[2] = ( enemyEnt->r.currentOrigin[2] + enemyEnt->r.mins[2] ) + halfHeight;
716 			VectorMA( end, halfHeight * 0.9 * ( ( (float)( ( i - 1 ) - ( ( i - 1 ) % 2 ) ) / 2 - 1.0 ) ), up, end );
717 		}
718 
719 		if ( /*allowHitWorld &&*/ !trap_InPVS( start, end ) ) {
720 			// not possibly attackable
721 			//continue;
722 			return qfalse;
723 		}
724 
725 		trap_Trace( &trace, start, mins, maxs, end, passEnt, traceMask );
726 		if ( trace.fraction == 1.0 ) {
727 			if ( !trace.startsolid ) {
728 				return qtrue;   // not sure why, but this fixes blackguards in chateau shooting through glass ceiling
729 			}
730 			//return qfalse;
731 			continue;
732 		}
733 		//if won't hit the enemy
734 		if ( trace.entityNum != enemy ) {
735 
736 			// RF, assume we can shoot through props (chairs, etc)
737 			if ( g_entities[trace.entityNum].takedamage && g_entities[trace.entityNum].health > 0 &&
738 				 !Q_strncmp( g_entities[trace.entityNum].classname, "props_", 6 ) ) {
739 				return qtrue;
740 			}
741 
742 			if ( !allowHitWorld ) {
743 				continue;
744 			}
745 
746 			if ( trace.startsolid ) {
747 				continue;
748 			}
749 
750 			//if the entity is a client
751 			if ( trace.entityNum >= 0 && trace.entityNum < MAX_CLIENTS ) {
752 				//if a teammate is hit
753 				if ( AICast_SameTeam( cs, trace.entityNum ) ) {
754 					return qfalse;
755 				}
756 			}
757 			//if the projectile does a radial damage
758 			if ( cs->weaponNum == WP_PANZERFAUST ) {
759 				if ( Distance( trace.endpos, g_entities[enemy].s.pos.trBase ) > 120 ) {
760 					continue;
761 				}
762 				//FIXME: check if a teammate gets radial damage
763 			}
764 		}
765 		// will successfully hit enemy
766 		return qtrue;
767 	}
768 	//
769 	return qfalse;
770 }
771 
772 /*
773 ==================
774 AICast_CheckAttackAtPos
775 ==================
776 */
AICast_CheckAttackAtPos(int entnum,int enemy,vec3_t pos,qboolean ducking,qboolean allowHitWorld)777 qboolean AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ) {
778 	gentity_t   *ent;
779 	vec3_t savepos;
780 	int saveview;
781 	qboolean rval;
782 	cast_state_t *cs;
783 
784 	cs = AICast_GetCastState( entnum );
785 	ent = &g_entities[cs->bs->entitynum];
786 
787 	VectorCopy( ent->r.currentOrigin, savepos );
788 	VectorCopy( pos, ent->r.currentOrigin );
789 
790 	saveview = ent->client->ps.viewheight;
791 	if ( ducking ) {
792 		if ( ent->client->ps.viewheight != ent->client->ps.crouchViewHeight ) {
793 			ent->client->ps.viewheight = ent->client->ps.crouchViewHeight;
794 		}
795 	} else {
796 		if ( ent->client->ps.viewheight != ent->client->ps.standViewHeight ) {
797 			ent->client->ps.viewheight = ent->client->ps.standViewHeight;
798 		}
799 	}
800 
801 	rval = AICast_CheckAttack_real( cs, enemy, allowHitWorld );
802 
803 	VectorCopy( savepos, ent->r.currentOrigin );
804 	ent->client->ps.viewheight = saveview;
805 
806 	return rval;
807 }
808 
809 /*
810 ==================
811 AICast_CheckAttack
812 
813   optimization, uses the cache to avoid possible duplicate calls with same world paramaters
814 ==================
815 */
AICast_CheckAttack(cast_state_t * cs,int enemy,qboolean allowHitWorld)816 qboolean AICast_CheckAttack( cast_state_t *cs, int enemy, qboolean allowHitWorld ) {
817 	if ( cs->bs ) {
818 		if (    ( cs->checkAttackCache.time == level.time )
819 				&&  ( cs->checkAttackCache.enemy == enemy )
820 				&&  ( cs->checkAttackCache.weapon == cs->weaponNum )
821 				&&  ( cs->checkAttackCache.allowHitWorld == allowHitWorld ) ) {
822 			//G_Printf( "checkattack cache hit\n" );
823 			return ( cs->checkAttackCache.result );
824 		} else {
825 			cs->checkAttackCache.allowHitWorld = allowHitWorld;
826 			cs->checkAttackCache.enemy = enemy;
827 			cs->checkAttackCache.time = level.time;
828 			cs->checkAttackCache.weapon = cs->weaponNum;
829 			return ( cs->checkAttackCache.result = AICast_CheckAttack_real( cs, enemy, allowHitWorld ) );
830 		}
831 	} else {
832 		return AICast_CheckAttack_real( cs, enemy, allowHitWorld );
833 	}
834 }
835 
836 /*
837 ==================
838 AICast_UpdateBattleInventory
839 ==================
840 */
AICast_UpdateBattleInventory(cast_state_t * cs,int enemy)841 void AICast_UpdateBattleInventory( cast_state_t *cs, int enemy ) {
842 	vec3_t dir;
843 	int i;
844 
845 	if ( enemy >= 0 ) {
846 		VectorSubtract( cs->vislist[cs->enemyNum].visible_pos, cs->bs->origin, dir );
847 		cs->enemyHeight = (int) dir[2];
848 		cs->enemyDist = (int) VectorLength( dir );
849 	}
850 
851 	// stock up ammo that should never run out
852 	for ( i = 0; i < WP_NUM_WEAPONS; i++ ) {
853 		if ( ( i >= WP_MONSTER_ATTACK1 && i <= WP_MONSTER_ATTACK3 ) || ( g_entities[cs->bs->entitynum].client->ps.ammo[ BG_FindAmmoForWeapon( i )] > 800 ) ) {
854 			//g_entities[cs->bs->entitynum].client->ps.ammo[ BG_FindAmmoForWeapon(i)] = 999;
855 			Add_Ammo( &g_entities[cs->entityNum], i, 999, qfalse );
856 		}
857 	}
858 
859 	BotAI_GetClientState( cs->entityNum, &( cs->bs->cur_ps ) );
860 
861 }
862 
863 /*
864 ==============
865 AICast_WeaponWantScale
866 ==============
867 */
AICast_WeaponWantScale(cast_state_t * cs,int weapon)868 float AICast_WeaponWantScale( cast_state_t *cs, int weapon ) {
869 	switch ( weapon ) {
870 	case WP_GAUNTLET:
871 		return 0.1;
872 	case WP_FLAMETHROWER:
873 		return 2.0;     // if we have this up close, definately use it
874 	default:
875 		return 1.0;
876 	}
877 }
878 
879 /*
880 ==============
881 AICast_GotEnoughAmmoForWeapon
882 ==============
883 */
AICast_GotEnoughAmmoForWeapon(cast_state_t * cs,int weapon)884 qboolean AICast_GotEnoughAmmoForWeapon( cast_state_t *cs, int weapon ) {
885 	gentity_t *ent;
886 	int ammo, clip;
887 
888 	ent = &g_entities[cs->entityNum];
889 	ammo = ent->client->ps.ammo[BG_FindAmmoForWeapon( weapon )];
890 	clip = ent->client->ps.ammoclip[BG_FindClipForWeapon( weapon )];
891 
892 	// TODO!! check some kind of weapon list that holds the minimum requirements for each weapon
893 	switch ( weapon ) {
894 	case WP_GAUNTLET:
895 		return qtrue;
896 	default:
897 		return (qboolean)( ( clip >= ammoTable[weapon].uses ) || ( ammo >= ammoTable[weapon].uses ) );    //----(SA)
898 	}
899 }
900 
901 /*
902 ==============
903 AICast_WeaponUsable
904 
905   This is used to prevent weapons from being selected, even if they have ammo.
906 
907   This can be used to add a delay between firing for special attacks, or make certain
908   that certain weapons are only selected within a certain range or under certain conditions.
909 
910   NOTE: that monster_attack2 will always override monster_attack1 if both are usable
911 ==============
912 */
AICast_WeaponUsable(cast_state_t * cs,int weaponNum)913 qboolean AICast_WeaponUsable( cast_state_t *cs, int weaponNum ) {
914 	int delay, hitclient;
915 	float dist = -1;
916 	gentity_t *ent, *grenade;
917 
918 	if ( cs->enemyNum >= 0 ) {
919 		dist = Distance( cs->bs->origin, g_entities[cs->enemyNum].s.pos.trBase );
920 	}
921 
922 	ent = &g_entities[cs->entityNum];
923 	delay = -1;
924 
925 	// just return qfalse if this weapon isn't ready for use
926 	switch ( weaponNum ) {
927 		// don't attempt to lob a grenade more than this often, since we will abort a grenade
928 		// throw if it's not safe, we shouldn't keep switching back too quickly
929 	case WP_DYNAMITE:
930 	case WP_GRENADE_LAUNCHER:
931 	case WP_GRENADE_PINEAPPLE:
932 		if ( cs->enemyNum < 0 ) {
933 			return qfalse;
934 		}
935 		delay = 5000;
936 		if ( dist > 0 && dist < 200 ) {
937 			return qfalse;
938 		}
939 		if ( cs->weaponFireTimes[weaponNum] < level.time - delay ) {
940 			// make sure it's safe
941 			CalcMuzzlePoints( ent, weaponNum );
942 			grenade = weapon_grenadelauncher_fire( ent, weaponNum );
943 			hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->enemyNum, g_entities[cs->enemyNum].s.pos.trBase, cs->entityNum, NULL );
944 			G_FreeEntity( grenade );
945 			if ( hitclient > -1 ) {
946 				return qtrue;
947 			} else {
948 				return qfalse;  // it's not safe
949 			}
950 		}
951 		break;
952 	case WP_TESLA:
953 		switch ( cs->aiCharacter ) {
954 		case AICHAR_STIMSOLDIER3:
955 			if ( dist < 0 || dist >= TESLA_RANGE ) {
956 				return qfalse;
957 			}
958 		}
959 		break;
960 	case WP_MONSTER_ATTACK1:
961 		switch ( g_entities[cs->entityNum].aiCharacter ) {
962 		case AICHAR_ZOMBIE: // zombie flaming attack
963 			delay = 4000;
964 			if ( dist < 0 ) { // || dist < 128) {
965 				return qfalse;
966 			}
967 			if ( dist > 1200 ) {
968 				return qfalse;
969 			}
970 			if ( cs->enemyNum < 0 ) {
971 				return qfalse;
972 			}
973 			//if (cs->vislist[cs->enemyNum].notvisible_timestamp > level.time - 500) {
974 			//	return qfalse;
975 			//}
976 			break;
977 
978 			// melee attacks are always available
979 		case AICHAR_LOPER:
980 		case AICHAR_WARZOMBIE:
981 			return qtrue;   // always usable
982 
983 		case AICHAR_STIMSOLDIER2:
984 			delay = 7000;
985 			if ( dist < 0 || dist < 300 ) {
986 				return qfalse;
987 			}
988 			break;
989 		case AICHAR_STIMSOLDIER3:   // stim flying tesla attack
990 			delay = 7000;
991 			if ( dist < 0 || dist < 300 ) {
992 				return qfalse;
993 			}
994 			break;
995 		case AICHAR_BLACKGUARD:
996 			delay = 5000;
997 			if ( dist < 0 || dist > BLACKGUARD_MELEE_RANGE ) {
998 				return qfalse;
999 			}
1000 			break;
1001 		default:
1002 			delay = -1;
1003 			break;
1004 		}
1005 		break;
1006 	case WP_MONSTER_ATTACK2:
1007 		switch ( g_entities[cs->entityNum].aiCharacter ) {
1008 		case AICHAR_HEINRICH:
1009 			delay = 6000;
1010 			break;
1011 		case AICHAR_WARZOMBIE:
1012 			delay = 9999999;
1013 			break;
1014 		case AICHAR_ZOMBIE:
1015 			delay = 6000;
1016 			// zombie "flying spirit" attack
1017 			if ( dist < 64 ) {
1018 				return qfalse;
1019 			}
1020 			if ( dist > 1200 ) {
1021 				return qfalse;
1022 			}
1023 			if ( cs->enemyNum < 0 ) {
1024 				return qfalse;
1025 			}
1026 			if ( cs->vislist[cs->enemyNum].notvisible_timestamp > level.time - 1500 ) {
1027 				return qfalse;
1028 			}
1029 			break;
1030 		case AICHAR_HELGA:
1031 			delay = 8000;
1032 			// zombie "flying spirit" attack
1033 			if ( dist < 0 || dist < 80 ) {
1034 				return qfalse;
1035 			}
1036 			if ( dist > 2000 ) {
1037 				return qfalse;
1038 			}
1039 			if ( cs->enemyNum < 0 ) {
1040 				return qfalse;
1041 			}
1042 			if ( cs->vislist[cs->enemyNum].notvisible_timestamp > level.time - 1500 ) {
1043 				return qfalse;
1044 			}
1045 			break;
1046 		case AICHAR_LOPER:  // loper leap attack
1047 			if ( cs->bs->areanum && VectorLength( cs->bs->velocity ) > 1 ) {    // if we are in a valid area, and are persuing, then leave a delay
1048 				// if there isn't a direct trace to our enemy, then fail
1049 				if ( cs->enemyNum >= 0 ) {
1050 					trace_t trace;
1051 					vec3_t mins;
1052 					VectorCopy( cs->bs->cur_ps.mins, mins );
1053 					mins[0] = 0;
1054 					trap_Trace( &trace, g_entities[cs->entityNum].client->ps.origin, mins, cs->bs->cur_ps.maxs, g_entities[cs->enemyNum].client->ps.origin, cs->entityNum, g_entities[cs->entityNum].clipmask );
1055 					if ( trace.entityNum != cs->enemyNum && trace.fraction < 1.0 ) {
1056 						return qfalse;
1057 					}
1058 				}
1059 				delay = 4500;
1060 				if ( dist < 200 ) {
1061 					return qfalse;
1062 				}
1063 			} else {
1064 				delay = 0;  // jump to get out of trouble
1065 			}
1066 			break;
1067 		default:
1068 			delay = -1;
1069 			break;
1070 		}
1071 		break;
1072 	case WP_MONSTER_ATTACK3:
1073 		switch ( g_entities[cs->entityNum].aiCharacter ) {
1074 		case AICHAR_HEINRICH:   // spirits
1075 			delay = 7000;
1076 			break;
1077 		case AICHAR_LOPER:  // loper ground zap
1078 			delay = 3500;
1079 			if ( dist < 0 || dist > LOPER_GROUND_RANGE ) {
1080 				return qfalse;
1081 			}
1082 			break;
1083 		case AICHAR_WARZOMBIE:  // warzombie defense
1084 			delay = 7000;
1085 			if ( dist < 120 || dist > 2000 ) {
1086 				return qfalse;
1087 			}
1088 			break;
1089 		case AICHAR_ZOMBIE:
1090 			return qtrue;   // always usable
1091 		default:
1092 			delay = -1;
1093 			break;
1094 		}
1095 		break;
1096 	default:
1097 		delay = -1;
1098 	}
1099 	//
1100 	return ( !cs->weaponFireTimes[weaponNum] || ( cs->weaponFireTimes[weaponNum] < level.time - delay ) );
1101 }
1102 
1103 /*
1104 ==============
1105 AICast_ChooseWeapon
1106 ==============
1107 */
AICast_ChooseWeapon(cast_state_t * cs,qboolean battleFunc)1108 void AICast_ChooseWeapon( cast_state_t *cs, qboolean battleFunc ) {
1109 	int i;
1110 	float wantScale, bestWantScale, enemyDist = 0;
1111 	qboolean inRange = qfalse, thisInRange, gotOne;
1112 
1113 	BotAI_GetClientState( cs->entityNum, &( cs->bs->cur_ps ) );
1114 	bestWantScale = 0.0;
1115 
1116 	if ( cs->enemyNum >= 0 ) {
1117 		enemyDist = VectorDistance( g_entities[cs->enemyNum].s.pos.trBase, cs->bs->origin );
1118 		// subtract distance to edge of bounding box
1119 		enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
1120 	}
1121 
1122 	if ( cs->bs->cur_ps.weaponstate == WEAPON_RAISING ||
1123 		 cs->bs->cur_ps.weaponstate == WEAPON_RAISING_TORELOAD ||   //----(SA)	added
1124 		 cs->bs->cur_ps.weaponstate == WEAPON_DROPPING ||
1125 		 cs->bs->cur_ps.weaponstate == WEAPON_DROPPING_TORELOAD ) { //----(SA)	added
1126 		return;
1127 	}
1128 
1129 // disabled this, makes grenade guy keep trying to throw a grenade he doesn't have
1130 //	if (cs->bs->cur_ps.weaponDelay || cs->bs->cur_ps.weaponTime)
1131 //		return;
1132 
1133 	if ( cs->weaponNum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) {
1134 		if ( AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) && AICast_WeaponUsable( cs, cs->weaponNum ) ) {
1135 			return;
1136 		} else {
1137 			cs->castScriptStatus.scriptFlags &= ~SFL_NOCHANGEWEAPON;
1138 		}
1139 	} else {
1140 		if ( cs->weaponNum == WP_GRENADE_LAUNCHER || cs->weaponNum == WP_GRENADE_PINEAPPLE ) {
1141 			cs->weaponNum = WP_NONE;    // dont use grenades at will
1142 		}
1143 	}
1144 
1145 	gotOne = qfalse;
1146 
1147 	// choose the best weapon to fight with
1148 	for ( i = 0; i < WP_NUM_WEAPONS; i++ ) {
1149 		if ( i == WP_GRENADE_LAUNCHER || i == WP_GRENADE_PINEAPPLE ) {
1150 			continue;   // never choose grenades at will, only when going into grenade flush mode
1151 		}
1152 
1153 		if ( !battleFunc && ( i == WP_MONSTER_ATTACK1 ) && cs->aifuncAttack1 ) {
1154 			continue;   // only choose this weapon from within AIFunc_BattleStart()
1155 		}
1156 		if ( !battleFunc && ( i == WP_MONSTER_ATTACK2 ) && cs->aifuncAttack2 ) {
1157 			continue;   // only choose this weapon from within AIFunc_BattleStart()
1158 		}
1159 		if ( !battleFunc && ( i == WP_MONSTER_ATTACK3 ) && cs->aifuncAttack3 ) {
1160 			continue;   // only choose this weapon from within AIFunc_BattleStart()
1161 		}
1162 
1163 		if ( COM_BitCheck( cs->bs->cur_ps.weapons, i ) ) {
1164 			gotOne = qtrue;
1165 			// check that our ammo is enough
1166 			if ( !AICast_GotEnoughAmmoForWeapon( cs, i ) ||
1167 				 !AICast_WeaponUsable( cs, i ) ) {
1168 				continue;
1169 			}
1170 			// get the wantScale for this weapon given the current circumstances (0.0 - 1.0)
1171 			wantScale = AICast_WeaponWantScale( cs, i );
1172 			thisInRange = qfalse;
1173 			// in range?
1174 			if ( enemyDist && AICast_WeaponRange( cs, i ) > enemyDist ) {
1175 				thisInRange = qtrue;
1176 			}
1177 			//
1178 			if ( ( !inRange && thisInRange ) || ( ( !inRange || thisInRange ) && ( wantScale >= bestWantScale ) ) ) {
1179 				cs->weaponNum = i;
1180 				bestWantScale = wantScale;
1181 				if ( thisInRange ) {  // we have found a weapon inside attackable range, don't override with one outside range
1182 					inRange = qtrue;
1183 				}
1184 			}
1185 		}
1186 	}
1187 
1188 	if ( !gotOne && ( cs->weaponNum < WP_MONSTER_ATTACK1 || cs->weaponNum > WP_MONSTER_ATTACK3 ) ) {
1189 		if ( g_cheats.integer && ( !cs->bs->cur_ps.weapons[0] && !cs->bs->cur_ps.weapons[1] ) ) {
1190 // (SA) the print statement is a bit much.  lots of actors have no ammo...
1191 //			G_Printf( "AI: %s has no ammo\n", g_entities[cs->entityNum].aiName);
1192 		}
1193 		// select no weapon
1194 		cs->weaponNum = WP_NONE;
1195 		// if we have no weapons at all, we dont need to switch
1196 		if ( !cs->bs->cur_ps.weapons[0] && !cs->bs->cur_ps.weapons[1] ) {
1197 			g_entities[cs->entityNum].client->ps.weapon = WP_NONE;
1198 		}
1199 	}
1200 }
1201 
1202 /*
1203 ==================
1204 AICast_Aggression
1205 
1206   Check all possible reasons why we shouldn't attack, returning a value from 1.0 (fully willing)
1207   to 0.0 (please don't hurt me).
1208 ==================
1209 */
AICast_Aggression(cast_state_t * cs)1210 float AICast_Aggression( cast_state_t *cs ) {
1211 	bot_state_t *bs;
1212 	float scale, dist;
1213 	int painTime;
1214 
1215 	bs = cs->bs;
1216 
1217 	// if we are out of ammo, we should never chase
1218 	if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) {
1219 		if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
1220 			return 0;
1221 		}
1222 	}
1223 
1224 	// start fully willing to attack
1225 	scale = 1.0;
1226 
1227 	//if the enemy is located way higher
1228 	//if (cs->enemyHeight > 200)
1229 	//	scale -= (cs->enemyHeight)/800.0;
1230 
1231 	//if very low on health
1232 	if ( bs->cur_ps.stats[STAT_HEALTH] < 50 ) {
1233 		scale -= ( 1.0 - cs->attributes[AGGRESSION] ) * ( 1.0 - ( (float)bs->cur_ps.stats[STAT_HEALTH] / 50.0 ) );
1234 	}
1235 
1236 	// if they've recently hit us, factor that in, so we get scared off by being
1237 	// damaged, but later return once we've regained our confidence
1238 	painTime = 15000 - (int)( 10000.0 * cs->attributes[AGGRESSION] * cs->attributes[AGGRESSION] );
1239 	if ( cs->lastPain + painTime > level.time ) {
1240 		scale -= 3 * ( 1.0 - cs->attributes[AGGRESSION] )   * ( (float)( cs->lastPain + painTime - level.time ) / (float)painTime );
1241 	}
1242 
1243 	// if we just rolled, stay out of view if we jumped behind cover
1244 	painTime = 10000 - (int)( 10000.0 * cs->attributes[AGGRESSION] * cs->attributes[AGGRESSION] );
1245 	if ( cs->battleRollTime + painTime > level.time ) {
1246 		scale -= 2 * ( 1.0 - cs->attributes[AGGRESSION] )   * ( (float)( cs->battleRollTime + painTime - level.time ) / (float)painTime );
1247 	}
1248 
1249 	// gain in confidence the further we are away
1250 	if ( cs->enemyNum >= 0 ) {
1251 		dist = Distance( cs->bs->origin, g_entities[cs->enemyNum].s.pos.trBase );
1252 		//if (dist > 512) {
1253 		scale += ( dist - 800.0 ) / ( 8000.0 );
1254 		//}
1255 	}
1256 
1257 	// if our weapon is reloading, we should hide
1258 	if ( cs->bs->cur_ps.weaponTime > 0 ) {
1259 		scale -= ( (float)cs->bs->cur_ps.weaponTime / 1000.0 );
1260 	}
1261 
1262 	scale *= cs->attributes[AGGRESSION];
1263 
1264 	// this should increase the chances of an ambush attack
1265 	if ( cs->entityNum >= 0 && ( ( level.time + 2000 * g_entities[cs->entityNum].aiTeam ) % ( 4000 + 500 * g_entities[cs->entityNum].aiTeam ) ) > 4000 ) {
1266 		if ( cs->vislist[cs->entityNum].visible_timestamp > level.time - 10000 ) {
1267 			scale += 0.3 * (float)( level.time - cs->vislist[cs->entityNum].visible_timestamp ) / 10000.0;
1268 		}
1269 	}
1270 
1271 	if ( scale < 0 ) {
1272 		scale = 0;
1273 	}
1274 
1275 	return scale;
1276 }
1277 
1278 /*
1279 ==================
1280 AICast_WantsToChase
1281 ==================
1282 */
AICast_WantsToChase(cast_state_t * cs)1283 int AICast_WantsToChase( cast_state_t *cs ) {
1284 	if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) {
1285 		if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
1286 			return qfalse;
1287 		}
1288 	}
1289 	if ( cs->attributes[AGGRESSION] == 1.0 ) {
1290 		return qtrue;
1291 	}
1292 	if ( AICast_Aggression( cs ) > 0.6 ) {
1293 		return qtrue;
1294 	}
1295 	return qfalse;
1296 }
1297 
1298 /*
1299 ==================
1300 AICast_WantsToTakeCover
1301 ==================
1302 */
AICast_WantsToTakeCover(cast_state_t * cs,qboolean attacking)1303 int AICast_WantsToTakeCover( cast_state_t *cs, qboolean attacking ) {
1304 	float aggrScale;
1305 
1306 	if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) {
1307 		if ( !cs->weaponNum ) {
1308 			return qtrue;
1309 		}
1310 		if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
1311 			return qtrue;
1312 		}
1313 	}
1314 	if ( cs->attributes[AGGRESSION] == 1.0 ) {
1315 		return qfalse;
1316 	}
1317 	// if currently attacking, we should stick around if not getting hurt
1318 	if ( attacking ) {
1319 		aggrScale = 1.2;
1320 	} else { aggrScale = 0.8 /*+ 0.4 * random()*/;}
1321 	//
1322 	// if currently following someone, we should be more aggressive
1323 	if ( cs->leaderNum >= 0 ) {
1324 		aggrScale *= 3;
1325 	}
1326 	//
1327 	// Dodge enemy aim?
1328 	if ( cs->attributes[AGGRESSION] < 1.0 && attacking && ( cs->enemyNum >= 0 ) && ( g_entities[cs->enemyNum].client->ps.weapon ) && ( cs->attributes[TACTICAL] > 0.5 ) && ( cs->aiFlags & AIFL_ROLL_ANIM ) && ( VectorLength( cs->bs->cur_ps.velocity ) < 1 ) ) {
1329 		vec3_t aim, enemyVec;
1330 		// are they aiming at us?
1331 		AngleVectors( g_entities[cs->enemyNum].client->ps.viewangles, aim, NULL, NULL );
1332 		VectorSubtract( cs->bs->origin, g_entities[cs->enemyNum].r.currentOrigin, enemyVec );
1333 		VectorNormalize( enemyVec );
1334 		// if they are looking at us, we should avoid them
1335 		if ( DotProduct( aim, enemyVec ) > 0.97 ) {
1336 			//G_Printf("%s: I'm in danger, I should probably avoid\n", g_entities[cs->entityNum].aiName);
1337 			aggrScale *= 0.6;
1338 		}
1339 	}
1340 	//
1341 	// FIXME: instead of a constant, call a "attack danger"
1342 	// function, so we only attack if our aggression is greater than
1343 	// the danger
1344 	if ( AICast_Aggression( cs ) * aggrScale < 0.4 ) {
1345 		//G_Printf("%s: run for your life!\n", g_entities[cs->entityNum].aiName);
1346 		return qtrue;
1347 	}
1348 	//
1349 	return qfalse;
1350 }
1351 
1352 /*
1353 ==================
1354 AICast_CombatMove
1355 ==================
1356 */
AICast_CombatMove(cast_state_t * cs,int tfl)1357 bot_moveresult_t AICast_CombatMove( cast_state_t *cs, int tfl ) {
1358 	bot_state_t *bs;
1359 	float dist;
1360 	vec3_t forward;
1361 	bot_moveresult_t moveresult;
1362 	bot_goal_t goal;
1363 
1364 	bs = cs->bs;
1365 
1366 	//get the enemy entity info
1367 	memset( &moveresult, 0, sizeof( bot_moveresult_t ) );
1368 	//initialize the movement state
1369 	BotSetupForMovement( bs );
1370 	//direction towards the enemy
1371 	VectorSubtract( cs->vislist[cs->enemyNum].visible_pos, bs->origin, forward );
1372 	//the distance towards the enemy
1373 	VectorNormalize( forward );
1374 	//
1375 	// do we have somewhere we are trying to get to?
1376 	if ( cs->combatGoalTime > level.time ) {
1377 		if ( VectorLength( cs->combatGoalOrigin ) > 1 ) {
1378 			//create the chase goal
1379 			goal.areanum = BotPointAreaNum( cs->combatGoalOrigin );
1380 			VectorCopy( cs->combatGoalOrigin, goal.origin );
1381 
1382 			// if we are really close, stop going for it
1383 			// FIXME: a better way of doing this, so we don't stop short of the goal?
1384 			if ( ( dist = Distance( goal.origin, cs->bs->origin ) ) < 32 ) {
1385 				if ( cs->combatGoalTime > level.time + 3000 ) {
1386 					cs->combatGoalTime = level.time + 2000 + rand() % 1000;
1387 					cs->combatSpotDelayTime = level.time + 4000 + rand() % 3000;
1388 				}
1389 				VectorClear( cs->combatGoalOrigin );
1390 			} else {
1391 				aicast_predictmove_t move;
1392 				//
1393 				AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 );
1394 				cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 32 );
1395 				//
1396 				// if we are going to move out of view very soon, stop moving
1397 				AICast_PredictMovement( cs, 1, 0.8, &move, &cs->lastucmd, -1 );
1398 				//
1399 				if ( move.numtouch || !AICast_CheckAttackAtPos( cs->entityNum, cs->enemyNum, move.endpos, qfalse, qfalse ) ) {
1400 					// abort the manouver, reached a good spot
1401 					cs->combatGoalTime = 0;
1402 					cs->combatSpotAttackCount = cs->startAttackCount;
1403 				}
1404 			}
1405 
1406 			// if we are there, and the enemy can see us, but we cant hit them, abort immediately
1407 		} else if ( !AICast_CheckAttack( cs, cs->enemyNum, qfalse ) &&
1408 					AICast_VisibleFromPos( cs->vislist[cs->enemyNum].visible_pos, cs->enemyNum, cs->bs->origin, cs->entityNum, qfalse ) ) {
1409 			cs->combatGoalTime = 0;
1410 			cs->combatSpotAttackCount = cs->startAttackCount;
1411 		}
1412 	} else {    // look for a good position to move to?
1413 		if (    (   ( cs->attributes[CAMPER] < random() )
1414 					&&  ( cs->takeCoverTime < level.time )
1415 					&&  ( cs->combatSpotAttackCount < cs->startAttackCount )
1416 					&&  ( cs->combatSpotDelayTime < level.time ) ) ) {
1417 
1418 			if (    ( cs->attributes[TACTICAL] > 0.3 + random() * 0.5 )
1419 					&&  trap_AAS_RT_GetHidePos( cs->bs->origin, cs->bs->entitynum, cs->bs->areanum, cs->vislist[cs->enemyNum].visible_pos, cs->enemyNum, BotPointAreaNum( cs->vislist[cs->enemyNum].visible_pos ), cs->combatGoalOrigin ) ) {
1420 				cs->combatGoalTime = level.time + 10000;                // give us plenty of time to get there
1421 				//cs->combatSpotAttackCount = cs->startAttackCount + 3;	// don't keep moving around to different positions on our own
1422 				cs->combatSpotDelayTime = level.time + 3000 + rand() % 3000;
1423 			} else {
1424 				// don't look again until we've moved
1425 				//cs->combatSpotAttackCount = cs->startAttackCount;
1426 				cs->combatSpotDelayTime = level.time + 3000 + rand() % 3000;
1427 			}
1428 		}
1429 	}
1430 	//
1431 	return moveresult;
1432 }
1433 
1434 /*
1435 ==================
1436 AICast_WeaponSway
1437 
1438   Some weapons should be "sprayed" around a bit while firing
1439 ==================
1440 */
AICast_WeaponSway(cast_state_t * cs,vec3_t ofs)1441 void AICast_WeaponSway( cast_state_t *cs, vec3_t ofs ) {
1442 	VectorClear( ofs );
1443 	switch ( cs->weaponNum ) {
1444 	case WP_MONSTER_ATTACK1:
1445 		if ( cs->aiCharacter != AICHAR_ZOMBIE ) {
1446 			break;      // only allow flaming zombie beyond here
1447 		}
1448 	case WP_FLAMETHROWER:
1449 		ofs[PITCH] = ( 3.0 + 4.0 * sin( ( (float)level.time / 320.0 ) ) ) * sin( ( (float)level.time / 500.0 ) );
1450 		ofs[YAW] = ( 6.0 + 8.0 * sin( ( (float)level.time / 250.0 ) ) ) * sin( ( (float)level.time / 400.0 ) );
1451 		ofs[ROLL] = 0;
1452 		break;
1453 	case WP_VENOM:
1454 		ofs[PITCH] = 2 * (float)cos( ( level.time / 200 ) );
1455 		ofs[YAW] = 10 * (float)sin( ( level.time / 150 ) ) * (float)sin( ( level.time / 100 ) );
1456 		ofs[ROLL] = 0;
1457 		break;
1458 	}
1459 }
1460 
1461 /*
1462 ==================
1463 AICast_AimAtEnemy
1464 ==================
1465 */
AICast_AimAtEnemy(cast_state_t * cs)1466 qboolean AICast_AimAtEnemy( cast_state_t *cs ) {
1467 	bot_state_t *bs;
1468 	float aim_skill, aim_accuracy;
1469 	vec3_t dir, bestorigin, start, enemyOrg;
1470 //	vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
1471 	float dist;
1472 	cast_visibility_t *vis;
1473 
1474 	//
1475 	if ( cs->castScriptStatus.scriptNoAttackTime >= ( level.time + 500 ) ) {
1476 		return qfalse;
1477 	}
1478 	//
1479 	if ( cs->noAttackTime >= level.time ) {
1480 		return qfalse;
1481 	}
1482 	//
1483 	bs = cs->bs;
1484 	//
1485 	//if the bot has no enemy
1486 	if ( cs->enemyNum < 0 ) {
1487 		return qfalse;
1488 	}
1489 	//
1490 	aim_skill = cs->attributes[AIM_SKILL];
1491 	aim_accuracy = AICast_GetAccuracy( cs->entityNum );
1492 	if ( aim_accuracy <= 0 ) {
1493 		aim_accuracy = 0.0001;
1494 	}
1495 
1496 	// StimSoldier is very good at firing Rocket Launcher
1497 	if ( cs->aiCharacter == AICHAR_STIMSOLDIER2 && cs->weaponNum == WP_PANZERFAUST ) {
1498 		aim_skill = 1;
1499 		aim_accuracy = 1;
1500 	}
1501 
1502 	//get the weapon information
1503 
1504 	//get the enemy entity information
1505 	vis = &cs->vislist[cs->enemyNum];
1506 	if ( vis->visible_timestamp < vis->lastcheck_timestamp ) {
1507 		// use our last visible position of them
1508 		if ( vis->real_visible_timestamp == vis->lastcheck_timestamp ) {
1509 			VectorCopy( vis->real_visible_pos, bestorigin );
1510 		} else {
1511 			VectorCopy( vis->visible_pos, bestorigin );
1512 		}
1513 	} else {
1514 		// we can see them, if this weapon isn't a direct-hit weapon (bullets),
1515 		// then predict where they are going to be
1516 		if ( cs->weaponNum == WP_GRENADE_LAUNCHER || cs->weaponNum == WP_GRENADE_PINEAPPLE ) {
1517 			aicast_predictmove_t move;
1518 			AICast_PredictMovement( AICast_GetCastState( cs->enemyNum ), 1, 1.0, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 );
1519 			VectorCopy( move.endpos, bestorigin );
1520 		} else {    // they are visible, use actual position
1521 			VectorCopy( g_entities[cs->enemyNum].client->ps.origin, bestorigin );
1522 		}
1523 	}
1524 
1525 	bestorigin[2] += g_entities[cs->enemyNum].client->ps.viewheight;
1526 	//get the start point shooting from
1527 	//NOTE: the x and y projectile start offsets are ignored
1528 	VectorCopy( bs->origin, start );
1529 	start[2] += bs->cur_ps.viewheight;
1530 	//
1531 	VectorCopy( bestorigin, enemyOrg );
1532 	//
1533 	// grenade hack: aim grenades at their feet if they are close
1534 	if ( cs->weaponNum == WP_GRENADE_LAUNCHER || cs->weaponNum == WP_GRENADE_PINEAPPLE ) {
1535 		if ( Distance( start, bestorigin ) < 180 ) {
1536 			bestorigin[2] = enemyOrg[2] + g_entities[cs->enemyNum].r.mins[2] + crandom() * 20;
1537 		} else if ( Distance( start, bestorigin ) > 400 ) { // aim up a bit for distance
1538 			bestorigin[2] += 12 + Distance( start, bestorigin ) / 50 + crandom() * 20;
1539 		}
1540 	}
1541 	dist = Distance( bs->eye, bestorigin );
1542 	// rocket launcher should aim ahead
1543 	if ( cs->weaponNum == WP_PANZERFAUST ) {
1544 		VectorMA( bestorigin, aim_skill * aim_skill * ( dist / 900 ), g_entities[cs->enemyNum].client->ps.velocity, bestorigin );
1545 		// if they are close, aim down at their feet
1546 		if ( dist < 512 ) {
1547 			bestorigin[2] -= ( VectorLength( g_entities[cs->enemyNum].client->ps.velocity ) / 500.0 ) * ( 1.0 - dist / 2048 ) * ( bestorigin[2] - ( g_entities[cs->enemyNum].client->ps.origin[2] + g_entities[cs->enemyNum].client->ps.mins[2] ) );
1548 		}
1549 	}
1550 	// if the enemy is moving, they are harder to hit
1551 	if ( dist > 256 ) {
1552 		VectorMA( bestorigin, ( 0.3 + 0.7 * ( 1 - aim_accuracy ) ) * 0.4 * sin( (float)level.time / ( 500.0 + ( 100.0 * ( ( cs->entityNum + 3 ) % 4 ) ) ) ), g_entities[cs->enemyNum].client->ps.velocity, bestorigin );
1553 	}
1554 	// if we are good at aiming, we should aim ahead of where they are now
1555 	// since by the time we have rotated to that direction, some time will have passed
1556 	if ( aim_skill > 0.2 ) {
1557 		VectorMA( bestorigin, aim_skill * 0.2, g_entities[cs->enemyNum].client->ps.velocity, bestorigin );
1558 	}
1559 	//get aim direction
1560 	VectorSubtract( bestorigin, bs->eye, dir );
1561 	//set the ideal view angles
1562 	vectoangles( dir, cs->ideal_viewangles );
1563 
1564 	return qtrue;   // do real aim checking after we've moved the angles
1565 }
1566 
1567 /*
1568 ==================
1569 AICast_CanMoveWhileFiringWeapon
1570 ==================
1571 */
AICast_CanMoveWhileFiringWeapon(int weaponnum)1572 qboolean AICast_CanMoveWhileFiringWeapon( int weaponnum ) {
1573 	switch ( weaponnum ) {
1574 	case WP_MAUSER:
1575 	case WP_GARAND:
1576 	case WP_SNIPERRIFLE:    //----(SA)	added
1577 	case WP_SNOOPERSCOPE:   //----(SA)	added
1578 //	case WP_FG42SCOPE:		//----(SA)	added
1579 	case WP_PANZERFAUST:
1580 		return qfalse;
1581 	default:
1582 		return qtrue;
1583 	}
1584 }
1585 
1586 /*
1587 ================
1588 AICast_RandomTriggerRelease
1589 ================
1590 */
AICast_RandomTriggerRelease(cast_state_t * cs)1591 qboolean AICast_RandomTriggerRelease( cast_state_t *cs ) {
1592 	// some characters override all weapon settings for trigger release
1593 	switch ( cs->aiCharacter ) {
1594 	case AICHAR_BLACKGUARD:     // this is here since his "ready" frame is different to his firing frame, so it looks wierd to keep swapping between them
1595 	case AICHAR_STIMSOLDIER1:
1596 	case AICHAR_STIMSOLDIER2:
1597 	case AICHAR_STIMSOLDIER3:
1598 		return qfalse;
1599 	}
1600 
1601 	switch ( cs->weaponNum ) {
1602 	case WP_MP40:
1603 	case WP_VENOM:
1604 	case WP_FG42SCOPE:
1605 	case WP_FG42:
1606 		//case WP_FLAMETHROWER:
1607 		return qtrue;
1608 	default:
1609 		return qfalse;
1610 	}
1611 }
1612 
1613 /*
1614 ==================
1615 AICast_ProcessAttack
1616 
1617   NOTE: this should always be called after the movement has been processed
1618 ==================
1619 */
AICast_ProcessAttack(cast_state_t * cs)1620 void AICast_ProcessAttack( cast_state_t *cs ) {
1621 	bot_state_t *bs;
1622 
1623 	// if our enemy is dead, stop attacking them
1624 	if ( cs->enemyNum >= 0 && g_entities[cs->enemyNum].health <= 0 ) {
1625 		return;
1626 	}
1627 	//
1628 	if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) {
1629 		return;
1630 	}
1631 	//
1632 	if ( cs->noAttackTime >= level.time ) {
1633 		return;
1634 	}
1635 	// select a weapon
1636 	AICast_ChooseWeapon( cs, qfalse );
1637 	//
1638 	if ( cs->weaponNum == WP_NONE ) {
1639 		return;
1640 	}
1641 	// never fire grenades from within here (needs special AIFunc_GrenadeFlush() code)
1642 	if ( cs->weaponNum == WP_GRENADE_LAUNCHER || cs->weaponNum == WP_GRENADE_PINEAPPLE ) {
1643 		return;
1644 	}
1645 	//
1646 	bs = cs->bs;
1647 	// check for not firing while moving flag, if present, abort attack if any movement has been issued
1648 	if ( !AICast_CanMoveWhileFiringWeapon( cs->weaponNum ) ) {
1649 		// if we are moving, don't fire
1650 		bot_input_t bi;
1651 		if ( cs->bs->cur_ps.weaponTime > 200 ) {
1652 			// if we recently fired, don't let us move for a bit
1653 			cs->speedScale = 0;
1654 			AICast_AimAtEnemy( cs );    // keep looking at them regardless
1655 		}
1656 		// if we're trying to move somewhere, don't let us shoot, until we've arrived
1657 		trap_EA_GetInput( bs->client, (float) level.time / 1000, &bi );
1658 		if (    ( cs->castScriptStatus.scriptNoMoveTime < level.time ) &&
1659 				(   ( bi.actionflags & ACTION_MOVEFORWARD ) ||
1660 					( bi.actionflags & ACTION_MOVEBACK ) ||
1661 					( bi.actionflags & ACTION_MOVELEFT ) ||
1662 					( bi.actionflags & ACTION_MOVERIGHT ) ||
1663 					( bi.speed ) ) ) {
1664 			return;
1665 		}
1666 	}
1667 	//
1668 	//if not a "walk forward" AI, then aim at the enemy regardless of whether we can attack them or not
1669 	if ( !( cs->aiFlags & AIFL_WALKFORWARD ) ) {
1670 		if ( !AICast_AimAtEnemy( cs ) ) {
1671 			return;
1672 		}
1673 	}
1674 	//
1675 	// if we are stuck in this position, we should duck if we can't hit them
1676 	if ( !AICast_CheckAttack( cs, cs->enemyNum, qfalse ) ) {
1677 		// we should duck if the enemy is shooting at us, and we can't hit them
1678 		if ( cs->attributes[ATTACK_CROUCH] && ( cs->castScriptStatus.scriptNoMoveTime >= level.time ) ) {
1679 			if ( !AICast_CheckAttackAtPos( cs->entityNum, cs->enemyNum, cs->bs->origin, qfalse, qfalse ) ) {
1680 				cs->attackcrouch_time = level.time + 2000;
1681 			} else {
1682 				cs->attackcrouch_time = 0;  // we can attack them if we stand, so go for it
1683 			}
1684 		}
1685 		return;
1686 	}
1687 	//
1688 	//if we are a "walk forward" AI, then aim at the enemy only if we can attack them
1689 	if ( cs->aiFlags & AIFL_WALKFORWARD ) {
1690 		if ( !AICast_AimAtEnemy( cs ) ) {
1691 			return;
1692 		}
1693 	}
1694 	//
1695 	// release the trigger every now and then
1696 	if ( AICast_RandomTriggerRelease( cs ) && cs->triggerReleaseTime < ( level.time - 500 ) ) {
1697 		if ( rand() % 5 == 0 ) {
1698 			cs->triggerReleaseTime = level.time + 100 + rand() % 100;
1699 			return;
1700 		}
1701 	}
1702 	//
1703 	if ( cs->triggerReleaseTime > level.time ) {
1704 		return;
1705 	}
1706 	//
1707 	// FIXME: handle fire-on-release weapons?
1708 	trap_EA_Attack( bs->client );
1709 	//
1710 	cs->bFlags |= BFL_ATTACKED;
1711 
1712 }
1713 
1714 /*
1715 ==============
1716 AICast_GetTakeCoverPos
1717 ==============
1718 */
AICast_GetTakeCoverPos(cast_state_t * cs,int enemyNum,vec3_t enemyPos,vec3_t returnPos)1719 qboolean AICast_GetTakeCoverPos( cast_state_t *cs, int enemyNum, vec3_t enemyPos, vec3_t returnPos ) {
1720 	cs->crouchHideFlag = qfalse;
1721 	//
1722 	if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) {
1723 		return qfalse;
1724 	}
1725 	//
1726 	cs->lastGetHidePos = level.time;
1727 	//
1728 	// can we just crouch?
1729 	if (    ( cs->attackcrouch_time < level.time )
1730 			&&  ( enemyNum < aicast_maxclients )
1731 			&&  AICast_CheckAttackAtPos( cs->entityNum, enemyNum, cs->bs->origin, qfalse, qfalse )
1732 			&&  !AICast_CheckAttackAtPos( cs->entityNum, enemyNum, cs->bs->origin, qtrue, qfalse ) ) {
1733 
1734 		// do a more thorough check to see if the enemy can see us if we crouch
1735 		vec3_t omaxs;
1736 		gentity_t *ent;
1737 		qboolean visible;
1738 
1739 		ent = &g_entities[cs->entityNum];
1740 		VectorCopy( ent->r.maxs, omaxs );
1741 		ent->r.maxs[2] = ent->client->ps.crouchMaxZ + 4;    // + 4 to be safe
1742 
1743 		visible = AICast_VisibleFromPos( g_entities[enemyNum].r.currentOrigin, enemyNum, cs->bs->origin, cs->entityNum, qfalse );
1744 
1745 		ent->r.maxs[2] = omaxs[2];
1746 
1747 		if ( !visible ) {
1748 			VectorCopy( enemyPos, cs->takeCoverEnemyPos );
1749 			VectorCopy( cs->bs->origin, returnPos );
1750 			cs->crouchHideFlag = qtrue;
1751 			return qtrue;
1752 		}
1753 	}
1754 	// if we are in a void, then we can't hide
1755 	// look for a hiding spot
1756 	if ( cs->bs->areanum && trap_AAS_RT_GetHidePos( cs->bs->origin, cs->bs->entitynum, cs->bs->areanum, enemyPos, enemyNum, BotPointAreaNum( enemyPos ), returnPos ) ) {
1757 		return qtrue;
1758 	}
1759 	// if we are hiding from a dangerous entity, try and avoid it
1760 	if ( cs->dangerEntity == enemyNum && cs->dangerEntityValidTime > level.time ) {
1761 		if ( cs->dangerLastGetAvoid > level.time - 750 ) {
1762 			return qtrue;
1763 		} else if ( AICast_GetAvoid( cs, NULL, cs->takeCoverPos, qtrue, cs->dangerEntity ) ) {
1764 			cs->dangerLastGetAvoid = level.time;
1765 			return qtrue;
1766 		}
1767 	}
1768 	//
1769 	return qfalse;
1770 }
1771 
1772 /*
1773 ==============
1774 AICast_AIDamageOK
1775 ==============
1776 */
AICast_AIDamageOK(cast_state_t * cs,cast_state_t * ocs)1777 qboolean AICast_AIDamageOK( cast_state_t *cs, cast_state_t *ocs ) {
1778 	if ( cs->castScriptStatus.scriptFlags & SFL_NOAIDAMAGE ) {
1779 		return qfalse;
1780 	} else {
1781 
1782 		if ( cs->aiCharacter == AICHAR_LOPER && ocs->aiCharacter == AICHAR_LOPER ) {
1783 			return qfalse;
1784 		}
1785 
1786 		return qtrue;
1787 	}
1788 }
1789 
1790 /*
1791 ===============
1792 AICast_RecordWeaponFire
1793 
1794   used for scripting, so we know when the weapon has been fired
1795 ===============
1796 */
AICast_RecordWeaponFire(gentity_t * ent)1797 void AICast_RecordWeaponFire( gentity_t *ent ) {
1798 	cast_state_t *cs;
1799 	float range;
1800 
1801 	cs = AICast_GetCastState( ent->s.number );
1802 	cs->lastWeaponFired = level.time;
1803 	cs->lastWeaponFiredWeaponNum = ent->client->ps.weapon;
1804 	VectorCopy( ent->r.currentOrigin, cs->lastWeaponFiredPos );
1805 
1806 	cs->weaponFireTimes[cs->lastWeaponFiredWeaponNum] = level.time;
1807 
1808 	// do sighting
1809 	range = AICast_GetWeaponSoundRange( cs->lastWeaponFiredWeaponNum );
1810 
1811 	AICast_AudibleEvent( cs->entityNum, cs->lastWeaponFiredPos, range );
1812 
1813 	if ( cs->bs ) {   // real player's don't need to play AI sounds
1814 		AIChar_AttackSound( cs );
1815 	}
1816 }
1817 
1818 /*
1819 ===============
1820 AICast_GetWeaponSoundRange
1821 ===============
1822 */
AICast_GetWeaponSoundRange(int weapon)1823 float AICast_GetWeaponSoundRange( int weapon ) {
1824 	// NOTE: made this a case, that way changing the ordering of weapons won't cause problems, as it would
1825 	// with an array lookup
1826 
1827 	switch ( weapon ) {
1828 	case    WP_NONE:
1829 		return 0;
1830 	case    WP_KNIFE:
1831 	case    WP_GAUNTLET:
1832 	case    WP_STEN:
1833 	case    WP_SILENCER:
1834 		return 64;
1835 	case    WP_GRENADE_LAUNCHER:
1836 	case    WP_GRENADE_PINEAPPLE:
1837 		return 1500;
1838 	case    WP_GARAND:
1839 	case    WP_SNOOPERSCOPE:
1840 		return 128;
1841 	case    WP_LUGER:
1842 	case    WP_COLT:
1843 	case    WP_AKIMBO:
1844 		return 700;
1845 
1846 	case    WP_MONSTER_ATTACK1:
1847 	case    WP_MONSTER_ATTACK2:
1848 	case    WP_MONSTER_ATTACK3:
1849 		// TODO: case for each monster
1850 		return 1000;
1851 
1852 	case    WP_MP40:
1853 	case    WP_THOMPSON:
1854 		return 1000;
1855 
1856 	case    WP_FG42:
1857 	case    WP_FG42SCOPE:
1858 		return 1500;
1859 
1860 	case    WP_SNIPERRIFLE:
1861 	case    WP_MAUSER:
1862 		return 2000;
1863 
1864 	case    WP_DYNAMITE:
1865 		return 3000;
1866 
1867 	case    WP_PANZERFAUST:
1868 	case    WP_VENOM:
1869 	case    WP_FLAMETHROWER:
1870 	case    WP_TESLA:
1871 		return 1000;
1872 	}
1873 
1874 	G_Error( "AICast_GetWeaponSoundRange: unknown weapon index: %i\n", weapon );
1875 	return 0;   // shutup the compiler
1876 }
1877 
1878 /*
1879 ===============
1880 AICast_StopAndAttack
1881 
1882   returns qtrue if they should go back to a battle state to attack,
1883   qfalse if they should keep chasing while they attack (like the Zombie)
1884 ===============
1885 */
AICast_StopAndAttack(cast_state_t * cs)1886 qboolean AICast_StopAndAttack( cast_state_t *cs ) {
1887 	float dist = -1;
1888 	cast_state_t *ecs;
1889 
1890 	if ( cs->enemyNum >= 0 ) {
1891 		dist = Distance( cs->bs->origin, g_entities[cs->enemyNum].r.currentOrigin );
1892 	}
1893 
1894 	switch ( cs->weaponNum ) {
1895 
1896 		// if they are using Venom, and are too far away to be effective, then keep chasing
1897 	case WP_VENOM:
1898 		if ( dist > 300 ) {
1899 			return qfalse;
1900 		}
1901 		// if we haven't injured them in a while, advance
1902 		if ( cs->enemyNum >= 0 ) {
1903 			ecs = AICast_GetCastState( cs->enemyNum );
1904 			if ( ecs->lastPain < level.time - 3000 ) {
1905 				return qfalse;
1906 			}
1907 		}
1908 		break;
1909 		// if they are using tesla (SUPERSOLDIER / BOSS2) try and get close
1910 	case WP_TESLA:
1911 		if ( dist > 128 /*&& (level.time%10000 < 8000)*/ ) {
1912 			return qfalse;
1913 		}
1914 		// if we haven't injured them in a while, advance
1915 		if ( cs->enemyNum >= 0 ) {
1916 			ecs = AICast_GetCastState( cs->enemyNum );
1917 			if ( ecs->lastPain < level.time - 3000 ) {
1918 				return qfalse;
1919 			}
1920 		}
1921 		break;
1922 	case WP_PANZERFAUST:
1923 		// if we haven't injured them in a while, advance
1924 		if ( ( cs->aiCharacter == AICHAR_PROTOSOLDIER || cs->aiCharacter == AICHAR_SUPERSOLDIER ) && cs->enemyNum >= 0 ) {
1925 			if ( dist > 300 ) {
1926 				return qfalse;
1927 			}
1928 			ecs = AICast_GetCastState( cs->enemyNum );
1929 			if ( ecs->lastPain < level.time - 3000 ) {
1930 				return qfalse;
1931 			}
1932 		}
1933 		break;
1934 	case WP_FLAMETHROWER:
1935 		// if we haven't injured them in a while, advance
1936 		if ( cs->aiCharacter == AICHAR_VENOM && cs->enemyNum >= 0 ) {
1937 			ecs = AICast_GetCastState( cs->enemyNum );
1938 			if ( ecs->lastPain < level.time - 3000 ) {
1939 				return qfalse;
1940 			}
1941 		}
1942 		break;
1943 
1944 	}
1945 
1946 	return qtrue;
1947 }
1948 
1949 /*
1950 ===============
1951 AICast_GetAccuracy
1952 ===============
1953 */
AICast_GetAccuracy(int entnum)1954 float AICast_GetAccuracy( int entnum ) {
1955 	#define AICAST_VARIABLE_ACC_ENABLED     1
1956 	#define AICAST_ACC_VISTIME  ( 500 + ( 3500 * ( 1.0 - aicast_skillscale ) ) )
1957 	#define AICAST_ACC_SCALE    0.4
1958 	cast_state_t *cs;
1959 	float acc;
1960 
1961 	cs = AICast_GetCastState( entnum );
1962 	// the more they stay in our sights, the more accurate we get
1963 	acc = cs->attributes[AIM_ACCURACY];
1964 
1965 	if ( AICAST_VARIABLE_ACC_ENABLED ) {
1966 		if ( cs->enemyNum >= 0 ) {
1967 			if ( cs->vislist[cs->enemyNum].real_notvisible_timestamp < level.time - AICAST_ACC_VISTIME ) {
1968 				acc += 0.5 * AICAST_ACC_SCALE;
1969 			} else {
1970 				acc += AICAST_ACC_SCALE * ( (float)( -0.5 * AICAST_ACC_VISTIME + level.time - cs->vislist[cs->enemyNum].real_notvisible_timestamp ) / (float)( AICAST_ACC_VISTIME ) );
1971 			}
1972 
1973 			if ( acc > 1.0 ) {
1974 				acc = 1.0;
1975 			} else if ( acc < 0.0 ) {
1976 				acc = 0.0;
1977 			}
1978 		}
1979 	}
1980 	return ( acc );
1981 }
1982 
1983 /*
1984 ==============
1985 AICast_WantToRetreat
1986 ==============
1987 */
AICast_WantToRetreat(cast_state_t * cs)1988 qboolean AICast_WantToRetreat( cast_state_t *cs ) {
1989 
1990 	if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) {
1991 		if ( !cs->weaponNum ) {
1992 			return qtrue;
1993 		}
1994 		if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
1995 			return qtrue;
1996 		}
1997 	}
1998 
1999 	if ( cs->attributes[AGGRESSION] >= 1.0 && cs->attributes[TACTICAL] <= 0.2 ) {
2000 		return qfalse;
2001 	}
2002 
2003 	// RF, (Last Minute Hack) big dudes should never retreat
2004 	if ( cs->aasWorldIndex != 0 ) {
2005 		return qfalse;
2006 	}
2007 
2008 	if  ( cs->leaderNum < 0 ) {
2009 		if  (   ( cs->attributes[TACTICAL] > 0.11 + random() * 0.5 ) &&
2010 				(   ( cs->bs->cur_ps.weaponTime > 500 ) ||
2011 					(   ( cs->takeCoverTime < level.time - 100 ) &&
2012 					( AICast_WantsToTakeCover( cs, qtrue ) ) ) ) ) {
2013 			return qtrue;
2014 		}
2015 	}
2016 	//
2017 	return qfalse;
2018 }
2019 
2020 /*
2021 ==============
2022 AICast_SafeMissileFire
2023 
2024   checks to see if firing the missile will be successful, neutral, or dangerous to us or a friendly
2025 ==============
2026 */
AICast_SafeMissileFire(gentity_t * ent,int duration,int enemyNum,vec3_t enemyPos,int selfNum,vec3_t endPos)2027 int AICast_SafeMissileFire( gentity_t *ent, int duration, int enemyNum, vec3_t enemyPos, int selfNum, vec3_t endPos ) {
2028 	int rval;
2029 	vec3_t org;
2030 	gentity_t   *trav;
2031 
2032 	if ( !G_PredictMissile( ent, duration, org, qtrue ) ) {
2033 		// not point firing, since it won't explode
2034 		return 0;
2035 	}
2036 
2037 	if ( endPos ) {
2038 		VectorCopy( org, endPos );
2039 	}
2040 
2041 	// at end of life, so do radius damage
2042 	rval = ( Distance( org, enemyPos ) < ent->splashRadius ) && AICast_VisibleFromPos( org, ent->s.number, enemyPos, enemyNum, qfalse );
2043 	if ( rval ) {
2044 		// don't hurt ourselves
2045 		if ( Distance( org, g_entities[selfNum].r.currentOrigin ) < ent->splashRadius * 1.5 ) {
2046 			return -1;
2047 		}
2048 		// make sure we don't injure a friendly
2049 		for ( trav = g_entities; trav < g_entities + g_maxclients.integer; trav++ ) {
2050 			if ( !trav->inuse ) {
2051 				continue;
2052 			}
2053 			if ( !trav->client ) {
2054 				continue;
2055 			}
2056 			if ( trav->health <= 0 ) {
2057 				continue;
2058 			}
2059 			if ( trav->s.number == selfNum ) {
2060 				continue;
2061 			}
2062 			if ( AICast_SameTeam( AICast_GetCastState( selfNum ), trav->s.number ) ) {
2063 				if ( Distance( org, trav->r.currentOrigin ) < ent->splashRadius ) {
2064 					return -1;
2065 				}
2066 			}
2067 		}
2068 	}
2069 	// if it overshot the mark
2070 	if ( !rval && Distance( g_entities[ent->r.ownerNum].r.currentOrigin, org ) > Distance( g_entities[ent->r.ownerNum].r.currentOrigin, enemyPos ) ) {
2071 		return -2;  // so the AI can try aiming down a bit next time
2072 	}
2073 	//
2074 	return rval;
2075 }
2076 
2077 /*
2078 =============
2079 AICast_CheckDangerousEntity
2080 
2081   check to see if the given entity can harm an AI character, if so, informs them
2082   appropriately
2083 =============
2084 */
AICast_CheckDangerousEntity(gentity_t * ent,int dangerFlags,float dangerDist,float tacticalLevel,float aggressionLevel,qboolean hurtFriendly)2085 void AICast_CheckDangerousEntity( gentity_t *ent, int dangerFlags, float dangerDist, float tacticalLevel, float aggressionLevel, qboolean hurtFriendly ) {
2086 	vec3_t org, fwd, vec;
2087 	cast_state_t *cs;
2088 	gentity_t *trav;
2089 	int i, endTime;
2090 	float dist;
2091 	//
2092 	//
2093 	if ( dangerFlags & DANGER_MISSILE ) {
2094 		// predict where the entity will explode
2095 		if ( !( endTime = G_PredictMissile( ent, ent->nextthink - level.time, org, qtrue ) ) ) {
2096 			return; // missile won't explode, so no danger
2097 		}
2098 	} else {
2099 		// just avoid it for a bit, then forget it
2100 		endTime = level.time + 1000;
2101 		VectorCopy( ent->r.currentOrigin, org );
2102 	}
2103 	if ( dangerFlags & DANGER_CLIENTAIM ) {
2104 		AngleVectors( ent->client->ps.viewangles, fwd, NULL, NULL );
2105 	}
2106 	// see if this will hurt anyone
2107 	for ( trav = g_entities, cs = AICast_GetCastState( 0 ), i = 0; i < level.numPlayingClients; cs++, trav++ ) {
2108 		if ( !trav->inuse || !trav->client ) {
2109 			continue;
2110 		}
2111 		i++;    // found a connected client
2112 		if ( trav == ent ) {  // don't be afraid of ourself
2113 			continue;
2114 		}
2115 		if ( trav->health <= 0 ) {
2116 			continue;
2117 		}
2118 		if ( !cs->bs ) {  // not an AI, they should look out for themselves
2119 			continue;
2120 		}
2121 		if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) {
2122 			continue;       // absolutely no sight (or hear) information allowed
2123 		}
2124 		if ( !hurtFriendly && ent->s.number < MAX_CLIENTS && AICast_SameTeam( cs, ent->s.number ) ) {
2125 			continue;   // trust that friends will not hurt us
2126 		}
2127 		if ( ( dangerFlags & DANGER_FLAMES ) && ( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) {
2128 			continue;   // venom not effected by flames
2129 		}
2130 		if ( cs->attributes[TACTICAL] < tacticalLevel ) { // not smart enough
2131 			continue;
2132 		}
2133 		if ( cs->aiState >= AISTATE_COMBAT && cs->attributes[AGGRESSION] > aggressionLevel ) { // we are too aggressive to worry about being hurt by this
2134 			continue;
2135 		}
2136 		// if they are below alert mode, and the danger is not in FOV, then ignore it
2137 		if ( cs->aiState < AISTATE_ALERT ) {
2138 			vec3_t ang, dir;
2139 			VectorSubtract( ent->r.currentOrigin, cs->bs->origin, dir );
2140 			VectorNormalize( dir );
2141 			vectoangles( dir, ang );
2142 			if ( !AICast_InFieldOfVision( cs->viewangles, cs->attributes[FOV], ang ) ) {
2143 				// can't see it
2144 				continue;
2145 			}
2146 		}
2147 		if (    ent->client &&
2148 				( !cs->vislist[ent->s.number].visible_timestamp || ( cs->vislist[ent->s.number].visible_timestamp < level.time - 3000 ) ) ) {
2149 			continue;   // not aware of them, and they're not aware of us
2150 		}
2151 		// are they in danger?
2152 		if ( cs->dangerEntityValidTime < level.time + 50 ) {
2153 			VectorSubtract( cs->bs->cur_ps.origin, org, vec );
2154 			dist = VectorLength( vec );
2155 			if ( dist < dangerDist ) {
2156 				if ( dangerFlags & DANGER_CLIENTAIM ) {
2157 					// also check aiming
2158 					if ( DotProduct( vec, fwd ) < ( dist * 0.95 - 100 ) ) {
2159 						continue;
2160 					}
2161 				}
2162 				//
2163 				cs->aiFlags &= ~AIFL_DENYACTION;
2164 				AICast_ScriptEvent( cs, "avoiddanger", ent->classname );
2165 				if ( cs->aiFlags & AIFL_DENYACTION ) {
2166 					continue;
2167 				}
2168 				cs->dangerEntity = ent->s.number;
2169 				VectorCopy( org, cs->dangerEntityPos );
2170 				cs->dangerEntityValidTime = endTime + 50;
2171 				cs->dangerDist = dangerDist * 1.5;    // when we hide from it, get a good distance away
2172 				cs->dangerEntityTimestamp = level.time;
2173 			}
2174 		}
2175 	}
2176 }
2177 
2178 
AICast_HasFiredWeapon(int entNum,int weapon)2179 qboolean AICast_HasFiredWeapon( int entNum, int weapon ) {
2180 	if ( AICast_GetCastState( entNum )->weaponFireTimes[weapon] ) {
2181 		return qtrue;
2182 	}
2183 
2184 	return qfalse;
2185 }
2186 
AICast_AllowFlameDamage(int entNum)2187 qboolean AICast_AllowFlameDamage( int entNum ) {
2188 	// DHM - Nerve :: caststates are not initialized in multiplayer
2189 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
2190 		return qtrue;
2191 	}
2192 	// dhm
2193 
2194 	if ( caststates[entNum].aiFlags & AIFL_NO_FLAME_DAMAGE ) {
2195 		return qfalse;
2196 	}
2197 	return qtrue;
2198 }
2199 
2200 /*
2201 =============
2202 AICast_ProcessBullet
2203 =============
2204 */
AICast_ProcessBullet(gentity_t * attacker,vec3_t start,vec3_t end)2205 void AICast_ProcessBullet( gentity_t *attacker, vec3_t start, vec3_t end ) {
2206 	gentity_t   *tent;
2207 	int i;
2208 	float dist;
2209 	vec3_t vProj, vDir, dir;
2210 	cast_state_t *cs;
2211 
2212 	VectorSubtract( end, start, dir );
2213 
2214 	// RF, AI should hear this pass by them really closely, or hitting a wall close by
2215 	for ( cs = caststates, tent = g_entities, i = 0; i < level.maxclients; i++, tent++, cs++ ) {
2216 		if ( !tent->inuse ) {
2217 			continue;
2218 		}
2219 		if ( tent == attacker ) {
2220 			continue;
2221 		}
2222 		if ( tent->aiInactive ) {
2223 			continue;
2224 		}
2225 		if ( tent->health <= 0 ) {
2226 			continue;
2227 		}
2228 		if ( cs->castScriptStatus.scriptNoSightTime > level.time ) {
2229 			continue;
2230 		}
2231 		if ( !( tent->r.svFlags & SVF_CASTAI ) ) {
2232 			continue;
2233 		}
2234 		if ( cs->aiState >= AISTATE_COMBAT ) { // RF add	// already fighting, not interested in bullet impacts
2235 			continue;
2236 		}
2237 		if ( cs->bulletImpactIgnoreTime > level.time ) {
2238 			continue;
2239 		}
2240 		dist = Distance( tent->client->ps.origin, end );
2241 		if ( dist <= cs->attributes[INNER_DETECTION_RADIUS] ) {
2242 			// close enough to hear/see the impact?
2243 			// first check pvs
2244 			if ( !trap_InPVS( tent->client->ps.origin, end ) ) {
2245 				continue;
2246 			}
2247 			// heard it
2248 			goto heard;
2249 		}
2250 		// are they within radius of the bullet path to hear it travel through the air?
2251 		ProjectPointOntoVector( tent->client->ps.origin, start, end, vProj );
2252 		VectorSubtract( vProj, start, vDir );
2253 		if ( DotProduct( vDir, dir ) < 0 ) {  // they are behind the path of the bullet
2254 			continue;
2255 		}
2256 		if ( Distance( vProj, tent->client->ps.origin ) > 0.5 * cs->attributes[INNER_DETECTION_RADIUS] ) {
2257 			continue;
2258 		}
2259 heard:
2260 		// call the script event
2261 		AICast_ScriptEvent( cs, "bulletimpact", "" );
2262 		if ( cs->aiFlags & AIFL_DENYACTION ) {
2263 			continue;   // ignore the bullet
2264 		}
2265 		//
2266 		cs->bulletImpactTime = level.time + 100 + rand() % 200;   // random reaction delay;
2267 		VectorCopy( start, cs->bulletImpactStart );
2268 		VectorCopy( end, cs->bulletImpactEnd );
2269 		cs->bulletImpactEntity = attacker->s.number;
2270 	}
2271 }
2272 
2273 /*
2274 ================
2275 AICast_AudibleEvent
2276 ================
2277 */
AICast_AudibleEvent(int srcnum,vec3_t pos,float range)2278 void AICast_AudibleEvent( int srcnum, vec3_t pos, float range ) {
2279 	int i;
2280 	cast_state_t *cs, *scs = 0;
2281 	gentity_t *ent, *sent;
2282 	float adjustedRange, localDist;
2283 
2284 	// DHM - Nerve :: caststates are not initialized in multiplayer
2285 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
2286 		return;
2287 	}
2288 	// dhm
2289 
2290 	if ( g_debugAudibleEvents.integer ) {
2291 		G_Printf( "AICast_AudibleEvent: (%0.1f %0.1f %0.1f) range: %0.0f\n",  pos[0], pos[1], pos[2], range );
2292 	}
2293 
2294 	sent = &g_entities[srcnum];
2295 	if ( sent->flags & FL_NOTARGET ) {
2296 		if ( g_debugAudibleEvents.integer ) {
2297 			G_Printf( "NOTARGET enabled, aborting\n" );
2298 		}
2299 		return;
2300 	}
2301 	if ( srcnum < level.maxclients ) {
2302 		scs = AICast_GetCastState( srcnum );
2303 	}
2304 
2305 	for ( ent = g_entities, cs = caststates, i = 0; i < level.maxclients; i++, cs++, ent++ ) {
2306 		if ( !cs->bs ) {
2307 			continue;
2308 		}
2309 		if ( ent == sent ) {
2310 			continue;
2311 		}
2312 		if ( cs->castScriptStatus.scriptNoSightTime > level.time ) {
2313 			continue;
2314 		}
2315 		if ( ent->health <= 0 ) {
2316 			continue;
2317 		}
2318 		// if within range, and this sound was made by an enemy
2319 		if ( scs ) {
2320 			if ( ( srcnum < level.maxclients ) && scs->aiState < AISTATE_COMBAT && !AICast_QueryEnemy( cs, srcnum ) ) {
2321 				continue;
2322 			}
2323 		}
2324 		// calculate the adjusted range according to this AI's hearing abilities
2325 		adjustedRange = range * cs->attributes[HEARING_SCALE];
2326 		localDist = DistanceSquared( pos, ent->s.pos.trBase );
2327 		if ( localDist > adjustedRange * adjustedRange ) {  // fast out if already outside range
2328 			continue;
2329 		}
2330 		if ( !trap_InPVS( pos, ent->s.pos.trBase ) ) {
2331 			adjustedRange *= cs->attributes[HEARING_SCALE_NOT_PVS];
2332 		}
2333 		if ( localDist > adjustedRange * adjustedRange ) {
2334 			continue;
2335 		}
2336 		// we heard it
2337 
2338 		if ( g_debugAudibleEvents.integer ) {
2339 			G_Printf( "AICast_AudibleEvent heard: %s \"%s\" (dist:%0.0f s:%0.2f pvss:%0.2f)\n", ent->classname, ent->aiName, ( sqrt( localDist ) ), cs->attributes[HEARING_SCALE], cs->attributes[HEARING_SCALE_NOT_PVS] );
2340 		}
2341 
2342 		cs->audibleEventTime = level.time + 200 + rand() % 300;   // random reaction delay
2343 		VectorCopy( pos, cs->audibleEventOrg );
2344 		cs->audibleEventEnt = ent->s.number;
2345 	}
2346 }
2347