untode.su

Unlocking FOV in Source games

This post consists of human-readable explanation of two commits I made into Refraction mod’s GitHub repository in 2022:

  1. First one removes the FOV limiters at all.

  2. Second one adds an arbitrary FOV limit back with a better code style.

Now, the quote-unquote patch itself is aimed to increase maximum FOV in SourceSDK 2013 SP games but it’s not out of the picture that it can be both be applied to SourceSDK 2013 MP and, by extension Team Fortress 2.

File: shareddefs.h

All the commonly used macros and definitions are located in a specific header file at game/shared/shareddefs.h. We are interested in the part that specifies the maximum FOV value:

// ...
#define MAX_PLACE_NAME_LENGTH 18

#define MAX_FOV 90
// ...

All we have to do in this file is to add the minimum FOV value and increase the maximum FOV value to desired value. May as well add my snarky comment from 2022:

// ...
#define MAX_PLACE_NAME_LENGTH 18

// Unlike the New Quirky Half-Life 2 Beta Branch, the FOV
// in here is defined to be 110, not 120 - avoiding graphical
// glitches related to the camera clipping through walls and it
// all being a potential wallhack yada yada yada; 110 degrees
// doesn't really have that issue, not that I saw anything like that
#define MIN_FOV 54
#define MAX_FOV 110
// ...

ConVar: fov_desired

The fov_desired console variable is located in SDK at game/client/hl2/clientmode_hlnormal.cpp and in case of TF2 it might be named something of the kind or even be located in a different place, search by identifier usually works to find the location.

// ...
ConVar fov_desired( "fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, 75.0, true, 90.0 );
// ...

We change the hardcoded minimum and maximum values with MIN_FOV and MAX_FOV respectively:

// ...
ConVar fov_desired("fov_desired", "75", FCVAR_ARCHIVE | FCVAR_USERINFO, "Sets the base field-of-view.", true, MIN_FOV, true, MAX_FOV);
// ...

File: gamerules.cpp

Common and default gamerules are defined in SDK at game/shared/gamerules.cpp and the FOV related stuff is located in the CGameRules::ClientSettingsChanged method.

// ...
const char *pszFov = engine->GetClientConVarValue( pPlayer->entindex(), "fov_desired" );
if( pszFov )
    pPlayer->SetDefaultFOV( atoi(pszFov) );

// NVNT see if this user is still or has began using a haptic device
const char *pszHH = engine->GetClientConVarValue( pPlayer->entindex(), "hap_HasDevice" );
// ...

The patch is simple, just replace the part under the if-statement with clamping the value between MIN_FOV and MAX_FOV and setting it instead:

// ...
const char *pszFov = engine->GetClientConVarValue(pPlayer->entindex(), "fov_desired");
if(pszFov) {
    int iFov = atoi(pszFov);
    iFov = clamp(iFov, MIN_FOV, MAX_FOV);
    pPlayer->SetDefaultFOV(iFov);
}

// NVNT see if this user is still or has began using a haptic device
const char *pszHH = engine->GetClientConVarValue( pPlayer->entindex(), "hap_HasDevice" );
// ...

File: baseplayer_shared.cpp

This file should really be common across the entire Source 2013 branch, located at game/shared/baseplayer_shared.cpp contains default player related stuff shared across mods. The FOV related stuff we need to change is located in the conveniently named CBasePlayer::GetDefaultFOV method:

int CBasePlayer::GetDefaultFOV( void ) const
{
    // ...
    int iFOV = (m_iDefaultFOV == 0) ? g_pGameRules->DefaultFOV() : m_iDefaultFOV;
    if(iFOV > MAX_FOV)
        iFOV = MAX_FOV;
    return iFOV;
}

Since we cannot be sure whether CBasePlayer-derived classes clamp the FOV, we clamp the value here again:

int CBasePlayer::GetDefaultFOV( void ) const
{
    // ...
    int iFov = m_iDefaultFOV ? m_iDefaultFOV : g_pGameRules->DefaultFOV();
    iFov = clamp(iFov, MIN_FOV, MAX_FOV);
    return iFov;
}

File: c_baseplayer.h and c_baseplayer.cpp

Just nuke the C_BasePlayer::GetMinFOV method since it’s now hardcoded to be whatever is defined in the shareddefs.h header.

  1. Remove the GetMinFOV declaration from game/client/c_baseplayer.h

  2. Remove the GetMinFOV definition from game/client/c_baseplayer.cpp

File: view.cpp

Because we don’t have GetMinFOV method anymore, we need to account for all the references to it. And there’s only one of them in game/client/view.cpp in the CViewRender::OnRenderStart() method.

//Update our FOV, including any zooms going on
int iDefaultFOV = default_fov.GetInt();
int localFOV = player->GetFOV();
int min_fov = player->GetMinFOV();

// Don't let it go too low
localFOV = MAX( min_fov, localFOV );

We remove min_fov and replace its name with MIN_FOV previously defined in the shareddefs.h header:

//Update our FOV, including any zooms going on
int iDefaultFOV = default_fov.GetInt();
int localFOV = player->GetFOV();

// Don't let it go too low
localFOV = MAX( MIN_FOV, localFOV );

Conclusion

If you have any questions, feel free to hunt me down in shounic’s Discord server and ping the living hell out of me in the programming channel