AimAssist.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Game_local.h"
  23. /*
  24. ================================================================================================
  25. Contains the AimAssist implementation.
  26. ================================================================================================
  27. */
  28. idCVar aa_targetAimAssistEnable ( "aa_targetAimAssistEnable", "0", CVAR_BOOL | CVAR_ARCHIVE, "Enables/Disables the entire Aim Assist system" );
  29. idCVar aa_targetAdhesionEnable ( "aa_targetAdhesionEnable", "1", CVAR_BOOL, "Enables Target Adhesion" );
  30. idCVar aa_targetFrictionEnable ( "aa_targetFrictionEnable", "1", CVAR_BOOL, "Enables Target Friction" );
  31. // Selection
  32. idCVar aa_targetMaxDistance ( "aa_targetMaxDistance", "3000", CVAR_FLOAT, "The Maximum Distance away for a target to be considered for adhesion, friction and target lock-to" );
  33. idCVar aa_targetSelectionRadius ( "aa_targetSelectionRadius", "128.0", CVAR_FLOAT, "Radius used to select targets for auto aiming" );
  34. // Adhesion
  35. idCVar aa_targetAdhesionRadius ( "aa_targetAdhesionRadius", "96.0", CVAR_FLOAT, "Radius used to apply adhesion amount" );
  36. idCVar aa_targetAdhesionYawSpeedMax ( "aa_targetAdhesionYawSpeedMax", "0.6", CVAR_FLOAT, "Max Yaw Adhesion Speed" );
  37. idCVar aa_targetAdhesionPitchSpeedMax ( "aa_targetAdhesionPitchSpeedMax", "0.6", CVAR_FLOAT, "Max Pitch Adhesion Speed" );
  38. idCVar aa_targetAdhesionContributionPctMax ( "aa_targetAdhesionContributionPctMax", "0.25", CVAR_FLOAT, "Max Adhesion Contribution Percentage - Range 0.0 - 1.0" );
  39. idCVar aa_targetAdhesionPlayerSpeedThreshold ( "aa_targetAdhesionPlayerSpeedThreshold", "10.0", CVAR_FLOAT, "Speed Threshold that determines how fast the player needs to move before adhesion is allowed to kick in" );
  40. // Friction
  41. idCVar aa_targetFrictionMaxDistance ( "aa_targetFrictionMaxDistance", "1024.0", CVAR_FLOAT, "Minimum Distance Friction takes effect" );
  42. idCVar aa_targetFrictionOptimalDistance ( "aa_targetFrictionOptimalDistance", "768.0", CVAR_FLOAT, "Optimal Distance for Friction to take an effect" );
  43. idCVar aa_targetFrictionRadius ( "aa_targetFrictionRadius", "96.0", CVAR_FLOAT, "Friction Collision Sphere Radius" );
  44. idCVar aa_targetFrictionOptimalRadius ( "aa_targetFrictionOptimalRadius", "192.0", CVAR_FLOAT, "Friction Collision Sphere Radius when at Optimal Distance" );
  45. idCVar aa_targetFrictionMultiplierMin ( "aa_targetFrictionMultiplierMin", "1.0", CVAR_FLOAT, "Minimum Friction Scalar" );
  46. idCVar aa_targetFrictionMultiplierMax ( "aa_targetFrictionMultiplierMax", "0.4", CVAR_FLOAT, "Maximum Friction Scalar" );
  47. /*
  48. ========================
  49. idAimAssist::Init
  50. ========================
  51. */
  52. void idAimAssist::Init( idPlayer *player_ ) {
  53. player = player_;
  54. angleCorrection = ang_zero;
  55. frictionScalar = 1.0f;
  56. lastTargetPos = vec3_zero;
  57. }
  58. /*
  59. ========================
  60. idAimAssist::Update
  61. ========================
  62. */
  63. void idAimAssist::Update() {
  64. angleCorrection = ang_zero;
  65. UpdateNewAimAssist();
  66. }
  67. /*
  68. ========================
  69. idAimAssist::UpdateNewAimAssist
  70. ========================
  71. */
  72. void idAimAssist::UpdateNewAimAssist() {
  73. angleCorrection = ang_zero;
  74. frictionScalar = 1.0f;
  75. idEntity* lastTarget = targetEntity;
  76. targetEntity = NULL;
  77. // is aim assisting allowed? If not then just bail out
  78. if ( !aa_targetAimAssistEnable.GetBool() ) {
  79. return;
  80. }
  81. bool forceLastTarget = false;
  82. idVec3 targetPos;
  83. idEntity * entity = NULL;
  84. if ( forceLastTarget ) {
  85. entity = lastTarget;
  86. targetPos = lastTargetPos;
  87. } else {
  88. entity = FindAimAssistTarget( targetPos );
  89. }
  90. if ( entity != NULL ) {
  91. UpdateFriction( entity, targetPos );
  92. // by default we don't allow adhesion when we are standing still
  93. const float playerMovementSpeedThreshold = Square( aa_targetAdhesionPlayerSpeedThreshold.GetFloat() );
  94. float playerSpeed = player->GetPhysics()->GetLinearVelocity().LengthSqr();
  95. // only allow adhesion on actors (ai) or players. Disallow adhesion on any static world entities such as explosive barrels
  96. if ( playerSpeed > playerMovementSpeedThreshold ) {
  97. UpdateAdhesion( entity, targetPos );
  98. }
  99. targetEntity = entity;
  100. }
  101. lastTargetPos = targetPos;
  102. }
  103. /*
  104. ========================
  105. idAimAssist::FindAimAssistTarget
  106. ========================
  107. */
  108. idEntity* idAimAssist::FindAimAssistTarget( idVec3& targetPos ) {
  109. if ( player == NULL ) {
  110. return NULL;
  111. }
  112. //TO DO: Make this faster
  113. //TO DO: Defer Traces
  114. idEntity * optimalTarget = NULL;
  115. float currentBestScore = -idMath::INFINITY;
  116. targetPos = vec3_zero;
  117. idVec3 cameraPos;
  118. idMat3 cameraAxis;
  119. player->GetViewPos( cameraPos, cameraAxis );
  120. float maxDistanceSquared = Square( aa_targetMaxDistance.GetFloat() );
  121. idVec3 dirToTarget;
  122. float distanceToTargetSquared;
  123. idVec3 primaryTargetPos;
  124. idVec3 secondaryTargetPos;
  125. for ( idEntity * entity = gameLocal.aimAssistEntities.Next(); entity != NULL; entity = entity->aimAssistNode.Next() ) {
  126. if ( !entity->IsActive() ) {
  127. continue;
  128. }
  129. if ( entity->IsHidden() ) {
  130. continue;
  131. }
  132. if ( entity->IsType( idActor::Type ) ) {
  133. idActor * actor = static_cast<idActor *>( entity );
  134. if ( actor->team == player->team ) {
  135. // In DM, LMS, and Tourney, all players are on the same team
  136. if ( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_SP ) {
  137. continue;
  138. }
  139. }
  140. }
  141. if ( entity->IsType( idAI::Type ) ) {
  142. idAI * aiEntity = static_cast<idAI *>( entity );
  143. if ( aiEntity->ReactionTo( player ) == ATTACK_IGNORE ) {
  144. continue;
  145. }
  146. }
  147. // check whether we have a valid target position for this entity - skip it if we don't
  148. if ( !ComputeTargetPos( entity, primaryTargetPos, secondaryTargetPos ) ) {
  149. continue;
  150. }
  151. // is it close enough to us
  152. dirToTarget = primaryTargetPos-cameraPos;
  153. distanceToTargetSquared = dirToTarget.LengthSqr();
  154. if ( distanceToTargetSquared > maxDistanceSquared ) {
  155. continue;
  156. }
  157. // Compute a score in the range of 0..1 for how much are looking towards the target.
  158. idVec3 forward = cameraAxis[ 0 ];
  159. forward.Normalize();
  160. dirToTarget.Normalize();
  161. float ViewDirDotTargetDir = idMath::ClampFloat( -1.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
  162. // throw out anything thats outside of weapon's global FOV.
  163. if ( ViewDirDotTargetDir < 0.0f ) {
  164. continue;
  165. }
  166. // to be consistent we always use the primaryTargetPos to compute the score for this entity
  167. float computedScore = ComputeEntityAimAssistScore( primaryTargetPos, cameraPos, cameraAxis );
  168. // check if the current score beats our current best score and we have line of sight to it.
  169. if ( computedScore > currentBestScore ) {
  170. // determine if the current target is in our line of sight
  171. trace_t tr;
  172. gameLocal.clip.TracePoint( tr, cameraPos, primaryTargetPos, MASK_MONSTERSOLID, player );
  173. // did our trace fail?
  174. if ( ( ( tr.fraction < 1.0f ) && ( tr.c.entityNum != entity->entityNumber ) ) || ( tr.fraction >= 1.0f ) ) {
  175. // if the collision test failed for the primary position -- check the secondary position
  176. trace_t tr2;
  177. gameLocal.clip.TracePoint( tr2, cameraPos, secondaryTargetPos, MASK_MONSTERSOLID, player );
  178. if ( ( ( tr2.fraction < 1.0f ) && ( tr2.c.entityNum != entity->entityNumber ) ) || ( tr2.fraction >= 1.0f ) ) {
  179. // if the secondary position is also not visible then give up
  180. continue;
  181. }
  182. // we can see the secondary target position so we should consider this entity but use
  183. // the secondary position as the target position
  184. targetPos = secondaryTargetPos;
  185. } else {
  186. targetPos = primaryTargetPos;
  187. }
  188. // if we got here then this is our new best score
  189. optimalTarget = entity;
  190. currentBestScore = computedScore;
  191. }
  192. }
  193. return optimalTarget;
  194. }
  195. /*
  196. ========================
  197. idAimAssist::ComputeEntityAimAssistScore
  198. ========================
  199. */
  200. float idAimAssist::ComputeEntityAimAssistScore( const idVec3& targetPos, const idVec3& cameraPos, const idMat3& cameraAxis ) {
  201. float score = 0.0f;
  202. idVec3 dirToTarget = targetPos - cameraPos;
  203. float distanceToTarget = dirToTarget.Length();
  204. // Compute a score in the range of 0..1 for how much are looking towards the target.
  205. idVec3 forward = cameraAxis[0];
  206. forward.Normalize();
  207. dirToTarget.Normalize();
  208. float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
  209. // the more we look at the target the higher our score
  210. score = ViewDirDotTargetDir;
  211. // weigh the score from the view angle higher than the distance score
  212. static float aimWeight = 0.8f;
  213. score *= aimWeight;
  214. // Add a score of 0..1 for how close the target is to the player
  215. if ( distanceToTarget < aa_targetMaxDistance.GetFloat() ) {
  216. float distanceScore = 1.0f - ( distanceToTarget / aa_targetMaxDistance.GetFloat() );
  217. float distanceWeight = 1.0f - aimWeight;
  218. score += ( distanceScore * distanceWeight );
  219. }
  220. return score * 1000.0f;
  221. }
  222. /*
  223. ========================
  224. idAimAssist::UpdateAdhesion
  225. ========================
  226. */
  227. void idAimAssist::UpdateAdhesion( idEntity* pTarget, const idVec3& targetPos ) {
  228. if ( !aa_targetAdhesionEnable.GetBool() ) {
  229. return;
  230. }
  231. if ( !pTarget ) {
  232. return;
  233. }
  234. float contributionPctMax = aa_targetAdhesionContributionPctMax.GetFloat();
  235. idVec3 cameraPos;
  236. idMat3 cameraAxis;
  237. player->GetViewPos(cameraPos, cameraAxis);
  238. idAngles cameraViewAngles = cameraAxis.ToAngles();
  239. cameraViewAngles.Normalize180();
  240. idVec3 cameraViewPos = cameraPos;
  241. idVec3 dirToTarget = targetPos - cameraViewPos;
  242. float distanceToTarget = dirToTarget.Length();
  243. idAngles aimAngles = dirToTarget.ToAngles();
  244. aimAngles.Normalize180();
  245. // find the delta
  246. aimAngles -= cameraViewAngles;
  247. // clamp velocities to some max values
  248. aimAngles.yaw = idMath::ClampFloat( -aa_targetAdhesionYawSpeedMax.GetFloat(), aa_targetAdhesionYawSpeedMax.GetFloat(), aimAngles.yaw );
  249. aimAngles.pitch = idMath::ClampFloat( -aa_targetAdhesionPitchSpeedMax.GetFloat(), aa_targetAdhesionPitchSpeedMax.GetFloat(), aimAngles.pitch );
  250. idVec3 forward = cameraAxis[0];
  251. forward.Normalize();
  252. dirToTarget.Normalize();
  253. float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
  254. float aimLength = ViewDirDotTargetDir * distanceToTarget;
  255. idVec3 aimPoint = cameraPos + ( forward * aimLength );
  256. float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) );
  257. float contribution = idMath::ClampFloat( 0.0f, contributionPctMax, 1.0f - ( delta / aa_targetAdhesionRadius.GetFloat() ) );
  258. angleCorrection.yaw = aimAngles.yaw * contribution;
  259. angleCorrection.pitch = aimAngles.pitch * contribution;
  260. }
  261. /*
  262. ========================
  263. idAimAssist::ComputeFrictionRadius
  264. ========================
  265. */
  266. float idAimAssist::ComputeFrictionRadius( float distanceToTarget ) {
  267. if ( ( distanceToTarget <= idMath::FLT_SMALLEST_NON_DENORMAL ) || distanceToTarget > aa_targetFrictionMaxDistance.GetFloat() ) {
  268. return aa_targetFrictionRadius.GetFloat();
  269. }
  270. float distanceContributionScalar = ( aa_targetFrictionOptimalDistance.GetFloat() > 0.0f ) ? ( distanceToTarget / aa_targetFrictionOptimalDistance.GetFloat() ) : 0.0f;
  271. if ( distanceToTarget > aa_targetFrictionOptimalDistance.GetFloat() ) {
  272. const float range = idMath::ClampFloat( 0.0f, aa_targetFrictionMaxDistance.GetFloat(), aa_targetFrictionMaxDistance.GetFloat() - aa_targetFrictionOptimalDistance.GetFloat() );
  273. if ( range > idMath::FLT_SMALLEST_NON_DENORMAL ) {
  274. distanceContributionScalar = 1.0f - ( ( distanceToTarget - aa_targetFrictionOptimalDistance.GetFloat() ) / range );
  275. }
  276. }
  277. return Lerp( aa_targetFrictionRadius.GetFloat(), aa_targetFrictionOptimalRadius.GetFloat(), distanceContributionScalar );
  278. }
  279. /*
  280. ========================
  281. idAimAssist::UpdateFriction
  282. ========================
  283. */
  284. void idAimAssist::UpdateFriction( idEntity* pTarget, const idVec3& targetPos ) {
  285. if ( !aa_targetFrictionEnable.GetBool() ) {
  286. return;
  287. }
  288. if ( pTarget == NULL ) {
  289. return;
  290. }
  291. idVec3 cameraPos;
  292. idMat3 cameraAxis;
  293. player->GetViewPos(cameraPos, cameraAxis);
  294. idVec3 dirToTarget = targetPos - cameraPos;
  295. float distanceToTarget = dirToTarget.Length();
  296. idVec3 forward = cameraAxis[0];
  297. forward.Normalize();
  298. dirToTarget.Normalize();
  299. float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
  300. float aimLength = ViewDirDotTargetDir * distanceToTarget;
  301. idVec3 aimPoint = cameraPos + ( forward * aimLength );
  302. float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) );
  303. const float radius = ComputeFrictionRadius( distanceToTarget );
  304. if ( delta < radius ) {
  305. float alpha = 1.0f - ( delta / radius );
  306. frictionScalar = Lerp( aa_targetFrictionMultiplierMin.GetFloat(), aa_targetFrictionMultiplierMax.GetFloat(), alpha );
  307. }
  308. }
  309. /*
  310. ========================
  311. idAimAssist::ComputeTargetPos
  312. ========================
  313. */
  314. bool idAimAssist::ComputeTargetPos( idEntity* entity, idVec3& primaryTargetPos, idVec3& secondaryTargetPos ) {
  315. primaryTargetPos = vec3_zero;
  316. secondaryTargetPos = vec3_zero;
  317. if ( entity == NULL ) {
  318. return false;
  319. }
  320. // The target point on actors can now be either the head or the torso
  321. idActor * actor = NULL;
  322. if ( entity->IsType( idActor::Type ) ) {
  323. actor = ( idActor *) entity;
  324. }
  325. if ( actor != NULL ) {
  326. // Actor AimPoint
  327. idVec3 torsoPos;
  328. idVec3 headPos = actor->GetEyePosition();
  329. if ( actor->GetHeadEntity() != NULL ) {
  330. torsoPos = actor->GetHeadEntity()->GetPhysics()->GetOrigin();
  331. } else {
  332. const float offsetScale = 0.9f;
  333. torsoPos = actor->GetPhysics()->GetOrigin() + ( actor->EyeOffset() * offsetScale );
  334. }
  335. primaryTargetPos = torsoPos;
  336. secondaryTargetPos = headPos;
  337. return true;
  338. } else if ( entity->GetPhysics()->GetClipModel() != NULL ) {
  339. const idBounds& box = entity->GetPhysics()->GetClipModel()->GetAbsBounds();
  340. primaryTargetPos = box.GetCenter();
  341. secondaryTargetPos = box.GetCenter();
  342. return true;
  343. }
  344. return false;
  345. }