//== Original Copyright © 1996-2005, Valve Corporation, All rights reserved. ==// // // Purpose: This bit of code is used on both the client and the server to determine // where a targeted (shot) projectile will go in a HL2 game. On the client side, this // code is called to determine where bullet impact decals/effects will be drawn // but it is also called on the server to make actual hit determination for // targets that can be influenced somehow if hit. To synchronize between the // client and server so that both have the bullet hit in the same location, both // share a common random number seed. // // The default Source system for handling weapon accuracy is to give them a spread // value in degrees. A weapon with a 5 degree spread for example will randomly // shoot a bullet somewhere inside of a cone centered on the aiming vector with a // 5 degree spread. Sometimes this cone is referred to as the “Cone of Fire” or COF // A weapon with a zero degree spread would be a “perfect shot” or what one might // call an FLB or “Frickin’ Laser Beam” // // The default Valve code for this gives the bullet a totally random distribution over // the COF, giving an unbiased shot pattern. That is, there is an even chance a bullet // will hit the center or edges of the target circle covered by the cone. This // is not a very realistic system, however it is an improvement over the old // HL1 system, where the bullet spread was in a square rather than circular // target. // // Note that the default Valve method in CShotManipulator::ApplySpreadVALVE // does include a shot bias calculation method, but it does not seem to be // used on many games. Looking at the source code it seems that games using // the default methods will just end up with unbiased shots by default. // // This is however not necessarily an accurate representation of a real gun’s // shot pattern, as the bullet distribution of a person shooting a real gun at // a real target will be biased, with more bullets hitting in the middle of the // target than at the edges. // // This code offers an alternative bullet spread calculation method that increases // the odds of a bullet hitting in the middle of the target circle. See the // comments in CShotManipulator::ApplySpreadBIAS for more information, including // a link to the mathematical reasoning behind the system. // // For more information, contact Tim Holt at tim.m.holt@gmail.com // // For examples of random (not biased) shot patterns of different weapons // from the Valve game Day of Defeat, see http://www.orst.edu/~holtt/range // The examples there show how the target circle grows with distance and how // the bullet distribution is random. // // This method is free and available for anyone to use in their HL/HL2 mod. //=============================================================================// #ifndef SHOT_MANIPULATOR_H #define SHOT_MANIPULATOR_H #ifdef _WIN32 #pragma once #endif #include "vector.h" extern ConVar ai_shot_bias_min; extern ConVar ai_shot_bias_max; //--------------------------------------------------------- // Caches off a shot direction and allows you to perform // various operations on it without having to recalculate // vecRight and vecUp each time. //--------------------------------------------------------- class CShotManipulator { public: CShotManipulator( const Vector &vecForward ) { SetShootDir( vecForward ); }; void SetShootDir( const Vector &vecForward ) { m_vecShotDirection = vecForward; VectorVectors( m_vecShotDirection, m_vecRight, m_vecUp ); } const Vector &ApplySpreadVALVE( const Vector &vecSpread, float bias = 1.0 ); const Vector &ApplySpreadBIAS( const Vector &vecSpread, float bias = 1.0 ); const Vector &ApplySpread( const Vector &vecSpread, float bias = 1.0 ); const Vector &GetShotDirection() { return m_vecShotDirection; } const Vector &GetResult() { return m_vecResult; } const Vector &GetRightVector() { return m_vecRight; } const Vector &GetUpVector() { return m_vecUp;} private: Vector m_vecShotDirection; Vector m_vecRight; Vector m_vecUp; Vector m_vecResult; }; // ------------------------------------------------------------------------------------- // CVB - holtt - Replace the normal ApplySpread with a call to either Valve's original // code, or a new bias code set that puts more shots in the center of // the shot spread. // ------------------------------------------------------------------------------------- inline const Vector &CShotManipulator::ApplySpread ( const Vector &vecSpread, float bias ) { // return ApplySpreadVALVE (vecSpread, bias); return ApplySpreadBIAS (vecSpread, bias); } //--------------------------------------------------------- // CVB - holtt - Renamed this as we are going to replace it // with a new version that biases shots // towards the middle instead of random. //--------------------------------------------------------- // Take a vector (direction) and another vector (spread) // and modify the direction to point somewhere within the // spread. This used to live inside FireBullets. //--------------------------------------------------------- inline const Vector &CShotManipulator::ApplySpreadVALVE ( const Vector &vecSpread, float bias ) { // get circular gaussian spread float x, y, z; if ( bias > 1.0 ) bias = 1.0; else if ( bias < 0.0 ) bias = 0.0; float shotBiasMin = ai_shot_bias_min.GetFloat(); float shotBiasMax = ai_shot_bias_max.GetFloat(); // 1.0 gaussian, 0.0 is flat, -1.0 is inverse gaussian float shotBias = ( ( shotBiasMax - shotBiasMin ) * bias ) + shotBiasMin; float flatness = ( fabsf(shotBias) * 0.5 ); do { x = random->RandomFloat(-1,1) * flatness + random->RandomFloat(-1,1) * (1 - flatness); y = random->RandomFloat(-1,1) * flatness + random->RandomFloat(-1,1) * (1 - flatness); if ( shotBias < 0 ) { x = ( x >= 0 ) ? 1.0 - x : -1.0 - x; y = ( y >= 0 ) ? 1.0 - y : -1.0 - y; } z = x*x+y*y; } while (z > 1); m_vecResult = m_vecShotDirection + x * vecSpread.x * m_vecRight + y * vecSpread.y * m_vecUp; return m_vecResult; } //--------------------------------------------------------- // CVB - holtt - This version creates shot patterns that // are biased towards the middle of the cone. //--------------------------------------------------------- // Take a vector (direction) and another vector (spread) // and modify the direction to point somewhere within the // spread. This used to live inside FireBullets. //--------------------------------------------------------- inline const Vector &CShotManipulator::ApplySpreadBIAS( const Vector &vecSpread, float bias ) { // get circular gaussian spread float x, y; float r, theta; float sinTheta, cosTheta; // Generate a random polar coordinate r = random->RandomFloat (0,1); theta = random->RandomFloat (0, DEG2RAD(360)); // --------------------------------------------------------------------------------- // Note - this polar coordinate we created does not create a random point spread // inside the unit circle. The key is the radius (R) randomization. Half of // our random radius points will lie between 0.0 and 0.5, and the other half // from 0.5 to 1.0. So half of our shots will lie in the inner half of the circle, // and the other half on the outer half. But since the area of the outer half is // larger than the inner half, we'll end up with a greater spread. // For a good writeup on randomness in circles (or lack of randomness), check // out http://mathworld.wolfram.com/DiskPointPicking.html // --------------------------------------------------------------------------------- // Convert to cartesian (X/Y) coordinates SinCos (theta, &sinTheta, &cosTheta); x = r * sinTheta; y = r * cosTheta; m_vecResult = m_vecShotDirection + x * vecSpread.x * m_vecRight + y * vecSpread.y * m_vecUp; return m_vecResult; } #endif // SHOT_MANIPULATOR_H