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_think.c
32 // Function: Wolfenstein AI Character Thinking
33 // Programmer: Ridah
34 // Tab Size: 4 (real tabs)
35 //===========================================================================
36
37 #include "g_local.h"
38 #include "../qcommon/q_shared.h"
39 #include "../botlib/botlib.h" //bot lib interface
40 #include "../botlib/be_aas.h"
41 #include "../botlib/be_ea.h"
42 #include "../botlib/be_ai_gen.h"
43 #include "../botlib/be_ai_goal.h"
44 #include "../botlib/be_ai_move.h"
45 #include "../botlib/botai.h" //bot ai interface
46
47 #include "ai_cast.h"
48
49 /*
50 The lowest level of Cast AI thinking.
51 */
52
53 /*
54 ============
55 AICast_ProcessAIFunctions
56 ============
57 */
AICast_ProcessAIFunctions(cast_state_t * cs,float thinktime)58 void AICast_ProcessAIFunctions( cast_state_t *cs, float thinktime ) {
59 int i;
60 char *funcname;
61
62 //check for air
63 BotCheckAir( cs->bs );
64 //if the cast has no ai function
65 if ( !cs->aifunc ) {
66 AIFunc_DefaultStart( cs );
67 }
68 //
69 // call AI funcs for this cast
70 //
71 AICast_DBG_InitAIFuncs();
72 //
73 // only allow looping in debug mode (since it's much slower)
74 for ( i = 0; i < ( aicast_debug.integer ? MAX_AIFUNCS : 1 ); i++ )
75 {
76 if ( !( funcname = cs->aifunc( cs ) ) ) {
77 break;
78 } else {
79 trap_BotResetAvoidReach( cs->bs->ms ); // reset avoidreach
80 cs->thinkFuncChangeTime = level.time;
81 AICast_DBG_AddAIFunc( cs, funcname );
82 }
83 }
84 //
85 //if the cast executed too many AI functions
86 //
87 if ( aicast_debug.integer && i >= MAX_AIFUNCS ) {
88 AICast_DBG_ListAIFuncs( cs, 10 ); // print the last 10 funcs called
89 }
90 }
91
92
93 /*
94 ==============
95 AICast_ChangeViewAngles
96 ==============
97 */
AICast_ChangeViewAngles(cast_state_t * cs,float thinktime)98 void AICast_ChangeViewAngles( cast_state_t *cs, float thinktime ) {
99 float diff, factor, maxchange, anglespeed;
100 int i;
101 bot_state_t *bs;
102
103 bs = cs->bs;
104 //
105 // restore locked viewangles if required
106 if ( cs->aiFlags & AIFL_VIEWLOCKED ) {
107 VectorCopy( cs->viewlock_viewangles, cs->ideal_viewangles );
108 } else {
109 // check for playanim angles
110 if ( cs->castScriptStatus.playAnimViewlockTime >= level.time ) {
111 // check to make sure we are still playing a legs animation
112 if ( !g_entities[cs->entityNum].client->ps.legsTimer ) {
113 cs->castScriptStatus.playAnimViewlockTime = 0;
114 } else {
115 VectorCopy( cs->castScriptStatus.playanim_viewangles, cs->ideal_viewangles );
116 }
117 }
118 }
119 //
120 if ( cs->ideal_viewangles[PITCH] > 180 ) {
121 cs->ideal_viewangles[PITCH] -= 360;
122 }
123 //
124 maxchange = cs->attributes[YAW_SPEED]; //300;
125 if ( cs->aiState >= AISTATE_COMBAT ) {
126 factor = 2.0;
127 maxchange *= 2.0;
128 } else {
129 factor = 0.7;
130 }
131 //
132 if ( cs->lockViewAnglesTime < level.time ) {
133 maxchange *= thinktime;
134 for ( i = 0; i < 3; i++ ) {
135 diff = fabs( AngleDifference( cs->viewangles[i], cs->ideal_viewangles[i] ) );
136 anglespeed = diff * factor;
137 if ( cs->aiState >= AISTATE_COMBAT ) {
138 if ( anglespeed < cs->attributes[YAW_SPEED] ) {
139 anglespeed = cs->attributes[YAW_SPEED];
140 }
141 }
142 if ( thinktime != 9999.0f ) {
143 if ( anglespeed > maxchange ) {
144 anglespeed = maxchange;
145 }
146 }
147 cs->viewangles[i] = BotChangeViewAngle( cs->viewangles[i],
148 cs->ideal_viewangles[i], anglespeed );
149 //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", cs->ideal_viewangles[0], cs->ideal_viewangles[1], cs->ideal_viewangles[2]);`
150 //cs->viewangles[i] = cs->ideal_viewangles[i];
151 }
152 }
153 if ( cs->viewangles[PITCH] > 180 ) {
154 cs->viewangles[PITCH] -= 360;
155 }
156 //elementary action: view
157 trap_EA_View( bs->client, cs->viewangles );
158 }
159
160
161 /*
162 ==============
163 AICast_InputToUserCommand
164 ==============
165 */
166 static int serverTime;
AICast_InputToUserCommand(cast_state_t * cs,bot_input_t * bi,usercmd_t * ucmd,int delta_angles[3])167 void AICast_InputToUserCommand( cast_state_t *cs, bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3] ) {
168 vec3_t angles, forward, right, up;
169 short temp;
170 int j;
171 signed char movechar;
172
173 //clear the whole structure
174 memset( ucmd, 0, sizeof( usercmd_t ) );
175 //
176 //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
177 //the duration for the user command in milli seconds
178 ucmd->serverTime = serverTime;
179 //crouch/movedown
180 if ( aiDefaults[cs->aiCharacter].attributes[ATTACK_CROUCH] ) { // only crouch if this character is physically able to
181 if ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE && bi->actionflags & ACTION_CROUCH ) {
182 ucmd->upmove -= 127;
183 }
184 }
185 //
186 // actions not effected by script pausing
187 //
188 // set zoom button
189 if ( cs->aiFlags & AIFL_ZOOMING ) {
190 ucmd->wbuttons |= WBUTTON_ZOOM;
191 }
192 //set the buttons
193 if ( bi->actionflags & ACTION_ATTACK ) {
194 vec3_t ofs;
195 // don't fire if we are not facing the right direction yet
196 if ( ( cs->triggerReleaseTime < level.time ) &&
197 ( ( cs->lockViewAnglesTime >= level.time ) ||
198 ( fabs( AngleDifference( cs->ideal_viewangles[YAW], cs->viewangles[YAW] ) ) < 20 ) ) &&
199 // check for radid luger firing by skilled users (release fire between shots)
200 ( ( ( level.time + cs->entityNum * 500 ) / 2000 ) % 2 || !( rand() % ( 1 + g_gameskill.integer ) ) || ( cs->attributes[ATTACK_SKILL] < 0.5 ) || ( cs->weaponNum != WP_LUGER ) || ( cs->bs->cur_ps.weaponTime == 0 ) || ( cs->bs->cur_ps.releasedFire ) ) ) {
201 ucmd->buttons |= BUTTON_ATTACK;
202 // do some swaying around for some weapons
203 AICast_WeaponSway( cs, ofs );
204 VectorAdd( bi->viewangles, ofs, bi->viewangles );
205 }
206 }
207 //
208 //set the view angles
209 //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
210 ucmd->angles[PITCH] = ANGLE2SHORT( bi->viewangles[PITCH] );
211 ucmd->angles[YAW] = ANGLE2SHORT( bi->viewangles[YAW] );
212 ucmd->angles[ROLL] = ANGLE2SHORT( bi->viewangles[ROLL] );
213 //subtract the delta angles
214 for ( j = 0; j < 3; j++ ) {
215 temp = ucmd->angles[j] - delta_angles[j];
216 ucmd->angles[j] = temp;
217 }
218
219
220 //----(SA) modified slightly for DM/DK
221 ucmd->weapon = bi->weapon;
222 //
223 // relaxed mode show no weapons
224 if ( cs->aiState <= AISTATE_QUERY ) {
225 if ( WEAPS_ONE_HANDED & ( 1 << ucmd->weapon ) ) { // one-handed wepons don't draw, others do
226 ucmd->weapon = WP_NONE;
227 }
228 }
229 //----(SA) end
230
231 //
232 if ( bi->actionflags & ACTION_GESTURE ) {
233 ucmd->buttons |= BUTTON_GESTURE;
234 }
235 if ( bi->actionflags & ACTION_RELOAD ) {
236 ucmd->wbuttons |= WBUTTON_RELOAD;
237 }
238 //
239 // if we are locked down, don't do anything
240 //
241 if ( cs->pauseTime > level.time ) {
242 return;
243 }
244 //
245 // if scripted pause, no movement
246 //
247 if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) {
248 return;
249 }
250 //
251 // if viewlock, wait until we are facing ideal angles before we move
252 if ( cs->aiFlags & AIFL_VIEWLOCKED ) {
253 if ( fabs( AngleDifference( cs->ideal_viewangles[YAW], cs->viewangles[YAW] ) ) > 10 ) {
254 return;
255 }
256 }
257 //
258 if ( bi->actionflags & ACTION_DELAYEDJUMP ) {
259 bi->actionflags |= ACTION_JUMP;
260 bi->actionflags &= ~ACTION_DELAYEDJUMP;
261 }
262 //
263 // only move if we are in combat or we are facing where our ideal angles
264 if ( bi->speed ) {
265 if ( ( !( cs->aiFlags & AIFL_WALKFORWARD ) && cs->enemyNum >= 0 ) || ( ( ucmd->forwardmove >= 0 ) && fabs( AngleNormalize180( AngleDifference( cs->ideal_viewangles[YAW], cs->viewangles[YAW] ) ) ) < 60 ) ) {
266 //NOTE: movement is relative to the REAL view angles
267 //get the horizontal forward and right vector
268 //get the pitch in the range [-180, 180]
269 if ( bi->dir[2] ) {
270 angles[PITCH] = bi->viewangles[PITCH];
271 } else { angles[PITCH] = 0;}
272 angles[YAW] = bi->viewangles[YAW];
273 angles[ROLL] = 0;
274 AngleVectors( angles, forward, right, up );
275 //bot input speed is in the range [0, 400]
276 bi->speed = bi->speed * 127 / 400;
277 //set the view independent movement
278 ucmd->forwardmove = DotProduct( forward, bi->dir ) * bi->speed;
279 ucmd->rightmove = DotProduct( right, bi->dir ) * bi->speed;
280
281 // RF, changed this to fix stim soldier flying attack
282 if ( !ucmd->upmove ) { // only change it if we don't already have an upmove set
283 ucmd->upmove = DotProduct( up, bi->dir ) * bi->speed;
284 }
285 //if (!ucmd->upmove) // only change it if we don't already have an upmove set
286 // ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
287 }
288 }
289 //
290 //normal keyboard movement
291 if ( cs->actionFlags & CASTACTION_WALK ) {
292 movechar = 70;
293 } else {
294 movechar = 127;
295 }
296 if ( bi->actionflags & ACTION_MOVEFORWARD ) {
297 ucmd->forwardmove = movechar;
298 }
299 if ( !( cs->aiFlags & AIFL_WALKFORWARD ) || ( !cs->bs->cur_ps.groundEntityNum || cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE ) ) { // only do other movements if we are allowed to
300 if ( bi->actionflags & ACTION_MOVEBACK ) {
301 ucmd->forwardmove = -movechar;
302 }
303 if ( bi->actionflags & ACTION_MOVELEFT ) {
304 ucmd->rightmove = -movechar;
305 }
306 if ( bi->actionflags & ACTION_MOVERIGHT ) {
307 ucmd->rightmove = movechar;
308 }
309 }
310 // prevent WALKFORWARD AI from moving backwards
311 if ( cs->aiFlags & AIFL_WALKFORWARD ) {
312 if ( ucmd->forwardmove < 0 ) {
313 ucmd->forwardmove = 0;
314 }
315 }
316 //jump/moveup
317 if ( bi->actionflags & ACTION_JUMP ) {
318 ucmd->upmove = 127; // JUMP always takes preference over ducking
319 }
320 if ( bi->actionflags & ACTION_MOVEDOWN ) {
321 ucmd->upmove = -127; // JUMP always takes preference over ducking
322 }
323 if ( bi->actionflags & ACTION_MOVEUP ) {
324 ucmd->upmove = 127; // JUMP always takes preference over ducking
325 }
326 //
327 //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
328 }
329
330
331 /*
332 ==============
333 AICast_UpdateInput
334 ==============
335 */
AICast_UpdateInput(cast_state_t * cs,int time)336 void AICast_UpdateInput( cast_state_t *cs, int time ) {
337 bot_input_t bi;
338 bot_state_t *bs;
339 int j;
340 float speed;
341
342 bs = cs->bs;
343
344 //add the delta angles to the bot's current view angles
345 for ( j = 0; j < 3; j++ ) {
346 cs->viewangles[j] = AngleMod( cs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
347 }
348 //
349 AICast_ChangeViewAngles( cs, (float) time / 1000 );
350 //
351 if ( cs->pauseTime > level.time ) {
352 trap_EA_View( bs->client, cs->viewangles );
353 trap_EA_GetInput( bs->client, (float) time / 1000, &bi );
354 AICast_InputToUserCommand( cs, &bi, &cs->lastucmd, bs->cur_ps.delta_angles );
355 g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED;
356 //
357 //subtract the delta angles
358 for ( j = 0; j < 3; j++ ) {
359 cs->viewangles[j] = AngleMod( cs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
360 }
361 //
362 return;
363 }
364 //
365 trap_EA_GetInput( bs->client, (float) time / 1000, &bi );
366 //
367 // restrict the speed according to the character and their current speedScale
368 // HACK, don't slow down while crouching
369 if ( bi.actionflags & ACTION_CROUCH && cs->speedScale < 1.0 ) {
370 cs->speedScale = 1.0;
371 }
372 //
373 // check some Cast AI specific movement flags
374 if ( cs->actionFlags & CASTACTION_WALK ) {
375 if ( cs->speedScale > ( cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED] ) ) {
376 cs->speedScale = ( cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED] );
377 }
378 }
379 // don't ever let the speed get too low
380 if ( cs->speedScale < 0.25 ) {
381 cs->speedScale = 0.25;
382 }
383 if ( cs->speedScale > 1.2 ) {
384 cs->speedScale = 1.2;
385 }
386 //
387 speed = cs->speedScale * cs->attributes[RUNNING_SPEED];
388 //
389 //if (speed <= (cs->attributes[WALKING_SPEED] + (cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1))) // do a fast shuffle if slightly over walking speed
390 if ( speed <= cs->attributes[WALKING_SPEED] ) {
391 cs->actionFlags |= CASTACTION_WALK;
392 }
393 //
394 // we use 300 here, because the default player speed is 300, so Cast AI's can't move faster than that
395 if ( ( bi.speed / 400.0 ) > ( speed / 300.0 ) ) {
396 bi.speed = 400.0 * ( speed / 300.0 );
397 if ( bi.speed > 400.0 ) {
398 bi.speed = 400.0; // just in case, we should never exceed this
399 }
400 }
401 //
402 // do a fast shuffle if slightly over walking speed
403 if ( bi.speed <= ( 400.0 / 300.0 ) * ( cs->attributes[WALKING_SPEED] + ( cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1 ) ) ) {
404 cs->actionFlags |= CASTACTION_WALK;
405 }
406 //
407 AICast_InputToUserCommand( cs, &bi, &cs->lastucmd, bs->cur_ps.delta_angles );
408 //
409 // check some Cast AI specific movement flags
410 if ( cs->actionFlags & CASTACTION_WALK ) {
411 cs->lastucmd.buttons |= BUTTON_WALKING; // play the walking animation
412 }
413 //
414 //subtract the delta angles
415 for ( j = 0; j < 3; j++ ) {
416 cs->viewangles[j] = AngleMod( cs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
417 }
418 //
419 // make sure the respawn flag is disabled (causes problems after multiple "map xxx" commands)
420 g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED;
421 // set the aiState
422 g_entities[cs->bs->entitynum].client->ps.aiState = cs->aiState;
423 }
424
425 /*
426 ============
427 AICast_Think
428
429 entry point for all cast AI
430 ============
431 */
AICast_Think(int client,float thinktime)432 void AICast_Think( int client, float thinktime ) {
433 gentity_t *ent;
434 cast_state_t *cs;
435 int i;
436 int animIndex;
437 animation_t *anim;
438
439 // if (saveGamePending || (strlen( g_missionStats.string ) > 2 )) {
440 // return;
441 // }
442
443 //
444 // get the cast ready for processing
445 //
446 cs = AICast_GetCastState( client );
447 ent = &g_entities[client];
448 //
449 // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
450 trap_AAS_SetCurrentWorld( cs->aasWorldIndex );
451 //
452 // make sure we have a valid navigation system
453 //
454 if ( !trap_AAS_Initialized() ) {
455 return;
456 }
457 //
458 trap_EA_ResetInput( client, NULL );
459 cs->aiFlags &= ~AIFL_VIEWLOCKED;
460 cs->aiFlags &= ~AIFL_SPECIAL_FUNC;
461 //cs->weaponNum = ent->client->ps.weapon;
462 //
463 // turn off flags that are set each frame if needed
464 ent->client->ps.eFlags &= ~( EF_NOSWINGANGLES | EF_MONSTER_EFFECT | EF_MONSTER_EFFECT2 | EF_MONSTER_EFFECT3 );
465 // conditional flags
466 if ( ent->aiCharacter == AICHAR_ZOMBIE ) {
467 if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) {
468 cs->aiFlags |= AIFL_NO_FLAME_DAMAGE;
469 SET_FLAMING_ZOMBIE( ent->s, 1 );
470 } else {
471 SET_FLAMING_ZOMBIE( ent->s, 0 );
472 }
473 }
474 //
475 // update bounding box
476 AIChar_SetBBox( ent, cs, qtrue );
477 //
478 // set/disable these each frame as required
479 //ent->r.svFlags |= SVF_BROADCAST;
480 ent->client->ps.eFlags &= ~EF_FORCE_END_FRAME;
481 //origin of the cast
482 VectorCopy( ent->client->ps.origin, cs->bs->origin );
483 //eye coordinates of the cast
484 VectorCopy( ent->client->ps.origin, cs->bs->eye );
485 cs->bs->eye[2] += ent->client->ps.viewheight;
486 //get the area the cast is in
487 cs->bs->areanum = BotPointAreaNum( cs->bs->origin );
488 if ( cs->bs->areanum ) {
489 cs->lastValidAreaNum[cs->aasWorldIndex] = cs->bs->areanum;
490 cs->lastValidAreaTime[cs->aasWorldIndex] = level.time;
491 }
492 // if we're dead, do special stuff only
493 if ( ent->health <= 0 || cs->revivingTime || cs->rebirthTime ) {
494 //
495 if ( cs->revivingTime && cs->revivingTime < level.time ) {
496 // start us thinking again
497 ent->client->ps.pm_type = PM_NORMAL;
498 cs->revivingTime = 0;
499 }
500 //
501 if ( cs->rebirthTime && cs->rebirthTime < level.time ) {
502 vec3_t mins, maxs;
503 int touch[10], numTouch;
504 float oldmaxZ;
505
506 oldmaxZ = ent->r.maxs[2];
507
508 // make sure the area is clear
509 AIChar_SetBBox( ent, cs, qfalse );
510
511 VectorAdd( ent->r.currentOrigin, ent->r.mins, mins );
512 VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs );
513 trap_UnlinkEntity( ent );
514
515 numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 );
516
517 if ( numTouch ) {
518 for ( i = 0; i < numTouch; i++ ) {
519 //if (!g_entities[touch[i]].client || g_entities[touch[i]].r.contents == CONTENTS_BODY)
520 if ( g_entities[touch[i]].r.contents & MASK_PLAYERSOLID ) {
521 break;
522 }
523 }
524 if ( i == numTouch ) {
525 numTouch = 0;
526 }
527 }
528
529 if ( numTouch == 0 ) { // ok to spawn
530
531 // give them health when they start reviving, so we won't gib after
532 // just a couple shots while reviving
533 ent->health =
534 ent->client->ps.stats[STAT_HEALTH] =
535 ent->client->ps.stats[STAT_MAX_HEALTH] =
536 ( ( cs->attributes[STARTING_HEALTH] - 50 ) > 30 ? ( cs->attributes[STARTING_HEALTH] - 50 ) : 30 );
537
538 ent->r.contents = CONTENTS_BODY;
539 ent->clipmask = MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP;
540 ent->takedamage = qtrue;
541 ent->waterlevel = 0;
542 ent->watertype = 0;
543 ent->flags = 0;
544 ent->die = AICast_Die;
545 ent->client->ps.eFlags &= ~EF_DEAD;
546 ent->s.eFlags &= ~EF_DEAD;
547
548 cs->rebirthTime = 0;
549 cs->deathTime = 0;
550
551 ent->client->ps.eFlags &= ~EF_DEATH_FRAME;
552 ent->client->ps.eFlags &= ~EF_FORCE_END_FRAME;
553 ent->client->ps.eFlags |= EF_NO_TURN_ANIM;
554
555 // play the revive animation
556 cs->revivingTime = level.time + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_REVIVE, qfalse, qtrue );;
557 } else {
558 // can't spawn yet, so set bbox back, and wait
559 ent->r.maxs[2] = oldmaxZ;
560 ent->client->ps.maxs[2] = ent->r.maxs[2];
561 }
562 trap_LinkEntity( ent );
563 }
564 // ZOMBIE should set effect flag if really dead
565 if ( cs->aiCharacter == AICHAR_ZOMBIE && !ent->r.contents ) {
566 ent->client->ps.eFlags |= EF_MONSTER_EFFECT2;
567 }
568 //
569 if ( ent->health > GIB_HEALTH && cs->deathTime && cs->deathTime < ( level.time - 1000 ) ) {
570 //ent->r.svFlags &= ~SVF_BROADCAST;
571 if ( !ent->client->ps.torsoTimer && !ent->client->ps.legsTimer ) {
572 ent->client->ps.eFlags |= EF_FORCE_END_FRAME;
573 }
574 // sink?
575 if ( cs->deadSinkStartTime ) {
576 ent->s.effect3Time = cs->deadSinkStartTime;
577 // if they are gone
578 if ( cs->deadSinkStartTime + DEAD_SINK_DURATION < level.time ) {
579 trap_DropClient( cs->entityNum, "" );
580 return;
581 }
582 }
583 // if we've been dead for a while, stop head-checking
584 if ( cs->deathTime < ( level.time - 5000 ) ) {
585 ent->flags |= FL_NO_HEADCHECK;
586 }
587 }
588 //
589 // do some special handling for this character
590 AICast_SpecialFunc( cs );
591 //
592 // no more thinking required
593 return;
594 }
595 //
596 // set some anim conditions
597 if ( cs->secondDeadTime ) {
598 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qtrue, qfalse );
599 } else {
600 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qfalse, qfalse );
601 }
602 // set health value
603 if ( ent->health <= 0.25 * cs->attributes[STARTING_HEALTH] ) {
604 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 2, qfalse );
605 } else if ( ent->health <= 0.5 * cs->attributes[STARTING_HEALTH] ) {
606 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 1, qfalse );
607 } else {
608 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 0, qfalse );
609 }
610 // set enemy position
611 if ( cs->enemyNum >= 0 ) {
612 if ( infront( ent, &g_entities[cs->enemyNum] ) ) {
613 BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue );
614 } else {
615 BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue );
616 }
617 } else {
618 BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_POSITION, POSITION_UNUSED, qtrue );
619 }
620 // set defense pose
621 if ( ent->flags & FL_DEFENSE_CROUCH ) {
622 BG_UpdateConditionValue( ent->s.number, ANIM_COND_DEFENSE, qtrue, qfalse );
623 } else {
624 BG_UpdateConditionValue( ent->s.number, ANIM_COND_DEFENSE, qfalse, qfalse );
625 }
626 //
627 cs->speedScale = 1.0; // reset each frame, set if required
628 cs->actionFlags = 0; // FIXME: move this to a Cast AI movement init function!
629 //retrieve the current client state
630 BotAI_GetClientState( client, &( cs->bs->cur_ps ) );
631 //
632 // setup movement speeds for the given state
633 // walking
634 animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALK );
635 if ( animIndex >= 0 ) {
636 anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
637 cs->attributes[WALKING_SPEED] = anim->moveSpeed;
638 }
639 // crouching
640 animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALKCR );
641 if ( animIndex >= 0 ) {
642 anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
643 cs->attributes[CROUCHING_SPEED] = anim->moveSpeed;
644 }
645 // running
646 animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_RUN );
647 if ( animIndex >= 0 ) {
648 anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
649 cs->attributes[RUNNING_SPEED] = anim->moveSpeed;
650 }
651 // update crouch speed scale
652 ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED];
653 //
654 // only enable headlook if we want to this frame
655 ent->client->ps.eFlags &= ~EF_HEADLOOK;
656 if ( cs->enemyNum >= 0 ) {
657 ent->client->ps.eFlags &= ~EF_STAND_IDLE2; // never use alt idle if fighting
658 }
659 //
660 // check for dead leader
661 if ( cs->leaderNum >= 0 && g_entities[cs->leaderNum].health <= 0 ) {
662 cs->leaderNum = -1;
663 }
664 //
665 #if 0
666 // HACK for village2, if they are stuck, find a good position (there is a friendly guy placed inside a table)
667 {
668 trace_t tr;
669 vec3_t org;
670 trap_Trace( &tr, cs->bs->cur_ps.origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, cs->bs->cur_ps.origin, cs->entityNum, CONTENTS_SOLID );
671 while ( tr.startsolid ) {
672 VectorCopy( cs->bs->cur_ps.origin, org );
673 org[0] += 96 * crandom();
674 org[1] += 96 * crandom();
675 org[2] += 16 * crandom();
676 trap_Trace( &tr, org, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, org, cs->entityNum, CONTENTS_SOLID );
677 G_SetOrigin( &g_entities[cs->entityNum], org );
678 VectorCopy( org, g_entities[cs->entityNum].client->ps.origin );
679 }
680 }
681 #endif
682 //add the delta angles to the cast's current view angles
683 for ( i = 0; i < 3; i++ ) {
684 cs->viewangles[i] = AngleMod( cs->viewangles[i] + SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) );
685 }
686 //
687 //increase the local time of the cast
688 cs->bs->ltime += thinktime;
689 //
690 cs->bs->thinktime = thinktime;
691 // clear flags each frame
692 cs->bFlags = 0;
693 //
694 // check enemy health
695 if ( cs->enemyNum >= 0 && g_entities[cs->enemyNum].health <= 0 ) {
696 cs->enemyNum = -1;
697 }
698 //
699 // if the previous movetype was temporary, set it back
700 if ( cs->movestateType == MSTYPE_TEMPORARY ) {
701 cs->movestate = MS_DEFAULT;
702 cs->movestateType = MSTYPE_NONE;
703 }
704 // crouching?
705 if ( ( cs->attackcrouch_time >= level.time ) /*&&
706 ((cs->lastAttackCrouch > level.time - 500) || (cs->thinkFuncChangeTime < level.time - 1000))*/) {
707 // if we are not moving, and we are firing, always stand, unless we are allowed to crouch + fire
708 if ( ( cs->lastucmd.forwardmove || cs->lastucmd.rightmove ) || ( cs->lastWeaponFired < level.time - 2000 ) || ( cs->aiFlags & AIFL_ATTACK_CROUCH ) ) {
709 cs->lastAttackCrouch = level.time;
710 trap_EA_Crouch( cs->bs->client );
711 }
712 }
713 //
714 //if (cs->enemyNum >= 0) {
715 //update the attack inventory values
716 AICast_UpdateBattleInventory( cs, cs->enemyNum );
717 //}
718 //
719 // if we don't have ammo for the current weapon, get rid of it
720 if ( !( COM_BitCheck( cs->bs->cur_ps.weapons, cs->weaponNum ) ) || !AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum ) ) {
721 // select a weapon
722 AICast_ChooseWeapon( cs, qfalse );
723 // if still no ammo, select a blank weapon
724 //if (!AICast_GotEnoughAmmoForWeapon( cs, cs->weaponNum )) {
725 // cs->weaponNum = WP_NONE;
726 //}
727 }
728 //
729 // in query mode, we do special handling (pause scripting, check for transition to alert/combat, etc)
730 if ( cs->aiState == AISTATE_QUERY ) {
731 AICast_QueryThink( cs );
732 } else if ( cs->pauseTime < level.time ) {
733 // do the thinking
734 AICast_ProcessAIFunctions( cs, thinktime );
735 //
736 // make sure the correct weapon is selected
737 trap_EA_SelectWeapon( cs->bs->client, cs->weaponNum );
738 //
739 // process current script if it exists
740 cs->castScriptStatusCurrent = cs->castScriptStatus;
741 AICast_ScriptRun( cs, qfalse );
742 //
743 // do some special handling for this character
744 AICast_SpecialFunc( cs );
745 }
746 //
747 // any anim playing on legs should prevent turn anims being played in the cgame
748 if ( ent->client->ps.legsTimer ) {
749 ent->client->ps.eFlags |= EF_NO_TURN_ANIM;
750 } else {
751 ent->client->ps.eFlags &= ~EF_NO_TURN_ANIM;
752 }
753 //
754 // set special movestate if necessary
755 if ( cs->movestateType != MSTYPE_NONE ) {
756 switch ( cs->movestate ) {
757 case MS_WALK:
758 cs->actionFlags |= CASTACTION_WALK;
759 break;
760 case MS_CROUCH:
761 trap_EA_Crouch( cs->entityNum );
762 break;
763 default:
764 break;
765 }
766 }
767 //
768 // set our weapon in the old structure, in case it's needed elsewhere (?)
769 cs->bs->weaponnum = cs->weaponNum;
770 // see if we were recently firing
771 if ( cs->lastWeaponFired && cs->lastWeaponFired > level.time - 2000 ) {
772 ent->client->ps.eFlags |= EF_RECENTLY_FIRING;
773 } else {
774 ent->client->ps.eFlags &= ~EF_RECENTLY_FIRING;
775 }
776 //
777 //subtract the delta angles
778 for ( i = 0; i < 3; i++ ) {
779 cs->viewangles[i] = AngleMod( cs->viewangles[i] - SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) );
780 }
781 }
782
783 /*
784 ============
785 AICast_StartFrame
786
787 Think any clients that need thinking
788 ============
789 */
790 void CopyToBodyQue( gentity_t *ent );
791
AICast_StartFrame(int time)792 void AICast_StartFrame( int time ) {
793 int i, elapsed, count, clCount;
794 cast_state_t *cs;
795 int castcount;
796 static int lasttime, lastthink;
797 static vmCvar_t aicast_disable;
798 gentity_t *ent;
799
800 if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) {
801 return;
802 }
803
804 if ( saveGamePending ) {
805 return;
806 }
807
808 // if waiting at intermission, don't think
809 if ( strlen( g_missionStats.string ) > 1 ) {
810 return;
811 }
812
813 if ( !aicast_disable.handle ) {
814 trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT );
815 } else
816 {
817 trap_Cvar_Update( &aicast_disable );
818 if ( aicast_disable.integer ) {
819 return;
820 }
821 }
822
823 trap_Cvar_Update( &aicast_debug );
824 trap_Cvar_Update( &aicast_debugname );
825 trap_Cvar_Update( &aicast_scripts );
826
827 // no need to think during the intermission
828 if ( level.intermissiontime ) {
829 return;
830 }
831 //
832 // make sure the AAS gets updated
833 trap_BotLibStartFrame( (float) time / 1000 );
834 //
835 //
836 elapsed = time - lasttime;
837 if ( elapsed == 0 ) {
838 return; // no time has elapsed
839 }
840
841 //G_Printf( "AI startframe: %i\n", time );
842
843 if ( elapsed < 0 ) {
844 elapsed = 0;
845 lasttime = time;
846 }
847 // don't let the SIGHTING framerate drop below 10 (too much sighting to process at once)
848 if ( elapsed > 100 ) {
849 elapsed = 100;
850 }
851 AICast_SightUpdate( (int)( (float)SIGHT_PER_SEC * ( (float)elapsed / 1000 ) ) );
852 //
853 // update the player's area, only update if it's valid
854 for ( i = 0; i < 2; i++ ) {
855 trap_AAS_SetCurrentWorld( i );
856 castcount = BotPointAreaNum( g_entities[0].s.pos.trBase );
857 if ( castcount ) {
858 caststates[0].lastValidAreaNum[i] = castcount;
859 caststates[0].lastValidAreaTime[i] = level.time;
860 }
861 }
862 //
863 count = 0;
864 castcount = 0;
865 clCount = 0;
866 //
867 if ( ++lastthink > level.maxclients ) {
868 lastthink = 0;
869 }
870 //update the AI characters
871 for ( i = lastthink, ent = &g_entities[lastthink]; clCount < level.numPlayingClients && count < aicast_maxthink; i++, ent++ )
872 {
873 if ( i >= level.maxclients ) {
874 // rewind back to the start
875 i = 0;
876 ent = g_entities;
877 }
878 //
879 lastthink = i;
880 if ( !ent->inuse ) {
881 continue;
882 }
883 if ( ent->client ) {
884 clCount++;
885 }
886 //
887 cs = AICast_GetCastState( i );
888 // is this a cast AI?
889 if ( cs->bs ) {
890 if ( ent->aiInactive == qfalse ) {
891 //
892 elapsed = time - cs->lastThink;
893 //
894 // if they're moving/firing think every frame
895 if ( ( elapsed && cs->scriptAnimTime && cs->scriptAnimTime >= ( level.time - 1000 ) ) ||
896 ( ( elapsed >= 50 ) &&
897 ( ( ( ( !VectorCompare( ent->client->ps.velocity, vec3_origin ) ) ||
898 ( cs->enemyNum >= 0 ) ||
899 ( cs->aiState >= AISTATE_COMBAT ) ||
900 ( cs->vislist[0].visible_timestamp && cs->vislist[0].visible_timestamp > level.time - 4000 ) ||
901 ( ent->client->buttons ) ||
902 ( elapsed >= aicast_thinktime ) ) /*&&
903 (count <= aicast_maxthink)*/) ||
904 ( elapsed >= aicast_thinktime * 2 ) ) ) ) {
905 // make it think now
906 AICast_Think( i, (float)elapsed / 1000 );
907 // did they drop?
908 if ( !cs->bs || !cs->bs->inuse ) {
909 break; // get out of here, to be safe
910 }
911 cs->lastThink = time + rand() % 20; // randomize this slightly to spread out thinks during high framerates
912 //
913 // only count live guys
914 if ( ent->health > 0 ) {
915 count++;
916 }
917 }
918 // check for any debug info updates
919 AICast_DebugFrame( cs );
920 } else if ( cs->aiFlags & AIFL_WAITINGTOSPAWN ) {
921 // check f the space is clear yet
922 ent->AIScript_AlertEntity( ent );
923 }
924 //
925 // see if we've checked all cast AI's
926 if ( ++castcount >= numcast ) {
927 break;
928 }
929 }
930 }
931 //
932 lasttime = time;
933 }
934
935 /*
936 ============
937 AICast_StartServerFrame
938
939 Do movements, sighting, etc
940 ============
941 */
AICast_StartServerFrame(int time)942 void AICast_StartServerFrame( int time ) {
943 int i, elapsed, activeCount;
944 cast_state_t *cs;
945 int castcount;
946 static int lasttime;
947 static vmCvar_t aicast_disable;
948 gentity_t *ent;
949 cast_state_t *pcs;
950 qboolean highPriority;
951 int oldLegsTimer;
952
953 if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) {
954 return;
955 }
956
957 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
958 return;
959 }
960
961 if ( saveGamePending ) {
962 return;
963 }
964
965 // if waiting at intermission, don't think
966 if ( strlen( g_missionStats.string ) > 1 ) {
967 return;
968 }
969
970 if ( !aicast_disable.handle ) {
971 trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT );
972 } else
973 {
974 trap_Cvar_Update( &aicast_disable );
975 if ( aicast_disable.integer ) {
976 return;
977 }
978 }
979
980 trap_Cvar_Update( &aicast_debug );
981
982 // no need to think during the intermission
983 if ( level.intermissiontime ) {
984 return;
985 }
986 //
987 // make sure the AAS gets updated
988 trap_BotLibStartFrame( (float) time / 1000 );
989 //
990 //
991 elapsed = time - lasttime;
992 if ( elapsed == 0 ) {
993 return; // no time has elapsed
994 }
995
996 pcs = AICast_GetCastState( 0 );
997
998 // G_Printf( "AI startserverframe: %i\n", time );
999
1000 AICast_AgePlayTime( 0 );
1001
1002 if ( elapsed < 0 ) {
1003 // elapsed = 0;
1004 lasttime = time;
1005 }
1006 // don't let the framerate drop below 10
1007 // if ( elapsed > 100 ) {
1008 // elapsed = 100;
1009 // }
1010 //
1011 // process player's current script if it exists
1012 AICast_ScriptRun( AICast_GetCastState( 0 ), qfalse );
1013 //
1014 // AICast_SightUpdate( (int)((float)SIGHT_PER_SEC * ((float)elapsed / 1000)) );
1015 //
1016 castcount = 0;
1017 activeCount = 0;
1018 //
1019 //update the AI characters
1020 for ( i = 0, ent = g_entities; i < level.maxclients /*&& clCount < level.numPlayingClients*/; i++, ent++ )
1021 {
1022 // if ( ent->inuse && ent->client ) {
1023 // clCount++;
1024 // }
1025 //
1026 cs = AICast_GetCastState( i );
1027 // is this a cast AI?
1028 if ( cs->bs ) {
1029 if ( ent->aiInactive == qfalse && ent->inuse ) {
1030 //
1031 elapsed = level.time - cs->lastMoveThink;
1032 if ( cs->lastThink && elapsed > 0 ) {
1033 highPriority = qfalse;
1034 if ( ent->health > 0 ) {
1035 highPriority = qtrue;
1036 } else if ( cs->deathTime > ( level.time - 5000 ) ) {
1037 highPriority = qtrue;
1038 }
1039 //
1040 if ( highPriority ) {
1041 activeCount++;
1042 }
1043 //
1044 // optimization, if they're not in the player's PVS, and they aren't trying to move, then don't bother thinking
1045 if ( ( highPriority && ( elapsed > 300 ) )
1046 || ( g_entities[0].client && g_entities[0].client->cameraPortal )
1047 || ( highPriority && ( cs->vislist[0].visible_timestamp == cs->vislist[0].lastcheck_timestamp ) )
1048 || ( highPriority && ( pcs->vislist[cs->entityNum].visible_timestamp == pcs->vislist[cs->entityNum].lastcheck_timestamp ) )
1049 || ( VectorLength( ent->client->ps.velocity ) > 0 )
1050 || ( highPriority && ( cs->lastucmd.forwardmove || cs->lastucmd.rightmove || cs->lastucmd.upmove > 0 || cs->lastucmd.buttons || cs->lastucmd.wbuttons ) )
1051 // !!NOTE: always allow thinking if in PVS, otherwise bosses won't gib, and dead guys might not push away from clipped walls
1052 || ( trap_InPVS( cs->bs->origin, g_entities[0].s.pos.trBase ) ) ) { // do pvs check last, since it's the most expensive to call
1053 oldLegsTimer = ent->client->ps.legsTimer;
1054 //
1055 // send it's movement commands
1056 //
1057 serverTime = time;
1058 AICast_UpdateInput( cs, elapsed );
1059 trap_BotUserCommand( cs->bs->client, &( cs->lastucmd ) );
1060 cs->lastMoveThink = level.time;
1061 //
1062 // check for anim changes that may require us to stay still
1063 //
1064 if ( oldLegsTimer < ent->client->ps.legsTimer && ent->client->ps.groundEntityNum == ENTITYNUM_WORLD ) {
1065 // dont move until they are finished
1066 if ( cs->castScriptStatus.scriptNoMoveTime < level.time + ent->client->ps.legsTimer ) {
1067 cs->castScriptStatus.scriptNoMoveTime = level.time + ent->client->ps.legsTimer;
1068 }
1069 }
1070 }
1071 }
1072 } else {
1073 trap_UnlinkEntity( ent );
1074 }
1075 //
1076 // see if we've checked all cast AI's
1077 if ( ++castcount >= numcast ) {
1078 break;
1079 }
1080 }
1081 }
1082 //
1083 lasttime = time;
1084 //
1085 if ( aicast_debug.integer == 3 ) {
1086 G_Printf( "AI Active Count: %i\n", activeCount );
1087 }
1088 }
1089
1090 /*
1091 ==============
1092 AICast_PredictMovement
1093
1094 Simulates movement over a number of frames, returning the end position
1095 ==============
1096 */
AICast_PredictMovement(cast_state_t * cs,int numframes,float frametime,aicast_predictmove_t * move,usercmd_t * ucmd,int checkHitEnt)1097 void AICast_PredictMovement( cast_state_t *cs, int numframes, float frametime, aicast_predictmove_t *move, usercmd_t *ucmd, int checkHitEnt ) {
1098 int frame, i;
1099 playerState_t ps;
1100 pmove_t pm;
1101 trace_t tr;
1102 vec3_t end, startHitVec = { 0 }, thisHitVec, lastOrg, projPoint;
1103 qboolean checkReachMarker;
1104 gentity_t *ent = &g_entities[cs->entityNum];
1105 bot_input_t bi;
1106
1107 // int pretime = Sys_MilliSeconds();
1108 // G_Printf("PredictMovement: %f duration, %i frames\n", frametime, numframes );
1109
1110 if ( cs->bs ) {
1111 ps = cs->bs->cur_ps;
1112 trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
1113 } else {
1114 ps = g_entities[cs->entityNum].client->ps;
1115 }
1116
1117 ps.eFlags |= EF_DUMMY_PMOVE;
1118
1119 move->stopevent = PREDICTSTOP_NONE;
1120
1121 if ( checkHitEnt >= 0 && !Q_stricmp( g_entities[checkHitEnt].classname, "ai_marker" ) ) {
1122 checkReachMarker = qtrue;
1123 VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, ps.origin, startHitVec );
1124 VectorCopy( ps.origin, lastOrg );
1125 } else {
1126 checkReachMarker = qfalse;
1127 }
1128
1129 // don't let the frametime be too high
1130 // while (frametime > 0.2) {
1131 // numframes *= 2;
1132 // frametime /= 2;
1133 // }
1134
1135 for ( frame = 0; frame < numframes; frame++ )
1136 {
1137 memset( &pm, 0, sizeof( pm ) );
1138 pm.ps = &ps;
1139 pm.cmd = *ucmd;
1140 pm.oldcmd = *ucmd;
1141 pm.ps->commandTime = 0;
1142 pm.cmd.serverTime = (int)( 1000.0 * frametime );
1143 pm.tracemask = g_entities[cs->entityNum].clipmask; //MASK_PLAYERSOLID;
1144
1145 pm.trace = trap_TraceCapsule; //trap_Trace;
1146 pm.pointcontents = trap_PointContents;
1147 pm.debugLevel = qfalse;
1148 pm.noFootsteps = qtrue;
1149 // RF, not needed for prediction
1150 //pm.noWeapClips = qtrue; // (SA) AI's ignore weapon clips
1151
1152 // perform a pmove
1153 Pmove( &pm );
1154
1155 if ( checkHitEnt >= 0 ) {
1156 // if we've hit the checkent, abort
1157 if ( checkReachMarker ) {
1158 VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, pm.ps->origin, thisHitVec );
1159 if ( DotProduct( startHitVec, thisHitVec ) < 0 ) {
1160 // project the marker onto the movement vec, and check distance
1161 ProjectPointOntoVector( g_entities[checkHitEnt].r.currentOrigin, lastOrg, pm.ps->origin, projPoint );
1162 if ( VectorDistance( g_entities[checkHitEnt].r.currentOrigin, projPoint ) < 8 ) {
1163 move->stopevent = PREDICTSTOP_HITENT;
1164 goto done;
1165 }
1166 }
1167 // use this position as the base for the next test
1168 //VectorCopy( thisHitVec, startHitVec );
1169 VectorCopy( pm.ps->origin, lastOrg );
1170 }
1171 // if we didn't reach the marker, then check for something that blocked us
1172 for ( i = 0; i < pm.numtouch; i++ ) {
1173 if ( pm.touchents[i] == pm.ps->groundEntityNum ) {
1174 continue;
1175 }
1176 if ( pm.touchents[i] == checkHitEnt ) {
1177 move->stopevent = PREDICTSTOP_HITENT;
1178 goto done;
1179 } else if ( pm.touchents[i] < MAX_CLIENTS ||
1180 ( pm.touchents[i] != ENTITYNUM_WORLD && ( g_entities[pm.touchents[i]].s.eType != ET_MOVER || g_entities[pm.touchents[i]].moverState != MOVER_POS1 ) ) ) {
1181 // we have hit another entity, so abort
1182 move->stopevent = PREDICTSTOP_HITCLIENT;
1183 goto done;
1184 } else if ( !Q_stricmp( g_entities[pm.touchents[i]].classname, "script_mover" ) ) {
1185 // avoid script_mover's
1186 move->stopevent = PREDICTSTOP_HITCLIENT;
1187 goto done;
1188 }
1189 }
1190 // if we are trying to get to something, we should keep adjusting our input to head towards them
1191 if ( cs->bs && checkHitEnt >= 0 ) {
1192 // keep walking straight to them
1193 VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, pm.ps->origin, bi.dir );
1194 if ( !ent->waterlevel ) {
1195 bi.dir[2] = 0;
1196 }
1197 VectorNormalize( bi.dir );
1198 bi.actionflags = 0;
1199 bi.speed = 400;
1200 AICast_InputToUserCommand( cs, &bi, ucmd, ps.delta_angles );
1201 }
1202 }
1203 }
1204
1205 done:
1206
1207 // hack, if we are above ground, chances are it's because we only did one frame, and gravity isn't applied until
1208 // after the frame, so try and drop us down some
1209 if ( pm.ps->groundEntityNum == ENTITYNUM_NONE ) {
1210 VectorCopy( pm.ps->origin, end );
1211 end[2] -= 32;
1212 trap_Trace( &tr, pm.ps->origin, pm.mins, pm.maxs, end, pm.ps->clientNum, pm.tracemask );
1213 if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1 ) {
1214 VectorCopy( tr.endpos, pm.ps->origin );
1215 pm.ps->groundEntityNum = tr.entityNum;
1216 }
1217 }
1218
1219 // copy off the results
1220 VectorCopy( pm.ps->origin, move->endpos );
1221 move->frames = frame;
1222 //move->presencetype = cs->bs->presencetype;
1223 VectorCopy( pm.ps->velocity, move->velocity );
1224 move->numtouch = pm.numtouch;
1225 memcpy( move->touchents, pm.touchents, sizeof( pm.touchents ) );
1226 move->groundEntityNum = pm.ps->groundEntityNum;
1227
1228 //G_Printf("PredictMovement: %i ms\n", -pretime + Sys_MilliSeconds() );
1229 }
1230
1231 /*
1232 ============
1233 AICast_GetAvoid
1234 ============
1235 */
AICast_GetAvoid(cast_state_t * cs,bot_goal_t * goal,vec3_t outpos,qboolean reverse,int blockEnt)1236 qboolean AICast_GetAvoid( cast_state_t *cs, bot_goal_t *goal, vec3_t outpos, qboolean reverse, int blockEnt ) {
1237 float yaw, oldyaw, distmoved, bestmoved;
1238 vec3_t bestpos = { 0 };
1239 aicast_predictmove_t castmove;
1240 usercmd_t ucmd;
1241 qboolean enemyVisible;
1242 float angleDiff;
1243 int starttraveltime = 0, traveltime;
1244 int invert;
1245 float inc;
1246 qboolean averting = qfalse;
1247 float maxYaw;
1248 static int lastTime;
1249 //
1250 // if we are in the air, no chance of avoiding
1251 if ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE && g_entities[cs->entityNum].waterlevel <= 1 ) {
1252 return qfalse;
1253 }
1254 //
1255 if ( cs->lastAvoid > level.time - rand() % 500 ) {
1256 return qfalse;
1257 }
1258 cs->lastAvoid = level.time + 50 + rand() % 500;
1259 //
1260 if ( lastTime == level.time ) {
1261 return qfalse;
1262 }
1263 lastTime = level.time;
1264
1265 // if they have an enemy, and can currently see them, don't move out of their view
1266 enemyVisible = ( cs->enemyNum >= 0 ) &&
1267 ( AICast_CheckAttack( cs, cs->enemyNum, qfalse ) );
1268 //
1269 // look for a good direction to move out of the way
1270 bestmoved = 0;
1271 if ( goal ) {
1272 starttraveltime = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, goal->areanum, cs->travelflags );
1273 }
1274 memcpy( &ucmd, &cs->lastucmd, sizeof( usercmd_t ) );
1275 ucmd.forwardmove = 127;
1276 ucmd.rightmove = 0;
1277 ucmd.upmove = 0;
1278 if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) {
1279 averting = qtrue;
1280 } else if ( !goal ) {
1281 averting = qtrue; // not heading for a goal, so we must be getting out of someone's way
1282 }
1283 //
1284 maxYaw = 0;
1285 //
1286 if ( averting ) {
1287 // avoiding danger, go anywhere!
1288 angleDiff = 300;
1289 inc = 60;
1290 invert = 1;
1291 } else {
1292 if ( level.time % 1000 < 500 ) {
1293 invert = 1;
1294 } else {
1295 invert = -1;
1296 }
1297 angleDiff = 140;
1298 inc = 35;
1299 }
1300 if ( blockEnt > aicast_maxclients ) {
1301 maxYaw = angleDiff;
1302 }
1303 //
1304 for ( yaw = -angleDiff * invert; yaw*invert <= maxYaw; yaw += inc * invert ) {
1305 if ( !averting && !yaw ) {
1306 continue;
1307 }
1308 oldyaw = cs->bs->cur_ps.viewangles[YAW];
1309 cs->bs->cur_ps.viewangles[YAW] += yaw + reverse * 180;
1310 //
1311 ucmd.angles[YAW] = ANGLE2SHORT( AngleMod( cs->bs->cur_ps.viewangles[YAW] ) );
1312 //
1313 AICast_PredictMovement( cs, 5, 0.4, &castmove, &ucmd, -1 );
1314 // if we have a danger entity, try and get away from it at all costs
1315 if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) {
1316 distmoved = Distance( castmove.endpos, cs->dangerEntityPos );
1317 } else if ( goal ) {
1318 //distmoved = 99999 - trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum(castmove.endpos), castmove.endpos, goal->areanum, cs->travelflags );
1319 distmoved = 99999 - Distance( castmove.endpos, goal->origin );
1320 } else {
1321 distmoved = Distance( castmove.endpos, cs->bs->cur_ps.origin );
1322 }
1323 if ( ( distmoved > bestmoved )
1324 //&& ((cs->bs->origin[2] - castmove.endpos[2]) < 64) // allow up, but not down (falling)
1325 && ( castmove.groundEntityNum != ENTITYNUM_NONE ) ) {
1326 // they all passed, check any other stuff
1327 if ( !enemyVisible || AICast_CheckAttackAtPos( cs->entityNum, cs->enemyNum, castmove.endpos, qfalse, qfalse ) ) {
1328 if ( !goal || ( traveltime = trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum( castmove.endpos ), castmove.endpos, goal->areanum, cs->travelflags ) ) < ( starttraveltime + 200 ) ) {
1329 bestmoved = distmoved;
1330 VectorCopy( castmove.endpos, bestpos );
1331 }
1332 }
1333 }
1334 //
1335 cs->bs->cur_ps.viewangles[YAW] = oldyaw;
1336 }
1337 //
1338 if ( bestmoved > 0 ) {
1339 VectorCopy( bestpos, outpos );
1340 return qtrue;
1341 } else {
1342 return qfalse;
1343 }
1344
1345 //G_Printf("GetAvoid: %i ms\n", -pretime + Sys_MilliSeconds() );
1346 }
1347
1348 /*
1349 ============
1350 AICast_Blocked
1351 ============
1352 */
AICast_Blocked(cast_state_t * cs,bot_moveresult_t * moveresult,int activate,bot_goal_t * goal)1353 void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ) {
1354 vec3_t pos, dir;
1355 aicast_predictmove_t move;
1356 usercmd_t ucmd;
1357 bot_input_t bi;
1358 cast_state_t *ocs;
1359 int i, blockEnt = -1;
1360 bot_goal_t ogoal;
1361
1362 if ( cs->blockedAvoidTime < level.time ) {
1363 if ( cs->blockedAvoidTime < level.time - 300 ) {
1364 if ( VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->lastucmd.forwardmove && !cs->lastucmd.rightmove ) {
1365 // not moving, don't bother checking
1366 cs->blockedAvoidTime = level.time - 1;
1367 return;
1368 }
1369 // are we going to hit someone soon?
1370 trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
1371 AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles );
1372 AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, ( goal && goal->entitynum > -1 ) ? goal->entitynum : cs->entityNum );
1373
1374 // blocked if we hit a client (or non-stationary mover) other than our enemy or goal
1375 if ( move.stopevent != PREDICTSTOP_HITCLIENT ) {
1376 // not blocked
1377 cs->blockedAvoidTime = level.time - 1;
1378 return;
1379 }
1380
1381 // if we stopped passed our goal, ignore it
1382 if ( goal ) {
1383 if ( VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos ) ) {
1384 vec3_t v1, v2;
1385 VectorSubtract( goal->origin, cs->bs->origin, v1 );
1386 VectorSubtract( goal->origin, move.endpos, v2 );
1387 VectorNormalize( v1 );
1388 VectorNormalize( v2 );
1389 if ( DotProduct( v1, v2 ) < 0 ) {
1390 // we went passed the goal, so assume we can reach it
1391 cs->blockedAvoidTime = level.time - 1;
1392 return;
1393 }
1394 }
1395 }
1396
1397 // try and get them to move, in case we can't get around them
1398 blockEnt = -1;
1399 for ( i = 0; i < move.numtouch; i++ ) {
1400 if ( move.touchents[i] >= MAX_CLIENTS ) {
1401 if ( !Q_stricmp( g_entities[move.touchents[i]].classname, "script_mover" ) ) {
1402 // avoid script_mover's
1403 blockEnt = move.touchents[i];
1404 }
1405 // if we are close to the impact point, then avoid this entity
1406 else if ( VectorDistance( cs->bs->origin, move.endpos ) < 10 ) {
1407 //G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname );
1408 blockEnt = move.touchents[i];
1409 }
1410 continue;
1411 }
1412 //
1413 ocs = AICast_GetCastState( move.touchents[i] );
1414 if ( !ocs->bs ) {
1415 blockEnt = move.touchents[i];
1416 }
1417 // reject this blocker if we are following or going to them
1418 else if ( cs->followEntity != ocs->entityNum ) {
1419 // if they are moving away from us already, let them go
1420 if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) {
1421 vec3_t v1, v2;
1422
1423 VectorSubtract( ocs->bs->origin, cs->bs->origin, v2 );
1424 VectorNormalize( v2 );
1425 VectorNormalize2( ocs->bs->cur_ps.velocity, v1 );
1426
1427 if ( DotProduct( v1, v2 ) > 0.0 ) {
1428 continue;
1429 }
1430 }
1431 //
1432 // if they recently were asked to avoid us, then they're probably not listening
1433 if ( ocs->obstructingTime > level.time - 500 ) {
1434 blockEnt = move.touchents[i];
1435 }
1436 //
1437 // if they are not avoiding, ignore
1438 if ( !( ocs->aiFlags & AIFL_NOAVOID ) ) {
1439 continue;
1440 }
1441 //
1442 // they should avoid us
1443 if ( ocs->leaderNum >= 0 ) {
1444 ogoal.entitynum = ocs->leaderNum;
1445 VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin );
1446 if ( AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) {
1447 // give them time to move somewhere else
1448 ocs->obstructingTime = level.time + 1000;
1449 } else {
1450 // make sure they don't call GetAvoid() for another few frames to let others avoid also
1451 ocs->obstructingTime = level.time - 1;
1452 blockEnt = move.touchents[i];
1453 }
1454 } else {
1455 if ( AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) {
1456 // give them time to move somewhere else
1457 ocs->obstructingTime = level.time + 1000;
1458 } else {
1459 // make sure they don't call GetAvoid() for another few frames to let others avoid also
1460 ocs->obstructingTime = level.time - 1;
1461 blockEnt = move.touchents[i];
1462 }
1463 }
1464 }
1465 }
1466
1467 } else {
1468 return;
1469 }
1470
1471 if ( blockEnt < 0 ) {
1472 // nothing found to be worth avoding
1473 cs->blockedAvoidTime = level.time - 1;
1474 return;
1475 }
1476
1477 // something is blocking our path
1478 if ( g_entities[blockEnt].aiName && g_entities[blockEnt].client && cs->bs
1479 && VectorDistance( cs->bs->origin, g_entities[blockEnt].r.currentOrigin ) < 128 ) {
1480 int oldId = cs->castScriptStatus.scriptId;
1481 AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName );
1482 if ( ( oldId != cs->castScriptStatus.scriptId ) || ( cs->aiFlags & AIFL_DENYACTION ) ) {
1483 // the script has changed, so assume the scripting is handling the avoidance
1484 return;
1485 }
1486 }
1487
1488 // avoid geometry and props, but assume clients will get out the way
1489 if ( /*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt ) ) {
1490 VectorSubtract( pos, cs->bs->cur_ps.origin, dir );
1491 VectorNormalize( dir );
1492 cs->blockedAvoidYaw = vectoyaw( dir );
1493 if ( blockEnt >= MAX_CLIENTS ) {
1494 cs->blockedAvoidTime = level.time + 100 + rand() % 200;
1495 } else {
1496 cs->blockedAvoidTime = level.time + 300 + rand() % 400;
1497 }
1498 } else {
1499 cs->blockedAvoidTime = level.time - 1; // don't look again for another few frames
1500 return;
1501 }
1502 }
1503
1504 VectorClear( pos );
1505 pos[YAW] = cs->blockedAvoidYaw;
1506 AngleVectors( pos, dir, NULL, NULL );
1507
1508 if ( moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE ) {
1509 trap_EA_Jump( cs->bs->entitynum );
1510 }
1511
1512 trap_EA_Move( cs->bs->entitynum, dir, 200 ); //400);
1513
1514 vectoangles( dir, cs->ideal_viewangles );
1515 cs->ideal_viewangles[2] *= 0.5;
1516 }
1517
1518 /*
1519 ================
1520 AICast_EvaluatePmove
1521
1522 Avoidance after the event (leaders instruct AI's to get out the way, AI's instruct other non-moving AI's to get out the way)
1523 ================
1524 */
AICast_EvaluatePmove(int clientnum,pmove_t * pm)1525 void AICast_EvaluatePmove( int clientnum, pmove_t *pm ) {
1526 cast_state_t *cs, *ocs;
1527 int i, ent;
1528 bot_goal_t ogoal;
1529
1530 //vec3_t pos, dir;
1531 cs = AICast_GetCastState( clientnum );
1532 // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
1533 trap_AAS_SetCurrentWorld( cs->aasWorldIndex );
1534
1535 // NOTE: this is only enabled for real clients, so their followers get out of their way
1536 //if (cs->bs)
1537 // return;
1538
1539 // look through the touchent's to see if we've bumped into something we should avoid, or react to
1540 for ( i = 0; i < pm->numtouch; i++ )
1541 {
1542 // mark the time, so they can deal with the obstruction in their own think functions
1543 cs->blockedTime = level.time;
1544
1545 if ( pm->touchents[i] == pm->ps->groundEntityNum ) {
1546 continue;
1547 }
1548
1549 // if they are an AI Cast, inform them of our disposition, and hope that they are reasonable
1550 // enough to assist us in our desire to move beyond our current position
1551 if ( pm->touchents[i] < aicast_maxclients ) {
1552 if ( !AICast_EntityVisible( cs, pm->touchents[i], qtrue ) ) {
1553 continue;
1554 }
1555
1556 // if we are inspecting the body, abort if we touch anything
1557 if ( cs->bs && cs->enemyNum >= 0 && g_entities[cs->enemyNum].health <= 0 ) {
1558 cs->enemyNum = -1;
1559 }
1560
1561 // anything we touch, should see us
1562 AICast_UpdateVisibility( &g_entities[pm->touchents[i]], &g_entities[cs->entityNum], qfalse, qtrue );
1563
1564 ocs = AICast_GetCastState( pm->touchents[i] );
1565 if ( ( ocs->bs ) &&
1566 ( AICast_SameTeam( cs, ocs->entityNum ) ) &&
1567 ( !( ocs->aiFlags & AIFL_NOAVOID ) ) &&
1568 ( ( ocs->leaderNum == cs->entityNum ) || ( VectorLength( ocs->bs->velocity ) < 5 ) ) &&
1569 ( ocs->obstructingTime < ( level.time + 100 ) ) ) {
1570 // if they are moving away from us already, let them go
1571 if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) {
1572 vec3_t v1, v2;
1573
1574 VectorSubtract( ocs->bs->origin, g_entities[clientnum].client->ps.velocity, v2 );
1575 VectorNormalize( v2 );
1576 VectorNormalize2( ocs->bs->cur_ps.velocity, v1 );
1577
1578 if ( DotProduct( v1, v2 ) > 0.0 ) {
1579 continue;
1580 }
1581 }
1582 if ( ocs->leaderNum >= 0 ) {
1583 VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin );
1584 ogoal.areanum = BotPointAreaNum( ogoal.origin );
1585 ogoal.entitynum = ocs->leaderNum;
1586 if ( ocs->bs && AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else
1587 ocs->obstructingTime = level.time + 1000;
1588 }
1589 } else {
1590 if ( ocs->bs && AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else
1591 ocs->obstructingTime = level.time + 1000;
1592 }
1593 }
1594 }
1595 } else if ( cs->bs ) {
1596 // if we are blocked by a brush entity, see if we can activate it
1597 ent = pm->touchents[i];
1598 if ( g_entities[ent].s.modelindex > 0 && g_entities[ent].s.eType == ET_MOVER ) {
1599 //find the bsp entity which should be activated in order to remove
1600 //the blocking entity
1601
1602 if ( !g_entities[ent].isProp
1603 && Q_stricmp( g_entities[ent].classname, "func_static" )
1604 && Q_stricmp( g_entities[ent].classname, "func_button" )
1605 && Q_stricmp( g_entities[ent].classname, "func_tram" ) ) {
1606 G_Activate( &g_entities[ent], &g_entities[cs->entityNum] );
1607 }
1608
1609 }
1610 }
1611 }
1612 }
1613
1614 /*
1615 ==============
1616 AICast_RequestCrouchAttack
1617 ==============
1618 */
AICast_RequestCrouchAttack(cast_state_t * cs,vec3_t org,float time)1619 qboolean AICast_RequestCrouchAttack( cast_state_t *cs, vec3_t org, float time ) {
1620 if ( cs->attributes[ATTACK_CROUCH] > 0 && AICast_CheckAttackAtPos( cs->entityNum, cs->enemyNum, org, qtrue, qfalse ) ) {
1621 if ( time ) {
1622 cs->attackcrouch_time = level.time + (int)( time * 1000 );
1623 }
1624 return qtrue;
1625 }
1626
1627 return qfalse;
1628 }
1629
1630 /*
1631 ==============
1632 AICast_QueryThink
1633 ==============
1634 */
AICast_QueryThink(cast_state_t * cs)1635 void AICast_QueryThink( cast_state_t *cs ) {
1636 gentity_t *ent;
1637 qboolean visible;
1638 cast_state_t *ocs;
1639 vec3_t vec;
1640
1641 ent = &g_entities[cs->entityNum];
1642 ocs = AICast_GetCastState( cs->enemyNum );
1643
1644 // never crouch while in this state (by choice anyway)
1645 cs->attackcrouch_time = 0;
1646
1647 // look at where we last (thought we) saw them
1648 VectorSubtract( cs->vislist[cs->enemyNum].visible_pos, cs->bs->origin, vec );
1649 VectorNormalize( vec );
1650 vectoangles( vec, cs->ideal_viewangles );
1651
1652 // are they visible now?
1653 visible = AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, g_entities[cs->enemyNum].r.currentOrigin, cs->enemyNum, qfalse );
1654
1655 // make sure we dont process the sighting of this enemy by going into query mode again, without them being visible again after we leave here
1656 cs->vislist[cs->enemyNum].flags &= ~AIVIS_PROCESS_SIGHTING;
1657
1658 // look towards where we last saw them
1659 AICast_AimAtEnemy( cs );
1660
1661 // if visible and alert time has expired, go POSTAL
1662 if ( ( cs->queryAlertSightTime < 0 ) || ( ( cs->queryAlertSightTime < level.time ) && visible ) ) {
1663 if ( !cs->queryAlertSightTime ) {
1664 // set the "short reaction" condition
1665 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse );
1666 }
1667 AICast_StateChange( cs, AISTATE_COMBAT );
1668 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse );
1669 AIFunc_BattleStart( cs );
1670 return;
1671 }
1672
1673 // if they've fired since the start of the query mode, go POSTAL
1674 if ( ocs->lastWeaponFired > cs->queryStartTime ) {
1675 // set the "short reaction" condition
1676 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse );
1677 AICast_StateChange( cs, AISTATE_COMBAT );
1678 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse );
1679 AIFunc_BattleStart( cs );
1680 return;
1681 }
1682
1683 // if not visible, then kill the Lock On timer
1684 if ( ( cs->queryAlertSightTime > 0 ) && !visible ) {
1685 cs->queryAlertSightTime = 0;
1686 }
1687
1688 // if the query has expired, go back to relaxed
1689 if ( !ent->client->ps.legsTimer ) {
1690 AICast_StateChange( cs, AISTATE_RELAXED );
1691 }
1692 }
1693
1694 /*
1695 ================
1696 AICast_DeadClipWalls
1697 ================
1698 */
AICast_DeadClipWalls(cast_state_t * cs)1699 void AICast_DeadClipWalls( cast_state_t *cs ) {
1700 /*
1701 //animation_t *anim;
1702 orientation_t or;
1703 vec3_t src, vel;
1704 trace_t tr;
1705
1706 // get the death animation we are currently playing
1707 //anim = BG_GetAnimationForIndex( cs->entityNum, (cs->bs->cur_ps.torsoAnim & ~ANIM_TOGGLEBIT) );
1708
1709 // find the head position
1710 trap_GetTag( cs->entityNum, "tag_head", &or );
1711 // move up a tad
1712 or.origin[2] += 3;
1713
1714 // trace from the base of our bounding box, to the head
1715 VectorCopy( cs->bs->origin, src );
1716 src[2] -= cs->bs->cur_ps.mins[2] + 3;
1717 trap_Trace( &tr, src, vec3_origin, vec3_origin, or.origin, cs->entityNum, MASK_SOLID );
1718
1719 // if we hit something, move away from it
1720 if (!tr.startsolid && !tr.allsolid && tr.fraction < 1.0) {
1721 VectorScale( tr.plane.normal, 80, vel );
1722 vel[2] = 0;
1723 VectorAdd( g_entities[cs->entityNum].client->ps.velocity, vel, g_entities[cs->entityNum].client->ps.velocity );
1724 }
1725 */
1726 }
1727
1728 /*
1729 ==================
1730 AICast_IdleReload
1731 ==================
1732 */
AICast_IdleReload(cast_state_t * cs)1733 void AICast_IdleReload( cast_state_t *cs ) {
1734 if ( AICast_NoReload( cs->entityNum ) ) {
1735 return;
1736 }
1737 if ( cs->noReloadTime >= level.time ) {
1738 return;
1739 }
1740 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 )] ) ) {
1741 return;
1742 }
1743 //
1744 trap_EA_Reload( cs->entityNum );
1745 }
1746