mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-11-21 20:01:13 +00:00
2986 lines
No EOL
78 KiB
C++
2986 lines
No EOL
78 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition 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 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "../../idlib/precompiled.h"
|
|
|
|
|
|
#include "../Game_local.h"
|
|
|
|
/***********************************************************************
|
|
|
|
AI Events
|
|
|
|
***********************************************************************/
|
|
|
|
const idEventDef AI_FindEnemy( "findEnemy", "d", 'e' );
|
|
const idEventDef AI_FindEnemyAI( "findEnemyAI", "d", 'e' );
|
|
const idEventDef AI_FindEnemyInCombatNodes( "findEnemyInCombatNodes", NULL, 'e' );
|
|
const idEventDef AI_ClosestReachableEnemyOfEntity( "closestReachableEnemyOfEntity", "E", 'e' );
|
|
const idEventDef AI_HeardSound( "heardSound", "d", 'e' );
|
|
const idEventDef AI_SetEnemy( "setEnemy", "E" );
|
|
const idEventDef AI_ClearEnemy( "clearEnemy" );
|
|
const idEventDef AI_MuzzleFlash( "muzzleFlash", "s" );
|
|
const idEventDef AI_CreateMissile( "createMissile", "s", 'e' );
|
|
const idEventDef AI_AttackMissile( "attackMissile", "s", 'e' );
|
|
const idEventDef AI_FireMissileAtTarget( "fireMissileAtTarget", "ss", 'e' );
|
|
const idEventDef AI_LaunchMissile( "launchMissile", "vv", 'e' );
|
|
const idEventDef AI_LaunchHomingMissile( "launchHomingMissile" );
|
|
const idEventDef AI_SetHomingMissileGoal( "setHomingMissileGoal" );
|
|
const idEventDef AI_LaunchProjectile( "launchProjectile", "s" );
|
|
const idEventDef AI_AttackMelee( "attackMelee", "s", 'd' );
|
|
const idEventDef AI_DirectDamage( "directDamage", "es" );
|
|
const idEventDef AI_RadiusDamageFromJoint( "radiusDamageFromJoint", "ss" );
|
|
const idEventDef AI_BeginAttack( "attackBegin", "s" );
|
|
const idEventDef AI_EndAttack( "attackEnd" );
|
|
const idEventDef AI_MeleeAttackToJoint( "meleeAttackToJoint", "ss", 'd' );
|
|
const idEventDef AI_RandomPath( "randomPath", NULL, 'e' );
|
|
const idEventDef AI_CanBecomeSolid( "canBecomeSolid", NULL, 'f' );
|
|
const idEventDef AI_BecomeSolid( "becomeSolid" );
|
|
const idEventDef AI_BecomeRagdoll( "becomeRagdoll", NULL, 'd' );
|
|
const idEventDef AI_StopRagdoll( "stopRagdoll" );
|
|
const idEventDef AI_SetHealth( "setHealth", "f" );
|
|
const idEventDef AI_GetHealth( "getHealth", NULL, 'f' );
|
|
const idEventDef AI_AllowDamage( "allowDamage" );
|
|
const idEventDef AI_IgnoreDamage( "ignoreDamage" );
|
|
const idEventDef AI_GetCurrentYaw( "getCurrentYaw", NULL, 'f' );
|
|
const idEventDef AI_TurnTo( "turnTo", "f" );
|
|
const idEventDef AI_TurnToPos( "turnToPos", "v" );
|
|
const idEventDef AI_TurnToEntity( "turnToEntity", "E" );
|
|
const idEventDef AI_MoveStatus( "moveStatus", NULL, 'd' );
|
|
const idEventDef AI_StopMove( "stopMove" );
|
|
const idEventDef AI_MoveToCover( "moveToCover" );
|
|
const idEventDef AI_MoveToEnemy( "moveToEnemy" );
|
|
const idEventDef AI_MoveToEnemyHeight( "moveToEnemyHeight" );
|
|
const idEventDef AI_MoveOutOfRange( "moveOutOfRange", "ef" );
|
|
const idEventDef AI_MoveToAttackPosition( "moveToAttackPosition", "es" );
|
|
const idEventDef AI_Wander( "wander" );
|
|
const idEventDef AI_MoveToEntity( "moveToEntity", "e" );
|
|
const idEventDef AI_MoveToPosition( "moveToPosition", "v" );
|
|
const idEventDef AI_SlideTo( "slideTo", "vf" );
|
|
const idEventDef AI_FacingIdeal( "facingIdeal", NULL, 'd' );
|
|
const idEventDef AI_FaceEnemy( "faceEnemy" );
|
|
const idEventDef AI_FaceEntity( "faceEntity", "E" );
|
|
const idEventDef AI_GetCombatNode( "getCombatNode", NULL, 'e' );
|
|
const idEventDef AI_EnemyInCombatCone( "enemyInCombatCone", "Ed", 'd' );
|
|
const idEventDef AI_WaitMove( "waitMove" );
|
|
const idEventDef AI_GetJumpVelocity( "getJumpVelocity", "vff", 'v' );
|
|
const idEventDef AI_GetTrajectoryToPlayer( "getTrajectoryToPlayer", NULL, 'v' );
|
|
const idEventDef AI_EntityInAttackCone( "entityInAttackCone", "E", 'd' );
|
|
const idEventDef AI_CanSeeEntity( "canSee", "E", 'd' );
|
|
const idEventDef AI_SetTalkTarget( "setTalkTarget", "E" );
|
|
const idEventDef AI_GetTalkTarget( "getTalkTarget", NULL, 'e' );
|
|
const idEventDef AI_SetTalkState( "setTalkState", "d" );
|
|
const idEventDef AI_EnemyRange( "enemyRange", NULL, 'f' );
|
|
const idEventDef AI_EnemyRange2D( "enemyRange2D", NULL, 'f' );
|
|
const idEventDef AI_GetEnemy( "getEnemy", NULL, 'e' );
|
|
const idEventDef AI_GetEnemyPos( "getEnemyPos", NULL, 'v' );
|
|
const idEventDef AI_GetEnemyEyePos( "getEnemyEyePos", NULL, 'v' );
|
|
const idEventDef AI_PredictEnemyPos( "predictEnemyPos", "f", 'v' );
|
|
const idEventDef AI_CanHitEnemy( "canHitEnemy", NULL, 'd' );
|
|
const idEventDef AI_CanHitEnemyFromAnim( "canHitEnemyFromAnim", "s", 'd' );
|
|
const idEventDef AI_CanHitEnemyFromJoint( "canHitEnemyFromJoint", "s", 'd' );
|
|
const idEventDef AI_EnemyPositionValid( "enemyPositionValid", NULL, 'd' );
|
|
const idEventDef AI_ChargeAttack( "chargeAttack", "s" );
|
|
const idEventDef AI_TestChargeAttack( "testChargeAttack", NULL, 'f' );
|
|
const idEventDef AI_TestMoveToPosition( "testMoveToPosition", "v", 'd' );
|
|
const idEventDef AI_TestAnimMoveTowardEnemy( "testAnimMoveTowardEnemy", "s", 'd' );
|
|
const idEventDef AI_TestAnimMove( "testAnimMove", "s", 'd' );
|
|
const idEventDef AI_TestMeleeAttack( "testMeleeAttack", NULL, 'd' );
|
|
const idEventDef AI_TestAnimAttack( "testAnimAttack", "s", 'd' );
|
|
const idEventDef AI_Burn( "burn" );
|
|
const idEventDef AI_ClearBurn( "clearBurn" );
|
|
const idEventDef AI_PreBurn( "preBurn" );
|
|
const idEventDef AI_SetSmokeVisibility( "setSmokeVisibility", "dd" );
|
|
const idEventDef AI_NumSmokeEmitters( "numSmokeEmitters", NULL, 'd' );
|
|
const idEventDef AI_WaitAction( "waitAction", "s" );
|
|
const idEventDef AI_StopThinking( "stopThinking" );
|
|
const idEventDef AI_GetTurnDelta( "getTurnDelta", NULL, 'f' );
|
|
const idEventDef AI_GetMoveType( "getMoveType", NULL, 'd' );
|
|
const idEventDef AI_SetMoveType( "setMoveType", "d" );
|
|
const idEventDef AI_SaveMove( "saveMove" );
|
|
const idEventDef AI_RestoreMove( "restoreMove" );
|
|
const idEventDef AI_AllowMovement( "allowMovement", "f" );
|
|
const idEventDef AI_JumpFrame( "<jumpframe>" );
|
|
const idEventDef AI_EnableClip( "enableClip" );
|
|
const idEventDef AI_DisableClip( "disableClip" );
|
|
const idEventDef AI_EnableGravity( "enableGravity" );
|
|
const idEventDef AI_DisableGravity( "disableGravity" );
|
|
const idEventDef AI_EnableAFPush( "enableAFPush" );
|
|
const idEventDef AI_DisableAFPush( "disableAFPush" );
|
|
const idEventDef AI_SetFlySpeed( "setFlySpeed", "f" );
|
|
const idEventDef AI_SetFlyOffset( "setFlyOffset", "d" );
|
|
const idEventDef AI_ClearFlyOffset( "clearFlyOffset" );
|
|
const idEventDef AI_GetClosestHiddenTarget( "getClosestHiddenTarget", "s", 'e' );
|
|
const idEventDef AI_GetRandomTarget( "getRandomTarget", "s", 'e' );
|
|
const idEventDef AI_TravelDistanceToPoint( "travelDistanceToPoint", "v", 'f' );
|
|
const idEventDef AI_TravelDistanceToEntity( "travelDistanceToEntity", "e", 'f' );
|
|
const idEventDef AI_TravelDistanceBetweenPoints( "travelDistanceBetweenPoints", "vv", 'f' );
|
|
const idEventDef AI_TravelDistanceBetweenEntities( "travelDistanceBetweenEntities", "ee", 'f' );
|
|
const idEventDef AI_LookAtEntity( "lookAt", "Ef" );
|
|
const idEventDef AI_LookAtEnemy( "lookAtEnemy", "f" );
|
|
const idEventDef AI_SetJointMod( "setBoneMod", "d" );
|
|
const idEventDef AI_ThrowMoveable( "throwMoveable" );
|
|
const idEventDef AI_ThrowAF( "throwAF" );
|
|
const idEventDef AI_RealKill( "<kill>" );
|
|
const idEventDef AI_Kill( "kill" );
|
|
const idEventDef AI_WakeOnFlashlight( "wakeOnFlashlight", "d" );
|
|
const idEventDef AI_LocateEnemy( "locateEnemy" );
|
|
const idEventDef AI_KickObstacles( "kickObstacles", "Ef" );
|
|
const idEventDef AI_GetObstacle( "getObstacle", NULL, 'e' );
|
|
const idEventDef AI_PushPointIntoAAS( "pushPointIntoAAS", "v", 'v' );
|
|
const idEventDef AI_GetTurnRate( "getTurnRate", NULL, 'f' );
|
|
const idEventDef AI_SetTurnRate( "setTurnRate", "f" );
|
|
const idEventDef AI_AnimTurn( "animTurn", "f" );
|
|
const idEventDef AI_AllowHiddenMovement( "allowHiddenMovement", "d" );
|
|
const idEventDef AI_TriggerParticles( "triggerParticles", "s" );
|
|
const idEventDef AI_FindActorsInBounds( "findActorsInBounds", "vv", 'e' );
|
|
const idEventDef AI_CanReachPosition( "canReachPosition", "v", 'd' );
|
|
const idEventDef AI_CanReachEntity( "canReachEntity", "E", 'd' );
|
|
const idEventDef AI_CanReachEnemy( "canReachEnemy", NULL, 'd' );
|
|
const idEventDef AI_GetReachableEntityPosition( "getReachableEntityPosition", "e", 'v' );
|
|
const idEventDef AI_MoveToPositionDirect( "moveToPositionDirect", "v" );
|
|
const idEventDef AI_AvoidObstacles( "avoidObstacles", "d" );
|
|
const idEventDef AI_TriggerFX( "triggerFX", "ss" );
|
|
const idEventDef AI_StartEmitter( "startEmitter", "sss", 'e' );
|
|
const idEventDef AI_GetEmitter( "getEmitter", "s", 'e' );
|
|
const idEventDef AI_StopEmitter( "stopEmitter", "s" );
|
|
|
|
|
|
|
|
CLASS_DECLARATION( idActor, idAI )
|
|
EVENT( EV_Activate, idAI::Event_Activate )
|
|
EVENT( EV_Touch, idAI::Event_Touch )
|
|
EVENT( AI_FindEnemy, idAI::Event_FindEnemy )
|
|
EVENT( AI_FindEnemyAI, idAI::Event_FindEnemyAI )
|
|
EVENT( AI_FindEnemyInCombatNodes, idAI::Event_FindEnemyInCombatNodes )
|
|
EVENT( AI_ClosestReachableEnemyOfEntity, idAI::Event_ClosestReachableEnemyOfEntity )
|
|
EVENT( AI_HeardSound, idAI::Event_HeardSound )
|
|
EVENT( AI_SetEnemy, idAI::Event_SetEnemy )
|
|
EVENT( AI_ClearEnemy, idAI::Event_ClearEnemy )
|
|
EVENT( AI_MuzzleFlash, idAI::Event_MuzzleFlash )
|
|
EVENT( AI_CreateMissile, idAI::Event_CreateMissile )
|
|
EVENT( AI_AttackMissile, idAI::Event_AttackMissile )
|
|
EVENT( AI_FireMissileAtTarget, idAI::Event_FireMissileAtTarget )
|
|
EVENT( AI_LaunchMissile, idAI::Event_LaunchMissile )
|
|
EVENT( AI_LaunchHomingMissile, idAI::Event_LaunchHomingMissile )
|
|
EVENT( AI_SetHomingMissileGoal, idAI::Event_SetHomingMissileGoal )
|
|
EVENT( AI_LaunchProjectile, idAI::Event_LaunchProjectile )
|
|
EVENT( AI_AttackMelee, idAI::Event_AttackMelee )
|
|
EVENT( AI_DirectDamage, idAI::Event_DirectDamage )
|
|
EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint )
|
|
EVENT( AI_BeginAttack, idAI::Event_BeginAttack )
|
|
EVENT( AI_EndAttack, idAI::Event_EndAttack )
|
|
EVENT( AI_MeleeAttackToJoint, idAI::Event_MeleeAttackToJoint )
|
|
EVENT( AI_RandomPath, idAI::Event_RandomPath )
|
|
EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid )
|
|
EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid )
|
|
EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid )
|
|
EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll )
|
|
EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll )
|
|
EVENT( AI_SetHealth, idAI::Event_SetHealth )
|
|
EVENT( AI_GetHealth, idAI::Event_GetHealth )
|
|
EVENT( AI_AllowDamage, idAI::Event_AllowDamage )
|
|
EVENT( AI_IgnoreDamage, idAI::Event_IgnoreDamage )
|
|
EVENT( AI_GetCurrentYaw, idAI::Event_GetCurrentYaw )
|
|
EVENT( AI_TurnTo, idAI::Event_TurnTo )
|
|
EVENT( AI_TurnToPos, idAI::Event_TurnToPos )
|
|
EVENT( AI_TurnToEntity, idAI::Event_TurnToEntity )
|
|
EVENT( AI_MoveStatus, idAI::Event_MoveStatus )
|
|
EVENT( AI_StopMove, idAI::Event_StopMove )
|
|
EVENT( AI_MoveToCover, idAI::Event_MoveToCover )
|
|
EVENT( AI_MoveToEnemy, idAI::Event_MoveToEnemy )
|
|
EVENT( AI_MoveToEnemyHeight, idAI::Event_MoveToEnemyHeight )
|
|
EVENT( AI_MoveOutOfRange, idAI::Event_MoveOutOfRange )
|
|
EVENT( AI_MoveToAttackPosition, idAI::Event_MoveToAttackPosition )
|
|
EVENT( AI_Wander, idAI::Event_Wander )
|
|
EVENT( AI_MoveToEntity, idAI::Event_MoveToEntity )
|
|
EVENT( AI_MoveToPosition, idAI::Event_MoveToPosition )
|
|
EVENT( AI_SlideTo, idAI::Event_SlideTo )
|
|
EVENT( AI_FacingIdeal, idAI::Event_FacingIdeal )
|
|
EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy )
|
|
EVENT( AI_FaceEntity, idAI::Event_FaceEntity )
|
|
EVENT( AI_WaitAction, idAI::Event_WaitAction )
|
|
EVENT( AI_GetCombatNode, idAI::Event_GetCombatNode )
|
|
EVENT( AI_EnemyInCombatCone, idAI::Event_EnemyInCombatCone )
|
|
EVENT( AI_WaitMove, idAI::Event_WaitMove )
|
|
EVENT( AI_GetJumpVelocity, idAI::Event_GetJumpVelocity )
|
|
EVENT( AI_GetTrajectoryToPlayer, idAI::Event_GetTrajectoryToPlayer )
|
|
EVENT( AI_EntityInAttackCone, idAI::Event_EntityInAttackCone )
|
|
EVENT( AI_CanSeeEntity, idAI::Event_CanSeeEntity )
|
|
EVENT( AI_SetTalkTarget, idAI::Event_SetTalkTarget )
|
|
EVENT( AI_GetTalkTarget, idAI::Event_GetTalkTarget )
|
|
EVENT( AI_SetTalkState, idAI::Event_SetTalkState )
|
|
EVENT( AI_EnemyRange, idAI::Event_EnemyRange )
|
|
EVENT( AI_EnemyRange2D, idAI::Event_EnemyRange2D )
|
|
EVENT( AI_GetEnemy, idAI::Event_GetEnemy )
|
|
EVENT( AI_GetEnemyPos, idAI::Event_GetEnemyPos )
|
|
EVENT( AI_GetEnemyEyePos, idAI::Event_GetEnemyEyePos )
|
|
EVENT( AI_PredictEnemyPos, idAI::Event_PredictEnemyPos )
|
|
EVENT( AI_CanHitEnemy, idAI::Event_CanHitEnemy )
|
|
EVENT( AI_CanHitEnemyFromAnim, idAI::Event_CanHitEnemyFromAnim )
|
|
EVENT( AI_CanHitEnemyFromJoint, idAI::Event_CanHitEnemyFromJoint )
|
|
EVENT( AI_EnemyPositionValid, idAI::Event_EnemyPositionValid )
|
|
EVENT( AI_ChargeAttack, idAI::Event_ChargeAttack )
|
|
EVENT( AI_TestChargeAttack, idAI::Event_TestChargeAttack )
|
|
EVENT( AI_TestAnimMoveTowardEnemy, idAI::Event_TestAnimMoveTowardEnemy )
|
|
EVENT( AI_TestAnimMove, idAI::Event_TestAnimMove )
|
|
EVENT( AI_TestMoveToPosition, idAI::Event_TestMoveToPosition )
|
|
EVENT( AI_TestMeleeAttack, idAI::Event_TestMeleeAttack )
|
|
EVENT( AI_TestAnimAttack, idAI::Event_TestAnimAttack )
|
|
EVENT( AI_Burn, idAI::Event_Burn )
|
|
EVENT( AI_PreBurn, idAI::Event_PreBurn )
|
|
EVENT( AI_SetSmokeVisibility, idAI::Event_SetSmokeVisibility )
|
|
EVENT( AI_NumSmokeEmitters, idAI::Event_NumSmokeEmitters )
|
|
EVENT( AI_ClearBurn, idAI::Event_ClearBurn )
|
|
EVENT( AI_StopThinking, idAI::Event_StopThinking )
|
|
EVENT( AI_GetTurnDelta, idAI::Event_GetTurnDelta )
|
|
EVENT( AI_GetMoveType, idAI::Event_GetMoveType )
|
|
EVENT( AI_SetMoveType, idAI::Event_SetMoveType )
|
|
EVENT( AI_SaveMove, idAI::Event_SaveMove )
|
|
EVENT( AI_RestoreMove, idAI::Event_RestoreMove )
|
|
EVENT( AI_AllowMovement, idAI::Event_AllowMovement )
|
|
EVENT( AI_JumpFrame, idAI::Event_JumpFrame )
|
|
EVENT( AI_EnableClip, idAI::Event_EnableClip )
|
|
EVENT( AI_DisableClip, idAI::Event_DisableClip )
|
|
EVENT( AI_EnableGravity, idAI::Event_EnableGravity )
|
|
EVENT( AI_DisableGravity, idAI::Event_DisableGravity )
|
|
EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush )
|
|
EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush )
|
|
EVENT( AI_SetFlySpeed, idAI::Event_SetFlySpeed )
|
|
EVENT( AI_SetFlyOffset, idAI::Event_SetFlyOffset )
|
|
EVENT( AI_ClearFlyOffset, idAI::Event_ClearFlyOffset )
|
|
EVENT( AI_GetClosestHiddenTarget, idAI::Event_GetClosestHiddenTarget )
|
|
EVENT( AI_GetRandomTarget, idAI::Event_GetRandomTarget )
|
|
EVENT( AI_TravelDistanceToPoint, idAI::Event_TravelDistanceToPoint )
|
|
EVENT( AI_TravelDistanceToEntity, idAI::Event_TravelDistanceToEntity )
|
|
EVENT( AI_TravelDistanceBetweenPoints, idAI::Event_TravelDistanceBetweenPoints )
|
|
EVENT( AI_TravelDistanceBetweenEntities, idAI::Event_TravelDistanceBetweenEntities )
|
|
EVENT( AI_LookAtEntity, idAI::Event_LookAtEntity )
|
|
EVENT( AI_LookAtEnemy, idAI::Event_LookAtEnemy )
|
|
EVENT( AI_SetJointMod, idAI::Event_SetJointMod )
|
|
EVENT( AI_ThrowMoveable, idAI::Event_ThrowMoveable )
|
|
EVENT( AI_ThrowAF, idAI::Event_ThrowAF )
|
|
EVENT( EV_GetAngles, idAI::Event_GetAngles )
|
|
EVENT( EV_SetAngles, idAI::Event_SetAngles )
|
|
EVENT( AI_RealKill, idAI::Event_RealKill )
|
|
EVENT( AI_Kill, idAI::Event_Kill )
|
|
EVENT( AI_WakeOnFlashlight, idAI::Event_WakeOnFlashlight )
|
|
EVENT( AI_LocateEnemy, idAI::Event_LocateEnemy )
|
|
EVENT( AI_KickObstacles, idAI::Event_KickObstacles )
|
|
EVENT( AI_GetObstacle, idAI::Event_GetObstacle )
|
|
EVENT( AI_PushPointIntoAAS, idAI::Event_PushPointIntoAAS )
|
|
EVENT( AI_GetTurnRate, idAI::Event_GetTurnRate )
|
|
EVENT( AI_SetTurnRate, idAI::Event_SetTurnRate )
|
|
EVENT( AI_AnimTurn, idAI::Event_AnimTurn )
|
|
EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement )
|
|
EVENT( AI_TriggerParticles, idAI::Event_TriggerParticles )
|
|
EVENT( AI_FindActorsInBounds, idAI::Event_FindActorsInBounds )
|
|
EVENT( AI_CanReachPosition, idAI::Event_CanReachPosition )
|
|
EVENT( AI_CanReachEntity, idAI::Event_CanReachEntity )
|
|
EVENT( AI_CanReachEnemy, idAI::Event_CanReachEnemy )
|
|
EVENT( AI_GetReachableEntityPosition, idAI::Event_GetReachableEntityPosition )
|
|
EVENT( AI_MoveToPositionDirect, idAI::Event_MoveToPositionDirect )
|
|
EVENT( AI_AvoidObstacles, idAI::Event_AvoidObstacles )
|
|
EVENT( AI_TriggerFX, idAI::Event_TriggerFX )
|
|
EVENT( AI_StartEmitter, idAI::Event_StartEmitter )
|
|
EVENT( AI_GetEmitter, idAI::Event_GetEmitter )
|
|
EVENT( AI_StopEmitter, idAI::Event_StopEmitter )
|
|
END_CLASS
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_Activate
|
|
=====================
|
|
*/
|
|
void idAI::Event_Activate( idEntity *activator ) {
|
|
Activate( activator );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_Touch
|
|
=====================
|
|
*/
|
|
void idAI::Event_Touch( idEntity *other, trace_t *trace ) {
|
|
if ( !enemy.GetEntity() && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) {
|
|
Activate( other );
|
|
}
|
|
AI_PUSHED = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FindEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_FindEnemy( int useFOV ) {
|
|
int i;
|
|
idEntity *ent;
|
|
idActor *actor;
|
|
|
|
if ( gameLocal.InPlayerPVS( this ) ) {
|
|
for ( i = 0; i < gameLocal.numClients ; i++ ) {
|
|
ent = gameLocal.entities[ i ];
|
|
|
|
if ( !ent || !ent->IsType( idActor::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
actor = static_cast<idActor *>( ent );
|
|
if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( CanSee( actor, useFOV != 0 ) ) {
|
|
idThread::ReturnEntity( actor );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FindEnemyAI
|
|
=====================
|
|
*/
|
|
void idAI::Event_FindEnemyAI( int useFOV ) {
|
|
idEntity *ent;
|
|
idActor *actor;
|
|
idActor *bestEnemy;
|
|
float bestDist;
|
|
float dist;
|
|
idVec3 delta;
|
|
pvsHandle_t pvs;
|
|
|
|
pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() );
|
|
|
|
bestDist = idMath::INFINITY;
|
|
bestEnemy = NULL;
|
|
for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
|
|
if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( idActor::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
actor = static_cast<idActor *>( ent );
|
|
if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) {
|
|
continue;
|
|
}
|
|
|
|
delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin();
|
|
dist = delta.LengthSqr();
|
|
if ( ( dist < bestDist ) && CanSee( actor, useFOV != 0 ) ) {
|
|
bestDist = dist;
|
|
bestEnemy = actor;
|
|
}
|
|
}
|
|
|
|
gameLocal.pvs.FreeCurrentPVS( pvs );
|
|
idThread::ReturnEntity( bestEnemy );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FindEnemyInCombatNodes
|
|
=====================
|
|
*/
|
|
void idAI::Event_FindEnemyInCombatNodes() {
|
|
int i, j;
|
|
idCombatNode *node;
|
|
idEntity *ent;
|
|
idEntity *targetEnt;
|
|
idActor *actor;
|
|
|
|
if ( !gameLocal.InPlayerPVS( this ) ) {
|
|
// don't locate the player when we're not in his PVS
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < gameLocal.numClients ; i++ ) {
|
|
ent = gameLocal.entities[ i ];
|
|
|
|
if ( !ent || !ent->IsType( idActor::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
actor = static_cast<idActor *>( ent );
|
|
if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) {
|
|
continue;
|
|
}
|
|
|
|
for( j = 0; j < targets.Num(); j++ ) {
|
|
targetEnt = targets[ j ].GetEntity();
|
|
if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
node = static_cast<idCombatNode *>( targetEnt );
|
|
if ( !node->IsDisabled() && node->EntityInView( actor, actor->GetPhysics()->GetOrigin() ) ) {
|
|
idThread::ReturnEntity( actor );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_ClosestReachableEnemyOfEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) {
|
|
idActor *actor;
|
|
idActor *ent;
|
|
idActor *bestEnt;
|
|
float bestDistSquared;
|
|
float distSquared;
|
|
idVec3 delta;
|
|
int areaNum;
|
|
int enemyAreaNum;
|
|
aasPath_t path;
|
|
|
|
if ( !team_mate->IsType( idActor::Type ) ) {
|
|
gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() );
|
|
}
|
|
|
|
actor = static_cast<idActor *>( team_mate );
|
|
|
|
const idVec3 &origin = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( origin );
|
|
|
|
bestDistSquared = idMath::INFINITY;
|
|
bestEnt = NULL;
|
|
for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
|
|
if ( ent->fl.hidden ) {
|
|
continue;
|
|
}
|
|
delta = ent->GetPhysics()->GetOrigin() - origin;
|
|
distSquared = delta.LengthSqr();
|
|
if ( distSquared < bestDistSquared ) {
|
|
const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin();
|
|
enemyAreaNum = PointReachableAreaNum( enemyPos );
|
|
if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) {
|
|
bestEnt = ent;
|
|
bestDistSquared = distSquared;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( bestEnt );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_HeardSound
|
|
=====================
|
|
*/
|
|
void idAI::Event_HeardSound( int ignore_team ) {
|
|
// check if we heard any sounds in the last frame
|
|
idActor *actor = gameLocal.GetAlertEntity();
|
|
if ( actor != NULL && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) {
|
|
idVec3 pos = actor->GetPhysics()->GetOrigin();
|
|
idVec3 org = physicsObj.GetOrigin();
|
|
float dist = ( pos - org ).LengthSqr();
|
|
if ( dist < Square( AI_HEARING_RANGE ) ) {
|
|
idThread::ReturnEntity( actor );
|
|
return;
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetEnemy( idEntity *ent ) {
|
|
if ( !ent ) {
|
|
ClearEnemy();
|
|
} else if ( !ent->IsType( idActor::Type ) ) {
|
|
gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", ent->name.c_str() );
|
|
} else {
|
|
SetEnemy( static_cast<idActor *>( ent ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_ClearEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_ClearEnemy() {
|
|
ClearEnemy();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MuzzleFlash
|
|
=====================
|
|
*/
|
|
void idAI::Event_MuzzleFlash( const char *jointname ) {
|
|
idVec3 muzzle;
|
|
idMat3 axis;
|
|
|
|
GetMuzzle( jointname, muzzle, axis );
|
|
TriggerWeaponEffects( muzzle );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CreateMissile
|
|
=====================
|
|
*/
|
|
void idAI::Event_CreateMissile( const char *jointname ) {
|
|
idVec3 muzzle;
|
|
idMat3 axis;
|
|
|
|
if ( !projectileDef ) {
|
|
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
|
return idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
GetMuzzle( jointname, muzzle, axis );
|
|
CreateProjectile( muzzle, viewAxis[ 0 ] * physicsObj.GetGravityAxis() );
|
|
if ( projectile.GetEntity() ) {
|
|
if ( !jointname || !jointname[ 0 ] ) {
|
|
projectile.GetEntity()->Bind( this, true );
|
|
} else {
|
|
projectile.GetEntity()->BindToJoint( this, jointname, true );
|
|
}
|
|
}
|
|
idThread::ReturnEntity( projectile.GetEntity() );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_AttackMissile
|
|
=====================
|
|
*/
|
|
void idAI::Event_AttackMissile( const char *jointname ) {
|
|
idProjectile *proj;
|
|
|
|
proj = LaunchProjectile( jointname, enemy.GetEntity(), true );
|
|
idThread::ReturnEntity( proj );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FireMissileAtTarget
|
|
=====================
|
|
*/
|
|
void idAI::Event_FireMissileAtTarget( const char *jointname, const char *targetname ) {
|
|
idEntity *aent;
|
|
idProjectile *proj;
|
|
|
|
aent = gameLocal.FindEntity( targetname );
|
|
if ( !aent ) {
|
|
gameLocal.Warning( "Entity '%s' not found for 'fireMissileAtTarget'", targetname );
|
|
}
|
|
|
|
proj = LaunchProjectile( jointname, aent, false );
|
|
idThread::ReturnEntity( proj );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LaunchMissile
|
|
=====================
|
|
*/
|
|
void idAI::Event_LaunchMissile( const idVec3 &org, const idAngles &ang ) {
|
|
idVec3 start;
|
|
trace_t tr;
|
|
idBounds projBounds;
|
|
const idClipModel *projClip;
|
|
idMat3 axis;
|
|
float distance;
|
|
|
|
if ( !projectileDef ) {
|
|
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
axis = ang.ToMat3();
|
|
if ( !projectile.GetEntity() ) {
|
|
CreateProjectile( org, axis[ 0 ] );
|
|
}
|
|
|
|
// make sure the projectile starts inside the monster bounding box
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
projClip = projectile.GetEntity()->GetPhysics()->GetClipModel();
|
|
projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() );
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
|
|
start = org + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this );
|
|
|
|
// launch the projectile
|
|
idThread::ReturnEntity( projectile.GetEntity() );
|
|
projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin );
|
|
projectile = NULL;
|
|
|
|
TriggerWeaponEffects( tr.endpos );
|
|
|
|
lastAttackTime = gameLocal.time;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LaunchProjectile
|
|
=====================
|
|
*/
|
|
void idAI::Event_LaunchProjectile( const char *entityDefName ) {
|
|
idVec3 muzzle, start, dir;
|
|
const idDict *projDef;
|
|
idMat3 axis;
|
|
const idClipModel *projClip;
|
|
idBounds projBounds;
|
|
trace_t tr;
|
|
idEntity *ent;
|
|
const char *clsname;
|
|
float distance;
|
|
idProjectile *proj = NULL;
|
|
|
|
projDef = gameLocal.FindEntityDefDict( entityDefName );
|
|
|
|
gameLocal.SpawnEntityDef( *projDef, &ent, false );
|
|
if ( ent == NULL ) {
|
|
clsname = projectileDef->GetString( "classname" );
|
|
gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
|
|
return;
|
|
}
|
|
|
|
if ( !ent->IsType( idProjectile::Type ) ) {
|
|
clsname = ent->GetClassname();
|
|
gameLocal.Error( "'%s' is not an idProjectile", clsname );
|
|
}
|
|
proj = ( idProjectile * )ent;
|
|
|
|
GetMuzzle( "pistol", muzzle, axis );
|
|
proj->Create( this, muzzle, axis[0] );
|
|
|
|
// make sure the projectile starts inside the monster bounding box
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
projClip = proj->GetPhysics()->GetClipModel();
|
|
projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() );
|
|
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
|
|
start = muzzle + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
gameLocal.clip.Translation( tr, start, muzzle, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this );
|
|
muzzle = tr.endpos;
|
|
|
|
GetAimDir( muzzle, enemy.GetEntity(), this, dir );
|
|
|
|
proj->Launch( muzzle, dir, vec3_origin );
|
|
|
|
TriggerWeaponEffects( muzzle );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_AttackMelee
|
|
=====================
|
|
*/
|
|
void idAI::Event_AttackMelee( const char *meleeDefName ) {
|
|
bool hit;
|
|
|
|
hit = AttackMelee( meleeDefName );
|
|
idThread::ReturnInt( hit );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DirectDamage
|
|
=====================
|
|
*/
|
|
void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) {
|
|
DirectDamage( damageDefName, damageTarget );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_RadiusDamageFromJoint
|
|
=====================
|
|
*/
|
|
void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) {
|
|
jointHandle_t joint;
|
|
idVec3 org;
|
|
idMat3 axis;
|
|
|
|
if ( !jointname || !jointname[ 0 ] ) {
|
|
org = physicsObj.GetOrigin();
|
|
} else {
|
|
joint = animator.GetJointHandle( jointname );
|
|
if ( joint == INVALID_JOINT ) {
|
|
gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
|
|
}
|
|
GetJointWorldTransform( joint, gameLocal.time, org, axis );
|
|
}
|
|
|
|
gameLocal.RadiusDamage( org, this, this, this, this, damageDefName );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_RandomPath
|
|
=====================
|
|
*/
|
|
void idAI::Event_RandomPath() {
|
|
idPathCorner *path;
|
|
|
|
path = idPathCorner::RandomPath( this, NULL );
|
|
idThread::ReturnEntity( path );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BeginAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_BeginAttack( const char *name ) {
|
|
BeginAttack( name );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EndAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_EndAttack() {
|
|
EndAttack();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MeleeAttackToJoint
|
|
=====================
|
|
*/
|
|
void idAI::Event_MeleeAttackToJoint( const char *jointname, const char *meleeDefName ) {
|
|
jointHandle_t joint;
|
|
idVec3 start;
|
|
idVec3 end;
|
|
idMat3 axis;
|
|
trace_t trace;
|
|
idEntity *hitEnt;
|
|
|
|
joint = animator.GetJointHandle( jointname );
|
|
if ( joint == INVALID_JOINT ) {
|
|
gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
|
|
}
|
|
animator.GetJointTransform( joint, gameLocal.time, end, axis );
|
|
end = physicsObj.GetOrigin() + ( end + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
|
|
start = GetEyePosition();
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorYellow, start, end, 1 );
|
|
}
|
|
|
|
gameLocal.clip.TranslationEntities( trace, start, end, NULL, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( trace.fraction < 1.0f ) {
|
|
hitEnt = gameLocal.GetTraceEntity( trace );
|
|
if ( hitEnt != NULL && hitEnt->IsType( idActor::Type ) ) {
|
|
DirectDamage( meleeDefName, hitEnt );
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
}
|
|
|
|
idThread::ReturnInt( false );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CanBecomeSolid
|
|
=====================
|
|
*/
|
|
void idAI::Event_CanBecomeSolid() {
|
|
int i;
|
|
int num;
|
|
bool returnValue = true;
|
|
idEntity * hit;
|
|
idClipModel *cm;
|
|
idClipModel *clipModels[ MAX_GENTITIES ];
|
|
|
|
num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
|
|
for ( i = 0; i < num; i++ ) {
|
|
cm = clipModels[ i ];
|
|
|
|
// don't check render entities
|
|
if ( cm->IsRenderModel() ) {
|
|
continue;
|
|
}
|
|
|
|
hit = cm->GetEntity();
|
|
if ( ( hit == this ) || !hit->fl.takedamage ) {
|
|
continue;
|
|
}
|
|
|
|
if ( spawnClearMoveables && hit->IsType( idMoveable::Type ) || hit->IsType( idBarrel::Type ) || hit->IsType( idExplodingBarrel::Type ) ) {
|
|
idVec3 push;
|
|
push = hit->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
|
|
push.z = 30.f;
|
|
push.NormalizeFast();
|
|
if ( (idMath::Fabs(push.x) < 0.15f) && (idMath::Fabs(push.y) < 0.15f) ) {
|
|
push.x = 10.f; push.y = 10.f; push.z = 15.f;
|
|
push.NormalizeFast();
|
|
}
|
|
push *= 300.f;
|
|
hit->GetPhysics()->SetLinearVelocity( push );
|
|
}
|
|
|
|
if ( physicsObj.ClipContents( cm ) ) {
|
|
returnValue = false;
|
|
}
|
|
}
|
|
|
|
idThread::ReturnFloat( returnValue );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BecomeSolid
|
|
=====================
|
|
*/
|
|
void idAI::Event_BecomeSolid() {
|
|
physicsObj.EnableClip();
|
|
if ( spawnArgs.GetBool( "big_monster" ) ) {
|
|
physicsObj.SetContents( 0 );
|
|
} else if ( use_combat_bbox ) {
|
|
physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
|
|
} else {
|
|
physicsObj.SetContents( CONTENTS_BODY );
|
|
}
|
|
physicsObj.GetClipModel()->Link( gameLocal.clip );
|
|
fl.takedamage = !spawnArgs.GetBool( "noDamage" );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BecomeNonSolid
|
|
=====================
|
|
*/
|
|
void idAI::Event_BecomeNonSolid() {
|
|
fl.takedamage = false;
|
|
physicsObj.SetContents( 0 );
|
|
physicsObj.GetClipModel()->Unlink();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BecomeRagdoll
|
|
=====================
|
|
*/
|
|
void idAI::Event_BecomeRagdoll() {
|
|
bool result;
|
|
|
|
result = StartRagdoll();
|
|
idThread::ReturnInt( result );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_StopRagdoll
|
|
=====================
|
|
*/
|
|
void idAI::Event_StopRagdoll() {
|
|
StopRagdoll();
|
|
|
|
// set back the monster physics
|
|
SetPhysics( &physicsObj );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetHealth
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetHealth( float newHealth ) {
|
|
health = newHealth;
|
|
fl.takedamage = true;
|
|
if ( health > 0 ) {
|
|
AI_DEAD = false;
|
|
} else {
|
|
AI_DEAD = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetHealth
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetHealth() {
|
|
idThread::ReturnFloat( health );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_AllowDamage
|
|
=====================
|
|
*/
|
|
void idAI::Event_AllowDamage() {
|
|
fl.takedamage = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_IgnoreDamage
|
|
=====================
|
|
*/
|
|
void idAI::Event_IgnoreDamage() {
|
|
fl.takedamage = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetCurrentYaw
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetCurrentYaw() {
|
|
idThread::ReturnFloat( current_yaw );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TurnTo
|
|
=====================
|
|
*/
|
|
void idAI::Event_TurnTo( float angle ) {
|
|
TurnToward( angle );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TurnToPos
|
|
=====================
|
|
*/
|
|
void idAI::Event_TurnToPos( const idVec3 &pos ) {
|
|
TurnToward( pos );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TurnToEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_TurnToEntity( idEntity *ent ) {
|
|
if ( ent ) {
|
|
TurnToward( ent->GetPhysics()->GetOrigin() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveStatus
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveStatus() {
|
|
idThread::ReturnInt( move.moveStatus );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_StopMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_StopMove() {
|
|
StopMove( MOVE_STATUS_DONE );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToCover
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToCover() {
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
if ( !enemyEnt || !MoveToCover( enemyEnt, lastVisibleEnemyPos ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToEnemy() {
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
if ( !enemy.GetEntity() || !MoveToEnemy() ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToEnemyHeight
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToEnemyHeight() {
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
MoveToEnemyHeight();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveOutOfRange
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveOutOfRange( idEntity *entity, float range ) {
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
MoveOutOfRange( entity, range );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToAttackPosition
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToAttackPosition( idEntity *entity, const char *attack_anim ) {
|
|
int anim;
|
|
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, attack_anim );
|
|
if ( !anim ) {
|
|
gameLocal.Error( "Unknown anim '%s'", attack_anim );
|
|
}
|
|
|
|
MoveToAttackPosition( entity, anim );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToEntity( idEntity *ent ) {
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
if ( ent ) {
|
|
MoveToEntity( ent );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_MoveToPosition
|
|
=====================
|
|
*/
|
|
void idAI::Event_MoveToPosition( const idVec3 &pos ) {
|
|
StopMove( MOVE_STATUS_DONE );
|
|
MoveToPosition( pos );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SlideTo
|
|
=====================
|
|
*/
|
|
void idAI::Event_SlideTo( const idVec3 &pos, float time ) {
|
|
SlideToPosition( pos, time );
|
|
}
|
|
/*
|
|
=====================
|
|
idAI::Event_Wander
|
|
=====================
|
|
*/
|
|
void idAI::Event_Wander() {
|
|
WanderAround();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FacingIdeal
|
|
=====================
|
|
*/
|
|
void idAI::Event_FacingIdeal() {
|
|
bool facing = FacingIdeal();
|
|
idThread::ReturnInt( facing );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FaceEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_FaceEnemy() {
|
|
FaceEnemy();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FaceEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_FaceEntity( idEntity *ent ) {
|
|
FaceEntity( ent );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_WaitAction
|
|
=====================
|
|
*/
|
|
void idAI::Event_WaitAction( const char *waitForState ) {
|
|
if ( idThread::BeginMultiFrameEvent( this, &AI_WaitAction ) ) {
|
|
SetWaitState( waitForState );
|
|
}
|
|
|
|
if ( !WaitState() ) {
|
|
idThread::EndMultiFrameEvent( this, &AI_WaitAction );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetCombatNode
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetCombatNode() {
|
|
int i;
|
|
float dist;
|
|
idEntity *targetEnt;
|
|
idCombatNode *node;
|
|
float bestDist;
|
|
idCombatNode *bestNode;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( !targets.Num() ) {
|
|
// no combat nodes
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
if ( !enemyEnt || !EnemyPositionValid() ) {
|
|
// don't return a combat node if we don't have an enemy or
|
|
// if we can see he's not in the last place we saw him
|
|
|
|
if ( team == 0 ) {
|
|
// find the closest attack node to the player
|
|
bestNode = NULL;
|
|
const idVec3 &myPos = physicsObj.GetOrigin();
|
|
const idVec3 &playerPos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin();
|
|
|
|
bestDist = ( myPos - playerPos ).LengthSqr();
|
|
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
targetEnt = targets[ i ].GetEntity();
|
|
if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
node = static_cast<idCombatNode *>( targetEnt );
|
|
if ( !node->IsDisabled() ) {
|
|
idVec3 org = node->GetPhysics()->GetOrigin();
|
|
dist = ( playerPos - org ).LengthSqr();
|
|
if ( dist < bestDist ) {
|
|
bestNode = node;
|
|
bestDist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( bestNode );
|
|
return;
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
// find the closest attack node that can see our enemy and is closer than our enemy
|
|
bestNode = NULL;
|
|
const idVec3 &myPos = physicsObj.GetOrigin();
|
|
bestDist = ( myPos - lastVisibleEnemyPos ).LengthSqr();
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
targetEnt = targets[ i ].GetEntity();
|
|
if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) {
|
|
continue;
|
|
}
|
|
|
|
node = static_cast<idCombatNode *>( targetEnt );
|
|
if ( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) {
|
|
idVec3 org = node->GetPhysics()->GetOrigin();
|
|
dist = ( myPos - org ).LengthSqr();
|
|
if ( dist < bestDist ) {
|
|
bestNode = node;
|
|
bestDist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( bestNode );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnemyInCombatCone
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnemyInCombatCone( idEntity *ent, int use_current_enemy_location ) {
|
|
idCombatNode *node;
|
|
bool result;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( !targets.Num() ) {
|
|
// no combat nodes
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( !enemyEnt ) {
|
|
// have to have an enemy
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( !ent || !ent->IsType( idCombatNode::Type ) ) {
|
|
// not a combat node
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
//Allow the level designers define attack nodes that the enemy should never leave.
|
|
//This is different that the turrent type combat nodes because they can play an animation
|
|
if(ent->spawnArgs.GetBool("neverLeave", "0")) {
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
|
|
node = static_cast<idCombatNode *>( ent );
|
|
if ( use_current_enemy_location ) {
|
|
const idVec3 &pos = enemyEnt->GetPhysics()->GetOrigin();
|
|
result = node->EntityInView( enemyEnt, pos );
|
|
} else {
|
|
result = node->EntityInView( enemyEnt, lastVisibleEnemyPos );
|
|
}
|
|
|
|
idThread::ReturnInt( result );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_WaitMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_WaitMove() {
|
|
idThread::BeginMultiFrameEvent( this, &AI_WaitMove );
|
|
|
|
if ( MoveDone() ) {
|
|
idThread::EndMultiFrameEvent( this, &AI_WaitMove );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetJumpVelocity
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetJumpVelocity( const idVec3 &pos, float speed, float max_height ) {
|
|
idVec3 start;
|
|
idVec3 end;
|
|
idVec3 dir;
|
|
float dist;
|
|
bool result;
|
|
idEntity *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnVector( vec3_zero );
|
|
return;
|
|
}
|
|
|
|
start = physicsObj.GetOrigin();
|
|
end = pos;
|
|
dir = end - start;
|
|
dist = dir.Normalize();
|
|
if ( dist > 16.0f ) {
|
|
dist -= 16.0f;
|
|
end -= dir * 16.0f;
|
|
}
|
|
|
|
result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, max_height, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir );
|
|
if ( result ) {
|
|
idThread::ReturnVector( dir * speed );
|
|
} else {
|
|
idThread::ReturnVector( vec3_zero );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EntityInAttackCone
|
|
=====================
|
|
*/
|
|
void idAI::Event_EntityInAttackCone( idEntity *ent ) {
|
|
float attack_cone;
|
|
idVec3 delta;
|
|
float yaw;
|
|
float relYaw;
|
|
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
delta = ent->GetPhysics()->GetOrigin() - GetEyePosition();
|
|
|
|
// get our gravity normal
|
|
const idVec3 &gravityDir = GetPhysics()->GetGravityNormal();
|
|
|
|
// infinite vertical vision, so project it onto our orientation plane
|
|
delta -= gravityDir * ( gravityDir * delta );
|
|
|
|
delta.Normalize();
|
|
yaw = delta.ToYaw();
|
|
|
|
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
|
|
relYaw = idMath::AngleNormalize180( ideal_yaw - yaw );
|
|
if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) {
|
|
idThread::ReturnInt( true );
|
|
} else {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CanSeeEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_CanSeeEntity( idEntity *ent ) {
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
bool cansee = CanSee( ent, false );
|
|
idThread::ReturnInt( cansee );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetTalkTarget
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetTalkTarget( idEntity *target ) {
|
|
if ( target && !target->IsType( idActor::Type ) ) {
|
|
gameLocal.Error( "Cannot set talk target to '%s'. Not a character or player.", target->GetName() );
|
|
}
|
|
talkTarget = static_cast<idActor *>( target );
|
|
if ( target ) {
|
|
AI_TALK = true;
|
|
} else {
|
|
AI_TALK = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetTalkTarget
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetTalkTarget() {
|
|
idThread::ReturnEntity( talkTarget.GetEntity() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetTalkState
|
|
================
|
|
*/
|
|
void idAI::Event_SetTalkState( int state ) {
|
|
if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) {
|
|
gameLocal.Error( "Invalid talk state (%d)", state );
|
|
}
|
|
|
|
talk_state = static_cast<talkState_t>( state );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnemyRange
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnemyRange() {
|
|
float dist;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( enemyEnt ) {
|
|
dist = ( enemyEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length();
|
|
} else {
|
|
// Just some really high number
|
|
dist = idMath::INFINITY;
|
|
}
|
|
|
|
idThread::ReturnFloat( dist );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnemyRange2D
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnemyRange2D() {
|
|
float dist;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( enemyEnt ) {
|
|
dist = ( enemyEnt->GetPhysics()->GetOrigin().ToVec2() - GetPhysics()->GetOrigin().ToVec2() ).Length();
|
|
} else {
|
|
// Just some really high number
|
|
dist = idMath::INFINITY;
|
|
}
|
|
|
|
idThread::ReturnFloat( dist );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetEnemy() {
|
|
idThread::ReturnEntity( enemy.GetEntity() );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetEnemyPos
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetEnemyPos() {
|
|
idThread::ReturnVector( lastVisibleEnemyPos );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetEnemyEyePos
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetEnemyEyePos() {
|
|
idThread::ReturnVector( lastVisibleEnemyPos + lastVisibleEnemyEyeOffset );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_PredictEnemyPos
|
|
=====================
|
|
*/
|
|
void idAI::Event_PredictEnemyPos( float time ) {
|
|
predictedPath_t path;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
// if no enemy set
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnVector( physicsObj.GetOrigin() );
|
|
return;
|
|
}
|
|
|
|
// predict the enemy movement
|
|
idAI::PredictPath( enemyEnt, aas, lastVisibleEnemyPos, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
idThread::ReturnVector( path.endPos );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CanHitEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_CanHitEnemy() {
|
|
trace_t tr;
|
|
idEntity *hit;
|
|
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
// don't check twice per frame
|
|
if ( gameLocal.time == lastHitCheckTime ) {
|
|
idThread::ReturnInt( lastHitCheckResult );
|
|
return;
|
|
}
|
|
|
|
lastHitCheckTime = gameLocal.time;
|
|
|
|
idVec3 toPos = enemyEnt->GetEyePosition();
|
|
idVec3 eye = GetEyePosition();
|
|
idVec3 dir;
|
|
|
|
// expand the ray out as far as possible so we can detect anything behind the enemy
|
|
dir = toPos - eye;
|
|
dir.Normalize();
|
|
toPos = eye + dir * MAX_WORLD_SIZE;
|
|
gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this );
|
|
hit = gameLocal.GetTraceEntity( tr );
|
|
if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) {
|
|
lastHitCheckResult = true;
|
|
} else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) &&
|
|
( static_cast<idAI *>( hit )->team != team ) ) {
|
|
lastHitCheckResult = true;
|
|
} else {
|
|
lastHitCheckResult = false;
|
|
}
|
|
|
|
idThread::ReturnInt( lastHitCheckResult );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CanHitEnemyFromAnim
|
|
=====================
|
|
*/
|
|
void idAI::Event_CanHitEnemyFromAnim( const char *animname ) {
|
|
int anim;
|
|
idVec3 dir;
|
|
idVec3 local_dir;
|
|
idVec3 fromPos;
|
|
idMat3 axis;
|
|
idVec3 start;
|
|
trace_t tr;
|
|
float distance;
|
|
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
// just do a ray test if close enough
|
|
if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) {
|
|
Event_CanHitEnemy();
|
|
return;
|
|
}
|
|
|
|
// calculate the world transform of the launch position
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
dir = lastVisibleEnemyPos - org;
|
|
physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
|
|
local_dir.z = 0.0f;
|
|
local_dir.ToVec2().Normalize();
|
|
axis = local_dir.ToMat3();
|
|
fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis;
|
|
|
|
if ( projectileClipModel == NULL ) {
|
|
CreateProjectileClipModel();
|
|
}
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
const idBounds &projBounds = projectileClipModel->GetBounds();
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
|
|
start = org + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, fromPos, projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this );
|
|
fromPos = tr.endpos;
|
|
|
|
if ( GetAimDir( fromPos, enemy.GetEntity(), this, dir ) ) {
|
|
idThread::ReturnInt( true );
|
|
} else {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_CanHitEnemyFromJoint
|
|
=====================
|
|
*/
|
|
void idAI::Event_CanHitEnemyFromJoint( const char *jointname ) {
|
|
trace_t tr;
|
|
idVec3 muzzle;
|
|
idMat3 axis;
|
|
idVec3 start;
|
|
float distance;
|
|
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
// don't check twice per frame
|
|
if ( gameLocal.time == lastHitCheckTime ) {
|
|
idThread::ReturnInt( lastHitCheckResult );
|
|
return;
|
|
}
|
|
|
|
lastHitCheckTime = gameLocal.time;
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
idVec3 toPos = enemyEnt->GetEyePosition();
|
|
jointHandle_t joint = animator.GetJointHandle( jointname );
|
|
if ( joint == INVALID_JOINT ) {
|
|
gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
|
|
}
|
|
animator.GetJointTransform( joint, gameLocal.time, muzzle, axis );
|
|
muzzle = org + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
|
|
|
|
if ( projectileClipModel == NULL ) {
|
|
CreateProjectileClipModel();
|
|
}
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
const idBounds &projBounds = projectileClipModel->GetBounds();
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
|
|
start = org + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, muzzle, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
|
|
muzzle = tr.endpos;
|
|
|
|
gameLocal.clip.Translation( tr, muzzle, toPos, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) {
|
|
lastHitCheckResult = true;
|
|
} else {
|
|
lastHitCheckResult = false;
|
|
}
|
|
|
|
idThread::ReturnInt( lastHitCheckResult );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnemyPositionValid
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnemyPositionValid() {
|
|
bool result;
|
|
|
|
result = EnemyPositionValid();
|
|
idThread::ReturnInt( result );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_ChargeAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_ChargeAttack( const char *damageDef ) {
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
|
if ( enemyEnt ) {
|
|
idVec3 enemyOrg;
|
|
|
|
if ( move.moveType == MOVETYPE_FLY ) {
|
|
// position destination so that we're in the enemy's view
|
|
enemyOrg = enemyEnt->GetEyePosition();
|
|
enemyOrg -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset;
|
|
} else {
|
|
enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
BeginAttack( damageDef );
|
|
DirectMoveToPosition( enemyOrg );
|
|
TurnToward( enemyOrg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestChargeAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestChargeAttack() {
|
|
trace_t trace;
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
predictedPath_t path;
|
|
idVec3 end;
|
|
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnFloat( 0.0f );
|
|
return;
|
|
}
|
|
|
|
if ( move.moveType == MOVETYPE_FLY ) {
|
|
// position destination so that we're in the enemy's view
|
|
end = enemyEnt->GetEyePosition();
|
|
end -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset;
|
|
} else {
|
|
end = enemyEnt->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), end - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), end, 1 );
|
|
gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), end, 1 );
|
|
}
|
|
|
|
if ( ( path.endEvent == 0 ) || ( path.blockingEntity == enemyEnt ) ) {
|
|
idVec3 delta = end - physicsObj.GetOrigin();
|
|
float time = delta.LengthFast();
|
|
idThread::ReturnFloat( time );
|
|
} else {
|
|
idThread::ReturnFloat( 0.0f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimMoveTowardEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
idVec3 moveVec;
|
|
float yaw;
|
|
idVec3 delta;
|
|
idActor *enemyEnt;
|
|
|
|
enemyEnt = enemy.GetEntity();
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin();
|
|
yaw = delta.ToYaw();
|
|
|
|
moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis();
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 );
|
|
gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 );
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimMove( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
idVec3 moveVec;
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis();
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 );
|
|
gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 );
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestMoveToPosition
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestMoveToPosition( const idVec3 &position ) {
|
|
predictedPath_t path;
|
|
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, 1 );
|
|
gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, 1 );
|
|
if ( path.endEvent ) {
|
|
gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, 1 );
|
|
}
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestMeleeAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestMeleeAttack() {
|
|
bool result = TestMelee();
|
|
idThread::ReturnInt( result );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimAttack( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.GetEntity() ) );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_PreBurn
|
|
=====================
|
|
*/
|
|
void idAI::Event_PreBurn() {
|
|
// No grabbing after the burn has started!
|
|
noGrab = true;
|
|
|
|
// for now this just turns shadows off
|
|
renderEntity.noShadow = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_Burn
|
|
=====================
|
|
*/
|
|
void idAI::Event_Burn() {
|
|
renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
|
|
SpawnParticles( "smoke_burnParticleSystem" );
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_ClearBurn
|
|
=====================
|
|
*/
|
|
void idAI::Event_ClearBurn() {
|
|
renderEntity.noShadow = spawnArgs.GetBool( "noshadows" );
|
|
renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f;
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetSmokeVisibility
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetSmokeVisibility( int num, int on ) {
|
|
int i;
|
|
int time;
|
|
|
|
if ( num >= particles.Num() ) {
|
|
gameLocal.Warning( "Particle #%d out of range (%d particles) on entity '%s'", num, particles.Num(), name.c_str() );
|
|
return;
|
|
}
|
|
|
|
if ( on != 0 ) {
|
|
time = gameLocal.time;
|
|
BecomeActive( TH_UPDATEPARTICLES );
|
|
} else {
|
|
time = 0;
|
|
}
|
|
|
|
if ( num >= 0 ) {
|
|
particles[ num ].time = time;
|
|
} else {
|
|
for ( i = 0; i < particles.Num(); i++ ) {
|
|
particles[ i ].time = time;
|
|
}
|
|
}
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_NumSmokeEmitters
|
|
=====================
|
|
*/
|
|
void idAI::Event_NumSmokeEmitters() {
|
|
idThread::ReturnInt( particles.Num() );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_StopThinking
|
|
=====================
|
|
*/
|
|
void idAI::Event_StopThinking() {
|
|
BecomeInactive( TH_THINK );
|
|
idThread *thread = idThread::CurrentThread();
|
|
if ( thread ) {
|
|
thread->DoneProcessing();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetTurnDelta
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetTurnDelta() {
|
|
float amount;
|
|
|
|
if ( turnRate ) {
|
|
amount = idMath::AngleNormalize180( ideal_yaw - current_yaw );
|
|
idThread::ReturnFloat( amount );
|
|
} else {
|
|
idThread::ReturnFloat( 0.0f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetMoveType
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetMoveType() {
|
|
idThread::ReturnInt( move.moveType );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetMoveTypes
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetMoveType( int moveType ) {
|
|
if ( ( moveType < 0 ) || ( moveType >= NUM_MOVETYPES ) ) {
|
|
gameLocal.Error( "Invalid movetype %d", moveType );
|
|
}
|
|
|
|
move.moveType = static_cast<moveType_t>( moveType );
|
|
if ( move.moveType == MOVETYPE_FLY ) {
|
|
travelFlags = TFL_WALK|TFL_AIR|TFL_FLY;
|
|
} else {
|
|
travelFlags = TFL_WALK|TFL_AIR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SaveMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_SaveMove() {
|
|
savedMove = move;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_RestoreMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_RestoreMove() {
|
|
idVec3 goalPos;
|
|
idVec3 dest;
|
|
|
|
switch( savedMove.moveCommand ) {
|
|
case MOVE_NONE :
|
|
StopMove( savedMove.moveStatus );
|
|
break;
|
|
|
|
case MOVE_FACE_ENEMY :
|
|
FaceEnemy();
|
|
break;
|
|
|
|
case MOVE_FACE_ENTITY :
|
|
FaceEntity( savedMove.goalEntity.GetEntity() );
|
|
break;
|
|
|
|
case MOVE_TO_ENEMY :
|
|
MoveToEnemy();
|
|
break;
|
|
|
|
case MOVE_TO_ENEMYHEIGHT :
|
|
MoveToEnemyHeight();
|
|
break;
|
|
|
|
case MOVE_TO_ENTITY :
|
|
MoveToEntity( savedMove.goalEntity.GetEntity() );
|
|
break;
|
|
|
|
case MOVE_OUT_OF_RANGE :
|
|
MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range );
|
|
break;
|
|
|
|
case MOVE_TO_ATTACK_POSITION :
|
|
MoveToAttackPosition( savedMove.goalEntity.GetEntity(), savedMove.anim );
|
|
break;
|
|
|
|
case MOVE_TO_COVER :
|
|
MoveToCover( savedMove.goalEntity.GetEntity(), lastVisibleEnemyPos );
|
|
break;
|
|
|
|
case MOVE_TO_POSITION :
|
|
MoveToPosition( savedMove.moveDest );
|
|
break;
|
|
|
|
case MOVE_TO_POSITION_DIRECT :
|
|
DirectMoveToPosition( savedMove.moveDest );
|
|
break;
|
|
|
|
case MOVE_SLIDE_TO_POSITION :
|
|
SlideToPosition( savedMove.moveDest, savedMove.duration );
|
|
break;
|
|
|
|
case MOVE_WANDER :
|
|
WanderAround();
|
|
break;
|
|
}
|
|
|
|
if ( GetMovePos( goalPos ) ) {
|
|
CheckObstacleAvoidance( goalPos, dest );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_AllowMovement
|
|
=====================
|
|
*/
|
|
void idAI::Event_AllowMovement( float flag ) {
|
|
allowMove = ( flag != 0.0f );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_JumpFrame
|
|
=====================
|
|
*/
|
|
void idAI::Event_JumpFrame() {
|
|
AI_JUMP = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableClip
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableClip() {
|
|
physicsObj.SetClipMask( MASK_MONSTERSOLID );
|
|
disableGravity = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableClip
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableClip() {
|
|
physicsObj.SetClipMask( 0 );
|
|
disableGravity = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableGravity
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableGravity() {
|
|
disableGravity = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableGravity
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableGravity() {
|
|
disableGravity = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableAFPush
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableAFPush() {
|
|
af_push_moveables = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableAFPush
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableAFPush() {
|
|
af_push_moveables = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetFlySpeed
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetFlySpeed( float speed ) {
|
|
if ( move.speed == fly_speed ) {
|
|
move.speed = speed;
|
|
}
|
|
fly_speed = speed;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetFlyOffset
|
|
================
|
|
*/
|
|
void idAI::Event_SetFlyOffset( int offset ) {
|
|
fly_offset = offset;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ClearFlyOffset
|
|
================
|
|
*/
|
|
void idAI::Event_ClearFlyOffset() {
|
|
spawnArgs.GetInt( "fly_offset", "0", fly_offset );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetClosestHiddenTarget
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetClosestHiddenTarget( const char *type ) {
|
|
int i;
|
|
idEntity *ent;
|
|
idEntity *bestEnt;
|
|
float time;
|
|
float bestTime;
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( !enemyEnt ) {
|
|
// no enemy to hide from
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
if ( targets.Num() == 1 ) {
|
|
ent = targets[ 0 ].GetEntity();
|
|
if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) {
|
|
if ( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, ent->GetPhysics()->GetOrigin() ) ) {
|
|
idThread::ReturnEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
bestEnt = NULL;
|
|
bestTime = idMath::INFINITY;
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
ent = targets[ i ].GetEntity();
|
|
if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) {
|
|
const idVec3 &destOrg = ent->GetPhysics()->GetOrigin();
|
|
time = TravelDistance( org, destOrg );
|
|
if ( ( time >= 0.0f ) && ( time < bestTime ) ) {
|
|
if ( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, destOrg ) ) {
|
|
bestEnt = ent;
|
|
bestTime = time;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
idThread::ReturnEntity( bestEnt );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_GetRandomTarget
|
|
=====================
|
|
*/
|
|
void idAI::Event_GetRandomTarget( const char *type ) {
|
|
int i;
|
|
int num;
|
|
int which;
|
|
idEntity *ent;
|
|
idEntity *ents[ MAX_GENTITIES ];
|
|
|
|
num = 0;
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
ent = targets[ i ].GetEntity();
|
|
if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) {
|
|
ents[ num++ ] = ent;
|
|
if ( num >= MAX_GENTITIES ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !num ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
which = gameLocal.random.RandomInt( num );
|
|
idThread::ReturnEntity( ents[ which ] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TravelDistanceToPoint
|
|
================
|
|
*/
|
|
void idAI::Event_TravelDistanceToPoint( const idVec3 &pos ) {
|
|
float time;
|
|
|
|
time = TravelDistance( physicsObj.GetOrigin(), pos );
|
|
idThread::ReturnFloat( time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TravelDistanceToEntity
|
|
================
|
|
*/
|
|
void idAI::Event_TravelDistanceToEntity( idEntity *ent ) {
|
|
float time;
|
|
|
|
time = TravelDistance( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() );
|
|
idThread::ReturnFloat( time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TravelDistanceBetweenPoints
|
|
================
|
|
*/
|
|
void idAI::Event_TravelDistanceBetweenPoints( const idVec3 &source, const idVec3 &dest ) {
|
|
float time;
|
|
|
|
time = TravelDistance( source, dest );
|
|
idThread::ReturnFloat( time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TravelDistanceBetweenEntities
|
|
================
|
|
*/
|
|
void idAI::Event_TravelDistanceBetweenEntities( idEntity *source, idEntity *dest ) {
|
|
float time;
|
|
|
|
assert( source );
|
|
assert( dest );
|
|
time = TravelDistance( source->GetPhysics()->GetOrigin(), dest->GetPhysics()->GetOrigin() );
|
|
idThread::ReturnFloat( time );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LookAtEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_LookAtEntity( idEntity *ent, float duration ) {
|
|
if ( ent == this ) {
|
|
ent = NULL;
|
|
}
|
|
|
|
if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) {
|
|
focusEntity = ent;
|
|
alignHeadTime = gameLocal.time;
|
|
forceAlignHeadTime = gameLocal.time + SEC2MS( 1 );
|
|
blink_time = 0;
|
|
}
|
|
|
|
focusTime = gameLocal.time + SEC2MS( duration );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LookAtEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_LookAtEnemy( float duration ) {
|
|
idActor *enemyEnt;
|
|
|
|
enemyEnt = enemy.GetEntity();
|
|
if ( ( enemyEnt != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) {
|
|
focusEntity = enemyEnt;
|
|
alignHeadTime = gameLocal.time;
|
|
forceAlignHeadTime = gameLocal.time + SEC2MS( 1 );
|
|
blink_time = 0;
|
|
}
|
|
|
|
focusTime = gameLocal.time + SEC2MS( duration );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idAI::Event_SetJointMod
|
|
===============
|
|
*/
|
|
void idAI::Event_SetJointMod( int allow ) {
|
|
allowJointMod = ( allow != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ThrowMoveable
|
|
================
|
|
*/
|
|
void idAI::Event_ThrowMoveable() {
|
|
idEntity *ent;
|
|
idEntity *moveable = NULL;
|
|
|
|
for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) {
|
|
if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::Type ) ) {
|
|
moveable = ent;
|
|
break;
|
|
}
|
|
}
|
|
if ( moveable ) {
|
|
moveable->Unbind();
|
|
moveable->PostEventMS( &EV_SetOwner, 200, NULL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ThrowAF
|
|
================
|
|
*/
|
|
void idAI::Event_ThrowAF() {
|
|
idEntity *ent;
|
|
idEntity *af = NULL;
|
|
|
|
for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) {
|
|
if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::Type ) ) {
|
|
af = ent;
|
|
break;
|
|
}
|
|
}
|
|
if ( af ) {
|
|
af->Unbind();
|
|
af->PostEventMS( &EV_SetOwner, 200, NULL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetAngles
|
|
================
|
|
*/
|
|
void idAI::Event_SetAngles( idAngles const &ang ) {
|
|
current_yaw = ang.yaw;
|
|
viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetAngles
|
|
================
|
|
*/
|
|
void idAI::Event_GetAngles() {
|
|
idThread::ReturnVector( idVec3( 0.0f, current_yaw, 0.0f ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetTrajectoryToPlayer
|
|
================
|
|
*/
|
|
void idAI::Event_GetTrajectoryToPlayer() {
|
|
|
|
idVec3 start;
|
|
idVec3 end;
|
|
idVec3 dir;
|
|
float dist;
|
|
// bool result;
|
|
idEntity *enemyEnt = enemy.GetEntity();
|
|
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnVector( vec3_zero );
|
|
return;
|
|
}
|
|
|
|
end = enemyEnt->GetPhysics()->GetOrigin();
|
|
float speed = 400.0f;
|
|
|
|
if ( speed <= 0.0f ) {
|
|
gameLocal.Error( "Invalid speed. speed must be > 0." );
|
|
}
|
|
|
|
start = physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 50.0f );
|
|
dir = end - start;
|
|
dist = dir.Normalize();
|
|
if ( dist > 16.0f ) {
|
|
dist -= 16.0f;
|
|
end -= dir * 16.0f;
|
|
}
|
|
|
|
|
|
|
|
idVec3 gravity;
|
|
ballistics_t ballistics[2];
|
|
Ballistics( start, end, speed, 0.0f, ballistics );
|
|
dir.y = ballistics[0].angle;
|
|
|
|
|
|
|
|
|
|
|
|
// result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, 1000.0f, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir );
|
|
// if ( result ) {
|
|
idThread::ReturnVector( dir * speed );
|
|
// } else {
|
|
// idThread::ReturnVector( vec3_zero );
|
|
// }
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_RealKill
|
|
================
|
|
*/
|
|
void idAI::Event_RealKill() {
|
|
health = 0;
|
|
|
|
if ( af.IsLoaded() ) {
|
|
// clear impacts
|
|
af.Rest();
|
|
|
|
// physics is turned off by calling af.Rest()
|
|
BecomeActive( TH_PHYSICS );
|
|
}
|
|
|
|
Killed( this, this, 0, vec3_zero, INVALID_JOINT );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_Kill
|
|
================
|
|
*/
|
|
void idAI::Event_Kill() {
|
|
PostEventMS( &AI_RealKill, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_WakeOnFlashlight
|
|
================
|
|
*/
|
|
void idAI::Event_WakeOnFlashlight( int enable ) {
|
|
wakeOnFlashlight = ( enable != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_LocateEnemy
|
|
================
|
|
*/
|
|
void idAI::Event_LocateEnemy() {
|
|
idActor *enemyEnt;
|
|
int areaNum;
|
|
|
|
enemyEnt = enemy.GetEntity();
|
|
if ( !enemyEnt ) {
|
|
return;
|
|
}
|
|
|
|
enemyEnt->GetAASLocation( aas, lastReachableEnemyPos, areaNum );
|
|
SetEnemyPosition();
|
|
UpdateEnemyPosition();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_KickObstacles
|
|
================
|
|
*/
|
|
void idAI::Event_KickObstacles( idEntity *kickEnt, float force ) {
|
|
idVec3 dir;
|
|
idEntity *obEnt;
|
|
|
|
if ( kickEnt ) {
|
|
obEnt = kickEnt;
|
|
} else {
|
|
obEnt = move.obstacle.GetEntity();
|
|
}
|
|
|
|
if ( obEnt ) {
|
|
dir = obEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin();
|
|
dir.Normalize();
|
|
} else {
|
|
dir = viewAxis[ 0 ];
|
|
}
|
|
KickObstacles( dir, force, obEnt );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetObstacle
|
|
================
|
|
*/
|
|
void idAI::Event_GetObstacle() {
|
|
idThread::ReturnEntity( move.obstacle.GetEntity() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_PushPointIntoAAS
|
|
================
|
|
*/
|
|
void idAI::Event_PushPointIntoAAS( const idVec3 &pos ) {
|
|
int areaNum;
|
|
idVec3 newPos;
|
|
|
|
areaNum = PointReachableAreaNum( pos );
|
|
if ( areaNum ) {
|
|
newPos = pos;
|
|
aas->PushPointIntoAreaNum( areaNum, newPos );
|
|
idThread::ReturnVector( newPos );
|
|
} else {
|
|
idThread::ReturnVector( pos );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetTurnRate
|
|
================
|
|
*/
|
|
void idAI::Event_GetTurnRate() {
|
|
idThread::ReturnFloat( turnRate );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetTurnRate
|
|
================
|
|
*/
|
|
void idAI::Event_SetTurnRate( float rate ) {
|
|
turnRate = rate;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_AnimTurn
|
|
================
|
|
*/
|
|
void idAI::Event_AnimTurn( float angles ) {
|
|
turnVel = 0.0f;
|
|
anim_turn_angles = angles;
|
|
if ( angles ) {
|
|
anim_turn_yaw = current_yaw;
|
|
anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( current_yaw - ideal_yaw ) );
|
|
if ( anim_turn_amount > anim_turn_angles ) {
|
|
anim_turn_amount = anim_turn_angles;
|
|
}
|
|
} else {
|
|
anim_turn_amount = 0.0f;
|
|
animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f );
|
|
animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f );
|
|
animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f );
|
|
animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_AllowHiddenMovement
|
|
================
|
|
*/
|
|
void idAI::Event_AllowHiddenMovement( int enable ) {
|
|
allowHiddenMovement = ( enable != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TriggerParticles
|
|
================
|
|
*/
|
|
void idAI::Event_TriggerParticles( const char *jointName ) {
|
|
TriggerParticles( jointName );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_FindActorsInBounds
|
|
=====================
|
|
*/
|
|
void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) {
|
|
idEntity * ent;
|
|
idEntity * entityList[ MAX_GENTITIES ];
|
|
int numListedEntities;
|
|
int i;
|
|
|
|
numListedEntities = gameLocal.clip.EntitiesTouchingBounds( idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES );
|
|
for( i = 0; i < numListedEntities; i++ ) {
|
|
ent = entityList[ i ];
|
|
if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::Type ) ) {
|
|
idThread::ReturnEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachPosition
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachPosition( const idVec3 &pos ) {
|
|
aasPath_t path;
|
|
int toAreaNum;
|
|
int areaNum;
|
|
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachEntity
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachEntity( idEntity *ent ) {
|
|
aasPath_t path;
|
|
int toAreaNum;
|
|
int areaNum;
|
|
idVec3 pos;
|
|
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if ( !ent->GetFloorPos( 64.0f, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
if ( ent->IsType( idActor::Type ) && static_cast<idActor *>( ent )->OnLadder() ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
} else {
|
|
pos = ent->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
if ( !toAreaNum ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( org );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachEnemy
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachEnemy() {
|
|
aasPath_t path;
|
|
int toAreaNum;
|
|
int areaNum;
|
|
idVec3 pos;
|
|
idActor *enemyEnt;
|
|
|
|
enemyEnt = enemy.GetEntity();
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if ( enemyEnt->OnLadder() ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
enemyEnt->GetAASLocation( aas, pos, toAreaNum );
|
|
} else {
|
|
pos = enemyEnt->GetPhysics()->GetOrigin();
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
}
|
|
|
|
if ( !toAreaNum ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( org );
|
|
if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetReachableEntityPosition
|
|
================
|
|
*/
|
|
void idAI::Event_GetReachableEntityPosition( idEntity *ent ) {
|
|
int toAreaNum;
|
|
idVec3 pos;
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if ( !ent->GetFloorPos( 64.0f, pos ) ) {
|
|
// NOTE: not a good way to return 'false'
|
|
return idThread::ReturnVector( vec3_zero );
|
|
}
|
|
if ( ent->IsType( idActor::Type ) && static_cast<idActor *>( ent )->OnLadder() ) {
|
|
// NOTE: not a good way to return 'false'
|
|
return idThread::ReturnVector( vec3_zero );
|
|
}
|
|
} else {
|
|
pos = ent->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
if ( aas ) {
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
aas->PushPointIntoAreaNum( toAreaNum, pos );
|
|
}
|
|
|
|
idThread::ReturnVector( pos );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_MoveToPositionDirect
|
|
================
|
|
*/
|
|
void idAI::Event_MoveToPositionDirect( const idVec3 &pos ) {
|
|
StopMove( MOVE_STATUS_DONE );
|
|
DirectMoveToPosition( pos );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_AvoidObstacles
|
|
================
|
|
*/
|
|
void idAI::Event_AvoidObstacles( int ignore) {
|
|
ignore_obstacles = (ignore == 1) ? false : true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_TriggerFX
|
|
================
|
|
*/
|
|
void idAI::Event_TriggerFX( const char* joint, const char* fx ) {
|
|
TriggerFX(joint, fx);
|
|
}
|
|
|
|
void idAI::Event_StartEmitter( const char* name, const char* joint, const char* particle ) {
|
|
idEntity *ent = StartEmitter(name, joint, particle);
|
|
idThread::ReturnEntity(ent);
|
|
}
|
|
|
|
void idAI::Event_GetEmitter( const char* name ) {
|
|
idThread::ReturnEntity(GetEmitter(name));
|
|
}
|
|
|
|
void idAI::Event_StopEmitter( const char* name ) {
|
|
StopEmitter(name);
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LaunchHomingMissile
|
|
=====================
|
|
*/
|
|
void idAI::Event_LaunchHomingMissile() {
|
|
idVec3 start;
|
|
trace_t tr;
|
|
idBounds projBounds;
|
|
const idClipModel *projClip;
|
|
idMat3 axis;
|
|
float distance;
|
|
|
|
if ( !projectileDef ) {
|
|
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
idActor *enemy = GetEnemy();
|
|
if ( enemy == NULL ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
idVec3 org = GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 250.0f );
|
|
idVec3 goal = enemy->GetPhysics()->GetOrigin();
|
|
homingMissileGoal = goal;
|
|
|
|
// axis = ( goal - org ).ToMat3();
|
|
// axis.Identity();
|
|
if ( !projectile.GetEntity() ) {
|
|
idHomingProjectile *homing = ( idHomingProjectile * ) CreateProjectile( org, idVec3( 0.0f, 0.0f, 1.0f ) );
|
|
if ( homing != NULL ) {
|
|
homing->SetEnemy( enemy );
|
|
homing->SetSeekPos( homingMissileGoal );
|
|
}
|
|
}
|
|
|
|
// make sure the projectile starts inside the monster bounding box
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
projClip = projectile.GetEntity()->GetPhysics()->GetClipModel();
|
|
projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() );
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
|
|
start = org + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this );
|
|
|
|
// launch the projectile
|
|
idThread::ReturnEntity( projectile.GetEntity() );
|
|
idVec3 dir = homingMissileGoal - org;
|
|
idAngles ang = dir.ToAngles();
|
|
ang.pitch = -45.0f;
|
|
projectile.GetEntity()->Launch( org, ang.ToForward(), vec3_origin );
|
|
projectile = NULL;
|
|
|
|
TriggerWeaponEffects( tr.endpos );
|
|
|
|
lastAttackTime = gameLocal.time;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_SetHomingMissileGoal
|
|
=====================
|
|
*/
|
|
void idAI::Event_SetHomingMissileGoal() {
|
|
idActor *enemy = GetEnemy();
|
|
if ( enemy == NULL ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
homingMissileGoal = enemy->GetPhysics()->GetOrigin();
|
|
} |