2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
32 #include "../Game_local.h"
34 static const char *moveCommandString[ NUM_MOVE_COMMANDS ] = {
39 "MOVE_TO_ENEMYHEIGHT",
42 "MOVE_TO_ATTACK_POSITION",
45 "MOVE_TO_POSITION_DIRECT",
46 "MOVE_SLIDE_TO_POSITION",
52 idMoveState::idMoveState
55 idMoveState::idMoveState() {
56 moveType = MOVETYPE_ANIM;
57 moveCommand = MOVE_NONE;
58 moveStatus = MOVE_STATUS_DONE;
60 moveDir.Set( 1.0f, 0.0f, 0.0f );
62 goalEntityOrigin.Zero();
72 lastMoveOrigin = vec3_origin;
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 );
105 =====================
107 =====================
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 );
133 idAASFindCover::idAASFindCover
136 idAASFindCover::idAASFindCover( const idVec3 &hideFromPos ) {
138 idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) );
141 numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
142 hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
147 idAASFindCover::~idAASFindCover
150 idAASFindCover::~idAASFindCover() {
151 gameLocal.pvs.FreeCurrentPVS( hidePVS );
156 idAASFindCover::TestArea
159 bool idAASFindCover::TestArea( const idAAS *aas, int areaNum ) {
162 int PVSAreas[ idEntity::MAX_PVS_AREAS ];
164 areaCenter = aas->AreaCenter( areaNum );
165 areaCenter[ 2 ] += 1.0f;
167 numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
168 if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) {
177 idAASFindAreaOutOfRange::idAASFindAreaOutOfRange
180 idAASFindAreaOutOfRange::idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ) {
181 this->targetPos = targetPos;
182 this->maxDistSqr = maxDist * maxDist;
187 idAASFindAreaOutOfRange::TestArea
190 bool idAASFindAreaOutOfRange::TestArea( const idAAS *aas, int areaNum ) {
191 const idVec3 &areaCenter = aas->AreaCenter( areaNum );
195 dist = ( targetPos.ToVec2() - areaCenter.ToVec2() ).LengthSqr();
197 if ( ( maxDistSqr > 0.0f ) && ( dist < maxDistSqr ) ) {
201 gameLocal.clip.TracePoint( trace, targetPos, areaCenter + idVec3( 0.0f, 0.0f, 1.0f ), MASK_OPAQUE, NULL );
202 if ( trace.fraction < 1.0f ) {
211 idAASFindAttackPosition::idAASFindAttackPosition
214 idAASFindAttackPosition::idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ) {
217 this->target = target;
218 this->targetPos = targetPos;
219 this->fireOffset = fireOffset;
221 this->gravityAxis = gravityAxis;
223 excludeBounds = idBounds( idVec3( -64.0, -64.0f, -8.0f ), idVec3( 64.0, 64.0f, 64.0f ) );
224 excludeBounds.TranslateSelf( self->GetPhysics()->GetOrigin() );
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 );
234 idAASFindAttackPosition::~idAASFindAttackPosition
237 idAASFindAttackPosition::~idAASFindAttackPosition() {
238 gameLocal.pvs.FreeCurrentPVS( targetPVS );
243 idAASFindAttackPosition::TestArea
246 bool idAASFindAttackPosition::TestArea( const idAAS *aas, int areaNum ) {
253 int PVSAreas[ idEntity::MAX_PVS_AREAS ];
255 areaCenter = aas->AreaCenter( areaNum );
256 areaCenter[ 2 ] += 1.0f;
258 if ( excludeBounds.ContainsPoint( areaCenter ) ) {
259 // too close to where we already are
263 numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
264 if ( !gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ) ) {
268 // calculate the world transform of the launch position
269 dir = targetPos - areaCenter;
270 gravityAxis.ProjectVector( dir, local_dir );
272 local_dir.ToVec2().Normalize();
273 axis = local_dir.ToMat3();
274 fromPos = areaCenter + fireOffset * axis;
276 return self->GetAimDir( fromPos, target, self, dir );
280 =====================
282 =====================
286 travelFlags = TFL_WALK|TFL_AIR;
289 ignore_obstacles = false;
290 blockedRadius = 0.0f;
291 blockedMoveTime = 750;
292 blockedAttackTime = 750;
295 anim_turn_yaw = 0.0f;
296 anim_turn_amount = 0.0f;
297 anim_turn_angles = 0.0f;
299 fly_seek_scale = 1.0f;
300 fly_roll_scale = 0.0f;
303 fly_pitch_scale = 0.0f;
304 fly_pitch_max = 0.0f;
307 allowHiddenMovement = false;
309 fly_bob_strength = 0.0f;
312 lastHitCheckResult = false;
313 lastHitCheckTime = 0;
316 projectile_height_to_distance_ratio = 1.0f;
317 projectileDef = NULL;
319 projectileClipModel = NULL;
320 projectileRadius = 0.0f;
321 projectileVelocity = vec3_origin;
322 projectileGravity = vec3_origin;
323 projectileSpeed = 0.0f;
328 talk_state = TALK_NEVER;
332 restartParticles = true;
335 wakeOnFlashlight = false;
336 memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
337 worldMuzzleFlashHandle = -1;
340 lastVisibleEnemyPos.Zero();
341 lastVisibleEnemyEyeOffset.Zero();
342 lastVisibleReachableEnemyPos.Zero();
343 lastReachableEnemyPos.Zero();
346 fl.neverDormant = false; // AI's can go dormant
351 spawnClearMoveables = false;
356 current_cinematic = 0;
358 allowEyeFocus = true;
360 allowJointMod = true;
364 forceAlignHeadTime = 0;
366 currentFocusPos.Zero();
377 flashJointWorld = INVALID_JOINT;
379 focusJoint = INVALID_JOINT;
380 orientationJoint = INVALID_JOINT;
381 flyTiltJoint = INVALID_JOINT;
383 eyeVerticalOffset = 0.0f;
384 eyeHorizontalOffset = 0.0f;
386 headFocusRate = 0.0f;
391 =====================
393 =====================
396 delete projectileClipModel;
397 DeconstructScriptObject();
399 if ( worldMuzzleFlashHandle != -1 ) {
400 gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
401 worldMuzzleFlashHandle = -1;
405 if ( harvestEnt.GetEntity() ) {
406 harvestEnt.GetEntity()->PostEventMS( &EV_Remove, 0 );
412 =====================
414 =====================
416 void idAI::Save( idSaveGame *savefile ) const {
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 );
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 );
436 savefile->WriteStaticObject( physicsObj );
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 );
451 savefile->WriteBool( allowMove );
452 savefile->WriteBool( allowHiddenMovement );
453 savefile->WriteBool( disableGravity );
454 savefile->WriteBool( af_push_moveables );
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 );
462 savefile->WriteInt( missileLaunchOffset.Num() );
463 for( i = 0; i < missileLaunchOffset.Num(); i++ ) {
464 savefile->WriteVec3( missileLaunchOffset[ i ] );
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 );
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 );
484 savefile->WriteInt( num_cinematics );
485 savefile->WriteInt( current_cinematic );
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 );
499 savefile->WriteInt( lookJoints.Num() );
500 for( i = 0; i < lookJoints.Num(); i++ ) {
501 savefile->WriteJoint( lookJoints[ i ] );
502 savefile->WriteAngles( lookJointAngles[ i ] );
505 savefile->WriteFloat( shrivel_rate );
506 savefile->WriteInt( shrivel_start );
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 );
514 savefile->WriteBool( restartParticles );
515 savefile->WriteBool( useBoneAxis );
517 enemy.Save( savefile );
518 savefile->WriteVec3( lastVisibleEnemyPos );
519 savefile->WriteVec3( lastVisibleEnemyEyeOffset );
520 savefile->WriteVec3( lastVisibleReachableEnemyPos );
521 savefile->WriteVec3( lastReachableEnemyPos );
522 savefile->WriteBool( wakeOnFlashlight );
524 savefile->WriteAngles( eyeMin );
525 savefile->WriteAngles( eyeMax );
527 savefile->WriteFloat( eyeVerticalOffset );
528 savefile->WriteFloat( eyeHorizontalOffset );
529 savefile->WriteFloat( eyeFocusRate );
530 savefile->WriteFloat( headFocusRate );
531 savefile->WriteInt( focusAlignTime );
533 savefile->WriteJoint( flashJointWorld );
534 savefile->WriteInt( muzzleFlashEnd );
536 savefile->WriteJoint( focusJoint );
537 savefile->WriteJoint( orientationJoint );
538 savefile->WriteJoint( flyTiltJoint );
540 savefile->WriteBool( GetPhysics() == static_cast<const idPhysics *>(&physicsObj) );
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);
551 harvestEnt.Save( savefile);
556 =====================
558 =====================
560 void idAI::Restore( idRestoreGame *savefile ) {
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 );
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 );
583 savefile->ReadStaticObject( physicsObj );
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 );
598 savefile->ReadBool( allowMove );
599 savefile->ReadBool( allowHiddenMovement );
600 savefile->ReadBool( disableGravity );
601 savefile->ReadBool( af_push_moveables );
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 );
609 savefile->ReadInt( num );
610 missileLaunchOffset.SetGranularity( 1 );
611 missileLaunchOffset.SetNum( num );
612 for( i = 0; i < num; i++ ) {
613 savefile->ReadVec3( missileLaunchOffset[ i ] );
616 idStr projectileName;
617 savefile->ReadString( projectileName );
618 if ( projectileName.Length() ) {
619 projectileDef = gameLocal.FindEntityDefDict( projectileName );
621 projectileDef = NULL;
623 savefile->ReadFloat( projectileRadius );
624 savefile->ReadFloat( projectileSpeed );
625 savefile->ReadVec3( projectileVelocity );
626 savefile->ReadVec3( projectileGravity );
627 projectile.Restore( savefile );
628 savefile->ReadString( attack );
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 );
638 savefile->ReadInt( num_cinematics );
639 savefile->ReadInt( current_cinematic );
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 );
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 ] );
663 savefile->ReadFloat( shrivel_rate );
664 savefile->ReadInt( shrivel_start );
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 );
673 savefile->ReadBool( restartParticles );
674 savefile->ReadBool( useBoneAxis );
676 enemy.Restore( savefile );
677 savefile->ReadVec3( lastVisibleEnemyPos );
678 savefile->ReadVec3( lastVisibleEnemyEyeOffset );
679 savefile->ReadVec3( lastVisibleReachableEnemyPos );
680 savefile->ReadVec3( lastReachableEnemyPos );
682 savefile->ReadBool( wakeOnFlashlight );
684 savefile->ReadAngles( eyeMin );
685 savefile->ReadAngles( eyeMax );
687 savefile->ReadFloat( eyeVerticalOffset );
688 savefile->ReadFloat( eyeHorizontalOffset );
689 savefile->ReadFloat( eyeFocusRate );
690 savefile->ReadFloat( headFocusRate );
691 savefile->ReadInt( focusAlignTime );
693 savefile->ReadJoint( flashJointWorld );
694 savefile->ReadInt( muzzleFlashEnd );
696 savefile->ReadJoint( focusJoint );
697 savefile->ReadJoint( orientationJoint );
698 savefile->ReadJoint( flyTiltJoint );
700 savefile->ReadBool( restorePhysics );
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() ) {
714 // Link the script variables back to the scriptobject
715 LinkScriptVariables();
717 if ( restorePhysics ) {
718 RestorePhysics( &physicsObj );
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 );
731 funcEmitters.Clear();
734 savefile->ReadInt( emitterCount );
735 for(int i = 0; i < emitterCount; i++) {
736 funcEmitter_t newEmitter;
737 memset(&newEmitter, 0, sizeof(newEmitter));
740 savefile->ReadString( name );
742 strcpy( newEmitter.name, name.c_str() );
744 savefile->ReadJoint( newEmitter.joint );
745 savefile->ReadObject(reinterpret_cast<idClass *&>(newEmitter.particle));
747 funcEmitters.Set(newEmitter.name, newEmitter);
750 harvestEnt.Restore(savefile);
751 //if(harvestEnt.GetEntity()) {
752 // harvestEnt.GetEntity()->SetParent(this);
759 =====================
761 =====================
763 void idAI::Spawn( void ) {
764 const char *jointname;
765 const idKeyValue *kv;
772 if ( !g_monsters.GetBool() ) {
773 PostEventMS( &EV_Remove, 0 );
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 );
790 spawnArgs.GetFloat( "melee_range", "64", melee_range );
791 spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio );
793 spawnArgs.GetFloat( "turn_rate", "360", turnRate );
795 spawnArgs.GetBool( "talks", "0", talks );
796 if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) {
798 talk_state = TALK_OK;
800 talk_state = TALK_BUSY;
803 talk_state = TALK_NEVER;
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 );
814 spawnArgs.GetInt( "num_cinematics", "0", num_cinematics );
815 current_cinematic = 0;
817 LinkScriptVariables();
819 fl.takedamage = !spawnArgs.GetBool( "noDamage" );
822 allowHiddenMovement = false;
824 animator.RemoveOriginOffset( true );
826 // create combat collision hull for exact collision detection
829 lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" );
830 lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" );
832 lookJoints.SetGranularity( 1 );
833 lookJointAngles.SetGranularity( 1 );
834 kv = spawnArgs.MatchPrefix( "look_joint", NULL );
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() );
842 jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" );
843 jointScale.roll = 0.0f;
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 );
852 kv = spawnArgs.MatchPrefix( "look_joint", kv );
855 // calculate joint positions on attack frames so we can do proper "can hit" tests
856 CalculateAttackOffsets();
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" ) );
866 flashJointWorld = animator.GetJointHandle( "flash" );
868 if ( head.GetEntity() ) {
869 idAnimator *headAnimator = head.GetEntity()->GetAnimator();
871 jointname = spawnArgs.GetString( "bone_focus" );
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() );
879 jointname = spawnArgs.GetString( "bone_focus" );
881 focusJoint = animator.GetJointHandle( jointname );
882 if ( focusJoint == INVALID_JOINT ) {
883 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
888 jointname = spawnArgs.GetString( "bone_orientation" );
890 orientationJoint = animator.GetJointHandle( jointname );
891 if ( orientationJoint == INVALID_JOINT ) {
892 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
896 jointname = spawnArgs.GetString( "bone_flytilt" );
898 flyTiltJoint = animator.GetJointHandle( jointname );
899 if ( flyTiltJoint == INVALID_JOINT ) {
900 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
906 physicsObj.SetSelf( this );
907 physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
908 physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
910 if ( spawnArgs.GetBool( "big_monster" ) ) {
911 physicsObj.SetContents( 0 );
912 physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY );
914 if ( use_combat_bbox ) {
915 physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
917 physicsObj.SetContents( CONTENTS_BODY );
919 physicsObj.SetClipMask( MASK_MONSTERSOLID );
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 ) );
925 if ( num_cinematics ) {
926 physicsObj.SetGravity( vec3_origin );
928 idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
929 gravity *= g_gravity.GetFloat();
930 physicsObj.SetGravity( gravity );
933 SetPhysics( &physicsObj );
935 physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir );
936 current_yaw = local_dir.ToYaw();
937 ideal_yaw = idMath::AngleNormalize180( current_yaw );
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();
959 restartParticles = true;
960 useBoneAxis = spawnArgs.GetBool( "useBoneAxis" );
961 SpawnParticles( "smokeParticleSystem" );
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();
969 // play a looping ambient sound if we have one
970 StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
974 gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() );
978 // set up monster chatter
981 BecomeActive( TH_THINK );
983 if ( af_push_moveables ) {
984 af.SetupPose( this, gameLocal.time );
985 af.GetPhysics()->EnableClip();
988 // init the move variables
989 StopMove( MOVE_STATUS_DONE );
993 spawnArgs.GetBool( "spawnClearMoveables", "0", spawnClearMoveables );
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();
1004 idActor::Gib(dir, damageDefName);
1010 idAI::InitMuzzleFlash
1013 void idAI::InitMuzzleFlash( void ) {
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" ) );
1022 memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
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;
1035 worldMuzzleFlashHandle = -1;
1043 void idAI::List_f( const idCmdArgs &args ) {
1047 const char *statename;
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 ) ) {
1059 if ( check->state ) {
1060 statename = check->state->Name();
1062 statename = "NULL state";
1065 gameLocal.Printf( "%4i: %-20s %-20s %s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove );
1069 gameLocal.Printf( "...%d monsters\n", count );
1076 called when entity becomes dormant
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;
1088 if ( enemyNode.InList() ) {
1089 // remove ourselves from the enemy's enemylist
1092 idActor::DormantBegin();
1099 called when entity wakes from being dormant
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 );
1108 if ( particles.Num() ) {
1109 for ( int i = 0; i < particles.Num(); i++ ) {
1110 particles[i].time = gameLocal.time;
1114 idActor::DormantEnd();
1118 =====================
1120 =====================
1122 void idAI::Think( void ) {
1123 // if we are completely closed off from the player, don't do anything at all
1124 if ( CheckDormant() ) {
1128 if ( thinkFlags & TH_THINK ) {
1129 // clear out the enemy when he dies or is hidden
1130 idActor *enemyEnt = enemy.GetEntity();
1132 if ( enemyEnt->health <= 0 ) {
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();
1142 if ( num_cinematics ) {
1143 if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) {
1147 } else if ( !allowHiddenMovement && IsHidden() ) {
1151 // clear the ik before we do anything else so the skeleton doesn't get updated twice
1152 walkIK.ClearJointMods();
1154 switch( move.moveType ) {
1155 case MOVETYPE_DEAD :
1163 UpdateEnemyPosition();
1170 case MOVETYPE_STATIC :
1172 UpdateEnemyPosition();
1179 case MOVETYPE_ANIM :
1180 // animation based movement
1181 UpdateEnemyPosition();
1188 case MOVETYPE_SLIDE :
1189 // velocity based movement
1190 UpdateEnemyPosition();
1199 // clear pain flag so that we recieve any damage between now and the next time we run the script
1201 AI_SPECIAL_DAMAGE = 0;
1203 } else if ( thinkFlags & TH_PHYSICS ) {
1207 if ( af_push_moveables ) {
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 );
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 );
1221 UpdateMuzzleFlash();
1225 UpdateDamageEffects();
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() );
1236 /***********************************************************************
1238 AI script state management
1240 ***********************************************************************/
1243 =====================
1244 idAI::LinkScriptVariables
1245 =====================
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" );
1269 =====================
1270 idAI::UpdateAIScript
1271 =====================
1273 void idAI::UpdateAIScript( void ) {
1276 // clear the hit enemy flag so we catch the next time we hit someone
1277 AI_HIT_ENEMY = false;
1279 if ( allowHiddenMovement || !IsHidden() ) {
1280 // update the animstate if we're not hidden
1285 /***********************************************************************
1289 ***********************************************************************/
1296 void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) {
1297 int i, numListedClipModels;
1298 idBounds clipBounds;
1300 idClipModel *clipModel;
1301 idClipModel *clipModelList[ MAX_GENTITIES ];
1306 idVec2 perpendicular;
1308 org = physicsObj.GetOrigin();
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
1325 if ( !clipModel->IsTraceModel() ) {
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;
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 );
1342 delta = alwaysKick->GetPhysics()->GetOrigin() - org;
1343 delta.NormalizeFast();
1344 perpendicular.x = -delta.y;
1345 perpendicular.y = delta.x;
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 );
1358 bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) {
1361 for ( i = 0; i < 3; i++ ) {
1362 if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) {
1365 if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) {
1373 =====================
1375 =====================
1377 void idAI::SetAAS( void ) {
1380 spawnArgs.GetString( "use_aas", NULL, use_aas );
1381 aas = gameLocal.GetAAS( use_aas );
1383 const idAASSettings *settings = aas->GetSettings();
1385 if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) {
1386 gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() );
1388 float height = settings->maxStepHeight;
1389 physicsObj.SetMaxStepHeight( height );
1395 gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() );
1399 =====================
1401 =====================
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 );
1408 aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
1414 =====================
1416 =====================
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 ) ) {
1426 if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
1427 if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
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 ) ) {
1442 =====================
1443 idAI::PointReachableAreaNum
1444 =====================
1446 int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const {
1455 size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale;
1460 if ( move.moveType == MOVETYPE_FLY ) {
1461 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY );
1463 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK );
1470 =====================
1472 =====================
1474 bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const {
1483 aas->PushPointIntoAreaNum( areaNum, org );
1489 aas->PushPointIntoAreaNum( goalAreaNum, goal );
1490 if ( !goalAreaNum ) {
1494 if ( move.moveType == MOVETYPE_FLY ) {
1495 return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1497 return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1502 =====================
1503 idAI::TravelDistance
1505 Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance.
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 =====================
1511 float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const {
1519 // no aas, so just take the straight line distance
1520 delta = end.ToVec2() - start.ToVec2();
1521 dist = delta.LengthFast();
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() );
1531 fromArea = PointReachableAreaNum( start );
1532 toArea = PointReachableAreaNum( end );
1534 if ( !fromArea || !toArea ) {
1535 // can't seem to get there
1539 if ( fromArea == toArea ) {
1540 // same area, so just take the straight line distance
1541 delta = end.ToVec2() - start.ToVec2();
1542 dist = delta.LengthFast();
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() );
1552 idReachability *reach;
1554 if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) {
1558 if ( ai_debugMove.GetBool() ) {
1559 if ( move.moveType == MOVETYPE_FLY ) {
1560 aas->ShowFlyPath( start, toArea, end );
1562 aas->ShowWalkPath( start, toArea, end );
1570 =====================
1572 =====================
1574 void idAI::StopMove( moveStatus_t status ) {
1575 AI_MOVE_DONE = true;
1577 move.moveCommand = MOVE_NONE;
1578 move.moveStatus = status;
1580 move.goalEntity = NULL;
1581 move.moveDest = physicsObj.GetOrigin();
1582 AI_DEST_UNREACHABLE = false;
1583 AI_OBSTACLE_IN_PATH = false;
1585 move.startTime = gameLocal.time;
1590 move.moveDir.Zero();
1591 move.lastMoveOrigin.Zero();
1592 move.lastMoveTime = gameLocal.time;
1596 =====================
1599 Continually face the enemy's last known position. MoveDone is always true in this case.
1600 =====================
1602 bool idAI::FaceEnemy( void ) {
1603 idActor *enemyEnt = enemy.GetEntity();
1605 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
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;
1616 AI_MOVE_DONE = true;
1618 AI_DEST_UNREACHABLE = false;
1624 =====================
1627 Continually face the entity position. MoveDone is always true in this case.
1628 =====================
1630 bool idAI::FaceEntity( idEntity *ent ) {
1632 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
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;
1644 AI_MOVE_DONE = true;
1646 AI_DEST_UNREACHABLE = false;
1652 =====================
1653 idAI::DirectMoveToPosition
1654 =====================
1656 bool idAI::DirectMoveToPosition( const idVec3 &pos ) {
1657 if ( ReachedPos( pos, move.moveCommand ) ) {
1658 StopMove( MOVE_STATUS_DONE );
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;
1672 if ( move.moveType == MOVETYPE_FLY ) {
1673 idVec3 dir = pos - physicsObj.GetOrigin();
1676 physicsObj.SetLinearVelocity( dir );
1683 =====================
1684 idAI::MoveToEnemyHeight
1685 =====================
1687 bool idAI::MoveToEnemyHeight( void ) {
1688 idActor *enemyEnt = enemy.GetEntity();
1690 if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) {
1691 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
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;
1701 AI_MOVE_DONE = false;
1702 AI_DEST_UNREACHABLE = false;
1709 =====================
1711 =====================
1713 bool idAI::MoveToEnemy( void ) {
1716 idActor *enemyEnt = enemy.GetEntity();
1719 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
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;
1729 StopMove( MOVE_STATUS_DONE );
1733 idVec3 pos = lastVisibleReachableEnemyPos;
1737 move.toAreaNum = PointReachableAreaNum( pos );
1738 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1740 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1741 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1742 AI_DEST_UNREACHABLE = true;
1747 if ( !move.toAreaNum ) {
1748 // if only trying to update the enemy position
1749 if ( move.moveCommand == MOVE_TO_ENEMY ) {
1751 // keep the move destination up to date for wandering
1752 move.moveDest = pos;
1757 if ( !NewWanderDir( pos ) ) {
1758 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1759 AI_DEST_UNREACHABLE = true;
1764 if ( move.moveCommand != MOVE_TO_ENEMY ) {
1765 move.moveCommand = MOVE_TO_ENEMY;
1766 move.startTime = gameLocal.time;
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;
1781 =====================
1783 =====================
1785 bool idAI::MoveToEntity( idEntity *ent ) {
1791 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
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 );
1800 if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) {
1801 StopMove( MOVE_STATUS_DONE );
1807 move.toAreaNum = PointReachableAreaNum( pos );
1808 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1810 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1811 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1812 AI_DEST_UNREACHABLE = true;
1817 if ( !move.toAreaNum ) {
1818 // if only trying to update the entity position
1819 if ( move.moveCommand == MOVE_TO_ENTITY ) {
1821 // keep the move destination up to date for wandering
1822 move.moveDest = pos;
1827 if ( !NewWanderDir( pos ) ) {
1828 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1829 AI_DEST_UNREACHABLE = true;
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;
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;
1852 =====================
1853 idAI::MoveOutOfRange
1854 =====================
1856 bool idAI::MoveOutOfRange( idEntity *ent, float range ) {
1858 aasObstacle_t obstacle;
1863 if ( !aas || !ent ) {
1864 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1865 AI_DEST_UNREACHABLE = true;
1869 const idVec3 &org = physicsObj.GetOrigin();
1870 areaNum = PointReachableAreaNum( org );
1872 // consider the entity the monster is getting close to as an obstacle
1873 obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1875 if ( ent == enemy.GetEntity() ) {
1876 pos = lastVisibleEnemyPos;
1878 pos = ent->GetPhysics()->GetOrigin();
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;
1888 if ( ReachedPos( goal.origin, move.moveCommand ) ) {
1889 StopMove( MOVE_STATUS_DONE );
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;
1899 move.speed = fly_speed;
1900 move.startTime = gameLocal.time;
1901 AI_MOVE_DONE = false;
1902 AI_DEST_UNREACHABLE = false;
1909 =====================
1910 idAI::MoveToAttackPosition
1911 =====================
1913 bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) {
1915 aasObstacle_t obstacle;
1920 if ( !aas || !ent ) {
1921 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1922 AI_DEST_UNREACHABLE = true;
1926 const idVec3 &org = physicsObj.GetOrigin();
1927 areaNum = PointReachableAreaNum( org );
1929 // consider the entity the monster is getting close to as an obstacle
1930 obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1932 if ( ent == enemy.GetEntity() ) {
1933 pos = lastVisibleEnemyPos;
1935 pos = ent->GetPhysics()->GetOrigin();
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;
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;
1961 =====================
1962 idAI::MoveToPosition
1963 =====================
1965 bool idAI::MoveToPosition( const idVec3 &pos ) {
1970 if ( ReachedPos( pos, move.moveCommand ) ) {
1971 StopMove( MOVE_STATUS_DONE );
1978 move.toAreaNum = PointReachableAreaNum( org );
1979 aas->PushPointIntoAreaNum( move.toAreaNum, org );
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;
1989 if ( !move.toAreaNum && !NewWanderDir( org ) ) {
1990 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1991 AI_DEST_UNREACHABLE = true;
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;
2009 =====================
2011 =====================
2013 bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) {
2015 aasObstacle_t obstacle;
2019 if ( !aas || !entity ) {
2020 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2021 AI_DEST_UNREACHABLE = true;
2025 const idVec3 &org = physicsObj.GetOrigin();
2026 areaNum = PointReachableAreaNum( org );
2028 // consider the entity the monster tries to hide from as an obstacle
2029 obstacle.absBounds = entity->GetPhysics()->GetAbsBounds();
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;
2038 if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) {
2039 StopMove( MOVE_STATUS_DONE );
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;
2058 =====================
2059 idAI::SlideToPosition
2060 =====================
2062 bool idAI::SlideToPosition( const idVec3 &pos, float time ) {
2063 StopMove( MOVE_STATUS_DONE );
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;
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;
2080 move.speed = move.moveDir.LengthFast();
2087 =====================
2089 =====================
2091 bool idAI::WanderAround( void ) {
2092 StopMove( MOVE_STATUS_DONE );
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;
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;
2112 =====================
2114 =====================
2116 bool idAI::MoveDone( void ) const {
2117 return ( move.moveCommand == MOVE_NONE );
2125 bool idAI::StepDirection( float dir ) {
2126 predictedPath_t path;
2129 move.wanderYaw = dir;
2130 move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward();
2132 org = physicsObj.GetOrigin();
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 );
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
2141 if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) {
2144 move.moveDir = path.endVelocity * 1.0f / 48.0f;
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 );
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;
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 );
2159 idVec3 ceilingPos = path.endPos;
2161 for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) {
2163 if ( z <= ceilingPos.z ) {
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;
2179 return ( path.endEvent == 0 );
2187 bool idAI::NewWanderDir( const idVec3 &dest ) {
2188 float deltax, deltay;
2190 float tdir, olddir, turnaround;
2192 move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 );
2194 olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 );
2195 turnaround = idMath::AngleNormalize360( olddir - 180 );
2197 idVec3 org = physicsObj.GetOrigin();
2198 deltax = dest.x - org.x;
2199 deltay = dest.y - org.y;
2200 if ( deltax > 10 ) {
2202 } else if ( deltax < -10 ) {
2208 if ( deltay < -10 ) {
2210 } else if ( deltay > 10 ) {
2217 if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) {
2218 if ( d[ 1 ] == 0 ) {
2219 tdir = d[ 2 ] == 90 ? 45 : 315;
2221 tdir = d[ 2 ] == 90 ? 135 : 215;
2224 if ( tdir != turnaround && StepDirection( tdir ) ) {
2229 // try other directions
2230 if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) {
2236 if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) {
2240 if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) {
2244 // there is no direct path to the player, so pick another direction
2245 if ( olddir != DI_NODIR && StepDirection( olddir ) ) {
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 ) ) {
2257 for ( tdir = 315; tdir >= 0; tdir -= 45 ) {
2258 if ( tdir != turnaround && StepDirection( tdir ) ) {
2264 if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) {
2269 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2274 =====================
2276 =====================
2278 bool idAI::GetMovePos( idVec3 &seekPos ) {
2284 org = physicsObj.GetOrigin();
2287 switch( move.moveCommand ) {
2289 seekPos = move.moveDest;
2293 case MOVE_FACE_ENEMY :
2294 case MOVE_FACE_ENTITY :
2295 seekPos = move.moveDest;
2299 case MOVE_TO_POSITION_DIRECT :
2300 seekPos = move.moveDest;
2301 if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2302 StopMove( MOVE_STATUS_DONE );
2307 case MOVE_SLIDE_TO_POSITION :
2313 if ( move.moveCommand == MOVE_TO_ENTITY ) {
2314 MoveToEntity( move.goalEntity.GetEntity() );
2317 move.moveStatus = MOVE_STATUS_MOVING;
2319 if ( gameLocal.time > move.blockTime ) {
2320 if ( move.moveCommand == MOVE_WANDER ) {
2321 move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
2323 if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2324 StopMove( MOVE_STATUS_DONE );
2330 if ( aas && move.toAreaNum ) {
2331 areaNum = PointReachableAreaNum( org );
2332 if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) {
2333 seekPos = path.moveGoal;
2335 move.nextWanderTime = 0;
2337 AI_DEST_UNREACHABLE = true;
2344 if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) {
2345 result = NewWanderDir( move.moveDest );
2347 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2348 AI_DEST_UNREACHABLE = true;
2356 seekPos = org + move.moveDir * 2048.0f;
2357 if ( ai_debugMove.GetBool() ) {
2358 gameRenderWorld->DebugLine( colorYellow, org, seekPos, gameLocal.msec, true );
2361 AI_DEST_UNREACHABLE = false;
2364 if ( result && ( ai_debugMove.GetBool() ) ) {
2365 gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos );
2372 =====================
2373 idAI::EntityCanSeePos
2374 =====================
2376 bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) {
2381 handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() );
2383 if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) {
2384 gameLocal.pvs.FreeCurrentPVS( handle );
2388 gameLocal.pvs.FreeCurrentPVS( handle );
2390 eye = actorOrigin + actor->EyeOffset();
2395 physicsObj.DisableClip();
2397 gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2398 if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2399 physicsObj.EnableClip();
2403 const idBounds &bounds = physicsObj.GetBounds();
2404 point[2] += bounds[1][2] - bounds[0][2];
2406 gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2407 physicsObj.EnableClip();
2408 if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2415 =====================
2416 idAI::BlockedFailSafe
2417 =====================
2419 void idAI::BlockedFailSafe( void ) {
2420 if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) {
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;
2428 if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) {
2429 if ( lastAttackTime < gameLocal.time - blockedAttackTime ) {
2431 move.lastMoveTime = gameLocal.time;
2436 /***********************************************************************
2440 ***********************************************************************/
2443 =====================
2445 =====================
2447 void idAI::Turn( void ) {
2451 animFlags_t animflags;
2457 // check if the animator has marker this anim as non-turning
2458 if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) {
2459 animflags = legsAnim.GetAnimFlags();
2461 animflags = torsoAnim.GetAnimFlags();
2463 if ( animflags.ai_no_turn ) {
2467 if ( anim_turn_angles && animflags.anim_turn ) {
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 );
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() );
2481 diff = idMath::AngleNormalize180( ideal_yaw - current_yaw );
2482 turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec );
2483 if ( turnVel > turnRate ) {
2485 } else if ( turnVel < -turnRate ) {
2486 turnVel = -turnRate;
2488 turnAmount = turnVel * MS2SEC( gameLocal.msec );
2489 if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) {
2490 turnVel = diff / MS2SEC( gameLocal.msec );
2492 } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) {
2493 turnVel = diff / MS2SEC( gameLocal.msec );
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;
2504 viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
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 );
2515 =====================
2517 =====================
2519 bool idAI::FacingIdeal( void ) {
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;
2537 =====================
2539 =====================
2541 bool idAI::TurnToward( float yaw ) {
2542 ideal_yaw = idMath::AngleNormalize180( yaw );
2543 bool result = FacingIdeal();
2548 =====================
2550 =====================
2552 bool idAI::TurnToward( const idVec3 &pos ) {
2557 dir = pos - physicsObj.GetOrigin();
2558 physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
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() );
2565 bool result = FacingIdeal();
2569 /***********************************************************************
2573 ***********************************************************************/
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 );
2590 =====================
2592 =====================
2594 void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) {
2595 idVec3 oldModelOrigin;
2598 animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
2599 delta = axis * delta;
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;
2610 delta *= physicsObj.GetGravityAxis();
2614 =====================
2615 idAI::CheckObstacleAvoidance
2616 =====================
2618 void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) {
2620 obstaclePath_t path;
2625 if ( ignore_obstacles ) {
2627 move.obstacle = NULL;
2631 const idVec3 &origin = physicsObj.GetOrigin();
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 );
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;
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;
2655 move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL;
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;
2662 dist = ( path.seekPos - origin ) * dir;
2663 if ( dist < 1.0f ) {
2664 AI_OBSTACLE_IN_PATH = true;
2665 obstacle = path.startPosObstacle;
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;
2673 // check if we're past where the goalPos was pushed out of the obstacle
2674 dir = goalPos - origin;
2676 dist = ( path.seekPos - origin ) * dir;
2677 if ( dist < 1.0f ) {
2678 obstacle = path.seekPosObstacle;
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
2684 if ( obstacle->IsType( idActor::Type ) ) {
2685 // monsters aren't kickable
2686 if ( obstacle == enemy.GetEntity() ) {
2687 move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY;
2689 move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER;
2692 // try kicking the object out of the way
2693 move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT;
2695 newPos = obstacle->GetPhysics()->GetOrigin();
2696 //newPos = path.seekPos;
2697 move.obstacle = obstacle;
2699 newPos = path.seekPos;
2700 move.obstacle = NULL;
2705 =====================
2707 =====================
2709 void idAI::DeadMove( void ) {
2711 monsterMoveResult_t moveResult;
2713 idVec3 org = physicsObj.GetOrigin();
2715 GetMoveDelta( viewAxis, viewAxis, delta );
2716 physicsObj.SetDelta( delta );
2720 moveResult = physicsObj.GetMoveResult();
2721 AI_ONGROUND = physicsObj.OnGround();
2725 =====================
2727 =====================
2729 void idAI::AnimMove( void ) {
2734 monsterMoveResult_t moveResult;
2737 idVec3 oldorigin = physicsObj.GetOrigin();
2738 idMat3 oldaxis = viewAxis;
2742 if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
2743 move.lastMoveOrigin.Zero();
2744 move.lastMoveTime = gameLocal.time;
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 );
2759 TurnToward( goalPos );
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;
2771 delta = move.moveDest - oldorigin;
2773 StopMove( MOVE_STATUS_DONE );
2775 } else if ( allowMove ) {
2776 GetMoveDelta( oldaxis, viewAxis, delta );
2781 if ( move.moveCommand == MOVE_TO_POSITION ) {
2782 goalDelta = move.moveDest - oldorigin;
2783 goalDist = goalDelta.LengthFast();
2784 if ( goalDist < delta.LengthFast() ) {
2790 physicsObj.UseFlyMove( false );
2792 physicsObj.SetDelta( delta );
2793 physicsObj.ForceDeltaMove( disableGravity );
2797 if ( ai_debugMove.GetBool() ) {
2798 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2801 moveResult = physicsObj.GetMoveResult();
2802 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2803 DirectDamage( attack, enemy.GetEntity() );
2805 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2806 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2807 KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2813 AI_ONGROUND = physicsObj.OnGround();
2815 idVec3 org = physicsObj.GetOrigin();
2816 if ( oldorigin != org ) {
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 );
2829 =====================
2831 =====================
2833 idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) {
2834 idVec3 predictedPos;
2838 // predict our position
2839 predictedPos = org + vel * prediction;
2840 goalDelta = goal - predictedPos;
2841 seekVel = goalDelta * MS2SEC( gameLocal.msec );
2847 =====================
2849 =====================
2851 void idAI::SlideMove( void ) {
2856 monsterMoveResult_t moveResult;
2859 idVec3 oldorigin = physicsObj.GetOrigin();
2860 idMat3 oldaxis = viewAxis;
2864 if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
2865 move.lastMoveOrigin.Zero();
2866 move.lastMoveTime = gameLocal.time;
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 );
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 );
2886 goalPos = move.moveDest;
2887 StopMove( MOVE_STATUS_DONE );
2891 if ( move.moveCommand == MOVE_TO_POSITION ) {
2892 goalDelta = move.moveDest - oldorigin;
2893 goalDist = goalDelta.LengthFast();
2894 if ( goalDist < delta.LengthFast() ) {
2899 idVec3 vel = physicsObj.GetLinearVelocity();
2901 idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION;
2903 // seek the goal position
2904 goalDelta = goalPos - predictedPos;
2905 vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
2906 vel += goalDelta * MS2SEC( gameLocal.msec );
2909 vel.Truncate( fly_speed );
2911 physicsObj.SetLinearVelocity( vel );
2912 physicsObj.UseVelocityMove( true );
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() );
2926 if ( ai_debugMove.GetBool() ) {
2927 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2930 moveResult = physicsObj.GetMoveResult();
2931 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2932 DirectDamage( attack, enemy.GetEntity() );
2934 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2935 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2936 KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2942 AI_ONGROUND = physicsObj.OnGround();
2944 idVec3 org = physicsObj.GetOrigin();
2945 if ( oldorigin != org ) {
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 );
2958 =====================
2959 idAI::AdjustFlyingAngles
2960 =====================
2962 void idAI::AdjustFlyingAngles( void ) {
2968 vel = physicsObj.GetLinearVelocity();
2970 speed = vel.Length();
2971 if ( speed < 5.0f ) {
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;
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;
2990 fly_roll = fly_roll * 0.95f + roll * 0.05f;
2991 fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
2993 if ( flyTiltJoint != INVALID_JOINT ) {
2994 animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
2996 viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3();
3001 =====================
3003 =====================
3005 void idAI::AddFlyBob( idVec3 &vel ) {
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 );
3021 =====================
3022 idAI::AdjustFlyHeight
3023 =====================
3025 void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) {
3026 const idVec3 &origin = physicsObj.GetOrigin();
3027 predictedPath_t path;
3034 // make sure we're not flying too high to get through doors
3036 if ( origin.z > goalPos.z ) {
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 );
3046 if ( ai_debugMove.GetBool() ) {
3047 gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
3052 // make sure we don't fly too low
3055 enemyEnt = enemy.GetEntity();
3057 end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset;
3059 // just use the default eye height for the player
3060 end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset;
3063 gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this );
3064 vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION );
3069 =====================
3071 =====================
3073 void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) {
3076 // seek the goal position
3077 seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION );
3078 seekVel *= fly_seek_scale;
3083 =====================
3084 idAI::AdjustFlySpeed
3085 =====================
3087 void idAI::AdjustFlySpeed( idVec3 &vel ) {
3091 vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
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 ) {
3098 } else if ( move.speed && ( speed > move.speed ) ) {
3106 =====================
3108 =====================
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() );
3125 =====================
3127 =====================
3129 void idAI::FlyMove( void ) {
3135 if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
3136 StopMove( MOVE_STATUS_DONE );
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 );
3143 if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
3144 idVec3 vel = physicsObj.GetLinearVelocity();
3146 if ( GetMovePos( goalPos ) ) {
3147 CheckObstacleAvoidance( goalPos, newDest );
3152 FlySeekGoal( vel, goalPos );
3158 if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
3159 AdjustFlyHeight( vel, goalPos );
3162 AdjustFlySpeed( vel );
3164 physicsObj.SetLinearVelocity( vel );
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 );
3178 monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
3179 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3180 DirectDamage( attack, enemy.GetEntity() );
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;
3191 idVec3 org = physicsObj.GetOrigin();
3192 if ( oldorigin != org ) {
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 );
3208 =====================
3210 =====================
3212 void idAI::StaticMove( void ) {
3213 idActor *enemyEnt = enemy.GetEntity();
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 );
3228 physicsObj.ForceDeltaMove( true ); // disable gravity
3231 AI_ONGROUND = false;
3233 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3234 DirectDamage( attack, enemyEnt );
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 );
3245 /***********************************************************************
3249 ***********************************************************************/
3252 =====================
3254 =====================
3256 int idAI::ReactionTo( const idEntity *ent ) {
3258 if ( ent->fl.hidden ) {
3259 // ignore hidden entities
3260 return ATTACK_IGNORE;
3263 if ( !ent->IsType( idActor::Type ) ) {
3264 return ATTACK_IGNORE;
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;
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;
3279 return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
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;
3288 return ATTACK_IGNORE;
3293 =====================
3295 =====================
3297 bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3300 AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location );
3306 // ignore damage from self
3307 if ( attacker != this ) {
3309 AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3311 AI_SPECIAL_DAMAGE = 0;
3314 if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) {
3315 actor = ( idActor * )attacker;
3316 if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) {
3317 gameLocal.AlertAI( actor );
3323 return ( AI_PAIN != 0 );
3328 =====================
3329 idAI::SpawnParticles
3330 =====================
3332 void idAI::SpawnParticles( const char *keyName ) {
3333 const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL );
3335 particleEmitter_t pe;
3337 idStr particleName = kv->GetValue();
3339 if ( particleName.Length() ) {
3341 idStr jointName = kv->GetValue();
3342 int dash = jointName.Find('-');
3344 particleName = particleName.Left( dash );
3345 jointName = jointName.Right( jointName.Length() - dash - 1 );
3348 SpawnParticlesOnJoint( pe, particleName, jointName );
3349 particles.Append( pe );
3352 kv = spawnArgs.MatchPrefix( keyName, kv );
3357 =====================
3358 idAI::SpawnParticlesOnJoint
3359 =====================
3361 const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) {
3365 if ( *particleName == '\0' ) {
3366 memset( &pe, 0, sizeof( pe ) );
3370 pe.joint = animator.GetJointHandle( jointName );
3371 if ( pe.joint == INVALID_JOINT ) {
3372 gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() );
3376 animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis );
3377 origin = renderEntity.origin + origin * renderEntity.axis;
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
3384 pe.time = gameLocal.time;
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*/ );
3394 =====================
3396 =====================
3398 void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3400 const char *modelDeath;
3402 // make sure the monster is activated
3405 if ( g_debugDamage.GetBool() ) {
3406 gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ),
3407 GetDamageGroup( location ) );
3411 AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3413 AI_SPECIAL_DAMAGE = 0;
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 );
3429 disableGravity = false;
3430 move.moveType = MOVETYPE_DEAD;
3431 af_push_moveables = false;
3433 physicsObj.UseFlyMove( false );
3434 physicsObj.ForceDeltaMove( false );
3436 // end our looping ambient sound
3437 StopSound( SND_CHANNEL_AMBIENT, false );
3439 if ( attacker && attacker->IsType( idActor::Type ) ) {
3440 gameLocal.AlertAI( ( idActor * )attacker );
3444 ActivateTargets( attacker );
3446 RemoveAttachments();
3448 StopMove( MOVE_STATUS_DONE );
3453 // make monster nonsolid
3454 physicsObj.SetContents( 0 );
3455 physicsObj.GetClipModel()->Unlink();
3459 if ( StartRagdoll() ) {
3460 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
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();
3472 // No grabbing if "model_death"
3477 restartParticles = false;
3479 state = GetScriptFunction( "state_Killed" );
3483 const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL );
3487 args.Set( "classname", kv->GetValue() );
3488 args.Set( "origin", physicsObj.GetOrigin().ToString() );
3489 gameLocal.SpawnEntityDef( args );
3490 kv = spawnArgs.MatchPrefix( "def_drops", kv );
3494 if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) {
3495 static_cast< idPlayer* >( attacker )->AddAIKill();
3500 if(spawnArgs.GetBool("harvest_on_death")) {
3501 const idDict *harvestDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_harvest_type"), false );
3504 gameLocal.SpawnEntityDef( *harvestDef, &temp, false );
3505 harvestEnt = static_cast<idHarvestable *>(temp);
3509 if(harvestEnt.GetEntity()) {
3510 //Let the harvest entity set itself up
3511 harvestEnt.GetEntity()->Init(this);
3512 harvestEnt.GetEntity()->BecomeActive( TH_THINK );
3518 /***********************************************************************
3522 ***********************************************************************/
3525 =====================
3527 =====================
3529 void idAI::PlayCinematic( void ) {
3530 const char *animname;
3532 if ( current_cinematic >= num_cinematics ) {
3533 if ( g_debugCinematic.GetBool() ) {
3534 gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
3536 if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) {
3539 current_cinematic = 0;
3540 ActivateTargets( gameLocal.GetLocalPlayer() );
3541 fl.neverDormant = false;
3546 current_cinematic++;
3548 allowJointMod = false;
3549 allowEyeFocus = false;
3551 spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname );
3553 gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() );
3557 if ( g_debugCinematic.GetBool() ) {
3558 gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname );
3561 headAnim.animBlendFrames = 0;
3562 headAnim.lastAnimBlendFrames = 0;
3563 headAnim.BecomeIdle();
3565 legsAnim.animBlendFrames = 0;
3566 legsAnim.lastAnimBlendFrames = 0;
3567 legsAnim.BecomeIdle();
3569 torsoAnim.animBlendFrames = 0;
3570 torsoAnim.lastAnimBlendFrames = 0;
3571 ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname );
3573 // make sure our model gets updated
3574 animator.ForceUpdate();
3576 // update the anim bounds
3581 if ( head.GetEntity() ) {
3582 // since the body anim was updated, we need to run physics to update the position of the head
3585 // make sure our model gets updated
3586 head.GetEntity()->GetAnimator()->ForceUpdate();
3588 // update the anim bounds
3589 head.GetEntity()->UpdateAnimation();
3590 head.GetEntity()->UpdateVisuals();
3591 head.GetEntity()->Present();
3594 fl.neverDormant = true;
3598 =====================
3601 Notifies the script that a monster has been activated by a trigger or flashlight
3602 =====================
3604 void idAI::Activate( idEntity *activator ) {
3608 // ignore it when they're dead
3612 // make sure he's not dormant
3615 if ( num_cinematics ) {
3618 AI_ACTIVATED = true;
3619 if ( !activator || !activator->IsType( idPlayer::Type ) ) {
3620 player = gameLocal.GetLocalPlayer();
3622 player = static_cast<idPlayer *>( activator );
3625 if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) {
3629 // update the script in cinematics so that entities don't start anims or show themselves a frame late.
3633 // make sure our model gets updated
3634 animator.ForceUpdate();
3636 // update the anim bounds
3641 if ( head.GetEntity() ) {
3642 // since the body anim was updated, we need to run physics to update the position of the head
3645 // make sure our model gets updated
3646 head.GetEntity()->GetAnimator()->ForceUpdate();
3648 // update the anim bounds
3649 head.GetEntity()->UpdateAnimation();
3650 head.GetEntity()->UpdateVisuals();
3651 head.GetEntity()->Present();
3658 =====================
3660 =====================
3662 void idAI::EnemyDead( void ) {
3664 AI_ENEMY_DEAD = true;
3668 =====================
3670 =====================
3672 void idAI::TalkTo( idActor *actor ) {
3673 if ( talk_state != TALK_OK ) {
3678 // Wake up monsters that are pretending to be NPC's
3679 if ( team == 1 && actor->team != team ) {
3680 ProcessEvent( &EV_Activate, actor );
3693 =====================
3695 =====================
3697 idActor *idAI::GetEnemy( void ) const {
3698 return enemy.GetEntity();
3702 =====================
3704 =====================
3706 talkState_t idAI::GetTalkState( void ) const {
3707 if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) {
3717 =====================
3718 idAI::TouchedByFlashlight
3719 =====================
3721 void idAI::TouchedByFlashlight( idActor *flashlight_owner ) {
3722 if ( wakeOnFlashlight ) {
3723 Activate( flashlight_owner );
3728 =====================
3730 =====================
3732 void idAI::ClearEnemy( void ) {
3733 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3734 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
3739 AI_ENEMY_IN_FOV = false;
3740 AI_ENEMY_VISIBLE = false;
3741 AI_ENEMY_DEAD = true;
3747 =====================
3748 idAI::EnemyPositionValid
3749 =====================
3751 bool idAI::EnemyPositionValid( void ) const {
3756 if ( !enemy.GetEntity() ) {
3760 if ( AI_ENEMY_VISIBLE ) {
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
3774 =====================
3775 idAI::SetEnemyPosition
3776 =====================
3778 void idAI::SetEnemyPosition( void ) {
3779 idActor *enemyEnt = enemy.GetEntity();
3782 int lastVisibleReachableEnemyAreaNum;
3791 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3792 lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset();
3793 lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin();
3794 if ( move.moveType == MOVETYPE_FLY ) {
3795 pos = lastVisibleEnemyPos;
3798 onGround = enemyEnt->GetFloorPos( 64.0f, pos );
3799 if ( enemyEnt->OnLadder() ) {
3805 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3806 AI_DEST_UNREACHABLE = true;
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.
3814 lastVisibleReachableEnemyPos = lastVisibleEnemyPos;
3815 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3816 AI_DEST_UNREACHABLE = false;
3821 lastVisibleReachableEnemyAreaNum = move.toAreaNum;
3822 enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f );
3823 if ( !enemyAreaNum ) {
3824 enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3825 pos = lastReachableEnemyPos;
3827 if ( !enemyAreaNum ) {
3828 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3829 AI_DEST_UNREACHABLE = true;
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;
3841 } else if ( move.moveCommand == MOVE_TO_ENEMY ) {
3842 AI_DEST_UNREACHABLE = true;
3847 if ( move.moveCommand == MOVE_TO_ENEMY ) {
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;
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 );
3868 =====================
3869 idAI::UpdateEnemyPosition
3870 =====================
3872 void idAI::UpdateEnemyPosition( void ) {
3873 idActor *enemyEnt = enemy.GetEntity();
3877 predictedPath_t predictedPath;
3885 const idVec3 &org = physicsObj.GetOrigin();
3887 if ( move.moveType == MOVETYPE_FLY ) {
3888 enemyPos = enemyEnt->GetPhysics()->GetOrigin();
3891 onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
3892 if ( enemyEnt->OnLadder() ) {
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.
3902 lastReachableEnemyPos = enemyPos;
3904 enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
3905 if ( enemyAreaNum ) {
3906 areaNum = PointReachableAreaNum( org );
3907 if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
3908 lastReachableEnemyPos = enemyPos;
3914 AI_ENEMY_IN_FOV = false;
3915 AI_ENEMY_VISIBLE = false;
3917 if ( CanSee( enemyEnt, false ) ) {
3918 AI_ENEMY_VISIBLE = true;
3919 if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
3920 AI_ENEMY_IN_FOV = true;
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 ) ) {
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 );
3941 =====================
3943 =====================
3945 void idAI::SetEnemy( idActor *newEnemy ) {
3953 AI_ENEMY_DEAD = false;
3956 } else if ( enemy.GetEntity() != newEnemy ) {
3958 enemyNode.AddToEnd( newEnemy->enemyList );
3959 if ( newEnemy->health <= 0 ) {
3963 // let the monster know where the enemy is
3964 newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum );
3968 lastReachableEnemyPos = lastVisibleEnemyPos;
3969 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3970 enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3971 if ( aas && enemyAreaNum ) {
3972 aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos );
3973 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3980 idAI::FirstVisiblePointOnPath
3983 idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const {
3984 int i, areaNum, targetAreaNum, curAreaNum, travelTime;
3986 idReachability *reach;
3992 areaNum = PointReachableAreaNum( origin );
3993 targetAreaNum = PointReachableAreaNum( target );
3995 if ( !areaNum || !targetAreaNum ) {
3999 if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) {
4003 curAreaNum = areaNum;
4006 for( i = 0; i < 10; i++ ) {
4008 if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) {
4016 curAreaNum = reach->toAreaNum;
4017 curOrigin = reach->end;
4019 if ( PointVisible( curOrigin ) ) {
4029 idAI::CalculateAttackOffsets
4031 calculate joint positions on attack frames so we can do proper "can hit" tests
4034 void idAI::CalculateAttackOffsets( void ) {
4035 const idDeclModelDef *modelDef;
4039 const frameCommand_t *command;
4042 jointHandle_t joint;
4044 modelDef = animator.ModelDef();
4048 num = modelDef->NumAnims();
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 );
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();
4059 for( i = 1; i <= num; i++ ) {
4060 missileLaunchOffset[ i ].Zero();
4061 anim = modelDef->GetAnim( i );
4063 frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
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() );
4069 GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
4074 animator.RemoveOriginOffset( true );
4078 =====================
4079 idAI::CreateProjectileClipModel
4080 =====================
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 ) );
4091 =====================
4093 =====================
4095 bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
4102 // if no aimAtEnt or projectile set
4103 if ( !aimAtEnt || !projectileDef ) {
4104 aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
4108 if ( projectileClipModel == NULL ) {
4109 CreateProjectileClipModel();
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 );
4117 targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
4118 targetPos2 = targetPos1;
4122 if ( this->team == 0 && !idStr::Cmp( aimAtEnt->GetEntityDefName(), "monster_demon_vulgar" ) ) {
4123 targetPos1.z -= 28.f;
4124 targetPos2.z -= 12.f;
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 ) ) {
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 );
4145 =====================
4147 =====================
4149 void idAI::BeginAttack( const char *name ) {
4151 lastAttackTime = gameLocal.time;
4155 =====================
4157 =====================
4159 void idAI::EndAttack( void ) {
4164 =====================
4165 idAI::CreateProjectile
4166 =====================
4168 idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) {
4170 const char *clsname;
4172 if ( !projectile.GetEntity() ) {
4173 gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
4175 clsname = projectileDef->GetString( "classname" );
4176 gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
4179 if ( !ent->IsType( idProjectile::Type ) ) {
4180 clsname = ent->GetClassname();
4181 gameLocal.Error( "'%s' is not an idProjectile", clsname );
4183 projectile = ( idProjectile * )ent;
4186 projectile.GetEntity()->Create( this, pos, dir );
4188 return projectile.GetEntity();
4192 =====================
4193 idAI::RemoveProjectile
4194 =====================
4196 void idAI::RemoveProjectile( void ) {
4197 if ( projectile.GetEntity() ) {
4198 projectile.GetEntity()->PostEventMS( &EV_Remove, 0 );
4204 =====================
4205 idAI::LaunchProjectile
4206 =====================
4208 idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
4213 idBounds projBounds;
4215 const idClipModel *projClip;
4216 float attack_accuracy;
4218 float projectile_spread;
4223 int num_projectiles;
4231 idProjectile *lastProjectile;
4233 if ( !projectileDef ) {
4234 gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
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" );
4243 forceMuzzle = spawnArgs.GetBool( "forceMuzzle", "0" );
4246 GetMuzzle( jointname, muzzle, axis );
4248 if ( !projectile.GetEntity() ) {
4249 CreateProjectile( muzzle, axis[ 0 ] );
4252 lastProjectile = projectile.GetEntity();
4254 if ( target != NULL ) {
4255 tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
4257 axis = tmp.ToMat3();
4262 // rotate it because the cone points up by default
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 );
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 ];
4284 start = ownerBounds.GetCenter();
4287 // projectile bounds bigger than the owner bounds, so just start it from the center
4288 start = ownerBounds.GetCenter();
4291 gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
4295 // set aiming direction
4296 GetAimDir( muzzle, target, this, dir );
4297 ang = dir.ToAngles();
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;
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;
4315 axis = ang.ToMat3();
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 ) );
4325 // launch the projectile
4326 if ( !projectile.GetEntity() ) {
4327 CreateProjectile( muzzle, dir );
4329 lastProjectile = projectile.GetEntity();
4330 lastProjectile->Launch( muzzle, dir, vec3_origin );
4334 TriggerWeaponEffects( muzzle );
4336 lastAttackTime = gameLocal.time;
4338 return lastProjectile;
4343 idAI::DamageFeedback
4345 callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
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.
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
4356 } else if ( victim == enemy.GetEntity() ) {
4357 AI_HIT_ENEMY = true;
4362 =====================
4365 Causes direct damage to an entity
4367 kickDir is specified in the monster's coordinate system, and gives the direction
4368 that the view kick and knockback should go
4369 =====================
4371 void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) {
4372 const idDict *meleeDef;
4374 const idSoundShader *shader;
4376 meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4378 gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() );
4381 if ( !ent->fl.takedamage ) {
4382 const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" ));
4383 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4390 p = meleeDef->GetString( "snd_hit" );
4392 shader = declManager->FindSound( p );
4393 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4397 meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4399 idVec3 globalKickDir;
4400 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4402 ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4404 // end the attack if we're a multiframe attack
4409 =====================
4411 =====================
4413 bool idAI::TestMelee( void ) const {
4415 idActor *enemyEnt = enemy.GetEntity();
4417 if ( !enemyEnt || !melee_range ) {
4421 //FIXME: make work with gravity vector
4422 idVec3 org = physicsObj.GetOrigin();
4423 const idBounds &myBounds = physicsObj.GetBounds();
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 );
4435 idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
4436 idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds();
4437 enemyBounds.TranslateSelf( enemyOrg );
4439 if ( ai_debugMove.GetBool() ) {
4440 gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec );
4443 if ( !bounds.IntersectsBounds( enemyBounds ) ) {
4447 idVec3 start = GetEyePosition();
4448 idVec3 end = enemyEnt->GetEyePosition();
4450 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4451 if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) {
4459 =====================
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.
4466 kickDir is specified in the monster's coordinate system, and gives the direction
4467 that the view kick and knockback should go
4468 =====================
4470 bool idAI::AttackMelee( const char *meleeDefName ) {
4471 const idDict *meleeDef;
4472 idActor *enemyEnt = enemy.GetEntity();
4474 const idSoundShader *shader;
4476 meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4478 gameLocal.Error( "Unknown melee '%s'", meleeDefName );
4482 p = meleeDef->GetString( "snd_miss" );
4484 shader = declManager->FindSound( p );
4485 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
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 ) {
4495 idPlayer *player = static_cast<idPlayer*>( enemyEnt );
4496 player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor );
4498 if ( enemyEnt->health <= damage ) {
4499 int t = gameLocal.time - player->lastSavingThrowTime;
4500 if ( t > SAVING_THROW_TIME ) {
4501 player->lastSavingThrowTime = gameLocal.time;
4505 gameLocal.Printf( "Saving throw.\n" );
4511 // make sure the trace can actually hit the enemy
4512 if ( forceMiss || !TestMelee() ) {
4514 p = meleeDef->GetString( "snd_miss" );
4516 shader = declManager->FindSound( p );
4517 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4525 p = meleeDef->GetString( "snd_hit" );
4527 shader = declManager->FindSound( p );
4528 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4532 meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4534 idVec3 globalKickDir;
4535 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4537 enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4539 lastAttackTime = gameLocal.time;
4549 void idAI::PushWithAF( void ) {
4551 afTouch_t touchList[ MAX_GENTITIES ];
4552 idEntity *pushed_ents[ MAX_GENTITIES ];
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 ) ) {
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 ) {
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();
4577 if ( attack.Length() && ent->IsType( idActor::Type ) ) {
4578 ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT );
4580 ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() );
4586 /***********************************************************************
4590 ***********************************************************************/
4597 void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) {
4598 jointHandle_t joint;
4600 if ( !jointname || !jointname[ 0 ] ) {
4601 muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
4602 muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
4604 joint = animator.GetJointHandle( jointname );
4605 if ( joint == INVALID_JOINT ) {
4606 gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
4608 GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
4614 idAI::TriggerWeaponEffects
4617 void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) {
4621 if ( !g_muzzleFlash.GetBool() ) {
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();
4630 if ( flashJointWorld != INVALID_JOINT ) {
4631 GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
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 );
4639 worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
4641 muzzleFlashEnd = gameLocal.time + flashTime;
4649 idAI::UpdateMuzzleFlash
4652 void idAI::UpdateMuzzleFlash( void ) {
4653 if ( worldMuzzleFlashHandle != -1 ) {
4654 if ( gameLocal.time >= muzzleFlashEnd ) {
4655 gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
4656 worldMuzzleFlashHandle = -1;
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 );
4673 void idAI::Hide( void ) {
4675 fl.takedamage = false;
4676 physicsObj.SetContents( 0 );
4677 physicsObj.GetClipModel()->Unlink();
4678 StopSound( SND_CHANNEL_AMBIENT, false );
4681 AI_ENEMY_IN_FOV = false;
4682 AI_ENEMY_VISIBLE = false;
4683 StopMove( MOVE_STATUS_DONE );
4691 void idAI::Show( void ) {
4693 if ( spawnArgs.GetBool( "big_monster" ) ) {
4694 physicsObj.SetContents( 0 );
4695 } else if ( use_combat_bbox ) {
4696 physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
4698 physicsObj.SetContents( CONTENTS_BODY );
4700 physicsObj.GetClipModel()->Link( gameLocal.clip );
4701 fl.takedamage = !spawnArgs.GetBool( "noDamage" );
4703 StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
4707 =====================
4709 =====================
4711 void idAI::SetChatSound( void ) {
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" ) );
4728 if ( snd && *snd ) {
4729 chat_snd = declManager->FindSound( snd );
4731 // set the next chat time
4732 chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4740 idAI::CanPlayChatterSounds
4742 Used for playing chatter sounds on monsters.
4745 bool idAI::CanPlayChatterSounds( void ) const {
4754 if ( enemy.GetEntity() ) {
4758 if ( spawnArgs.GetBool( "no_idle_chatter" ) ) {
4766 =====================
4768 =====================
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 ) ) {
4776 StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL );
4778 // set the next chat time
4779 chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4783 =====================
4784 idAI::UpdateParticles
4785 =====================
4787 void idAI::UpdateParticles( void ) {
4788 if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
4792 int particlesAlive = 0;
4793 for ( int i = 0; i < particles.Num(); i++ ) {
4795 // Smoke particles on AI characters will always be "slow", even when held by grabber
4796 SetTimeState ts(TIME_GROUP1);
4798 if ( particles[i].particle && particles[i].time ) {
4800 if (af.IsActive()) {
4801 realAxis = mat3_identity;
4802 realVector = GetPhysics()->GetOrigin();
4804 animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis );
4805 realAxis *= renderEntity.axis;
4806 realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() );
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;
4813 particles[i].time = 0;
4819 if ( particlesAlive == 0 ) {
4820 BecomeInactive( TH_UPDATEPARTICLES );
4826 =====================
4827 idAI::TriggerParticles
4828 =====================
4830 void idAI::TriggerParticles( const char *jointName ) {
4831 jointHandle_t jointNum;
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 );
4843 void idAI::TriggerFX( const char* joint, const char* fx ) {
4845 if( !strcmp(joint, "origin") ) {
4846 idEntityFx::StartFx( fx, NULL, NULL, this, true );
4848 idVec3 joint_origin;
4850 jointHandle_t jointNum;
4851 jointNum = animator.GetJointHandle( joint );
4853 if ( jointNum == INVALID_JOINT ) {
4854 gameLocal.Warning( "Unknown fx joint '%s' on entity %s", joint, name.c_str() );
4858 GetJointWorldTransform( jointNum, gameLocal.time, joint_origin, joint_axis );
4859 idEntityFx::StartFx( fx, &joint_origin, &joint_axis, this, true );
4863 idEntity* idAI::StartEmitter( const char* name, const char* joint, const char* particle ) {
4865 idEntity* existing = GetEmitter(name);
4870 jointHandle_t jointNum;
4871 jointNum = animator.GetJointHandle( joint );
4876 GetJointWorldTransform( jointNum, gameLocal.time, offset, axis );
4878 /*animator.GetJointTransform( jointNum, gameLocal.time, offset, axis );
4879 offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis();
4880 axis = axis * GetPhysics()->GetAxis();*/
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);
4893 gameLocal.SpawnEntityDef(args, &ent, false);
4895 ent->GetPhysics()->SetOrigin(offset);
4896 //ent->GetPhysics()->SetAxis(axis);
4898 // align z-axis of model with the direction
4900 axis = (viewAxis[ 0 ] * physicsObj.GetGravityAxis()).ToMat3();
4905 ent->GetPhysics()->SetAxis(axis);*/
4907 axis = physicsObj.GetGravityAxis();
4908 ent->GetPhysics()->SetAxis(axis);
4911 ent->GetPhysics()->GetClipModel()->SetOwner( this );
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);
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;
4929 idEntity* idAI::GetEmitter( const char* name ) {
4930 funcEmitter_t* emitter;
4931 funcEmitters.Get(name, &emitter);
4933 return emitter->particle;
4938 void idAI::StopEmitter( const char* name ) {
4939 funcEmitter_t* emitter;
4940 funcEmitters.Get(name, &emitter);
4942 emitter->particle->Unbind();
4943 emitter->particle->PostEventMS( &EV_Remove, 0 );
4944 funcEmitters.Remove(name);
4951 /***********************************************************************
4955 ***********************************************************************/
4959 idAI::UpdateAnimationControllers
4962 bool idAI::UpdateAnimationControllers( void ) {
4968 idVec3 orientationJointPos;
4970 idAngles newLookAng;
4974 idMat3 orientationJointAxis;
4975 idAFAttachment *headEnt = head.GetEntity();
4980 float orientationJointYaw;
4983 return idActor::UpdateAnimationControllers();
4986 if ( orientationJoint == INVALID_JOINT ) {
4987 orientationJointAxis = viewAxis;
4988 orientationJointPos = physicsObj.GetOrigin();
4989 orientationJointYaw = current_yaw;
4991 GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
4992 orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
4993 orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
4996 if ( focusJoint != INVALID_JOINT ) {
4998 headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
5000 GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
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 );
5007 eyepos = GetEyePosition();
5011 CopyJointsFromBodyToHead();
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();
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();
5034 focusPos = focusEnt->GetPhysics()->GetOrigin();
5037 currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
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;
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 );
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;
5058 diff = newLookAng - lookAng;
5060 if ( eyeAng != diff ) {
5062 eyeAng.Clamp( eyeMin, eyeMax );
5063 idAngles angDelta = diff - eyeAng;
5064 if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
5065 alignHeadTime = gameLocal.time;
5067 alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
5071 if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
5072 alignHeadTime = gameLocal.time;
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 );
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;
5087 if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
5088 if ( diff.yaw > 180.0f ) {
5090 } else if ( diff.yaw <= -180.0f ) {
5094 lookAng = lookAng + diff * headFocusRate;
5095 lookAng.Normalize180();
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() );
5104 if ( move.moveType == MOVETYPE_FLY ) {
5106 AdjustFlyingAngles();
5110 idAnimator *headAnimator = headEnt->GetAnimator();
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 );
5120 headAnimator->ClearJoint( leftEyeJoint );
5121 headAnimator->ClearJoint( rightEyeJoint );
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 );
5132 animator.ClearJoint( leftEyeJoint );
5133 animator.ClearJoint( rightEyeJoint );
5140 /***********************************************************************
5144 ***********************************************************************/
5146 const idEventDef EV_CombatNode_MarkUsed( "markUsed" );
5148 CLASS_DECLARATION( idEntity, idCombatNode )
5149 EVENT( EV_CombatNode_MarkUsed, idCombatNode::Event_MarkUsed )
5150 EVENT( EV_Activate, idCombatNode::Event_Activate )
5154 =====================
5155 idCombatNode::idCombatNode
5156 =====================
5158 idCombatNode::idCombatNode( void ) {
5171 =====================
5173 =====================
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 );
5188 =====================
5189 idCombatNode::Restore
5190 =====================
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 );
5205 =====================
5207 =====================
5209 void idCombatNode::Spawn( void ) {
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" );
5220 const idVec3 &org = GetPhysics()->GetOrigin() + offset;
5221 min_height = org.z - height * 0.5f;
5222 max_height = min_height + height;
5224 const idMat3 &axis = GetPhysics()->GetAxis();
5225 yaw = axis[ 0 ].ToYaw();
5227 idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
5228 cone_left = leftang.ToForward();
5230 idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
5231 cone_right = rightang.ToForward();
5233 disabled = spawnArgs.GetBool( "start_off" );
5237 =====================
5238 idCombatNode::IsDisabled
5239 =====================
5241 bool idCombatNode::IsDisabled( void ) const {
5246 =====================
5247 idCombatNode::DrawDebugInfo
5248 =====================
5250 void idCombatNode::DrawDebugInfo( void ) {
5253 idPlayer *player = gameLocal.GetLocalPlayer();
5255 idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) );
5257 for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
5258 if ( !ent->IsType( idCombatNode::Type ) ) {
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;
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;
5275 bounds[ 1 ].z = node->max_height;
5277 leftDir.NormalizeFast();
5278 rightDir.NormalizeFast();
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;
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 );
5300 =====================
5301 idCombatNode::EntityInView
5302 =====================
5304 bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) {
5305 if ( !actor || ( actor->health <= 0 ) ) {
5309 const idBounds &bounds = actor->GetPhysics()->GetBounds();
5310 if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) {
5314 const idVec3 &org = GetPhysics()->GetOrigin() + offset;
5315 const idMat3 &axis = GetPhysics()->GetAxis();
5316 idVec3 dir = pos - org;
5317 float dist = dir * axis[ 0 ];
5319 if ( ( dist < min_dist ) || ( dist > max_dist ) ) {
5323 float left_dot = dir * cone_left;
5324 if ( left_dot < 0.0f ) {
5328 float right_dot = dir * cone_right;
5329 if ( right_dot < 0.0f ) {
5337 =====================
5338 idCombatNode::Event_Activate
5339 =====================
5341 void idCombatNode::Event_Activate( idEntity *activator ) {
5342 disabled = !disabled;
5346 =====================
5347 idCombatNode::Event_MarkUsed
5348 =====================
5350 void idCombatNode::Event_MarkUsed( void ) {
5351 if ( spawnArgs.GetBool( "use_once" ) ) {