mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 13:11:47 +00:00
485 lines
16 KiB
C++
485 lines
16 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "Game_local.h"
|
|
|
|
/*
|
|
================================================================================================
|
|
Contains the AimAssist implementation.
|
|
================================================================================================
|
|
*/
|
|
|
|
idCVar aa_targetAimAssistEnable( "aa_targetAimAssistEnable", "0", CVAR_BOOL | CVAR_ARCHIVE, "Enables/Disables the entire Aim Assist system" );
|
|
|
|
idCVar aa_targetAdhesionEnable( "aa_targetAdhesionEnable", "1", CVAR_BOOL, "Enables Target Adhesion" );
|
|
idCVar aa_targetFrictionEnable( "aa_targetFrictionEnable", "1", CVAR_BOOL, "Enables Target Friction" );
|
|
|
|
// Selection
|
|
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" );
|
|
idCVar aa_targetSelectionRadius( "aa_targetSelectionRadius", "128.0", CVAR_FLOAT, "Radius used to select targets for auto aiming" );
|
|
|
|
// Adhesion
|
|
idCVar aa_targetAdhesionRadius( "aa_targetAdhesionRadius", "96.0", CVAR_FLOAT, "Radius used to apply adhesion amount" );
|
|
idCVar aa_targetAdhesionYawSpeedMax( "aa_targetAdhesionYawSpeedMax", "0.6", CVAR_FLOAT, "Max Yaw Adhesion Speed" );
|
|
idCVar aa_targetAdhesionPitchSpeedMax( "aa_targetAdhesionPitchSpeedMax", "0.6", CVAR_FLOAT, "Max Pitch Adhesion Speed" );
|
|
idCVar aa_targetAdhesionContributionPctMax( "aa_targetAdhesionContributionPctMax", "0.25", CVAR_FLOAT, "Max Adhesion Contribution Percentage - Range 0.0 - 1.0" );
|
|
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" );
|
|
|
|
// Friction
|
|
idCVar aa_targetFrictionMaxDistance( "aa_targetFrictionMaxDistance", "1024.0", CVAR_FLOAT, "Minimum Distance Friction takes effect" );
|
|
idCVar aa_targetFrictionOptimalDistance( "aa_targetFrictionOptimalDistance", "768.0", CVAR_FLOAT, "Optimal Distance for Friction to take an effect" );
|
|
idCVar aa_targetFrictionRadius( "aa_targetFrictionRadius", "96.0", CVAR_FLOAT, "Friction Collision Sphere Radius" );
|
|
idCVar aa_targetFrictionOptimalRadius( "aa_targetFrictionOptimalRadius", "192.0", CVAR_FLOAT, "Friction Collision Sphere Radius when at Optimal Distance" );
|
|
idCVar aa_targetFrictionMultiplierMin( "aa_targetFrictionMultiplierMin", "1.0", CVAR_FLOAT, "Minimum Friction Scalar" );
|
|
idCVar aa_targetFrictionMultiplierMax( "aa_targetFrictionMultiplierMax", "0.4", CVAR_FLOAT, "Maximum Friction Scalar" );
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::Init
|
|
========================
|
|
*/
|
|
void idAimAssist::Init( idPlayer* player_ )
|
|
{
|
|
player = player_;
|
|
angleCorrection = ang_zero;
|
|
frictionScalar = 1.0f;
|
|
lastTargetPos = vec3_zero;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::Update
|
|
========================
|
|
*/
|
|
void idAimAssist::Update()
|
|
{
|
|
angleCorrection = ang_zero;
|
|
|
|
UpdateNewAimAssist();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::UpdateNewAimAssist
|
|
========================
|
|
*/
|
|
void idAimAssist::UpdateNewAimAssist()
|
|
{
|
|
|
|
angleCorrection = ang_zero;
|
|
frictionScalar = 1.0f;
|
|
idEntity* lastTarget = targetEntity;
|
|
targetEntity = NULL;
|
|
|
|
// is aim assisting allowed? If not then just bail out
|
|
if( !aa_targetAimAssistEnable.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool forceLastTarget = false;
|
|
idVec3 targetPos;
|
|
|
|
idEntity* entity = NULL;
|
|
if( forceLastTarget )
|
|
{
|
|
entity = lastTarget;
|
|
targetPos = lastTargetPos;
|
|
}
|
|
else
|
|
{
|
|
entity = FindAimAssistTarget( targetPos );
|
|
}
|
|
|
|
if( entity != NULL )
|
|
{
|
|
|
|
UpdateFriction( entity, targetPos );
|
|
|
|
// by default we don't allow adhesion when we are standing still
|
|
const float playerMovementSpeedThreshold = Square( aa_targetAdhesionPlayerSpeedThreshold.GetFloat() );
|
|
float playerSpeed = player->GetPhysics()->GetLinearVelocity().LengthSqr();
|
|
|
|
// only allow adhesion on actors (ai) or players. Disallow adhesion on any static world entities such as explosive barrels
|
|
if( playerSpeed > playerMovementSpeedThreshold )
|
|
{
|
|
UpdateAdhesion( entity, targetPos );
|
|
}
|
|
|
|
targetEntity = entity;
|
|
}
|
|
|
|
lastTargetPos = targetPos;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::FindAimAssistTarget
|
|
========================
|
|
*/
|
|
idEntity* idAimAssist::FindAimAssistTarget( idVec3& targetPos )
|
|
{
|
|
if( player == NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//TO DO: Make this faster
|
|
//TO DO: Defer Traces
|
|
idEntity* optimalTarget = NULL;
|
|
float currentBestScore = -idMath::INFINITY;
|
|
targetPos = vec3_zero;
|
|
|
|
idVec3 cameraPos;
|
|
idMat3 cameraAxis;
|
|
player->GetViewPos( cameraPos, cameraAxis );
|
|
|
|
float maxDistanceSquared = Square( aa_targetMaxDistance.GetFloat() );
|
|
|
|
idVec3 dirToTarget;
|
|
float distanceToTargetSquared;
|
|
idVec3 primaryTargetPos;
|
|
idVec3 secondaryTargetPos;
|
|
|
|
for( idEntity* entity = gameLocal.aimAssistEntities.Next(); entity != NULL; entity = entity->aimAssistNode.Next() )
|
|
{
|
|
if( !entity->IsActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( entity->IsHidden() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( entity->IsType( idActor::Type ) )
|
|
{
|
|
idActor* actor = static_cast<idActor*>( entity );
|
|
if( actor->team == player->team )
|
|
{
|
|
// In DM, LMS, and Tourney, all players are on the same team
|
|
if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_SP )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( entity->IsType( idAI::Type ) )
|
|
{
|
|
idAI* aiEntity = static_cast<idAI*>( entity );
|
|
if( aiEntity->ReactionTo( player ) == ATTACK_IGNORE )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// check whether we have a valid target position for this entity - skip it if we don't
|
|
if( !ComputeTargetPos( entity, primaryTargetPos, secondaryTargetPos ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// is it close enough to us
|
|
dirToTarget = primaryTargetPos - cameraPos;
|
|
distanceToTargetSquared = dirToTarget.LengthSqr();
|
|
if( distanceToTargetSquared > maxDistanceSquared )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Compute a score in the range of 0..1 for how much are looking towards the target.
|
|
idVec3 forward = cameraAxis[ 0 ];
|
|
forward.Normalize();
|
|
dirToTarget.Normalize();
|
|
float ViewDirDotTargetDir = idMath::ClampFloat( -1.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
|
|
|
|
// throw out anything thats outside of weapon's global FOV.
|
|
if( ViewDirDotTargetDir < 0.0f )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// to be consistent we always use the primaryTargetPos to compute the score for this entity
|
|
float computedScore = ComputeEntityAimAssistScore( primaryTargetPos, cameraPos, cameraAxis );
|
|
|
|
// check if the current score beats our current best score and we have line of sight to it.
|
|
if( computedScore > currentBestScore )
|
|
{
|
|
|
|
// determine if the current target is in our line of sight
|
|
trace_t tr;
|
|
gameLocal.clip.TracePoint( tr, cameraPos, primaryTargetPos, MASK_MONSTERSOLID, player );
|
|
|
|
// did our trace fail?
|
|
if( ( ( tr.fraction < 1.0f ) && ( tr.c.entityNum != entity->entityNumber ) ) || ( tr.fraction >= 1.0f ) )
|
|
{
|
|
|
|
// if the collision test failed for the primary position -- check the secondary position
|
|
trace_t tr2;
|
|
gameLocal.clip.TracePoint( tr2, cameraPos, secondaryTargetPos, MASK_MONSTERSOLID, player );
|
|
|
|
if( ( ( tr2.fraction < 1.0f ) && ( tr2.c.entityNum != entity->entityNumber ) ) || ( tr2.fraction >= 1.0f ) )
|
|
{
|
|
// if the secondary position is also not visible then give up
|
|
continue;
|
|
}
|
|
|
|
// we can see the secondary target position so we should consider this entity but use
|
|
// the secondary position as the target position
|
|
targetPos = secondaryTargetPos;
|
|
}
|
|
else
|
|
{
|
|
targetPos = primaryTargetPos;
|
|
}
|
|
|
|
// if we got here then this is our new best score
|
|
optimalTarget = entity;
|
|
currentBestScore = computedScore;
|
|
}
|
|
}
|
|
|
|
return optimalTarget;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::ComputeEntityAimAssistScore
|
|
========================
|
|
*/
|
|
float idAimAssist::ComputeEntityAimAssistScore( const idVec3& targetPos, const idVec3& cameraPos, const idMat3& cameraAxis )
|
|
{
|
|
|
|
float score = 0.0f;
|
|
|
|
idVec3 dirToTarget = targetPos - cameraPos;
|
|
float distanceToTarget = dirToTarget.Length();
|
|
|
|
// Compute a score in the range of 0..1 for how much are looking towards the target.
|
|
idVec3 forward = cameraAxis[0];
|
|
forward.Normalize();
|
|
dirToTarget.Normalize();
|
|
float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
|
|
|
|
// the more we look at the target the higher our score
|
|
score = ViewDirDotTargetDir;
|
|
|
|
// weigh the score from the view angle higher than the distance score
|
|
static float aimWeight = 0.8f;
|
|
score *= aimWeight;
|
|
// Add a score of 0..1 for how close the target is to the player
|
|
if( distanceToTarget < aa_targetMaxDistance.GetFloat() )
|
|
{
|
|
float distanceScore = 1.0f - ( distanceToTarget / aa_targetMaxDistance.GetFloat() );
|
|
float distanceWeight = 1.0f - aimWeight;
|
|
score += ( distanceScore * distanceWeight );
|
|
}
|
|
|
|
return score * 1000.0f;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::UpdateAdhesion
|
|
========================
|
|
*/
|
|
void idAimAssist::UpdateAdhesion( idEntity* pTarget, const idVec3& targetPos )
|
|
{
|
|
|
|
if( !aa_targetAdhesionEnable.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !pTarget )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float contributionPctMax = aa_targetAdhesionContributionPctMax.GetFloat();
|
|
|
|
idVec3 cameraPos;
|
|
idMat3 cameraAxis;
|
|
player->GetViewPos( cameraPos, cameraAxis );
|
|
|
|
idAngles cameraViewAngles = cameraAxis.ToAngles();
|
|
cameraViewAngles.Normalize180();
|
|
|
|
idVec3 cameraViewPos = cameraPos;
|
|
idVec3 dirToTarget = targetPos - cameraViewPos;
|
|
float distanceToTarget = dirToTarget.Length();
|
|
|
|
idAngles aimAngles = dirToTarget.ToAngles();
|
|
aimAngles.Normalize180();
|
|
|
|
// find the delta
|
|
aimAngles -= cameraViewAngles;
|
|
|
|
// clamp velocities to some max values
|
|
aimAngles.yaw = idMath::ClampFloat( -aa_targetAdhesionYawSpeedMax.GetFloat(), aa_targetAdhesionYawSpeedMax.GetFloat(), aimAngles.yaw );
|
|
aimAngles.pitch = idMath::ClampFloat( -aa_targetAdhesionPitchSpeedMax.GetFloat(), aa_targetAdhesionPitchSpeedMax.GetFloat(), aimAngles.pitch );
|
|
|
|
idVec3 forward = cameraAxis[0];
|
|
forward.Normalize();
|
|
dirToTarget.Normalize();
|
|
float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
|
|
float aimLength = ViewDirDotTargetDir * distanceToTarget;
|
|
idVec3 aimPoint = cameraPos + ( forward * aimLength );
|
|
float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) );
|
|
|
|
float contribution = idMath::ClampFloat( 0.0f, contributionPctMax, 1.0f - ( delta / aa_targetAdhesionRadius.GetFloat() ) );
|
|
angleCorrection.yaw = aimAngles.yaw * contribution;
|
|
angleCorrection.pitch = aimAngles.pitch * contribution;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::ComputeFrictionRadius
|
|
========================
|
|
*/
|
|
float idAimAssist::ComputeFrictionRadius( float distanceToTarget )
|
|
{
|
|
|
|
if( ( distanceToTarget <= idMath::FLT_SMALLEST_NON_DENORMAL ) || distanceToTarget > aa_targetFrictionMaxDistance.GetFloat() )
|
|
{
|
|
return aa_targetFrictionRadius.GetFloat();
|
|
}
|
|
|
|
float distanceContributionScalar = ( aa_targetFrictionOptimalDistance.GetFloat() > 0.0f ) ? ( distanceToTarget / aa_targetFrictionOptimalDistance.GetFloat() ) : 0.0f;
|
|
|
|
if( distanceToTarget > aa_targetFrictionOptimalDistance.GetFloat() )
|
|
{
|
|
const float range = idMath::ClampFloat( 0.0f, aa_targetFrictionMaxDistance.GetFloat(), aa_targetFrictionMaxDistance.GetFloat() - aa_targetFrictionOptimalDistance.GetFloat() );
|
|
if( range > idMath::FLT_SMALLEST_NON_DENORMAL )
|
|
{
|
|
distanceContributionScalar = 1.0f - ( ( distanceToTarget - aa_targetFrictionOptimalDistance.GetFloat() ) / range );
|
|
}
|
|
}
|
|
|
|
return Lerp( aa_targetFrictionRadius.GetFloat(), aa_targetFrictionOptimalRadius.GetFloat(), distanceContributionScalar );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::UpdateFriction
|
|
========================
|
|
*/
|
|
void idAimAssist::UpdateFriction( idEntity* pTarget, const idVec3& targetPos )
|
|
{
|
|
|
|
if( !aa_targetFrictionEnable.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( pTarget == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
idVec3 cameraPos;
|
|
idMat3 cameraAxis;
|
|
player->GetViewPos( cameraPos, cameraAxis );
|
|
idVec3 dirToTarget = targetPos - cameraPos;
|
|
float distanceToTarget = dirToTarget.Length();
|
|
idVec3 forward = cameraAxis[0];
|
|
forward.Normalize();
|
|
dirToTarget.Normalize();
|
|
float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error
|
|
float aimLength = ViewDirDotTargetDir * distanceToTarget;
|
|
idVec3 aimPoint = cameraPos + ( forward * aimLength );
|
|
float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) );
|
|
|
|
const float radius = ComputeFrictionRadius( distanceToTarget );
|
|
if( delta < radius )
|
|
{
|
|
float alpha = 1.0f - ( delta / radius );
|
|
frictionScalar = Lerp( aa_targetFrictionMultiplierMin.GetFloat(), aa_targetFrictionMultiplierMax.GetFloat(), alpha );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idAimAssist::ComputeTargetPos
|
|
========================
|
|
*/
|
|
bool idAimAssist::ComputeTargetPos( idEntity* entity, idVec3& primaryTargetPos, idVec3& secondaryTargetPos )
|
|
{
|
|
|
|
primaryTargetPos = vec3_zero;
|
|
secondaryTargetPos = vec3_zero;
|
|
|
|
if( entity == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The target point on actors can now be either the head or the torso
|
|
idActor* actor = NULL;
|
|
if( entity->IsType( idActor::Type ) )
|
|
{
|
|
actor = ( idActor* ) entity;
|
|
}
|
|
if( actor != NULL )
|
|
{
|
|
// Actor AimPoint
|
|
|
|
idVec3 torsoPos;
|
|
idVec3 headPos = actor->GetEyePosition();
|
|
|
|
if( actor->GetHeadEntity() != NULL )
|
|
{
|
|
torsoPos = actor->GetHeadEntity()->GetPhysics()->GetOrigin();
|
|
}
|
|
else
|
|
{
|
|
const float offsetScale = 0.9f;
|
|
torsoPos = actor->GetPhysics()->GetOrigin() + ( actor->EyeOffset() * offsetScale );
|
|
}
|
|
|
|
primaryTargetPos = torsoPos;
|
|
secondaryTargetPos = headPos;
|
|
return true;
|
|
|
|
}
|
|
else if( entity->GetPhysics()->GetClipModel() != NULL )
|
|
{
|
|
|
|
const idBounds& box = entity->GetPhysics()->GetClipModel()->GetAbsBounds();
|
|
primaryTargetPos = box.GetCenter();
|
|
secondaryTargetPos = box.GetCenter();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|