1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein multiplayer 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 multiplayer GPL Source Code (“RTCW MP Source Code”).
8 
9 RTCW MP 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 MP 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 MP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW MP 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 MP 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  * name:		ai_cast_funcs.c
31  *
32  * desc:		Wolfenstein AI Character Decision Making
33  *
34 */
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 This file contains the generic thinking states for the characters.
51 
52 Different types of movement or behaviour will be represented by
53 a seperate thinking function, which may or may not pass control
54 over to a new behaviour function.
55 
56 If control is passed onto a new function, the string name of the
57 current function is returned, mostly for debugging purposes.
58 
59 !!! NOTE: control must not be passed to a new AI func from outside of
60 this file. A new AI func must only be called from within another AI func.
61 
62 This gives us the ability to keep all code related to sections of AI
63 self-contained, so adding new features to the AI will be less likely to
64 step on other areas of AI.
65 */
66 
67 static int enemies[MAX_CLIENTS], numEnemies;
68 
69 // this is used to prevent try/abort/try/abort/etc grenade flush behaviour
70 static int lastGrenadeFlush = 0;
71 
72 #define AICAST_LEADERDIST_MAX   240     // try and stay at least this close to them when nothing else to do
73 #define AICAST_LEADERDIST_MIN   64      // get this close if we have a clear line of sight to them
74 
75 char *AIFunc_BattleChase( cast_state_t *cs );
76 char *AIFunc_Battle( cast_state_t *cs );
77 
78 static bot_moveresult_t *moveresult;
79 
80 /*
81 ============
82 AIFunc_Restore()
83 
84   restores the last aifunc that was backed up
85 ============
86 */
AIFunc_Restore(cast_state_t * cs)87 char *AIFunc_Restore( cast_state_t *cs ) {
88 	// if the old aifunc was BattleChase, set it back to Battle, in case we have found a good position
89 	if ( cs->oldAifunc == AIFunc_BattleChase ) {
90 		cs->oldAifunc = AIFunc_Battle;
91 	}
92 	cs->aifunc = cs->oldAifunc;
93 	return cs->aifunc( cs );
94 }
95 
96 /*
97 ============
98 AICast_GetRandomViewAngle()
99 ============
100 */
AICast_GetRandomViewAngle(cast_state_t * cs,float tracedist)101 float AICast_GetRandomViewAngle( cast_state_t *cs, float tracedist ) {
102 	int cnt, passent, contents_mask;
103 	vec3_t vec, dir, start, end;
104 	trace_t trace;
105 	float bestdist, bestyaw;
106 
107 	cnt = 0;
108 	VectorClear( vec );
109 	//
110 	VectorCopy( cs->bs->origin, start );
111 	start[2] += cs->bs->cur_ps.viewheight;
112 	//
113 	passent = cs->bs->entitynum;
114 	contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WATER | CONTENTS_SLIME;
115 //	contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WATER;
116 	bestdist = 0;
117 	bestyaw = 0;
118 	//
119 	while ( cnt++ < 4 )
120 	{
121 		vec[YAW] = random() * 360.0;
122 		//
123 		AngleVectors( vec, dir, NULL, NULL );
124 		VectorMA( start, tracedist, dir, end );
125 		//
126 		trap_Trace( &trace, start, NULL, NULL, end, passent, contents_mask );
127 		//
128 		if ( trace.fraction >= 1 ) {
129 			return vec[YAW];
130 		} else if ( !bestdist || bestdist < trace.fraction ) {
131 			bestdist = trace.fraction;
132 			bestyaw = vec[YAW];
133 		}
134 	}
135 	//
136 	if ( bestdist ) {
137 		return bestyaw;
138 	}
139 	// just return their current direction
140 	return cs->bs->ideal_viewangles[YAW];
141 }
142 
143 /*
144 ============
145 AICast_MoveToPos()
146 
147   returns a pointer to the moveresult it used to make the move, so we can investigate it
148   outside of this function
149 ============
150 */
AICast_MoveToPos(cast_state_t * cs,vec3_t pos,int entnum)151 bot_moveresult_t *AICast_MoveToPos( cast_state_t *cs, vec3_t pos, int entnum ) {
152 	bot_goal_t goal;
153 	vec3_t /*target,*/ dir;
154 	static bot_moveresult_t lmoveresult;
155 	int tfl;
156 	bot_state_t *bs;
157 
158 //int pretime = Sys_MilliSeconds();
159 
160 	moveresult = NULL;
161 
162 	if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) {
163 		return NULL;
164 	}
165 	if ( cs->pauseTime > level.time ) {
166 		return NULL;
167 	}
168 	//
169 	bs = cs->bs;
170 	tfl = cs->travelflags;
171 	//if in lava or slime the bot should be able to get out
172 	if ( BotInLava( bs ) ) {
173 		tfl |= TFL_LAVA;
174 	}
175 	if ( BotInSlime( bs ) ) {
176 		tfl |= TFL_SLIME;
177 	}
178 	//
179 	//create the chase goal
180 	memset( &goal, 0, sizeof( goal ) );
181 	goal.entitynum = entnum;
182 	goal.areanum = BotPointAreaNum( pos );
183 	VectorCopy( pos, goal.origin );
184 	VectorSet( goal.mins, -8, -8, -8 );
185 	VectorSet( goal.maxs, 8, 8, 8 );
186 	if ( entnum == cs->followEntity && !cs->followSlowApproach ) {
187 		goal.flags |= GFL_NOSLOWAPPROACH;   // just speed right passed it
188 	}
189 	//
190 	// debugging, show the route
191 	if ( aicast_debug.integer == 2 && ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) {
192 		trap_AAS_RT_ShowRoute( cs->bs->origin, cs->bs->areanum, goal.areanum );
193 	}
194 	//
195 	//initialize the movement state
196 	BotSetupForMovement( bs );
197 	//if this is a slow moving creature, don't use avoidreach
198 	if ( cs->attributes[RUNNING_SPEED] < 100 ) {
199 		//reset the avoid reach, otherwise bot is stuck in current area
200 		trap_BotResetAvoidReach( bs->ms );
201 	} else if ( !VectorCompare( cs->lastMoveToPosGoalOrg, pos ) ) {
202 		//reset the avoid reach, otherwise bot is stuck in current area
203 		trap_BotResetAvoidReach( bs->ms );
204 		VectorCopy( pos, cs->lastMoveToPosGoalOrg );
205 	}
206 	//move towards the goal
207 	trap_BotMoveToGoal( &lmoveresult, bs->ms, &goal, tfl );
208 	//if the movement failed
209 	if ( lmoveresult.failure ) {
210 		//reset the avoid reach, otherwise bot is stuck in current area
211 		trap_BotResetAvoidReach( bs->ms );
212 		//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", lmoveresult.traveltype);
213 		// clear all movement
214 		trap_EA_Move( cs->entityNum, vec3_origin, 0 );
215 	}
216 	//
217 	if ( lmoveresult.flags & ( MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) {
218 		VectorCopy( lmoveresult.ideal_viewangles, bs->ideal_viewangles );
219 		VectorCopy( bs->ideal_viewangles, cs->viewlock_viewangles );
220 		cs->aiFlags |= AIFL_VIEWLOCKED;
221 	} else if ( !( cs->bs->flags & BFL_ATTACKED ) )       { // if we are attacking, don't change angles
222 		bot_input_t bi;
223 
224 		trap_EA_GetInput( bs->client, 0.1, &bi );
225 		if ( VectorLength( lmoveresult.movedir ) < 0.5 ) {
226 			VectorSubtract( goal.origin, bs->origin, dir );
227 			vectoangles( dir, bs->ideal_viewangles );
228 		} else {
229 			// use our velocity if we are moving
230 			if ( VectorNormalize2( cs->bs->cur_ps.velocity, dir ) > 1 ) {
231 				vectoangles( dir, bs->ideal_viewangles );
232 			} else {
233 				vectoangles( lmoveresult.movedir, bs->ideal_viewangles );
234 			}
235 		}
236 		bs->ideal_viewangles[2] *= 0.5;
237 // disabled, needs work
238 /*
239 		if (lmoveresult.flags & MOVERESULT_FUTUREVIEW) {
240 			if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) > 45)
241 				bs->ideal_viewangles[1] += 45;
242 			else if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) < -45)
243 				bs->ideal_viewangles[1] -= 45;
244 			else
245 				bs->ideal_viewangles[1] = lmoveresult.ideal_viewangles[1];
246 			bs->ideal_viewangles[1] = AngleNormalize360( bs->ideal_viewangles[1] );
247 			bs->ideal_viewangles[0] = lmoveresult.ideal_viewangles[0];
248 			bs->ideal_viewangles[0] = 0.5*AngleNormalize180( bs->ideal_viewangles[0] );
249 		}
250 */
251 	}
252 	// this must go last so we face the direction we avoid move
253 	AICast_Blocked( cs, &lmoveresult, qfalse, &goal );
254 
255 //	G_Printf("MoveToPos: %i ms\n", -pretime + Sys_MilliSeconds() );
256 
257 	// debug, print movement info
258 	if ( 0 ) {	// (SA) added to hide the print
259 		bot_input_t bi;
260 
261 		trap_EA_GetInput( cs->bs->client, (float) level.time / 1000, &bi );
262 		G_Printf( "spd: %i\n", (int)bi.speed );
263 	}
264 
265 	return &lmoveresult;
266 }
267 
268 /*
269 ============
270 AICast_SpeedScaleForDistance()
271 ============
272 */
AICast_SpeedScaleForDistance(cast_state_t * cs,float startdist,float idealDist)273 float AICast_SpeedScaleForDistance( cast_state_t *cs, float startdist, float idealDist ) {
274 #define PREDICT_TIME_WALK   0.2
275 #define PREDICT_TIME_CROUCH 0.2
276 #define PREDICT_TIME_RUN    0.3
277 	float speed, dist;
278 
279 	dist = startdist - idealDist;
280 	if ( dist < 1 ) {
281 		dist = 1;
282 	}
283 
284 	// if walking
285 	if ( cs->movestate == MS_WALK ) {
286 		speed = cs->attributes[WALKING_SPEED];
287 		if ( speed * PREDICT_TIME_WALK > dist ) {
288 			return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_WALK ) );
289 		} else {
290 			return 1.0;
291 		}
292 	} else
293 	// if crouching
294 	if ( cs->movestate == MS_CROUCH || cs->bs->attackcrouch_time > trap_AAS_Time() ) {
295 		speed = cs->attributes[RUNNING_SPEED] * cs->bs->cur_ps.crouchSpeedScale;
296 		if ( speed * PREDICT_TIME_CROUCH > dist ) {
297 			return 0.3 + 0.7 * ( dist / ( speed * PREDICT_TIME_CROUCH ) );
298 		} else {
299 			return 1.0;
300 		}
301 	} else
302 	// running
303 	{
304 		speed = cs->attributes[RUNNING_SPEED];
305 		if ( speed * PREDICT_TIME_RUN > dist ) {
306 			return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_RUN ) );
307 		} else {
308 			return 1.0;
309 		}
310 	}
311 }
312 
313 /*
314 ============
315 AIFunc_Idle()
316 
317   The cast AI is standing around, contemplating the meaning of life
318 ============
319 */
AIFunc_Idle(cast_state_t * cs)320 char *AIFunc_Idle( cast_state_t *cs ) {
321 
322 	// we are in an idle state, looking for something to do
323 
324 	//
325 	// do we need to avoid a danger?
326 	if ( cs->dangerEntityValidTime >= level.time ) {
327 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
328 			// shit??
329 		}
330 		// go to a position that cannot be seen from the dangerPos
331 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
332 		cs->bs->attackcrouch_time = 0;
333 		return AIFunc_AvoidDangerStart( cs );
334 	}
335 	//
336 	// are we waiting for a door?
337 	if ( cs->doorMarkerTime > level.time - 100 ) {
338 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
339 	}
340 	//
341 	// do we need to go to our leader?
342 	if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
343 		return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
344 	}
345 	//
346 	// look for things we should attack
347 	numEnemies = AICast_ScanForEnemies( cs, enemies );
348 	if ( numEnemies == -1 ) {     // query mode
349 		return NULL;
350 	} else if ( numEnemies == -2 )     { // inspection may be required
351 		char *retval;
352 
353 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
354 			return retval;
355 		}
356 	} else if ( numEnemies == -3 )     { // bullet impact
357 		if ( cs->aiState < AISTATE_COMBAT ) {
358 			return AIFunc_InspectBulletImpactStart( cs );
359 		}
360 	} else if ( numEnemies == -4 )     { // audible event
361 		if ( cs->aiState < AISTATE_COMBAT ) {
362 			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
363 		}
364 	} else if ( numEnemies > 0 )     {
365 		int i;
366 
367 		cs->bs->enemy = -1;
368 		// choose an enemy
369 		for ( i = 0; i < numEnemies; i++ ) {
370 			if ( Distance( cs->bs->origin, cs->vislist[enemies[i]].visible_pos ) > 16 ) { // if we are really close to the last place we saw them, no point trying to attack, since we'll just end up back here
371 				if ( cs->bs->enemy < 0 ) {
372 					cs->bs->enemy = enemies[i];
373 				} else if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
374 					cs->bs->enemy = enemies[i];
375 					return AIFunc_BattleStart( cs );
376 				}
377 			}
378 		}
379 		if ( cs->bs->enemy >= 0 ) {
380 			if ( ( ( cs->leaderNum < 0 ) || ( cs->thinkFuncChangeTime < level.time - 3000 ) ) && AICast_WantsToChase( cs ) ) {  // don't leave our leader as soon as we get to them
381 				return AIFunc_BattleStart( cs );
382 			} else if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) {
383 				// if we are tactical enough, look for a hiding spot
384 				if ( !( cs->leaderNum >= 0 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[AGGRESSION] < 1.0 ) {
385 					// they can see us, and we want to hide from them
386 					if ( !AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
387 						// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
388 						cs->takeCoverTime = level.time + 2000 + rand() % 3000;
389 						return AIFunc_BattleTakeCoverStart( cs );
390 					}
391 				}
392 				// attack them if nothing else to do, since they can attack us here
393 				return AIFunc_BattleStart( cs );
394 			} else if ( cs->leaderNum < 0 ) {     // we should pursue if no leader, and not wanting to hide
395 				return AIFunc_BattleStart( cs );
396 			} else {
397 				// they can't see us anyway, so ignore them
398 				cs->lastEnemy = cs->bs->enemy;  // at least face them if they come to get us
399 				cs->bs->enemy = -1;
400 				// crouching makes us look like we are hiding, which is what we are doing
401 				if ( cs->attributes[ATTACK_CROUCH] > 0.5 ) {
402 					cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
403 				}
404 			}
405 		}
406 	}
407 	//
408 	// if we are in combat mode, then we should relax, since we dont have an enemy
409 	if ( cs->aiState >= AISTATE_COMBAT ) {
410 		AICast_StateChange( cs, AISTATE_ALERT );
411 	}
412 	//
413 	// if we couldn't find anything, see if our previous enemy is still around, if so, go find them
414 	// this is an attempt to prevent guys from running away to hide from something, never to
415 	// be seen again. They shouldn't really "forget" that they are indeed soldiers.
416 	if ( !( cs->leaderNum >= 0 ) && cs->lastEnemy >= 0 && g_entities[cs->lastEnemy].health > 0 && cs->vislist[cs->lastEnemy].real_visible_timestamp < level.time - 5000 &&
417 		 cs->takeCoverTime < level.time - 5000 ) {
418 		cs->bs->enemy = cs->lastEnemy;  // just go to the place we last saw them
419 		return AIFunc_BattleStart( cs );
420 	}
421 	//
422 	// if we've recently been in a fight, keep looking around, so we don't look stupid
423 	if ( cs->lastEnemy >= 0 ) {
424 		//
425 		// if we like to crouch, then do so, since we are in a battle situation
426 /*		if (cs->lastEnemy > 0) {	// not the player
427 			if (cs->attributes[ATTACK_CROUCH] > 0.3) {
428 				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
429 			}
430 		} else {	// if we were attacking the player, relax a bit more
431 			if (cs->attributes[ATTACK_CROUCH] > 0.6) {
432 				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
433 			}
434 		}
435 */                                                                                                                                                                                                                                                                                                                                    // if we only just recently saw them, face that direction
436 		if ( ( g_entities[cs->lastEnemy].health > 0 ) && cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 )
437 			 && AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->vislist[cs->lastEnemy].visible_pos, cs->lastEnemy, qfalse ) ) {
438 			vec3_t dir;
439 			//
440 			VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
441 			if ( VectorLength( dir ) < 1 ) {
442 				cs->bs->ideal_viewangles[PITCH] = 0;
443 			} else {
444 				VectorNormalize( dir );
445 				vectoangles( dir, cs->bs->ideal_viewangles );
446 				cs->bs->ideal_viewangles[PITCH] = AngleNormalize180( cs->bs->ideal_viewangles[PITCH] ) * 0.5;
447 			}
448 			// if we like to crouch, then do so, since we are in a battle situation
449 //			if (cs->attributes[ATTACK_CROUCH] > 0.1) {
450 //				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
451 //			}
452 		} else if ( /*cs->castScriptStatus.castScriptEventIndex < 0 &&*/
453 			cs->attributes[TACTICAL] && cs->nextIdleAngleChange < level.time ) {
454 			// wait a second before changing again
455 			if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {
456 
457 				// FIXME: This could be changed to use some AAS sampling, which would:
458 				//
459 				//	Given a src area, pick a random dest area which is visible from that area
460 				//	and return it's position, which we'd then use to set the next view angles
461 				//
462 				//	This would result in more efficient, more realistic behaviour, since they'd
463 				//	also use PITCH angles to look at areas above/below them
464 
465 				cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );
466 
467 				if ( fabs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
468 					cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
469 				} else { // do really fast
470 					cs->nextIdleAngleChange = level.time + 500;
471 				}
472 
473 				// adjust with time
474 				cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
475 				/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);
476 
477 				cs->bs->ideal_viewangles[PITCH] = 0;
478 			}
479 		} else if ( cs->idleYawChange )     {
480 			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
481 			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
482 		}
483 	}
484 
485 	// check for a movement we should be making
486 	if ( cs->obstructingTime > level.time ) {
487 		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
488 		if ( cs->movestate != MS_CROUCH ) {
489 			cs->movestate = MS_WALK;
490 		}
491 		cs->movestateType = MSTYPE_TEMPORARY;
492 	}
493 
494 	// set head look flag if no enemy
495 	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
496 		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
497 	}
498 
499 	// reload?
500 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
501 		trap_EA_Reload( cs->entityNum );
502 	}
503 
504 	return NULL;
505 }
506 
507 /*
508 ============
509 AIFunc_IdleStart()
510 ============
511 */
AIFunc_IdleStart(cast_state_t * cs)512 char *AIFunc_IdleStart( cast_state_t *cs ) {
513 	g_entities[cs->entityNum].flags &= ~FL_AI_GRENADE_KICK;
514 	// stop following
515 	cs->followEntity = -1;
516 	// if our enemy has just died, inspect the body
517 	if ( cs->bs->enemy >= 0 ) {
518 		if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI && g_entities[cs->bs->enemy].health <= 0 ) {
519 			return AIFunc_InspectBodyStart( cs );
520 		} else {
521 			cs->bs->enemy = -1;
522 		}
523 	}
524 	// make sure we don't avoid any areas when we start again
525 	trap_BotInitAvoidReach( cs->bs->ms );
526 
527 	// randomly choose idle animation
528 //----(SA)	try always using the 'casual' stand on spawn and change to crouching one when 'alerted'
529 	if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
530 //		if (rand()%2 || (cs->lastEnemy < 0 && cs->aiFlags & AIFL_TALKING))
531 		g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2;
532 //		else
533 //			g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
534 	}
535 
536 	cs->aifunc = AIFunc_Idle;
537 	return "AIFunc_Idle";
538 }
539 
540 /*
541 ============
542 AIFunc_InspectFriendly()
543 ============
544 */
AIFunc_InspectFriendly(cast_state_t * cs)545 char *AIFunc_InspectFriendly( cast_state_t *cs ) {
546 	gentity_t   *followent, *ent;
547 	bot_state_t *bs;
548 	vec3_t destorg;
549 	float dist;
550 	qboolean moved = qfalse;
551 
552 	ent = &g_entities[cs->entityNum];
553 
554 	// if we have an enemy, attack now!
555 	if ( cs->bs->enemy >= 0 ) {
556 		return AIFunc_BattleStart( cs );
557 	}
558 
559 	cs->followEntity = cs->inspectNum;
560 	cs->followDist = 64;
561 
562 	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc
563 
564 	// do we need to avoid a danger?
565 	if ( cs->dangerEntityValidTime >= level.time ) {
566 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
567 			// go to a position that cannot be seen from the dangerPos
568 			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
569 			cs->bs->attackcrouch_time = 0;
570 			cs->castScriptStatus.scriptGotoId = -1;
571 			cs->movestate = MS_DEFAULT;
572 			cs->movestateType = MSTYPE_NONE;
573 			return AIFunc_AvoidDangerStart( cs );
574 		}
575 	}
576 	//
577 	// are we waiting for a door?
578 	if ( cs->doorMarkerTime > level.time - 100 ) {
579 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
580 	}
581 
582 	followent = &g_entities[cs->followEntity];
583 
584 	// if the entity is not ready yet
585 	if ( !followent->inuse ) {
586 		// if it's a connecting client, wait
587 		if (    cs->followEntity < MAX_CLIENTS
588 				&&  (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
589 						|| ( level.time < 3000 ) ) ) {
590 			return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
591 		} else    // stop following it
592 		{
593 			AICast_EndChase( cs );
594 			return AIFunc_IdleStart( cs );
595 		}
596 	}
597 
598 	if ( followent->client ) {
599 		VectorCopy( followent->client->ps.origin, destorg );
600 	} else {
601 		VectorCopy( followent->r.currentOrigin, destorg );
602 	}
603 
604 	// they are ready, are they inside range? FIXME: make configurable
605 	dist = Distance( destorg, cs->bs->origin );
606 	if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) {
607 		//
608 		// go to them
609 		//
610 		bs = cs->bs;
611 
612 		// set this flag so we know when we;ve just reached them
613 		cs->aiFlags |= AIFL_MISCFLAG1;
614 
615 		// move straight to them if we can
616 		if (    !moved &&
617 				( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
618 			aicast_predictmove_t move;
619 			vec3_t dir;
620 			bot_input_t bi;
621 			usercmd_t ucmd;
622 			trace_t tr;
623 			qboolean simTest = qfalse;
624 
625 			if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
626 				simTest = qtrue;
627 			}
628 
629 			if ( !simTest ) {
630 				// trace will eliminate most unsuccessful paths
631 				trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
632 				if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) {
633 					simTest = qtrue;
634 				}
635 			}
636 
637 			if ( simTest ) {
638 				// try walking straight to them
639 				VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
640 				VectorNormalize( dir );
641 				if ( !ent->waterlevel ) {
642 					dir[2] = 0;
643 				}
644 				trap_EA_Move( cs->entityNum, dir, 400 );
645 				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
646 				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
647 				AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity );
648 
649 				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
650 					vectoangles( dir, cs->bs->ideal_viewangles );
651 					cs->bs->ideal_viewangles[2] *= 0.5;
652 					moved = qtrue;
653 				}
654 			}
655 		}
656 		//
657 		if ( !moved ) {
658 			// use AAS routing
659 			moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity );
660 			// if we cant get there, forget it
661 			if ( moveresult && moveresult->failure ) {
662 				return AIFunc_DefaultStart( cs );
663 			}
664 		}
665 
666 		// should we slow down?
667 		if ( cs->followDist && cs->followSlowApproach ) {
668 			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
669 		}
670 /*
671 		// check for a movement we should be making
672 		if (cs->obstructingTime > level.time)
673 		{
674 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
675 			if (cs->movestate != MS_CROUCH) {
676 				cs->movestate = MS_WALK;
677 			}
678 			cs->movestateType = MSTYPE_TEMPORARY;
679 		}
680 */
681 	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
682 		cs->aiFlags &= ~AIFL_MISCFLAG1;
683 		if ( g_entities[cs->inspectNum].health <= 0 ) {
684 			// call a script event
685 			cs->aiFlags &= ~AIFL_DENYACTION;
686 			AICast_ForceScriptEvent( cs, "inspectbodyend", g_entities[cs->inspectNum].aiName );
687 			if ( cs->aiFlags & AIFL_DENYACTION ) {
688 				// relinguish control back to scripting
689 				return AIFunc_DefaultStart( cs );
690 			}
691 		}
692 	}
693 
694 	{
695 		int numEnemies;
696 		//
697 		// look for things we should attack
698 		numEnemies = AICast_ScanForEnemies( cs, enemies );
699 		if ( numEnemies == -1 ) { // query mode
700 			return NULL;
701 		} else if ( numEnemies == -2 )     { // inspection
702 			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
703 			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {
704 				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
705 			}
706 		}
707 		// RF, disabled this, if we are interrupted, scripts might not work right, and anyway, this is only a bullet, not as if it's a dead guy or anything
708 		//else if (numEnemies == -3)	// bullet impact
709 		//{
710 		//	if (cs->aiState < AISTATE_COMBAT) {
711 		//		return AIFunc_InspectBulletImpactStart( cs );
712 		//	}
713 		//}
714 		else if ( numEnemies > 0 ) {
715 			int i;
716 
717 			cs->bs->enemy = enemies[0]; // just attack the first one
718 			// override with a visible enemy
719 			for ( i = 1; i < numEnemies; i++ ) {
720 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
721 					cs->bs->enemy = enemies[i];
722 					break;
723 				} else if ( cs->bs->enemy < 0 ) {
724 					cs->lastEnemy = enemies[i];
725 				}
726 			}
727 
728 			return AIFunc_BattleStart( cs );
729 		}
730 	}
731 
732 	if ( cs->nextIdleAngleChange < level.time ) {
733 		// wait a second before changing again
734 		if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {
735 
736 			// FIXME: This could be changed to use some AAS sampling, which would:
737 			//
738 			//	Given a src area, pick a random dest area which is visible from that area
739 			//	and return it's position, which we'd then use to set the next view angles
740 			//
741 			//	This would result in more efficient, more realistic behaviour, since they'd
742 			//	also use PITCH angles to look at areas above/below them
743 
744 			cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );
745 
746 			if ( fabs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
747 				cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
748 			} else { // do really fast
749 				cs->nextIdleAngleChange = level.time + 500;
750 			}
751 
752 			// adjust with time
753 			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
754 			/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);
755 
756 			cs->bs->ideal_viewangles[PITCH] = 0;
757 		}
758 	} else if ( cs->idleYawChange )     {
759 		cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
760 		cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
761 	}
762 
763 	// set head look flag if no enemy
764 	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
765 		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
766 	}
767 
768 	// reload?
769 	if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
770 		trap_EA_Reload( cs->entityNum );
771 	}
772 
773 	return NULL;
774 }
775 
776 /*
777 ============
778 AIFunc_InspectFriendlyStart
779 ============
780 */
AIFunc_InspectFriendlyStart(cast_state_t * cs,int entnum)781 char *AIFunc_InspectFriendlyStart( cast_state_t *cs, int entnum ) {
782 	cast_state_t *ocs;
783 
784 	ocs = AICast_GetCastState( entnum );
785 
786 	// we are about to deal with the request for inspection
787 	cs->vislist[entnum].flags &= ~AIVIS_INSPECT;
788 	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc
789 
790 	if ( ocs->aiState >= AISTATE_COMBAT || g_entities[entnum].health <= 0 ) {
791 		// mark this character as having been inspected
792 		cs->vislist[entnum].flags |= AIVIS_INSPECTED;
793 	}
794 
795 	// what should we do? wait here? hide? go see them?
796 
797 	// if dead, go see them
798 	if ( g_entities[entnum].health <= 0 ) {
799 		cs->inspectNum = entnum;
800 		cs->aifunc = AIFunc_InspectFriendly;
801 		return "AIFunc_InspectFriendlyStart";
802 	}
803 
804 	// not dead, so call scripting event
805 	AICast_ForceScriptEvent( cs, "inspectfriendlycombatstart", g_entities[entnum].aiName );
806 	if ( cs->aiFlags & AIFL_DENYACTION ) {
807 		// ignore this friendly forever and ever amen
808 		cs->vislist[entnum].flags |= AIVIS_INSPECTED;
809 		return NULL;
810 	}
811 
812 	// if they are in combat, then act according to aggressiveness
813 	if ( ocs->aiState >= AISTATE_COMBAT ) {
814 		if ( cs->attributes[AGGRESSION] < 0.3 ) {
815 			if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) {
816 				cs->takeCoverTime = level.time + 10000; // hide for 10 seconds
817 				cs->scriptPauseTime = cs->takeCoverTime;
818 				// crouch there if possible
819 				if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
820 					cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
821 				}
822 				return AIFunc_BattleTakeCoverStart( cs );
823 			}
824 		}
825 	}
826 
827 	// if still around, then we need to go to them
828 	cs->inspectNum = entnum;
829 	cs->aifunc = AIFunc_InspectFriendly;
830 	return "AIFunc_InspectFriendly";
831 }
832 
833 /*
834 ============
835 AIFunc_InspectBulletImpact()
836 ============
837 */
AIFunc_InspectBulletImpact(cast_state_t * cs)838 char *AIFunc_InspectBulletImpact( cast_state_t *cs ) {
839 	gentity_t *ent;
840 	vec3_t v1;
841 	//
842 	//
843 	ent = &g_entities[cs->entityNum];
844 	//
845 	cs->bulletImpactIgnoreTime = level.time + 800;
846 	//
847 	// wait until we are looking at the impact
848 	if ( cs->aiFlags & AIFL_MISCFLAG2 ) {
849 		// pause any scripting
850 		cs->scriptPauseTime = level.time + 1000;
851 		// look at bullet impact
852 		VectorSubtract( cs->bulletImpactEnd, cs->bs->origin, v1 );
853 		VectorNormalize( v1 );
854 		vectoangles( v1, cs->bs->ideal_viewangles );
855 		//
856 		// if we are facing that direction, we've looked at the impact point
857 		if ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 1 ) {
858 			cs->aiFlags &= ~AIFL_MISCFLAG2;
859 		}
860 		return NULL;
861 	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
862 		// clear the flag now
863 		cs->aiFlags &= ~AIFL_MISCFLAG1;
864 		// start looking back at bullet
865 		VectorSubtract( cs->bulletImpactStart, cs->bs->origin, v1 );
866 		VectorNormalize( v1 );
867 		vectoangles( v1, cs->bs->ideal_viewangles );
868 		if ( cs->aiState < AISTATE_ALERT ) {
869 			// change to alert state
870 			if ( !AICast_StateChange( cs, AISTATE_ALERT ) ) {
871 				// stop doing whatever we are doing, and return control to scripting
872 				cs->scriptPauseTime = 0;
873 				return AIFunc_IdleStart( cs );
874 			}
875 			// make sure we didn't change thinkfunc
876 			if ( cs->aifunc != AIFunc_InspectBulletImpact ) {
877 				//G_Error( "scripting passed control out of AIFunc_InspectBulletImpact(), this is bad" );
878 				return NULL;
879 			}
880 		}
881 		// pause any scripting
882 		if ( ent->client->ps.legsTimer ) {
883 			cs->scriptPauseTime = level.time + ent->client->ps.legsTimer;
884 		} else { // just wait for a few seconds looking at the source
885 			cs->scriptPauseTime = level.time + 3500;
886 		}
887 	}
888 	// are we done?
889 	if ( cs->scriptPauseTime < level.time ) {
890 		return AIFunc_IdleStart( cs );
891 	}
892 	//
893 	// reload?
894 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
895 		trap_EA_Reload( cs->entityNum );
896 	}
897 	//
898 	// check for enemies
899 	{
900 		int numEnemies;
901 		//
902 		// look for things we should attack
903 		numEnemies = AICast_ScanForEnemies( cs, enemies );
904 		if ( numEnemies == -2 ) { // inspection
905 			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
906 			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {
907 
908 				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
909 			}
910 		} else if ( numEnemies > 0 )     {
911 			int i;
912 
913 			cs->bs->enemy = enemies[0]; // just attack the first one
914 			// override with a visible enemy
915 			for ( i = 1; i < numEnemies; i++ ) {
916 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
917 					cs->bs->enemy = enemies[i];
918 					break;
919 				} else if ( cs->bs->enemy < 0 ) {
920 					cs->lastEnemy = enemies[i];
921 				}
922 			}
923 
924 			return AIFunc_BattleStart( cs );
925 		}
926 	}
927 	//
928 	return NULL;
929 }
930 
931 /*
932 ============
933 AIFunc_InspectBulletImpactStart()
934 ============
935 */
AIFunc_InspectBulletImpactStart(cast_state_t * cs)936 char *AIFunc_InspectBulletImpactStart( cast_state_t *cs ) {
937 	int oldScriptIndex;
938 	// set the impact timer so we ignore bullets while inspecting this one
939 	cs->bulletImpactIgnoreTime = level.time + 5000;
940 	// pause any scripting
941 	cs->scriptPauseTime = level.time + 1000;
942 	// set this so we know if we've started the trace back to the bullet origin
943 	cs->aiFlags |= AIFL_MISCFLAG1;
944 	cs->aiFlags |= AIFL_MISCFLAG2;
945 	//
946 	// call the script event
947 	oldScriptIndex = cs->scriptCallIndex;
948 	AICast_ScriptEvent( cs, "bulletimpactsound", "" );
949 	if ( oldScriptIndex == cs->scriptCallIndex ) {
950 		// no script event, so call the animation script
951 		BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_BULLETIMPACT, qfalse, qtrue );
952 	}
953 	//
954 	cs->aifunc = AIFunc_InspectBulletImpact;
955 	return "AIFunc_InspectBulletImpact";
956 }
957 
958 /*
959 ============
960 AIFunc_InspectAudibleEvent()
961 ============
962 */
AIFunc_InspectAudibleEvent(cast_state_t * cs)963 char *AIFunc_InspectAudibleEvent( cast_state_t *cs ) {
964 	gentity_t   *ent;
965 	bot_state_t *bs;
966 	vec3_t destorg;
967 	float dist;
968 	qboolean moved = qfalse;
969 
970 	ent = &g_entities[cs->entityNum];
971 
972 	// if we have an enemy, attack now!
973 	if ( cs->bs->enemy >= 0 ) {
974 		return AIFunc_BattleStart( cs );
975 	}
976 
977 	cs->followDist = 64;
978 
979 	// do we need to avoid a danger?
980 	if ( cs->dangerEntityValidTime >= level.time ) {
981 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
982 			// go to a position that cannot be seen from the dangerPos
983 			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
984 			cs->bs->attackcrouch_time = 0;
985 			cs->castScriptStatus.scriptGotoId = -1;
986 			cs->movestate = MS_DEFAULT;
987 			cs->movestateType = MSTYPE_NONE;
988 			return AIFunc_AvoidDangerStart( cs );
989 		}
990 	}
991 	//
992 	// are we waiting for a door?
993 	if ( cs->doorMarkerTime > level.time - 100 ) {
994 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
995 	}
996 
997 	VectorCopy( cs->audibleEventOrg, destorg );
998 
999 	// they are ready, are they inside range? FIXME: make configurable
1000 	dist = Distance( destorg, cs->bs->origin );
1001 	if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) {
1002 		//
1003 		// go to them
1004 		//
1005 		bs = cs->bs;
1006 
1007 		// set this flag so we know when we;ve just reached them
1008 		cs->aiFlags |= AIFL_MISCFLAG1;
1009 
1010 		// if not overly aggressive, pursue with caution
1011 		if ( cs->attributes[AGGRESSION] <= 0.8 ) {
1012 			cs->movestate = MS_CROUCH;
1013 			cs->movestateType = MSTYPE_TEMPORARY;
1014 		}
1015 
1016 		// move straight to them if we can
1017 		if (    !moved &&
1018 				( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
1019 			aicast_predictmove_t move;
1020 			vec3_t dir;
1021 			bot_input_t bi;
1022 			usercmd_t ucmd;
1023 			trace_t tr;
1024 			qboolean simTest = qfalse;
1025 
1026 			if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
1027 				simTest = qtrue;
1028 			}
1029 
1030 			if ( !simTest ) {
1031 				// trace will eliminate most unsuccessful paths
1032 				trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, destorg, cs->entityNum, g_entities[cs->entityNum].clipmask );
1033 				if ( tr.fraction == 1 ) {
1034 					simTest = qtrue;
1035 				}
1036 			}
1037 
1038 			if ( simTest ) {
1039 				// try walking straight to them
1040 				gentity_t *gent;
1041 				//
1042 				gent = G_Spawn();
1043 				VectorCopy( destorg, gent->r.currentOrigin );
1044 				//
1045 				VectorSubtract( destorg, cs->bs->origin, dir );
1046 				VectorNormalize( dir );
1047 				if ( !ent->waterlevel ) {
1048 					dir[2] = 0;
1049 				}
1050 				trap_EA_Move( cs->entityNum, dir, 400 );
1051 				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
1052 				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
1053 				AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, gent->s.number );
1054 				//
1055 				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
1056 					vectoangles( dir, cs->bs->ideal_viewangles );
1057 					cs->bs->ideal_viewangles[2] *= 0.5;
1058 					moved = qtrue;
1059 				}
1060 				//
1061 				G_FreeEntity( gent );
1062 			}
1063 		}
1064 		//
1065 		if ( !moved ) {
1066 			// use AAS routing
1067 			AICast_MoveToPos( cs, destorg, -1 );
1068 			// if we cant get there, forget it
1069 			if ( moveresult && moveresult->failure ) {
1070 				return AIFunc_DefaultStart( cs );
1071 			}
1072 		}
1073 
1074 		// should we slow down?
1075 		if ( cs->followDist && cs->followSlowApproach ) {
1076 			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
1077 		}
1078 /*
1079 		// check for a movement we should be making
1080 		if (cs->obstructingTime > level.time)
1081 		{
1082 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
1083 			if (cs->movestate != MS_CROUCH) {
1084 				cs->movestate = MS_WALK;
1085 			}
1086 			cs->movestateType = MSTYPE_TEMPORARY;
1087 		}
1088 */
1089 	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
1090 		cs->aiFlags &= ~AIFL_MISCFLAG1;
1091 		// call a script event
1092 		cs->aiFlags &= ~AIFL_DENYACTION;
1093 		AICast_ForceScriptEvent( cs, "inspectsoundend", g_entities[cs->audibleEventEnt].aiName );
1094 		if ( cs->aiFlags & AIFL_DENYACTION ) {
1095 			// relinguish control back to scripting
1096 			return AIFunc_DefaultStart( cs );
1097 		}
1098 	} else {
1099 		// look around randomly
1100 		if ( cs->battleHuntViewTime < level.time ) {
1101 			cs->battleHuntViewTime = level.time + 700 + rand() % 1000;
1102 			// set a random viewangle
1103 			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) );
1104 		}
1105 		//
1106 		if ( cs->scriptPauseTime < level.time ) {
1107 			// we're done waiting around here
1108 			return AIFunc_DefaultStart( cs );
1109 		}
1110 	}
1111 
1112 	{
1113 		int numEnemies;
1114 		//
1115 		// look for things we should attack
1116 		numEnemies = AICast_ScanForEnemies( cs, enemies );
1117 		if ( numEnemies == -1 ) { // query mode
1118 			return NULL;
1119 		} else if ( numEnemies == -2 )     { // inspection
1120 			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
1121 			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {
1122 				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
1123 			}
1124 		} else if ( numEnemies > 0 )     {
1125 			int i;
1126 
1127 			cs->bs->enemy = enemies[0]; // just attack the first one
1128 			// override with a visible enemy
1129 			for ( i = 1; i < numEnemies; i++ ) {
1130 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
1131 					cs->bs->enemy = enemies[i];
1132 					break;
1133 				} else if ( cs->bs->enemy < 0 ) {
1134 					cs->lastEnemy = enemies[i];
1135 				}
1136 			}
1137 
1138 			return AIFunc_BattleStart( cs );
1139 		}
1140 	}
1141 
1142 	// set head look flag if no enemy
1143 	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
1144 		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
1145 	}
1146 
1147 	// reload?
1148 	if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
1149 		trap_EA_Reload( cs->entityNum );
1150 	}
1151 
1152 	return NULL;
1153 }
1154 
1155 /*
1156 ============
1157 AIFunc_InspectAudibleEventStart
1158 ============
1159 */
AIFunc_InspectAudibleEventStart(cast_state_t * cs,int entnum)1160 char *AIFunc_InspectAudibleEventStart( cast_state_t *cs, int entnum ) {
1161 	cast_state_t *ocs;
1162 	int oldScriptIndex;
1163 
1164 	ocs = AICast_GetCastState( entnum );
1165 
1166 	// we have now processed the audible event (whether we act on it or not)
1167 	cs->audibleEventTime = -9999;
1168 
1169 	// trigger a script event, which has the ability to deny the request
1170 	oldScriptIndex = cs->scriptCallIndex;
1171 	AICast_ForceScriptEvent( cs, "inspectsoundstart", g_entities[cs->audibleEventEnt].aiName );
1172 	if ( cs->aiFlags & AIFL_DENYACTION ) {
1173 		return NULL;
1174 	}
1175 
1176 	// if not in alert mode, go there now
1177 	if ( cs->aiState < AISTATE_ALERT ) {
1178 		AICast_StateChange( cs, AISTATE_ALERT );
1179 	}
1180 
1181 	if ( oldScriptIndex == cs->scriptCallIndex ) {
1182 		BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_INSPECTSOUND, qfalse, qtrue );
1183 	}
1184 
1185 	// pause the scripting for now
1186 	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc
1187 
1188 	// what should we do? wait here? hide? go see them?
1189 
1190 	// if dead, go see them
1191 	if ( g_entities[entnum].health <= 0 ) {
1192 		cs->inspectNum = entnum;
1193 		cs->aifunc = AIFunc_InspectFriendly;
1194 		return "AIFunc_InspectFriendlyStart";
1195 	}
1196 
1197 	// if they are in combat, then act according to aggressiveness
1198 	if ( ocs->aiState >= AISTATE_COMBAT ) {
1199 		if ( cs->attributes[AGGRESSION] < 0.3 ) {
1200 			if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) {
1201 				cs->takeCoverTime = level.time + 10000; // hide for 10 seconds
1202 				cs->scriptPauseTime = cs->takeCoverTime;
1203 				// crouch there if possible
1204 				if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
1205 					cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
1206 				}
1207 				return AIFunc_BattleTakeCoverStart( cs );
1208 			}
1209 		}
1210 	}
1211 
1212 	cs->aifunc = AIFunc_InspectAudibleEvent;
1213 	return "AIFunc_InspectAudibleEvent";
1214 }
1215 
1216 /*
1217 ============
1218 AIFunc_ChaseGoalIdle()
1219 ============
1220 */
AIFunc_ChaseGoalIdle(cast_state_t * cs)1221 char *AIFunc_ChaseGoalIdle( cast_state_t *cs ) {
1222 	gentity_t   *followent;
1223 	vec3_t dir;
1224 
1225 	if ( cs->followEntity < 0 ) {
1226 		AICast_EndChase( cs );
1227 		return AIFunc_IdleStart( cs );
1228 	}
1229 
1230 	followent = &g_entities[cs->followEntity];
1231 
1232 	// CHECK: will this interfere with scripting?
1233 	//
1234 	// do we need to avoid a danger?
1235 	if ( cs->dangerEntityValidTime >= level.time ) {
1236 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
1237 			// go to a position that cannot be seen from the dangerPos
1238 			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
1239 			cs->bs->attackcrouch_time = 0;
1240 			return AIFunc_AvoidDangerStart( cs );
1241 		}
1242 	}
1243 	//
1244 	// are we waiting for a door?
1245 	if ( cs->doorMarkerTime > level.time - 100 ) {
1246 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
1247 	}
1248 	//
1249 	// if the player is not ready yet, wait
1250 	if ( !followent->inuse ) {
1251 		return NULL;
1252 	}
1253 
1254 	// has the scripting stopped asking us to pursue this goal?
1255 	if ( cs->followIsGoto && ( cs->followTime < level.time ) ) {
1256 		return AIFunc_Idle( cs );
1257 	}
1258 
1259 	// they are ready, are they outside range?
1260 	if ( Distance( followent->r.currentOrigin, cs->bs->origin ) > cs->followDist ) {
1261 		return AIFunc_ChaseGoalStart( cs, cs->followEntity, cs->followDist, qtrue );
1262 	}
1263 
1264 	// check for a movement we should be making
1265 	if ( cs->obstructingTime > level.time ) {
1266 		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
1267 		cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED];
1268 	}
1269 	// if we have an enemy, fire if they're visible
1270 	else if ( cs->bs->enemy >= 0 ) {
1271 		//attack the enemy if possible
1272 		AICast_ProcessAttack( cs );
1273 	}
1274 	// if we had an enemy recently, face them
1275 	else if ( cs->lastEnemy >= 0 ) {
1276 		vec3_t dir;
1277 		//
1278 		VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
1279 		if ( VectorLength( dir ) < 1 ) {
1280 			cs->bs->ideal_viewangles[PITCH] = 0;
1281 		} else {
1282 			VectorNormalize( dir );
1283 			vectoangles( dir, cs->bs->ideal_viewangles );
1284 		}
1285 		// reload?
1286 		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
1287 			trap_EA_Reload( cs->entityNum );
1288 		}
1289 	} else if ( followent->client )     {
1290 		// face them
1291 		VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
1292 		dir[2] += followent->client->ps.viewheight - g_entities[cs->bs->entitynum].client->ps.viewheight;
1293 		VectorNormalize( dir );
1294 		vectoangles( dir, cs->bs->ideal_viewangles );
1295 	}
1296 
1297 	// look for things we should attack
1298 	numEnemies = AICast_ScanForEnemies( cs, enemies );
1299 	if ( numEnemies == -1 ) { // query mode
1300 		return NULL;
1301 	} else if ( numEnemies == -2 )     { // inspection may be required
1302 		char *retval;
1303 
1304 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
1305 			return retval;
1306 		}
1307 	} else if ( numEnemies == -3 )     { // bullet impact
1308 		if ( cs->aiState < AISTATE_COMBAT ) {
1309 			return AIFunc_InspectBulletImpactStart( cs );
1310 		}
1311 	} else if ( numEnemies == -4 )     { // audible event
1312 		if ( cs->aiState < AISTATE_COMBAT ) {
1313 			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
1314 		}
1315 	} else if ( numEnemies > 0 )     {
1316 		cs->bs->enemy = enemies[0]; // just attack the first one
1317 	}
1318 
1319 	// set head look flag if no enemy
1320 	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
1321 		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
1322 	}
1323 
1324 	return NULL;
1325 }
1326 
1327 /*
1328 ============
1329 AIFunc_ChaseGoalIdleStart()
1330 ============
1331 */
AIFunc_ChaseGoalIdleStart(cast_state_t * cs,int entitynum,float reachdist)1332 char *AIFunc_ChaseGoalIdleStart( cast_state_t *cs, int entitynum, float reachdist ) {
1333 	// make sure we don't avoid any areas when we start again
1334 	trap_BotInitAvoidReach( cs->bs->ms );
1335 
1336 	// if we are following someone, always use the default (ready for action) anim
1337 	if ( entitynum < MAX_CLIENTS ) {
1338 		g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
1339 	} else {
1340 		// randomly choose idle animation
1341 //----(SA)	try always using the 'casual' stand on spawn and change to crouching one when 'alerted'
1342 		if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
1343 //			if (cs->lastEnemy < 0)
1344 			g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2;
1345 //			else
1346 //				g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
1347 		}
1348 	}
1349 
1350 	cs->followEntity = entitynum;
1351 	cs->followDist = reachdist;
1352 	cs->aifunc = AIFunc_ChaseGoalIdle;
1353 	return "AIFunc_ChaseGoalIdle";
1354 }
1355 
1356 /*
1357 ============
1358 AIFunc_ChaseGoal()
1359 ============
1360 */
AIFunc_ChaseGoal(cast_state_t * cs)1361 char *AIFunc_ChaseGoal( cast_state_t *cs ) {
1362 	gentity_t   *followent, *ent;
1363 	bot_state_t *bs;
1364 	vec3_t destorg;
1365 	float dist;
1366 	qboolean moved = qfalse;
1367 
1368 	ent = &g_entities[cs->entityNum];
1369 
1370 	if ( cs->followEntity < 0 ) {
1371 		AICast_EndChase( cs );
1372 		return AIFunc_IdleStart( cs );
1373 	}
1374 
1375 	// CHECK: will this mess with scripting?
1376 	//
1377 	// do we need to avoid a danger?
1378 	if ( cs->dangerEntityValidTime >= level.time ) {
1379 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
1380 			// go to a position that cannot be seen from the dangerPos
1381 			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
1382 			cs->bs->attackcrouch_time = 0;
1383 			cs->castScriptStatus.scriptGotoId = -1;
1384 			cs->movestate = MS_DEFAULT;
1385 			cs->movestateType = MSTYPE_NONE;
1386 			return AIFunc_AvoidDangerStart( cs );
1387 		}
1388 	}
1389 	//
1390 	// are we waiting for a door?
1391 	if ( cs->doorMarkerTime > level.time - 100 ) {
1392 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
1393 	}
1394 
1395 	followent = &g_entities[cs->followEntity];
1396 
1397 	// if the entity is not ready yet
1398 	if ( !followent->inuse ) {
1399 		// if it's a connecting client, wait
1400 		if (    cs->followEntity < MAX_CLIENTS
1401 				&&  (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
1402 						|| ( level.time < 3000 ) ) ) {
1403 			return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
1404 		} else    // stop following it
1405 		{
1406 			AICast_EndChase( cs );
1407 			return AIFunc_IdleStart( cs );
1408 		}
1409 	}
1410 
1411 	// has the scripting stopped asking us to pursue this goal?
1412 	if ( cs->followIsGoto && ( cs->followTime < level.time ) ) {
1413 		return AIFunc_Idle( cs );
1414 	}
1415 
1416 	if ( followent->client ) {
1417 		VectorCopy( followent->client->ps.origin, destorg );
1418 	} else {
1419 		VectorCopy( followent->r.currentOrigin, destorg );
1420 	}
1421 
1422 	// they are ready, are they inside range? FIXME: make configurable
1423 	dist = Distance( destorg, cs->bs->origin );
1424 	if ( cs->followSlowApproach && dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) {
1425 		// if this is a scripted GOTO, stop following now
1426 		if ( cs->followEntity == cs->castScriptStatus.scriptGotoEnt ) {
1427 			AICast_EndChase( cs );
1428 			return AIFunc_IdleStart( cs );
1429 		}
1430 		// if we have reached our leader
1431 		else
1432 		{
1433 			if ( cs->followEntity == cs->leaderNum ) {
1434 				if ( dist < AICAST_LEADERDIST_MIN ) {
1435 					AICast_EndChase( cs );
1436 					return AIFunc_IdleStart( cs );
1437 				} else {
1438 					trace_t tr;
1439 					// if we have a clear line to our leader, move closer, since there may be others following also
1440 					trap_Trace( &tr, cs->bs->origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, g_entities[cs->followEntity].r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
1441 					if ( tr.entityNum != cs->followEntity ) {
1442 						AICast_EndChase( cs );
1443 						return AIFunc_IdleStart( cs );
1444 					}
1445 					// if we have crouching ability, then use it while we are just moving closer
1446 					if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
1447 						cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
1448 					}
1449 				}
1450 			} else
1451 			{
1452 				return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
1453 			}
1454 		}
1455 	}
1456 	//
1457 	// go to them
1458 	//
1459 	bs = cs->bs;
1460 	//
1461 	// RF, disabled this, MIKE sees dead people
1462 	//if (followent->client && followent->health <= 0) {
1463 	//	AICast_EndChase( cs );
1464 	//	return AIFunc_IdleStart(cs);
1465 	//}
1466 
1467 	// move straight to them if we can
1468 	if (    !moved &&
1469 			( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
1470 		aicast_predictmove_t move;
1471 		vec3_t dir;
1472 		bot_input_t bi;
1473 		usercmd_t ucmd;
1474 		trace_t tr;
1475 		qboolean simTest = qfalse;
1476 
1477 		if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
1478 			simTest = qtrue;
1479 		}
1480 
1481 		if ( !simTest ) {
1482 			// trace will eliminate most unsuccessful paths
1483 			trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
1484 			if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) {
1485 				simTest = qtrue;
1486 			}
1487 		}
1488 
1489 		if ( simTest ) {
1490 			// try walking straight to them
1491 			VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
1492 			if ( !ent->waterlevel ) {
1493 				dir[2] = 0;
1494 			}
1495 			trap_EA_Move( cs->entityNum, dir, 400 );
1496 			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
1497 			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
1498 			AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity );
1499 
1500 			if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
1501 				vectoangles( dir, cs->bs->ideal_viewangles );
1502 				cs->bs->ideal_viewangles[2] *= 0.5;
1503 				moved = qtrue;
1504 			}
1505 		}
1506 	}
1507 	//
1508 	if ( !moved ) {
1509 		// use AAS routing
1510 		moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity );
1511 		if ( moveresult && moveresult->failure ) {
1512 			// shit?
1513 		}
1514 	}
1515 
1516 	// should we slow down?
1517 	if ( cs->followDist && cs->followSlowApproach && cs->followDist < 48 ) {
1518 		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
1519 	}
1520 
1521 	// check for a movement we should be making
1522 	if ( cs->obstructingTime > level.time ) {
1523 		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
1524 		if ( cs->movestate != MS_CROUCH ) {
1525 			cs->movestate = MS_WALK;
1526 		}
1527 		cs->movestateType = MSTYPE_TEMPORARY;
1528 	}
1529 
1530 	// if we have an enemy, fire if they're visible
1531 	if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible
1532 		AICast_ProcessAttack( cs );
1533 	} else {
1534 		int numEnemies;
1535 		//
1536 		// look for things we should attack
1537 		numEnemies = AICast_ScanForEnemies( cs, enemies );
1538 		if ( numEnemies == -1 ) { // query mode
1539 			return NULL;
1540 		} else if ( numEnemies == -2 )     { // inspection may be required
1541 			char *retval;
1542 
1543 			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
1544 				return retval;
1545 			}
1546 		} else if ( numEnemies == -3 )     { // bullet impact
1547 			if ( cs->aiState < AISTATE_COMBAT ) {
1548 				return AIFunc_InspectBulletImpactStart( cs );
1549 			}
1550 		} else if ( numEnemies == -4 )     { // audible event
1551 			if ( cs->aiState < AISTATE_COMBAT ) {
1552 				return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
1553 			}
1554 		} else if ( numEnemies > 0 )     {
1555 			int i;
1556 
1557 			cs->bs->enemy = enemies[0]; // just attack the first one
1558 			// override with a visible enemy
1559 			for ( i = 1; i < numEnemies; i++ ) {
1560 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
1561 					cs->bs->enemy = enemies[i];
1562 					break;
1563 				} else if ( cs->bs->enemy < 0 ) {
1564 					cs->lastEnemy = enemies[i];
1565 				}
1566 			}
1567 		}
1568 		// reload?
1569 		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
1570 			trap_EA_Reload( cs->entityNum );
1571 		}
1572 	}
1573 
1574 	// set head look flag if no enemy
1575 	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
1576 		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
1577 	}
1578 
1579 	return NULL;
1580 
1581 }
1582 
1583 /*
1584 ============
1585 AIFunc_ChaseGoalStart()
1586 ============
1587 */
AIFunc_ChaseGoalStart(cast_state_t * cs,int entitynum,float reachdist,qboolean slowApproach)1588 char *AIFunc_ChaseGoalStart( cast_state_t *cs, int entitynum, float reachdist, qboolean slowApproach ) {
1589 	cs->followEntity = entitynum;
1590 	cs->followDist = reachdist;
1591 	cs->followIsGoto = qfalse;
1592 	cs->followSlowApproach = slowApproach;
1593 	cs->aifunc = AIFunc_ChaseGoal;
1594 	return "AIFunc_ChaseGoal";
1595 }
1596 
1597 /*
1598 ============
1599 AIFunc_DoorMarker()
1600 ============
1601 */
AIFunc_DoorMarker(cast_state_t * cs)1602 char *AIFunc_DoorMarker( cast_state_t *cs ) {
1603 	gentity_t   *followent, *door;
1604 	vec3_t destorg = { 0 };
1605 	float dist;
1606 	//
1607 	// do we need to avoid a danger?
1608 	if ( cs->dangerEntityValidTime >= level.time ) {
1609 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
1610 			// shit??
1611 		}
1612 		// go to a position that cannot be seen from the dangerPos
1613 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
1614 		cs->bs->attackcrouch_time = 0;
1615 		return AIFunc_AvoidDangerStart( cs );
1616 	}
1617 
1618 	followent = &g_entities[cs->doorMarker];
1619 
1620 	// if the entity is not ready yet
1621 	if ( !followent->inuse ) {
1622 		cs->doorMarkerTime = 0;
1623 		//return AIFunc_DefaultStart( cs );
1624 		return AIFunc_Restore( cs );
1625 	}
1626 
1627 	// if the door is open or idle
1628 	door = &g_entities[cs->doorEntNum];
1629 	if (    ( !door->key ) &&
1630 			( door->s.apos.trType == TR_STATIONARY && door->s.pos.trType == TR_STATIONARY ) ) {
1631 		cs->doorMarkerTime = 0;
1632 		//return AIFunc_DefaultStart( cs );
1633 		return AIFunc_Restore( cs );
1634 	}
1635 
1636 	// if we have an enemy, fire if they're visible
1637 	if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible
1638 		AICast_ProcessAttack( cs );
1639 	}
1640 
1641 	// they are ready, are they inside range? FIXME: make configurable
1642 	dist = Distance( destorg, cs->bs->origin );
1643 	if ( dist < 12 ) {
1644 		// check for a movement we should be making
1645 		if ( cs->obstructingTime > level.time ) {
1646 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
1647 		}
1648 		// if the door is locked, resume
1649 		if ( followent->key ) {
1650 			return AIFunc_Restore( cs );
1651 		}
1652 		return NULL;
1653 	}
1654 
1655 	// go to it
1656 	//
1657 	moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, followent->s.number );
1658 	// if we cant get there, forget it
1659 	if ( moveresult && moveresult->failure ) {
1660 		return AIFunc_Restore( cs );
1661 	}
1662 	// should we slow down?
1663 	if ( cs->followDist ) {
1664 		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
1665 	}
1666 	// reload?
1667 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
1668 		trap_EA_Reload( cs->entityNum );
1669 	}
1670 	return NULL;
1671 
1672 }
1673 
1674 /*
1675 ============
1676 AIFunc_DoorMarkerStart()
1677 ============
1678 */
AIFunc_DoorMarkerStart(cast_state_t * cs,int doornum,int markernum)1679 char *AIFunc_DoorMarkerStart( cast_state_t *cs, int doornum, int markernum ) {
1680 	cs->doorEntNum = doornum;
1681 	cs->doorMarker = markernum;
1682 	cs->oldAifunc = cs->aifunc;
1683 	cs->aifunc = AIFunc_DoorMarker;
1684 	return "AIFunc_DoorMarker";
1685 }
1686 
1687 /*
1688 =============
1689 AIFunc_BattleRoll()
1690 =============
1691 */
AIFunc_BattleRoll(cast_state_t * cs)1692 char *AIFunc_BattleRoll( cast_state_t *cs ) {
1693 	gclient_t *client = &level.clients[cs->entityNum];
1694 	vec3_t dir;
1695 	//
1696 	// record the time
1697 	cs->lastRollMove = level.time;
1698 	client->ps.eFlags |= EF_NOSWINGANGLES;
1699 	//
1700 	if ( !client->ps.torsoTimer ) {
1701 		if ( cs->battleRollTime < level.time ) {
1702 			return AIFunc_Restore( cs );
1703 		} else {
1704 			// attack?
1705 			if ( cs->bs->enemy >= 0 ) {
1706 				AICast_ProcessAttack( cs );
1707 			}
1708 		}
1709 	}
1710 	if ( g_entities[cs->entityNum].health <= 0 ) {
1711 		return AIFunc_DefaultStart( cs );
1712 	}
1713 	if ( ( cs->bs->enemy >= 0 ) && ( client->ps.torsoTimer < 400 ) && ( cs->takeCoverTime < level.time ) ) {
1714 		// start turning towards our enemy
1715 		AICast_AimAtEnemy( cs );
1716 		if ( client->ps.torsoTimer ) {
1717 			AICast_ProcessAttack( cs );
1718 		}
1719 	}
1720 	//
1721 	// all characters so far only move during the first second of animation
1722 	if ( cs->thinkFuncChangeTime > level.time - 800 ) {
1723 		// just move in the direction of our ideal_viewangles
1724 		AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL );
1725 		trap_EA_Move( cs->entityNum, dir, 300 );
1726 		// if we are crouching, move a little faster than normal
1727 		if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) {
1728 			cs->speedScale = 1.5;
1729 		}
1730 	} else if ( cs->takeCoverTime > level.time ) {
1731 		//
1732 		// if we are taking Cover, use this position, if it's bad, we'll just look for a better spot once we're done here
1733 		VectorCopy( cs->bs->origin, cs->takeCoverPos );
1734 	}
1735 	//
1736 	return NULL;
1737 }
1738 
1739 /*
1740 =============
1741 AIFunc_BattleRollStart()
1742 =============
1743 */
AIFunc_BattleRollStart(cast_state_t * cs,vec3_t vec)1744 char *AIFunc_BattleRollStart( cast_state_t *cs, vec3_t vec ) {
1745 	int duration;
1746 //	gclient_t *client = &level.clients[cs->entityNum];
1747 	//
1748 	// backup the current thinkfunc, so we can return to it when done
1749 	cs->oldAifunc = cs->aifunc;
1750 	//
1751 	// face the direction of movement
1752 	vectoangles( vec, cs->bs->ideal_viewangles );
1753 	// do the roll
1754 	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse );
1755 	//
1756 	if ( duration < 0 ) { // it failed
1757 		return NULL;
1758 	}
1759 	// add some duration to make sure it fully plays out
1760 	duration += 100;
1761 	g_entities[cs->entityNum].client->ps.legsTimer = duration;
1762 	g_entities[cs->entityNum].client->ps.torsoTimer = duration;
1763 	//
1764 	cs->noAttackTime = level.time + duration - 200;
1765 	// set the duration
1766 	cs->battleRollTime = level.time + duration;
1767 	// move into crouch position
1768 	cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0;
1769 	// record the time
1770 	cs->lastRollMove = level.time;
1771 	//
1772 	// make sure we move this frame
1773 	AIFunc_BattleRoll( cs );
1774 	//
1775 	cs->aifunc = AIFunc_BattleRoll;
1776 	return "AIFunc_BattleRoll";
1777 }
1778 
1779 /*
1780 =============
1781 AIFunc_BattleDiveStart()
1782 =============
1783 */
AIFunc_BattleDiveStart(cast_state_t * cs,vec3_t vec)1784 char *AIFunc_BattleDiveStart( cast_state_t *cs, vec3_t vec ) {
1785 	int duration;
1786 //	gclient_t *client = &level.clients[cs->entityNum];
1787 	//
1788 	// backup the current thinkfunc, so we can return to it when done
1789 	cs->oldAifunc = cs->aifunc;
1790 	//
1791 	// face the direction of movement
1792 	vectoangles( vec, cs->bs->ideal_viewangles );
1793 	// do the roll
1794 	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_DIVE, qfalse, qfalse );
1795 	//
1796 	if ( duration < 0 ) { // it failed
1797 		return NULL;
1798 	}
1799 	//
1800 	cs->noAttackTime = level.time + duration - 200;
1801 	// set the duration
1802 	cs->battleRollTime = level.time + duration;
1803 	// move into crouch position
1804 	cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0;
1805 	// record the time
1806 	cs->lastRollMove = level.time;
1807 	//
1808 	// make sure we move this frame
1809 	AIFunc_BattleRoll( cs );
1810 	//
1811 	cs->aifunc = AIFunc_BattleRoll;
1812 	return "AIFunc_BattleRoll";
1813 }
1814 
1815 /*
1816 =============
1817 AIFunc_FlipMove()
1818 =============
1819 */
AIFunc_FlipMove(cast_state_t * cs)1820 char *AIFunc_FlipMove( cast_state_t *cs ) {
1821 	gclient_t *client = &level.clients[cs->entityNum];
1822 	vec3_t dir;
1823 	//
1824 	if ( !client->ps.torsoTimer ) {
1825 		cs->bs->attackcrouch_time = 0;
1826 		return AIFunc_Restore( cs );
1827 	}
1828 	if ( g_entities[cs->entityNum].health <= 0 ) {
1829 		return AIFunc_DefaultStart( cs );
1830 	}
1831 	//
1832 	// just move in the direction of our ideal_viewangles
1833 	AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL );
1834 	trap_EA_Move( cs->entityNum, dir, 400 );
1835 	// if we are crouching, move a little faster than normal
1836 	if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) {
1837 		cs->speedScale = 1.5;
1838 	}
1839 	//
1840 	return NULL;
1841 }
1842 
1843 /*
1844 =============
1845 AIFunc_FlipMoveStart()
1846 =============
1847 */
AIFunc_FlipMoveStart(cast_state_t * cs,vec3_t vec)1848 char *AIFunc_FlipMoveStart( cast_state_t *cs, vec3_t vec ) {
1849 	int duration;
1850 //	gclient_t *client = &level.clients[cs->entityNum];
1851 	//
1852 	// backup the current thinkfunc, so we can return to it when done
1853 	cs->oldAifunc = cs->aifunc;
1854 	//
1855 	// record the time
1856 	cs->lastRollMove = level.time;
1857 	// face the direction of movement
1858 	vectoangles( vec, cs->bs->ideal_viewangles );
1859 	cs->noAttackTime = level.time + 1200;
1860 	// do the roll
1861 	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse );
1862 	//
1863 	if ( duration < 0 ) { // it failed
1864 		return NULL;
1865 	}
1866 	// move into crouch position
1867 	cs->bs->attackcrouch_time = trap_AAS_Time() + 0.8;
1868 	//
1869 	// make sure we move this frame
1870 	AIFunc_FlipMove( cs );
1871 	//
1872 	cs->aifunc = AIFunc_FlipMove;
1873 	return "AIFunc_FlipMove";
1874 }
1875 
1876 /*
1877 =============
1878 AIFunc_BattleHunt()
1879 =============
1880 */
AIFunc_BattleHunt(cast_state_t * cs)1881 char *AIFunc_BattleHunt( cast_state_t *cs ) {
1882 	const float chaseDist = 32;
1883 	gentity_t   *followent;
1884 	bot_state_t *bs;
1885 	vec3_t destorg;
1886 	qboolean moved = qfalse;
1887 	char *rval;
1888 	float dist = 0;
1889 	int i;
1890 
1891 	//
1892 	// do we need to avoid a danger?
1893 	if ( cs->dangerEntityValidTime >= level.time ) {
1894 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
1895 			// shit??
1896 		}
1897 		// go to a position that cannot be seen from the dangerPos
1898 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
1899 		cs->bs->attackcrouch_time = 0;
1900 		return AIFunc_AvoidDangerStart( cs );
1901 	}
1902 	//
1903 	// are we waiting for a door?
1904 	if ( cs->doorMarkerTime > level.time - 100 ) {
1905 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
1906 	}
1907 	//
1908 	bs = cs->bs;
1909 	//
1910 	if ( bs->enemy < 0 ) {
1911 		return AIFunc_IdleStart( cs );
1912 	}
1913 	//
1914 	AICast_GetCastState( cs->bs->enemy );
1915 	//
1916 	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
1917 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
1918 	}
1919 	//
1920 	followent = &g_entities[bs->enemy];
1921 	//
1922 	// if the entity is not ready yet
1923 	if ( !followent->inuse ) {
1924 		// if it's a connecting client, wait
1925 		if ( !(   ( bs->enemy < MAX_CLIENTS )
1926 				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
1927 						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
1928 			bs->enemy = -1;
1929 		}
1930 
1931 		return AIFunc_IdleStart( cs );
1932 	}
1933 	//
1934 	// if we can see them, go back to an attack state
1935 	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
1936 	if (    AICast_EntityVisible( cs, bs->enemy, qtrue )	// take into account reaction time
1937 			&&  AICast_CheckAttack( cs, bs->enemy, qfalse )
1938 			&&  cs->obstructingTime < level.time ) {
1939 		if ( AICast_StopAndAttack( cs ) ) {
1940 
1941 			if ( ( rval = AIFunc_BattleStart( cs ) ) ) {
1942 				return rval;
1943 			}
1944 		} else {    // just attack them now and keep chasing
1945 			AICast_ProcessAttack( cs );
1946 		}
1947 		AICast_ChooseWeapon( cs, qfalse );
1948 	} else
1949 	{
1950 		int numEnemies;
1951 
1952 		AICast_ChooseWeapon( cs, qfalse );
1953 
1954 		numEnemies = AICast_ScanForEnemies( cs, enemies );
1955 		if ( numEnemies == -1 ) { // query mode
1956 			return NULL;
1957 		} else if ( numEnemies == -2 )     { // inspection may be required
1958 			char *retval;
1959 			if ( cs->aiState < AISTATE_COMBAT ) {
1960 
1961 				if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
1962 					return retval;
1963 				}
1964 			}
1965 		} else if ( numEnemies == -3 )     { // bullet impact
1966 			if ( cs->aiState < AISTATE_COMBAT ) {
1967 				return AIFunc_InspectBulletImpactStart( cs );
1968 			}
1969 		} else if ( numEnemies == -4 )     { // audible event
1970 			if ( cs->aiState < AISTATE_COMBAT ) {
1971 				return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
1972 			}
1973 		} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
1974 			if ( numEnemies > 0 ) {
1975 				// default to the first known enemy, overwrite if we find a clearer shot
1976 				cs->bs->enemy = enemies[0];
1977 				//
1978 				for ( i = 0; i < numEnemies; i++ ) {
1979 					if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
1980 						cs->bs->enemy = enemies[i];
1981 						break;
1982 					} else if ( cs->bs->enemy < 0 ) {
1983 						cs->lastEnemy = enemies[i];
1984 					}
1985 				}
1986 				// note: next frame we'll process this new enemy an begin an attack if necessary
1987 			}
1988 		}
1989 		AICast_ChooseWeapon( cs, qfalse );
1990 	}
1991 
1992 	// have we spent enough time in combat mode?
1993 	if ( cs->aiState == AISTATE_COMBAT ) {
1994 		if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) {
1995 			AICast_StateChange( cs, AISTATE_ALERT );
1996 		}
1997 	}
1998 
1999 	// while hunting, use crouch mode if possible
2000 	if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) {
2001 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
2002 	}
2003 
2004 	if ( cs->battleHuntPauseTime ) {
2005 		if ( cs->battleHuntPauseTime < level.time ) {
2006 			// pausetime has expired, so go into ambush mode
2007 			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->takeCoverPos ) ) {
2008 				// wait in ambush, for them to return
2009 				VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->combatGoalOrigin );
2010 				return AIFunc_BattleAmbushStart( cs );
2011 			}
2012 			// couldn't find a spot, so just stay here?
2013 			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
2014 			VectorCopy( cs->bs->origin, cs->takeCoverPos );
2015 			return AIFunc_BattleAmbushStart( cs );
2016 		} else {
2017 			// stay here, looking around
2018 			if ( cs->battleHuntViewTime < level.time ) {
2019 				cs->battleHuntViewTime = level.time + 700 + rand() % 1000;
2020 				// set a random viewangle
2021 				cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) );
2022 				cs->bs->ideal_viewangles[PITCH] = 0;
2023 			}
2024 		}
2025 	} else {
2026 		// cycle through markers
2027 		VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], destorg );
2028 		if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) {
2029 			if ( cs->battleChaseMarker == ( cs->vislist[bs->enemy].chase_marker_count - 1 ) ) {
2030 				cs->battleHuntPauseTime = level.time + 4000;
2031 				cs->battleHuntViewTime = level.time + 1000;
2032 			} else {
2033 				cs->battleChaseMarker += cs->battleChaseMarkerDir;
2034 				if ( cs->battleChaseMarker > cs->vislist[bs->enemy].chase_marker_count ) {
2035 					cs->battleChaseMarkerDir *= -1;
2036 					cs->battleChaseMarker = cs->vislist[bs->enemy].chase_marker_count - 1;
2037 				} else if ( cs->battleChaseMarker < 0 )     {
2038 					cs->battleChaseMarkerDir *= -1;
2039 					cs->battleChaseMarker = 0;
2040 				}
2041 			}
2042 		}
2043 		//
2044 		if ( cs->battleHuntPauseTime < level.time ) {
2045 			// just go to them
2046 			if ( !moved && cs->leaderNum < 0 ) {
2047 				moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
2048 				if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
2049 					// try to go to ambush mode
2050 					cs->bs->enemy = -1;
2051 					return AIFunc_DefaultStart( cs );
2052 				}
2053 			}
2054 		}
2055 	}
2056 	//
2057 	// slow down real close to the goal, so we don't go passed it
2058 	cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist );
2059 
2060 	// reload?
2061 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
2062 		trap_EA_Reload( cs->entityNum );
2063 	}
2064 
2065 	return NULL;
2066 }
2067 
2068 /*
2069 =============
2070 AIFunc_BattleHuntStart()
2071 =============
2072 */
AIFunc_BattleHuntStart(cast_state_t * cs)2073 char *AIFunc_BattleHuntStart( cast_state_t *cs ) {
2074 	cs->combatGoalTime = 0;
2075 	cs->battleChaseMarker = 0;
2076 	cs->battleHuntPauseTime = 0;
2077 	//
2078 	cs->aifunc = AIFunc_BattleHunt;
2079 	return "AIFunc_BattleHunt";
2080 }
2081 
2082 /*
2083 =============
2084 AIFunc_BattleAmbush()
2085 =============
2086 */
AIFunc_BattleAmbush(cast_state_t * cs)2087 char *AIFunc_BattleAmbush( cast_state_t *cs ) {
2088 	bot_state_t *bs;
2089 	vec3_t destorg, vec;
2090 	float dist;
2091 	int enemies[MAX_CLIENTS], numEnemies, i;
2092 	qboolean shouldAttack, idleYaw;
2093 	aicast_predictmove_t move;
2094 	vec3_t dir;
2095 //	gclient_t	*client = &level.clients[cs->entityNum];
2096 	//
2097 	// do we need to avoid a danger?
2098 	if ( cs->dangerEntityValidTime >= level.time ) {
2099 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
2100 			// shit??
2101 		}
2102 		// go to a position that cannot be seen from the dangerPos
2103 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
2104 		cs->bs->attackcrouch_time = 0;
2105 		return AIFunc_AvoidDangerStart( cs );
2106 	}
2107 	//
2108 	// are we waiting for a door?
2109 	if ( cs->doorMarkerTime > level.time - 100 ) {
2110 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
2111 	}
2112 
2113 	// we need to move towards it
2114 	bs = cs->bs;
2115 	//
2116 	// note: removing this will cause problems down below!
2117 	if ( bs->enemy < 0 ) {
2118 		return AIFunc_IdleStart( cs );
2119 	}
2120 	//
2121 	// have we spent enough time in combat mode?
2122 	if ( cs->aiState == AISTATE_COMBAT ) {
2123 		if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) {
2124 			AICast_StateChange( cs, AISTATE_ALERT );
2125 		}
2126 	}
2127 	// while hunting, use crouch mode if possible
2128 	if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) {
2129 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
2130 	}
2131 	//
2132 	VectorCopy( cs->takeCoverPos, destorg );
2133 	VectorSubtract( destorg, cs->bs->origin, vec );
2134 	vec[2] *= 0.2;
2135 	dist = VectorLength( vec );
2136 	//
2137 	// update the chase marker
2138 	if ( cs->vislist[cs->bs->enemy].chase_marker_count > 0 ) {
2139 		VectorCopy( cs->vislist[cs->bs->enemy].chase_marker[cs->vislist[cs->bs->enemy].chase_marker_count - 1], cs->combatGoalOrigin );
2140 	}
2141 	//
2142 	// look for things we should attack
2143 	// if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding)
2144 	shouldAttack = qfalse;
2145 	numEnemies = AICast_ScanForEnemies( cs, enemies );
2146 
2147 	// we shouldnt be interrupted from BattleAmbush mode, so try to handle these without interference
2148 	if ( numEnemies == -1 ) { // query mode
2149 		return NULL;
2150 	} else if ( numEnemies == -2 )     { // inspection may be required
2151 		char *retval;
2152 		// TTimo gcc: suggest parentheses around assignment used as truth value
2153 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
2154 			return retval;
2155 		}
2156 	} else if ( numEnemies == -3 )     { // bullet impact
2157 		if ( cs->aiState < AISTATE_COMBAT ) {
2158 			return AIFunc_InspectBulletImpactStart( cs );
2159 		}
2160 	} else if ( numEnemies == -4 )     { // audible event
2161 		if ( cs->aiState < AISTATE_COMBAT ) {
2162 			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
2163 		}
2164 	} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
2165 		if ( numEnemies > 0 ) {
2166 			// default to the first known enemy, overwrite if we find a clearer shot
2167 			cs->bs->enemy = enemies[0];
2168 			//
2169 			for ( i = 0; i < numEnemies; i++ ) {
2170 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
2171 					cs->bs->enemy = enemies[i];
2172 					shouldAttack = qtrue;
2173 					break;
2174 				} else if ( cs->bs->enemy < 0 ) {
2175 					cs->lastEnemy = enemies[i];
2176 				}
2177 			}
2178 		}
2179 
2180 	} else {    // keep hiding forever
2181 
2182 		cs->takeCoverTime = level.time + 1000;
2183 
2184 	}
2185 	//
2186 	memset( &move, 0, sizeof( move ) );
2187 	//
2188 	// are we close enough to the goal?
2189 	if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 && ( cs->obstructingTime < level.time ) && !shouldAttack ) {
2190 		const float simTime = 0.8;
2191 		float enemyDist;
2192 		//
2193 		// we haven't reached it yet, make sure we at least wait there for a few seconds after arriving
2194 		cs->takeCoverTime = level.time + 2000 + rand() % 2000;
2195 		//
2196 		moveresult = AICast_MoveToPos( cs, destorg, -1 );
2197 		if ( moveresult ) {
2198 			//if the movement failed
2199 			if ( moveresult->failure ) {
2200 				//reset the avoid reach, otherwise bot is stuck in current area
2201 				trap_BotResetAvoidReach( bs->ms );
2202 				// couldn't get there, so stop trying to get there
2203 				VectorClear( cs->takeCoverPos );
2204 				dist = 0;
2205 			}
2206 			//
2207 			if ( moveresult->blocked ) {
2208 				// abort the TakeCover
2209 				VectorClear( cs->takeCoverPos );
2210 				dist = 0;
2211 			}
2212 		}
2213 		//
2214 		// NOTE: this is also used by hidepos prediction (below)
2215 		// if we are going to bump into something soon, abort it
2216 		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 );
2217 		enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase );
2218 		VectorSubtract( move.endpos, cs->bs->origin, vec );
2219 		VectorNormalize( vec );
2220 		//
2221 		if (    ( move.numtouch && move.touchents[0] < aicast_maxclients )    // hit something
2222 				// or moved closer to the enemy
2223 				||  (   ( enemyDist < 128 )
2224 						&&  ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) {
2225 			// abort the manouver
2226 			VectorClear( cs->takeCoverPos );
2227 		}
2228 		//
2229 		// we should slow down on approaching the destination point
2230 		else if ( dist < 64 ) {
2231 			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
2232 		}
2233 
2234 		// don't actually hide, check if we are about to, so we can hide right here
2235 		if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
2236 			if ( move.numtouch || !AICast_VisibleFromPos( move.endpos, cs->entityNum, cs->combatGoalOrigin, cs->bs->enemy, qfalse ) ) {
2237 				// abort the manouver, reached a good spot
2238 				cs->aiFlags |= AIFL_MISCFLAG1;
2239 				VectorCopy( cs->bs->origin, cs->takeCoverPos );
2240 			}
2241 		}
2242 
2243 	} else {
2244 		//
2245 		// check for a movement we should be making
2246 		if ( cs->obstructingTime > level.time ) {
2247 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
2248 		}
2249 		// if we have some enemies that we can attack immediately (without going anywhere to chase them)
2250 		if ( shouldAttack ) {
2251 			return AIFunc_BattleStart( cs );
2252 		}
2253 		// do we need to go to our leader?
2254 		else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
2255 			// wait until we've been hiding for long enough
2256 			if ( level.time > cs->takeCoverTime ) {
2257 				return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
2258 			}
2259 		}
2260 		// else, crouch while we hide
2261 		if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) {
2262 			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
2263 		}
2264 	}
2265 	//
2266 	idleYaw = qtrue;
2267 	// if we can see the place we are hiding from, look at it
2268 	if ( AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->combatGoalOrigin, cs->lastEnemy, qfalse ) ) {
2269 		// face the position we are retreating from
2270 		VectorSubtract( cs->combatGoalOrigin, cs->bs->origin, dir );
2271 		dir[2] = 0;
2272 		if ( VectorNormalize( dir ) > 4 ) {
2273 			idleYaw = qfalse;
2274 			vectoangles( dir, cs->bs->ideal_viewangles );
2275 		}
2276 
2277 	}
2278 	//
2279 	if ( idleYaw ) {  // look around randomly (but not straight into walls)
2280 
2281 		if ( cs->nextIdleAngleChange < level.time ) {
2282 			// wait a second before changing again
2283 			if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {
2284 
2285 				// FIXME: This could be changed to use some AAS sampling, which would:
2286 				//
2287 				//	Given a src area, pick a random dest area which is visible from that area
2288 				//	and return it's position, which we'd then use to set the next view angles
2289 				//
2290 				//	This would result in more efficient, more realistic behaviour, since they'd
2291 				//	also use PITCH angles to look at areas above/below them
2292 
2293 				cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );
2294 
2295 				if ( fabs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
2296 					cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
2297 				} else { // do really fast
2298 					cs->nextIdleAngleChange = level.time + 500;
2299 				}
2300 
2301 				// adjust with time
2302 				cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
2303 				/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);
2304 
2305 				cs->bs->ideal_viewangles[PITCH] = 0;
2306 			}
2307 		} else if ( cs->idleYawChange )     {
2308 			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
2309 			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
2310 		}
2311 
2312 	}
2313 	//
2314 	if ( !cs->crouchHideFlag ) {  // no enemy, and no need to crouch, so stop crouching
2315 		if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1 ) {
2316 			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
2317 		}
2318 	}
2319 
2320 	// reload?
2321 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
2322 		trap_EA_Reload( cs->entityNum );
2323 	}
2324 
2325 	return NULL;
2326 }
2327 
2328 /*
2329 =============
2330 AIFunc_BattleAmbushStart()
2331 =============
2332 */
AIFunc_BattleAmbushStart(cast_state_t * cs)2333 char *AIFunc_BattleAmbushStart( cast_state_t *cs ) {
2334 	if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) {
2335 		// always run to the cover point
2336 		cs->bs->attackcrouch_time = 0;
2337 	} else if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1.0 ) {
2338 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
2339 	}
2340 
2341 	//
2342 	// start a crouch attack?
2343 	if ( cs->attributes[ATTACK_CROUCH] > 0.1 && cs->bs->attackcrouch_time >= trap_AAS_Time() ) {
2344 		// continue
2345 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
2346 	}
2347 	// if we arent crouching, start crouching soon after we start retreating
2348 	if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
2349 		cs->aiFlags |= AIFL_ATTACK_CROUCH;
2350 	} else {
2351 		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
2352 	}
2353 
2354 	// miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over
2355 	cs->aiFlags &= ~AIFL_MISCFLAG1;
2356 
2357 	cs->aifunc = AIFunc_BattleAmbush;
2358 	return "AIFunc_BattleAmbush";
2359 }
2360 
2361 /*
2362 ============
2363 AIFunc_BattleChase()
2364 ============
2365 */
AIFunc_BattleChase(cast_state_t * cs)2366 char *AIFunc_BattleChase( cast_state_t *cs ) {
2367 	const float chaseDist = 32;
2368 	gentity_t   *followent, *ent;
2369 	bot_state_t *bs;
2370 	vec3_t destorg;
2371 	qboolean moved = qfalse;
2372 	gclient_t *client = &level.clients[cs->entityNum];
2373 	char *rval;
2374 	float dist;
2375 	cast_state_t *ocs;
2376 
2377 	ent = &g_entities[cs->entityNum];
2378 
2379 	//
2380 	// do we need to avoid a danger?
2381 	if ( cs->dangerEntityValidTime >= level.time ) {
2382 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
2383 			// shit??
2384 		}
2385 		// go to a position that cannot be seen from the dangerPos
2386 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
2387 		cs->bs->attackcrouch_time = 0;
2388 		return AIFunc_AvoidDangerStart( cs );
2389 	}
2390 	//
2391 	// are we waiting for a door?
2392 	if ( cs->doorMarkerTime > level.time - 100 ) {
2393 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
2394 	}
2395 
2396 	bs = cs->bs;
2397 	//
2398 	if ( bs->enemy < 0 ) {
2399 		return AIFunc_IdleStart( cs );
2400 	}
2401 	//
2402 	// Retreat?
2403 	if ( AICast_WantToRetreat( cs ) ) {
2404 		if  ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
2405 			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
2406 			cs->takeCoverTime = level.time + 2000 + rand() % 3000;
2407 			return AIFunc_BattleTakeCoverStart( cs );
2408 		}
2409 	}
2410 	//
2411 	ocs = AICast_GetCastState( cs->bs->enemy );
2412 	//
2413 	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
2414 		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
2415 	}
2416 	//
2417 	followent = &g_entities[bs->enemy];
2418 	//
2419 	// if the entity is not ready yet
2420 	if ( !followent->inuse ) {
2421 		// if it's a connecting client, wait
2422 		if ( !(   ( bs->enemy < MAX_CLIENTS )
2423 				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
2424 						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
2425 			bs->enemy = -1;
2426 		}
2427 
2428 		return AIFunc_IdleStart( cs );
2429 	}
2430 	//
2431 	// if we can see them, go back to an attack state
2432 	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
2433 	if (    AICast_EntityVisible( cs, bs->enemy, qtrue )    // take into account reaction time
2434 			&&  AICast_CheckAttack( cs, bs->enemy, qfalse )
2435 			&&  cs->obstructingTime < level.time ) {
2436 		if ( AICast_StopAndAttack( cs ) ) {
2437 			// TTimo gcc: suggest parentheses around assignment used as truth value
2438 			if ( ( rval = AIFunc_BattleStart( cs ) ) ) {
2439 				return rval;
2440 			}
2441 		} else {    // just attack them now and keep chasing
2442 			AICast_ProcessAttack( cs );
2443 		}
2444 		AICast_ChooseWeapon( cs, qfalse );
2445 	} else
2446 	{
2447 		AICast_ChooseWeapon( cs, qfalse );
2448 		// not visible, go to their previously visible position
2449 		/*
2450 		if (!cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].visible_pos ) < 16)
2451 		{
2452 			// we're done attacking, go back to default state, which in turn will recall previous state
2453 			//
2454 			return AIFunc_DefaultStart( cs );
2455 		}
2456 		*/
2457 	}
2458 	//
2459 	// find the chase position
2460 	if ( followent->client ) {
2461 		// go to the last visible position
2462 		VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg );
2463 		// if we have reached it, go into hunt mode
2464 		if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) {
2465 			// if we haven't been hunted for a while, do so
2466 			if ( ocs->lastBattleHunted < level.time - 5000 ) {
2467 				ocs->lastBattleHunted = level.time;
2468 				return AIFunc_BattleHuntStart( cs );
2469 			}
2470 			// otherwise, go into ambush mode
2471 			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
2472 				VectorCopy( cs->vislist[cs->bs->enemy].real_visible_pos, cs->combatGoalOrigin );
2473 				return AIFunc_BattleAmbushStart( cs );
2474 			}
2475 			// couldn't find a spot, so just stay here?
2476 			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
2477 			VectorCopy( cs->bs->origin, cs->takeCoverPos );
2478 			return AIFunc_BattleAmbushStart( cs );
2479 		}
2480 	} else    // assume we know where other entities are
2481 	{
2482 		VectorCopy( followent->s.pos.trBase, destorg );
2483 		dist = Distance( cs->bs->origin, destorg );
2484 	}
2485 	//
2486 	// if the enemy is inside a CONTENTS_DONOTENTER brush, and we are close enough, stop chasing them
2487 	if ( AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) && VectorDistance( cs->bs->origin, destorg ) < 384 ) {
2488 		if ( trap_PointContents( destorg, cs->bs->enemy ) & ( CONTENTS_DONOTENTER | CONTENTS_DONOTENTER_LARGE ) ) {
2489 			// just stay here, and hope they move out of the brush without finding a spot where they can hit us but we can't hit them
2490 			return NULL;
2491 		}
2492 	}
2493 	//
2494 	// is there someone else we can go for instead?
2495 	numEnemies = AICast_ScanForEnemies( cs, enemies );
2496 	if ( numEnemies == -1 ) { // query mode
2497 		return NULL;
2498 	} else if ( numEnemies == -2 )     { // inspection may be required
2499 		char *retval;
2500 		// TTimo gcc: suggest parentheses around assignment used as truth value
2501 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
2502 			return retval;
2503 		}
2504 	}
2505 	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
2506 	if ( numEnemies > 0 ) {
2507 		int i;
2508 		for ( i = 0; i < numEnemies; i++ ) {
2509 			if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
2510 				cs->bs->enemy = enemies[i];
2511 				return AIFunc_BattleStart( cs );
2512 			}
2513 		}
2514 	}
2515 	AICast_ChooseWeapon( cs, qfalse );
2516 
2517 	//
2518 	// if we only recently saw them, face them
2519 	//
2520 	if ( cs->vislist[cs->bs->enemy].visible_timestamp > level.time - 3000 ) {
2521 		AICast_AimAtEnemy( cs );    // be ready for an attack if they become visible again
2522 		//if (cs->attributes[ATTACK_CROUCH] > 0.1) {	// crouching for combat
2523 		//	cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
2524 		//}
2525 	}
2526 
2527 	//
2528 	// Lob a Grenade?
2529 	// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
2530 	if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
2531 		 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
2532 		 ( cs->startGrenadeFlushTime < level.time - 3000 ) &&
2533 		 ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
2534 		 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
2535 		 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
2536 		 ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
2537 		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) &&
2538 		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) {
2539 		// try and flush them out with a grenade
2540 		//G_Printf("pineapple?\n");
2541 		return AIFunc_GrenadeFlushStart( cs );
2542 	} else if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
2543 				( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
2544 				( cs->startGrenadeFlushTime < level.time - 2000 ) &&
2545 				( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) &&
2546 				( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) &&
2547 				( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
2548 				( ( cs->bs->weaponnum == WP_GRENADE_PINEAPPLE ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
2549 				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) &&
2550 				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) {
2551 		// try and flush them out with a grenade
2552 		//G_Printf("pineapple?\n");
2553 		return AIFunc_GrenadeFlushStart( cs );
2554 	}
2555 	//
2556 	// Flaming Zombie? Shoot flames while running
2557 	if ( ( cs->aiCharacter == AICHAR_ZOMBIE ) &&
2558 		 ( IS_FLAMING_ZOMBIE( ent->s ) ) &&
2559 		 ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 5 ) ) {
2560 		if ( fabs( sin( ( level.time + cs->entityNum * 314 ) / 1000 ) * cos( ( level.time + cs->entityNum * 267 ) / 979 ) ) < 0.5 ) {
2561 			ent->s.time = level.time + 800;
2562 		}
2563 	}
2564 	// reload?
2565 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
2566 		trap_EA_Reload( cs->entityNum );
2567 	}
2568 
2569 	if ( dist < chaseDist ) {
2570 		return NULL;
2571 	}
2572 
2573 	//
2574 	// go to them
2575 	//
2576 	// ...........................................................
2577 	// Do the movement..
2578 	//
2579 	// move straight to them if we can
2580 	if (    !moved && cs->leaderNum < 0 &&
2581 			( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) &&
2582 			AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) {
2583 		aicast_predictmove_t move;
2584 		vec3_t dir;
2585 		bot_input_t bi;
2586 		usercmd_t ucmd;
2587 		trace_t tr;
2588 
2589 		// trace will eliminate most unsuccessful paths
2590 		trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
2591 		if ( tr.entityNum == followent->s.number ) {
2592 			// try walking straight to them
2593 			VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
2594 			VectorNormalize( dir );
2595 			if ( !ent->waterlevel ) {
2596 				dir[2] = 0;
2597 			}
2598 			trap_EA_Move( cs->entityNum, dir, 400 );
2599 			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
2600 			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
2601 			AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy );
2602 
2603 			if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
2604 				vectoangles( dir, cs->bs->ideal_viewangles );
2605 				cs->bs->ideal_viewangles[2] *= 0.5;
2606 				moved = qtrue;
2607 			}
2608 		}
2609 	}
2610 	//
2611 	// if they are visible, but not attackable, look for a spot where we can attack them, and head
2612 	// for there. This should prevent AI's getting stuck in a bunch.
2613 	if ( !moved && cs->bs->weaponnum >= WP_LUGER && cs->bs->weaponnum <= WP_SPEARGUN_CO2 && cs->attributes[TACTICAL] >= 0.1 ) {
2614 		//
2615 		// check for another movement we should be making
2616 		if ( cs->obstructingTime > level.time ) {
2617 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
2618 			moved = qtrue;
2619 		}
2620 		//
2621 		if ( cs->leaderNum >= 0 ) {
2622 			if ( cs->combatGoalTime < level.time ) {
2623 				if ( cs->attackSpotTime < level.time ) {
2624 					cs->attackSpotTime = level.time + 500 + rand() % 500;
2625 					if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->leaderNum, cs->bs->enemy, MAX_LEADER_DIST, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) {
2626 						cs->combatGoalTime = level.time + 2000;
2627 					}
2628 				}
2629 			}
2630 			if ( cs->combatGoalTime > level.time ) {
2631 				if ( Distance( cs->combatGoalOrigin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
2632 					// go find a new combatSpot
2633 					cs->combatGoalTime = 0;
2634 				} else {
2635 					// go to the combat spot
2636 					moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 );
2637 					if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
2638 						cs->combatGoalTime = 0;
2639 					} else {
2640 						moved = qtrue;
2641 						if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) {
2642 							cs->combatGoalTime = 0;
2643 						}
2644 					}
2645 				}
2646 			} else {
2647 				// we can't find a way to get to our enemy, so go back to our leader if outside range
2648 				// do we need to go to our leader?
2649 				if ( Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
2650 					return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
2651 				}
2652 			}
2653 		} else {
2654 			if ( cs->combatGoalTime < level.time ) {
2655 				if ( cs->attackSpotTime < level.time ) {
2656 					cs->attackSpotTime = level.time + 500 + rand() % 500;
2657 					if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->entityNum, cs->bs->enemy, 512, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) {
2658 						cs->combatGoalTime = level.time + 2000;
2659 					}
2660 				}
2661 			}
2662 			if ( cs->combatGoalTime > level.time ) {
2663 				// go to the combat spot
2664 				moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 );
2665 				if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
2666 					cs->combatGoalTime = 0;
2667 				} else {
2668 					moved = qtrue;
2669 					if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) {
2670 						cs->combatGoalTime = 0;
2671 					}
2672 				}
2673 			}
2674 		}
2675 	}
2676 	// just go to them
2677 	if ( !moved && cs->leaderNum < 0 ) {
2678 		moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
2679 		if ( moveresult && moveresult->failure ) {    // no path, so try and hude from them
2680 			// pausetime has expired, so go into ambush mode
2681 			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
2682 				// wait in ambush, for them to return
2683 				VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
2684 				return AIFunc_BattleAmbushStart( cs );
2685 			}
2686 			// couldn't find a spot, so just stay here?
2687 			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
2688 			VectorCopy( cs->bs->origin, cs->takeCoverPos );
2689 			return AIFunc_BattleAmbushStart( cs );
2690 		}
2691 	}
2692 	//
2693 	// slow down real close to the goal, so we don't go passed it
2694 	cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist );
2695 	//
2696 	// ...........................................................
2697 	// speed up over some time
2698 	#define BATTLE_CHASE_ACCEL_TIME     300
2699 	if ( ( cs->attributes[RUNNING_SPEED] > 170 ) && ( cs->bs->weaponnum != WP_GAUNTLET ) && ( level.time < ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) ) ) {
2700 		float ideal;
2701 
2702 		ideal = 0.5 + 0.5 * ( 1.0 - ( (float)( ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) - level.time ) / BATTLE_CHASE_ACCEL_TIME ) );
2703 		if ( ideal < cs->speedScale ) {
2704 			cs->speedScale = ideal;
2705 		}
2706 	}
2707 	//
2708 	// if we are going to reach them soon, predict the attack
2709 	{
2710 		float simTime = 1.0;
2711 		aicast_predictmove_t move;
2712 		float moveDist;
2713 		vec3_t vec;
2714 		//
2715 		if ( cs->bs->weaponnum == WP_GAUNTLET ) {
2716 			simTime = 0.5;
2717 		}
2718 		//
2719 		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, cs->bs->enemy );
2720 		VectorSubtract( move.endpos, cs->bs->origin, vec );
2721 		moveDist = VectorNormalize( vec );
2722 		//
2723 		if ( cs->bs->weaponnum == WP_GAUNTLET ) {
2724 			if ( move.stopevent == PREDICTSTOP_HITENT ) {
2725 				AICast_AimAtEnemy( cs );
2726 				trap_EA_Attack( bs->client );
2727 				bs->flags |= BFL_ATTACKED;
2728 			}
2729 		}
2730 		//
2731 		// do we went to play a diving animation into a cover position?
2732 		else if (   ( cs->attributes[TACTICAL] > 0.85 && cs->aiFlags & AIFL_ROLL_ANIM && !client->ps.torsoTimer && !client->ps.legsTimer && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.8 && move.groundEntityNum == ENTITYNUM_WORLD ) &&
2733 					( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->attackcrouch_time > trap_AAS_Time(), qfalse ) ) ) {
2734 			return AIFunc_BattleRollStart( cs, vec );
2735 		}
2736 		//
2737 		else if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) {
2738 			int destarea, simarea, starttravel, simtravel;
2739 			// if we'll be closer after the move, proceed
2740 			destarea = BotPointAreaNum( destorg );
2741 			simarea = BotPointAreaNum( move.endpos );
2742 			starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags );
2743 			simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags );
2744 			if ( simtravel < starttravel ) {
2745 				return AIFunc_FlipMoveStart( cs, vec );
2746 			}
2747 		}
2748 		// slow down? so we don't go too far from behind the obstruction which is protecting us
2749 		else if ( ( cs->obstructingTime < level.time ) && ( cs->attributes[TACTICAL] > 0.1 ) &&
2750 				  ( AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) {
2751 			// start a crouch attack?
2752 			//if (cs->attributes[ATTACK_CROUCH] > 0.1) {
2753 			//	cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
2754 			//else
2755 			cs->bs->attackcrouch_time = 0;
2756 			if ( cs->bs->cur_ps.viewheight > cs->bs->cur_ps.crouchViewHeight && cs->attributes[RUNNING_SPEED] * cs->speedScale > 120 ) {
2757 				cs->speedScale = 120.0 * cs->attributes[RUNNING_SPEED];
2758 			}
2759 			// also face them, ready for the attack
2760 			AICast_AimAtEnemy( cs );
2761 			/* disabled, causes them to use up ammo in the clip before they get visible
2762 			if ((cs->castScriptStatus.scriptNoAttackTime < level.time) && (cs->noAttackTime < level.time)) {
2763 				// if we are using a bullet weapon, start firing now
2764 				switch (cs->bs->weaponnum) {
2765 				case WP_MP40:
2766 				case WP_VENOM:
2767 				case WP_THOMPSON:
2768 				case WP_STEN:	//----(SA)	added
2769 					trap_EA_Attack(cs->entityNum);
2770 				}
2771 			}
2772 			*/
2773 		}
2774 	}
2775 
2776 	// reload?
2777 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
2778 		trap_EA_Reload( cs->entityNum );
2779 	}
2780 
2781 	return NULL;
2782 }
2783 
2784 /*
2785 ============
2786 AIFunc_BattleChaseStart()
2787 ============
2788 */
AIFunc_BattleChaseStart(cast_state_t * cs)2789 char *AIFunc_BattleChaseStart( cast_state_t *cs ) {
2790 	cs->startBattleChaseTime = level.time;
2791 	cs->combatGoalTime = 0;
2792 	cs->battleChaseMarker = -99;
2793 	cs->battleChaseMarkerDir = 1;
2794 	// don't wait too long before taking cover, if we just aborted one
2795 	if ( cs->takeCoverTime > level.time ) {
2796 		cs->takeCoverTime = level.time + 1500 + rand() % 500;
2797 	}
2798 	//
2799 	// start a crouch attack?
2800 	if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
2801 		cs->aiFlags |= AIFL_ATTACK_CROUCH;
2802 	} else {
2803 		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
2804 	}
2805 	//
2806 	cs->aifunc = AIFunc_BattleChase;
2807 	return "AIFunc_BattleChase";
2808 }
2809 
2810 /*
2811 ============
2812 AIFunc_AvoidDanger()
2813 ============
2814 */
AIFunc_AvoidDanger(cast_state_t * cs)2815 char *AIFunc_AvoidDanger( cast_state_t *cs ) {
2816 	bot_state_t *bs;
2817 	vec3_t destorg, vec;
2818 	float dist;
2819 	int enemies[MAX_CLIENTS], numEnemies, i;
2820 	qboolean shouldAttack;
2821 	gentity_t *ent;
2822 	trace_t tr;
2823 	vec3_t end;
2824 	gentity_t *danger;
2825 
2826 	// we need to move towards it
2827 	bs = cs->bs;
2828 	ent = g_entities + cs->entityNum;
2829 	//
2830 	// TODO: if we are on fire, play the correct torso animation
2831 	if ( ent->s.onFireEnd > level.time ) {
2832 		// set the animation, and a short timer, but long enough to last until the next frame
2833 		//if (g_cheats.integer) G_Printf( "TODO: torso onfire animation\n" );
2834 	}
2835 	//
2836 	// is the danger gone?
2837 	if ( cs->dangerEntityValidTime < level.time ) {
2838 		return AIFunc_DefaultStart( cs );
2839 	}
2840 	//
2841 	// special case: if it's a grenade, and it's going to land near us with some time left before it
2842 	// explodes, try and kick it back
2843 	//
2844 	danger = &g_entities[cs->dangerEntity];
2845 	if ( ent->s.onFireEnd < level.time ) {
2846 		if ( ( danger->s.weapon == WP_GRENADE_LAUNCHER || danger->s.weapon == WP_GRENADE_PINEAPPLE ) &&
2847 			 ( danger->nextthink - level.time > 1500 ) &&
2848 			 ( level.lastGrenadeKick < level.time - 3000 ) &&
2849 			 ( cs->aiFlags & AIFL_CATCH_GRENADE ) &&
2850 			 !( danger->flags & FL_AI_GRENADE_KICK ) ) {
2851 			// if it was thrown by a friend of ours, leave it alone
2852 			if ( !AICast_SameTeam( cs, danger->r.ownerNum ) ) {
2853 				if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) {
2854 					// make sure it's a valid position, and drop it down to the ground
2855 					cs->takeCoverPos[2] += -ent->r.mins[2] + 12;
2856 					VectorCopy( cs->takeCoverPos, end );
2857 					end[2] -= 90;
2858 					trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID );
2859 					VectorCopy( tr.endpos, cs->takeCoverPos );
2860 					if ( !tr.startsolid && ( tr.fraction < 1.0 ) &&
2861 						 VectorDistance( cs->bs->origin, cs->takeCoverPos ) < cs->attributes[RUNNING_SPEED] * 0.0004 * ( danger->nextthink - level.time - 2000 ) ) {
2862 
2863 						// check for a clear path to the grenade
2864 						trap_Trace( &tr, cs->bs->origin, ent->r.mins, ent->r.maxs, cs->takeCoverPos, cs->entityNum, MASK_SOLID );
2865 
2866 						if ( VectorDistance( tr.endpos, cs->takeCoverPos ) < 8 ) {
2867 							danger->flags |= FL_AI_GRENADE_KICK;
2868 							ent->flags |= FL_AI_GRENADE_KICK;
2869 							level.lastGrenadeKick = level.time;
2870 							return AIFunc_GrenadeKickStart( cs );   // we should decide our course of action within this start function (dive or return grenade)
2871 						}
2872 					}
2873 				}
2874 			}
2875 			// if it's really close to us, and we're heading for it, may as well pick it up
2876 			if ( VectorLength( danger->s.pos.trDelta ) < 10 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 &&
2877 				 ( level.lastGrenadeKick < level.time - 3000 ) &&
2878 				 ( cs->aiFlags & AIFL_CATCH_GRENADE ) ) {
2879 				vec3_t vec;
2880 				VectorSubtract( danger->r.currentOrigin, cs->bs->origin, vec );
2881 				if ( DotProduct( vec, cs->bs->velocity ) > 0 ) {
2882 					danger->flags |= FL_AI_GRENADE_KICK;
2883 					ent->flags |= FL_AI_GRENADE_KICK;
2884 					level.lastGrenadeKick = level.time;
2885 					return AIFunc_GrenadeKickStart( cs );   // we should decide our course of action within this start function (dive or return grenade)
2886 				}
2887 			}
2888 		}
2889 	}
2890 	//
2891 	if ( g_entities[cs->dangerEntity].inuse ) {
2892 		// is our current destination still safe?
2893 		if ( Distance( cs->dangerEntityPos, cs->takeCoverPos ) < cs->dangerDist &&
2894 			 AICast_VisibleFromPos( cs->dangerEntityPos, cs->dangerEntity, cs->takeCoverPos, cs->entityNum, qfalse ) ) {
2895 			//G_Printf("current coverPos is dangerous, looking for a better place..\n" );
2896 			if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
2897 				// just run away from it ???
2898 			}
2899 		}
2900 	} else {
2901 		// the entity isn't here anymore, so stop hiding
2902 		cs->dangerEntityValidTime = -1;
2903 		return AIFunc_DefaultStart( cs );
2904 	}
2905 	//
2906 	VectorCopy( cs->takeCoverPos, destorg );
2907 	VectorSubtract( destorg, cs->bs->origin, vec );
2908 	vec[2] *= 0.2;
2909 	dist = VectorLength( vec );
2910 	//
2911 	shouldAttack = qfalse;
2912 	if ( ent->s.onFireEnd < level.time ) {
2913 		// look for things we should attack
2914 		numEnemies = AICast_ScanForEnemies( cs, enemies );
2915 		if ( numEnemies > 0 ) {
2916 			// default to the first known enemy, overwrite if we find a clearer shot
2917 			cs->bs->enemy = enemies[0];
2918 			//
2919 			for ( i = 0; i < numEnemies; i++ ) {
2920 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
2921 					cs->bs->enemy = enemies[i];
2922 					shouldAttack = qtrue;
2923 					break;
2924 				} else if ( cs->bs->enemy < 0 ) {
2925 					cs->lastEnemy = enemies[i];
2926 				}
2927 			}
2928 		}
2929 	}
2930 	//
2931 	// if we are now safe from the danger, stop running away
2932 	if ( cs->dangerEntity >= MAX_CLIENTS && Distance( cs->dangerEntityPos, cs->bs->origin ) > cs->dangerDist * 1.5 ) {
2933 		// don't move, wait for danger to pass
2934 	} else
2935 	// are we close enough to the goal?
2936 	if ( dist > 8 ) {
2937 		moveresult = AICast_MoveToPos( cs, destorg, -1 );
2938 		if ( moveresult ) {
2939 			//if the movement failed
2940 			if ( moveresult->failure || moveresult->blocked ) {
2941 				//reset the avoid reach, otherwise bot is stuck in current area
2942 				trap_BotResetAvoidReach( bs->ms );
2943 				if ( g_entities[cs->dangerEntity].inuse ) {
2944 					// find a better spot?
2945 					AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos );
2946 				} else {
2947 					VectorCopy( cs->bs->origin, cs->takeCoverPos );
2948 				}
2949 			}
2950 		}
2951 		if ( ent->s.onFireEnd < level.time ) {
2952 			// slow down real close to the goal, so we don't go passed it
2953 			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
2954 		}
2955 		//
2956 		// pretend we can still see them while we run to our hide pos, this way they are less likely
2957 		// to forget about their enemy once they get there
2958 		if ( ent->s.onFireEnd < level.time && cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && ( cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 10000 ) ) {
2959 			AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp );
2960 		}
2961 
2962 	} else {
2963 		// set our origin as the hidepos, that way if we are still in danger, we should find a better spot
2964 		VectorCopy( cs->bs->origin, cs->takeCoverPos );
2965 		// check for a movement we should be making
2966 		if ( cs->obstructingTime > level.time ) {
2967 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
2968 		}
2969 
2970 		// if we are on fire, never stop
2971 		if ( ent->s.onFireEnd > level.time ) {
2972 			VectorCopy( cs->bs->origin, cs->dangerEntityPos );
2973 			cs->dangerEntityValidTime = level.time + 10000;
2974 		}
2975 
2976 	}
2977 	//
2978 	// if we should be attacking something on our way
2979 	if ( shouldAttack ) {
2980 		//attack the enemy if possible
2981 		AICast_ProcessAttack( cs );
2982 	} else { //if (dist < 48) {
2983 		// if we've recently been in a fight, look towards the enemy
2984 		if ( cs->lastEnemy >= 0 ) {
2985 			// if we only just recently saw them, face that direction
2986 			if ( cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 ) ) {
2987 				vec3_t dir;
2988 				//
2989 				VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
2990 				VectorNormalize( dir );
2991 				vectoangles( dir, cs->bs->ideal_viewangles );
2992 			}
2993 		}
2994 	}
2995 
2996 	// reload?
2997 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
2998 		trap_EA_Reload( cs->entityNum );
2999 	}
3000 
3001 	return NULL;
3002 }
3003 
3004 /*
3005 ============
3006 AIFunc_AvoidDangerStart()
3007 ============
3008 */
AIFunc_AvoidDangerStart(cast_state_t * cs)3009 char *AIFunc_AvoidDangerStart( cast_state_t *cs ) {
3010 	//
3011 	//if (!AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum )) {
3012 	// always run to the cover point
3013 	cs->bs->attackcrouch_time = 0;
3014 	//}
3015 	// make sure we move if we are allowed (scripting will overwrite this if necessary)
3016 	cs->castScriptStatus.scriptNoMoveTime = 0;
3017 	// resume following once danger has gone
3018 	cs->castScriptStatus.scriptGotoId = -1;
3019 	//
3020 	cs->aifunc = AIFunc_AvoidDanger;
3021 	return "AIFunc_AvoidDanger";
3022 }
3023 
3024 /*
3025 ============
3026 AIFunc_BattleTakeCover()
3027 ============
3028 */
AIFunc_BattleTakeCover(cast_state_t * cs)3029 char *AIFunc_BattleTakeCover( cast_state_t *cs ) {
3030 	bot_state_t *bs;
3031 	vec3_t destorg, vec;
3032 	float dist, moveDist;
3033 	int enemies[MAX_CLIENTS], numEnemies, i;
3034 	qboolean shouldAttack;
3035 	aicast_predictmove_t move;
3036 	gclient_t   *client = &level.clients[cs->entityNum];
3037 	//
3038 	// do we need to avoid a danger?
3039 	if ( cs->dangerEntityValidTime >= level.time ) {
3040 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
3041 			// shit??
3042 		}
3043 		// go to a position that cannot be seen from the dangerPos
3044 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
3045 		cs->bs->attackcrouch_time = 0;
3046 		return AIFunc_AvoidDangerStart( cs );
3047 	}
3048 	//
3049 	// are we waiting for a door?
3050 	if ( cs->doorMarkerTime > level.time - 100 ) {
3051 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
3052 	}
3053 
3054 	// we need to move towards it
3055 	bs = cs->bs;
3056 	//
3057 	// note: removing this will cause problems down below!
3058 	if ( bs->enemy < 0 ) {
3059 		return AIFunc_IdleStart( cs );
3060 	}
3061 	//
3062 	VectorCopy( cs->takeCoverPos, destorg );
3063 	VectorSubtract( destorg, cs->bs->origin, vec );
3064 	vec[2] *= 0.2;
3065 	dist = VectorLength( vec );
3066 	//
3067 	// look for things we should attack
3068 	// if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding)
3069 	shouldAttack = qfalse;
3070 	numEnemies = AICast_ScanForEnemies( cs, enemies );
3071 	if ( numEnemies == -1 ) { // query mode
3072 		return NULL;
3073 	} else if ( numEnemies == -2 )     { // inspection may be required
3074 		char *retval;
3075 		// TTimo gcc: suggest parentheses around assignment used as truth value
3076 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
3077 			return retval;
3078 		}
3079 	} else if ( numEnemies == -3 )     { // bullet impact
3080 		if ( cs->aiState < AISTATE_COMBAT ) {
3081 			return AIFunc_InspectBulletImpactStart( cs );
3082 		}
3083 	} else if ( numEnemies == -4 )     { // audible event
3084 		if ( cs->aiState < AISTATE_COMBAT ) {
3085 			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
3086 		}
3087 	} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
3088 		if ( numEnemies > 0 ) {
3089 			// default to the first known enemy, overwrite if we find a clearer shot
3090 			cs->bs->enemy = enemies[0];
3091 			//
3092 			for ( i = 0; i < numEnemies; i++ ) {
3093 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
3094 					cs->bs->enemy = enemies[i];
3095 					shouldAttack = qtrue;
3096 					break;
3097 				} else if ( cs->bs->enemy < 0 ) {
3098 					cs->lastEnemy = enemies[i];
3099 				}
3100 			}
3101 		}
3102 	}
3103 	//
3104 	if ( !shouldAttack ) {
3105 		// if the enemy can see our hide position, find a better spot
3106 		if ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) {
3107 			if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) {
3108 				// shit!! umm.. try and fire?
3109 				return AIFunc_BattleStart( cs );
3110 			} else {    // recalc distance
3111 				VectorCopy( cs->takeCoverPos, destorg );
3112 				VectorSubtract( destorg, cs->bs->origin, vec );
3113 				vec[2] *= 0.2;
3114 				dist = VectorLength( vec );
3115 			}
3116 		} else if ( dist < 8 )     {
3117 			// if they can see us, find a better spot
3118 			if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) {
3119 				if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) {
3120 					// shit!! umm.. try and fire?
3121 					return AIFunc_BattleStart( cs );
3122 				} else {    // recalc distance
3123 					VectorCopy( cs->takeCoverPos, destorg );
3124 					VectorSubtract( destorg, cs->bs->origin, vec );
3125 					vec[2] *= 0.2;
3126 					dist = VectorLength( vec );
3127 				}
3128 			}
3129 		}
3130 		//cs->takeCoverTime = level.time + 1000;
3131 	}
3132 	//
3133 	// pretend we can still see them while we run to our hide pos, this way they are less likely
3134 	// to forget about their enemy once they get there
3135 // DISABLED: doesn't work well with new AI system
3136 	//if (cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && (cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 2000)) {
3137 	//	AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp );
3138 	//}
3139 	//
3140 	memset( &move, 0, sizeof( move ) );
3141 	//
3142 	// are we close enough to the goal?
3143 	if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 ) {
3144 		const float simTime = 0.8;
3145 		float enemyDist;
3146 		//
3147 		// we haven't reached it yet, make sure we at least wait there for a few seconds after arriving
3148 		cs->takeCoverTime = level.time + 2000 + rand() % 2000;
3149 		//
3150 		moveresult = AICast_MoveToPos( cs, destorg, -1 );
3151 		if ( moveresult ) {
3152 			//if the movement failed
3153 			if ( moveresult->failure ) {
3154 				//reset the avoid reach, otherwise bot is stuck in current area
3155 				trap_BotResetAvoidReach( bs->ms );
3156 				// couldn't get there, so stop trying to get there
3157 				VectorClear( cs->takeCoverPos );
3158 				dist = 0;
3159 			}
3160 			//
3161 			if ( moveresult->blocked ) {
3162 				// abort the TakeCover
3163 				VectorClear( cs->takeCoverPos );
3164 				dist = 0;
3165 			}
3166 		}
3167 		//
3168 		// if we are going to bump into something soon, abort it
3169 		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 );
3170 		enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase );
3171 		VectorSubtract( move.endpos, cs->bs->origin, vec );
3172 		moveDist = VectorNormalize( vec );
3173 		//
3174 		if (    ( move.numtouch && move.touchents[0] < aicast_maxclients )    // hit something
3175 				// or moved closer to the enemy
3176 				||  (   ( enemyDist < 128 )
3177 						&&  ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) {
3178 			// abort the manouver
3179 			VectorClear( cs->takeCoverPos );
3180 		}
3181 		//
3182 		// do we went to play a rolling animation into a cover position?
3183 		else if (   ( cs->aiFlags & AIFL_DIVE_ANIM && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > 64 && move.groundEntityNum == ENTITYNUM_WORLD ) &&
3184 					( shouldAttack && !AICast_VisibleFromPos( g_entities[cs->bs->enemy].s.pos.trBase, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) {
3185 			VectorClear( cs->takeCoverPos );    // stay there when done rolling
3186 			return AIFunc_BattleDiveStart( cs, vec );
3187 		}
3188 		//
3189 		// we should slow down on approaching the destination point
3190 		else if ( dist < 64 ) {
3191 			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
3192 		}
3193 		//
3194 		// if they cant see us, then stay here
3195 		if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse )
3196 			 && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, cs->bs->origin, cs->entityNum, qfalse ) ) {
3197 			VectorCopy( move.endpos, cs->takeCoverPos );
3198 			cs->aiFlags |= AIFL_MISCFLAG1;  // dont do this again
3199 		}
3200 		//
3201 		if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) {
3202 			int destarea, simarea, starttravel, simtravel;
3203 			// if we'll be closer after the move, proceed
3204 			destarea = BotPointAreaNum( destorg );
3205 			simarea = BotPointAreaNum( move.endpos );
3206 			starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags );
3207 			simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags );
3208 			if ( simtravel < starttravel ) {
3209 				return AIFunc_FlipMoveStart( cs, vec );
3210 			}
3211 		}
3212 		// set crouching status
3213 		//if (dist && (cs->thinkFuncChangeTime < level.time - 2000) && (cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH)) {
3214 		if ( cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH ) {
3215 			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
3216 		}
3217 
3218 	} else {
3219 		//
3220 		// have we been Taking Cover for enough time?
3221 		if ( level.time > cs->takeCoverTime ) {
3222 			return AIFunc_DefaultStart( cs );
3223 		}
3224 		//
3225 		// check for a movement we should be making
3226 		if ( cs->obstructingTime > level.time ) {
3227 			VectorClear( cs->takeCoverPos );
3228 			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
3229 		}
3230 		// if we have some enemies that we can attack immediately (without going anywhere to chase them)
3231 		if ( shouldAttack ) {
3232 			return AIFunc_BattleStart( cs );
3233 		}
3234 		// if we have some enemies in sight, but they can't attack us, flee if possible, otherwise if we are not afraid, go attack them
3235 		else if ( numEnemies ) {
3236 
3237 			// we can't hit them and they cant hit us, so dont bother doing anything
3238 
3239 			//if (!AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos )) {
3240 			//if (!AICast_WantsToTakeCover(cs, qfalse))
3241 			//return AIFunc_BattleStart( cs );
3242 			//}
3243 		}
3244 		// do we need to go to our leader?
3245 		else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
3246 			// wait until we've been hiding for long enough
3247 			if ( level.time > cs->takeCoverTime ) {
3248 				return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
3249 			}
3250 		}
3251 
3252 		// else, crouch while we hide
3253 		if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) {
3254 			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
3255 		}
3256 	}
3257 	//
3258 	// if we should be attacking something on our way
3259 	if ( shouldAttack ) {
3260 		vec3_t vec, dir;
3261 		float dist;
3262 		//
3263 		// if they are close, and we're heading for them, we should abort this manouver
3264 		VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec );
3265 		if ( ( dist = VectorNormalize( vec ) ) < 256 ) {
3266 			if ( VectorNormalize2( bs->velocity, dir ) > 20 ) {   // we are moving
3267 				if ( DotProduct( dir, vec ) > 0 ) {
3268 					// abort
3269 					return AIFunc_BattleStart( cs );
3270 				}
3271 			}
3272 		}
3273 		//
3274 		// if the enemy can see our hide position, abort the manouver
3275 		if ( ( cs->thinkFuncChangeTime < level.time - 1000 ) && ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) ) {
3276 			// abort
3277 			return AIFunc_BattleStart( cs );
3278 		}
3279 		//
3280 		// if we are tactical and can crouch, do so
3281 		if ( !move.numtouch && ( dist > 128 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[ATTACK_CROUCH] > 0.1 &&
3282 			 ( cs->bs->attackcrouch_time >= trap_AAS_Time() ) ) {
3283 			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
3284 		}
3285 		//
3286 		//attack the enemy if possible
3287 		AICast_ProcessAttack( cs );
3288 		//
3289 	} else /*if (dist < 48)*/ {
3290 		// if we've recently been in a fight, look towards the enemy
3291 		if ( cs->bs->enemy >= 0 ) {
3292 			AICast_AimAtEnemy( cs );
3293 		} else if ( cs->lastEnemy >= 0 )     {
3294 			// if we are not moving, face them
3295 			if ( VectorLength( cs->bs->cur_ps.velocity ) < 50 ) {
3296 				vec3_t dir;
3297 				//
3298 				VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
3299 				VectorNormalize( dir );
3300 				vectoangles( dir, cs->bs->ideal_viewangles );
3301 			}
3302 		} else if ( !cs->crouchHideFlag )     { // no enemy, and no need to crouch, so stop crouching
3303 			//if (cs->bs->attackcrouch_time > trap_AAS_Time() + 1) {
3304 			//	cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
3305 			//}
3306 		}
3307 		// reload?
3308 		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
3309 			trap_EA_Reload( cs->entityNum );
3310 		}
3311 	}
3312 
3313 	return NULL;
3314 }
3315 
3316 /*
3317 ============
3318 AIFunc_BattleTakeCoverStart()
3319 ============
3320 */
AIFunc_BattleTakeCoverStart(cast_state_t * cs)3321 char *AIFunc_BattleTakeCoverStart( cast_state_t *cs ) {
3322 // debugging
3323 #ifdef DEBUG
3324 	if ( cs->attributes[AGGRESSION] >= 1.0 ) {
3325 		AICast_Printf( 0, "AI taking cover with full aggression!\n" );
3326 	}
3327 #endif
3328 
3329 	if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) {
3330 		// always run to the cover point
3331 		cs->bs->attackcrouch_time = 0;
3332 		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
3333 	} else {
3334 		// if we arent crouching, start crouching soon after we start retreating
3335 		if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
3336 			cs->aiFlags |= AIFL_ATTACK_CROUCH;
3337 		} else {
3338 			cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
3339 		}
3340 	}
3341 
3342 	// miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over
3343 	cs->aiFlags &= ~AIFL_MISCFLAG1;
3344 
3345 	cs->aifunc = AIFunc_BattleTakeCover;
3346 	return "AIFunc_BattleTakeCover";
3347 }
3348 
3349 /*
3350 ============
3351 AIFunc_GrenadeFlush()
3352 ============
3353 */
AIFunc_GrenadeFlush(cast_state_t * cs)3354 char *AIFunc_GrenadeFlush( cast_state_t *cs ) {
3355 	vec3_t dir;
3356 	gentity_t   *followent, *grenade, *ent;
3357 	bot_state_t *bs;
3358 	vec3_t destorg, endPos;
3359 	qboolean moved = qfalse;
3360 	int hitclient;
3361 	qboolean attacked = qfalse;
3362 	float dist, oldyaw;
3363 	int grenadeType;
3364 
3365 	bs = cs->bs;
3366 	ent = &g_entities[cs->entityNum];
3367 	//
3368 	// if we are throwing the grenade, keep holding down fire
3369 
3370 	// (SA) probably read the fweapon from t
3371 	if ( cs->grenadeFlushFiring ) {
3372 		if ( cs->weaponFireTimes[cs->grenadeFlushFiring] < cs->thinkFuncChangeTime ) {
3373 			// have we switched weapons?
3374 			if ( cs->bs->weaponnum != cs->grenadeFlushFiring ) {
3375 				// damn
3376 				hitclient = -1;
3377 			} else {
3378 				// keep checking it's ok
3379 				grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER );
3380 				hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, NULL );
3381 				G_FreeEntity( grenade );
3382 			}
3383 			if ( hitclient == -1 ) {  // doh
3384 				//G_Printf("aborted grenade\n");
3385 				cs->castScriptStatus.scriptNoMoveTime = 0;
3386 				cs->lockViewAnglesTime = 0;
3387 				AICast_ChooseWeapon( cs, qfalse );
3388 				return AIFunc_DefaultStart( cs );
3389 			}
3390 			if ( !cs->bs->cur_ps.grenadeTimeLeft ) {
3391 				// hold fire button down
3392 				trap_EA_Attack( bs->client );
3393 				bs->flags |= BFL_ATTACKED;
3394 			}
3395 			cs->lockViewAnglesTime = level.time + 500;
3396 			return NULL;
3397 		}
3398 		// the grenade/pineapple has been released!
3399 		cs->lockViewAnglesTime = -1;
3400 		cs->startGrenadeFlushTime = level.time + 2000 + rand() % 2000;    // dont throw one again for a bit
3401 		return AIFunc_DefaultStart( cs );
3402 	}
3403 	//
3404 	// do we need to avoid a danger?
3405 	if ( cs->dangerEntityValidTime >= level.time ) {
3406 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
3407 			// shit??
3408 		}
3409 		// go to a position that cannot be seen from the dangerPos
3410 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
3411 		cs->bs->attackcrouch_time = 0;
3412 		return AIFunc_AvoidDangerStart( cs );
3413 	}
3414 	//
3415 	// are we waiting for a door?
3416 	if ( cs->doorMarkerTime > level.time - 100 ) {
3417 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
3418 	}
3419 	//
3420 	if ( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) {
3421 		return AIFunc_IdleStart( cs );
3422 	}
3423 	//
3424 	if ( bs->enemy < 0 ) {
3425 		return AIFunc_IdleStart( cs );
3426 	}
3427 	//
3428 	// if we have started a script, abort the grenade flush
3429 	if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
3430 		return AIFunc_IdleStart( cs );
3431 	}
3432 	// trying for too long?
3433 	if ( cs->startGrenadeFlushTime < level.time - 4000 ) {
3434 		cs->startGrenadeFlushTime = level.time;
3435 		return AIFunc_IdleStart( cs );
3436 	}
3437 	//
3438 	followent = &g_entities[bs->enemy];
3439 	//
3440 	// if the entity is not ready yet
3441 	if ( !followent->inuse ) {
3442 		// if it's a connecting client, wait
3443 		if ( !(   ( bs->enemy < MAX_CLIENTS )
3444 				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
3445 						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
3446 			bs->enemy = -1;
3447 		}
3448 		return AIFunc_IdleStart( cs );
3449 	}
3450 	//
3451 	// if we can see them, go back to an attack state after some time
3452 	if (    AICast_CheckAttack( cs, bs->enemy, qfalse )
3453 			&&  cs->obstructingTime < level.time ) { // give us some time to throw the grenade, otherwise go back to attack state
3454 		if ( ( cs->grenadeFlushEndTime > 0 && cs->grenadeFlushEndTime < level.time ) ) {
3455 //			G_Printf( "aborting, enemy is attackable\n" );
3456 			return AIFunc_BattleStart( cs );
3457 		} else if ( cs->grenadeFlushEndTime < 0 ) {
3458 			cs->grenadeFlushEndTime = level.time + 1500;
3459 		}
3460 		//attack the enemy if possible
3461 		AICast_ProcessAttack( cs );
3462 		attacked = qtrue;
3463 	} else {
3464 		// not visible, go to their previously visible position
3465 		if ( !cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].real_visible_pos ) < 16 ) {
3466 			// we're done attacking, go back to default state, which in turn will recall previous state
3467 			//
3468 			// face the direction they currently are from this position (bit of a hack, but it looks best)
3469 			VectorSubtract( g_entities[bs->enemy].client->ps.origin, cs->vislist[bs->enemy].visible_pos, dir );
3470 			vectoangles( dir, cs->bs->ideal_viewangles );
3471 			//
3472 			//G_Printf("aborting, reached visible pos\n");
3473 			return AIFunc_DefaultStart( cs );
3474 		}
3475 	}
3476 
3477 	// is there someone else we can go for instead?
3478 	numEnemies = AICast_ScanForEnemies( cs, enemies );
3479 	if ( numEnemies == -1 ) { // query mode
3480 		return NULL;
3481 	} else if ( numEnemies == -2 )     { // inspection may be required
3482 		char *retval;
3483 
3484 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
3485 			return retval;
3486 		}
3487 	} else if ( !( cs->bs->flags & BFL_ATTACKED ) && numEnemies > 0 )       {
3488 		int i;
3489 		for ( i = 0; i < numEnemies; i++ ) {
3490 			if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
3491 				cs->bs->enemy = enemies[i];
3492 				//G_Printf("aborting, other enemy\n");
3493 				return AIFunc_BattleStart( cs );
3494 			}
3495 		}
3496 	}
3497 
3498 	if ( followent->client ) { // go to the last visible position
3499 		VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg );
3500 	} else    // assume we know where other entities are
3501 	{
3502 		VectorCopy( followent->s.pos.trBase, destorg );
3503 	}
3504 	//
3505 	dist = VectorDistance( destorg, cs->bs->origin );
3506 	//
3507 	if ( cs->vislist[bs->enemy].lastcheck_timestamp > cs->vislist[bs->enemy].real_visible_timestamp ||
3508 		 dist > 128 ) {
3509 		//
3510 		// go to them
3511 		//
3512 		if ( followent->client && followent->health <= 0 ) {
3513 			cs->bs->enemy = -1;
3514 			//G_Printf("aborting, enemy dead\n");
3515 			return AIFunc_DefaultStart( cs );
3516 		}
3517 		//
3518 		// ...........................................................
3519 		// Do the movement..
3520 		//
3521 		// move straight to them if we can
3522 		if ( ( cs->leaderNum < 0 ) &&
3523 			 ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
3524 			aicast_predictmove_t move;
3525 			vec3_t dir;
3526 			bot_input_t bi;
3527 			usercmd_t ucmd;
3528 			trace_t tr;
3529 
3530 			// trace will eliminate most unsuccessful paths
3531 			trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
3532 			if ( tr.entityNum == followent->s.number ) {
3533 				// try walking straight to them
3534 				VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
3535 				VectorNormalize( dir );
3536 				if ( !ent->waterlevel ) {
3537 					dir[2] = 0;
3538 				}
3539 				trap_EA_Move( cs->entityNum, dir, 400 );
3540 				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
3541 				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
3542 				AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy );
3543 
3544 				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
3545 					vectoangles( dir, cs->bs->ideal_viewangles );
3546 					cs->bs->ideal_viewangles[2] *= 0.5;
3547 					moved = qtrue;
3548 				}
3549 			}
3550 		}
3551 		// just go to them
3552 		if ( !moved ) {
3553 			moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
3554 			if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
3555 				cs->bs->enemy = -1;
3556 				//G_Printf("aborting, movement failure\n");
3557 				return AIFunc_DefaultStart( cs );
3558 			}
3559 		}
3560 	}
3561 	//
3562 	// ...........................................................
3563 	// if we throw a grenade from here, will it get their last visible position?
3564 	//
3565 	if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) {
3566 		grenadeType = WP_GRENADE_LAUNCHER;
3567 	} else if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) {
3568 		grenadeType = WP_GRENADE_PINEAPPLE;
3569 	} else { // not enough ammo, abort
3570 		return AIFunc_DefaultStart( cs );
3571 	}
3572 
3573 	CalcMuzzlePoints( ent, grenadeType );
3574 	// fire a dummy grenade
3575 	grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER );
3576 	// check to see what will happen
3577 	hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, endPos );
3578 	// kill the grenade
3579 	G_FreeEntity( grenade );
3580 	if ( !attacked ) {
3581 		cs->bs->weaponnum = grenadeType;    // select grenade launcher
3582 	}
3583 	// set our angles for the next frame
3584 	oldyaw = cs->bs->ideal_viewangles[YAW];
3585 	AICast_AimAtEnemy( cs );
3586 	// if we can't see them, keep facing our movement dir, but use the pitch information
3587 	if ( !AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) {
3588 		cs->bs->ideal_viewangles[YAW] = oldyaw;
3589 	}
3590 
3591 	if ( hitclient == 1 ) {
3592 		// it will hit their last visible position
3593 		// give us some time to aim and adjust
3594 		if ( cs->thinkFuncChangeTime < level.time - 200 ) {
3595 			trap_EA_Attack( bs->client );
3596 			bs->flags |= BFL_ATTACKED;
3597 			cs->bs->weaponnum = grenadeType;    // select grenade launcher
3598 			cs->grenadeFlushFiring = cs->bs->weaponnum;
3599 			// pause for a bit, so the grenade comes out correctly
3600 			cs->lockViewAnglesTime = level.time + 1200;
3601 			if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) {
3602 				cs->castScriptStatus.scriptNoMoveTime = level.time + 1200;
3603 			}
3604 			return NULL;
3605 		}
3606 	} else if ( hitclient == -1 ) {
3607 		// hit a friendly
3608 		cs->startGrenadeFlushTime = level.time + 3000;  // don't try again for a while
3609 		//G_Printf("aborting, too dangerous\n");
3610 		return AIFunc_DefaultStart( cs );
3611 	} else if ( hitclient == -2 ) {
3612 		// went too far, so angle down a bit
3613 		cs->bs->ideal_viewangles[PITCH] += 15 * random();
3614 	} else {
3615 		if ( cs->thinkFuncChangeTime < level.time - 200 ) {
3616 			// if it went reasonably close to them, but safe from us, then fire away
3617 			if ( Distance( endPos, cs->bs->origin ) > 100 + Distance( endPos, g_entities[cs->bs->enemy].r.currentOrigin ) ) {
3618 				trap_EA_Attack( bs->client );
3619 				bs->flags |= BFL_ATTACKED;
3620 				cs->bs->weaponnum = grenadeType;    // select grenade launcher
3621 				cs->grenadeFlushFiring = cs->bs->weaponnum;
3622 				// pause for a bit, so the grenade comes out correctly
3623 				cs->lockViewAnglesTime = level.time + 1200;
3624 				if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) {
3625 					cs->castScriptStatus.scriptNoMoveTime = level.time + 1200;
3626 				}
3627 				return NULL;
3628 			}
3629 		}
3630 		cs->bs->ideal_viewangles[PITCH] += -10 * random();
3631 	}
3632 	//
3633 	return NULL;
3634 }
3635 
3636 /*
3637 ============
3638 AIFunc_GrenadeFlushStart()
3639 ============
3640 */
AIFunc_GrenadeFlushStart(cast_state_t * cs)3641 char *AIFunc_GrenadeFlushStart( cast_state_t *cs ) {
3642 	lastGrenadeFlush = level.time; // + rand()%2000;
3643 	cs->startGrenadeFlushTime = level.time;
3644 	cs->grenadeFlushEndTime = -1;
3645 	cs->lockViewAnglesTime = 0;
3646 	cs->combatGoalTime = 0;
3647 	cs->grenadeFlushFiring = qfalse;
3648 	// don't wait too long before taking cover, if we just aborted one
3649 	if ( cs->takeCoverTime > level.time + 1000 ) {
3650 		cs->takeCoverTime = level.time + 500 + rand() % 500;
3651 	}
3652 	//
3653 	cs->aifunc = AIFunc_GrenadeFlush;
3654 	return "AIFunc_GrenadeFlush";
3655 }
3656 
3657 /*
3658 ============
3659 AIFunc_BattleMG42()
3660 ============
3661 */
AIFunc_BattleMG42(cast_state_t * cs)3662 char *AIFunc_BattleMG42( cast_state_t *cs ) {
3663 	bot_state_t *bs;
3664 	gentity_t *mg42;
3665 	vec3_t angles, vec;
3666 
3667 	mg42 = &g_entities[cs->mountedEntity];
3668 	bs = cs->bs;
3669 
3670 	// have we dismounted the MG42?
3671 	if ( !g_entities[cs->entityNum].active ) {
3672 		return AIFunc_DefaultStart( cs );
3673 	}
3674 
3675 	// if enemy is dead, stop attacking them
3676 	if ( g_entities[bs->enemy].health <= 0 ) {
3677 		bs->enemy = -1;
3678 	}
3679 
3680 	//if no enemy, or our current enemy isn't attackable, look for a better enemy
3681 	if ( bs->enemy >= 0 ) {
3682 		if ( cs->vislist[bs->enemy].real_visible_timestamp && cs->vislist[bs->enemy].real_visible_timestamp > ( level.time - 5000 ) ) {
3683 			VectorSubtract( cs->vislist[bs->enemy].real_visible_pos, mg42->r.currentOrigin, vec );
3684 		} else if ( cs->vislist[bs->enemy].visible_timestamp && cs->vislist[bs->enemy].visible_timestamp > ( level.time - 5000 ) ) {
3685 			VectorSubtract( cs->vislist[bs->enemy].visible_pos, mg42->r.currentOrigin, vec );
3686 		} else { // just aim straight forward
3687 			AngleVectors( mg42->s.angles, vec, NULL, NULL );
3688 		}
3689 
3690 		VectorNormalize( vec );
3691 		vectoangles( vec, angles );
3692 		angles[PITCH] = AngleNormalize180( angles[PITCH] );
3693 	}
3694 
3695 	if (    bs->enemy < 0 ||
3696 			!AICast_CheckAttack( cs, bs->enemy, qfalse ) ||
3697 			( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) ||
3698 			( angles[PITCH] < 0 && angles[PITCH] + 5 < -mg42->varc ) ||
3699 			( angles[PITCH] > 0 && angles[PITCH] - 5 > 5.0 ) ) {
3700 		qboolean shouldAttack;
3701 
3702 		// look for a better enemy
3703 		numEnemies = AICast_ScanForEnemies( cs, enemies );
3704 		if ( numEnemies == -1 ) { // query mode
3705 			return NULL;
3706 		}
3707 		if ( numEnemies == -2 ) { // inspection may be required
3708 			char *retval;
3709 
3710 			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
3711 				return retval;
3712 			}
3713 		}
3714 		shouldAttack = qfalse;
3715 		if ( numEnemies > 0 ) {
3716 			int i;
3717 			// default to the first known enemy, overwrite if we find a clearer shot
3718 			cs->bs->enemy = enemies[0];
3719 			//
3720 			for ( i = 0; i < numEnemies; i++ ) {
3721 				if ( !cs->vislist[enemies[i]].real_visible_timestamp ||
3722 					 ( cs->vislist[enemies[i]].real_visible_timestamp < level.time - 2000 ) ) {
3723 					// we can't see them, ignore them
3724 					continue;
3725 				}
3726 
3727 				// if they are in the range
3728 				if ( cs->vislist[enemies[i]].real_visible_timestamp > ( level.time - 5000 ) ) {
3729 					VectorSubtract( cs->vislist[enemies[i]].real_visible_pos, mg42->r.currentOrigin, vec );
3730 				} else {
3731 					VectorSubtract( cs->vislist[enemies[i]].visible_pos, mg42->r.currentOrigin, vec );
3732 				}
3733 				VectorNormalize( vec );
3734 				vectoangles( vec, angles );
3735 				angles[PITCH] = AngleNormalize180( angles[PITCH] );
3736 				if ( !(  ( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) ||
3737 						 ( angles[YAW] < 0 && angles[YAW] + 2 < -mg42->varc ) ||
3738 						 ( angles[YAW] > 0 && angles[YAW] - 2 > 5.0 ) ) ) {
3739 					if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
3740 						cs->bs->enemy = enemies[i];
3741 						shouldAttack = qtrue;
3742 						break;
3743 					} else if ( AICast_CheckAttack( cs, enemies[i], qtrue ) ) {
3744 						// keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot
3745 						cs->bs->enemy = enemies[i];
3746 						shouldAttack = qtrue;
3747 					}
3748 				}
3749 			}
3750 		}
3751 
3752 		if ( !shouldAttack ) {
3753 			// keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot
3754 			if ( bs->enemy < 0 || !AICast_CheckAttack( cs, bs->enemy, qtrue ) ||
3755 				 (   !cs->vislist[bs->enemy].real_visible_timestamp ||
3756 					 ( cs->vislist[bs->enemy].real_visible_timestamp < level.time - 2000 ) ) ) {
3757 				// face straight forward
3758 				cs->bs->ideal_viewangles[PITCH] = 0;
3759 				return NULL;
3760 			}
3761 		}
3762 	}
3763 	//
3764 	// hold down fire, and track them
3765 	//
3766 	// TODO: play a special "holding mg42" torso animation
3767 	//
3768 	VectorCopy( angles, bs->ideal_viewangles );
3769 	if ( cs->triggerReleaseTime < level.time ) {
3770 		trap_EA_Attack( bs->client );
3771 		bs->flags |= BFL_ATTACKED;
3772 
3773 		if ( cs->triggerReleaseTime < level.time - 3000 ) {
3774 			cs->triggerReleaseTime = level.time + 700 + rand() % 700;
3775 		}
3776 	}
3777 	//
3778 	return NULL;
3779 }
3780 
3781 /*
3782 ============
3783 AIFunc_BattleMG42Start()
3784 ============
3785 */
AIFunc_BattleMG42Start(cast_state_t * cs)3786 char *AIFunc_BattleMG42Start( cast_state_t *cs ) {
3787 	cs->aifunc = AIFunc_BattleMG42;
3788 	return "AIFunc_BattleMG42";
3789 }
3790 
3791 /*
3792 ============
3793 AIFunc_InspectBody()
3794 
3795   go up to the enemy, and have a good look at them, randomly taunt them
3796 ============
3797 */
AIFunc_InspectBody(cast_state_t * cs)3798 char *AIFunc_InspectBody( cast_state_t *cs ) {
3799 	bot_state_t *bs;
3800 	vec3_t destorg, enemyOrg;
3801 	//
3802 	// stop crouching
3803 	cs->bs->attackcrouch_time = 0;
3804 	//
3805 	// do we need to avoid a danger?
3806 	if ( cs->dangerEntityValidTime >= level.time ) {
3807 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
3808 			// shit??
3809 		}
3810 		// go to a position that cannot be seen from the dangerPos
3811 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
3812 		cs->bs->attackcrouch_time = 0;
3813 		return AIFunc_AvoidDangerStart( cs );
3814 	}
3815 	//
3816 	// are we waiting for a door?
3817 	if ( cs->doorMarkerTime > level.time - 100 ) {
3818 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
3819 	}
3820 	//
3821 	// if running a script
3822 	if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
3823 		cs->bs->enemy = -1;
3824 		return AIFunc_IdleStart( cs );
3825 	}
3826 	//
3827 	bs = cs->bs;
3828 	//
3829 	if ( bs->enemy < 0 ) {
3830 		return AIFunc_IdleStart( cs );
3831 	}
3832 	//
3833 	// look for things we should attack
3834 	numEnemies = AICast_ScanForEnemies( cs, enemies );
3835 	if ( numEnemies == -1 ) { // query mode
3836 		return NULL;
3837 	} else if ( numEnemies == -2 )     { // inspection may be required
3838 		char *retval;
3839 
3840 		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
3841 			return retval;
3842 		}
3843 	} else if ( numEnemies == -3 )     { // bullet impact
3844 		if ( cs->aiState < AISTATE_COMBAT ) {
3845 			return AIFunc_InspectBulletImpactStart( cs );
3846 		}
3847 	} else if ( numEnemies == -4 )     { // audible event
3848 		if ( cs->aiState < AISTATE_COMBAT ) {
3849 			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
3850 		}
3851 	} else if ( numEnemies > 0 )     {
3852 		cs->bs->enemy = enemies[0]; // just attack the first one
3853 		return AIFunc_BattleStart( cs );
3854 	}
3855 	//
3856 	VectorCopy( cs->vislist[bs->enemy].visible_pos, enemyOrg );
3857 	if ( ( cs->inspectBodyTime < 0 ) && ( Distance( cs->bs->origin, enemyOrg ) > 64 ) ) {
3858 		// if they were gibbed, don't go all the way
3859 		if ( g_entities[cs->bs->enemy].health < GIB_HEALTH && ( Distance( cs->bs->origin, enemyOrg ) < 180 ) ) {
3860 			cs->inspectBodyTime = level.time + 1000 + rand() % 1000;
3861 			trap_EA_Gesture( cs->entityNum );
3862 			G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) );
3863 		}
3864 		// walk to them
3865 		if ( cs->movestate != MS_CROUCH ) {
3866 			cs->movestate = MS_WALK;
3867 		}
3868 		cs->movestateType = MSTYPE_TEMPORARY;
3869 		//
3870 		moveresult = AICast_MoveToPos( cs, enemyOrg, -1 );
3871 		//if the movement failed
3872 		if ( !moveresult || moveresult->failure || moveresult->blocked ) {
3873 			//reset the avoid reach, otherwise bot is stuck in current area
3874 			trap_BotResetAvoidReach( bs->ms );
3875 			// couldn't get there, so stop trying to get there
3876 			cs->bs->enemy = -1;
3877 			return AIFunc_IdleStart( cs );
3878 		}
3879 		if ( Distance( cs->bs->origin, enemyOrg ) < 180 ) {
3880 			// look down at them
3881 			VectorSubtract( enemyOrg, cs->bs->origin, destorg );
3882 			destorg[2] -= 20;
3883 			VectorNormalize( destorg );
3884 			vectoangles( destorg, cs->bs->ideal_viewangles );
3885 		}
3886 	} else if ( cs->inspectBodyTime < 0 ) {
3887 		// just reached them
3888 		cs->inspectBodyTime = level.time + 1000 + rand() % 1000;
3889 		trap_EA_Gesture( cs->entityNum );
3890 		G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) );
3891 	} else if ( cs->inspectBodyTime < level.time ) {
3892 		vec3_t vec;
3893 		VectorSubtract( cs->startOrigin, cs->bs->origin, vec );
3894 		vec[2] = 0;
3895 		// ready to go back to start position
3896 		if ( VectorLength( vec ) > 64 ) {
3897 			if ( cs->movestate != MS_CROUCH ) {
3898 				cs->movestate = MS_WALK;
3899 			}
3900 			cs->movestateType = MSTYPE_TEMPORARY;
3901 			moveresult = AICast_MoveToPos( cs, cs->startOrigin, -1 );
3902 			//if the movement failed
3903 			if ( !moveresult || moveresult->failure || moveresult->blocked ) {
3904 				//reset the avoid reach, otherwise bot is stuck in current area
3905 				trap_BotResetAvoidReach( bs->ms );
3906 				// couldn't get there, so stop trying to get there
3907 				cs->bs->enemy = -1;
3908 				return AIFunc_IdleStart( cs );
3909 			}
3910 			// stay looking at them for a bit after starting to walk back
3911 			if ( cs->inspectBodyTime + 750 > level.time ) {
3912 				// look down at them
3913 				VectorSubtract( enemyOrg, cs->bs->origin, destorg );
3914 				destorg[2] -= 20;
3915 				VectorNormalize( destorg );
3916 				vectoangles( destorg, cs->bs->ideal_viewangles );
3917 			}
3918 		} else {
3919 			cs->attackSNDtime = level.time;
3920 			cs->bs->enemy = -1;
3921 			return AIFunc_IdleStart( cs );
3922 		}
3923 	}
3924 	//
3925 	return NULL;
3926 }
3927 
3928 /*
3929 ============
3930 AIFunc_InspectBodyStart()
3931 ============
3932 */
AIFunc_InspectBodyStart(cast_state_t * cs)3933 char *AIFunc_InspectBodyStart( cast_state_t *cs ) {
3934 	static int lastInspect;
3935 	//
3936 	// if an inspection was already started not long ago, forget it
3937 	if ( lastInspect <= level.time && lastInspect > level.time - 1000 ) {
3938 		cs->inspectBodyTime = 1;    // go back to start position
3939 	} else {
3940 		lastInspect = level.time;
3941 		cs->inspectBodyTime = -1;
3942 	}
3943 	cs->aifunc = AIFunc_InspectBody;
3944 	return "AIFunc_InspectBody";
3945 }
3946 
3947 /*
3948 ============
3949 AIFunc_GrenadeKick()
3950 ============
3951 */
AIFunc_GrenadeKick(cast_state_t * cs)3952 char *AIFunc_GrenadeKick( cast_state_t *cs ) {
3953 	bot_state_t *bs;
3954 	vec3_t destorg, vec;
3955 	float dist, speed;
3956 	int enemies[MAX_CLIENTS], numEnemies = 0, i;
3957 	qboolean shouldAttack;
3958 	gentity_t *danger;
3959 	gentity_t *ent;
3960 	vec3_t end;
3961 	trace_t tr;
3962 	vec3_t dir;
3963 	int weapon;
3964 
3965 	// !!! NOTE: the only way control should pass out of here, is by calling AIFunc_DefaultStart()
3966 	ent = &g_entities[cs->entityNum];
3967 	danger = &g_entities[cs->dangerEntity];
3968 
3969 	weapon = cs->grenadeKickWeapon;
3970 
3971 	// just to be sure, give us the grenade launcher
3972 	//ent->client->ps.weapons |= (1 << weapon);
3973 
3974 	//
3975 	// NOTE: ignore all danger, since we are trying to solve the situation anyway
3976 	//
3977 	// we need to move towards it
3978 	bs = cs->bs;
3979 	//
3980 	// are we throwing it back?
3981 	if ( cs->grenadeFlushFiring ) {
3982 		// wait until the animation is done
3983 		if ( !ent->client->ps.legsTimer ) {
3984 			return AIFunc_DefaultStart( cs );
3985 		}
3986 		// wait till its finished
3987 		return NULL;
3988 		/*
3989 		cs->bs->weaponnum = weapon;	// select grenade launcher
3990 		//
3991 		if (cs->weaponFireTimes[weapon] < cs->thinkFuncChangeTime) {
3992 			if (!cs->bs->cur_ps.grenadeTimeLeft) {
3993 				// hold fire button down
3994 				AICast_AimAtEnemy( cs );
3995 				trap_EA_Attack(bs->client);
3996 				bs->flags |= BFL_ATTACKED;
3997 			}
3998 			//
3999 			return NULL;
4000 		}
4001 		// the grenade has been released!
4002 		//
4003 		// modify the explode time
4004 		g_entities[ent->grenadeFired].nextthink = ent->grenadeExplodeTime;
4005 		if (g_entities[ent->grenadeFired].nextthink < level.time + 200) {	// cut them some slack
4006 			g_entities[ent->grenadeFired].nextthink = level.time + 200 + rand()%500;
4007 		}
4008 		// make sure no-one tries to throw this back again (hot potatoe syndrome)
4009 		g_entities[ent->grenadeFired].flags |= FL_AI_GRENADE_KICK;
4010 		//
4011 		cs->grenadeFlushFiring = qfalse;
4012 		cs->lockViewAnglesTime = -1;
4013 		cs->startGrenadeFlushTime = level.time + 2000 + rand()%2000;	// dont throw one again for a bit
4014 		level.lastGrenadeKick = level.time;
4015 		return AIFunc_DefaultStart( cs );
4016 		*/
4017 	}
4018 	//
4019 /*
4020 	// have we caught the grenade?
4021 	if (!(ent->flags & FL_AI_GRENADE_KICK)) {
4022 		// select grenades
4023 		cs->bs->weaponnum = weapon;	// select grenade launcher
4024 		AICast_AimAtEnemy( cs );
4025 		// hold fire
4026 		trap_EA_Attack(bs->client);
4027 		bs->flags |= BFL_ATTACKED;
4028 		cs->grenadeFlushFiring = qtrue;
4029 		//
4030 		return NULL;
4031 	}
4032 */
4033 	//
4034 	// is it about to explode in our face?
4035 	if ( level.time > danger->nextthink - (int)( 2.0 * VectorDistance( cs->bs->origin, danger->r.currentOrigin ) ) ) {
4036 		// abort!!
4037 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
4038 			// shit??
4039 		}
4040 		// go to a position that cannot be seen from the dangerPos
4041 		cs->takeCoverTime = danger->nextthink + 1000;
4042 		cs->bs->attackcrouch_time = 0;
4043 		level.lastGrenadeKick = level.time;
4044 		return AIFunc_AvoidDangerStart( cs );
4045 	}
4046 	//
4047 	/*
4048 	// are we close enough to start crouching?
4049 	if (danger->s.pos.trDelta[2] < 40 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && (danger->r.currentOrigin[2] < cs->bs->origin[2]) &&
4050 		VectorLength(danger->s.pos.trDelta) < 40) {
4051 		// crouch to pick it up
4052 		cs->bs->attackcrouch_time = trap_AAS_Time() + 0.3;
4053 	}
4054 	*/
4055 	cs->bs->attackcrouch_time = 0;  // animation is played from standing start
4056 	//
4057 	// are we close enough to pick it up?
4058 	if ( /*cs->grenadeGrabFlag <= 0 || */
4059 		( danger->s.pos.trDelta[2] < 20 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && ( danger->r.currentOrigin[2] < cs->bs->origin[2] ) &&
4060 		  VectorLength( danger->s.pos.trDelta ) < 50 ) ) {
4061 		//
4062 		// we have a choice here, either pick up and return, or just kick it
4063 //		if ((cs->grenadeGrabFlag == -1) || (cs->grenadeGrabFlag == qtrue && level.time > danger->nextthink - 2000)) {	// kick
4064 
4065 		// play the kick anim
4066 		if ( cs->grenadeGrabFlag == qtrue ) {
4067 			AICast_AimAtEnemy( cs );
4068 			// play the kick anim
4069 			BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_KICKGRENADE, qfalse, qtrue );
4070 			cs->grenadeGrabFlag = -1;
4071 			// stop the grenade from moving away
4072 			danger->s.pos.trDelta[0] = 0;
4073 			danger->s.pos.trDelta[1] = 0;
4074 			if ( danger->s.pos.trDelta[2] > 0 ) {
4075 				danger->s.pos.trDelta[2] = 0;
4076 			}
4077 		} else if ( ent->client->ps.legsTimer < 800 ) {
4078 			// send the grenade on its way
4079 			cs->grenadeFlushFiring = qtrue;
4080 			AngleVectors( cs->bs->viewangles, dir, NULL, NULL );
4081 			dir[2] = 0.4;
4082 			VectorNormalize( dir );
4083 			speed = 400;
4084 			if ( cs->bs->enemy >= 0 ) {
4085 				speed = 1.5 * VectorDistance( danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin );
4086 				if ( speed > 650 ) {
4087 					speed = 650;
4088 				}
4089 			}
4090 			VectorScale( dir, speed, danger->s.pos.trDelta );
4091 			danger->r.ownerNum = ent->s.number;     // we are now the owner, let it pass through us
4092 			danger->s.pos.trTime = level.time - 50;         // move a bit on the very first frame
4093 			VectorCopy( danger->r.currentOrigin, danger->s.pos.trBase );
4094 			danger->s.pos.trType = TR_GRAVITY;
4095 			SnapVector( danger->s.pos.trDelta );                // save net bandwidth
4096 		}
4097 /*
4098 		} else { // throw
4099 
4100 			if (cs->grenadeGrabFlag == qtrue) {
4101 				AICast_AimAtEnemy( cs );
4102 				// play the pickup anim
4103 				BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PICKUPGRENADE, qfalse, qtrue );
4104 				cs->grenadeGrabFlag = qfalse;
4105 				// stop the grenade from moving away
4106 				danger->s.pos.trDelta[0] = 0;
4107 				danger->s.pos.trDelta[1] = 0;
4108 				if (danger->s.pos.trDelta[2] > 0) {
4109 					danger->s.pos.trDelta[2] = 0;
4110 				}
4111 			} else if (ent->client->ps.legsTimer < 400) {
4112 				// send the grenade on its way
4113 				cs->grenadeFlushFiring = qtrue;
4114 				AngleVectors( cs->bs->viewangles, dir, NULL, NULL );
4115 				dir[2] = 0.4;
4116 				VectorNormalize( dir );
4117 				speed = 500;
4118 				if (cs->bs->enemy >= 0) {
4119 					speed = 2*VectorDistance(danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin);
4120 					if (speed > 650)
4121 						speed = 650;
4122 				}
4123 				VectorScale( dir, speed, danger->s.pos.trDelta );
4124 				trap_LinkEntity( danger );
4125 			} else if (ent->client->ps.legsTimer < 800) {
4126 				// stop showing the grenade
4127 				trap_UnlinkEntity( danger );
4128 			}
4129 		}
4130 */
4131 		//
4132 		return NULL;
4133 	}
4134 	//
4135 	cs->grenadeGrabFlag = qtrue;    // we must play the anim before we can grab it
4136 	//
4137 	// is the danger gone?
4138 	if ( level.time > cs->dangerEntityValidTime || !danger->inuse ) {
4139 		return AIFunc_DefaultStart( cs );
4140 	}
4141 	//
4142 	// update the predicted position of the grenade
4143 	if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) {
4144 		// make sure it's a valid position, and drop it down to the ground
4145 		cs->takeCoverPos[2] += -ent->r.mins[2] + 8;
4146 		VectorCopy( cs->takeCoverPos, end );
4147 		end[2] -= 80;
4148 		trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID );
4149 		if ( tr.startsolid ) {    // not a valid position, abort
4150 			level.lastGrenadeKick = level.time;
4151 			return AIFunc_DefaultStart( cs );
4152 		}
4153 		VectorCopy( tr.endpos, cs->takeCoverPos );
4154 	} else {    // prediction failed, so use current position
4155 		VectorCopy( danger->r.currentOrigin, cs->takeCoverPos );
4156 		cs->takeCoverPos[2] += 16;  // lift it off the floor
4157 	}
4158 
4159 	VectorCopy( cs->takeCoverPos, destorg );
4160 	VectorSubtract( destorg, cs->bs->origin, vec );
4161 	//vec[2] *= 0.2;
4162 	dist = VectorLength( vec );
4163 	//
4164 	// look for things we should attack
4165 	// if we are out of ammo, we shouldn't bother trying to attack
4166 	shouldAttack = qfalse;
4167 	if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
4168 		numEnemies = AICast_ScanForEnemies( cs, enemies );
4169 		if ( numEnemies == -1 ) { // query mode
4170 			return NULL;
4171 		}
4172 		if ( numEnemies == -2 ) { // inspection may be required
4173 			char *retval;
4174 
4175 			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
4176 				return retval;
4177 			}
4178 		}
4179 		if ( numEnemies > 0 ) {
4180 			// default to the first known enemy, overwrite if we find a clearer shot
4181 			cs->bs->enemy = enemies[0];
4182 			//
4183 			for ( i = 0; i < numEnemies; i++ ) {
4184 				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
4185 					cs->bs->enemy = enemies[i];
4186 					shouldAttack = qtrue;
4187 					break;
4188 				} else if ( cs->bs->enemy < 0 ) {
4189 					cs->lastEnemy = enemies[i];
4190 				}
4191 			}
4192 		}
4193 	}
4194 	//
4195 	// are we close enough to the goal?
4196 	if ( dist > 12 ) { // not close enough
4197 					  //
4198 		moveresult = AICast_MoveToPos( cs, destorg, -1 );
4199 		if ( moveresult ) {
4200 			//if the movement failed
4201 			if ( moveresult->failure ) {
4202 				//reset the avoid reach, otherwise bot is stuck in current area
4203 				trap_BotResetAvoidReach( bs->ms );
4204 				// couldn't get there, so stop trying to get there
4205 				level.lastGrenadeKick = level.time;
4206 				return AIFunc_DefaultStart( cs );
4207 			}
4208 			//
4209 			if ( moveresult->blocked ) {  // abort if we get blocked at any point
4210 				level.lastGrenadeKick = level.time;
4211 				return AIFunc_DefaultStart( cs );
4212 			}
4213 		}
4214 		// we should slow down on approaching it
4215 		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
4216 	}
4217 	//
4218 	// if we are close, put our weapon away and get ready to catch it
4219 	if ( VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 ) {
4220 		// put weapon away, select grenades
4221 		// FIXME: does this fail if we don't have any grenades?
4222 		cs->bs->weaponnum = weapon; // select grenade launcher
4223 		shouldAttack = qfalse;  // don't attack until we've caught it
4224 	}
4225 
4226 	// if we should be attacking something on our way
4227 	if ( shouldAttack ) {
4228 		vec3_t vec, dir;
4229 
4230 		//attack the enemy if possible
4231 		AICast_ProcessAttack( cs );
4232 		//
4233 		// if they are close, and we're heading for them, we should abort this manouver
4234 		VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec );
4235 		if ( VectorNormalize( vec ) < 64 ) {
4236 			if ( VectorNormalize2( bs->velocity, dir ) > 20 ) {   // we are moving
4237 				if ( DotProduct( dir, vec ) > 0 ) {
4238 					// abort
4239 					level.lastGrenadeKick = level.time;
4240 					return AIFunc_DefaultStart( cs );
4241 				}
4242 			}
4243 		}
4244 	} else {
4245 		// face the direction that the grenade is coming
4246 		VectorSubtract( danger->r.currentOrigin, cs->bs->origin, dir );
4247 		dir[2] = 0;
4248 		VectorNormalize( dir );
4249 		vectoangles( dir, cs->bs->ideal_viewangles );
4250 	}
4251 
4252 	return NULL;
4253 }
4254 
4255 /*
4256 =============
4257 AIFunc_GrenadeKickStart()
4258 =============
4259 */
AIFunc_GrenadeKickStart(cast_state_t * cs)4260 char *AIFunc_GrenadeKickStart( cast_state_t *cs ) {
4261 	gentity_t *danger;
4262 
4263 	danger = &g_entities[cs->dangerEntity];
4264 	// we have decided to kick or throw the grenade away
4265 	cs->grenadeKickWeapon = danger->s.weapon;
4266 	cs->grenadeFlushFiring = qfalse;
4267 	cs->aifunc = AIFunc_GrenadeKick;
4268 	return "AIFunc_GrenadeKick";
4269 }
4270 
4271 /*
4272 ============
4273 AIFunc_Battle()
4274 ============
4275 */
AIFunc_Battle(cast_state_t * cs)4276 char *AIFunc_Battle( cast_state_t *cs ) {
4277 	bot_moveresult_t moveresult;
4278 	bot_state_t *bs;
4279 	gentity_t *ent, *enemy;
4280 
4281 	ent = &g_entities[cs->entityNum];
4282 	enemy = &g_entities[cs->bs->enemy];
4283 
4284 	// if we are not in combat mode, then go there now!
4285 	if ( cs->aiState < AISTATE_COMBAT ) {
4286 		AICast_StateChange( cs, AISTATE_COMBAT );   // just go straight to combat mode
4287 	}
4288 	//
4289 	// do we need to avoid a danger?
4290 	if ( cs->dangerEntityValidTime >= level.time ) {
4291 		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
4292 			// shit??
4293 		}
4294 		// go to a position that cannot be seen from the dangerPos
4295 		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
4296 		cs->bs->attackcrouch_time = 0;
4297 		return AIFunc_AvoidDangerStart( cs );
4298 	}
4299 	//
4300 	// are we waiting for a door?
4301 	if ( cs->doorMarkerTime > level.time - 100 ) {
4302 		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
4303 	}
4304 	//
4305 	// do we need to go to our leader?
4306 	if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
4307 		return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
4308 	}
4309 	bs = cs->bs;
4310 	//if no enemy
4311 	if ( bs->enemy < 0 ) {
4312 		// go back to whatever our default action is
4313 		return AIFunc_DefaultStart( cs );
4314 	}
4315 	//
4316 	if ( enemy->health <= 0 ) {
4317 		// go back to whatever our default action is
4318 		if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI ) {
4319 			return AIFunc_InspectBodyStart( cs );
4320 		} else {
4321 			return AIFunc_DefaultStart( cs );
4322 		}
4323 	}
4324 	//
4325 	// if we are crouching, don't stay down for too long after we finish fighting
4326 	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
4327 		bs->attackcrouch_time = trap_AAS_Time() + 1;
4328 	} else {
4329 		bs->attackcrouch_time = 0;  // only set it if we need it
4330 	}
4331 	//
4332 	// if the enemy is no longer visible
4333 	if (    ( cs->bs->cur_ps.weaponTime < 100 )   // if reloading, don't chase until ready
4334 			&&  ( cs->castScriptStatus.scriptNoMoveTime < level.time )
4335 			&&  ( !AICast_EntityVisible( cs, bs->enemy, qtrue ) || !AICast_CheckAttack( cs, bs->enemy, qfalse ) ) ) {
4336 
4337 		// if we are heading for a combatGoal, give us some time to get there
4338 		if ( cs->combatGoalTime > level.time ) {
4339 			if ( cs->combatGoalTime > level.time + 3000 ) {
4340 				cs->combatGoalTime = level.time + 2000 + rand() % 1000;
4341 				cs->combatSpotDelayTime = level.time + 4000 + rand() % 3000;
4342 			}
4343 		} else
4344 		if ( cs->leaderNum >= 0 ) {
4345 			// chase them, nothing else to do
4346 			return AIFunc_BattleChaseStart( cs );
4347 		} else
4348 		// if we weren't moving, it is likely they have dodged back behind something, ready to duck out and take another
4349 		// shot. so, we could fool them by hiding from the position we last saw them from, in the hope that when they
4350 		// return to fire at us, we won't be in their sight.
4351 		if (    cs->attributes[TACTICAL] > 0.3
4352 				&&  cs->attributes[AGGRESSION] < 1.0
4353 				&&  cs->attributes[AGGRESSION] < ( random() + 0.5 * cs->attributes[TACTICAL] )
4354 				&&  ( cs->takeCoverTime < level.time )
4355 				&&  AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
4356 			// start taking cover
4357 			cs->takeCoverTime = level.time + 2000 + rand() % 4000;    // only move a little bit
4358 			//cs->bs->attackcrouch_time = 0;		// get out of here real quick
4359 			return AIFunc_BattleTakeCoverStart( cs );
4360 		} else
4361 		// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
4362 		if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
4363 			 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
4364 			 (   (   ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
4365 					 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
4366 					 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) ) ||
4367 				 (   ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) &&
4368 					( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) &&
4369 					( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) ) ) &&
4370 			 !( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
4371 			 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) &&
4372 			 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 1200 ) &&
4373 			 ( AICast_WantsToChase( cs ) ) ) {
4374 			// try and flush them out with a grenade
4375 			//G_Printf("get outta there..\n");
4376 			return AIFunc_GrenadeFlushStart( cs );
4377 		} else
4378 		// not visible, should we chase them?
4379 		if ( AICast_WantsToChase( cs ) ) {
4380 			// chase them
4381 			return AIFunc_BattleChaseStart( cs );
4382 		} else
4383 		// Take Cover?
4384 		if (    AICast_WantsToTakeCover( cs, qfalse )
4385 				&&  ( cs->takeCoverTime < level.time )
4386 				&&  AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
4387 			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
4388 			cs->takeCoverTime = level.time + 4000 + rand() % 2000;
4389 			//cs->bs->attackcrouch_time = 0;
4390 			return AIFunc_BattleTakeCoverStart( cs );
4391 		} else
4392 		{
4393 			// chase them, nothing else to do
4394 			return AIFunc_BattleChaseStart( cs );
4395 		}
4396 	}
4397 	// if we are obstructing someone else, move out the way
4398 	if ( cs->obstructingTime > level.time ) {
4399 		// setup a combatgoal in the obstructionYaw direction
4400 		//cs->combatGoalTime = level.time + 10;
4401 		//VectorCopy( cs->obstructingPos, cs->combatGoalOrigin );
4402 		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
4403 		// if not crouching, walk instead of running
4404 		cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED];
4405 	}
4406 	// if the enemy is really close, avoid them
4407 	else if (   ( cs->obstructingTime < ( level.time - 500 + rand() % 300 ) ) &&
4408 				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 100 ) ) {
4409 		if ( AICast_GetAvoid( cs, NULL, cs->obstructingPos, qtrue, cs->bs->enemy ) ) {
4410 			cs->obstructingTime = level.time + 500;
4411 		} else {
4412 			cs->obstructingTime = level.time - 1;   // wait a bit before trying again
4413 		}
4414 	}
4415 	//
4416 	// setup for the fight
4417 	//
4418 	/*
4419 	moveresult = AICast_CombatMove(cs, tfl);
4420 	//if the movement failed
4421 	if (moveresult.failure) {
4422 		//reset the avoid reach, otherwise bot is stuck in current area
4423 		trap_BotResetAvoidReach(bs->ms);
4424 		// reset the combatgoal
4425 		cs->combatGoalTime = 0;
4426 	} else if (cs->combatGoalTime > level.time && VectorLength(cs->bs->cur_ps.velocity)) {	// crouch if moving?
4427 		if (cs->attributes[ATTACK_CROUCH] > 0.1) {
4428 			AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.5 );
4429 		}
4430 	}
4431 	*/
4432 	//
4433 	AICast_Blocked( cs, &moveresult, qfalse, NULL );
4434 	//
4435 	// Retreat?
4436 	if ( AICast_WantToRetreat( cs ) ) {
4437 		if  ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
4438 			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
4439 			cs->takeCoverTime = level.time + 2000 + rand() % 3000;
4440 			return AIFunc_BattleTakeCoverStart( cs );
4441 		}
4442 	}
4443 	//
4444 	// Lob a Grenade?
4445 	// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
4446 	if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
4447 		 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
4448 		 ( cs->startGrenadeFlushTime < level.time - 3000 ) &&
4449 		 ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
4450 		 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
4451 		 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
4452 		 ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
4453 		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) &&
4454 		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 2000 ) ) {
4455 		// try and flush them out with a grenade
4456 		//G_Printf("pineapple?\n");
4457 		return AIFunc_GrenadeFlushStart( cs );
4458 	}
4459 	//
4460 	// Dodge enemy aim?
4461 	if (    ( cs->attributes[AGGRESSION] < 1.0 ) &&
4462 			( ent->client->ps.weapon ) &&
4463 			( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD ) &&
4464 			( !cs->lastRollMove || cs->lastRollMove < level.time - 4000 ) &&
4465 			( cs->attributes[TACTICAL] > 0.5 ) && ( cs->aiFlags & AIFL_ROLL_ANIM ) &&
4466 			( VectorLength( cs->bs->cur_ps.velocity ) < 1 ) ) {
4467 		vec3_t aim, enemyVec, right;
4468 		// are they aiming at us?
4469 		AngleVectors( enemy->client->ps.viewangles, aim, right, NULL );
4470 		VectorSubtract( cs->bs->origin, enemy->r.currentOrigin, enemyVec );
4471 		VectorNormalize( enemyVec );
4472 		// if they are looking at us, we should avoid them
4473 		if ( DotProduct( aim, enemyVec ) > 0.97 ) {
4474 			aicast_predictmove_t move;
4475 			vec3_t dir;
4476 			bot_input_t bi, bi_back;
4477 			usercmd_t ucmd;
4478 			float simTime = 0.8;
4479 
4480 			cs->lastRollMove = level.time;
4481 
4482 			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi_back );
4483 			trap_EA_ResetInput( cs->entityNum, NULL );
4484 			if ( level.time % 200 < 100 ) {
4485 				VectorNegate( right, dir );
4486 			} else { VectorCopy( right, dir );}
4487 			trap_EA_Move( cs->entityNum, dir, 400 );
4488 			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
4489 			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
4490 			AICast_PredictMovement( cs, 4, simTime / 4, &move, &ucmd, bs->enemy );
4491 
4492 			trap_EA_ResetInput( cs->entityNum, &bi_back );
4493 
4494 			if ( move.groundEntityNum == ENTITYNUM_WORLD &&
4495 				 VectorDistance( move.endpos, cs->bs->origin ) > simTime * cs->attributes[RUNNING_SPEED] * 0.8 ) {
4496 				// good enough
4497 				if ( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->cur_ps.viewheight == cs->bs->cur_ps.crouchViewHeight, qfalse ) ) {
4498 					return AIFunc_BattleRollStart( cs, dir );
4499 				}
4500 			}
4501 		}
4502 	}
4503 	//
4504 	// reload?
4505 	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( ammoTable[cs->bs->cur_ps.weapon].uses ) ) ) {
4506 		if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
4507 			trap_EA_Reload( cs->entityNum );
4508 		} else {    // no ammo, switch?
4509 			AICast_ChooseWeapon( cs, qfalse );
4510 			if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
4511 				// no ammo, get out of here
4512 				return AIFunc_DefaultStart( cs );
4513 			}
4514 		}
4515 	} else {
4516 		//attack the enemy if possible
4517 		AICast_ProcessAttack( cs );
4518 	}
4519 	//
4520 	return NULL;
4521 }
4522 
4523 /*
4524 ============
4525 AIFunc_BattleStart()
4526 ============
4527 */
AIFunc_BattleStart(cast_state_t * cs)4528 char *AIFunc_BattleStart( cast_state_t *cs ) {
4529 	char *rval;
4530 	int lastweap;
4531 	// make sure we don't avoid any areas when we start again
4532 	trap_BotInitAvoidReach( cs->bs->ms );
4533 	// wait some time before taking cover again
4534 	cs->takeCoverTime = level.time + 300 + rand() % ( 2000 + (int)( 2000.0 * cs->attributes[AGGRESSION] ) );
4535 	// wait some time before going to a combat spot
4536 	cs->combatSpotDelayTime = level.time + 1500 + rand() % 2500;
4537 	//
4538 	// start a crouch attack?
4539 	if ( ( random() * 3.0 + 1.0 < cs->attributes[ATTACK_CROUCH] )
4540 		 && AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.0 ) ) {
4541 		cs->aiFlags |= AIFL_ATTACK_CROUCH;
4542 	} else {
4543 		cs->bs->attackcrouch_time = 0;
4544 		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
4545 	}
4546 	//
4547 	cs->lastEnemy = cs->bs->enemy;
4548 	cs->startAttackCount++;
4549 	cs->crouchHideFlag = qfalse;
4550 	//
4551 	// get out of talking state
4552 	cs->aiFlags &= ~AIFL_TALKING;
4553 	//
4554 	//update the attack inventory values
4555 	AICast_UpdateBattleInventory( cs, cs->bs->enemy );
4556 	//
4557 	// if we have a special attack, call the correct AI routine
4558 recheck:
4559 	rval = NULL;
4560 	// ignore special attacks until we are facing our enemy
4561 	if ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) < 10 ) {
4562 		// select a weapon
4563 		AICast_ChooseWeapon( cs, qtrue );
4564 		//
4565 		if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK1 ) && cs->aifuncAttack1 ) {
4566 			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
4567 				rval = cs->aifuncAttack1( cs );
4568 			} else {
4569 				rval = AIFunc_BattleChaseStart( cs );
4570 			}
4571 		} else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK2 ) && cs->aifuncAttack2 ) {
4572 			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
4573 				rval = cs->aifuncAttack2( cs );
4574 			} else {
4575 				rval = AIFunc_BattleChaseStart( cs );
4576 			}
4577 		} else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK3 ) && cs->aifuncAttack3 ) {
4578 			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
4579 				rval = cs->aifuncAttack3( cs );
4580 			} else {
4581 				rval = AIFunc_BattleChaseStart( cs );
4582 			}
4583 		}
4584 		//
4585 		if ( !rval && cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) {
4586 			// don't use this weapon again for a while
4587 			cs->weaponFireTimes[cs->bs->weaponnum] = level.time;
4588 			// select a different weapon
4589 			lastweap = cs->bs->weaponnum;
4590 			AICast_ChooseWeapon( cs, qfalse );  // qfalse so we don't choose a special weapon
4591 			if ( cs->bs->weaponnum == lastweap ) {
4592 				return NULL;
4593 			}
4594 			// try again
4595 			goto recheck;
4596 		}
4597 	} else {    // normal weapons
4598 		// select a weapon
4599 		AICast_ChooseWeapon( cs, qfalse );
4600 		rval = NULL;
4601 	}
4602 	//
4603 	if ( !rval ) {    // use the generic battle routine for all "normal" weapons
4604 		if ( cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) {
4605 			// monster attacks are not allowed to go into the normal battle mode
4606 			return NULL;
4607 		} else {
4608 			cs->aifunc = AIFunc_Battle;
4609 			return "AIFunc_Battle";
4610 		}
4611 	}
4612 	//
4613 	// we decided to start a special monster attack
4614 	return rval;
4615 }
4616 
4617 /*
4618 ============
4619 AIFunc_DefaultStart()
4620 ============
4621 */
AIFunc_DefaultStart(cast_state_t * cs)4622 char *AIFunc_DefaultStart( cast_state_t *cs ) {
4623 	qboolean first = qfalse;
4624 	char    *rval = NULL;
4625 	//
4626 	if ( cs->aiFlags & AIFL_JUST_SPAWNED ) {
4627 		first = qtrue;
4628 		cs->aiFlags &= ~AIFL_JUST_SPAWNED;
4629 	}
4630 	//
4631 	switch ( cs->aiCharacter ) {
4632 	case AICHAR_FEMZOMBIE:
4633 		if ( first ) {
4634 			return AIFunc_FZombie_IdleStart( cs );
4635 		}
4636 		break;
4637 	case AICHAR_ZOMBIE:
4638 		// portal zombie, requires spawning effect
4639 		if ( first && ( g_entities[cs->entityNum].spawnflags & 4 ) ) {
4640 			return AIFunc_FlameZombie_PortalStart( cs );
4641 		}
4642 		break;
4643 	case AICHAR_HELGA:
4644 		if ( first ) {
4645 			return AIFunc_Helga_IdleStart( cs );
4646 		}
4647 		break;
4648 	}
4649 	//
4650 	// if they have an enemy, then pursue
4651 	if ( cs->bs->enemy >= 0 ) {
4652 		rval = AIFunc_BattleStart( cs );
4653 	}
4654 	//
4655 	if ( !rval ) {
4656 		return AIFunc_IdleStart( cs );
4657 	}
4658 	//
4659 	return rval;
4660 }
4661