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