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 current_cinematic = 0;
353 allowEyeFocus = true;
355 allowJointMod = true;
359 forceAlignHeadTime = 0;
361 currentFocusPos.Zero();
372 flashJointWorld = INVALID_JOINT;
374 focusJoint = INVALID_JOINT;
375 orientationJoint = INVALID_JOINT;
376 flyTiltJoint = INVALID_JOINT;
378 eyeVerticalOffset = 0.0f;
379 eyeHorizontalOffset = 0.0f;
381 headFocusRate = 0.0f;
386 =====================
388 =====================
391 delete projectileClipModel;
392 DeconstructScriptObject();
394 if ( worldMuzzleFlashHandle != -1 ) {
395 gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
396 worldMuzzleFlashHandle = -1;
401 =====================
403 =====================
405 void idAI::Save( idSaveGame *savefile ) const {
408 savefile->WriteInt( travelFlags );
409 move.Save( savefile );
410 savedMove.Save( savefile );
411 savefile->WriteFloat( kickForce );
412 savefile->WriteBool( ignore_obstacles );
413 savefile->WriteFloat( blockedRadius );
414 savefile->WriteInt( blockedMoveTime );
415 savefile->WriteInt( blockedAttackTime );
417 savefile->WriteFloat( ideal_yaw );
418 savefile->WriteFloat( current_yaw );
419 savefile->WriteFloat( turnRate );
420 savefile->WriteFloat( turnVel );
421 savefile->WriteFloat( anim_turn_yaw );
422 savefile->WriteFloat( anim_turn_amount );
423 savefile->WriteFloat( anim_turn_angles );
425 savefile->WriteStaticObject( physicsObj );
427 savefile->WriteFloat( fly_speed );
428 savefile->WriteFloat( fly_bob_strength );
429 savefile->WriteFloat( fly_bob_vert );
430 savefile->WriteFloat( fly_bob_horz );
431 savefile->WriteInt( fly_offset );
432 savefile->WriteFloat( fly_seek_scale );
433 savefile->WriteFloat( fly_roll_scale );
434 savefile->WriteFloat( fly_roll_max );
435 savefile->WriteFloat( fly_roll );
436 savefile->WriteFloat( fly_pitch_scale );
437 savefile->WriteFloat( fly_pitch_max );
438 savefile->WriteFloat( fly_pitch );
440 savefile->WriteBool( allowMove );
441 savefile->WriteBool( allowHiddenMovement );
442 savefile->WriteBool( disableGravity );
443 savefile->WriteBool( af_push_moveables );
445 savefile->WriteBool( lastHitCheckResult );
446 savefile->WriteInt( lastHitCheckTime );
447 savefile->WriteInt( lastAttackTime );
448 savefile->WriteFloat( melee_range );
449 savefile->WriteFloat( projectile_height_to_distance_ratio );
451 savefile->WriteInt( missileLaunchOffset.Num() );
452 for( i = 0; i < missileLaunchOffset.Num(); i++ ) {
453 savefile->WriteVec3( missileLaunchOffset[ i ] );
456 idStr projectileName;
457 spawnArgs.GetString( "def_projectile", "", projectileName );
458 savefile->WriteString( projectileName );
459 savefile->WriteFloat( projectileRadius );
460 savefile->WriteFloat( projectileSpeed );
461 savefile->WriteVec3( projectileVelocity );
462 savefile->WriteVec3( projectileGravity );
463 projectile.Save( savefile );
464 savefile->WriteString( attack );
466 savefile->WriteSoundShader( chat_snd );
467 savefile->WriteInt( chat_min );
468 savefile->WriteInt( chat_max );
469 savefile->WriteInt( chat_time );
470 savefile->WriteInt( talk_state );
471 talkTarget.Save( savefile );
473 savefile->WriteInt( num_cinematics );
474 savefile->WriteInt( current_cinematic );
476 savefile->WriteBool( allowJointMod );
477 focusEntity.Save( savefile );
478 savefile->WriteVec3( currentFocusPos );
479 savefile->WriteInt( focusTime );
480 savefile->WriteInt( alignHeadTime );
481 savefile->WriteInt( forceAlignHeadTime );
482 savefile->WriteAngles( eyeAng );
483 savefile->WriteAngles( lookAng );
484 savefile->WriteAngles( destLookAng );
485 savefile->WriteAngles( lookMin );
486 savefile->WriteAngles( lookMax );
488 savefile->WriteInt( lookJoints.Num() );
489 for( i = 0; i < lookJoints.Num(); i++ ) {
490 savefile->WriteJoint( lookJoints[ i ] );
491 savefile->WriteAngles( lookJointAngles[ i ] );
494 savefile->WriteFloat( shrivel_rate );
495 savefile->WriteInt( shrivel_start );
497 savefile->WriteInt( particles.Num() );
498 for ( i = 0; i < particles.Num(); i++ ) {
499 savefile->WriteParticle( particles[i].particle );
500 savefile->WriteInt( particles[i].time );
501 savefile->WriteJoint( particles[i].joint );
503 savefile->WriteBool( restartParticles );
504 savefile->WriteBool( useBoneAxis );
506 enemy.Save( savefile );
507 savefile->WriteVec3( lastVisibleEnemyPos );
508 savefile->WriteVec3( lastVisibleEnemyEyeOffset );
509 savefile->WriteVec3( lastVisibleReachableEnemyPos );
510 savefile->WriteVec3( lastReachableEnemyPos );
511 savefile->WriteBool( wakeOnFlashlight );
513 savefile->WriteAngles( eyeMin );
514 savefile->WriteAngles( eyeMax );
516 savefile->WriteFloat( eyeVerticalOffset );
517 savefile->WriteFloat( eyeHorizontalOffset );
518 savefile->WriteFloat( eyeFocusRate );
519 savefile->WriteFloat( headFocusRate );
520 savefile->WriteInt( focusAlignTime );
522 savefile->WriteJoint( flashJointWorld );
523 savefile->WriteInt( muzzleFlashEnd );
525 savefile->WriteJoint( focusJoint );
526 savefile->WriteJoint( orientationJoint );
527 savefile->WriteJoint( flyTiltJoint );
529 savefile->WriteBool( GetPhysics() == static_cast<const idPhysics *>(&physicsObj) );
533 =====================
535 =====================
537 void idAI::Restore( idRestoreGame *savefile ) {
543 savefile->ReadInt( travelFlags );
544 move.Restore( savefile );
545 savedMove.Restore( savefile );
546 savefile->ReadFloat( kickForce );
547 savefile->ReadBool( ignore_obstacles );
548 savefile->ReadFloat( blockedRadius );
549 savefile->ReadInt( blockedMoveTime );
550 savefile->ReadInt( blockedAttackTime );
552 savefile->ReadFloat( ideal_yaw );
553 savefile->ReadFloat( current_yaw );
554 savefile->ReadFloat( turnRate );
555 savefile->ReadFloat( turnVel );
556 savefile->ReadFloat( anim_turn_yaw );
557 savefile->ReadFloat( anim_turn_amount );
558 savefile->ReadFloat( anim_turn_angles );
560 savefile->ReadStaticObject( physicsObj );
562 savefile->ReadFloat( fly_speed );
563 savefile->ReadFloat( fly_bob_strength );
564 savefile->ReadFloat( fly_bob_vert );
565 savefile->ReadFloat( fly_bob_horz );
566 savefile->ReadInt( fly_offset );
567 savefile->ReadFloat( fly_seek_scale );
568 savefile->ReadFloat( fly_roll_scale );
569 savefile->ReadFloat( fly_roll_max );
570 savefile->ReadFloat( fly_roll );
571 savefile->ReadFloat( fly_pitch_scale );
572 savefile->ReadFloat( fly_pitch_max );
573 savefile->ReadFloat( fly_pitch );
575 savefile->ReadBool( allowMove );
576 savefile->ReadBool( allowHiddenMovement );
577 savefile->ReadBool( disableGravity );
578 savefile->ReadBool( af_push_moveables );
580 savefile->ReadBool( lastHitCheckResult );
581 savefile->ReadInt( lastHitCheckTime );
582 savefile->ReadInt( lastAttackTime );
583 savefile->ReadFloat( melee_range );
584 savefile->ReadFloat( projectile_height_to_distance_ratio );
586 savefile->ReadInt( num );
587 missileLaunchOffset.SetGranularity( 1 );
588 missileLaunchOffset.SetNum( num );
589 for( i = 0; i < num; i++ ) {
590 savefile->ReadVec3( missileLaunchOffset[ i ] );
593 idStr projectileName;
594 savefile->ReadString( projectileName );
595 if ( projectileName.Length() ) {
596 projectileDef = gameLocal.FindEntityDefDict( projectileName );
598 projectileDef = NULL;
600 savefile->ReadFloat( projectileRadius );
601 savefile->ReadFloat( projectileSpeed );
602 savefile->ReadVec3( projectileVelocity );
603 savefile->ReadVec3( projectileGravity );
604 projectile.Restore( savefile );
605 savefile->ReadString( attack );
607 savefile->ReadSoundShader( chat_snd );
608 savefile->ReadInt( chat_min );
609 savefile->ReadInt( chat_max );
610 savefile->ReadInt( chat_time );
611 savefile->ReadInt( i );
612 talk_state = static_cast<talkState_t>( i );
613 talkTarget.Restore( savefile );
615 savefile->ReadInt( num_cinematics );
616 savefile->ReadInt( current_cinematic );
618 savefile->ReadBool( allowJointMod );
619 focusEntity.Restore( savefile );
620 savefile->ReadVec3( currentFocusPos );
621 savefile->ReadInt( focusTime );
622 savefile->ReadInt( alignHeadTime );
623 savefile->ReadInt( forceAlignHeadTime );
624 savefile->ReadAngles( eyeAng );
625 savefile->ReadAngles( lookAng );
626 savefile->ReadAngles( destLookAng );
627 savefile->ReadAngles( lookMin );
628 savefile->ReadAngles( lookMax );
630 savefile->ReadInt( num );
631 lookJoints.SetGranularity( 1 );
632 lookJoints.SetNum( num );
633 lookJointAngles.SetGranularity( 1 );
634 lookJointAngles.SetNum( num );
635 for( i = 0; i < num; i++ ) {
636 savefile->ReadJoint( lookJoints[ i ] );
637 savefile->ReadAngles( lookJointAngles[ i ] );
640 savefile->ReadFloat( shrivel_rate );
641 savefile->ReadInt( shrivel_start );
643 savefile->ReadInt( num );
644 particles.SetNum( num );
645 for ( i = 0; i < particles.Num(); i++ ) {
646 savefile->ReadParticle( particles[i].particle );
647 savefile->ReadInt( particles[i].time );
648 savefile->ReadJoint( particles[i].joint );
650 savefile->ReadBool( restartParticles );
651 savefile->ReadBool( useBoneAxis );
653 enemy.Restore( savefile );
654 savefile->ReadVec3( lastVisibleEnemyPos );
655 savefile->ReadVec3( lastVisibleEnemyEyeOffset );
656 savefile->ReadVec3( lastVisibleReachableEnemyPos );
657 savefile->ReadVec3( lastReachableEnemyPos );
659 savefile->ReadBool( wakeOnFlashlight );
661 savefile->ReadAngles( eyeMin );
662 savefile->ReadAngles( eyeMax );
664 savefile->ReadFloat( eyeVerticalOffset );
665 savefile->ReadFloat( eyeHorizontalOffset );
666 savefile->ReadFloat( eyeFocusRate );
667 savefile->ReadFloat( headFocusRate );
668 savefile->ReadInt( focusAlignTime );
670 savefile->ReadJoint( flashJointWorld );
671 savefile->ReadInt( muzzleFlashEnd );
673 savefile->ReadJoint( focusJoint );
674 savefile->ReadJoint( orientationJoint );
675 savefile->ReadJoint( flyTiltJoint );
677 savefile->ReadBool( restorePhysics );
679 // Set the AAS if the character has the correct gravity vector
680 idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
681 gravity *= g_gravity.GetFloat();
682 if ( gravity == gameLocal.GetGravity() ) {
691 // Link the script variables back to the scriptobject
692 LinkScriptVariables();
694 if ( restorePhysics ) {
695 RestorePhysics( &physicsObj );
700 =====================
702 =====================
704 void idAI::Spawn( void ) {
705 const char *jointname;
706 const idKeyValue *kv;
713 if ( !g_monsters.GetBool() ) {
714 PostEventMS( &EV_Remove, 0 );
718 spawnArgs.GetInt( "team", "1", team );
719 spawnArgs.GetInt( "rank", "0", rank );
720 spawnArgs.GetInt( "fly_offset", "0", fly_offset );
721 spawnArgs.GetFloat( "fly_speed", "100", fly_speed );
722 spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength );
723 spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz );
724 spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert );
725 spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale );
726 spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale );
727 spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max );
728 spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale );
729 spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max );
731 spawnArgs.GetFloat( "melee_range", "64", melee_range );
732 spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio );
734 spawnArgs.GetFloat( "turn_rate", "360", turnRate );
736 spawnArgs.GetBool( "talks", "0", talks );
737 if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) {
739 talk_state = TALK_OK;
741 talk_state = TALK_BUSY;
744 talk_state = TALK_NEVER;
747 spawnArgs.GetBool( "animate_z", "0", disableGravity );
748 spawnArgs.GetBool( "af_push_moveables", "0", af_push_moveables );
749 spawnArgs.GetFloat( "kick_force", "4096", kickForce );
750 spawnArgs.GetBool( "ignore_obstacles", "0", ignore_obstacles );
751 spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius );
752 spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime );
753 spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime );
755 spawnArgs.GetInt( "num_cinematics", "0", num_cinematics );
756 current_cinematic = 0;
758 LinkScriptVariables();
760 fl.takedamage = !spawnArgs.GetBool( "noDamage" );
763 allowHiddenMovement = false;
765 animator.RemoveOriginOffset( true );
767 // create combat collision hull for exact collision detection
770 lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" );
771 lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" );
773 lookJoints.SetGranularity( 1 );
774 lookJointAngles.SetGranularity( 1 );
775 kv = spawnArgs.MatchPrefix( "look_joint", NULL );
777 jointName = kv->GetKey();
778 jointName.StripLeadingOnce( "look_joint " );
779 joint = animator.GetJointHandle( jointName );
780 if ( joint == INVALID_JOINT ) {
781 gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() );
783 jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" );
784 jointScale.roll = 0.0f;
786 // if no scale on any component, then don't bother adding it. this may be done to
787 // zero out rotation from an inherited entitydef.
788 if ( jointScale != ang_zero ) {
789 lookJoints.Append( joint );
790 lookJointAngles.Append( jointScale );
793 kv = spawnArgs.MatchPrefix( "look_joint", kv );
796 // calculate joint positions on attack frames so we can do proper "can hit" tests
797 CalculateAttackOffsets();
799 eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" );
800 eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" );
801 eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" );
802 eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" );
803 eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" );
804 headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.1" );
805 focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) );
807 flashJointWorld = animator.GetJointHandle( "flash" );
809 if ( head.GetEntity() ) {
810 idAnimator *headAnimator = head.GetEntity()->GetAnimator();
812 jointname = spawnArgs.GetString( "bone_focus" );
814 focusJoint = headAnimator->GetJointHandle( jointname );
815 if ( focusJoint == INVALID_JOINT ) {
816 gameLocal.Warning( "Joint '%s' not found on head on '%s'", jointname, name.c_str() );
820 jointname = spawnArgs.GetString( "bone_focus" );
822 focusJoint = animator.GetJointHandle( jointname );
823 if ( focusJoint == INVALID_JOINT ) {
824 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
829 jointname = spawnArgs.GetString( "bone_orientation" );
831 orientationJoint = animator.GetJointHandle( jointname );
832 if ( orientationJoint == INVALID_JOINT ) {
833 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
837 jointname = spawnArgs.GetString( "bone_flytilt" );
839 flyTiltJoint = animator.GetJointHandle( jointname );
840 if ( flyTiltJoint == INVALID_JOINT ) {
841 gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
847 physicsObj.SetSelf( this );
848 physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
849 physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
851 if ( spawnArgs.GetBool( "big_monster" ) ) {
852 physicsObj.SetContents( 0 );
853 physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY );
855 if ( use_combat_bbox ) {
856 physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
858 physicsObj.SetContents( CONTENTS_BODY );
860 physicsObj.SetClipMask( MASK_MONSTERSOLID );
863 // move up to make sure the monster is at least an epsilon above the floor
864 physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
866 if ( num_cinematics ) {
867 physicsObj.SetGravity( vec3_origin );
869 idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
870 gravity *= g_gravity.GetFloat();
871 physicsObj.SetGravity( gravity );
874 SetPhysics( &physicsObj );
876 physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir );
877 current_yaw = local_dir.ToYaw();
878 ideal_yaw = idMath::AngleNormalize180( current_yaw );
885 projectileDef = NULL;
886 projectileClipModel = NULL;
887 idStr projectileName;
888 if ( spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) {
889 projectileDef = gameLocal.FindEntityDefDict( projectileName );
890 CreateProjectile( vec3_origin, viewAxis[ 0 ] );
891 projectileRadius = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius();
892 projectileVelocity = idProjectile::GetVelocity( projectileDef );
893 projectileGravity = idProjectile::GetGravity( projectileDef );
894 projectileSpeed = projectileVelocity.Length();
895 delete projectile.GetEntity();
900 restartParticles = true;
901 useBoneAxis = spawnArgs.GetBool( "useBoneAxis" );
902 SpawnParticles( "smokeParticleSystem" );
904 if ( num_cinematics || spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "teleport" ) || spawnArgs.GetBool( "trigger_anim" ) ) {
905 fl.takedamage = false;
906 physicsObj.SetContents( 0 );
907 physicsObj.GetClipModel()->Unlink();
910 // play a looping ambient sound if we have one
911 StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
915 gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() );
919 // set up monster chatter
922 BecomeActive( TH_THINK );
924 if ( af_push_moveables ) {
925 af.SetupPose( this, gameLocal.time );
926 af.GetPhysics()->EnableClip();
929 // init the move variables
930 StopMove( MOVE_STATUS_DONE );
935 idAI::InitMuzzleFlash
938 void idAI::InitMuzzleFlash( void ) {
942 spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader );
943 spawnArgs.GetVector( "flashColor", "0 0 0", flashColor );
944 float flashRadius = spawnArgs.GetFloat( "flashRadius" );
945 flashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) );
947 memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
949 worldMuzzleFlash.pointLight = true;
950 worldMuzzleFlash.shader = declManager->FindMaterial( shader, false );
951 worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0];
952 worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1];
953 worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2];
954 worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
955 worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
956 worldMuzzleFlash.lightRadius[0] = flashRadius;
957 worldMuzzleFlash.lightRadius[1] = flashRadius;
958 worldMuzzleFlash.lightRadius[2] = flashRadius;
960 worldMuzzleFlashHandle = -1;
968 void idAI::List_f( const idCmdArgs &args ) {
972 const char *statename;
976 gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" );
977 gameLocal.Printf( "------------------------------------------------\n" );
978 for( e = 0; e < MAX_GENTITIES; e++ ) {
979 check = static_cast<idAI *>(gameLocal.entities[ e ]);
980 if ( !check || !check->IsType( idAI::Type ) ) {
984 if ( check->state ) {
985 statename = check->state->Name();
987 statename = "NULL state";
990 gameLocal.Printf( "%4i: %-20s %-20s %s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove );
994 gameLocal.Printf( "...%d monsters\n", count );
1001 called when entity becomes dormant
1004 void idAI::DormantBegin( void ) {
1005 // since dormant happens on a timer, we wont get to update particles to
1006 // hidden through the think loop, but we need to hide them though.
1007 if ( particles.Num() ) {
1008 for ( int i = 0; i < particles.Num(); i++ ) {
1009 particles[i].time = 0;
1013 if ( enemyNode.InList() ) {
1014 // remove ourselves from the enemy's enemylist
1017 idActor::DormantBegin();
1024 called when entity wakes from being dormant
1027 void idAI::DormantEnd( void ) {
1028 if ( enemy.GetEntity() && !enemyNode.InList() ) {
1029 // let our enemy know we're back on the trail
1030 enemyNode.AddToEnd( enemy.GetEntity()->enemyList );
1033 if ( particles.Num() ) {
1034 for ( int i = 0; i < particles.Num(); i++ ) {
1035 particles[i].time = gameLocal.time;
1039 idActor::DormantEnd();
1043 =====================
1045 =====================
1047 void idAI::Think( void ) {
1048 // if we are completely closed off from the player, don't do anything at all
1049 if ( CheckDormant() ) {
1053 if ( thinkFlags & TH_THINK ) {
1054 // clear out the enemy when he dies or is hidden
1055 idActor *enemyEnt = enemy.GetEntity();
1057 if ( enemyEnt->health <= 0 ) {
1062 current_yaw += deltaViewAngles.yaw;
1063 ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
1064 deltaViewAngles.Zero();
1065 viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
1067 if ( num_cinematics ) {
1068 if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) {
1072 } else if ( !allowHiddenMovement && IsHidden() ) {
1076 // clear the ik before we do anything else so the skeleton doesn't get updated twice
1077 walkIK.ClearJointMods();
1079 switch( move.moveType ) {
1080 case MOVETYPE_DEAD :
1088 UpdateEnemyPosition();
1095 case MOVETYPE_STATIC :
1097 UpdateEnemyPosition();
1104 case MOVETYPE_ANIM :
1105 // animation based movement
1106 UpdateEnemyPosition();
1113 case MOVETYPE_SLIDE :
1114 // velocity based movement
1115 UpdateEnemyPosition();
1124 // clear pain flag so that we recieve any damage between now and the next time we run the script
1126 AI_SPECIAL_DAMAGE = 0;
1128 } else if ( thinkFlags & TH_PHYSICS ) {
1132 if ( af_push_moveables ) {
1136 if ( fl.hidden && allowHiddenMovement ) {
1137 // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement
1138 animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
1140 /* this still draws in retail builds.. not sure why.. don't care at this point.
1141 if ( !aas && developer.GetBool() && !fl.hidden && !num_cinematics ) {
1142 gameRenderWorld->DrawText( "No AAS", physicsObj.GetAbsBounds().GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, gameLocal.msec );
1146 UpdateMuzzleFlash();
1150 UpdateDamageEffects();
1154 /***********************************************************************
1156 AI script state management
1158 ***********************************************************************/
1161 =====================
1162 idAI::LinkScriptVariables
1163 =====================
1165 void idAI::LinkScriptVariables( void ) {
1166 AI_TALK.LinkTo( scriptObject, "AI_TALK" );
1167 AI_DAMAGE.LinkTo( scriptObject, "AI_DAMAGE" );
1168 AI_PAIN.LinkTo( scriptObject, "AI_PAIN" );
1169 AI_SPECIAL_DAMAGE.LinkTo( scriptObject, "AI_SPECIAL_DAMAGE" );
1170 AI_DEAD.LinkTo( scriptObject, "AI_DEAD" );
1171 AI_ENEMY_VISIBLE.LinkTo( scriptObject, "AI_ENEMY_VISIBLE" );
1172 AI_ENEMY_IN_FOV.LinkTo( scriptObject, "AI_ENEMY_IN_FOV" );
1173 AI_ENEMY_DEAD.LinkTo( scriptObject, "AI_ENEMY_DEAD" );
1174 AI_MOVE_DONE.LinkTo( scriptObject, "AI_MOVE_DONE" );
1175 AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
1176 AI_ACTIVATED.LinkTo( scriptObject, "AI_ACTIVATED" );
1177 AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" );
1178 AI_JUMP.LinkTo( scriptObject, "AI_JUMP" );
1179 AI_BLOCKED.LinkTo( scriptObject, "AI_BLOCKED" );
1180 AI_DEST_UNREACHABLE.LinkTo( scriptObject, "AI_DEST_UNREACHABLE" );
1181 AI_HIT_ENEMY.LinkTo( scriptObject, "AI_HIT_ENEMY" );
1182 AI_OBSTACLE_IN_PATH.LinkTo( scriptObject, "AI_OBSTACLE_IN_PATH" );
1183 AI_PUSHED.LinkTo( scriptObject, "AI_PUSHED" );
1187 =====================
1188 idAI::UpdateAIScript
1189 =====================
1191 void idAI::UpdateAIScript( void ) {
1194 // clear the hit enemy flag so we catch the next time we hit someone
1195 AI_HIT_ENEMY = false;
1197 if ( allowHiddenMovement || !IsHidden() ) {
1198 // update the animstate if we're not hidden
1203 /***********************************************************************
1207 ***********************************************************************/
1214 void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) {
1215 int i, numListedClipModels;
1216 idBounds clipBounds;
1218 idClipModel *clipModel;
1219 idClipModel *clipModelList[ MAX_GENTITIES ];
1224 idVec2 perpendicular;
1226 org = physicsObj.GetOrigin();
1228 // find all possible obstacles
1229 clipBounds = physicsObj.GetAbsBounds();
1230 clipBounds.TranslateSelf( dir * 32.0f );
1231 clipBounds.ExpandSelf( 8.0f );
1232 clipBounds.AddPoint( org );
1233 clipmask = physicsObj.GetClipMask();
1234 numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipmask, clipModelList, MAX_GENTITIES );
1235 for ( i = 0; i < numListedClipModels; i++ ) {
1236 clipModel = clipModelList[i];
1237 obEnt = clipModel->GetEntity();
1238 if ( obEnt == alwaysKick ) {
1239 // we'll kick this one outside the loop
1243 if ( !clipModel->IsTraceModel() ) {
1247 if ( obEnt->IsType( idMoveable::Type ) && obEnt->GetPhysics()->IsPushable() ) {
1248 delta = obEnt->GetPhysics()->GetOrigin() - org;
1249 delta.NormalizeFast();
1250 perpendicular.x = -delta.y;
1251 perpendicular.y = delta.x;
1253 delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
1254 forceVec = delta * force * obEnt->GetPhysics()->GetMass();
1255 obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec );
1260 delta = alwaysKick->GetPhysics()->GetOrigin() - org;
1261 delta.NormalizeFast();
1262 perpendicular.x = -delta.y;
1263 perpendicular.y = delta.x;
1265 delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
1266 forceVec = delta * force * alwaysKick->GetPhysics()->GetMass();
1267 alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec );
1276 bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) {
1279 for ( i = 0; i < 3; i++ ) {
1280 if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) {
1283 if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) {
1291 =====================
1293 =====================
1295 void idAI::SetAAS( void ) {
1298 spawnArgs.GetString( "use_aas", NULL, use_aas );
1299 aas = gameLocal.GetAAS( use_aas );
1301 const idAASSettings *settings = aas->GetSettings();
1303 if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) {
1304 gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() );
1306 float height = settings->maxStepHeight;
1307 physicsObj.SetMaxStepHeight( height );
1313 gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() );
1317 =====================
1319 =====================
1321 void idAI::DrawRoute( void ) const {
1322 if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY && move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
1323 if ( move.moveType == MOVETYPE_FLY ) {
1324 aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
1326 aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
1332 =====================
1334 =====================
1336 bool idAI::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
1337 if ( move.moveType == MOVETYPE_SLIDE ) {
1338 idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
1339 bnds.TranslateSelf( physicsObj.GetOrigin() );
1340 if ( bnds.ContainsPoint( pos ) ) {
1344 if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
1345 if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
1349 idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 64.0f ) );
1350 bnds.TranslateSelf( physicsObj.GetOrigin() );
1351 if ( bnds.ContainsPoint( pos ) ) {
1360 =====================
1361 idAI::PointReachableAreaNum
1362 =====================
1364 int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const {
1373 size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale;
1378 if ( move.moveType == MOVETYPE_FLY ) {
1379 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY );
1381 areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK );
1388 =====================
1390 =====================
1392 bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const {
1401 aas->PushPointIntoAreaNum( areaNum, org );
1407 aas->PushPointIntoAreaNum( goalAreaNum, goal );
1408 if ( !goalAreaNum ) {
1412 if ( move.moveType == MOVETYPE_FLY ) {
1413 return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1415 return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
1420 =====================
1421 idAI::TravelDistance
1423 Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance.
1425 This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you
1426 are from the goal, so try to break the goals up into shorter distances.
1427 =====================
1429 float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const {
1437 // no aas, so just take the straight line distance
1438 delta = end.ToVec2() - start.ToVec2();
1439 dist = delta.LengthFast();
1441 if ( ai_debugMove.GetBool() ) {
1442 gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
1443 gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
1449 fromArea = PointReachableAreaNum( start );
1450 toArea = PointReachableAreaNum( end );
1452 if ( !fromArea || !toArea ) {
1453 // can't seem to get there
1457 if ( fromArea == toArea ) {
1458 // same area, so just take the straight line distance
1459 delta = end.ToVec2() - start.ToVec2();
1460 dist = delta.LengthFast();
1462 if ( ai_debugMove.GetBool() ) {
1463 gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
1464 gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
1470 idReachability *reach;
1472 if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) {
1476 if ( ai_debugMove.GetBool() ) {
1477 if ( move.moveType == MOVETYPE_FLY ) {
1478 aas->ShowFlyPath( start, toArea, end );
1480 aas->ShowWalkPath( start, toArea, end );
1488 =====================
1490 =====================
1492 void idAI::StopMove( moveStatus_t status ) {
1493 AI_MOVE_DONE = true;
1495 move.moveCommand = MOVE_NONE;
1496 move.moveStatus = status;
1498 move.goalEntity = NULL;
1499 move.moveDest = physicsObj.GetOrigin();
1500 AI_DEST_UNREACHABLE = false;
1501 AI_OBSTACLE_IN_PATH = false;
1503 move.startTime = gameLocal.time;
1508 move.moveDir.Zero();
1509 move.lastMoveOrigin.Zero();
1510 move.lastMoveTime = gameLocal.time;
1514 =====================
1517 Continually face the enemy's last known position. MoveDone is always true in this case.
1518 =====================
1520 bool idAI::FaceEnemy( void ) {
1521 idActor *enemyEnt = enemy.GetEntity();
1523 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1527 TurnToward( lastVisibleEnemyPos );
1528 move.goalEntity = enemyEnt;
1529 move.moveDest = physicsObj.GetOrigin();
1530 move.moveCommand = MOVE_FACE_ENEMY;
1531 move.moveStatus = MOVE_STATUS_WAITING;
1532 move.startTime = gameLocal.time;
1534 AI_MOVE_DONE = true;
1536 AI_DEST_UNREACHABLE = false;
1542 =====================
1545 Continually face the entity position. MoveDone is always true in this case.
1546 =====================
1548 bool idAI::FaceEntity( idEntity *ent ) {
1550 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1554 idVec3 entityOrg = ent->GetPhysics()->GetOrigin();
1555 TurnToward( entityOrg );
1556 move.goalEntity = ent;
1557 move.moveDest = physicsObj.GetOrigin();
1558 move.moveCommand = MOVE_FACE_ENTITY;
1559 move.moveStatus = MOVE_STATUS_WAITING;
1560 move.startTime = gameLocal.time;
1562 AI_MOVE_DONE = true;
1564 AI_DEST_UNREACHABLE = false;
1570 =====================
1571 idAI::DirectMoveToPosition
1572 =====================
1574 bool idAI::DirectMoveToPosition( const idVec3 &pos ) {
1575 if ( ReachedPos( pos, move.moveCommand ) ) {
1576 StopMove( MOVE_STATUS_DONE );
1580 move.moveDest = pos;
1581 move.goalEntity = NULL;
1582 move.moveCommand = MOVE_TO_POSITION_DIRECT;
1583 move.moveStatus = MOVE_STATUS_MOVING;
1584 move.startTime = gameLocal.time;
1585 move.speed = fly_speed;
1586 AI_MOVE_DONE = false;
1587 AI_DEST_UNREACHABLE = false;
1590 if ( move.moveType == MOVETYPE_FLY ) {
1591 idVec3 dir = pos - physicsObj.GetOrigin();
1594 physicsObj.SetLinearVelocity( dir );
1601 =====================
1602 idAI::MoveToEnemyHeight
1603 =====================
1605 bool idAI::MoveToEnemyHeight( void ) {
1606 idActor *enemyEnt = enemy.GetEntity();
1608 if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) {
1609 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1613 move.moveDest.z = lastVisibleEnemyPos.z + enemyEnt->EyeOffset().z + fly_offset;
1614 move.goalEntity = enemyEnt;
1615 move.moveCommand = MOVE_TO_ENEMYHEIGHT;
1616 move.moveStatus = MOVE_STATUS_MOVING;
1617 move.startTime = gameLocal.time;
1619 AI_MOVE_DONE = false;
1620 AI_DEST_UNREACHABLE = false;
1627 =====================
1629 =====================
1631 bool idAI::MoveToEnemy( void ) {
1634 idActor *enemyEnt = enemy.GetEntity();
1637 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1641 if ( ReachedPos( lastVisibleReachableEnemyPos, MOVE_TO_ENEMY ) ) {
1642 if ( !ReachedPos( lastVisibleEnemyPos, MOVE_TO_ENEMY ) || !AI_ENEMY_VISIBLE ) {
1643 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1644 AI_DEST_UNREACHABLE = true;
1647 StopMove( MOVE_STATUS_DONE );
1651 idVec3 pos = lastVisibleReachableEnemyPos;
1655 move.toAreaNum = PointReachableAreaNum( pos );
1656 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1658 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1659 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1660 AI_DEST_UNREACHABLE = true;
1665 if ( !move.toAreaNum ) {
1666 // if only trying to update the enemy position
1667 if ( move.moveCommand == MOVE_TO_ENEMY ) {
1669 // keep the move destination up to date for wandering
1670 move.moveDest = pos;
1675 if ( !NewWanderDir( pos ) ) {
1676 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1677 AI_DEST_UNREACHABLE = true;
1682 if ( move.moveCommand != MOVE_TO_ENEMY ) {
1683 move.moveCommand = MOVE_TO_ENEMY;
1684 move.startTime = gameLocal.time;
1687 move.moveDest = pos;
1688 move.goalEntity = enemyEnt;
1689 move.speed = fly_speed;
1690 move.moveStatus = MOVE_STATUS_MOVING;
1691 AI_MOVE_DONE = false;
1692 AI_DEST_UNREACHABLE = false;
1699 =====================
1701 =====================
1703 bool idAI::MoveToEntity( idEntity *ent ) {
1709 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
1713 pos = ent->GetPhysics()->GetOrigin();
1714 if ( ( move.moveType != MOVETYPE_FLY ) && ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntityOrigin != pos ) ) ) {
1715 ent->GetFloorPos( 64.0f, pos );
1718 if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) {
1719 StopMove( MOVE_STATUS_DONE );
1725 move.toAreaNum = PointReachableAreaNum( pos );
1726 aas->PushPointIntoAreaNum( move.toAreaNum, pos );
1728 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1729 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
1730 AI_DEST_UNREACHABLE = true;
1735 if ( !move.toAreaNum ) {
1736 // if only trying to update the entity position
1737 if ( move.moveCommand == MOVE_TO_ENTITY ) {
1739 // keep the move destination up to date for wandering
1740 move.moveDest = pos;
1745 if ( !NewWanderDir( pos ) ) {
1746 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1747 AI_DEST_UNREACHABLE = true;
1752 if ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntity.GetEntity() != ent ) ) {
1753 move.startTime = gameLocal.time;
1754 move.goalEntity = ent;
1755 move.moveCommand = MOVE_TO_ENTITY;
1758 move.moveDest = pos;
1759 move.goalEntityOrigin = ent->GetPhysics()->GetOrigin();
1760 move.moveStatus = MOVE_STATUS_MOVING;
1761 move.speed = fly_speed;
1762 AI_MOVE_DONE = false;
1763 AI_DEST_UNREACHABLE = false;
1770 =====================
1771 idAI::MoveOutOfRange
1772 =====================
1774 bool idAI::MoveOutOfRange( idEntity *ent, float range ) {
1776 aasObstacle_t obstacle;
1781 if ( !aas || !ent ) {
1782 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1783 AI_DEST_UNREACHABLE = true;
1787 const idVec3 &org = physicsObj.GetOrigin();
1788 areaNum = PointReachableAreaNum( org );
1790 // consider the entity the monster is getting close to as an obstacle
1791 obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1793 if ( ent == enemy.GetEntity() ) {
1794 pos = lastVisibleEnemyPos;
1796 pos = ent->GetPhysics()->GetOrigin();
1799 idAASFindAreaOutOfRange findGoal( pos, range );
1800 if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
1801 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1802 AI_DEST_UNREACHABLE = true;
1806 if ( ReachedPos( goal.origin, move.moveCommand ) ) {
1807 StopMove( MOVE_STATUS_DONE );
1811 move.moveDest = goal.origin;
1812 move.toAreaNum = goal.areaNum;
1813 move.goalEntity = ent;
1814 move.moveCommand = MOVE_OUT_OF_RANGE;
1815 move.moveStatus = MOVE_STATUS_MOVING;
1817 move.speed = fly_speed;
1818 move.startTime = gameLocal.time;
1819 AI_MOVE_DONE = false;
1820 AI_DEST_UNREACHABLE = false;
1827 =====================
1828 idAI::MoveToAttackPosition
1829 =====================
1831 bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) {
1833 aasObstacle_t obstacle;
1838 if ( !aas || !ent ) {
1839 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1840 AI_DEST_UNREACHABLE = true;
1844 const idVec3 &org = physicsObj.GetOrigin();
1845 areaNum = PointReachableAreaNum( org );
1847 // consider the entity the monster is getting close to as an obstacle
1848 obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
1850 if ( ent == enemy.GetEntity() ) {
1851 pos = lastVisibleEnemyPos;
1853 pos = ent->GetPhysics()->GetOrigin();
1856 idAASFindAttackPosition findGoal( this, physicsObj.GetGravityAxis(), ent, pos, missileLaunchOffset[ attack_anim ] );
1857 if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
1858 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1859 AI_DEST_UNREACHABLE = true;
1863 move.moveDest = goal.origin;
1864 move.toAreaNum = goal.areaNum;
1865 move.goalEntity = ent;
1866 move.moveCommand = MOVE_TO_ATTACK_POSITION;
1867 move.moveStatus = MOVE_STATUS_MOVING;
1868 move.speed = fly_speed;
1869 move.startTime = gameLocal.time;
1870 move.anim = attack_anim;
1871 AI_MOVE_DONE = false;
1872 AI_DEST_UNREACHABLE = false;
1879 =====================
1880 idAI::MoveToPosition
1881 =====================
1883 bool idAI::MoveToPosition( const idVec3 &pos ) {
1888 if ( ReachedPos( pos, move.moveCommand ) ) {
1889 StopMove( MOVE_STATUS_DONE );
1896 move.toAreaNum = PointReachableAreaNum( org );
1897 aas->PushPointIntoAreaNum( move.toAreaNum, org );
1899 areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
1900 if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, org ) ) {
1901 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1902 AI_DEST_UNREACHABLE = true;
1907 if ( !move.toAreaNum && !NewWanderDir( org ) ) {
1908 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1909 AI_DEST_UNREACHABLE = true;
1913 move.moveDest = org;
1914 move.goalEntity = NULL;
1915 move.moveCommand = MOVE_TO_POSITION;
1916 move.moveStatus = MOVE_STATUS_MOVING;
1917 move.startTime = gameLocal.time;
1918 move.speed = fly_speed;
1919 AI_MOVE_DONE = false;
1920 AI_DEST_UNREACHABLE = false;
1927 =====================
1929 =====================
1931 bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) {
1933 aasObstacle_t obstacle;
1937 if ( !aas || !entity ) {
1938 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1939 AI_DEST_UNREACHABLE = true;
1943 const idVec3 &org = physicsObj.GetOrigin();
1944 areaNum = PointReachableAreaNum( org );
1946 // consider the entity the monster tries to hide from as an obstacle
1947 obstacle.absBounds = entity->GetPhysics()->GetAbsBounds();
1949 idAASFindCover findCover( hideFromPos );
1950 if ( !aas->FindNearestGoal( hideGoal, areaNum, org, hideFromPos, travelFlags, &obstacle, 1, findCover ) ) {
1951 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
1952 AI_DEST_UNREACHABLE = true;
1956 if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) {
1957 StopMove( MOVE_STATUS_DONE );
1961 move.moveDest = hideGoal.origin;
1962 move.toAreaNum = hideGoal.areaNum;
1963 move.goalEntity = entity;
1964 move.moveCommand = MOVE_TO_COVER;
1965 move.moveStatus = MOVE_STATUS_MOVING;
1966 move.startTime = gameLocal.time;
1967 move.speed = fly_speed;
1968 AI_MOVE_DONE = false;
1969 AI_DEST_UNREACHABLE = false;
1976 =====================
1977 idAI::SlideToPosition
1978 =====================
1980 bool idAI::SlideToPosition( const idVec3 &pos, float time ) {
1981 StopMove( MOVE_STATUS_DONE );
1983 move.moveDest = pos;
1984 move.goalEntity = NULL;
1985 move.moveCommand = MOVE_SLIDE_TO_POSITION;
1986 move.moveStatus = MOVE_STATUS_MOVING;
1987 move.startTime = gameLocal.time;
1988 move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
1989 AI_MOVE_DONE = false;
1990 AI_DEST_UNREACHABLE = false;
1993 if ( move.duration > 0 ) {
1994 move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration );
1995 if ( move.moveType != MOVETYPE_FLY ) {
1996 move.moveDir.z = 0.0f;
1998 move.speed = move.moveDir.LengthFast();
2005 =====================
2007 =====================
2009 bool idAI::WanderAround( void ) {
2010 StopMove( MOVE_STATUS_DONE );
2012 move.moveDest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
2013 if ( !NewWanderDir( move.moveDest ) ) {
2014 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2015 AI_DEST_UNREACHABLE = true;
2019 move.moveCommand = MOVE_WANDER;
2020 move.moveStatus = MOVE_STATUS_MOVING;
2021 move.startTime = gameLocal.time;
2022 move.speed = fly_speed;
2023 AI_MOVE_DONE = false;
2030 =====================
2032 =====================
2034 bool idAI::MoveDone( void ) const {
2035 return ( move.moveCommand == MOVE_NONE );
2043 bool idAI::StepDirection( float dir ) {
2044 predictedPath_t path;
2047 move.wanderYaw = dir;
2048 move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward();
2050 org = physicsObj.GetOrigin();
2052 idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
2054 if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) {
2055 // don't report being blocked if we ran into our goal entity
2059 if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) {
2062 move.moveDir = path.endVelocity * 1.0f / 48.0f;
2064 // trace down to the floor and see if we can go forward
2065 idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path );
2067 idVec3 floorPos = path.endPos;
2068 idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
2069 if ( !path.endEvent ) {
2070 move.moveDir.z = -1.0f;
2074 // trace up to see if we can go over something and go forward
2075 idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path );
2077 idVec3 ceilingPos = path.endPos;
2079 for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) {
2081 if ( z <= ceilingPos.z ) {
2088 idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
2089 if ( !path.endEvent ) {
2090 move.moveDir.z = 1.0f;
2097 return ( path.endEvent == 0 );
2105 bool idAI::NewWanderDir( const idVec3 &dest ) {
2106 float deltax, deltay;
2108 float tdir, olddir, turnaround;
2110 move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 );
2112 olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 );
2113 turnaround = idMath::AngleNormalize360( olddir - 180 );
2115 idVec3 org = physicsObj.GetOrigin();
2116 deltax = dest.x - org.x;
2117 deltay = dest.y - org.y;
2118 if ( deltax > 10 ) {
2120 } else if ( deltax < -10 ) {
2126 if ( deltay < -10 ) {
2128 } else if ( deltay > 10 ) {
2135 if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) {
2136 if ( d[ 1 ] == 0 ) {
2137 tdir = d[ 2 ] == 90 ? 45 : 315;
2139 tdir = d[ 2 ] == 90 ? 135 : 215;
2142 if ( tdir != turnaround && StepDirection( tdir ) ) {
2147 // try other directions
2148 if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) {
2154 if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) {
2158 if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) {
2162 // there is no direct path to the player, so pick another direction
2163 if ( olddir != DI_NODIR && StepDirection( olddir ) ) {
2167 // randomly determine direction of search
2168 if ( gameLocal.random.RandomInt() & 1 ) {
2169 for( tdir = 0; tdir <= 315; tdir += 45 ) {
2170 if ( tdir != turnaround && StepDirection( tdir ) ) {
2175 for ( tdir = 315; tdir >= 0; tdir -= 45 ) {
2176 if ( tdir != turnaround && StepDirection( tdir ) ) {
2182 if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) {
2187 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2192 =====================
2194 =====================
2196 bool idAI::GetMovePos( idVec3 &seekPos ) {
2202 org = physicsObj.GetOrigin();
2205 switch( move.moveCommand ) {
2207 seekPos = move.moveDest;
2211 case MOVE_FACE_ENEMY :
2212 case MOVE_FACE_ENTITY :
2213 seekPos = move.moveDest;
2217 case MOVE_TO_POSITION_DIRECT :
2218 seekPos = move.moveDest;
2219 if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2220 StopMove( MOVE_STATUS_DONE );
2225 case MOVE_SLIDE_TO_POSITION :
2231 if ( move.moveCommand == MOVE_TO_ENTITY ) {
2232 MoveToEntity( move.goalEntity.GetEntity() );
2235 move.moveStatus = MOVE_STATUS_MOVING;
2237 if ( gameLocal.time > move.blockTime ) {
2238 if ( move.moveCommand == MOVE_WANDER ) {
2239 move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
2241 if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
2242 StopMove( MOVE_STATUS_DONE );
2248 if ( aas && move.toAreaNum ) {
2249 areaNum = PointReachableAreaNum( org );
2250 if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) {
2251 seekPos = path.moveGoal;
2253 move.nextWanderTime = 0;
2255 AI_DEST_UNREACHABLE = true;
2262 if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) {
2263 result = NewWanderDir( move.moveDest );
2265 StopMove( MOVE_STATUS_DEST_UNREACHABLE );
2266 AI_DEST_UNREACHABLE = true;
2274 seekPos = org + move.moveDir * 2048.0f;
2275 if ( ai_debugMove.GetBool() ) {
2276 gameRenderWorld->DebugLine( colorYellow, org, seekPos, gameLocal.msec, true );
2279 AI_DEST_UNREACHABLE = false;
2282 if ( result && ( ai_debugMove.GetBool() ) ) {
2283 gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos );
2290 =====================
2291 idAI::EntityCanSeePos
2292 =====================
2294 bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) {
2299 handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() );
2301 if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) {
2302 gameLocal.pvs.FreeCurrentPVS( handle );
2306 gameLocal.pvs.FreeCurrentPVS( handle );
2308 eye = actorOrigin + actor->EyeOffset();
2313 physicsObj.DisableClip();
2315 gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2316 if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2317 physicsObj.EnableClip();
2321 const idBounds &bounds = physicsObj.GetBounds();
2322 point[2] += bounds[1][2] - bounds[0][2];
2324 gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
2325 physicsObj.EnableClip();
2326 if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
2333 =====================
2334 idAI::BlockedFailSafe
2335 =====================
2337 void idAI::BlockedFailSafe( void ) {
2338 if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) {
2341 if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL ||
2342 ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( blockedRadius ) ) {
2343 move.lastMoveOrigin = physicsObj.GetOrigin();
2344 move.lastMoveTime = gameLocal.time;
2346 if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) {
2347 if ( lastAttackTime < gameLocal.time - blockedAttackTime ) {
2349 move.lastMoveTime = gameLocal.time;
2354 /***********************************************************************
2358 ***********************************************************************/
2361 =====================
2363 =====================
2365 void idAI::Turn( void ) {
2369 animFlags_t animflags;
2375 // check if the animator has marker this anim as non-turning
2376 if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) {
2377 animflags = legsAnim.GetAnimFlags();
2379 animflags = torsoAnim.GetAnimFlags();
2381 if ( animflags.ai_no_turn ) {
2385 if ( anim_turn_angles && animflags.anim_turn ) {
2388 // set the blend between no turn and full turn
2389 float frac = anim_turn_amount / anim_turn_angles;
2390 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac );
2391 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac );
2392 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac );
2393 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac );
2395 // get the total rotation from the start of the anim
2396 animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis );
2397 current_yaw = idMath::AngleNormalize180( anim_turn_yaw + rotateAxis[ 0 ].ToYaw() );
2399 diff = idMath::AngleNormalize180( ideal_yaw - current_yaw );
2400 turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec );
2401 if ( turnVel > turnRate ) {
2403 } else if ( turnVel < -turnRate ) {
2404 turnVel = -turnRate;
2406 turnAmount = turnVel * MS2SEC( gameLocal.msec );
2407 if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) {
2408 turnVel = diff / MS2SEC( gameLocal.msec );
2410 } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) {
2411 turnVel = diff / MS2SEC( gameLocal.msec );
2414 current_yaw += turnAmount;
2415 current_yaw = idMath::AngleNormalize180( current_yaw );
2416 diff2 = idMath::AngleNormalize180( ideal_yaw - current_yaw );
2417 if ( idMath::Fabs( diff2 ) < 0.1f ) {
2418 current_yaw = ideal_yaw;
2422 viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
2424 if ( ai_debugMove.GetBool() ) {
2425 const idVec3 &org = physicsObj.GetOrigin();
2426 gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec );
2427 gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, current_yaw, 0 ).ToForward() * 48, gameLocal.msec );
2428 gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, current_yaw + turnVel, 0 ).ToForward() * 32, gameLocal.msec );
2433 =====================
2435 =====================
2437 bool idAI::FacingIdeal( void ) {
2444 diff = idMath::AngleNormalize180( current_yaw - ideal_yaw );
2445 if ( idMath::Fabs( diff ) < 0.01f ) {
2446 // force it to be exact
2447 current_yaw = ideal_yaw;
2455 =====================
2457 =====================
2459 bool idAI::TurnToward( float yaw ) {
2460 ideal_yaw = idMath::AngleNormalize180( yaw );
2461 bool result = FacingIdeal();
2466 =====================
2468 =====================
2470 bool idAI::TurnToward( const idVec3 &pos ) {
2475 dir = pos - physicsObj.GetOrigin();
2476 physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
2478 lengthSqr = local_dir.LengthSqr();
2479 if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) {
2480 ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
2483 bool result = FacingIdeal();
2487 /***********************************************************************
2491 ***********************************************************************/
2498 void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
2499 // FIXME: Jim take a look at this and see if this is a reasonable thing to do
2500 // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 )
2501 // and we don't want him taking physics impulses as it can knock him off the path
2502 if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) {
2503 idActor::ApplyImpulse( ent, id, point, impulse );
2508 =====================
2510 =====================
2512 void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) {
2513 idVec3 oldModelOrigin;
2516 animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
2517 delta = axis * delta;
2519 if ( modelOffset != vec3_zero ) {
2520 // the pivot of the monster's model is around its origin, and not around the bounding
2521 // box's origin, so we have to compensate for this when the model is offset so that
2522 // the monster still appears to rotate around it's origin.
2523 oldModelOrigin = modelOffset * oldaxis;
2524 modelOrigin = modelOffset * axis;
2525 delta += oldModelOrigin - modelOrigin;
2528 delta *= physicsObj.GetGravityAxis();
2532 =====================
2533 idAI::CheckObstacleAvoidance
2534 =====================
2536 void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) {
2538 obstaclePath_t path;
2543 if ( ignore_obstacles ) {
2545 move.obstacle = NULL;
2549 const idVec3 &origin = physicsObj.GetOrigin();
2552 AI_OBSTACLE_IN_PATH = false;
2553 foundPath = FindPathAroundObstacles( &physicsObj, aas, enemy.GetEntity(), origin, goalPos, path );
2554 if ( ai_showObstacleAvoidance.GetBool() ) {
2555 gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec );
2556 gameRenderWorld->DebugLine( foundPath ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec );
2560 // couldn't get around obstacles
2561 if ( path.firstObstacle ) {
2562 AI_OBSTACLE_IN_PATH = true;
2563 if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.firstObstacle->GetPhysics()->GetAbsBounds() ) ) {
2564 obstacle = path.firstObstacle;
2566 } else if ( path.startPosObstacle ) {
2567 AI_OBSTACLE_IN_PATH = true;
2568 if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.startPosObstacle->GetPhysics()->GetAbsBounds() ) ) {
2569 obstacle = path.startPosObstacle;
2573 move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL;
2576 } else if ( path.startPosObstacle ) {
2577 // check if we're past where the our origin was pushed out of the obstacle
2578 dir = goalPos - origin;
2580 dist = ( path.seekPos - origin ) * dir;
2581 if ( dist < 1.0f ) {
2582 AI_OBSTACLE_IN_PATH = true;
2583 obstacle = path.startPosObstacle;
2586 } else if ( path.seekPosObstacle ) {
2587 // if the AI is very close to the path.seekPos already and path.seekPosObstacle != NULL
2588 // then we want to push the path.seekPosObstacle entity out of the way
2589 AI_OBSTACLE_IN_PATH = true;
2591 // check if we're past where the goalPos was pushed out of the obstacle
2592 dir = goalPos - origin;
2594 dist = ( path.seekPos - origin ) * dir;
2595 if ( dist < 1.0f ) {
2596 obstacle = path.seekPosObstacle;
2600 // if we had an obstacle, set our move status based on the type, and kick it out of the way if it's a moveable
2602 if ( obstacle->IsType( idActor::Type ) ) {
2603 // monsters aren't kickable
2604 if ( obstacle == enemy.GetEntity() ) {
2605 move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY;
2607 move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER;
2610 // try kicking the object out of the way
2611 move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT;
2613 newPos = obstacle->GetPhysics()->GetOrigin();
2614 //newPos = path.seekPos;
2615 move.obstacle = obstacle;
2617 newPos = path.seekPos;
2618 move.obstacle = NULL;
2623 =====================
2625 =====================
2627 void idAI::DeadMove( void ) {
2629 monsterMoveResult_t moveResult;
2631 idVec3 org = physicsObj.GetOrigin();
2633 GetMoveDelta( viewAxis, viewAxis, delta );
2634 physicsObj.SetDelta( delta );
2638 moveResult = physicsObj.GetMoveResult();
2639 AI_ONGROUND = physicsObj.OnGround();
2643 =====================
2645 =====================
2647 void idAI::AnimMove( void ) {
2652 monsterMoveResult_t moveResult;
2655 idVec3 oldorigin = physicsObj.GetOrigin();
2656 idMat3 oldaxis = viewAxis;
2660 if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
2661 move.lastMoveOrigin.Zero();
2662 move.lastMoveTime = gameLocal.time;
2665 move.obstacle = NULL;
2666 if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2667 TurnToward( lastVisibleEnemyPos );
2668 goalPos = oldorigin;
2669 } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2670 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2671 goalPos = oldorigin;
2672 } else if ( GetMovePos( goalPos ) ) {
2673 if ( move.moveCommand != MOVE_WANDER ) {
2674 CheckObstacleAvoidance( goalPos, newDest );
2675 TurnToward( newDest );
2677 TurnToward( goalPos );
2683 if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
2684 if ( gameLocal.time < move.startTime + move.duration ) {
2685 goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
2686 delta = goalPos - oldorigin;
2689 delta = move.moveDest - oldorigin;
2691 StopMove( MOVE_STATUS_DONE );
2693 } else if ( allowMove ) {
2694 GetMoveDelta( oldaxis, viewAxis, delta );
2699 if ( move.moveCommand == MOVE_TO_POSITION ) {
2700 goalDelta = move.moveDest - oldorigin;
2701 goalDist = goalDelta.LengthFast();
2702 if ( goalDist < delta.LengthFast() ) {
2707 physicsObj.SetDelta( delta );
2708 physicsObj.ForceDeltaMove( disableGravity );
2712 if ( ai_debugMove.GetBool() ) {
2713 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2716 moveResult = physicsObj.GetMoveResult();
2717 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2718 DirectDamage( attack, enemy.GetEntity() );
2720 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2721 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2722 KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2728 AI_ONGROUND = physicsObj.OnGround();
2730 idVec3 org = physicsObj.GetOrigin();
2731 if ( oldorigin != org ) {
2735 if ( ai_debugMove.GetBool() ) {
2736 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
2737 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
2738 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
2744 =====================
2746 =====================
2748 idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) {
2749 idVec3 predictedPos;
2753 // predict our position
2754 predictedPos = org + vel * prediction;
2755 goalDelta = goal - predictedPos;
2756 seekVel = goalDelta * MS2SEC( gameLocal.msec );
2762 =====================
2764 =====================
2766 void idAI::SlideMove( void ) {
2771 monsterMoveResult_t moveResult;
2774 idVec3 oldorigin = physicsObj.GetOrigin();
2775 idMat3 oldaxis = viewAxis;
2779 if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
2780 move.lastMoveOrigin.Zero();
2781 move.lastMoveTime = gameLocal.time;
2784 move.obstacle = NULL;
2785 if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2786 TurnToward( lastVisibleEnemyPos );
2787 goalPos = move.moveDest;
2788 } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2789 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2790 goalPos = move.moveDest;
2791 } else if ( GetMovePos( goalPos ) ) {
2792 CheckObstacleAvoidance( goalPos, newDest );
2793 TurnToward( newDest );
2797 if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
2798 if ( gameLocal.time < move.startTime + move.duration ) {
2799 goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
2801 goalPos = move.moveDest;
2802 StopMove( MOVE_STATUS_DONE );
2806 if ( move.moveCommand == MOVE_TO_POSITION ) {
2807 goalDelta = move.moveDest - oldorigin;
2808 goalDist = goalDelta.LengthFast();
2809 if ( goalDist < delta.LengthFast() ) {
2814 idVec3 vel = physicsObj.GetLinearVelocity();
2816 idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION;
2818 // seek the goal position
2819 goalDelta = goalPos - predictedPos;
2820 vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
2821 vel += goalDelta * MS2SEC( gameLocal.msec );
2824 vel.Truncate( fly_speed );
2826 physicsObj.SetLinearVelocity( vel );
2827 physicsObj.UseVelocityMove( true );
2830 if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
2831 TurnToward( lastVisibleEnemyPos );
2832 } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
2833 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
2834 } else if ( move.moveCommand != MOVE_NONE ) {
2835 if ( vel.ToVec2().LengthSqr() > 0.1f ) {
2836 TurnToward( vel.ToYaw() );
2841 if ( ai_debugMove.GetBool() ) {
2842 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
2845 moveResult = physicsObj.GetMoveResult();
2846 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
2847 DirectDamage( attack, enemy.GetEntity() );
2849 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
2850 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
2851 KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
2857 AI_ONGROUND = physicsObj.OnGround();
2859 idVec3 org = physicsObj.GetOrigin();
2860 if ( oldorigin != org ) {
2864 if ( ai_debugMove.GetBool() ) {
2865 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
2866 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
2867 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
2873 =====================
2874 idAI::AdjustFlyingAngles
2875 =====================
2877 void idAI::AdjustFlyingAngles( void ) {
2883 vel = physicsObj.GetLinearVelocity();
2885 speed = vel.Length();
2886 if ( speed < 5.0f ) {
2890 roll = vel * viewAxis[ 1 ] * -fly_roll_scale / fly_speed;
2891 if ( roll > fly_roll_max ) {
2892 roll = fly_roll_max;
2893 } else if ( roll < -fly_roll_max ) {
2894 roll = -fly_roll_max;
2897 pitch = vel * viewAxis[ 2 ] * -fly_pitch_scale / fly_speed;
2898 if ( pitch > fly_pitch_max ) {
2899 pitch = fly_pitch_max;
2900 } else if ( pitch < -fly_pitch_max ) {
2901 pitch = -fly_pitch_max;
2905 fly_roll = fly_roll * 0.95f + roll * 0.05f;
2906 fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
2908 if ( flyTiltJoint != INVALID_JOINT ) {
2909 animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
2911 viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3();
2916 =====================
2918 =====================
2920 void idAI::AddFlyBob( idVec3 &vel ) {
2924 if ( fly_bob_strength ) {
2925 t = MS2SEC( gameLocal.time + entityNumber * 497 );
2926 fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * fly_bob_vert ) ) * fly_bob_strength;
2927 vel += fly_bob_add * MS2SEC( gameLocal.msec );
2928 if ( ai_debugMove.GetBool() ) {
2929 const idVec3 &origin = physicsObj.GetOrigin();
2930 gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 );
2936 =====================
2937 idAI::AdjustFlyHeight
2938 =====================
2940 void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) {
2941 const idVec3 &origin = physicsObj.GetOrigin();
2942 predictedPath_t path;
2949 // make sure we're not flying too high to get through doors
2951 if ( origin.z > goalPos.z ) {
2953 dest.z = origin.z + 128.0f;
2954 idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path );
2955 if ( path.endPos.z < origin.z ) {
2956 idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION );
2961 if ( ai_debugMove.GetBool() ) {
2962 gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
2967 // make sure we don't fly too low
2970 enemyEnt = enemy.GetEntity();
2972 end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset;
2974 // just use the default eye height for the player
2975 end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset;
2978 gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this );
2979 vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION );
2984 =====================
2986 =====================
2988 void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) {
2991 // seek the goal position
2992 seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION );
2993 seekVel *= fly_seek_scale;
2998 =====================
2999 idAI::AdjustFlySpeed
3000 =====================
3002 void idAI::AdjustFlySpeed( idVec3 &vel ) {
3006 vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
3008 // gradually speed up/slow down to desired speed
3009 speed = vel.Normalize();
3010 speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
3011 if ( speed < 0.0f ) {
3013 } else if ( move.speed && ( speed > move.speed ) ) {
3021 =====================
3023 =====================
3025 void idAI::FlyTurn( void ) {
3026 if ( move.moveCommand == MOVE_FACE_ENEMY ) {
3027 TurnToward( lastVisibleEnemyPos );
3028 } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
3029 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
3030 } else if ( move.speed > 0.0f ) {
3031 const idVec3 &vel = physicsObj.GetLinearVelocity();
3032 if ( vel.ToVec2().LengthSqr() > 0.1f ) {
3033 TurnToward( vel.ToYaw() );
3040 =====================
3042 =====================
3044 void idAI::FlyMove( void ) {
3050 if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
3051 StopMove( MOVE_STATUS_DONE );
3054 if ( ai_debugMove.GetBool() ) {
3055 gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), moveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, fly_speed );
3058 if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
3059 idVec3 vel = physicsObj.GetLinearVelocity();
3061 if ( GetMovePos( goalPos ) ) {
3062 CheckObstacleAvoidance( goalPos, newDest );
3067 FlySeekGoal( vel, goalPos );
3073 if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
3074 AdjustFlyHeight( vel, goalPos );
3077 AdjustFlySpeed( vel );
3079 physicsObj.SetLinearVelocity( vel );
3085 // run the physics for this frame
3086 oldorigin = physicsObj.GetOrigin();
3087 physicsObj.UseFlyMove( true );
3088 physicsObj.UseVelocityMove( false );
3089 physicsObj.SetDelta( vec3_zero );
3090 physicsObj.ForceDeltaMove( disableGravity );
3093 monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
3094 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3095 DirectDamage( attack, enemy.GetEntity() );
3097 idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
3098 if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
3099 KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
3100 } else if ( moveResult == MM_BLOCKED ) {
3101 move.blockTime = gameLocal.time + 500;
3106 idVec3 org = physicsObj.GetOrigin();
3107 if ( oldorigin != org ) {
3111 if ( ai_debugMove.GetBool() ) {
3112 gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
3113 gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
3114 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
3115 gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
3116 gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
3117 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
3123 =====================
3125 =====================
3127 void idAI::StaticMove( void ) {
3128 idActor *enemyEnt = enemy.GetEntity();
3134 if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemyEnt ) {
3135 TurnToward( lastVisibleEnemyPos );
3136 } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
3137 TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
3138 } else if ( move.moveCommand != MOVE_NONE ) {
3139 TurnToward( move.moveDest );
3143 physicsObj.ForceDeltaMove( true ); // disable gravity
3146 AI_ONGROUND = false;
3148 if ( !af_push_moveables && attack.Length() && TestMelee() ) {
3149 DirectDamage( attack, enemyEnt );
3152 if ( ai_debugMove.GetBool() ) {
3153 const idVec3 &org = physicsObj.GetOrigin();
3154 gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
3155 gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true );
3156 gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
3160 /***********************************************************************
3164 ***********************************************************************/
3167 =====================
3169 =====================
3171 int idAI::ReactionTo( const idEntity *ent ) {
3173 if ( ent->fl.hidden ) {
3174 // ignore hidden entities
3175 return ATTACK_IGNORE;
3178 if ( !ent->IsType( idActor::Type ) ) {
3179 return ATTACK_IGNORE;
3182 const idActor *actor = static_cast<const idActor *>( ent );
3183 if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
3184 // ignore players in noclip mode
3185 return ATTACK_IGNORE;
3188 // actors on different teams will always fight each other
3189 if ( actor->team != team ) {
3190 if ( actor->fl.notarget ) {
3191 // don't attack on sight when attacker is notargeted
3192 return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
3194 return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
3197 // monsters will fight when attacked by lower ranked monsters. rank 0 never fights back.
3198 if ( rank && ( actor->rank < rank ) ) {
3199 return ATTACK_ON_DAMAGE;
3203 return ATTACK_IGNORE;
3208 =====================
3210 =====================
3212 bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3215 AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location );
3221 // ignore damage from self
3222 if ( attacker != this ) {
3224 AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3226 AI_SPECIAL_DAMAGE = 0;
3229 if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) {
3230 actor = ( idActor * )attacker;
3231 if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) {
3232 gameLocal.AlertAI( actor );
3238 return ( AI_PAIN != 0 );
3243 =====================
3244 idAI::SpawnParticles
3245 =====================
3247 void idAI::SpawnParticles( const char *keyName ) {
3248 const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL );
3250 particleEmitter_t pe;
3252 idStr particleName = kv->GetValue();
3254 if ( particleName.Length() ) {
3256 idStr jointName = kv->GetValue();
3257 int dash = jointName.Find('-');
3259 particleName = particleName.Left( dash );
3260 jointName = jointName.Right( jointName.Length() - dash - 1 );
3263 SpawnParticlesOnJoint( pe, particleName, jointName );
3264 particles.Append( pe );
3267 kv = spawnArgs.MatchPrefix( keyName, kv );
3272 =====================
3273 idAI::SpawnParticlesOnJoint
3274 =====================
3276 const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) {
3280 if ( *particleName == '\0' ) {
3281 memset( &pe, 0, sizeof( pe ) );
3285 pe.joint = animator.GetJointHandle( jointName );
3286 if ( pe.joint == INVALID_JOINT ) {
3287 gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() );
3291 animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis );
3292 origin = renderEntity.origin + origin * renderEntity.axis;
3294 BecomeActive( TH_UPDATEPARTICLES );
3295 if ( !gameLocal.time ) {
3296 // particles with time of 0 don't show, so set the time differently on the first frame
3299 pe.time = gameLocal.time;
3301 pe.particle = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, particleName ) );
3302 gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis );
3309 =====================
3311 =====================
3313 void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
3315 const char *modelDeath;
3317 // make sure the monster is activated
3320 if ( g_debugDamage.GetBool() ) {
3321 gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ),
3322 GetDamageGroup( location ) );
3326 AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
3328 AI_SPECIAL_DAMAGE = 0;
3337 // stop all voice sounds
3338 StopSound( SND_CHANNEL_VOICE, false );
3339 if ( head.GetEntity() ) {
3340 head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false );
3341 head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 );
3344 disableGravity = false;
3345 move.moveType = MOVETYPE_DEAD;
3346 af_push_moveables = false;
3348 physicsObj.UseFlyMove( false );
3349 physicsObj.ForceDeltaMove( false );
3351 // end our looping ambient sound
3352 StopSound( SND_CHANNEL_AMBIENT, false );
3354 if ( attacker && attacker->IsType( idActor::Type ) ) {
3355 gameLocal.AlertAI( ( idActor * )attacker );
3359 ActivateTargets( attacker );
3361 RemoveAttachments();
3363 StopMove( MOVE_STATUS_DONE );
3368 // make monster nonsolid
3369 physicsObj.SetContents( 0 );
3370 physicsObj.GetClipModel()->Unlink();
3374 if ( StartRagdoll() ) {
3375 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
3378 if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) {
3379 // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here
3380 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
3381 renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
3382 SetModel( modelDeath );
3383 physicsObj.SetLinearVelocity( vec3_zero );
3384 physicsObj.PutToRest();
3385 physicsObj.DisableImpact();
3388 restartParticles = false;
3390 state = GetScriptFunction( "state_Killed" );
3394 const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL );
3398 args.Set( "classname", kv->GetValue() );
3399 args.Set( "origin", physicsObj.GetOrigin().ToString() );
3400 gameLocal.SpawnEntityDef( args );
3401 kv = spawnArgs.MatchPrefix( "def_drops", kv );
3404 if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) {
3405 static_cast< idPlayer* >( attacker )->AddAIKill();
3409 /***********************************************************************
3413 ***********************************************************************/
3416 =====================
3418 =====================
3420 void idAI::PlayCinematic( void ) {
3421 const char *animname;
3423 if ( current_cinematic >= num_cinematics ) {
3424 if ( g_debugCinematic.GetBool() ) {
3425 gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
3427 if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) {
3430 current_cinematic = 0;
3431 ActivateTargets( gameLocal.GetLocalPlayer() );
3432 fl.neverDormant = false;
3437 current_cinematic++;
3439 allowJointMod = false;
3440 allowEyeFocus = false;
3442 spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname );
3444 gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() );
3448 if ( g_debugCinematic.GetBool() ) {
3449 gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname );
3452 headAnim.animBlendFrames = 0;
3453 headAnim.lastAnimBlendFrames = 0;
3454 headAnim.BecomeIdle();
3456 legsAnim.animBlendFrames = 0;
3457 legsAnim.lastAnimBlendFrames = 0;
3458 legsAnim.BecomeIdle();
3460 torsoAnim.animBlendFrames = 0;
3461 torsoAnim.lastAnimBlendFrames = 0;
3462 ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname );
3464 // make sure our model gets updated
3465 animator.ForceUpdate();
3467 // update the anim bounds
3472 if ( head.GetEntity() ) {
3473 // since the body anim was updated, we need to run physics to update the position of the head
3476 // make sure our model gets updated
3477 head.GetEntity()->GetAnimator()->ForceUpdate();
3479 // update the anim bounds
3480 head.GetEntity()->UpdateAnimation();
3481 head.GetEntity()->UpdateVisuals();
3482 head.GetEntity()->Present();
3485 fl.neverDormant = true;
3489 =====================
3492 Notifies the script that a monster has been activated by a trigger or flashlight
3493 =====================
3495 void idAI::Activate( idEntity *activator ) {
3499 // ignore it when they're dead
3503 // make sure he's not dormant
3506 if ( num_cinematics ) {
3509 AI_ACTIVATED = true;
3510 if ( !activator || !activator->IsType( idPlayer::Type ) ) {
3511 player = gameLocal.GetLocalPlayer();
3513 player = static_cast<idPlayer *>( activator );
3516 if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) {
3520 // update the script in cinematics so that entities don't start anims or show themselves a frame late.
3524 // make sure our model gets updated
3525 animator.ForceUpdate();
3527 // update the anim bounds
3532 if ( head.GetEntity() ) {
3533 // since the body anim was updated, we need to run physics to update the position of the head
3536 // make sure our model gets updated
3537 head.GetEntity()->GetAnimator()->ForceUpdate();
3539 // update the anim bounds
3540 head.GetEntity()->UpdateAnimation();
3541 head.GetEntity()->UpdateVisuals();
3542 head.GetEntity()->Present();
3549 =====================
3551 =====================
3553 void idAI::EnemyDead( void ) {
3555 AI_ENEMY_DEAD = true;
3559 =====================
3561 =====================
3563 void idAI::TalkTo( idActor *actor ) {
3564 if ( talk_state != TALK_OK ) {
3577 =====================
3579 =====================
3581 idActor *idAI::GetEnemy( void ) const {
3582 return enemy.GetEntity();
3586 =====================
3588 =====================
3590 talkState_t idAI::GetTalkState( void ) const {
3591 if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) {
3601 =====================
3602 idAI::TouchedByFlashlight
3603 =====================
3605 void idAI::TouchedByFlashlight( idActor *flashlight_owner ) {
3606 if ( wakeOnFlashlight ) {
3607 Activate( flashlight_owner );
3612 =====================
3614 =====================
3616 void idAI::ClearEnemy( void ) {
3617 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3618 StopMove( MOVE_STATUS_DEST_NOT_FOUND );
3623 AI_ENEMY_IN_FOV = false;
3624 AI_ENEMY_VISIBLE = false;
3625 AI_ENEMY_DEAD = true;
3631 =====================
3632 idAI::EnemyPositionValid
3633 =====================
3635 bool idAI::EnemyPositionValid( void ) const {
3640 if ( !enemy.GetEntity() ) {
3644 if ( AI_ENEMY_VISIBLE ) {
3648 gameLocal.clip.TracePoint( tr, GetEyePosition(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, MASK_OPAQUE, this );
3649 if ( tr.fraction < 1.0f ) {
3650 // can't see the area yet, so don't know if he's there or not
3658 =====================
3659 idAI::SetEnemyPosition
3660 =====================
3662 void idAI::SetEnemyPosition( void ) {
3663 idActor *enemyEnt = enemy.GetEntity();
3666 int lastVisibleReachableEnemyAreaNum;
3675 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3676 lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset();
3677 lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin();
3678 if ( move.moveType == MOVETYPE_FLY ) {
3679 pos = lastVisibleEnemyPos;
3682 onGround = enemyEnt->GetFloorPos( 64.0f, pos );
3683 if ( enemyEnt->OnLadder() ) {
3689 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3690 AI_DEST_UNREACHABLE = true;
3695 // when we don't have an AAS, we can't tell if an enemy is reachable or not,
3696 // so just assume that he is.
3698 lastVisibleReachableEnemyPos = lastVisibleEnemyPos;
3699 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3700 AI_DEST_UNREACHABLE = false;
3705 lastVisibleReachableEnemyAreaNum = move.toAreaNum;
3706 enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f );
3707 if ( !enemyAreaNum ) {
3708 enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3709 pos = lastReachableEnemyPos;
3711 if ( !enemyAreaNum ) {
3712 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3713 AI_DEST_UNREACHABLE = true;
3717 const idVec3 &org = physicsObj.GetOrigin();
3718 areaNum = PointReachableAreaNum( org );
3719 if ( PathToGoal( path, areaNum, org, enemyAreaNum, pos ) ) {
3720 lastVisibleReachableEnemyPos = pos;
3721 lastVisibleReachableEnemyAreaNum = enemyAreaNum;
3722 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3723 AI_DEST_UNREACHABLE = false;
3725 } else if ( move.moveCommand == MOVE_TO_ENEMY ) {
3726 AI_DEST_UNREACHABLE = true;
3731 if ( move.moveCommand == MOVE_TO_ENEMY ) {
3733 // keep the move destination up to date for wandering
3734 move.moveDest = lastVisibleReachableEnemyPos;
3735 } else if ( enemyAreaNum ) {
3736 move.toAreaNum = lastVisibleReachableEnemyAreaNum;
3737 move.moveDest = lastVisibleReachableEnemyPos;
3740 if ( move.moveType == MOVETYPE_FLY ) {
3741 predictedPath_t path;
3742 idVec3 end = move.moveDest;
3743 end.z += enemyEnt->EyeOffset().z + fly_offset;
3744 idAI::PredictPath( this, aas, move.moveDest, end - move.moveDest, 1000, 1000, SE_BLOCKED, path );
3745 move.moveDest = path.endPos;
3746 move.toAreaNum = PointReachableAreaNum( move.moveDest, 1.0f );
3752 =====================
3753 idAI::UpdateEnemyPosition
3754 =====================
3756 void idAI::UpdateEnemyPosition( void ) {
3757 idActor *enemyEnt = enemy.GetEntity();
3761 predictedPath_t predictedPath;
3769 const idVec3 &org = physicsObj.GetOrigin();
3771 if ( move.moveType == MOVETYPE_FLY ) {
3772 enemyPos = enemyEnt->GetPhysics()->GetOrigin();
3775 onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
3776 if ( enemyEnt->OnLadder() ) {
3782 // when we don't have an AAS, we can't tell if an enemy is reachable or not,
3783 // so just assume that he is.
3786 lastReachableEnemyPos = enemyPos;
3788 enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
3789 if ( enemyAreaNum ) {
3790 areaNum = PointReachableAreaNum( org );
3791 if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
3792 lastReachableEnemyPos = enemyPos;
3798 AI_ENEMY_IN_FOV = false;
3799 AI_ENEMY_VISIBLE = false;
3801 if ( CanSee( enemyEnt, false ) ) {
3802 AI_ENEMY_VISIBLE = true;
3803 if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
3804 AI_ENEMY_IN_FOV = true;
3809 // check if we heard any sounds in the last frame
3810 if ( enemyEnt == gameLocal.GetAlertEntity() ) {
3811 float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr();
3812 if ( dist < Square( AI_HEARING_RANGE ) ) {
3818 if ( ai_debugMove.GetBool() ) {
3819 gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec );
3820 gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec );
3825 =====================
3827 =====================
3829 void idAI::SetEnemy( idActor *newEnemy ) {
3837 AI_ENEMY_DEAD = false;
3840 } else if ( enemy.GetEntity() != newEnemy ) {
3842 enemyNode.AddToEnd( newEnemy->enemyList );
3843 if ( newEnemy->health <= 0 ) {
3847 // let the monster know where the enemy is
3848 newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum );
3852 lastReachableEnemyPos = lastVisibleEnemyPos;
3853 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3854 enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
3855 if ( aas && enemyAreaNum ) {
3856 aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos );
3857 lastVisibleReachableEnemyPos = lastReachableEnemyPos;
3864 idAI::FirstVisiblePointOnPath
3867 idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const {
3868 int i, areaNum, targetAreaNum, curAreaNum, travelTime;
3870 idReachability *reach;
3876 areaNum = PointReachableAreaNum( origin );
3877 targetAreaNum = PointReachableAreaNum( target );
3879 if ( !areaNum || !targetAreaNum ) {
3883 if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) {
3887 curAreaNum = areaNum;
3890 for( i = 0; i < 10; i++ ) {
3892 if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) {
3900 curAreaNum = reach->toAreaNum;
3901 curOrigin = reach->end;
3903 if ( PointVisible( curOrigin ) ) {
3913 idAI::CalculateAttackOffsets
3915 calculate joint positions on attack frames so we can do proper "can hit" tests
3918 void idAI::CalculateAttackOffsets( void ) {
3919 const idDeclModelDef *modelDef;
3923 const frameCommand_t *command;
3926 jointHandle_t joint;
3928 modelDef = animator.ModelDef();
3932 num = modelDef->NumAnims();
3934 // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim
3935 animator.RemoveOriginOffset( false );
3937 // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for
3938 // launch offsets so that anim number can be used without subtracting 1.
3939 missileLaunchOffset.SetGranularity( 1 );
3940 missileLaunchOffset.SetNum( num + 1 );
3941 missileLaunchOffset[ 0 ].Zero();
3943 for( i = 1; i <= num; i++ ) {
3944 missileLaunchOffset[ i ].Zero();
3945 anim = modelDef->GetAnim( i );
3947 frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
3949 joint = animator.GetJointHandle( command->string->c_str() );
3950 if ( joint == INVALID_JOINT ) {
3951 gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() );
3953 GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
3958 animator.RemoveOriginOffset( true );
3962 =====================
3963 idAI::CreateProjectileClipModel
3964 =====================
3966 void idAI::CreateProjectileClipModel( void ) const {
3967 if ( projectileClipModel == NULL ) {
3968 idBounds projectileBounds( vec3_origin );
3969 projectileBounds.ExpandSelf( projectileRadius );
3970 projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) );
3975 =====================
3977 =====================
3979 bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
3986 // if no aimAtEnt or projectile set
3987 if ( !aimAtEnt || !projectileDef ) {
3988 aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
3992 if ( projectileClipModel == NULL ) {
3993 CreateProjectileClipModel();
3996 if ( aimAtEnt == enemy.GetEntity() ) {
3997 static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 );
3998 } else if ( aimAtEnt->IsType( idActor::Type ) ) {
3999 static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 );
4001 targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
4002 targetPos2 = targetPos1;
4005 // try aiming for chest
4006 delta = firePos - targetPos1;
4007 max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
4008 result = PredictTrajectory( firePos, targetPos1, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
4009 if ( result || !aimAtEnt->IsType( idActor::Type ) ) {
4013 // try aiming for head
4014 delta = firePos - targetPos2;
4015 max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
4016 result = PredictTrajectory( firePos, targetPos2, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
4022 =====================
4024 =====================
4026 void idAI::BeginAttack( const char *name ) {
4028 lastAttackTime = gameLocal.time;
4032 =====================
4034 =====================
4036 void idAI::EndAttack( void ) {
4041 =====================
4042 idAI::CreateProjectile
4043 =====================
4045 idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) {
4047 const char *clsname;
4049 if ( !projectile.GetEntity() ) {
4050 gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
4052 clsname = projectileDef->GetString( "classname" );
4053 gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
4056 if ( !ent->IsType( idProjectile::Type ) ) {
4057 clsname = ent->GetClassname();
4058 gameLocal.Error( "'%s' is not an idProjectile", clsname );
4060 projectile = ( idProjectile * )ent;
4063 projectile.GetEntity()->Create( this, pos, dir );
4065 return projectile.GetEntity();
4069 =====================
4070 idAI::RemoveProjectile
4071 =====================
4073 void idAI::RemoveProjectile( void ) {
4074 if ( projectile.GetEntity() ) {
4075 projectile.GetEntity()->PostEventMS( &EV_Remove, 0 );
4081 =====================
4082 idAI::LaunchProjectile
4083 =====================
4085 idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
4090 idBounds projBounds;
4092 const idClipModel *projClip;
4093 float attack_accuracy;
4095 float projectile_spread;
4100 int num_projectiles;
4104 idProjectile *lastProjectile;
4106 if ( !projectileDef ) {
4107 gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
4111 attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
4112 attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
4113 projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
4114 num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
4116 GetMuzzle( jointname, muzzle, axis );
4118 if ( !projectile.GetEntity() ) {
4119 CreateProjectile( muzzle, axis[ 0 ] );
4122 lastProjectile = projectile.GetEntity();
4124 if ( target != NULL ) {
4125 tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
4127 axis = tmp.ToMat3();
4132 // rotate it because the cone points up by default
4137 // make sure the projectile starts inside the monster bounding box
4138 const idBounds &ownerBounds = physicsObj.GetAbsBounds();
4139 projClip = lastProjectile->GetPhysics()->GetClipModel();
4140 projBounds = projClip->GetBounds().Rotate( axis );
4142 // check if the owner bounds is bigger than the projectile bounds
4143 if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
4144 ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
4145 ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
4146 if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
4147 start = muzzle + distance * viewAxis[ 0 ];
4149 start = ownerBounds.GetCenter();
4152 // projectile bounds bigger than the owner bounds, so just start it from the center
4153 start = ownerBounds.GetCenter();
4156 gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
4159 // set aiming direction
4160 GetAimDir( muzzle, target, this, dir );
4161 ang = dir.ToAngles();
4163 // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
4164 float t = MS2SEC( gameLocal.time + entityNumber * 497 );
4165 ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
4166 ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
4168 if ( clampToAttackCone ) {
4169 // clamp the attack direction to be within monster's attack cone so he doesn't do
4170 // things like throw the missile backwards if you're behind him
4171 diff = idMath::AngleDelta( ang.yaw, current_yaw );
4172 if ( diff > attack_cone ) {
4173 ang.yaw = current_yaw + attack_cone;
4174 } else if ( diff < -attack_cone ) {
4175 ang.yaw = current_yaw - attack_cone;
4179 axis = ang.ToMat3();
4181 float spreadRad = DEG2RAD( projectile_spread );
4182 for( i = 0; i < num_projectiles; i++ ) {
4183 // spread the projectiles out
4184 angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
4185 spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
4186 dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
4189 // launch the projectile
4190 if ( !projectile.GetEntity() ) {
4191 CreateProjectile( muzzle, dir );
4193 lastProjectile = projectile.GetEntity();
4194 lastProjectile->Launch( muzzle, dir, vec3_origin );
4198 TriggerWeaponEffects( muzzle );
4200 lastAttackTime = gameLocal.time;
4202 return lastProjectile;
4207 idAI::DamageFeedback
4209 callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
4211 FIXME: This gets called when we call idPlayer::CalcDamagePoints from idAI::AttackMelee, which then checks for a saving throw,
4212 possibly forcing a miss. This is harmless behavior ATM, but is not intuitive.
4215 void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
4216 if ( ( victim == this ) && inflictor->IsType( idProjectile::Type ) ) {
4217 // monsters only get half damage from their own projectiles
4218 damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage
4220 } else if ( victim == enemy.GetEntity() ) {
4221 AI_HIT_ENEMY = true;
4226 =====================
4229 Causes direct damage to an entity
4231 kickDir is specified in the monster's coordinate system, and gives the direction
4232 that the view kick and knockback should go
4233 =====================
4235 void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) {
4236 const idDict *meleeDef;
4238 const idSoundShader *shader;
4240 meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4242 gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() );
4245 if ( !ent->fl.takedamage ) {
4246 const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" ));
4247 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4254 p = meleeDef->GetString( "snd_hit" );
4256 shader = declManager->FindSound( p );
4257 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4261 meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4263 idVec3 globalKickDir;
4264 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4266 ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4268 // end the attack if we're a multiframe attack
4273 =====================
4275 =====================
4277 bool idAI::TestMelee( void ) const {
4279 idActor *enemyEnt = enemy.GetEntity();
4281 if ( !enemyEnt || !melee_range ) {
4285 //FIXME: make work with gravity vector
4286 idVec3 org = physicsObj.GetOrigin();
4287 const idBounds &myBounds = physicsObj.GetBounds();
4290 // expand the bounds out by our melee range
4291 bounds[0][0] = -melee_range;
4292 bounds[0][1] = -melee_range;
4293 bounds[0][2] = myBounds[0][2] - 4.0f;
4294 bounds[1][0] = melee_range;
4295 bounds[1][1] = melee_range;
4296 bounds[1][2] = myBounds[1][2] + 4.0f;
4297 bounds.TranslateSelf( org );
4299 idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
4300 idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds();
4301 enemyBounds.TranslateSelf( enemyOrg );
4303 if ( ai_debugMove.GetBool() ) {
4304 gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec );
4307 if ( !bounds.IntersectsBounds( enemyBounds ) ) {
4311 idVec3 start = GetEyePosition();
4312 idVec3 end = enemyEnt->GetEyePosition();
4314 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4315 if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) {
4323 =====================
4326 jointname allows the endpoint to be exactly specified in the model,
4327 as for the commando tentacle. If not specified, it will be set to
4328 the facing direction + melee_range.
4330 kickDir is specified in the monster's coordinate system, and gives the direction
4331 that the view kick and knockback should go
4332 =====================
4334 bool idAI::AttackMelee( const char *meleeDefName ) {
4335 const idDict *meleeDef;
4336 idActor *enemyEnt = enemy.GetEntity();
4338 const idSoundShader *shader;
4340 meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
4342 gameLocal.Error( "Unknown melee '%s'", meleeDefName );
4346 p = meleeDef->GetString( "snd_miss" );
4348 shader = declManager->FindSound( p );
4349 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4354 // check for the "saving throw" automatic melee miss on lethal blow
4355 // stupid place for this.
4356 bool forceMiss = false;
4357 if ( enemyEnt->IsType( idPlayer::Type ) && g_skill.GetInteger() < 2 ) {
4359 idPlayer *player = static_cast<idPlayer*>( enemyEnt );
4360 player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor );
4362 if ( enemyEnt->health <= damage ) {
4363 int t = gameLocal.time - player->lastSavingThrowTime;
4364 if ( t > SAVING_THROW_TIME ) {
4365 player->lastSavingThrowTime = gameLocal.time;
4369 gameLocal.Printf( "Saving throw.\n" );
4375 // make sure the trace can actually hit the enemy
4376 if ( forceMiss || !TestMelee() ) {
4378 p = meleeDef->GetString( "snd_miss" );
4380 shader = declManager->FindSound( p );
4381 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4389 p = meleeDef->GetString( "snd_hit" );
4391 shader = declManager->FindSound( p );
4392 StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
4396 meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
4398 idVec3 globalKickDir;
4399 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
4401 enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
4403 lastAttackTime = gameLocal.time;
4413 void idAI::PushWithAF( void ) {
4415 afTouch_t touchList[ MAX_GENTITIES ];
4416 idEntity *pushed_ents[ MAX_GENTITIES ];
4422 af.ChangePose( this, gameLocal.time );
4423 int num = af.EntitiesTouchingAF( touchList );
4424 for( i = 0; i < num; i++ ) {
4425 if ( touchList[ i ].touchedEnt->IsType( idProjectile::Type ) ) {
4430 // make sure we havent pushed this entity already. this avoids causing double damage
4431 for( j = 0; j < num_pushed; j++ ) {
4432 if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) {
4436 if ( j >= num_pushed ) {
4437 ent = touchList[ i ].touchedEnt;
4438 pushed_ents[num_pushed++] = ent;
4439 vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin();
4441 if ( attack.Length() && ent->IsType( idActor::Type ) ) {
4442 ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT );
4444 ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() );
4450 /***********************************************************************
4454 ***********************************************************************/
4461 void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) {
4462 jointHandle_t joint;
4464 if ( !jointname || !jointname[ 0 ] ) {
4465 muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
4466 muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
4468 joint = animator.GetJointHandle( jointname );
4469 if ( joint == INVALID_JOINT ) {
4470 gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
4472 GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
4478 idAI::TriggerWeaponEffects
4481 void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) {
4485 if ( !g_muzzleFlash.GetBool() ) {
4490 // offset the shader parms so muzzle flashes show up
4491 renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
4492 renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat();
4494 if ( flashJointWorld != INVALID_JOINT ) {
4495 GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
4497 if ( worldMuzzleFlash.lightRadius.x > 0.0f ) {
4498 worldMuzzleFlash.axis = axis;
4499 worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
4500 if ( worldMuzzleFlashHandle != - 1 ) {
4501 gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
4503 worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
4505 muzzleFlashEnd = gameLocal.time + flashTime;
4513 idAI::UpdateMuzzleFlash
4516 void idAI::UpdateMuzzleFlash( void ) {
4517 if ( worldMuzzleFlashHandle != -1 ) {
4518 if ( gameLocal.time >= muzzleFlashEnd ) {
4519 gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
4520 worldMuzzleFlashHandle = -1;
4523 animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
4524 animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
4525 muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
4526 worldMuzzleFlash.origin = muzzle;
4527 gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
4537 void idAI::Hide( void ) {
4539 fl.takedamage = false;
4540 physicsObj.SetContents( 0 );
4541 physicsObj.GetClipModel()->Unlink();
4542 StopSound( SND_CHANNEL_AMBIENT, false );
4545 AI_ENEMY_IN_FOV = false;
4546 AI_ENEMY_VISIBLE = false;
4547 StopMove( MOVE_STATUS_DONE );
4555 void idAI::Show( void ) {
4557 if ( spawnArgs.GetBool( "big_monster" ) ) {
4558 physicsObj.SetContents( 0 );
4559 } else if ( use_combat_bbox ) {
4560 physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
4562 physicsObj.SetContents( CONTENTS_BODY );
4564 physicsObj.GetClipModel()->Link( gameLocal.clip );
4565 fl.takedamage = !spawnArgs.GetBool( "noDamage" );
4567 StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
4571 =====================
4573 =====================
4575 void idAI::SetChatSound( void ) {
4580 } else if ( enemy.GetEntity() ) {
4581 snd = spawnArgs.GetString( "snd_chatter_combat", NULL );
4582 chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) );
4583 chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) );
4584 } else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) {
4585 snd = spawnArgs.GetString( "snd_chatter", NULL );
4586 chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) );
4587 chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) );
4592 if ( snd && *snd ) {
4593 chat_snd = declManager->FindSound( snd );
4595 // set the next chat time
4596 chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4604 idAI::CanPlayChatterSounds
4606 Used for playing chatter sounds on monsters.
4609 bool idAI::CanPlayChatterSounds( void ) const {
4618 if ( enemy.GetEntity() ) {
4622 if ( spawnArgs.GetBool( "no_idle_chatter" ) ) {
4630 =====================
4632 =====================
4634 void idAI::PlayChatter( void ) {
4635 // check if it's time to play a chat sound
4636 if ( AI_DEAD || !chat_snd || ( chat_time > gameLocal.time ) ) {
4640 StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL );
4642 // set the next chat time
4643 chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
4647 =====================
4648 idAI::UpdateParticles
4649 =====================
4651 void idAI::UpdateParticles( void ) {
4652 if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
4656 int particlesAlive = 0;
4657 for ( int i = 0; i < particles.Num(); i++ ) {
4658 if ( particles[i].particle && particles[i].time ) {
4660 if (af.IsActive()) {
4661 realAxis = mat3_identity;
4662 realVector = GetPhysics()->GetOrigin();
4664 animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis );
4665 realAxis *= renderEntity.axis;
4666 realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() );
4669 if ( !gameLocal.smokeParticles->EmitSmoke( particles[i].particle, particles[i].time, gameLocal.random.CRandomFloat(), realVector, realAxis )) {
4670 if ( restartParticles ) {
4671 particles[i].time = gameLocal.time;
4673 particles[i].time = 0;
4679 if ( particlesAlive == 0 ) {
4680 BecomeInactive( TH_UPDATEPARTICLES );
4686 =====================
4687 idAI::TriggerParticles
4688 =====================
4690 void idAI::TriggerParticles( const char *jointName ) {
4691 jointHandle_t jointNum;
4693 jointNum = animator.GetJointHandle( jointName );
4694 for ( int i = 0; i < particles.Num(); i++ ) {
4695 if ( particles[i].joint == jointNum ) {
4696 particles[i].time = gameLocal.time;
4697 BecomeActive( TH_UPDATEPARTICLES );
4703 /***********************************************************************
4707 ***********************************************************************/
4711 idAI::UpdateAnimationControllers
4714 bool idAI::UpdateAnimationControllers( void ) {
4720 idVec3 orientationJointPos;
4722 idAngles newLookAng;
4726 idMat3 orientationJointAxis;
4727 idAFAttachment *headEnt = head.GetEntity();
4732 float orientationJointYaw;
4735 return idActor::UpdateAnimationControllers();
4738 if ( orientationJoint == INVALID_JOINT ) {
4739 orientationJointAxis = viewAxis;
4740 orientationJointPos = physicsObj.GetOrigin();
4741 orientationJointYaw = current_yaw;
4743 GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
4744 orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
4745 orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
4748 if ( focusJoint != INVALID_JOINT ) {
4750 headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
4752 GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
4754 eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
4755 if ( ai_debugMove.GetBool() ) {
4756 gameRenderWorld->DebugLine( colorRed, eyepos, eyepos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
4759 eyepos = GetEyePosition();
4763 CopyJointsFromBodyToHead();
4766 // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
4767 // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which
4768 // are already up to date because of getting the joints in this function) and then sets their positions, which
4769 // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled,
4770 // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no
4771 // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number
4772 // in order to see how many times an entity transforms the joints per frame.
4773 idActor::UpdateAnimationControllers();
4775 idEntity *focusEnt = focusEntity.GetEntity();
4776 if ( !allowJointMod || !allowEyeFocus || ( gameLocal.time >= focusTime ) ) {
4777 focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
4778 } else if ( focusEnt == NULL ) {
4779 // keep looking at last position until focusTime is up
4780 focusPos = currentFocusPos;
4781 } else if ( focusEnt == enemy.GetEntity() ) {
4782 focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
4783 } else if ( focusEnt->IsType( idActor::Type ) ) {
4784 focusPos = static_cast<idActor *>( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
4786 focusPos = focusEnt->GetPhysics()->GetOrigin();
4789 currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
4791 // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
4792 dir = focusPos - orientationJointPos;
4793 newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
4794 newLookAng.roll = 0.0f;
4795 newLookAng.pitch = 0.0f;
4798 gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec );
4799 gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
4800 gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec );
4803 // determine pitch from joint position
4804 dir = focusPos - eyepos;
4805 dir.NormalizeFast();
4806 orientationJointAxis.ProjectVector( dir, localDir );
4807 newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() );
4808 newLookAng.roll = 0.0f;
4810 diff = newLookAng - lookAng;
4812 if ( eyeAng != diff ) {
4814 eyeAng.Clamp( eyeMin, eyeMax );
4815 idAngles angDelta = diff - eyeAng;
4816 if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
4817 alignHeadTime = gameLocal.time;
4819 alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
4823 if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
4824 alignHeadTime = gameLocal.time;
4827 if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
4828 alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
4829 destLookAng = newLookAng;
4830 destLookAng.Clamp( lookMin, lookMax );
4833 diff = destLookAng - lookAng;
4834 if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
4835 if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
4836 diff.pitch = 360.0f - diff.pitch;
4839 if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
4840 if ( diff.yaw > 180.0f ) {
4842 } else if ( diff.yaw <= -180.0f ) {
4846 lookAng = lookAng + diff * headFocusRate;
4847 lookAng.Normalize180();
4849 jointAng.roll = 0.0f;
4850 for( i = 0; i < lookJoints.Num(); i++ ) {
4851 jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch;
4852 jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw;
4853 animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
4856 if ( move.moveType == MOVETYPE_FLY ) {
4858 AdjustFlyingAngles();
4862 idAnimator *headAnimator = headEnt->GetAnimator();
4864 if ( allowEyeFocus ) {
4865 idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
4866 axis = eyeAxis * orientationJointAxis;
4867 left = axis[ 1 ] * eyeHorizontalOffset;
4868 eyepos -= headEnt->GetPhysics()->GetOrigin();
4869 headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
4870 headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
4872 headAnimator->ClearJoint( leftEyeJoint );
4873 headAnimator->ClearJoint( rightEyeJoint );
4876 if ( allowEyeFocus ) {
4877 idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
4878 axis = eyeAxis * orientationJointAxis;
4879 left = axis[ 1 ] * eyeHorizontalOffset;
4880 eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
4881 animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
4882 animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
4884 animator.ClearJoint( leftEyeJoint );
4885 animator.ClearJoint( rightEyeJoint );
4892 /***********************************************************************
4896 ***********************************************************************/
4898 const idEventDef EV_CombatNode_MarkUsed( "markUsed" );
4900 CLASS_DECLARATION( idEntity, idCombatNode )
4901 EVENT( EV_CombatNode_MarkUsed, idCombatNode::Event_MarkUsed )
4902 EVENT( EV_Activate, idCombatNode::Event_Activate )
4906 =====================
4907 idCombatNode::idCombatNode
4908 =====================
4910 idCombatNode::idCombatNode( void ) {
4923 =====================
4925 =====================
4927 void idCombatNode::Save( idSaveGame *savefile ) const {
4928 savefile->WriteFloat( min_dist );
4929 savefile->WriteFloat( max_dist );
4930 savefile->WriteFloat( cone_dist );
4931 savefile->WriteFloat( min_height );
4932 savefile->WriteFloat( max_height );
4933 savefile->WriteVec3( cone_left );
4934 savefile->WriteVec3( cone_right );
4935 savefile->WriteVec3( offset );
4936 savefile->WriteBool( disabled );
4940 =====================
4941 idCombatNode::Restore
4942 =====================
4944 void idCombatNode::Restore( idRestoreGame *savefile ) {
4945 savefile->ReadFloat( min_dist );
4946 savefile->ReadFloat( max_dist );
4947 savefile->ReadFloat( cone_dist );
4948 savefile->ReadFloat( min_height );
4949 savefile->ReadFloat( max_height );
4950 savefile->ReadVec3( cone_left );
4951 savefile->ReadVec3( cone_right );
4952 savefile->ReadVec3( offset );
4953 savefile->ReadBool( disabled );
4957 =====================
4959 =====================
4961 void idCombatNode::Spawn( void ) {
4966 min_dist = spawnArgs.GetFloat( "min" );
4967 max_dist = spawnArgs.GetFloat( "max" );
4968 height = spawnArgs.GetFloat( "height" );
4969 fov = spawnArgs.GetFloat( "fov", "60" );
4970 offset = spawnArgs.GetVector( "offset" );
4972 const idVec3 &org = GetPhysics()->GetOrigin() + offset;
4973 min_height = org.z - height * 0.5f;
4974 max_height = min_height + height;
4976 const idMat3 &axis = GetPhysics()->GetAxis();
4977 yaw = axis[ 0 ].ToYaw();
4979 idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
4980 cone_left = leftang.ToForward();
4982 idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
4983 cone_right = rightang.ToForward();
4985 disabled = spawnArgs.GetBool( "start_off" );
4989 =====================
4990 idCombatNode::IsDisabled
4991 =====================
4993 bool idCombatNode::IsDisabled( void ) const {
4998 =====================
4999 idCombatNode::DrawDebugInfo
5000 =====================
5002 void idCombatNode::DrawDebugInfo( void ) {
5005 idPlayer *player = gameLocal.GetLocalPlayer();
5007 idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) );
5009 for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
5010 if ( !ent->IsType( idCombatNode::Type ) ) {
5014 node = static_cast<idCombatNode *>( ent );
5015 if ( node->disabled ) {
5016 color = colorMdGrey;
5017 } else if ( player && node->EntityInView( player, player->GetPhysics()->GetOrigin() ) ) {
5018 color = colorYellow;
5023 idVec3 leftDir( -node->cone_left.y, node->cone_left.x, 0.0f );
5024 idVec3 rightDir( node->cone_right.y, -node->cone_right.x, 0.0f );
5025 idVec3 org = node->GetPhysics()->GetOrigin() + node->offset;
5027 bounds[ 1 ].z = node->max_height;
5029 leftDir.NormalizeFast();
5030 rightDir.NormalizeFast();
5032 const idMat3 &axis = node->GetPhysics()->GetAxis();
5033 float cone_dot = node->cone_right * axis[ 1 ];
5034 if ( idMath::Fabs( cone_dot ) > 0.1 ) {
5035 float cone_dist = node->max_dist / cone_dot;
5036 idVec3 pos1 = org + leftDir * node->min_dist;
5037 idVec3 pos2 = org + leftDir * cone_dist;
5038 idVec3 pos3 = org + rightDir * node->min_dist;
5039 idVec3 pos4 = org + rightDir * cone_dist;
5041 gameRenderWorld->DebugLine( color, node->GetPhysics()->GetOrigin(), ( pos1 + pos3 ) * 0.5f, gameLocal.msec );
5042 gameRenderWorld->DebugLine( color, pos1, pos2, gameLocal.msec );
5043 gameRenderWorld->DebugLine( color, pos1, pos3, gameLocal.msec );
5044 gameRenderWorld->DebugLine( color, pos3, pos4, gameLocal.msec );
5045 gameRenderWorld->DebugLine( color, pos2, pos4, gameLocal.msec );
5046 gameRenderWorld->DebugBounds( color, bounds, org, gameLocal.msec );
5052 =====================
5053 idCombatNode::EntityInView
5054 =====================
5056 bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) {
5057 if ( !actor || ( actor->health <= 0 ) ) {
5061 const idBounds &bounds = actor->GetPhysics()->GetBounds();
5062 if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) {
5066 const idVec3 &org = GetPhysics()->GetOrigin() + offset;
5067 const idMat3 &axis = GetPhysics()->GetAxis();
5068 idVec3 dir = pos - org;
5069 float dist = dir * axis[ 0 ];
5071 if ( ( dist < min_dist ) || ( dist > max_dist ) ) {
5075 float left_dot = dir * cone_left;
5076 if ( left_dot < 0.0f ) {
5080 float right_dot = dir * cone_right;
5081 if ( right_dot < 0.0f ) {
5089 =====================
5090 idCombatNode::Event_Activate
5091 =====================
5093 void idCombatNode::Event_Activate( idEntity *activator ) {
5094 disabled = !disabled;
5098 =====================
5099 idCombatNode::Event_MarkUsed
5100 =====================
5102 void idCombatNode::Event_MarkUsed( void ) {
5103 if ( spawnArgs.GetBool( "use_once" ) ) {