]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/game/ai/AI.cpp
hello world
[icculus/iodoom3.git] / neo / game / ai / AI.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 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 Doom 3 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 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 Doom 3 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 #include "../../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "../Game_local.h"
33
34 static const char *moveCommandString[ NUM_MOVE_COMMANDS ] = {
35         "MOVE_NONE",
36         "MOVE_FACE_ENEMY",
37         "MOVE_FACE_ENTITY",
38         "MOVE_TO_ENEMY",
39         "MOVE_TO_ENEMYHEIGHT",
40         "MOVE_TO_ENTITY",
41         "MOVE_OUT_OF_RANGE",
42         "MOVE_TO_ATTACK_POSITION",
43         "MOVE_TO_COVER",
44         "MOVE_TO_POSITION",
45         "MOVE_TO_POSITION_DIRECT",
46         "MOVE_SLIDE_TO_POSITION",
47         "MOVE_WANDER"
48 };
49
50 /*
51 =====================
52 idMoveState::idMoveState
53 =====================
54 */
55 idMoveState::idMoveState() {
56         moveType                        = MOVETYPE_ANIM;
57         moveCommand                     = MOVE_NONE;
58         moveStatus                      = MOVE_STATUS_DONE;
59         moveDest.Zero();
60         moveDir.Set( 1.0f, 0.0f, 0.0f );
61         goalEntity                      = NULL;
62         goalEntityOrigin.Zero();
63         toAreaNum                       = 0;
64         startTime                       = 0;
65         duration                        = 0;
66         speed                           = 0.0f;
67         range                           = 0.0f;
68         wanderYaw                       = 0;
69         nextWanderTime          = 0;
70         blockTime                       = 0;
71         obstacle                        = NULL;
72         lastMoveOrigin          = vec3_origin;
73         lastMoveTime            = 0;
74         anim                            = 0;
75 }
76
77 /*
78 =====================
79 idMoveState::Save
80 =====================
81 */
82 void idMoveState::Save( idSaveGame *savefile ) const {
83         savefile->WriteInt( (int)moveType );
84         savefile->WriteInt( (int)moveCommand );
85         savefile->WriteInt( (int)moveStatus );
86         savefile->WriteVec3( moveDest );
87         savefile->WriteVec3( moveDir );
88         goalEntity.Save( savefile );
89         savefile->WriteVec3( goalEntityOrigin );
90         savefile->WriteInt( toAreaNum );
91         savefile->WriteInt( startTime );
92         savefile->WriteInt( duration );
93         savefile->WriteFloat( speed );
94         savefile->WriteFloat( range );
95         savefile->WriteFloat( wanderYaw );
96         savefile->WriteInt( nextWanderTime );
97         savefile->WriteInt( blockTime );
98         obstacle.Save( savefile );
99         savefile->WriteVec3( lastMoveOrigin );
100         savefile->WriteInt( lastMoveTime );
101         savefile->WriteInt( anim );
102 }
103
104 /*
105 =====================
106 idMoveState::Restore
107 =====================
108 */
109 void idMoveState::Restore( idRestoreGame *savefile ) {
110         savefile->ReadInt( (int &)moveType );
111         savefile->ReadInt( (int &)moveCommand );
112         savefile->ReadInt( (int &)moveStatus );
113         savefile->ReadVec3( moveDest );
114         savefile->ReadVec3( moveDir );
115         goalEntity.Restore( savefile );
116         savefile->ReadVec3( goalEntityOrigin );
117         savefile->ReadInt( toAreaNum );
118         savefile->ReadInt( startTime );
119         savefile->ReadInt( duration );
120         savefile->ReadFloat( speed );
121         savefile->ReadFloat( range );
122         savefile->ReadFloat( wanderYaw );
123         savefile->ReadInt( nextWanderTime );
124         savefile->ReadInt( blockTime );
125         obstacle.Restore( savefile );
126         savefile->ReadVec3( lastMoveOrigin );
127         savefile->ReadInt( lastMoveTime );
128         savefile->ReadInt( anim );
129 }
130
131 /*
132 ============
133 idAASFindCover::idAASFindCover
134 ============
135 */
136 idAASFindCover::idAASFindCover( const idVec3 &hideFromPos ) {
137         int                     numPVSAreas;
138         idBounds        bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) );
139
140         // setup PVS
141         numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
142         hidePVS         = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
143 }
144
145 /*
146 ============
147 idAASFindCover::~idAASFindCover
148 ============
149 */
150 idAASFindCover::~idAASFindCover() {
151         gameLocal.pvs.FreeCurrentPVS( hidePVS );
152 }
153
154 /*
155 ============
156 idAASFindCover::TestArea
157 ============
158 */
159 bool idAASFindCover::TestArea( const idAAS *aas, int areaNum ) {
160         idVec3  areaCenter;
161         int             numPVSAreas;
162         int             PVSAreas[ idEntity::MAX_PVS_AREAS ];
163
164         areaCenter = aas->AreaCenter( areaNum );
165         areaCenter[ 2 ] += 1.0f;
166
167         numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
168         if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) {
169                 return true;
170         }
171
172         return false;
173 }
174
175 /*
176 ============
177 idAASFindAreaOutOfRange::idAASFindAreaOutOfRange
178 ============
179 */
180 idAASFindAreaOutOfRange::idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ) {
181         this->targetPos         = targetPos;
182         this->maxDistSqr        = maxDist * maxDist;
183 }
184
185 /*
186 ============
187 idAASFindAreaOutOfRange::TestArea
188 ============
189 */
190 bool idAASFindAreaOutOfRange::TestArea( const idAAS *aas, int areaNum ) {
191         const idVec3 &areaCenter = aas->AreaCenter( areaNum );
192         trace_t trace;
193         float dist;
194
195         dist = ( targetPos.ToVec2() - areaCenter.ToVec2() ).LengthSqr();
196
197         if ( ( maxDistSqr > 0.0f ) && ( dist < maxDistSqr ) ) {
198                 return false;
199         }
200
201         gameLocal.clip.TracePoint( trace, targetPos, areaCenter + idVec3( 0.0f, 0.0f, 1.0f ), MASK_OPAQUE, NULL );
202         if ( trace.fraction < 1.0f ) {
203                 return false;
204         }
205
206         return true;
207 }
208
209 /*
210 ============
211 idAASFindAttackPosition::idAASFindAttackPosition
212 ============
213 */
214 idAASFindAttackPosition::idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ) {
215         int     numPVSAreas;
216
217         this->target            = target;
218         this->targetPos         = targetPos;
219         this->fireOffset        = fireOffset;
220         this->self                      = self;
221         this->gravityAxis       = gravityAxis;
222
223         excludeBounds           = idBounds( idVec3( -64.0, -64.0f, -8.0f ), idVec3( 64.0, 64.0f, 64.0f ) );
224         excludeBounds.TranslateSelf( self->GetPhysics()->GetOrigin() ); 
225
226         // setup PVS
227         idBounds bounds( targetPos - idVec3( 16, 16, 0 ), targetPos + idVec3( 16, 16, 64 ) );
228         numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
229         targetPVS       = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
230 }
231
232 /*
233 ============
234 idAASFindAttackPosition::~idAASFindAttackPosition
235 ============
236 */
237 idAASFindAttackPosition::~idAASFindAttackPosition() {
238         gameLocal.pvs.FreeCurrentPVS( targetPVS );
239 }
240
241 /*
242 ============
243 idAASFindAttackPosition::TestArea
244 ============
245 */
246 bool idAASFindAttackPosition::TestArea( const idAAS *aas, int areaNum ) {
247         idVec3  dir;
248         idVec3  local_dir;
249         idVec3  fromPos;
250         idMat3  axis;
251         idVec3  areaCenter;
252         int             numPVSAreas;
253         int             PVSAreas[ idEntity::MAX_PVS_AREAS ];
254
255         areaCenter = aas->AreaCenter( areaNum );
256         areaCenter[ 2 ] += 1.0f;
257
258         if ( excludeBounds.ContainsPoint( areaCenter ) ) {
259                 // too close to where we already are
260                 return false;
261         }
262
263         numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
264         if ( !gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ) ) {
265                 return false;
266         }
267
268         // calculate the world transform of the launch position
269         dir = targetPos - areaCenter;
270         gravityAxis.ProjectVector( dir, local_dir );
271         local_dir.z = 0.0f;
272         local_dir.ToVec2().Normalize();
273         axis = local_dir.ToMat3();
274         fromPos = areaCenter + fireOffset * axis;
275
276         return self->GetAimDir( fromPos, target, self, dir );
277 }
278
279 /*
280 =====================
281 idAI::idAI
282 =====================
283 */
284 idAI::idAI() {
285         aas                                     = NULL;
286         travelFlags                     = TFL_WALK|TFL_AIR;
287
288         kickForce                       = 2048.0f;
289         ignore_obstacles        = false;
290         blockedRadius           = 0.0f;
291         blockedMoveTime         = 750;
292         blockedAttackTime       = 750;
293         turnRate                        = 360.0f;
294         turnVel                         = 0.0f;
295         anim_turn_yaw           = 0.0f;
296         anim_turn_amount        = 0.0f;
297         anim_turn_angles        = 0.0f;
298         fly_offset                      = 0;
299         fly_seek_scale          = 1.0f;
300         fly_roll_scale          = 0.0f;
301         fly_roll_max            = 0.0f;
302         fly_roll                        = 0.0f;
303         fly_pitch_scale         = 0.0f;
304         fly_pitch_max           = 0.0f;
305         fly_pitch                       = 0.0f;
306         allowMove                       = false;
307         allowHiddenMovement     = false;
308         fly_speed                       = 0.0f;
309         fly_bob_strength        = 0.0f;
310         fly_bob_vert            = 0.0f;
311         fly_bob_horz            = 0.0f;
312         lastHitCheckResult      = false;
313         lastHitCheckTime        = 0;
314         lastAttackTime          = 0;
315         melee_range                     = 0.0f;
316         projectile_height_to_distance_ratio = 1.0f;
317         projectileDef           = NULL;
318         projectile                      = NULL;
319         projectileClipModel     = NULL;
320         projectileRadius        = 0.0f;
321         projectileVelocity      = vec3_origin;
322         projectileGravity       = vec3_origin;
323         projectileSpeed         = 0.0f;
324         chat_snd                        = NULL;
325         chat_min                        = 0;
326         chat_max                        = 0;
327         chat_time                       = 0;
328         talk_state                      = TALK_NEVER;
329         talkTarget                      = NULL;
330
331         particles.Clear();
332         restartParticles        = true;
333         useBoneAxis                     = false;
334
335         wakeOnFlashlight        = false;
336         memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
337         worldMuzzleFlashHandle = -1;
338
339         enemy                           = NULL;
340         lastVisibleEnemyPos.Zero();
341         lastVisibleEnemyEyeOffset.Zero();
342         lastVisibleReachableEnemyPos.Zero();
343         lastReachableEnemyPos.Zero();
344         shrivel_rate            = 0.0f;
345         shrivel_start           = 0;
346         fl.neverDormant         = false;                // AI's can go dormant
347         current_yaw                     = 0.0f;
348         ideal_yaw                       = 0.0f;
349
350         num_cinematics          = 0;
351         current_cinematic       = 0;
352
353         allowEyeFocus           = true;
354         allowPain                       = true;
355         allowJointMod           = true;
356         focusEntity                     = NULL;
357         focusTime                       = 0;
358         alignHeadTime           = 0;
359         forceAlignHeadTime      = 0;
360
361         currentFocusPos.Zero();
362         eyeAng.Zero();
363         lookAng.Zero();
364         destLookAng.Zero();
365         lookMin.Zero();
366         lookMax.Zero();
367
368         eyeMin.Zero();
369         eyeMax.Zero();
370         muzzleFlashEnd          = 0;
371         flashTime                       = 0;
372         flashJointWorld         = INVALID_JOINT;
373
374         focusJoint                      = INVALID_JOINT;
375         orientationJoint        = INVALID_JOINT;
376         flyTiltJoint            = INVALID_JOINT;
377
378         eyeVerticalOffset       = 0.0f;
379         eyeHorizontalOffset = 0.0f;
380         eyeFocusRate            = 0.0f;
381         headFocusRate           = 0.0f;
382         focusAlignTime          = 0;
383 }
384
385 /*
386 =====================
387 idAI::~idAI
388 =====================
389 */
390 idAI::~idAI() {
391         delete projectileClipModel;
392         DeconstructScriptObject();
393         scriptObject.Free();
394         if ( worldMuzzleFlashHandle != -1 ) {
395                 gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
396                 worldMuzzleFlashHandle = -1;
397         }
398 }
399
400 /*
401 =====================
402 idAI::Save
403 =====================
404 */
405 void idAI::Save( idSaveGame *savefile ) const {
406         int i;
407
408         savefile->WriteInt( travelFlags );
409         move.Save( savefile );
410         savedMove.Save( savefile );
411         savefile->WriteFloat( kickForce );
412         savefile->WriteBool( ignore_obstacles );
413         savefile->WriteFloat( blockedRadius );
414         savefile->WriteInt( blockedMoveTime );
415         savefile->WriteInt( blockedAttackTime );
416
417         savefile->WriteFloat( ideal_yaw );
418         savefile->WriteFloat( current_yaw );
419         savefile->WriteFloat( turnRate );
420         savefile->WriteFloat( turnVel );
421         savefile->WriteFloat( anim_turn_yaw );
422         savefile->WriteFloat( anim_turn_amount );
423         savefile->WriteFloat( anim_turn_angles );
424
425         savefile->WriteStaticObject( physicsObj );
426
427         savefile->WriteFloat( fly_speed );
428         savefile->WriteFloat( fly_bob_strength );
429         savefile->WriteFloat( fly_bob_vert );
430         savefile->WriteFloat( fly_bob_horz );
431         savefile->WriteInt( fly_offset );
432         savefile->WriteFloat( fly_seek_scale );
433         savefile->WriteFloat( fly_roll_scale );
434         savefile->WriteFloat( fly_roll_max );
435         savefile->WriteFloat( fly_roll );
436         savefile->WriteFloat( fly_pitch_scale );
437         savefile->WriteFloat( fly_pitch_max );
438         savefile->WriteFloat( fly_pitch );
439
440         savefile->WriteBool( allowMove );
441         savefile->WriteBool( allowHiddenMovement );
442         savefile->WriteBool( disableGravity );
443         savefile->WriteBool( af_push_moveables );
444
445         savefile->WriteBool( lastHitCheckResult );
446         savefile->WriteInt( lastHitCheckTime );
447         savefile->WriteInt( lastAttackTime );
448         savefile->WriteFloat( melee_range );
449         savefile->WriteFloat( projectile_height_to_distance_ratio );
450
451         savefile->WriteInt( missileLaunchOffset.Num() );
452         for( i = 0; i < missileLaunchOffset.Num(); i++ ) {
453                 savefile->WriteVec3( missileLaunchOffset[ i ] );
454         }
455
456         idStr projectileName;
457         spawnArgs.GetString( "def_projectile", "", projectileName );
458         savefile->WriteString( projectileName );
459         savefile->WriteFloat( projectileRadius );
460         savefile->WriteFloat( projectileSpeed );
461         savefile->WriteVec3( projectileVelocity );
462         savefile->WriteVec3( projectileGravity );
463         projectile.Save( savefile );
464         savefile->WriteString( attack );
465
466         savefile->WriteSoundShader( chat_snd );
467         savefile->WriteInt( chat_min );
468         savefile->WriteInt( chat_max );
469         savefile->WriteInt( chat_time );
470         savefile->WriteInt( talk_state );
471         talkTarget.Save( savefile );
472
473         savefile->WriteInt( num_cinematics );
474         savefile->WriteInt( current_cinematic );
475
476         savefile->WriteBool( allowJointMod );
477         focusEntity.Save( savefile );
478         savefile->WriteVec3( currentFocusPos );
479         savefile->WriteInt( focusTime );
480         savefile->WriteInt( alignHeadTime );
481         savefile->WriteInt( forceAlignHeadTime );
482         savefile->WriteAngles( eyeAng );
483         savefile->WriteAngles( lookAng );
484         savefile->WriteAngles( destLookAng );
485         savefile->WriteAngles( lookMin );
486         savefile->WriteAngles( lookMax );
487
488         savefile->WriteInt( lookJoints.Num() );
489         for( i = 0; i < lookJoints.Num(); i++ ) {
490                 savefile->WriteJoint( lookJoints[ i ] );
491                 savefile->WriteAngles( lookJointAngles[ i ] );
492         }
493
494         savefile->WriteFloat( shrivel_rate );
495         savefile->WriteInt( shrivel_start );
496
497         savefile->WriteInt( particles.Num() );
498         for  ( i = 0; i < particles.Num(); i++ ) {
499                 savefile->WriteParticle( particles[i].particle );
500                 savefile->WriteInt( particles[i].time );
501                 savefile->WriteJoint( particles[i].joint );
502         }
503         savefile->WriteBool( restartParticles );
504         savefile->WriteBool( useBoneAxis );
505
506         enemy.Save( savefile );
507         savefile->WriteVec3( lastVisibleEnemyPos );
508         savefile->WriteVec3( lastVisibleEnemyEyeOffset );
509         savefile->WriteVec3( lastVisibleReachableEnemyPos );
510         savefile->WriteVec3( lastReachableEnemyPos );
511         savefile->WriteBool( wakeOnFlashlight );
512
513         savefile->WriteAngles( eyeMin );
514         savefile->WriteAngles( eyeMax );
515
516         savefile->WriteFloat( eyeVerticalOffset );
517         savefile->WriteFloat( eyeHorizontalOffset );
518         savefile->WriteFloat( eyeFocusRate );
519         savefile->WriteFloat( headFocusRate );
520         savefile->WriteInt( focusAlignTime );
521
522         savefile->WriteJoint( flashJointWorld );
523         savefile->WriteInt( muzzleFlashEnd );
524
525         savefile->WriteJoint( focusJoint );
526         savefile->WriteJoint( orientationJoint );
527         savefile->WriteJoint( flyTiltJoint );
528
529         savefile->WriteBool( GetPhysics() == static_cast<const idPhysics *>(&physicsObj) );
530 }
531
532 /*
533 =====================
534 idAI::Restore
535 =====================
536 */
537 void idAI::Restore( idRestoreGame *savefile ) {
538         bool            restorePhysics;
539         int                     i;
540         int                     num;
541         idBounds        bounds;
542
543         savefile->ReadInt( travelFlags );
544         move.Restore( savefile );
545         savedMove.Restore( savefile );
546         savefile->ReadFloat( kickForce );
547         savefile->ReadBool( ignore_obstacles );
548         savefile->ReadFloat( blockedRadius );
549         savefile->ReadInt( blockedMoveTime );
550         savefile->ReadInt( blockedAttackTime );
551
552         savefile->ReadFloat( ideal_yaw );
553         savefile->ReadFloat( current_yaw );
554         savefile->ReadFloat( turnRate );
555         savefile->ReadFloat( turnVel );
556         savefile->ReadFloat( anim_turn_yaw );
557         savefile->ReadFloat( anim_turn_amount );
558         savefile->ReadFloat( anim_turn_angles );
559
560         savefile->ReadStaticObject( physicsObj );
561
562         savefile->ReadFloat( fly_speed );
563         savefile->ReadFloat( fly_bob_strength );
564         savefile->ReadFloat( fly_bob_vert );
565         savefile->ReadFloat( fly_bob_horz );
566         savefile->ReadInt( fly_offset );
567         savefile->ReadFloat( fly_seek_scale );
568         savefile->ReadFloat( fly_roll_scale );
569         savefile->ReadFloat( fly_roll_max );
570         savefile->ReadFloat( fly_roll );
571         savefile->ReadFloat( fly_pitch_scale );
572         savefile->ReadFloat( fly_pitch_max );
573         savefile->ReadFloat( fly_pitch );
574
575         savefile->ReadBool( allowMove );
576         savefile->ReadBool( allowHiddenMovement );
577         savefile->ReadBool( disableGravity );
578         savefile->ReadBool( af_push_moveables );
579
580         savefile->ReadBool( lastHitCheckResult );
581         savefile->ReadInt( lastHitCheckTime );
582         savefile->ReadInt( lastAttackTime );
583         savefile->ReadFloat( melee_range );
584         savefile->ReadFloat( projectile_height_to_distance_ratio );
585
586         savefile->ReadInt( num );
587         missileLaunchOffset.SetGranularity( 1 );
588         missileLaunchOffset.SetNum( num );
589         for( i = 0; i < num; i++ ) {
590                 savefile->ReadVec3( missileLaunchOffset[ i ] );
591         }
592
593         idStr projectileName;
594         savefile->ReadString( projectileName );
595         if ( projectileName.Length() ) {
596                 projectileDef = gameLocal.FindEntityDefDict( projectileName );
597         } else {
598                 projectileDef = NULL;
599         }
600         savefile->ReadFloat( projectileRadius );
601         savefile->ReadFloat( projectileSpeed );
602         savefile->ReadVec3( projectileVelocity );
603         savefile->ReadVec3( projectileGravity );
604         projectile.Restore( savefile );
605         savefile->ReadString( attack );
606
607         savefile->ReadSoundShader( chat_snd );
608         savefile->ReadInt( chat_min );
609         savefile->ReadInt( chat_max );
610         savefile->ReadInt( chat_time );
611         savefile->ReadInt( i );
612         talk_state = static_cast<talkState_t>( i );
613         talkTarget.Restore( savefile );
614
615         savefile->ReadInt( num_cinematics );
616         savefile->ReadInt( current_cinematic );
617
618         savefile->ReadBool( allowJointMod );
619         focusEntity.Restore( savefile );
620         savefile->ReadVec3( currentFocusPos );
621         savefile->ReadInt( focusTime );
622         savefile->ReadInt( alignHeadTime );
623         savefile->ReadInt( forceAlignHeadTime );
624         savefile->ReadAngles( eyeAng );
625         savefile->ReadAngles( lookAng );
626         savefile->ReadAngles( destLookAng );
627         savefile->ReadAngles( lookMin );
628         savefile->ReadAngles( lookMax );
629
630         savefile->ReadInt( num );
631         lookJoints.SetGranularity( 1 );
632         lookJoints.SetNum( num );
633         lookJointAngles.SetGranularity( 1 );
634         lookJointAngles.SetNum( num );
635         for( i = 0; i < num; i++ ) {
636                 savefile->ReadJoint( lookJoints[ i ] );
637                 savefile->ReadAngles( lookJointAngles[ i ] );
638         }
639
640         savefile->ReadFloat( shrivel_rate );
641         savefile->ReadInt( shrivel_start );
642
643         savefile->ReadInt( num );
644         particles.SetNum( num );
645         for  ( i = 0; i < particles.Num(); i++ ) {
646                 savefile->ReadParticle( particles[i].particle );
647                 savefile->ReadInt( particles[i].time );
648                 savefile->ReadJoint( particles[i].joint );
649         }
650         savefile->ReadBool( restartParticles );
651         savefile->ReadBool( useBoneAxis );
652
653         enemy.Restore( savefile );
654         savefile->ReadVec3( lastVisibleEnemyPos );
655         savefile->ReadVec3( lastVisibleEnemyEyeOffset );
656         savefile->ReadVec3( lastVisibleReachableEnemyPos );
657         savefile->ReadVec3( lastReachableEnemyPos );
658
659         savefile->ReadBool( wakeOnFlashlight );
660
661         savefile->ReadAngles( eyeMin );
662         savefile->ReadAngles( eyeMax );
663
664         savefile->ReadFloat( eyeVerticalOffset );
665         savefile->ReadFloat( eyeHorizontalOffset );
666         savefile->ReadFloat( eyeFocusRate );
667         savefile->ReadFloat( headFocusRate );
668         savefile->ReadInt( focusAlignTime );
669
670         savefile->ReadJoint( flashJointWorld );
671         savefile->ReadInt( muzzleFlashEnd );
672
673         savefile->ReadJoint( focusJoint );
674         savefile->ReadJoint( orientationJoint );
675         savefile->ReadJoint( flyTiltJoint );
676
677         savefile->ReadBool( restorePhysics );
678
679         // Set the AAS if the character has the correct gravity vector
680         idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
681         gravity *= g_gravity.GetFloat();
682         if ( gravity == gameLocal.GetGravity() ) {
683                 SetAAS();
684         }
685
686         SetCombatModel();
687         LinkCombat();
688
689         InitMuzzleFlash();
690
691         // Link the script variables back to the scriptobject
692         LinkScriptVariables();
693
694         if ( restorePhysics ) {
695                 RestorePhysics( &physicsObj );
696         }
697 }
698
699 /*
700 =====================
701 idAI::Spawn
702 =====================
703 */
704 void idAI::Spawn( void ) {
705         const char                      *jointname;
706         const idKeyValue        *kv;
707         idStr                           jointName;
708         idAngles                        jointScale;
709         jointHandle_t           joint;
710         idVec3                          local_dir;
711         bool                            talks;
712
713         if ( !g_monsters.GetBool() ) {
714                 PostEventMS( &EV_Remove, 0 );
715                 return;
716         }
717
718         spawnArgs.GetInt(       "team",                                 "1",            team );
719         spawnArgs.GetInt(       "rank",                                 "0",            rank );
720         spawnArgs.GetInt(       "fly_offset",                   "0",            fly_offset );
721         spawnArgs.GetFloat( "fly_speed",                        "100",          fly_speed );
722         spawnArgs.GetFloat( "fly_bob_strength",         "50",           fly_bob_strength );
723         spawnArgs.GetFloat( "fly_bob_vert",                     "2",            fly_bob_horz );
724         spawnArgs.GetFloat( "fly_bob_horz",                     "2.7",          fly_bob_vert );
725         spawnArgs.GetFloat( "fly_seek_scale",           "4",            fly_seek_scale );
726         spawnArgs.GetFloat( "fly_roll_scale",           "90",           fly_roll_scale );
727         spawnArgs.GetFloat( "fly_roll_max",                     "60",           fly_roll_max );
728         spawnArgs.GetFloat( "fly_pitch_scale",          "45",           fly_pitch_scale );
729         spawnArgs.GetFloat( "fly_pitch_max",            "30",           fly_pitch_max );
730
731         spawnArgs.GetFloat( "melee_range",                      "64",           melee_range );
732         spawnArgs.GetFloat( "projectile_height_to_distance_ratio",      "1", projectile_height_to_distance_ratio );
733         
734         spawnArgs.GetFloat( "turn_rate",                        "360",          turnRate );
735
736         spawnArgs.GetBool( "talks",                                     "0",            talks );
737         if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) {
738                 if ( talks ) {
739                         talk_state = TALK_OK;
740                 } else {
741                         talk_state = TALK_BUSY;
742                 }
743         } else {
744                 talk_state = TALK_NEVER;
745         }
746
747         spawnArgs.GetBool( "animate_z",                         "0",            disableGravity );
748         spawnArgs.GetBool( "af_push_moveables",         "0",            af_push_moveables );
749         spawnArgs.GetFloat( "kick_force",                       "4096",         kickForce );
750         spawnArgs.GetBool( "ignore_obstacles",          "0",            ignore_obstacles );
751         spawnArgs.GetFloat( "blockedRadius",            "-1",           blockedRadius );
752         spawnArgs.GetInt( "blockedMoveTime",            "750",          blockedMoveTime );
753         spawnArgs.GetInt( "blockedAttackTime",          "750",          blockedAttackTime );
754
755         spawnArgs.GetInt(       "num_cinematics",               "0",            num_cinematics );
756         current_cinematic = 0;
757
758         LinkScriptVariables();
759
760         fl.takedamage           = !spawnArgs.GetBool( "noDamage" );
761         enemy                           = NULL;
762         allowMove                       = true;
763         allowHiddenMovement = false;
764
765         animator.RemoveOriginOffset( true );
766
767         // create combat collision hull for exact collision detection
768         SetCombatModel();
769
770         lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" );
771         lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" );
772
773         lookJoints.SetGranularity( 1 );
774         lookJointAngles.SetGranularity( 1 );
775         kv = spawnArgs.MatchPrefix( "look_joint", NULL );
776         while( kv ) {
777                 jointName = kv->GetKey();
778                 jointName.StripLeadingOnce( "look_joint " );
779                 joint = animator.GetJointHandle( jointName );
780                 if ( joint == INVALID_JOINT ) {
781                         gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() );
782                 } else {
783                         jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" );
784                         jointScale.roll = 0.0f;
785
786                         // if no scale on any component, then don't bother adding it.  this may be done to
787                         // zero out rotation from an inherited entitydef.
788                         if ( jointScale != ang_zero ) {
789                                 lookJoints.Append( joint );
790                                 lookJointAngles.Append( jointScale );
791                         }
792                 }
793                 kv = spawnArgs.MatchPrefix( "look_joint", kv );
794         }
795
796         // calculate joint positions on attack frames so we can do proper "can hit" tests
797         CalculateAttackOffsets();
798
799         eyeMin                          = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" );
800         eyeMax                          = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" );
801         eyeVerticalOffset       = spawnArgs.GetFloat( "eye_verticle_offset", "5" );
802         eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" );
803         eyeFocusRate            = spawnArgs.GetFloat( "eye_focus_rate", "0.5" );
804         headFocusRate           = spawnArgs.GetFloat( "head_focus_rate", "0.1" );
805         focusAlignTime          = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) );
806
807         flashJointWorld = animator.GetJointHandle( "flash" );
808
809         if ( head.GetEntity() ) {
810                 idAnimator *headAnimator = head.GetEntity()->GetAnimator();
811
812                 jointname = spawnArgs.GetString( "bone_focus" );
813                 if ( *jointname ) {
814                         focusJoint = headAnimator->GetJointHandle( jointname );
815                         if ( focusJoint == INVALID_JOINT ) {
816                                 gameLocal.Warning( "Joint '%s' not found on head on '%s'", jointname, name.c_str() );
817                         }
818                 }
819         } else {
820                 jointname = spawnArgs.GetString( "bone_focus" );
821                 if ( *jointname ) {
822                         focusJoint = animator.GetJointHandle( jointname );
823                         if ( focusJoint == INVALID_JOINT ) {
824                                 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
825                         }
826                 }
827         }
828
829         jointname = spawnArgs.GetString( "bone_orientation" );
830         if ( *jointname ) {
831                 orientationJoint = animator.GetJointHandle( jointname );
832                 if ( orientationJoint == INVALID_JOINT ) {
833                         gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
834                 }
835         }
836
837         jointname = spawnArgs.GetString( "bone_flytilt" );
838         if ( *jointname ) {
839                 flyTiltJoint = animator.GetJointHandle( jointname );
840                 if ( flyTiltJoint == INVALID_JOINT ) {
841                         gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
842                 }
843         }
844
845         InitMuzzleFlash();
846
847         physicsObj.SetSelf( this );
848         physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
849         physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
850
851         if ( spawnArgs.GetBool( "big_monster" ) ) {
852                 physicsObj.SetContents( 0 );
853                 physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY );
854         } else {
855                 if ( use_combat_bbox ) {
856                         physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
857                 } else {
858                         physicsObj.SetContents( CONTENTS_BODY );
859                 }
860                 physicsObj.SetClipMask( MASK_MONSTERSOLID );
861         }
862
863         // move up to make sure the monster is at least an epsilon above the floor
864         physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
865         
866         if ( num_cinematics ) {
867                 physicsObj.SetGravity( vec3_origin );
868         } else {
869                 idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
870                 gravity *= g_gravity.GetFloat();
871                 physicsObj.SetGravity( gravity );
872         }
873
874         SetPhysics( &physicsObj );
875
876         physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir );
877         current_yaw             = local_dir.ToYaw();
878         ideal_yaw               = idMath::AngleNormalize180( current_yaw );
879
880         move.blockTime = 0;
881
882         SetAAS();
883
884         projectile              = NULL;
885         projectileDef   = NULL;
886         projectileClipModel     = NULL;
887         idStr projectileName;
888         if ( spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) {
889                 projectileDef = gameLocal.FindEntityDefDict( projectileName );
890                 CreateProjectile( vec3_origin, viewAxis[ 0 ] );
891                 projectileRadius        = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius();
892                 projectileVelocity      = idProjectile::GetVelocity( projectileDef );
893                 projectileGravity       = idProjectile::GetGravity( projectileDef );
894                 projectileSpeed         = projectileVelocity.Length();
895                 delete projectile.GetEntity();
896                 projectile = NULL;
897         }
898
899         particles.Clear();
900         restartParticles = true;
901         useBoneAxis = spawnArgs.GetBool( "useBoneAxis" );
902         SpawnParticles( "smokeParticleSystem" );
903
904         if ( num_cinematics || spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "teleport" ) || spawnArgs.GetBool( "trigger_anim" ) ) {
905                 fl.takedamage = false;
906                 physicsObj.SetContents( 0 );
907                 physicsObj.GetClipModel()->Unlink();
908                 Hide();
909         } else {
910                 // play a looping ambient sound if we have one
911                 StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
912         }
913
914         if ( health <= 0 ) {
915                 gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() );
916                 health = 1;
917         }
918
919         // set up monster chatter
920         SetChatSound();
921
922         BecomeActive( TH_THINK );
923
924         if ( af_push_moveables ) {
925                 af.SetupPose( this, gameLocal.time );
926                 af.GetPhysics()->EnableClip();
927         }
928
929         // init the move variables
930         StopMove( MOVE_STATUS_DONE );
931 }
932
933 /*
934 ===================
935 idAI::InitMuzzleFlash
936 ===================
937 */
938 void idAI::InitMuzzleFlash( void ) {
939         const char                      *shader;
940         idVec3                          flashColor;
941
942         spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader );
943         spawnArgs.GetVector( "flashColor", "0 0 0", flashColor );
944         float flashRadius = spawnArgs.GetFloat( "flashRadius" );        
945         flashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) );
946
947         memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
948
949         worldMuzzleFlash.pointLight = true;
950         worldMuzzleFlash.shader = declManager->FindMaterial( shader, false );
951         worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0];
952         worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1];
953         worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2];
954         worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
955         worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
956         worldMuzzleFlash.lightRadius[0] = flashRadius;
957         worldMuzzleFlash.lightRadius[1] = flashRadius;
958         worldMuzzleFlash.lightRadius[2] = flashRadius;
959
960         worldMuzzleFlashHandle = -1;
961 }
962
963 /*
964 ===================
965 idAI::List_f
966 ===================
967 */
968 void idAI::List_f( const idCmdArgs &args ) {
969         int             e;
970         idAI    *check;
971         int             count;
972         const char *statename;
973
974         count = 0;
975
976         gameLocal.Printf( "%-4s  %-20s %s\n", " Num", "EntityDef", "Name" );
977         gameLocal.Printf( "------------------------------------------------\n" );
978         for( e = 0; e < MAX_GENTITIES; e++ ) {
979                 check = static_cast<idAI *>(gameLocal.entities[ e ]);
980                 if ( !check || !check->IsType( idAI::Type ) ) {
981                         continue;
982                 }
983
984                 if ( check->state ) {
985                         statename = check->state->Name();
986                 } else {
987                         statename = "NULL state";
988                 }
989
990                 gameLocal.Printf( "%4i: %-20s %-20s %s  move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove );
991                 count++;
992         }
993
994         gameLocal.Printf( "...%d monsters\n", count );
995 }
996
997 /*
998 ================
999 idAI::DormantBegin
1000
1001 called when entity becomes dormant
1002 ================
1003 */
1004 void idAI::DormantBegin( void ) {
1005         // since dormant happens on a timer, we wont get to update particles to
1006         // hidden through the think loop, but we need to hide them though.
1007         if ( particles.Num() ) {
1008                 for ( int i = 0; i < particles.Num(); i++ ) {
1009                         particles[i].time = 0;
1010                 }
1011         }
1012
1013         if ( enemyNode.InList() ) {
1014                 // remove ourselves from the enemy's enemylist
1015                 enemyNode.Remove();
1016         }
1017         idActor::DormantBegin();
1018 }
1019
1020 /*
1021 ================
1022 idAI::DormantEnd
1023
1024 called when entity wakes from being dormant
1025 ================
1026 */
1027 void idAI::DormantEnd( void ) {
1028         if ( enemy.GetEntity() && !enemyNode.InList() ) {
1029                 // let our enemy know we're back on the trail
1030                 enemyNode.AddToEnd( enemy.GetEntity()->enemyList );
1031         }
1032         
1033         if ( particles.Num() ) {
1034                 for ( int i = 0; i < particles.Num(); i++ ) {
1035                         particles[i].time = gameLocal.time;
1036                 }
1037         }
1038
1039         idActor::DormantEnd();
1040 }
1041
1042 /*
1043 =====================
1044 idAI::Think
1045 =====================
1046 */
1047 void idAI::Think( void ) {
1048         // if we are completely closed off from the player, don't do anything at all
1049         if ( CheckDormant() ) {
1050                 return;
1051         }
1052
1053         if ( thinkFlags & TH_THINK ) {
1054                 // clear out the enemy when he dies or is hidden
1055                 idActor *enemyEnt = enemy.GetEntity();
1056                 if ( enemyEnt ) {
1057                         if ( enemyEnt->health <= 0 ) {
1058                                 EnemyDead();
1059                         }
1060                 }
1061
1062                 current_yaw += deltaViewAngles.yaw;
1063                 ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
1064                 deltaViewAngles.Zero();
1065                 viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
1066
1067                 if ( num_cinematics ) {
1068                         if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) {
1069                                 PlayCinematic();
1070                         }
1071                         RunPhysics();
1072                 } else if ( !allowHiddenMovement && IsHidden() ) {
1073                         // hidden monsters
1074                         UpdateAIScript();
1075                 } else {
1076                         // clear the ik before we do anything else so the skeleton doesn't get updated twice
1077                         walkIK.ClearJointMods();
1078
1079                         switch( move.moveType ) {
1080                         case MOVETYPE_DEAD :
1081                                 // dead monsters
1082                                 UpdateAIScript();
1083                                 DeadMove();
1084                                 break;
1085
1086                         case MOVETYPE_FLY :
1087                                 // flying monsters
1088                                 UpdateEnemyPosition();
1089                                 UpdateAIScript();
1090                                 FlyMove();
1091                                 PlayChatter();
1092                                 CheckBlink();
1093                                 break;
1094
1095                         case MOVETYPE_STATIC :
1096                                 // static monsters
1097                                 UpdateEnemyPosition();
1098                                 UpdateAIScript();
1099                                 StaticMove();
1100                                 PlayChatter();
1101                                 CheckBlink();
1102                                 break;
1103
1104                         case MOVETYPE_ANIM :
1105                                 // animation based movement
1106                                 UpdateEnemyPosition();
1107                                 UpdateAIScript();
1108                                 AnimMove();
1109                                 PlayChatter();
1110                                 CheckBlink();
1111                                 break;
1112
1113                         case MOVETYPE_SLIDE :
1114                                 // velocity based movement
1115                                 UpdateEnemyPosition();
1116                                 UpdateAIScript();
1117                                 SlideMove();
1118                                 PlayChatter();
1119                                 CheckBlink();
1120                                 break;
1121                         }
1122                 }
1123
1124                 // clear pain flag so that we recieve any damage between now and the next time we run the script
1125                 AI_PAIN = false;
1126                 AI_SPECIAL_DAMAGE = 0;
1127                 AI_PUSHED = false;
1128         } else if ( thinkFlags & TH_PHYSICS ) {
1129                 RunPhysics();
1130         }
1131
1132         if ( af_push_moveables ) {
1133                 PushWithAF();
1134         }
1135
1136         if ( fl.hidden && allowHiddenMovement ) {
1137                 // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement
1138                 animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
1139         }
1140 /*      this still draws in retail builds.. not sure why.. don't care at this point.
1141         if ( !aas && developer.GetBool() && !fl.hidden && !num_cinematics ) {
1142                 gameRenderWorld->DrawText( "No AAS", physicsObj.GetAbsBounds().GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, gameLocal.msec );
1143         }
1144 */
1145
1146         UpdateMuzzleFlash();
1147         UpdateAnimation();
1148         UpdateParticles();
1149         Present();
1150         UpdateDamageEffects();
1151         LinkCombat();
1152 }
1153
1154 /***********************************************************************
1155
1156         AI script state management
1157
1158 ***********************************************************************/
1159
1160 /*
1161 =====================
1162 idAI::LinkScriptVariables
1163 =====================
1164 */
1165 void idAI::LinkScriptVariables( void ) {
1166         AI_TALK.LinkTo(                         scriptObject, "AI_TALK" );
1167         AI_DAMAGE.LinkTo(                       scriptObject, "AI_DAMAGE" );
1168         AI_PAIN.LinkTo(                         scriptObject, "AI_PAIN" );
1169         AI_SPECIAL_DAMAGE.LinkTo(       scriptObject, "AI_SPECIAL_DAMAGE" );
1170         AI_DEAD.LinkTo(                         scriptObject, "AI_DEAD" );
1171         AI_ENEMY_VISIBLE.LinkTo(        scriptObject, "AI_ENEMY_VISIBLE" );
1172         AI_ENEMY_IN_FOV.LinkTo(         scriptObject, "AI_ENEMY_IN_FOV" );
1173         AI_ENEMY_DEAD.LinkTo(           scriptObject, "AI_ENEMY_DEAD" );
1174         AI_MOVE_DONE.LinkTo(            scriptObject, "AI_MOVE_DONE" );
1175         AI_ONGROUND.LinkTo(                     scriptObject, "AI_ONGROUND" );
1176         AI_ACTIVATED.LinkTo(            scriptObject, "AI_ACTIVATED" );
1177         AI_FORWARD.LinkTo(                      scriptObject, "AI_FORWARD" );
1178         AI_JUMP.LinkTo(                         scriptObject, "AI_JUMP" );
1179         AI_BLOCKED.LinkTo(                      scriptObject, "AI_BLOCKED" );
1180         AI_DEST_UNREACHABLE.LinkTo( scriptObject, "AI_DEST_UNREACHABLE" );
1181         AI_HIT_ENEMY.LinkTo(            scriptObject, "AI_HIT_ENEMY" );
1182         AI_OBSTACLE_IN_PATH.LinkTo(     scriptObject, "AI_OBSTACLE_IN_PATH" );
1183         AI_PUSHED.LinkTo(                       scriptObject, "AI_PUSHED" );
1184 }
1185
1186 /*
1187 =====================
1188 idAI::UpdateAIScript
1189 =====================
1190 */
1191 void idAI::UpdateAIScript( void ) {
1192         UpdateScript();
1193
1194         // clear the hit enemy flag so we catch the next time we hit someone
1195         AI_HIT_ENEMY = false;
1196
1197         if ( allowHiddenMovement || !IsHidden() ) {
1198                 // update the animstate if we're not hidden
1199                 UpdateAnimState();
1200         }
1201 }
1202
1203 /***********************************************************************
1204
1205         navigation
1206
1207 ***********************************************************************/
1208
1209 /*
1210 ============
1211 idAI::KickObstacles
1212 ============
1213 */
1214 void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) {
1215         int i, numListedClipModels;
1216         idBounds clipBounds;
1217         idEntity *obEnt;
1218         idClipModel *clipModel;
1219         idClipModel *clipModelList[ MAX_GENTITIES ];
1220         int clipmask;
1221         idVec3 org;
1222         idVec3 forceVec;
1223         idVec3 delta;
1224         idVec2 perpendicular;
1225
1226         org = physicsObj.GetOrigin();
1227
1228         // find all possible obstacles
1229         clipBounds = physicsObj.GetAbsBounds();
1230         clipBounds.TranslateSelf( dir * 32.0f );
1231         clipBounds.ExpandSelf( 8.0f );
1232         clipBounds.AddPoint( org );
1233         clipmask = physicsObj.GetClipMask();
1234         numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipmask, clipModelList, MAX_GENTITIES );
1235         for ( i = 0; i < numListedClipModels; i++ ) {
1236                 clipModel = clipModelList[i];
1237                 obEnt = clipModel->GetEntity();
1238                 if ( obEnt == alwaysKick ) {
1239                         // we'll kick this one outside the loop
1240                         continue;
1241                 }
1242
1243                 if ( !clipModel->IsTraceModel() ) {
1244                         continue;
1245                 }
1246
1247                 if ( obEnt->IsType( idMoveable::Type ) && obEnt->GetPhysics()->IsPushable() ) {
1248                         delta = obEnt->GetPhysics()->GetOrigin() - org;
1249                         delta.NormalizeFast();
1250                         perpendicular.x = -delta.y;
1251                         perpendicular.y = delta.x;
1252                         delta.z += 0.5f;
1253                         delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
1254                         forceVec = delta * force * obEnt->GetPhysics()->GetMass();
1255                         obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec );
1256                 }
1257         }
1258
1259         if ( alwaysKick ) {
1260                 delta = alwaysKick->GetPhysics()->GetOrigin() - org;
1261                 delta.NormalizeFast();
1262                 perpendicular.x = -delta.y;
1263                 perpendicular.y = delta.x;
1264                 delta.z += 0.5f;
1265                 delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
1266                 forceVec = delta * force * alwaysKick->GetPhysics()->GetMass();
1267                 alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec );
1268         }
1269 }
1270
1271 /*
1272 ============
1273 ValidForBounds
1274 ============
1275 */
1276 bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) {
1277         int i;
1278
1279         for ( i = 0; i < 3; i++ ) {
1280                 if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) {
1281                         return false;
1282                 }
1283                 if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) {
1284                         return false;
1285                 }
1286         }
1287         return true;
1288 }
1289
1290 /*
1291 =====================
1292 idAI::SetAAS
1293 =====================
1294 */
1295 void idAI::SetAAS( void ) {
1296         idStr use_aas;
1297
1298         spawnArgs.GetString( "use_aas", NULL, use_aas );
1299         aas = gameLocal.GetAAS( use_aas );
1300         if ( aas ) {
1301                 const idAASSettings *settings = aas->GetSettings();
1302                 if ( settings ) {
1303                         if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) {
1304                                 gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() );
1305                         }
1306                         float height = settings->maxStepHeight;
1307                         physicsObj.SetMaxStepHeight( height );
1308                         return;
1309                 } else {
1310                         aas = NULL;
1311                 }
1312         }
1313         gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() );
1314 }
1315
1316 /*
1317 =====================
1318 idAI::DrawRoute
1319 =====================
1320 */
1321 void idAI::DrawRoute( void ) const {
1322         if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY && move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
1323                 if ( move.moveType == MOVETYPE_FLY ) {
1324                         aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
1325                 } else {
1326                         aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
1327                 }
1328         }
1329 }
1330
1331 /*
1332 =====================
1333 idAI::ReachedPos
1334 =====================
1335 */
1336 bool idAI::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
1337         if ( move.moveType == MOVETYPE_SLIDE ) {
1338                 idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
1339                 bnds.TranslateSelf( physicsObj.GetOrigin() );   
1340                 if ( bnds.ContainsPoint( pos ) ) {
1341                         return true;
1342                 }
1343         } else {
1344                 if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
1345                         if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
1346                                 return true;
1347                         }
1348                 } else {
1349                         idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 64.0f ) );
1350                         bnds.TranslateSelf( physicsObj.GetOrigin() );   
1351                         if ( bnds.ContainsPoint( pos ) ) {
1352                                 return true;
1353                         }
1354                 }
1355         }
1356         return false;
1357 }
1358
1359 /*
1360 =====================
1361 idAI::PointReachableAreaNum
1362 =====================
1363 */
1364 int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const {
1365         int areaNum;
1366         idVec3 size;
1367         idBounds bounds;
1368
1369         if ( !aas ) {
1370                 return 0;
1371         }
1372
1373         size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale;
1374         bounds[0] = -size;
1375         size.z = 32.0f;
1376         bounds[1] = size;
1377
1378         if ( move.moveType == MOVETYPE_FLY ) {
1379                 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY );
1380         } else {
1381                 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK );
1382         }
1383
1384         return areaNum;
1385 }
1386
1387 /*
1388 =====================
1389 idAI::PathToGoal
1390 =====================
1391 */
1392 bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const {
1393         idVec3 org;
1394         idVec3 goal;
1395
1396         if ( !aas ) {
1397                 return false;
1398         }
1399
1400         org = origin;
1401         aas->PushPointIntoAreaNum( areaNum, org );
1402         if ( !areaNum ) {
1403                 return false;
1404         }
1405
1406         goal = goalOrigin;
1407         aas->PushPointIntoAreaNum( goalAreaNum, goal );
1408         if ( !goalAreaNum ) {
1409                 return false;
1410         }
1411
1412         if ( move.moveType == MOVETYPE_FLY ) {
1413                 return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1414         } else {
1415                 return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1416         }
1417 }
1418
1419 /*
1420 =====================
1421 idAI::TravelDistance
1422
1423 Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance.
1424
1425 This is feakin' slow, so it's not good to do it too many times per frame.  It also is slower the further you
1426 are from the goal, so try to break the goals up into shorter distances.
1427 =====================
1428 */
1429 float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const {
1430         int                     fromArea;
1431         int                     toArea;
1432         float           dist;
1433         idVec2          delta;
1434         aasPath_t       path;
1435
1436         if ( !aas ) {
1437                 // no aas, so just take the straight line distance
1438                 delta = end.ToVec2() - start.ToVec2();
1439                 dist = delta.LengthFast();
1440
1441                 if ( ai_debugMove.GetBool() ) {
1442                         gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
1443                         gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
1444                 }
1445
1446                 return dist;
1447         }
1448
1449         fromArea = PointReachableAreaNum( start );
1450         toArea = PointReachableAreaNum( end );
1451
1452         if ( !fromArea || !toArea ) {
1453                 // can't seem to get there
1454                 return -1;
1455         }
1456
1457         if ( fromArea == toArea ) {
1458                 // same area, so just take the straight line distance
1459                 delta = end.ToVec2() - start.ToVec2();
1460                 dist = delta.LengthFast();
1461
1462                 if ( ai_debugMove.GetBool() ) {
1463                         gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
1464                         gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
1465                 }
1466
1467                 return dist;
1468         }
1469
1470         idReachability *reach;
1471         int travelTime;
1472         if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) {
1473                 return -1;
1474         }
1475
1476         if ( ai_debugMove.GetBool() ) {
1477                 if ( move.moveType == MOVETYPE_FLY ) {
1478                         aas->ShowFlyPath( start, toArea, end );
1479                 } else {
1480                         aas->ShowWalkPath( start, toArea, end );
1481                 }
1482         }
1483
1484         return travelTime;
1485 }
1486
1487 /*
1488 =====================
1489 idAI::StopMove
1490 =====================
1491 */
1492 void idAI::StopMove( moveStatus_t status ) {
1493         AI_MOVE_DONE            = true;
1494         AI_FORWARD                      = false;
1495         move.moveCommand        = MOVE_NONE;
1496         move.moveStatus         = status;
1497         move.toAreaNum          = 0;
1498         move.goalEntity         = NULL;
1499         move.moveDest           = physicsObj.GetOrigin();
1500         AI_DEST_UNREACHABLE     = false;
1501         AI_OBSTACLE_IN_PATH = false;
1502         AI_BLOCKED                      = false;
1503         move.startTime          = gameLocal.time;
1504         move.duration           = 0;
1505         move.range                      = 0.0f;
1506         move.speed                      = 0.0f;
1507         move.anim                       = 0;
1508         move.moveDir.Zero();
1509         move.lastMoveOrigin.Zero();
1510         move.lastMoveTime       = gameLocal.time;
1511 }
1512
1513 /*
1514 =====================
1515 idAI::FaceEnemy
1516
1517 Continually face the enemy's last known position.  MoveDone is always true in this case.
1518 =====================
1519 */
1520 bool idAI::FaceEnemy( void ) {
1521         idActor *enemyEnt = enemy.GetEntity();
1522         if ( !enemyEnt ) {
1523                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1524                 return false;
1525         }
1526
1527         TurnToward( lastVisibleEnemyPos );
1528         move.goalEntity         = enemyEnt;
1529         move.moveDest           = physicsObj.GetOrigin();
1530         move.moveCommand        = MOVE_FACE_ENEMY;
1531         move.moveStatus         = MOVE_STATUS_WAITING;
1532         move.startTime          = gameLocal.time;
1533         move.speed                      = 0.0f;
1534         AI_MOVE_DONE            = true;
1535         AI_FORWARD                      = false;
1536         AI_DEST_UNREACHABLE = false;
1537
1538         return true;
1539 }
1540
1541 /*
1542 =====================
1543 idAI::FaceEntity
1544
1545 Continually face the entity position.  MoveDone is always true in this case.
1546 =====================
1547 */
1548 bool idAI::FaceEntity( idEntity *ent ) {
1549         if ( !ent ) {
1550                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1551                 return false;
1552         }
1553
1554         idVec3 entityOrg = ent->GetPhysics()->GetOrigin();
1555         TurnToward( entityOrg );
1556         move.goalEntity         = ent;
1557         move.moveDest           = physicsObj.GetOrigin();
1558         move.moveCommand        = MOVE_FACE_ENTITY;
1559         move.moveStatus         = MOVE_STATUS_WAITING;
1560         move.startTime          = gameLocal.time;
1561         move.speed                      = 0.0f;
1562         AI_MOVE_DONE            = true;
1563         AI_FORWARD                      = false;
1564         AI_DEST_UNREACHABLE = false;
1565
1566         return true;
1567 }
1568
1569 /*
1570 =====================
1571 idAI::DirectMoveToPosition
1572 =====================
1573 */
1574 bool idAI::DirectMoveToPosition( const idVec3 &pos ) {
1575         if ( ReachedPos( pos, move.moveCommand ) ) {
1576                 StopMove( MOVE_STATUS_DONE );
1577                 return true;
1578         }
1579
1580         move.moveDest           = pos;
1581         move.goalEntity         = NULL;
1582         move.moveCommand        = MOVE_TO_POSITION_DIRECT;
1583         move.moveStatus         = MOVE_STATUS_MOVING;
1584         move.startTime          = gameLocal.time;
1585         move.speed                      = fly_speed;
1586         AI_MOVE_DONE            = false;
1587         AI_DEST_UNREACHABLE = false;
1588         AI_FORWARD                      = true;
1589
1590         if ( move.moveType == MOVETYPE_FLY ) {
1591                 idVec3 dir = pos - physicsObj.GetOrigin();
1592                 dir.Normalize();
1593                 dir *= fly_speed;
1594                 physicsObj.SetLinearVelocity( dir );
1595         }
1596
1597         return true;
1598 }
1599
1600 /*
1601 =====================
1602 idAI::MoveToEnemyHeight
1603 =====================
1604 */
1605 bool idAI::MoveToEnemyHeight( void ) {
1606         idActor *enemyEnt = enemy.GetEntity();
1607
1608         if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) {
1609                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1610                 return false;
1611         }
1612
1613         move.moveDest.z         = lastVisibleEnemyPos.z + enemyEnt->EyeOffset().z + fly_offset;
1614         move.goalEntity         = enemyEnt;
1615         move.moveCommand        = MOVE_TO_ENEMYHEIGHT;
1616         move.moveStatus         = MOVE_STATUS_MOVING;
1617         move.startTime          = gameLocal.time;
1618         move.speed                      = 0.0f;
1619         AI_MOVE_DONE            = false;
1620         AI_DEST_UNREACHABLE = false;
1621         AI_FORWARD                      = false;
1622
1623         return true;
1624 }
1625
1626 /*
1627 =====================
1628 idAI::MoveToEnemy
1629 =====================
1630 */
1631 bool idAI::MoveToEnemy( void ) {
1632         int                     areaNum;
1633         aasPath_t       path;
1634         idActor         *enemyEnt = enemy.GetEntity();
1635
1636         if ( !enemyEnt ) {
1637                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1638                 return false;
1639         }
1640
1641         if ( ReachedPos( lastVisibleReachableEnemyPos, MOVE_TO_ENEMY ) ) {
1642                 if ( !ReachedPos( lastVisibleEnemyPos, MOVE_TO_ENEMY ) || !AI_ENEMY_VISIBLE ) {
1643                         StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1644                         AI_DEST_UNREACHABLE = true;
1645                         return false;
1646                 }
1647                 StopMove( MOVE_STATUS_DONE );
1648                 return true;
1649         }
1650
1651         idVec3 pos = lastVisibleReachableEnemyPos;
1652
1653         move.toAreaNum = 0;
1654         if ( aas ) {
1655                 move.toAreaNum = PointReachableAreaNum( pos );
1656                 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1657
1658                 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1659                 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1660                         AI_DEST_UNREACHABLE = true;
1661                         return false;
1662                 }
1663         }
1664
1665         if ( !move.toAreaNum ) {
1666                 // if only trying to update the enemy position
1667                 if ( move.moveCommand == MOVE_TO_ENEMY ) {
1668                         if ( !aas ) {
1669                                 // keep the move destination up to date for wandering
1670                                 move.moveDest = pos;
1671                         }
1672                         return false;
1673                 }
1674
1675                 if ( !NewWanderDir( pos ) ) {
1676                         StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1677                         AI_DEST_UNREACHABLE = true;
1678                         return false;
1679                 }
1680         }
1681
1682         if ( move.moveCommand != MOVE_TO_ENEMY ) {
1683                 move.moveCommand        = MOVE_TO_ENEMY;
1684                 move.startTime          = gameLocal.time;
1685         }
1686
1687         move.moveDest           = pos;
1688         move.goalEntity         = enemyEnt;
1689         move.speed                      = fly_speed;
1690         move.moveStatus         = MOVE_STATUS_MOVING;
1691         AI_MOVE_DONE            = false;
1692         AI_DEST_UNREACHABLE = false;
1693         AI_FORWARD                      = true;
1694
1695         return true;
1696 }
1697
1698 /*
1699 =====================
1700 idAI::MoveToEntity
1701 =====================
1702 */
1703 bool idAI::MoveToEntity( idEntity *ent ) {
1704         int                     areaNum;
1705         aasPath_t       path;
1706         idVec3          pos;
1707
1708         if ( !ent ) {
1709                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1710                 return false;
1711         }
1712
1713         pos = ent->GetPhysics()->GetOrigin();
1714         if ( ( move.moveType != MOVETYPE_FLY ) && ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntityOrigin != pos ) ) ) {
1715                 ent->GetFloorPos( 64.0f, pos );
1716         }
1717
1718         if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) {
1719                 StopMove( MOVE_STATUS_DONE );
1720                 return true;
1721         }
1722
1723         move.toAreaNum = 0;
1724         if ( aas ) {
1725                 move.toAreaNum = PointReachableAreaNum( pos );
1726                 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1727
1728                 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1729                 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1730                         AI_DEST_UNREACHABLE = true;
1731                         return false;
1732                 }
1733         }
1734
1735         if ( !move.toAreaNum ) {
1736                 // if only trying to update the entity position
1737                 if ( move.moveCommand == MOVE_TO_ENTITY ) {
1738                         if ( !aas ) {
1739                                 // keep the move destination up to date for wandering
1740                                 move.moveDest = pos;
1741                         }
1742                         return false;
1743                 }
1744
1745                 if ( !NewWanderDir( pos ) ) {
1746                         StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1747                         AI_DEST_UNREACHABLE = true;
1748                         return false;
1749                 }
1750         }
1751
1752         if ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntity.GetEntity() != ent ) ) {
1753                 move.startTime          = gameLocal.time;
1754                 move.goalEntity         = ent;
1755                 move.moveCommand        = MOVE_TO_ENTITY;
1756         }
1757
1758         move.moveDest                   = pos;
1759         move.goalEntityOrigin   = ent->GetPhysics()->GetOrigin();
1760         move.moveStatus                 = MOVE_STATUS_MOVING;
1761         move.speed                              = fly_speed;
1762         AI_MOVE_DONE                    = false;
1763         AI_DEST_UNREACHABLE             = false;
1764         AI_FORWARD                              = true;
1765
1766         return true;
1767 }
1768
1769 /*
1770 =====================
1771 idAI::MoveOutOfRange
1772 =====================
1773 */
1774 bool idAI::MoveOutOfRange( idEntity *ent, float range ) {
1775         int                             areaNum;
1776         aasObstacle_t   obstacle;
1777         aasGoal_t               goal;
1778         idBounds                bounds;
1779         idVec3                  pos;
1780
1781         if ( !aas || !ent ) {
1782                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1783                 AI_DEST_UNREACHABLE = true;
1784                 return false;
1785         }
1786
1787         const idVec3 &org = physicsObj.GetOrigin();
1788         areaNum = PointReachableAreaNum( org );
1789
1790         // consider the entity the monster is getting close to as an obstacle
1791         obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1792
1793         if ( ent == enemy.GetEntity() ) {
1794                 pos = lastVisibleEnemyPos;
1795         } else {
1796                 pos = ent->GetPhysics()->GetOrigin();
1797         }
1798
1799         idAASFindAreaOutOfRange findGoal( pos, range );
1800         if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
1801                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1802                 AI_DEST_UNREACHABLE = true;
1803                 return false;
1804         }
1805
1806         if ( ReachedPos( goal.origin, move.moveCommand ) ) {
1807                 StopMove( MOVE_STATUS_DONE );
1808                 return true;
1809         }
1810
1811         move.moveDest           = goal.origin;
1812         move.toAreaNum          = goal.areaNum;
1813         move.goalEntity         = ent;
1814         move.moveCommand        = MOVE_OUT_OF_RANGE;
1815         move.moveStatus         = MOVE_STATUS_MOVING;
1816         move.range                      = range;
1817         move.speed                      = fly_speed;
1818         move.startTime          = gameLocal.time;
1819         AI_MOVE_DONE            = false;
1820         AI_DEST_UNREACHABLE = false;
1821         AI_FORWARD                      = true;
1822
1823         return true;
1824 }
1825
1826 /*
1827 =====================
1828 idAI::MoveToAttackPosition
1829 =====================
1830 */
1831 bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) {
1832         int                             areaNum;
1833         aasObstacle_t   obstacle;
1834         aasGoal_t               goal;
1835         idBounds                bounds;
1836         idVec3                  pos;
1837
1838         if ( !aas || !ent ) {
1839                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1840                 AI_DEST_UNREACHABLE = true;
1841                 return false;
1842         }
1843
1844         const idVec3 &org = physicsObj.GetOrigin();
1845         areaNum = PointReachableAreaNum( org );
1846
1847         // consider the entity the monster is getting close to as an obstacle
1848         obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1849
1850         if ( ent == enemy.GetEntity() ) {
1851                 pos = lastVisibleEnemyPos;
1852         } else {
1853                 pos = ent->GetPhysics()->GetOrigin();
1854         }
1855
1856         idAASFindAttackPosition findGoal( this, physicsObj.GetGravityAxis(), ent, pos, missileLaunchOffset[ attack_anim ] );
1857         if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
1858                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1859                 AI_DEST_UNREACHABLE = true;
1860                 return false;
1861         }
1862
1863         move.moveDest           = goal.origin;
1864         move.toAreaNum          = goal.areaNum;
1865         move.goalEntity         = ent;
1866         move.moveCommand        = MOVE_TO_ATTACK_POSITION;
1867         move.moveStatus         = MOVE_STATUS_MOVING;
1868         move.speed                      = fly_speed;
1869         move.startTime          = gameLocal.time;
1870         move.anim                       = attack_anim;
1871         AI_MOVE_DONE            = false;
1872         AI_DEST_UNREACHABLE = false;
1873         AI_FORWARD                      = true;
1874
1875         return true;
1876 }
1877
1878 /*
1879 =====================
1880 idAI::MoveToPosition
1881 =====================
1882 */
1883 bool idAI::MoveToPosition( const idVec3 &pos ) {
1884         idVec3          org;
1885         int                     areaNum;
1886         aasPath_t       path;
1887
1888         if ( ReachedPos( pos, move.moveCommand ) ) {
1889                 StopMove( MOVE_STATUS_DONE );
1890                 return true;
1891         }
1892
1893         org = pos;
1894         move.toAreaNum = 0;
1895         if ( aas ) {
1896                 move.toAreaNum = PointReachableAreaNum( org );
1897                 aas->PushPointIntoAreaNum( move.toAreaNum, org );
1898
1899                 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1900                 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, org ) ) {
1901                         StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1902                         AI_DEST_UNREACHABLE = true;
1903                         return false;
1904                 }
1905         }
1906
1907         if ( !move.toAreaNum && !NewWanderDir( org ) ) {
1908                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1909                 AI_DEST_UNREACHABLE = true;
1910                 return false;
1911         }
1912
1913         move.moveDest           = org;
1914         move.goalEntity         = NULL;
1915         move.moveCommand        = MOVE_TO_POSITION;
1916         move.moveStatus         = MOVE_STATUS_MOVING;
1917         move.startTime          = gameLocal.time;
1918         move.speed                      = fly_speed;
1919         AI_MOVE_DONE            = false;
1920         AI_DEST_UNREACHABLE = false;
1921         AI_FORWARD                      = true;
1922
1923         return true;
1924 }
1925
1926 /*
1927 =====================
1928 idAI::MoveToCover
1929 =====================
1930 */
1931 bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) {
1932         int                             areaNum;
1933         aasObstacle_t   obstacle;
1934         aasGoal_t               hideGoal;
1935         idBounds                bounds;
1936
1937         if ( !aas || !entity ) {
1938                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1939                 AI_DEST_UNREACHABLE = true;
1940                 return false;
1941         }
1942
1943         const idVec3 &org = physicsObj.GetOrigin();
1944         areaNum = PointReachableAreaNum( org );
1945
1946         // consider the entity the monster tries to hide from as an obstacle
1947         obstacle.absBounds = entity->GetPhysics()->GetAbsBounds();
1948
1949         idAASFindCover findCover( hideFromPos );
1950         if ( !aas->FindNearestGoal( hideGoal, areaNum, org, hideFromPos, travelFlags, &obstacle, 1, findCover ) ) {
1951                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1952                 AI_DEST_UNREACHABLE = true;
1953                 return false;
1954         }
1955
1956         if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) {
1957                 StopMove( MOVE_STATUS_DONE );
1958                 return true;
1959         }
1960
1961         move.moveDest           = hideGoal.origin;
1962         move.toAreaNum          = hideGoal.areaNum;
1963         move.goalEntity         = entity;
1964         move.moveCommand        = MOVE_TO_COVER;
1965         move.moveStatus         = MOVE_STATUS_MOVING;
1966         move.startTime          = gameLocal.time;
1967         move.speed                      = fly_speed;
1968         AI_MOVE_DONE            = false;
1969         AI_DEST_UNREACHABLE = false;
1970         AI_FORWARD                      = true;
1971
1972         return true;
1973 }
1974
1975 /*
1976 =====================
1977 idAI::SlideToPosition
1978 =====================
1979 */
1980 bool idAI::SlideToPosition( const idVec3 &pos, float time ) {
1981         StopMove( MOVE_STATUS_DONE );
1982
1983         move.moveDest           = pos;
1984         move.goalEntity         = NULL;
1985         move.moveCommand        = MOVE_SLIDE_TO_POSITION;
1986         move.moveStatus         = MOVE_STATUS_MOVING;
1987         move.startTime          = gameLocal.time;
1988         move.duration           = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
1989         AI_MOVE_DONE            = false;
1990         AI_DEST_UNREACHABLE = false;
1991         AI_FORWARD                      = false;
1992
1993         if ( move.duration > 0 ) {
1994                 move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration );
1995                 if ( move.moveType != MOVETYPE_FLY ) {
1996                         move.moveDir.z = 0.0f;
1997                 }
1998                 move.speed = move.moveDir.LengthFast();
1999         }
2000
2001         return true;
2002 }
2003
2004 /*
2005 =====================
2006 idAI::WanderAround
2007 =====================
2008 */
2009 bool idAI::WanderAround( void ) {
2010         StopMove( MOVE_STATUS_DONE );
2011         
2012         move.moveDest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
2013         if ( !NewWanderDir( move.moveDest ) ) {
2014                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2015                 AI_DEST_UNREACHABLE = true;
2016                 return false;
2017         }
2018
2019         move.moveCommand        = MOVE_WANDER;
2020         move.moveStatus         = MOVE_STATUS_MOVING;
2021         move.startTime          = gameLocal.time;
2022         move.speed                      = fly_speed;
2023         AI_MOVE_DONE            = false;
2024         AI_FORWARD                      = true;
2025
2026         return true;
2027 }
2028
2029 /*
2030 =====================
2031 idAI::MoveDone
2032 =====================
2033 */
2034 bool idAI::MoveDone( void ) const {
2035         return ( move.moveCommand == MOVE_NONE );
2036 }
2037
2038 /*
2039 ================
2040 idAI::StepDirection
2041 ================
2042 */
2043 bool idAI::StepDirection( float dir ) {
2044         predictedPath_t path;
2045         idVec3 org;
2046
2047         move.wanderYaw = dir;
2048         move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward();
2049
2050         org = physicsObj.GetOrigin();
2051
2052         idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
2053
2054         if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) {
2055                 // don't report being blocked if we ran into our goal entity
2056                 return true;
2057         }
2058
2059         if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) {
2060                 float z;
2061
2062                 move.moveDir = path.endVelocity * 1.0f / 48.0f;
2063
2064                 // trace down to the floor and see if we can go forward
2065                 idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path );
2066
2067                 idVec3 floorPos = path.endPos;
2068                 idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
2069                 if ( !path.endEvent ) {
2070                         move.moveDir.z = -1.0f;
2071                         return true;
2072                 }
2073
2074                 // trace up to see if we can go over something and go forward
2075                 idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path );
2076
2077                 idVec3 ceilingPos = path.endPos;
2078
2079                 for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) {
2080                         idVec3 start;
2081                         if ( z <= ceilingPos.z ) {
2082                                 start.x = org.x;
2083                                 start.y = org.y;
2084                 start.z = z;
2085                         } else {
2086                                 start = ceilingPos;
2087                         }
2088                         idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
2089                         if ( !path.endEvent ) {
2090                                 move.moveDir.z = 1.0f;
2091                                 return true;
2092                         }
2093                 }
2094                 return false;
2095         }
2096
2097         return ( path.endEvent == 0 );
2098 }
2099
2100 /*
2101 ================
2102 idAI::NewWanderDir
2103 ================
2104 */
2105 bool idAI::NewWanderDir( const idVec3 &dest ) {
2106         float   deltax, deltay;
2107         float   d[ 3 ];
2108         float   tdir, olddir, turnaround;
2109
2110         move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 );
2111
2112         olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 );
2113         turnaround = idMath::AngleNormalize360( olddir - 180 );
2114
2115         idVec3 org = physicsObj.GetOrigin();
2116         deltax = dest.x - org.x;
2117         deltay = dest.y - org.y;
2118         if ( deltax > 10 ) {
2119                 d[ 1 ]= 0;
2120         } else if ( deltax < -10 ) {
2121                 d[ 1 ] = 180;
2122         } else {
2123                 d[ 1 ] = DI_NODIR;
2124         }
2125
2126         if ( deltay < -10 ) {
2127                 d[ 2 ] = 270;
2128         } else if ( deltay > 10 ) {
2129                 d[ 2 ] = 90;
2130         } else {
2131                 d[ 2 ] = DI_NODIR;
2132         }
2133
2134         // try direct route
2135         if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) {
2136                 if ( d[ 1 ] == 0 ) {
2137                         tdir = d[ 2 ] == 90 ? 45 : 315;
2138                 } else {
2139                         tdir = d[ 2 ] == 90 ? 135 : 215;
2140                 }
2141
2142                 if ( tdir != turnaround && StepDirection( tdir ) ) {
2143                         return true;
2144                 }
2145         }
2146
2147         // try other directions
2148         if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) {
2149                 tdir = d[ 1 ];
2150                 d[ 1 ] = d[ 2 ];
2151                 d[ 2 ] = tdir;
2152         }
2153
2154         if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) {
2155                 return true;
2156         }
2157
2158         if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) {
2159                 return true;
2160         }
2161
2162         // there is no direct path to the player, so pick another direction
2163         if ( olddir != DI_NODIR && StepDirection( olddir ) ) {
2164                 return true;
2165         }
2166
2167          // randomly determine direction of search
2168         if ( gameLocal.random.RandomInt() & 1 ) {
2169                 for( tdir = 0; tdir <= 315; tdir += 45 ) {
2170                         if ( tdir != turnaround && StepDirection( tdir ) ) {
2171                 return true;
2172                         }
2173                 }
2174         } else {
2175                 for ( tdir = 315; tdir >= 0; tdir -= 45 ) {
2176                         if ( tdir != turnaround && StepDirection( tdir ) ) {
2177                                 return true;
2178                         }
2179                 }
2180         }
2181
2182         if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) {
2183                 return true;
2184         }
2185
2186         // can't move
2187         StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2188         return false;
2189 }
2190
2191 /*
2192 =====================
2193 idAI::GetMovePos
2194 =====================
2195 */
2196 bool idAI::GetMovePos( idVec3 &seekPos ) {
2197         int                     areaNum;
2198         aasPath_t       path;
2199         bool            result;
2200         idVec3          org;
2201
2202         org = physicsObj.GetOrigin();
2203         seekPos = org;
2204
2205         switch( move.moveCommand ) {
2206         case MOVE_NONE :
2207                 seekPos = move.moveDest;
2208                 return false;
2209                 break;
2210
2211         case MOVE_FACE_ENEMY :
2212         case MOVE_FACE_ENTITY :
2213                 seekPos = move.moveDest;
2214                 return false;
2215                 break;
2216
2217         case MOVE_TO_POSITION_DIRECT :
2218                 seekPos = move.moveDest;
2219                 if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2220                         StopMove( MOVE_STATUS_DONE );
2221                 }
2222                 return false;
2223                 break;
2224         
2225         case MOVE_SLIDE_TO_POSITION :
2226                 seekPos = org;
2227                 return false;
2228                 break;
2229         }
2230
2231         if ( move.moveCommand == MOVE_TO_ENTITY ) {
2232                 MoveToEntity( move.goalEntity.GetEntity() );
2233         }
2234
2235         move.moveStatus = MOVE_STATUS_MOVING;
2236         result = false;
2237         if ( gameLocal.time > move.blockTime ) {
2238                 if ( move.moveCommand == MOVE_WANDER ) {
2239                         move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
2240                 } else {
2241                         if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2242                                 StopMove( MOVE_STATUS_DONE );
2243                                 seekPos = org;
2244                                 return false;
2245                         }
2246                 }
2247
2248                 if ( aas && move.toAreaNum ) {
2249                         areaNum = PointReachableAreaNum( org );
2250                         if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) {
2251                                 seekPos = path.moveGoal;
2252                                 result = true;
2253                                 move.nextWanderTime = 0;
2254                         } else {
2255                                 AI_DEST_UNREACHABLE = true;
2256                         }
2257                 }
2258         }
2259
2260         if ( !result ) {
2261                 // wander around
2262                 if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) {
2263                         result = NewWanderDir( move.moveDest );
2264                         if ( !result ) {
2265                                 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2266                                 AI_DEST_UNREACHABLE = true;
2267                                 seekPos = org;
2268                                 return false;
2269                         }
2270                 } else {
2271                         result = true;
2272                 }
2273
2274                 seekPos = org + move.moveDir * 2048.0f;
2275                 if ( ai_debugMove.GetBool() ) {
2276                         gameRenderWorld->DebugLine( colorYellow, org, seekPos, gameLocal.msec, true );
2277                 }
2278         } else {
2279                 AI_DEST_UNREACHABLE = false;
2280         }
2281
2282         if ( result && ( ai_debugMove.GetBool() ) ) {
2283                 gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos );
2284         }
2285
2286         return result;
2287 }
2288
2289 /*
2290 =====================
2291 idAI::EntityCanSeePos
2292 =====================
2293 */
2294 bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) {
2295         idVec3 eye, point;
2296         trace_t results;
2297         pvsHandle_t handle;
2298
2299         handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() );
2300
2301         if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) {
2302                 gameLocal.pvs.FreeCurrentPVS( handle );
2303                 return false;
2304         }
2305
2306         gameLocal.pvs.FreeCurrentPVS( handle );
2307
2308         eye = actorOrigin + actor->EyeOffset();
2309
2310         point = pos;
2311         point[2] += 1.0f;
2312
2313         physicsObj.DisableClip();
2314
2315         gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2316         if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2317                 physicsObj.EnableClip();
2318                 return true;
2319         }
2320
2321         const idBounds &bounds = physicsObj.GetBounds();
2322         point[2] += bounds[1][2] - bounds[0][2];
2323
2324         gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2325         physicsObj.EnableClip();
2326         if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2327                 return true;
2328         }
2329         return false;
2330 }
2331
2332 /*
2333 =====================
2334 idAI::BlockedFailSafe
2335 =====================
2336 */
2337 void idAI::BlockedFailSafe( void ) {
2338         if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) {
2339                 return;
2340         }
2341         if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL ||
2342                         ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( blockedRadius ) ) {
2343                 move.lastMoveOrigin = physicsObj.GetOrigin();
2344                 move.lastMoveTime = gameLocal.time;
2345         }
2346         if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) {
2347                 if ( lastAttackTime < gameLocal.time - blockedAttackTime ) {
2348                         AI_BLOCKED = true;
2349                         move.lastMoveTime = gameLocal.time;
2350                 }
2351         }
2352 }
2353
2354 /***********************************************************************
2355
2356         turning
2357
2358 ***********************************************************************/
2359
2360 /*
2361 =====================
2362 idAI::Turn
2363 =====================
2364 */
2365 void idAI::Turn( void ) {
2366         float diff;
2367         float diff2;
2368         float turnAmount;
2369         animFlags_t animflags;
2370
2371         if ( !turnRate ) {
2372                 return;
2373         }
2374
2375         // check if the animator has marker this anim as non-turning
2376         if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) {
2377                 animflags = legsAnim.GetAnimFlags();
2378         } else {
2379                 animflags = torsoAnim.GetAnimFlags();
2380         }
2381         if ( animflags.ai_no_turn ) {
2382                 return;
2383         }
2384
2385         if ( anim_turn_angles && animflags.anim_turn ) {
2386                 idMat3 rotateAxis;
2387
2388                 // set the blend between no turn and full turn
2389                 float frac = anim_turn_amount / anim_turn_angles;
2390                 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac );
2391                 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac );
2392                 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac );
2393                 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac );
2394
2395                 // get the total rotation from the start of the anim
2396                 animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis );
2397                 current_yaw = idMath::AngleNormalize180( anim_turn_yaw + rotateAxis[ 0 ].ToYaw() );
2398         } else {
2399                 diff = idMath::AngleNormalize180( ideal_yaw - current_yaw );
2400                 turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec );
2401                 if ( turnVel > turnRate ) {
2402                         turnVel = turnRate;
2403                 } else if ( turnVel < -turnRate ) {
2404                         turnVel = -turnRate;
2405                 }
2406                 turnAmount = turnVel * MS2SEC( gameLocal.msec );
2407                 if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) {
2408                         turnVel = diff / MS2SEC( gameLocal.msec );
2409                         turnAmount = diff;
2410                 } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) {
2411                         turnVel = diff / MS2SEC( gameLocal.msec );
2412                         turnAmount = diff;
2413                 }
2414                 current_yaw += turnAmount;
2415                 current_yaw = idMath::AngleNormalize180( current_yaw );
2416                 diff2 = idMath::AngleNormalize180( ideal_yaw - current_yaw );
2417                 if ( idMath::Fabs( diff2 ) < 0.1f ) {
2418                         current_yaw = ideal_yaw;
2419                 }
2420         }
2421
2422         viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
2423
2424         if ( ai_debugMove.GetBool() ) {
2425                 const idVec3 &org = physicsObj.GetOrigin();
2426                 gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec );
2427                 gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, current_yaw, 0 ).ToForward() * 48, gameLocal.msec );
2428                 gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, current_yaw + turnVel, 0 ).ToForward() * 32, gameLocal.msec );
2429         }
2430 }
2431
2432 /*
2433 =====================
2434 idAI::FacingIdeal
2435 =====================
2436 */
2437 bool idAI::FacingIdeal( void ) {
2438         float diff;
2439
2440         if ( !turnRate ) {
2441                 return true;
2442         }
2443
2444         diff = idMath::AngleNormalize180( current_yaw - ideal_yaw );
2445         if ( idMath::Fabs( diff ) < 0.01f ) {
2446                 // force it to be exact
2447                 current_yaw = ideal_yaw;
2448                 return true;
2449         }
2450
2451         return false;
2452 }
2453
2454 /*
2455 =====================
2456 idAI::TurnToward
2457 =====================
2458 */
2459 bool idAI::TurnToward( float yaw ) {
2460         ideal_yaw = idMath::AngleNormalize180( yaw );
2461         bool result = FacingIdeal();
2462         return result;
2463 }
2464
2465 /*
2466 =====================
2467 idAI::TurnToward
2468 =====================
2469 */
2470 bool idAI::TurnToward( const idVec3 &pos ) {
2471         idVec3 dir;
2472         idVec3 local_dir;
2473         float lengthSqr;
2474
2475         dir = pos - physicsObj.GetOrigin();
2476         physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
2477         local_dir.z = 0.0f;
2478         lengthSqr = local_dir.LengthSqr();
2479         if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) {
2480                 ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
2481         }
2482
2483         bool result = FacingIdeal();
2484         return result;
2485 }
2486
2487 /***********************************************************************
2488
2489         Movement
2490
2491 ***********************************************************************/
2492
2493 /*
2494 ================
2495 idAI::ApplyImpulse
2496 ================
2497 */
2498 void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
2499         // FIXME: Jim take a look at this and see if this is a reasonable thing to do
2500         // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 )
2501         // and we don't want him taking physics impulses as it can knock him off the path
2502         if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) {
2503                 idActor::ApplyImpulse( ent, id, point, impulse );
2504         }
2505 }
2506
2507 /*
2508 =====================
2509 idAI::GetMoveDelta
2510 =====================
2511 */
2512 void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) {
2513         idVec3 oldModelOrigin;
2514         idVec3 modelOrigin;
2515
2516         animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
2517         delta = axis * delta;
2518
2519         if ( modelOffset != vec3_zero ) {
2520                 // the pivot of the monster's model is around its origin, and not around the bounding
2521                 // box's origin, so we have to compensate for this when the model is offset so that
2522                 // the monster still appears to rotate around it's origin.
2523                 oldModelOrigin = modelOffset * oldaxis;
2524                 modelOrigin = modelOffset * axis;
2525                 delta += oldModelOrigin - modelOrigin;
2526         }
2527
2528         delta *= physicsObj.GetGravityAxis();
2529 }
2530
2531 /*
2532 =====================
2533 idAI::CheckObstacleAvoidance
2534 =====================
2535 */
2536 void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) {
2537         idEntity                *obstacle;
2538         obstaclePath_t  path;
2539         idVec3                  dir;
2540         float                   dist;
2541         bool                    foundPath;
2542
2543         if ( ignore_obstacles ) {
2544                 newPos = goalPos;
2545                 move.obstacle = NULL;
2546                 return;
2547         }
2548
2549         const idVec3 &origin = physicsObj.GetOrigin();
2550
2551         obstacle = NULL;
2552         AI_OBSTACLE_IN_PATH = false;
2553         foundPath = FindPathAroundObstacles( &physicsObj, aas, enemy.GetEntity(), origin, goalPos, path );
2554         if ( ai_showObstacleAvoidance.GetBool() ) {
2555                 gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec );
2556                 gameRenderWorld->DebugLine( foundPath ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec );
2557         }
2558
2559         if ( !foundPath ) {
2560                 // couldn't get around obstacles
2561                 if ( path.firstObstacle ) {
2562                         AI_OBSTACLE_IN_PATH = true;
2563                         if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.firstObstacle->GetPhysics()->GetAbsBounds() ) ) {
2564                                 obstacle = path.firstObstacle;
2565                         }
2566                 } else if ( path.startPosObstacle ) {
2567                         AI_OBSTACLE_IN_PATH = true;
2568                         if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.startPosObstacle->GetPhysics()->GetAbsBounds() ) ) {
2569                                 obstacle = path.startPosObstacle;
2570                         }
2571                 } else {
2572                         // Blocked by wall
2573                         move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL;
2574                 }
2575 #if 0
2576         } else if ( path.startPosObstacle ) {
2577                 // check if we're past where the our origin was pushed out of the obstacle
2578                 dir = goalPos - origin;
2579                 dir.Normalize();
2580                 dist = ( path.seekPos - origin ) * dir;
2581                 if ( dist < 1.0f ) {
2582                         AI_OBSTACLE_IN_PATH = true;
2583                         obstacle = path.startPosObstacle;
2584                 }
2585 #endif
2586         } else if ( path.seekPosObstacle ) {
2587                 // if the AI is very close to the path.seekPos already and path.seekPosObstacle != NULL
2588                 // then we want to push the path.seekPosObstacle entity out of the way
2589                 AI_OBSTACLE_IN_PATH = true;
2590
2591                 // check if we're past where the goalPos was pushed out of the obstacle
2592                 dir = goalPos - origin;
2593                 dir.Normalize();
2594                 dist = ( path.seekPos - origin ) * dir;
2595                 if ( dist < 1.0f ) {
2596                         obstacle = path.seekPosObstacle;
2597                 }
2598         }
2599
2600         // if we had an obstacle, set our move status based on the type, and kick it out of the way if it's a moveable
2601         if ( obstacle ) {
2602                 if ( obstacle->IsType( idActor::Type ) ) {
2603                         // monsters aren't kickable
2604                         if ( obstacle == enemy.GetEntity() ) {
2605                                 move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY;
2606                         } else {
2607                                 move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER;
2608                         }
2609                 } else {
2610                         // try kicking the object out of the way
2611                         move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT;
2612                 }
2613                 newPos = obstacle->GetPhysics()->GetOrigin();
2614                 //newPos = path.seekPos;
2615                 move.obstacle = obstacle;
2616         } else {
2617                 newPos = path.seekPos;
2618                 move.obstacle = NULL;
2619         }
2620 }
2621
2622 /*
2623 =====================
2624 idAI::DeadMove
2625 =====================
2626 */
2627 void idAI::DeadMove( void ) {
2628         idVec3                          delta;
2629         monsterMoveResult_t     moveResult;
2630
2631         idVec3 org = physicsObj.GetOrigin();
2632
2633         GetMoveDelta( viewAxis, viewAxis, delta );
2634         physicsObj.SetDelta( delta );
2635
2636         RunPhysics();
2637
2638         moveResult = physicsObj.GetMoveResult();
2639         AI_ONGROUND = physicsObj.OnGround();
2640 }
2641
2642 /*
2643 =====================
2644 idAI::AnimMove
2645 =====================
2646 */
2647 void idAI::AnimMove( void ) {
2648         idVec3                          goalPos;
2649         idVec3                          delta;
2650         idVec3                          goalDelta;
2651         float                           goalDist;
2652         monsterMoveResult_t     moveResult;
2653         idVec3                          newDest;
2654
2655         idVec3 oldorigin = physicsObj.GetOrigin();
2656         idMat3 oldaxis = viewAxis;
2657
2658         AI_BLOCKED = false;
2659
2660         if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ 
2661                 move.lastMoveOrigin.Zero();
2662                 move.lastMoveTime = gameLocal.time;
2663         }
2664
2665         move.obstacle = NULL;
2666         if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2667                 TurnToward( lastVisibleEnemyPos );
2668                 goalPos = oldorigin;
2669         } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2670                 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2671                 goalPos = oldorigin;
2672         } else if ( GetMovePos( goalPos ) ) {
2673                 if ( move.moveCommand != MOVE_WANDER ) {
2674                         CheckObstacleAvoidance( goalPos, newDest );
2675                         TurnToward( newDest );
2676                 } else {
2677                         TurnToward( goalPos );
2678                 }
2679         }
2680
2681         Turn();
2682
2683         if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
2684                 if ( gameLocal.time < move.startTime + move.duration ) {
2685                         goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
2686                         delta = goalPos - oldorigin;
2687                         delta.z = 0.0f;
2688                 } else {
2689                         delta = move.moveDest - oldorigin;
2690                         delta.z = 0.0f;
2691                         StopMove( MOVE_STATUS_DONE );
2692                 }
2693         } else if ( allowMove ) {
2694                 GetMoveDelta( oldaxis, viewAxis, delta );
2695         } else {
2696                 delta.Zero();
2697         }
2698
2699         if ( move.moveCommand == MOVE_TO_POSITION ) {
2700                 goalDelta = move.moveDest - oldorigin;
2701                 goalDist = goalDelta.LengthFast();
2702                 if ( goalDist < delta.LengthFast() ) {
2703                         delta = goalDelta;
2704                 }
2705         }
2706
2707         physicsObj.SetDelta( delta );
2708         physicsObj.ForceDeltaMove( disableGravity );
2709
2710         RunPhysics();
2711
2712         if ( ai_debugMove.GetBool() ) {
2713                 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2714         }
2715
2716         moveResult = physicsObj.GetMoveResult();
2717         if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2718                 DirectDamage( attack, enemy.GetEntity() );
2719         } else {
2720                 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2721                 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2722                         KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2723                 }
2724         }
2725
2726         BlockedFailSafe();
2727
2728         AI_ONGROUND = physicsObj.OnGround();
2729
2730         idVec3 org = physicsObj.GetOrigin();
2731         if ( oldorigin != org ) {
2732                 TouchTriggers();
2733         }
2734
2735         if ( ai_debugMove.GetBool() ) {
2736                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
2737                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
2738                 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
2739                 DrawRoute();
2740         }
2741 }
2742
2743 /*
2744 =====================
2745 Seek
2746 =====================
2747 */
2748 idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) {
2749         idVec3 predictedPos;
2750         idVec3 goalDelta;
2751         idVec3 seekVel;
2752
2753         // predict our position
2754         predictedPos = org + vel * prediction;
2755         goalDelta = goal - predictedPos;
2756         seekVel = goalDelta * MS2SEC( gameLocal.msec );
2757
2758         return seekVel;
2759 }
2760
2761 /*
2762 =====================
2763 idAI::SlideMove
2764 =====================
2765 */
2766 void idAI::SlideMove( void ) {
2767         idVec3                          goalPos;
2768         idVec3                          delta;
2769         idVec3                          goalDelta;
2770         float                           goalDist;
2771         monsterMoveResult_t     moveResult;
2772         idVec3                          newDest;
2773
2774         idVec3 oldorigin = physicsObj.GetOrigin();
2775         idMat3 oldaxis = viewAxis;
2776
2777         AI_BLOCKED = false;
2778
2779         if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ 
2780                 move.lastMoveOrigin.Zero();
2781                 move.lastMoveTime = gameLocal.time;
2782         }
2783
2784         move.obstacle = NULL;
2785         if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2786                 TurnToward( lastVisibleEnemyPos );
2787                 goalPos = move.moveDest;
2788         } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2789                 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2790                 goalPos = move.moveDest;
2791         } else if ( GetMovePos( goalPos ) ) {
2792                 CheckObstacleAvoidance( goalPos, newDest );
2793                 TurnToward( newDest );
2794                 goalPos = newDest;
2795         }
2796
2797         if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
2798                 if ( gameLocal.time < move.startTime + move.duration ) {
2799                         goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
2800                 } else {
2801                         goalPos = move.moveDest;
2802                         StopMove( MOVE_STATUS_DONE );
2803                 }
2804         }
2805
2806         if ( move.moveCommand == MOVE_TO_POSITION ) {
2807                 goalDelta = move.moveDest - oldorigin;
2808                 goalDist = goalDelta.LengthFast();
2809                 if ( goalDist < delta.LengthFast() ) {
2810                         delta = goalDelta;
2811                 }
2812         }
2813
2814         idVec3 vel = physicsObj.GetLinearVelocity();
2815         float z = vel.z;
2816         idVec3  predictedPos = oldorigin + vel * AI_SEEK_PREDICTION;
2817
2818         // seek the goal position
2819         goalDelta = goalPos - predictedPos;
2820         vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
2821         vel += goalDelta * MS2SEC( gameLocal.msec );
2822
2823         // cap our speed
2824         vel.Truncate( fly_speed );
2825         vel.z = z;
2826         physicsObj.SetLinearVelocity( vel );
2827         physicsObj.UseVelocityMove( true );
2828         RunPhysics();
2829
2830         if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2831                 TurnToward( lastVisibleEnemyPos );
2832         } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2833                 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2834         } else if ( move.moveCommand != MOVE_NONE ) {
2835                 if ( vel.ToVec2().LengthSqr() > 0.1f ) {
2836                         TurnToward( vel.ToYaw() );
2837                 }
2838         }
2839         Turn();
2840
2841         if ( ai_debugMove.GetBool() ) {
2842                 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2843         }
2844
2845         moveResult = physicsObj.GetMoveResult();
2846         if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2847                 DirectDamage( attack, enemy.GetEntity() );
2848         } else {
2849                 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2850                 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2851                         KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2852                 }
2853         }
2854
2855         BlockedFailSafe();
2856
2857         AI_ONGROUND = physicsObj.OnGround();
2858
2859         idVec3 org = physicsObj.GetOrigin();
2860         if ( oldorigin != org ) {
2861                 TouchTriggers();
2862         }
2863
2864         if ( ai_debugMove.GetBool() ) {
2865                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
2866                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
2867                 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
2868                 DrawRoute();
2869         }
2870 }
2871
2872 /*
2873 =====================
2874 idAI::AdjustFlyingAngles
2875 =====================
2876 */
2877 void idAI::AdjustFlyingAngles( void ) {
2878         idVec3  vel;
2879         float   speed;
2880         float   roll;
2881         float   pitch;
2882
2883         vel = physicsObj.GetLinearVelocity();
2884
2885         speed = vel.Length();
2886         if ( speed < 5.0f ) {
2887                 roll = 0.0f;
2888                 pitch = 0.0f;
2889         } else {
2890                 roll = vel * viewAxis[ 1 ] * -fly_roll_scale / fly_speed;
2891                 if ( roll > fly_roll_max ) {
2892                         roll = fly_roll_max;
2893                 } else if ( roll < -fly_roll_max ) {
2894                         roll = -fly_roll_max;
2895                 }
2896
2897                 pitch = vel * viewAxis[ 2 ] * -fly_pitch_scale / fly_speed;
2898                 if ( pitch > fly_pitch_max ) {
2899                         pitch = fly_pitch_max;
2900                 } else if ( pitch < -fly_pitch_max ) {
2901                         pitch = -fly_pitch_max;
2902                 }
2903         }
2904
2905         fly_roll = fly_roll * 0.95f + roll * 0.05f;
2906         fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
2907
2908         if ( flyTiltJoint != INVALID_JOINT ) {
2909                 animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
2910         } else {
2911                 viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3();
2912         }
2913 }
2914
2915 /*
2916 =====================
2917 idAI::AddFlyBob
2918 =====================
2919 */
2920 void idAI::AddFlyBob( idVec3 &vel ) {
2921         idVec3  fly_bob_add;
2922         float   t;
2923
2924         if ( fly_bob_strength ) {
2925                 t = MS2SEC( gameLocal.time + entityNumber * 497 );
2926                 fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * fly_bob_vert ) ) * fly_bob_strength;
2927                 vel += fly_bob_add * MS2SEC( gameLocal.msec );
2928                 if ( ai_debugMove.GetBool() ) {
2929                         const idVec3 &origin = physicsObj.GetOrigin();
2930                         gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 );
2931                 }
2932         }
2933 }
2934
2935 /*
2936 =====================
2937 idAI::AdjustFlyHeight
2938 =====================
2939 */
2940 void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) {
2941         const idVec3    &origin = physicsObj.GetOrigin();
2942         predictedPath_t path;
2943         idVec3                  end;
2944         idVec3                  dest;
2945         trace_t                 trace;
2946         idActor                 *enemyEnt;
2947         bool                    goLower;
2948
2949         // make sure we're not flying too high to get through doors
2950         goLower = false;
2951         if ( origin.z > goalPos.z ) {
2952                 dest = goalPos;
2953                 dest.z = origin.z + 128.0f;
2954                 idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path );
2955                 if ( path.endPos.z < origin.z ) {
2956                         idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION );
2957                         vel.z += addVel.z;
2958                         goLower = true;
2959                 }
2960         
2961                 if ( ai_debugMove.GetBool() ) {
2962                         gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
2963                 }
2964         }
2965
2966         if ( !goLower ) {
2967                 // make sure we don't fly too low
2968                 end = origin;
2969
2970                 enemyEnt = enemy.GetEntity();
2971                 if ( enemyEnt ) {
2972                         end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset;
2973                 } else {
2974                         // just use the default eye height for the player
2975                         end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset;
2976                 }
2977
2978                 gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this );
2979                 vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION );
2980         }
2981 }
2982
2983 /*
2984 =====================
2985 idAI::FlySeekGoal
2986 =====================
2987 */
2988 void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) {
2989         idVec3 seekVel;
2990         
2991         // seek the goal position
2992         seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION );
2993         seekVel *= fly_seek_scale;
2994         vel += seekVel;
2995 }
2996
2997 /*
2998 =====================
2999 idAI::AdjustFlySpeed
3000 =====================
3001 */
3002 void idAI::AdjustFlySpeed( idVec3 &vel ) {
3003         float speed;
3004
3005         // apply dampening
3006         vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
3007
3008         // gradually speed up/slow down to desired speed
3009         speed = vel.Normalize();
3010         speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
3011         if ( speed < 0.0f ) {
3012                 speed = 0.0f;
3013         } else if ( move.speed && ( speed > move.speed ) ) {
3014                 speed = move.speed;
3015         }
3016
3017         vel *= speed;
3018 }
3019
3020 /*
3021 =====================
3022 idAI::FlyTurn
3023 =====================
3024 */
3025 void idAI::FlyTurn( void ) {
3026         if ( move.moveCommand == MOVE_FACE_ENEMY ) {
3027                 TurnToward( lastVisibleEnemyPos );
3028         } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
3029                 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
3030         } else if ( move.speed > 0.0f ) {
3031                 const idVec3 &vel = physicsObj.GetLinearVelocity();
3032                 if ( vel.ToVec2().LengthSqr() > 0.1f ) {
3033                         TurnToward( vel.ToYaw() );
3034                 }
3035         }
3036         Turn();
3037 }
3038
3039 /*
3040 =====================
3041 idAI::FlyMove
3042 =====================
3043 */
3044 void idAI::FlyMove( void ) {
3045         idVec3  goalPos;
3046         idVec3  oldorigin;
3047         idVec3  newDest;
3048
3049         AI_BLOCKED = false;
3050         if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
3051                 StopMove( MOVE_STATUS_DONE );
3052         }
3053
3054         if ( ai_debugMove.GetBool() ) {
3055                 gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), moveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, fly_speed );
3056         }
3057
3058         if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
3059                 idVec3 vel = physicsObj.GetLinearVelocity();
3060
3061                 if ( GetMovePos( goalPos ) ) {
3062                         CheckObstacleAvoidance( goalPos, newDest );
3063                         goalPos = newDest;
3064                 }
3065
3066                 if ( move.speed ) {
3067                         FlySeekGoal( vel, goalPos );
3068                 }
3069
3070                 // add in bobbing
3071                 AddFlyBob( vel );
3072
3073                 if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
3074                         AdjustFlyHeight( vel, goalPos );
3075                 }
3076
3077                 AdjustFlySpeed( vel );
3078
3079                 physicsObj.SetLinearVelocity( vel );
3080         }
3081
3082         // turn
3083         FlyTurn();
3084
3085         // run the physics for this frame
3086         oldorigin = physicsObj.GetOrigin();
3087         physicsObj.UseFlyMove( true );
3088         physicsObj.UseVelocityMove( false );
3089         physicsObj.SetDelta( vec3_zero );
3090         physicsObj.ForceDeltaMove( disableGravity );
3091         RunPhysics();
3092
3093         monsterMoveResult_t     moveResult = physicsObj.GetMoveResult();
3094         if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3095                 DirectDamage( attack, enemy.GetEntity() );
3096         } else {
3097                 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
3098                 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
3099                         KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
3100                 } else if ( moveResult == MM_BLOCKED ) {
3101                         move.blockTime = gameLocal.time + 500;
3102                         AI_BLOCKED = true;
3103                 }
3104         }
3105
3106         idVec3 org = physicsObj.GetOrigin();
3107         if ( oldorigin != org ) {
3108                 TouchTriggers();
3109         }
3110
3111         if ( ai_debugMove.GetBool() ) {
3112                 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
3113                 gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
3114                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
3115                 gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
3116                 gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
3117                 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
3118                 DrawRoute();
3119         }
3120 }
3121
3122 /*
3123 =====================
3124 idAI::StaticMove
3125 =====================
3126 */
3127 void idAI::StaticMove( void ) {
3128         idActor *enemyEnt = enemy.GetEntity();
3129
3130         if ( AI_DEAD ) {
3131                 return;
3132         }
3133
3134         if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemyEnt ) {
3135                 TurnToward( lastVisibleEnemyPos );
3136         } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
3137                 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
3138         } else if ( move.moveCommand != MOVE_NONE ) {
3139                 TurnToward( move.moveDest );
3140         }
3141         Turn();
3142
3143         physicsObj.ForceDeltaMove( true ); // disable gravity
3144         RunPhysics();
3145
3146         AI_ONGROUND = false;
3147
3148         if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3149                 DirectDamage( attack, enemyEnt );
3150         }
3151
3152         if ( ai_debugMove.GetBool() ) {
3153                 const idVec3 &org = physicsObj.GetOrigin();
3154                 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
3155                 gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true );
3156                 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
3157         }
3158 }
3159
3160 /***********************************************************************
3161
3162         Damage
3163
3164 ***********************************************************************/
3165
3166 /*
3167 =====================
3168 idAI::ReactionTo
3169 =====================
3170 */
3171 int idAI::ReactionTo( const idEntity *ent ) {
3172
3173         if ( ent->fl.hidden ) {
3174                 // ignore hidden entities
3175                 return ATTACK_IGNORE;
3176         }
3177
3178         if ( !ent->IsType( idActor::Type ) ) {
3179                 return ATTACK_IGNORE;
3180         }
3181
3182         const idActor *actor = static_cast<const idActor *>( ent );
3183         if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
3184                 // ignore players in noclip mode
3185                 return ATTACK_IGNORE;
3186         }
3187
3188         // actors on different teams will always fight each other
3189         if ( actor->team != team ) {
3190                 if ( actor->fl.notarget ) {
3191                         // don't attack on sight when attacker is notargeted
3192                         return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
3193                 }
3194                 return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
3195         }
3196
3197         // monsters will fight when attacked by lower ranked monsters.  rank 0 never fights back.
3198         if ( rank && ( actor->rank < rank ) ) {
3199                 return ATTACK_ON_DAMAGE;
3200         }
3201
3202         // don't fight back
3203         return ATTACK_IGNORE;
3204 }
3205
3206
3207 /*
3208 =====================
3209 idAI::Pain
3210 =====================
3211 */
3212 bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3213         idActor *actor;
3214
3215         AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location );
3216         AI_DAMAGE = true;
3217
3218         // force a blink
3219         blink_time = 0;
3220
3221         // ignore damage from self
3222         if ( attacker != this ) {
3223                 if ( inflictor ) {
3224                         AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3225                 } else {
3226                         AI_SPECIAL_DAMAGE = 0;
3227                 }
3228
3229                 if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) {
3230                         actor = ( idActor * )attacker;
3231                         if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) {
3232                                 gameLocal.AlertAI( actor );
3233                                 SetEnemy( actor );
3234                         }
3235                 }
3236         }
3237
3238         return ( AI_PAIN != 0 );
3239 }
3240
3241
3242 /*
3243 =====================
3244 idAI::SpawnParticles
3245 =====================
3246 */
3247 void idAI::SpawnParticles( const char *keyName ) {
3248         const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL );
3249         while ( kv ) {
3250                 particleEmitter_t pe;
3251
3252                 idStr particleName = kv->GetValue();
3253
3254                 if ( particleName.Length() ) {
3255
3256                         idStr jointName = kv->GetValue();
3257                         int dash = jointName.Find('-');
3258                         if ( dash > 0 ) {
3259                                 particleName = particleName.Left( dash );
3260                                 jointName = jointName.Right( jointName.Length() - dash - 1 );
3261                         }
3262
3263                         SpawnParticlesOnJoint( pe, particleName, jointName );
3264                         particles.Append( pe );
3265                 }
3266
3267                 kv = spawnArgs.MatchPrefix( keyName, kv );
3268         }
3269 }
3270
3271 /*
3272 =====================
3273 idAI::SpawnParticlesOnJoint
3274 =====================
3275 */
3276 const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) {
3277         idVec3 origin;
3278         idMat3 axis;
3279
3280         if ( *particleName == '\0' ) {
3281                 memset( &pe, 0, sizeof( pe ) );
3282                 return pe.particle;
3283         }
3284
3285         pe.joint = animator.GetJointHandle( jointName );
3286         if ( pe.joint == INVALID_JOINT ) {
3287                 gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() );
3288                 pe.time = 0;
3289                 pe.particle = NULL;
3290         } else {
3291                 animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis );
3292                 origin = renderEntity.origin + origin * renderEntity.axis;
3293
3294                 BecomeActive( TH_UPDATEPARTICLES );
3295                 if ( !gameLocal.time ) {
3296                         // particles with time of 0 don't show, so set the time differently on the first frame
3297                         pe.time = 1;
3298                 } else {
3299                         pe.time = gameLocal.time;
3300                 }
3301                 pe.particle = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, particleName ) );
3302                 gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis );
3303         }
3304
3305         return pe.particle;
3306 }
3307
3308 /*
3309 =====================
3310 idAI::Killed
3311 =====================
3312 */
3313 void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3314         idAngles ang;
3315         const char *modelDeath;
3316
3317         // make sure the monster is activated
3318         EndAttack();
3319
3320         if ( g_debugDamage.GetBool() ) {
3321                 gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), 
3322                         GetDamageGroup( location ) );
3323         }
3324
3325         if ( inflictor ) {
3326                 AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3327         } else {
3328                 AI_SPECIAL_DAMAGE = 0;
3329         }
3330
3331         if ( AI_DEAD ) {
3332                 AI_PAIN = true;
3333                 AI_DAMAGE = true;
3334                 return;
3335         }
3336
3337         // stop all voice sounds
3338         StopSound( SND_CHANNEL_VOICE, false );
3339         if ( head.GetEntity() ) {
3340                 head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false );
3341                 head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 );
3342         }
3343
3344         disableGravity = false;
3345         move.moveType = MOVETYPE_DEAD;
3346         af_push_moveables = false;
3347
3348         physicsObj.UseFlyMove( false );
3349         physicsObj.ForceDeltaMove( false );
3350
3351         // end our looping ambient sound
3352         StopSound( SND_CHANNEL_AMBIENT, false );
3353
3354         if ( attacker && attacker->IsType( idActor::Type ) ) {
3355                 gameLocal.AlertAI( ( idActor * )attacker );
3356         }
3357
3358         // activate targets
3359         ActivateTargets( attacker );
3360
3361         RemoveAttachments();
3362         RemoveProjectile();
3363         StopMove( MOVE_STATUS_DONE );
3364
3365         ClearEnemy();
3366         AI_DEAD = true;
3367
3368         // make monster nonsolid
3369         physicsObj.SetContents( 0 );
3370         physicsObj.GetClipModel()->Unlink();
3371
3372         Unbind();
3373
3374         if ( StartRagdoll() ) {
3375                 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
3376         }
3377
3378         if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) {
3379                 // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here
3380                 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
3381                 renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
3382                 SetModel( modelDeath );
3383                 physicsObj.SetLinearVelocity( vec3_zero );
3384                 physicsObj.PutToRest();
3385                 physicsObj.DisableImpact();
3386         }
3387
3388         restartParticles = false;
3389
3390         state = GetScriptFunction( "state_Killed" );
3391         SetState( state );
3392         SetWaitState( "" );
3393
3394         const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL );
3395         while( kv ) {
3396                 idDict args;
3397
3398                 args.Set( "classname", kv->GetValue() );
3399                 args.Set( "origin", physicsObj.GetOrigin().ToString() );
3400                 gameLocal.SpawnEntityDef( args );
3401                 kv = spawnArgs.MatchPrefix( "def_drops", kv );
3402         }
3403
3404         if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) {
3405                 static_cast< idPlayer* >( attacker )->AddAIKill();
3406         }
3407 }
3408
3409 /***********************************************************************
3410
3411         Targeting/Combat
3412
3413 ***********************************************************************/
3414
3415 /*
3416 =====================
3417 idAI::PlayCinematic
3418 =====================
3419 */
3420 void idAI::PlayCinematic( void ) {
3421         const char *animname;
3422
3423         if ( current_cinematic >= num_cinematics ) {
3424                 if ( g_debugCinematic.GetBool() ) {
3425                         gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
3426                 }
3427                 if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) {
3428                         Hide();
3429                 }
3430                 current_cinematic = 0;
3431                 ActivateTargets( gameLocal.GetLocalPlayer() );
3432                 fl.neverDormant = false;
3433                 return;
3434         }
3435
3436         Show();
3437         current_cinematic++;
3438
3439         allowJointMod = false;
3440         allowEyeFocus = false;
3441
3442         spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname );
3443         if ( !animname ) {
3444                 gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() );
3445                 return;
3446         }
3447
3448         if ( g_debugCinematic.GetBool() ) {
3449                 gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname );
3450         }
3451
3452         headAnim.animBlendFrames = 0;
3453         headAnim.lastAnimBlendFrames = 0;
3454         headAnim.BecomeIdle();
3455
3456         legsAnim.animBlendFrames = 0;
3457         legsAnim.lastAnimBlendFrames = 0;
3458         legsAnim.BecomeIdle();
3459
3460         torsoAnim.animBlendFrames = 0;
3461         torsoAnim.lastAnimBlendFrames = 0;
3462         ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname );
3463
3464         // make sure our model gets updated
3465         animator.ForceUpdate();
3466
3467         // update the anim bounds
3468         UpdateAnimation();
3469         UpdateVisuals();
3470         Present();
3471
3472         if ( head.GetEntity() ) {
3473                 // since the body anim was updated, we need to run physics to update the position of the head
3474                 RunPhysics();
3475
3476                 // make sure our model gets updated
3477                 head.GetEntity()->GetAnimator()->ForceUpdate();
3478
3479                 // update the anim bounds
3480                 head.GetEntity()->UpdateAnimation();
3481                 head.GetEntity()->UpdateVisuals();
3482                 head.GetEntity()->Present();
3483         }
3484
3485         fl.neverDormant = true;
3486 }
3487
3488 /*
3489 =====================
3490 idAI::Activate
3491
3492 Notifies the script that a monster has been activated by a trigger or flashlight
3493 =====================
3494 */
3495 void idAI::Activate( idEntity *activator ) {
3496         idPlayer *player;
3497
3498         if ( AI_DEAD ) {
3499                 // ignore it when they're dead
3500                 return;
3501         }
3502
3503         // make sure he's not dormant
3504         dormantStart = 0;
3505
3506         if ( num_cinematics ) {
3507                 PlayCinematic();
3508         } else {
3509                 AI_ACTIVATED = true;
3510                 if ( !activator || !activator->IsType( idPlayer::Type ) ) {
3511                         player = gameLocal.GetLocalPlayer();
3512                 } else {
3513                         player = static_cast<idPlayer *>( activator );
3514                 }
3515
3516                 if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) {
3517                         SetEnemy( player );
3518                 }
3519
3520                 // update the script in cinematics so that entities don't start anims or show themselves a frame late.
3521                 if ( cinematic ) {
3522             UpdateAIScript();
3523
3524                         // make sure our model gets updated
3525                         animator.ForceUpdate();
3526
3527                         // update the anim bounds
3528                         UpdateAnimation();
3529                         UpdateVisuals();
3530                         Present();
3531
3532                         if ( head.GetEntity() ) {
3533                                 // since the body anim was updated, we need to run physics to update the position of the head
3534                                 RunPhysics();
3535
3536                                 // make sure our model gets updated
3537                                 head.GetEntity()->GetAnimator()->ForceUpdate();
3538
3539                                 // update the anim bounds
3540                                 head.GetEntity()->UpdateAnimation();
3541                                 head.GetEntity()->UpdateVisuals();
3542                                 head.GetEntity()->Present();
3543                         }
3544                 }
3545         }
3546 }
3547
3548 /*
3549 =====================
3550 idAI::EnemyDead
3551 =====================
3552 */
3553 void idAI::EnemyDead( void ) {
3554         ClearEnemy();
3555         AI_ENEMY_DEAD = true;
3556 }
3557
3558 /*
3559 =====================
3560 idAI::TalkTo
3561 =====================
3562 */
3563 void idAI::TalkTo( idActor *actor ) {
3564         if ( talk_state != TALK_OK ) {
3565                 return;
3566         }
3567
3568         talkTarget = actor;
3569         if ( actor ) {
3570                 AI_TALK = true;
3571         } else {
3572                 AI_TALK = false;
3573         }
3574 }
3575
3576 /*
3577 =====================
3578 idAI::GetEnemy
3579 =====================
3580 */
3581 idActor *idAI::GetEnemy( void ) const {
3582         return enemy.GetEntity();
3583 }
3584
3585 /*
3586 =====================
3587 idAI::GetTalkState
3588 =====================
3589 */
3590 talkState_t idAI::GetTalkState( void ) const {
3591         if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) {
3592                 return TALK_DEAD;
3593         }
3594         if ( IsHidden() ) {
3595                 return TALK_NEVER;
3596         }
3597         return talk_state;
3598 }
3599
3600 /*
3601 =====================
3602 idAI::TouchedByFlashlight
3603 =====================
3604 */
3605 void idAI::TouchedByFlashlight( idActor *flashlight_owner ) {
3606         if ( wakeOnFlashlight ) {
3607                 Activate( flashlight_owner );
3608         }
3609 }
3610
3611 /*
3612 =====================
3613 idAI::ClearEnemy
3614 =====================
3615 */
3616 void idAI::ClearEnemy( void ) {
3617         if ( move.moveCommand == MOVE_TO_ENEMY ) {
3618                 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
3619         }
3620
3621         enemyNode.Remove();
3622         enemy                           = NULL;
3623         AI_ENEMY_IN_FOV         = false;
3624         AI_ENEMY_VISIBLE        = false;
3625         AI_ENEMY_DEAD           = true;
3626
3627         SetChatSound();
3628 }
3629
3630 /*
3631 =====================
3632 idAI::EnemyPositionValid
3633 =====================
3634 */
3635 bool idAI::EnemyPositionValid( void ) const {
3636         trace_t tr;
3637         idVec3  muzzle;
3638         idMat3  axis;
3639
3640         if ( !enemy.GetEntity() ) {
3641                 return false;
3642         }
3643
3644         if ( AI_ENEMY_VISIBLE ) {
3645                 return true;
3646         }
3647
3648         gameLocal.clip.TracePoint( tr, GetEyePosition(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, MASK_OPAQUE, this );
3649         if ( tr.fraction < 1.0f ) {
3650                 // can't see the area yet, so don't know if he's there or not
3651                 return true;
3652         }
3653
3654         return false;
3655 }
3656
3657 /*
3658 =====================
3659 idAI::SetEnemyPosition
3660 =====================
3661 */
3662 void idAI::SetEnemyPosition( void ) {
3663         idActor         *enemyEnt = enemy.GetEntity();
3664         int                     enemyAreaNum;
3665         int                     areaNum;
3666         int                     lastVisibleReachableEnemyAreaNum;
3667         aasPath_t       path;
3668         idVec3          pos;
3669         bool            onGround;
3670
3671         if ( !enemyEnt ) {
3672                 return;
3673         }
3674
3675         lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3676         lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset();
3677         lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin();
3678         if ( move.moveType == MOVETYPE_FLY ) {
3679                 pos = lastVisibleEnemyPos;
3680                 onGround = true;
3681         } else {
3682                 onGround = enemyEnt->GetFloorPos( 64.0f, pos );
3683                 if ( enemyEnt->OnLadder() ) {
3684                         onGround = false;
3685                 }
3686         }
3687
3688         if ( !onGround ) {
3689                 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3690                         AI_DEST_UNREACHABLE = true;
3691                 }
3692                 return;
3693         }
3694
3695         // when we don't have an AAS, we can't tell if an enemy is reachable or not,
3696         // so just assume that he is.
3697         if ( !aas ) {
3698                 lastVisibleReachableEnemyPos = lastVisibleEnemyPos;
3699                 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3700                         AI_DEST_UNREACHABLE = false;
3701                 }
3702                 enemyAreaNum = 0;
3703                 areaNum = 0;
3704         } else {
3705                 lastVisibleReachableEnemyAreaNum = move.toAreaNum;
3706                 enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f );
3707                 if ( !enemyAreaNum ) {
3708                         enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3709                         pos = lastReachableEnemyPos;
3710                 }
3711                 if ( !enemyAreaNum ) {
3712                         if ( move.moveCommand == MOVE_TO_ENEMY ) {
3713                                 AI_DEST_UNREACHABLE = true;
3714                         }
3715                         areaNum = 0;
3716                 } else {
3717                         const idVec3 &org = physicsObj.GetOrigin();
3718                         areaNum = PointReachableAreaNum( org );
3719                         if ( PathToGoal( path, areaNum, org, enemyAreaNum, pos ) ) {
3720                                 lastVisibleReachableEnemyPos = pos;
3721                                 lastVisibleReachableEnemyAreaNum = enemyAreaNum;
3722                                 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3723                                         AI_DEST_UNREACHABLE = false;
3724                                 }
3725                         } else if ( move.moveCommand == MOVE_TO_ENEMY ) {
3726                                 AI_DEST_UNREACHABLE = true;
3727                         }
3728                 }
3729         }
3730
3731         if ( move.moveCommand == MOVE_TO_ENEMY ) {
3732                 if ( !aas ) {
3733                         // keep the move destination up to date for wandering
3734                         move.moveDest = lastVisibleReachableEnemyPos;
3735                 } else if ( enemyAreaNum ) {
3736                         move.toAreaNum = lastVisibleReachableEnemyAreaNum;
3737                         move.moveDest = lastVisibleReachableEnemyPos;
3738                 }
3739
3740                 if ( move.moveType == MOVETYPE_FLY ) {
3741                         predictedPath_t path;
3742                         idVec3 end = move.moveDest;
3743                         end.z += enemyEnt->EyeOffset().z + fly_offset;
3744                         idAI::PredictPath( this, aas, move.moveDest, end - move.moveDest, 1000, 1000, SE_BLOCKED, path );
3745                         move.moveDest = path.endPos;
3746                         move.toAreaNum = PointReachableAreaNum( move.moveDest, 1.0f );
3747                 }
3748         }
3749 }
3750
3751 /*
3752 =====================
3753 idAI::UpdateEnemyPosition
3754 =====================
3755 */
3756 void idAI::UpdateEnemyPosition( void ) {
3757         idActor *enemyEnt = enemy.GetEntity();
3758         int                             enemyAreaNum;
3759         int                             areaNum;
3760         aasPath_t               path;
3761         predictedPath_t predictedPath;
3762         idVec3                  enemyPos;
3763         bool                    onGround;
3764
3765         if ( !enemyEnt ) {
3766                 return;
3767         }
3768
3769         const idVec3 &org = physicsObj.GetOrigin();
3770
3771         if ( move.moveType == MOVETYPE_FLY ) {
3772                 enemyPos = enemyEnt->GetPhysics()->GetOrigin();
3773                 onGround = true;
3774         } else {
3775                 onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
3776                 if ( enemyEnt->OnLadder() ) {
3777                         onGround = false;
3778                 }
3779         }
3780
3781         if ( onGround ) {
3782                 // when we don't have an AAS, we can't tell if an enemy is reachable or not,
3783                 // so just assume that he is.
3784                 if ( !aas ) {
3785                         enemyAreaNum = 0;
3786                         lastReachableEnemyPos = enemyPos;
3787                 } else {
3788                         enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
3789                         if ( enemyAreaNum ) {
3790                                 areaNum = PointReachableAreaNum( org );
3791                                 if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
3792                                         lastReachableEnemyPos = enemyPos;
3793                                 }
3794                         }
3795                 }
3796         }
3797
3798         AI_ENEMY_IN_FOV         = false;
3799         AI_ENEMY_VISIBLE        = false;
3800
3801         if ( CanSee( enemyEnt, false ) ) {
3802                 AI_ENEMY_VISIBLE = true;
3803                 if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
3804                         AI_ENEMY_IN_FOV = true;
3805                 }
3806
3807                 SetEnemyPosition();
3808         } else {
3809                 // check if we heard any sounds in the last frame
3810                 if ( enemyEnt == gameLocal.GetAlertEntity() ) {
3811                         float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr();
3812                         if ( dist < Square( AI_HEARING_RANGE ) ) {
3813                                 SetEnemyPosition();
3814                         }
3815                 }
3816         }
3817
3818         if ( ai_debugMove.GetBool() ) {
3819                 gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec );
3820                 gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec );
3821         }
3822 }
3823
3824 /*
3825 =====================
3826 idAI::SetEnemy
3827 =====================
3828 */
3829 void idAI::SetEnemy( idActor *newEnemy ) {
3830         int enemyAreaNum;
3831
3832         if ( AI_DEAD ) {
3833                 ClearEnemy();
3834                 return;
3835         }
3836
3837         AI_ENEMY_DEAD = false;
3838         if ( !newEnemy ) {
3839                 ClearEnemy();
3840         } else if ( enemy.GetEntity() != newEnemy ) {
3841                 enemy = newEnemy;
3842                 enemyNode.AddToEnd( newEnemy->enemyList );
3843                 if ( newEnemy->health <= 0 ) {
3844                         EnemyDead();
3845                         return;
3846                 }
3847                 // let the monster know where the enemy is
3848                 newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum );
3849                 SetEnemyPosition();
3850                 SetChatSound();
3851
3852                 lastReachableEnemyPos = lastVisibleEnemyPos;
3853                 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3854                 enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3855                 if ( aas && enemyAreaNum ) {
3856                         aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos );
3857                         lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3858                 }
3859         }
3860 }
3861
3862 /*
3863 ============
3864 idAI::FirstVisiblePointOnPath
3865 ============
3866 */
3867 idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const {
3868         int i, areaNum, targetAreaNum, curAreaNum, travelTime;
3869         idVec3 curOrigin;
3870         idReachability *reach;
3871
3872         if ( !aas ) {
3873                 return origin;
3874         }
3875
3876         areaNum = PointReachableAreaNum( origin );
3877         targetAreaNum = PointReachableAreaNum( target );
3878
3879         if ( !areaNum || !targetAreaNum ) {
3880                 return origin;
3881         }
3882
3883         if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) {
3884                 return origin;
3885         }
3886
3887         curAreaNum = areaNum;
3888         curOrigin = origin;
3889
3890         for( i = 0; i < 10; i++ ) {
3891
3892                 if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) {
3893                         break;
3894                 }
3895
3896                 if ( !reach ) {
3897                         return target;
3898                 }
3899
3900                 curAreaNum = reach->toAreaNum;
3901                 curOrigin = reach->end;
3902
3903                 if ( PointVisible( curOrigin ) ) {
3904                         return curOrigin;
3905                 }
3906         }
3907
3908         return origin;
3909 }
3910
3911 /*
3912 ===================
3913 idAI::CalculateAttackOffsets
3914
3915 calculate joint positions on attack frames so we can do proper "can hit" tests
3916 ===================
3917 */
3918 void idAI::CalculateAttackOffsets( void ) {
3919         const idDeclModelDef    *modelDef;
3920         int                                             num;
3921         int                                             i;
3922         int                                             frame;
3923         const frameCommand_t    *command;
3924         idMat3                                  axis;
3925         const idAnim                    *anim;
3926         jointHandle_t                   joint;
3927
3928         modelDef = animator.ModelDef();
3929         if ( !modelDef ) {
3930                 return;
3931         }
3932         num = modelDef->NumAnims();
3933         
3934         // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim
3935         animator.RemoveOriginOffset( false );
3936
3937         // anim number 0 is reserved for non-existant anims.  to avoid off by one issues, just allocate an extra spot for
3938         // launch offsets so that anim number can be used without subtracting 1.
3939         missileLaunchOffset.SetGranularity( 1 );
3940         missileLaunchOffset.SetNum( num + 1 );
3941         missileLaunchOffset[ 0 ].Zero();
3942
3943         for( i = 1; i <= num; i++ ) {
3944                 missileLaunchOffset[ i ].Zero();
3945                 anim = modelDef->GetAnim( i );
3946                 if ( anim ) {
3947                         frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
3948                         if ( frame >= 0 ) {
3949                                 joint = animator.GetJointHandle( command->string->c_str() );
3950                                 if ( joint == INVALID_JOINT ) {
3951                                         gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() );
3952                                 }
3953                                 GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
3954                         }
3955                 }
3956         }
3957
3958         animator.RemoveOriginOffset( true );
3959 }
3960
3961 /*
3962 =====================
3963 idAI::CreateProjectileClipModel
3964 =====================
3965 */
3966 void idAI::CreateProjectileClipModel( void ) const {
3967         if ( projectileClipModel == NULL ) {
3968                 idBounds projectileBounds( vec3_origin );
3969                 projectileBounds.ExpandSelf( projectileRadius );
3970                 projectileClipModel     = new idClipModel( idTraceModel( projectileBounds ) );
3971         }
3972 }
3973
3974 /*
3975 =====================
3976 idAI::GetAimDir
3977 =====================
3978 */
3979 bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
3980         idVec3  targetPos1;
3981         idVec3  targetPos2;
3982         idVec3  delta;
3983         float   max_height;
3984         bool    result;
3985
3986         // if no aimAtEnt or projectile set
3987         if ( !aimAtEnt || !projectileDef ) {
3988                 aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
3989                 return false;
3990         }
3991
3992         if ( projectileClipModel == NULL ) {
3993                 CreateProjectileClipModel();
3994         }
3995
3996         if ( aimAtEnt == enemy.GetEntity() ) {
3997                 static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 );
3998         } else if ( aimAtEnt->IsType( idActor::Type ) ) {
3999                 static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 );
4000         } else {
4001                 targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
4002                 targetPos2 = targetPos1;
4003         }
4004
4005         // try aiming for chest
4006         delta = firePos - targetPos1;
4007         max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
4008         result = PredictTrajectory( firePos, targetPos1, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
4009         if ( result || !aimAtEnt->IsType( idActor::Type ) ) {
4010                 return result;
4011         }
4012
4013         // try aiming for head
4014         delta = firePos - targetPos2;
4015         max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
4016         result = PredictTrajectory( firePos, targetPos2, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
4017
4018         return result;
4019 }
4020
4021 /*
4022 =====================
4023 idAI::BeginAttack
4024 =====================
4025 */
4026 void idAI::BeginAttack( const char *name ) {
4027         attack = name;
4028         lastAttackTime = gameLocal.time;
4029 }
4030
4031 /*
4032 =====================
4033 idAI::EndAttack
4034 =====================
4035 */
4036 void idAI::EndAttack( void ) {
4037         attack = "";
4038 }
4039
4040 /*
4041 =====================
4042 idAI::CreateProjectile
4043 =====================
4044 */
4045 idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) {
4046         idEntity *ent;
4047         const char *clsname;
4048
4049         if ( !projectile.GetEntity() ) {
4050                 gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
4051                 if ( !ent ) {
4052                         clsname = projectileDef->GetString( "classname" );
4053                         gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
4054                 }
4055                 
4056                 if ( !ent->IsType( idProjectile::Type ) ) {
4057                         clsname = ent->GetClassname();
4058                         gameLocal.Error( "'%s' is not an idProjectile", clsname );
4059                 }
4060                 projectile = ( idProjectile * )ent;
4061         }
4062
4063         projectile.GetEntity()->Create( this, pos, dir );
4064
4065         return projectile.GetEntity();
4066 }
4067
4068 /*
4069 =====================
4070 idAI::RemoveProjectile
4071 =====================
4072 */
4073 void idAI::RemoveProjectile( void ) {
4074         if ( projectile.GetEntity() ) {
4075                 projectile.GetEntity()->PostEventMS( &EV_Remove, 0 );
4076                 projectile = NULL;
4077         }
4078 }
4079
4080 /*
4081 =====================
4082 idAI::LaunchProjectile
4083 =====================
4084 */
4085 idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
4086         idVec3                          muzzle;
4087         idVec3                          dir;
4088         idVec3                          start;
4089         trace_t                         tr;
4090         idBounds                        projBounds;
4091         float                           distance;
4092         const idClipModel       *projClip;
4093         float                           attack_accuracy;
4094         float                           attack_cone;
4095         float                           projectile_spread;
4096         float                           diff;
4097         float                           angle;
4098         float                           spin;
4099         idAngles                        ang;
4100         int                                     num_projectiles;
4101         int                                     i;
4102         idMat3                          axis;
4103         idVec3                          tmp;
4104         idProjectile            *lastProjectile;
4105
4106         if ( !projectileDef ) {
4107                 gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
4108                 return NULL;
4109         }
4110
4111         attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
4112         attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
4113         projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
4114         num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
4115
4116         GetMuzzle( jointname, muzzle, axis );
4117
4118         if ( !projectile.GetEntity() ) {
4119                 CreateProjectile( muzzle, axis[ 0 ] );
4120         }
4121
4122         lastProjectile = projectile.GetEntity();
4123
4124         if ( target != NULL ) {
4125                 tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
4126                 tmp.Normalize();
4127                 axis = tmp.ToMat3();
4128         } else {
4129                 axis = viewAxis;
4130         }
4131
4132         // rotate it because the cone points up by default
4133         tmp = axis[2];
4134         axis[2] = axis[0];
4135         axis[0] = -tmp;
4136
4137         // make sure the projectile starts inside the monster bounding box
4138         const idBounds &ownerBounds = physicsObj.GetAbsBounds();
4139         projClip = lastProjectile->GetPhysics()->GetClipModel();
4140         projBounds = projClip->GetBounds().Rotate( axis );
4141
4142         // check if the owner bounds is bigger than the projectile bounds
4143         if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
4144                 ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
4145                 ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
4146                 if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
4147                         start = muzzle + distance * viewAxis[ 0 ];
4148                 } else {
4149                         start = ownerBounds.GetCenter();
4150                 }
4151         } else {
4152                 // projectile bounds bigger than the owner bounds, so just start it from the center
4153                 start = ownerBounds.GetCenter();
4154         }
4155
4156         gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
4157         muzzle = tr.endpos;
4158
4159         // set aiming direction
4160         GetAimDir( muzzle, target, this, dir );
4161         ang = dir.ToAngles();
4162
4163         // adjust his aim so it's not perfect.  uses sine based movement so the tracers appear less random in their spread.
4164         float t = MS2SEC( gameLocal.time + entityNumber * 497 );
4165         ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
4166         ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
4167
4168         if ( clampToAttackCone ) {
4169                 // clamp the attack direction to be within monster's attack cone so he doesn't do
4170                 // things like throw the missile backwards if you're behind him
4171                 diff = idMath::AngleDelta( ang.yaw, current_yaw );
4172                 if ( diff > attack_cone ) {
4173                         ang.yaw = current_yaw + attack_cone;
4174                 } else if ( diff < -attack_cone ) {
4175                         ang.yaw = current_yaw - attack_cone;
4176                 }
4177         }
4178
4179         axis = ang.ToMat3();
4180
4181         float spreadRad = DEG2RAD( projectile_spread );
4182         for( i = 0; i < num_projectiles; i++ ) {
4183                 // spread the projectiles out
4184                 angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
4185                 spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
4186                 dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
4187                 dir.Normalize();
4188
4189                 // launch the projectile
4190                 if ( !projectile.GetEntity() ) {
4191                         CreateProjectile( muzzle, dir );
4192                 }
4193                 lastProjectile = projectile.GetEntity();
4194                 lastProjectile->Launch( muzzle, dir, vec3_origin );
4195                 projectile = NULL;
4196         }
4197
4198         TriggerWeaponEffects( muzzle );
4199
4200         lastAttackTime = gameLocal.time;
4201
4202         return lastProjectile;
4203 }
4204
4205 /*
4206 ================
4207 idAI::DamageFeedback
4208
4209 callback function for when another entity received damage from this entity.  damage can be adjusted and returned to the caller.
4210
4211 FIXME: This gets called when we call idPlayer::CalcDamagePoints from idAI::AttackMelee, which then checks for a saving throw,
4212 possibly forcing a miss.  This is harmless behavior ATM, but is not intuitive.
4213 ================
4214 */
4215 void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
4216         if ( ( victim == this ) && inflictor->IsType( idProjectile::Type ) ) {
4217                 // monsters only get half damage from their own projectiles
4218                 damage = ( damage + 1 ) / 2;  // round up so we don't do 0 damage
4219
4220         } else if ( victim == enemy.GetEntity() ) {
4221                 AI_HIT_ENEMY = true;
4222         }
4223 }
4224
4225 /*
4226 =====================
4227 idAI::DirectDamage
4228
4229 Causes direct damage to an entity
4230
4231 kickDir is specified in the monster's coordinate system, and gives the direction
4232 that the view kick and knockback should go
4233 =====================
4234 */
4235 void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) {
4236         const idDict *meleeDef;
4237         const char *p;
4238         const idSoundShader *shader;
4239
4240         meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4241         if ( !meleeDef ) {
4242                 gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() );
4243         }
4244
4245         if ( !ent->fl.takedamage ) {
4246                 const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" ));
4247                 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4248                 return;
4249         }
4250
4251         //
4252         // do the damage
4253         //
4254         p = meleeDef->GetString( "snd_hit" );
4255         if ( p && *p ) {
4256                 shader = declManager->FindSound( p );
4257                 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4258         }
4259
4260         idVec3  kickDir;
4261         meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4262
4263         idVec3  globalKickDir;
4264         globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4265
4266         ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4267
4268         // end the attack if we're a multiframe attack
4269         EndAttack();
4270 }
4271
4272 /*
4273 =====================
4274 idAI::TestMelee
4275 =====================
4276 */
4277 bool idAI::TestMelee( void ) const {
4278         trace_t trace;
4279         idActor *enemyEnt = enemy.GetEntity();
4280
4281         if ( !enemyEnt || !melee_range ) {
4282                 return false;
4283         }
4284
4285         //FIXME: make work with gravity vector
4286         idVec3 org = physicsObj.GetOrigin();
4287         const idBounds &myBounds = physicsObj.GetBounds();
4288         idBounds bounds;
4289
4290         // expand the bounds out by our melee range
4291         bounds[0][0] = -melee_range;
4292         bounds[0][1] = -melee_range;
4293         bounds[0][2] = myBounds[0][2] - 4.0f;
4294         bounds[1][0] = melee_range;
4295         bounds[1][1] = melee_range;
4296         bounds[1][2] = myBounds[1][2] + 4.0f;
4297         bounds.TranslateSelf( org );
4298
4299         idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
4300         idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds();
4301         enemyBounds.TranslateSelf( enemyOrg );
4302
4303         if ( ai_debugMove.GetBool() ) {
4304                 gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec );
4305         }
4306
4307         if ( !bounds.IntersectsBounds( enemyBounds ) ) {
4308                 return false;
4309         }
4310
4311         idVec3 start = GetEyePosition();
4312         idVec3 end = enemyEnt->GetEyePosition();
4313
4314         gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4315         if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) {
4316                 return true;
4317         }
4318
4319         return false;
4320 }
4321
4322 /*
4323 =====================
4324 idAI::AttackMelee
4325
4326 jointname allows the endpoint to be exactly specified in the model,
4327 as for the commando tentacle.  If not specified, it will be set to
4328 the facing direction + melee_range.
4329
4330 kickDir is specified in the monster's coordinate system, and gives the direction
4331 that the view kick and knockback should go
4332 =====================
4333 */
4334 bool idAI::AttackMelee( const char *meleeDefName ) {
4335         const idDict *meleeDef;
4336         idActor *enemyEnt = enemy.GetEntity();
4337         const char *p;
4338         const idSoundShader *shader;
4339
4340         meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4341         if ( !meleeDef ) {
4342                 gameLocal.Error( "Unknown melee '%s'", meleeDefName );
4343         }
4344
4345         if ( !enemyEnt ) {
4346                 p = meleeDef->GetString( "snd_miss" );
4347                 if ( p && *p ) {
4348                         shader = declManager->FindSound( p );
4349                         StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4350                 }
4351                 return false;
4352         }
4353
4354         // check for the "saving throw" automatic melee miss on lethal blow
4355         // stupid place for this.
4356         bool forceMiss = false;
4357         if ( enemyEnt->IsType( idPlayer::Type ) && g_skill.GetInteger() < 2 ) {
4358                 int     damage, armor;
4359                 idPlayer *player = static_cast<idPlayer*>( enemyEnt );
4360                 player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor );
4361
4362                 if ( enemyEnt->health <= damage ) {
4363                         int     t = gameLocal.time - player->lastSavingThrowTime;
4364                         if ( t > SAVING_THROW_TIME ) {
4365                                 player->lastSavingThrowTime = gameLocal.time;
4366                                 t = 0;
4367                         }
4368                         if ( t < 1000 ) {
4369                                 gameLocal.Printf( "Saving throw.\n" );
4370                                 forceMiss = true;
4371                         }
4372                 }
4373         }
4374
4375         // make sure the trace can actually hit the enemy
4376         if ( forceMiss || !TestMelee() ) {
4377                 // missed
4378                 p = meleeDef->GetString( "snd_miss" );
4379                 if ( p && *p ) {
4380                         shader = declManager->FindSound( p );
4381                         StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4382                 }
4383                 return false;
4384         }
4385
4386         //
4387         // do the damage
4388         //
4389         p = meleeDef->GetString( "snd_hit" );
4390         if ( p && *p ) {
4391                 shader = declManager->FindSound( p );
4392                 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4393         }
4394
4395         idVec3  kickDir;
4396         meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4397
4398         idVec3  globalKickDir;
4399         globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4400
4401         enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4402
4403         lastAttackTime = gameLocal.time;
4404
4405         return true;
4406 }
4407
4408 /*
4409 ================
4410 idAI::PushWithAF
4411 ================
4412 */
4413 void idAI::PushWithAF( void ) {
4414         int i, j;
4415         afTouch_t touchList[ MAX_GENTITIES ];
4416         idEntity *pushed_ents[ MAX_GENTITIES ];
4417         idEntity *ent;
4418         idVec3 vel;
4419         int num_pushed;
4420
4421         num_pushed = 0;
4422         af.ChangePose( this, gameLocal.time );
4423         int num = af.EntitiesTouchingAF( touchList );
4424         for( i = 0; i < num; i++ ) {
4425                 if ( touchList[ i ].touchedEnt->IsType( idProjectile::Type ) ) {
4426                         // skip projectiles
4427                         continue;
4428                 }
4429
4430                 // make sure we havent pushed this entity already.  this avoids causing double damage
4431                 for( j = 0; j < num_pushed; j++ ) {
4432                         if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) {
4433                                 break;
4434                         }
4435                 }
4436                 if ( j >= num_pushed ) {
4437                         ent = touchList[ i ].touchedEnt;
4438                         pushed_ents[num_pushed++] = ent;
4439                         vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin();
4440                         vel.Normalize();
4441                         if ( attack.Length() && ent->IsType( idActor::Type ) ) {
4442                                 ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT );
4443                         } else {
4444                                 ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() );
4445                         }
4446                 }
4447         }
4448 }
4449
4450 /***********************************************************************
4451
4452         Misc
4453
4454 ***********************************************************************/
4455
4456 /*
4457 ================
4458 idAI::GetMuzzle
4459 ================
4460 */
4461 void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) {
4462         jointHandle_t joint;
4463
4464         if ( !jointname || !jointname[ 0 ] ) {
4465                 muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
4466                 muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
4467         } else {
4468                 joint = animator.GetJointHandle( jointname );
4469                 if ( joint == INVALID_JOINT ) {
4470                         gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
4471                 }
4472                 GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
4473         }
4474 }
4475
4476 /*
4477 ================
4478 idAI::TriggerWeaponEffects
4479 ================
4480 */
4481 void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) {
4482         idVec3 org;
4483         idMat3 axis;
4484
4485         if ( !g_muzzleFlash.GetBool() ) {
4486                 return;
4487         }
4488
4489         // muzzle flash
4490         // offset the shader parms so muzzle flashes show up
4491         renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
4492         renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat();
4493
4494         if ( flashJointWorld != INVALID_JOINT ) {
4495                 GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
4496
4497                 if ( worldMuzzleFlash.lightRadius.x > 0.0f ) {
4498                         worldMuzzleFlash.axis = axis;
4499                         worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
4500                         if ( worldMuzzleFlashHandle != - 1 ) {
4501                                 gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
4502                         } else {
4503                                 worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
4504                         }
4505                         muzzleFlashEnd = gameLocal.time + flashTime;
4506                         UpdateVisuals();
4507                 }
4508         }
4509 }
4510
4511 /*
4512 ================
4513 idAI::UpdateMuzzleFlash
4514 ================
4515 */
4516 void idAI::UpdateMuzzleFlash( void ) {
4517         if ( worldMuzzleFlashHandle != -1 ) { 
4518                 if ( gameLocal.time >= muzzleFlashEnd ) {
4519                         gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
4520                         worldMuzzleFlashHandle = -1;
4521                 } else {
4522                         idVec3 muzzle;
4523                         animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
4524                         animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
4525                         muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
4526                         worldMuzzleFlash.origin = muzzle;
4527                         gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
4528                 }
4529         }
4530 }
4531
4532 /*
4533 ================
4534 idAI::Hide
4535 ================
4536 */
4537 void idAI::Hide( void ) {
4538         idActor::Hide();
4539         fl.takedamage = false;
4540         physicsObj.SetContents( 0 );
4541         physicsObj.GetClipModel()->Unlink();
4542         StopSound( SND_CHANNEL_AMBIENT, false );
4543         SetChatSound();
4544
4545         AI_ENEMY_IN_FOV         = false;
4546         AI_ENEMY_VISIBLE        = false;
4547         StopMove( MOVE_STATUS_DONE );
4548 }
4549
4550 /*
4551 ================
4552 idAI::Show
4553 ================
4554 */
4555 void idAI::Show( void ) {
4556         idActor::Show();
4557         if ( spawnArgs.GetBool( "big_monster" ) ) {
4558                 physicsObj.SetContents( 0 );
4559         } else if ( use_combat_bbox ) {
4560                 physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
4561         } else {
4562                 physicsObj.SetContents( CONTENTS_BODY );
4563         }
4564         physicsObj.GetClipModel()->Link( gameLocal.clip );
4565         fl.takedamage = !spawnArgs.GetBool( "noDamage" );
4566         SetChatSound();
4567         StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
4568 }
4569
4570 /*
4571 =====================
4572 idAI::SetChatSound
4573 =====================
4574 */
4575 void idAI::SetChatSound( void ) {
4576         const char *snd;
4577
4578         if ( IsHidden() ) {
4579                 snd = NULL;
4580         } else if ( enemy.GetEntity() ) {
4581                 snd = spawnArgs.GetString( "snd_chatter_combat", NULL );
4582                 chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) );
4583                 chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) );
4584         } else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) {
4585                 snd = spawnArgs.GetString( "snd_chatter", NULL );
4586                 chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) );
4587                 chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) );
4588         } else {
4589                 snd = NULL;
4590         }
4591
4592         if ( snd && *snd ) {
4593                 chat_snd = declManager->FindSound( snd );
4594
4595                 // set the next chat time
4596                 chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4597         } else {
4598                 chat_snd = NULL;
4599         }
4600 }
4601
4602 /*
4603 ================
4604 idAI::CanPlayChatterSounds
4605
4606 Used for playing chatter sounds on monsters.
4607 ================
4608 */
4609 bool idAI::CanPlayChatterSounds( void ) const {
4610         if ( AI_DEAD ) {
4611                 return false;
4612         }
4613
4614         if ( IsHidden() ) {
4615                 return false;
4616         }
4617
4618         if ( enemy.GetEntity() ) {
4619                 return true;
4620         }
4621
4622         if ( spawnArgs.GetBool( "no_idle_chatter" ) ) {
4623                 return false;
4624         }
4625
4626         return true;
4627 }
4628
4629 /*
4630 =====================
4631 idAI::PlayChatter
4632 =====================
4633 */
4634 void idAI::PlayChatter( void ) {
4635         // check if it's time to play a chat sound
4636         if ( AI_DEAD || !chat_snd || ( chat_time > gameLocal.time ) ) {
4637                 return;
4638         }
4639
4640         StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL );
4641
4642         // set the next chat time
4643         chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4644 }
4645
4646 /*
4647 =====================
4648 idAI::UpdateParticles
4649 =====================
4650 */
4651 void idAI::UpdateParticles( void ) {
4652         if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
4653                 idVec3 realVector;
4654                 idMat3 realAxis;
4655
4656                 int particlesAlive = 0;
4657                 for ( int i = 0; i < particles.Num(); i++ ) {
4658                         if ( particles[i].particle && particles[i].time ) {
4659                                 particlesAlive++;
4660                                 if (af.IsActive()) {
4661                                         realAxis = mat3_identity;
4662                                         realVector = GetPhysics()->GetOrigin();
4663                                 } else {
4664                                         animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis );
4665                                         realAxis *= renderEntity.axis;
4666                                         realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() );
4667                                 }
4668
4669                                 if ( !gameLocal.smokeParticles->EmitSmoke( particles[i].particle, particles[i].time, gameLocal.random.CRandomFloat(), realVector, realAxis )) {
4670                                         if ( restartParticles ) {
4671                                                 particles[i].time = gameLocal.time;
4672                                         } else {
4673                                                 particles[i].time = 0;
4674                                                 particlesAlive--;
4675                                         }
4676                                 }
4677                         }
4678                 }
4679                 if ( particlesAlive == 0 ) {
4680                         BecomeInactive( TH_UPDATEPARTICLES );
4681                 }
4682         }
4683 }
4684
4685 /*
4686 =====================
4687 idAI::TriggerParticles
4688 =====================
4689 */
4690 void idAI::TriggerParticles( const char *jointName ) {
4691         jointHandle_t jointNum;
4692
4693         jointNum = animator.GetJointHandle( jointName );
4694         for ( int i = 0; i < particles.Num(); i++ ) {
4695                 if ( particles[i].joint == jointNum ) {
4696                         particles[i].time = gameLocal.time;
4697                         BecomeActive( TH_UPDATEPARTICLES );
4698                 }
4699         }
4700 }
4701
4702
4703 /***********************************************************************
4704
4705         Head & torso aiming
4706
4707 ***********************************************************************/
4708
4709 /*
4710 ================
4711 idAI::UpdateAnimationControllers
4712 ================
4713 */
4714 bool idAI::UpdateAnimationControllers( void ) {
4715         idVec3          local;
4716         idVec3          focusPos;
4717         idQuat          jawQuat;
4718         idVec3          left;
4719         idVec3          dir;
4720         idVec3          orientationJointPos;
4721         idVec3          localDir;
4722         idAngles        newLookAng;
4723         idAngles        diff;
4724         idMat3          mat;
4725         idMat3          axis;
4726         idMat3          orientationJointAxis;
4727         idAFAttachment  *headEnt = head.GetEntity();
4728         idVec3          eyepos;
4729         idVec3          pos;
4730         int                     i;
4731         idAngles        jointAng;
4732         float           orientationJointYaw;
4733
4734         if ( AI_DEAD ) {
4735                 return idActor::UpdateAnimationControllers();
4736         }
4737
4738         if ( orientationJoint == INVALID_JOINT ) {
4739                 orientationJointAxis = viewAxis;
4740                 orientationJointPos = physicsObj.GetOrigin();
4741                 orientationJointYaw = current_yaw;
4742         } else {
4743                 GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
4744                 orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
4745                 orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
4746         }
4747
4748         if ( focusJoint != INVALID_JOINT ) {
4749                 if ( headEnt ) {
4750                         headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
4751                 } else {
4752                         GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
4753                 }
4754                 eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
4755                 if ( ai_debugMove.GetBool() ) {
4756                         gameRenderWorld->DebugLine( colorRed, eyepos, eyepos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
4757                 }
4758         } else {
4759                 eyepos = GetEyePosition();
4760         }
4761
4762         if ( headEnt ) {
4763                 CopyJointsFromBodyToHead();
4764         }
4765
4766         // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
4767         // Getting the joint positions causes the joints to be updated.  The IK gets joint positions itself (which
4768         // are already up to date because of getting the joints in this function) and then sets their positions, which
4769         // forces the heirarchy to be updated again next time we get a joint or present the model.  If IK is enabled,
4770         // or if we have a seperate head, we end up transforming the joints twice per frame.  Characters with no
4771         // head entity and no ik will only transform their joints once.  Set g_debuganim to the current entity number
4772         // in order to see how many times an entity transforms the joints per frame.
4773         idActor::UpdateAnimationControllers();
4774
4775         idEntity *focusEnt = focusEntity.GetEntity();
4776         if ( !allowJointMod || !allowEyeFocus || ( gameLocal.time >= focusTime ) ) {
4777             focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
4778         } else if ( focusEnt == NULL ) {
4779                 // keep looking at last position until focusTime is up
4780                 focusPos = currentFocusPos;
4781         } else if ( focusEnt == enemy.GetEntity() ) {
4782                 focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
4783         } else if ( focusEnt->IsType( idActor::Type ) ) {
4784                 focusPos = static_cast<idActor *>( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
4785         } else {
4786                 focusPos = focusEnt->GetPhysics()->GetOrigin();
4787         }
4788
4789         currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
4790
4791         // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
4792         dir = focusPos - orientationJointPos;
4793         newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
4794         newLookAng.roll = 0.0f;
4795         newLookAng.pitch = 0.0f;
4796
4797 #if 0
4798         gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec );
4799         gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
4800         gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec );
4801 #endif
4802
4803         // determine pitch from joint position
4804         dir = focusPos - eyepos;
4805         dir.NormalizeFast();
4806         orientationJointAxis.ProjectVector( dir, localDir );
4807         newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() );
4808         newLookAng.roll = 0.0f;
4809
4810         diff = newLookAng - lookAng;
4811         
4812         if ( eyeAng != diff ) {
4813                 eyeAng = diff;
4814                 eyeAng.Clamp( eyeMin, eyeMax );
4815                 idAngles angDelta = diff - eyeAng;
4816                 if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
4817                         alignHeadTime = gameLocal.time;
4818                 } else {
4819                         alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
4820                 }
4821         }
4822
4823         if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
4824                 alignHeadTime = gameLocal.time;
4825         }
4826
4827         if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
4828                 alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
4829                 destLookAng = newLookAng;
4830                 destLookAng.Clamp( lookMin, lookMax );
4831         }
4832
4833         diff = destLookAng - lookAng;
4834         if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
4835                 if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
4836                         diff.pitch = 360.0f - diff.pitch;
4837                 }
4838         }
4839         if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
4840                 if ( diff.yaw > 180.0f ) {
4841                         diff.yaw -= 360.0f;
4842                 } else if ( diff.yaw <= -180.0f ) {
4843                         diff.yaw += 360.0f;
4844                 }
4845         }
4846         lookAng = lookAng + diff * headFocusRate;
4847         lookAng.Normalize180();
4848
4849         jointAng.roll = 0.0f;
4850         for( i = 0; i < lookJoints.Num(); i++ ) {
4851                 jointAng.pitch  = lookAng.pitch * lookJointAngles[ i ].pitch;
4852                 jointAng.yaw    = lookAng.yaw * lookJointAngles[ i ].yaw;
4853                 animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
4854         }
4855
4856         if ( move.moveType == MOVETYPE_FLY ) {
4857                 // lean into turns
4858                 AdjustFlyingAngles();
4859         }
4860         
4861         if ( headEnt ) {
4862                 idAnimator *headAnimator = headEnt->GetAnimator();
4863
4864                 if ( allowEyeFocus ) {
4865                         idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
4866                         axis =  eyeAxis * orientationJointAxis;
4867                         left = axis[ 1 ] * eyeHorizontalOffset;
4868                         eyepos -= headEnt->GetPhysics()->GetOrigin();
4869                         headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
4870                         headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
4871                 } else {
4872                         headAnimator->ClearJoint( leftEyeJoint );
4873                         headAnimator->ClearJoint( rightEyeJoint );
4874                 }
4875         } else {
4876                 if ( allowEyeFocus ) {
4877                         idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
4878                         axis =  eyeAxis * orientationJointAxis;
4879                         left = axis[ 1 ] * eyeHorizontalOffset;
4880                         eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
4881                         animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
4882                         animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
4883                 } else {
4884                         animator.ClearJoint( leftEyeJoint );
4885                         animator.ClearJoint( rightEyeJoint );
4886                 }
4887         }
4888
4889         return true;
4890 }
4891
4892 /***********************************************************************
4893
4894 idCombatNode
4895
4896 ***********************************************************************/
4897
4898 const idEventDef EV_CombatNode_MarkUsed( "markUsed" );
4899
4900 CLASS_DECLARATION( idEntity, idCombatNode )
4901         EVENT( EV_CombatNode_MarkUsed,                          idCombatNode::Event_MarkUsed )
4902         EVENT( EV_Activate,                                                     idCombatNode::Event_Activate )
4903 END_CLASS
4904
4905 /*
4906 =====================
4907 idCombatNode::idCombatNode
4908 =====================
4909 */
4910 idCombatNode::idCombatNode( void ) {
4911         min_dist = 0.0f;
4912         max_dist = 0.0f;
4913         cone_dist = 0.0f;
4914         min_height = 0.0f;
4915         max_height = 0.0f;
4916         cone_left.Zero();
4917         cone_right.Zero();
4918         offset.Zero();
4919         disabled = false;
4920 }
4921
4922 /*
4923 =====================
4924 idCombatNode::Save
4925 =====================
4926 */
4927 void idCombatNode::Save( idSaveGame *savefile ) const {
4928         savefile->WriteFloat( min_dist );
4929         savefile->WriteFloat( max_dist );
4930         savefile->WriteFloat( cone_dist );
4931         savefile->WriteFloat( min_height );
4932         savefile->WriteFloat( max_height );
4933         savefile->WriteVec3( cone_left );
4934         savefile->WriteVec3( cone_right );
4935         savefile->WriteVec3( offset );
4936         savefile->WriteBool( disabled );
4937 }
4938
4939 /*
4940 =====================
4941 idCombatNode::Restore
4942 =====================
4943 */
4944 void idCombatNode::Restore( idRestoreGame *savefile ) {
4945         savefile->ReadFloat( min_dist );
4946         savefile->ReadFloat( max_dist );
4947         savefile->ReadFloat( cone_dist );
4948         savefile->ReadFloat( min_height );
4949         savefile->ReadFloat( max_height );
4950         savefile->ReadVec3( cone_left );
4951         savefile->ReadVec3( cone_right );
4952         savefile->ReadVec3( offset );
4953         savefile->ReadBool( disabled );
4954 }
4955
4956 /*
4957 =====================
4958 idCombatNode::Spawn
4959 =====================
4960 */
4961 void idCombatNode::Spawn( void ) {
4962         float fov;
4963         float yaw;
4964         float height;
4965
4966         min_dist = spawnArgs.GetFloat( "min" );
4967         max_dist = spawnArgs.GetFloat( "max" );
4968         height = spawnArgs.GetFloat( "height" );
4969         fov = spawnArgs.GetFloat( "fov", "60" );
4970         offset = spawnArgs.GetVector( "offset" );
4971
4972         const idVec3 &org = GetPhysics()->GetOrigin() + offset;
4973         min_height = org.z - height * 0.5f;
4974         max_height = min_height + height;
4975
4976         const idMat3 &axis = GetPhysics()->GetAxis();
4977         yaw = axis[ 0 ].ToYaw();
4978
4979         idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
4980         cone_left = leftang.ToForward();
4981
4982         idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
4983         cone_right = rightang.ToForward();
4984
4985         disabled = spawnArgs.GetBool( "start_off" );
4986 }
4987
4988 /*
4989 =====================
4990 idCombatNode::IsDisabled
4991 =====================
4992 */
4993 bool idCombatNode::IsDisabled( void ) const {
4994         return disabled;
4995 }
4996
4997 /*
4998 =====================
4999 idCombatNode::DrawDebugInfo
5000 =====================
5001 */
5002 void idCombatNode::DrawDebugInfo( void ) {
5003         idEntity                *ent;
5004         idCombatNode    *node;
5005         idPlayer                *player = gameLocal.GetLocalPlayer();
5006         idVec4                  color;
5007         idBounds                bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) );
5008         
5009         for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
5010                 if ( !ent->IsType( idCombatNode::Type ) ) {
5011                         continue;
5012                 }
5013
5014                 node = static_cast<idCombatNode *>( ent );
5015                 if ( node->disabled ) {
5016                         color = colorMdGrey;
5017                 } else if ( player && node->EntityInView( player, player->GetPhysics()->GetOrigin() ) ) {
5018                         color = colorYellow;
5019                 } else {
5020                         color = colorRed;
5021                 }
5022
5023                 idVec3 leftDir( -node->cone_left.y, node->cone_left.x, 0.0f );
5024                 idVec3 rightDir( node->cone_right.y, -node->cone_right.x, 0.0f );
5025                 idVec3 org = node->GetPhysics()->GetOrigin() + node->offset;
5026
5027                 bounds[ 1 ].z = node->max_height;
5028
5029                 leftDir.NormalizeFast();
5030                 rightDir.NormalizeFast();
5031
5032                 const idMat3 &axis = node->GetPhysics()->GetAxis();
5033                 float cone_dot = node->cone_right * axis[ 1 ];
5034                 if ( idMath::Fabs( cone_dot ) > 0.1 ) {
5035                         float cone_dist = node->max_dist / cone_dot;
5036                         idVec3 pos1 = org + leftDir * node->min_dist;
5037                         idVec3 pos2 = org + leftDir * cone_dist;
5038                         idVec3 pos3 = org + rightDir * node->min_dist;
5039                         idVec3 pos4 = org + rightDir * cone_dist;
5040
5041                         gameRenderWorld->DebugLine( color, node->GetPhysics()->GetOrigin(), ( pos1 + pos3 ) * 0.5f, gameLocal.msec );
5042                         gameRenderWorld->DebugLine( color, pos1, pos2, gameLocal.msec );
5043                         gameRenderWorld->DebugLine( color, pos1, pos3, gameLocal.msec );
5044                         gameRenderWorld->DebugLine( color, pos3, pos4, gameLocal.msec );
5045                         gameRenderWorld->DebugLine( color, pos2, pos4, gameLocal.msec );
5046                         gameRenderWorld->DebugBounds( color, bounds, org, gameLocal.msec );
5047                 }
5048         }
5049 }
5050
5051 /*
5052 =====================
5053 idCombatNode::EntityInView
5054 =====================
5055 */
5056 bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) {
5057         if ( !actor || ( actor->health <= 0 ) ) {
5058                 return false;
5059         }
5060
5061         const idBounds &bounds = actor->GetPhysics()->GetBounds();
5062         if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) {
5063                 return false;
5064         }
5065
5066         const idVec3 &org = GetPhysics()->GetOrigin() + offset;
5067         const idMat3 &axis = GetPhysics()->GetAxis();
5068         idVec3 dir = pos - org;
5069         float  dist = dir * axis[ 0 ];
5070         
5071         if ( ( dist < min_dist ) || ( dist > max_dist ) ) {
5072                 return false;
5073         }
5074
5075         float left_dot = dir * cone_left;
5076         if ( left_dot < 0.0f ) {
5077                 return false;
5078         }
5079
5080         float right_dot = dir * cone_right;
5081         if ( right_dot < 0.0f ) {
5082                 return false;
5083         }
5084
5085         return true;
5086 }
5087
5088 /*
5089 =====================
5090 idCombatNode::Event_Activate
5091 =====================
5092 */
5093 void idCombatNode::Event_Activate( idEntity *activator ) {
5094         disabled = !disabled;
5095 }
5096
5097 /*
5098 =====================
5099 idCombatNode::Event_MarkUsed
5100 =====================
5101 */
5102 void idCombatNode::Event_MarkUsed( void ) {
5103         if ( spawnArgs.GetBool( "use_once" ) ) {
5104                 disabled = true;
5105         }
5106 }