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