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