Tutorial: Coding Fog Last edited 5 years ago2019-01-01 20:24:10 UTC

You are viewing an older revision of this wiki page. The current revision may be more detailed and up-to-date. Click here to see the current revision of this page.
This tutorial will help you to create fog in your levels. This particular type of fog gets updated when the game changes levels and loads. You can also switch it on and off should you desire. You'll need the following to do this:

Server side

Server: effects.cpp

So, lets begin with the server side. First, we'll open effects.cpp and add the following code at the bottom:
//=======================
// ClientFog
//=======================
extern int gmsgSetFog;

const int SF_FOG_STARTON = 1;

LINK_ENTITY_TO_CLASS( env_fog, CClientFog );

TYPEDESCRIPTION CClientFog::m_SaveData[] =
{
	DEFINE_FIELD( CClientFog, m_fActive, FIELD_BOOLEAN ),
	DEFINE_FIELD( CClientFog, m_iStartDist, FIELD_INTEGER ),
	DEFINE_FIELD( CClientFog, m_iEndDist, FIELD_INTEGER ),
};

IMPLEMENT_SAVERESTORE( CClientFog, CBaseEntity );

CClientFog *CClientFog::FogCreate()
{
	CClientFog *pFog = GetClassPtr( ( CClientFog * ) NULL );
	pFog->pev->classname = MAKE_STRING( "env_fog" );
	pFog->Spawn();

	return pFog;
}

void CClientFog::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "startdist" ) )
	{
		m_iStartDist = atoi( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "enddist" ) )
	{
		m_iEndDist = atoi( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
		CBaseEntity::KeyValue( pkvd );
}

void CClientFog::Spawn()
{
	pev->effects |= EF_NODRAW;

	if( FStringNull( pev->targetname ) )
		pev->spawnflags |= SF_FOG_STARTON;

	//Delay fog startup to the next frame
	pev->nextthink = gpGlobals->time + 0.01;

	SetThink( &CClientFog::FogThink );
}

void CClientFog::FogThink()
{
	if( !( pev->spawnflags & SF_FOG_STARTON ) )
	{
		m_fActive = FALSE;
	}
	else
	{
		m_fActive = TRUE;
		EnableForAll();
	}

	SetThink( NULL );
}

void CClientFog::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if( !m_fActive )
	{
		m_fActive = TRUE;
		EnableForAll();
	}
	else
	{
		m_fActive = FALSE;
		SetFogAll( g_vecZero, 0, 0 );
	}
}

void CClientFog::CheckFogForClient( edict_t* pClient )
{
	//TODO: currently only one fog entity can exist in a map
	//This can be updated to use a fog manager object that keeps track of which fog entity is active to automatically change fog settings
	CClientFog* pFog = ( CClientFog* ) UTIL_FindEntityByClassname( NULL, "env_fog" );

	if( pFog && pFog->m_fActive == TRUE )
	{
		pFog->SetFog( pClient, pFog->pev->rendercolor, pFog->m_iStartDist, pFog->m_iEndDist );
	}
	else
	{
		SetFog( pClient, g_vecZero, 0, 0 );
	}
}

void CClientFog::EnableForAll()
{
	SetFogAll( pev->rendercolor, m_iStartDist, m_iEndDist );
}

static void InternalSetFog( edict_t* pClient, const Vector& color, float startDistance, float endDistance )
{
	MESSAGE_BEGIN( pClient ? MSG_ONE : MSG_ALL, gmsgSetFog, NULL, pClient );
	WRITE_SHORT( color.x );
	WRITE_SHORT( color.y );
	WRITE_SHORT( color.z );
	WRITE_SHORT( startDistance );
	WRITE_SHORT( endDistance );
	MESSAGE_END();
}

void CClientFog::SetFog( edict_t* pClient, const Vector& color, float startDistance, float endDistance )
{
	if( pClient == NULL )
	{
		ALERT( at_error, "CClientFog::SetFog called with NULL client\n" );
		return;
	}

	InternalSetFog( pClient, color, startDistance, endDistance );
}

void CClientFog::SetFogAll( const Vector& color, float startDistance, float endDistance )
{
	InternalSetFog( NULL, color, startDistance, endDistance );
}

Server: effects.h

Thats it, we're done with effects.ccp, but we still need to add a bit of code in effects.h at the bottom before the #endif:
//=======================
// ClientFog
//=======================
class CClientFog : public CBaseEntity
{
public:
	void Spawn();
	void KeyValue( KeyValueData *pkvd );
	void EXPORT FogThink();
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	float m_iStartDist;
	float m_iEndDist;
	BOOL m_fActive;
	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );
	static TYPEDESCRIPTION m_SaveData[];

	/**
	*	@brief Checks if there's a fog entity active whose settings needs sending. Otherwise, tells the client to disable fog
	*/
	static void CheckFogForClient( edict_t* pClient );

private:
	void EnableForAll();

	/**
	*	@brief Sets the fog settings for the given client
	*/
	static void SetFog( edict_t* pClient, const Vector& color, float startDistance, float endDistance );

	/**
	*	@brief Sets the fog settings for all clients
	*/
	static void SetFogAll( const Vector& color, float startDistance, float endDistance );

public:
	static CClientFog *FogCreate();
};

Server: player.cpp

So, we're done with this. Head over to player.cpp.

Here, at the top we can see the following line:

extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle;

Now, lets add a bit of code that will check if a new level gets loaded.
To do this, simply add the following line after the one above:

extern DLL_GLOBAL int gLevelLoaded;

We'll still need to add a few variables in other files, but let's finish this one first.

Now, look for this line:

int gmsgTeamNames = 0;

Directly after this, add this new line:

int gmsgSetFog = 0;

And, in void LinkUserMessages( void ), after...

gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 );

...add the following line:

gmsgSetFog = REG_USER_MSG("SetFog", -1 );

Now, in CBasePlayer::PlayerDeathThink add the following code before the nextthink, before the respawn code:

m_fUpdateFog = TRUE;

Now, in CBasePlayer::Restore add the following code at the very end, right before the `return status;`:
//Force the fog to update next frame
m_fUpdateFog = TRUE;
This will ensure that the fog is properly restored after loading a saved game.

Now, in CBasePlayer :: UpdateClientData add the following code:
//Update fog after respawn (also sets the fog after connect in multiplayer)
if( m_fUpdateFog )
{
	m_fUpdateFog = FALSE;
	CClientFog::CheckFogForClient( edict() );
}

//Enable fog after level load (singleplayer only)
if( gLevelLoaded )
{
	CClientFog::CheckFogForClient( edict() );
	gLevelLoaded = FALSE;
}

Server: player.h

Thats it! We're all done with player.cpp. Now, lets open up player.h and add a variable in class `CBasePlayer`:

BOOL m_fUpdateFog; // True when fog update msg needs to be sent

Server: globals.cpp

Now, let's open up globals.cpp, and add the following line at the bottom

DLL_GLOBAL int gLevelLoaded;

Server: world.cpp

We'll need to set this variable at every levelchange, since we cant directly detect a map change we will need to set it by an entity that respawns at every levelchange. So open up world.cpp, and in CWorld :: Precache( void ) add he following line:

gLevelLoaded = TRUE;

Client Side

Client: hud.cpp

So, were all done with the server side, now we need to open up the client side. Here we begin with hud.cpp, and after
    int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf )
    {
    	return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf );
    }
add the following code:
    int __MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf )
    {
    	return gHUD.MsgFunc_SetFog( pszName, iSize, pbuf );
    }
After HOOK_MESSAGE( TeamInfo ); add the following line:

HOOK_MESSAGE( SetFog );

Client: hud.h

We're done with hud.cpp, but the new method must be declared in hud.h, so open that and look for this line:

int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf );

And add this after it:

int _cdecl MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf);

Client: hud_msg.cpp

Now let's go to hug_msg.cpp and at the top after the #define we add the following code:
    vec3_t FogColor;
    float g_iFogColor[3];
    float g_iStartDist;
    float g_iEndDist;
In CHud::MsgFunc_ResetHUD we add the following code:
	g_iStartDist = 0.0;
	g_iEndDist = 0.0;
At the bottom of the file we add the following code:
int CHud :: MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf )
{

	BEGIN_READ( pbuf, iSize );
	FogColor.x = TransformColor ( READ_SHORT() );
	FogColor.y = TransformColor ( READ_SHORT() );
	FogColor.z = TransformColor ( READ_SHORT() );
	g_iStartDist = READ_SHORT();
	g_iEndDist = READ_SHORT();

	return 1;
}

Client: cl_util.h

This reads the information sent by the server and sends it to triapi, where it will be used to set the fog. We'll need to add a new function called TransformColor, so open up cl_util.h and add this at the bottom:
inline float TransformColor ( float color )
{
	float trns_clr;
	if(color >= 0 ) trns_clr = color / 255.0f;
	else trns_clr = 1.0;//default value
	return trns_clr;
}

Client: tri.cpp

Now, open up tri.cpp, and add the following code at the top:
#include "winsani_in.h"
#include <windows.h>
#include "winsani_out.h"
#include <gl/gl.h>
Now, after #include "particleman.h" add the following include:

#include "r_studioint.h"

Now, after the #define add the following code
extern float g_iFogColor[4];
extern float g_iStartDist;
extern float g_iEndDist;
extern int g_iWaterLevel;
extern vec3_t FogColor;

extern engine_studio_api_t IEngineStudio;

void BlackFog ( void )
{
	static float fColorBlack[3] = {0,0,0};
	bool bFog = g_iStartDist > 0 && g_iEndDist > 0;
	if (bFog)
		gEngfuncs.pTriAPI->Fog ( fColorBlack, g_iStartDist, g_iEndDist, bFog );
	else
		gEngfuncs.pTriAPI->Fog ( g_iFogColor, g_iStartDist, g_iEndDist, bFog );
}

void RenderFog ( void )
{
	float g_iFogColor[4] = { FogColor.x, FogColor.y, FogColor.z, 1.0 };
	bool bFog = g_iStartDist > 0 && g_iEndDist > 0;
	if ( bFog )
	{
		if ( IEngineStudio.IsHardware() == 2 )
		{
			gEngfuncs.pTriAPI->Fog ( g_iFogColor, g_iStartDist, g_iEndDist, bFog );
		}
		else if ( IEngineStudio.IsHardware() == 1 )
		{
			glEnable(GL_FOG);
			glFogi (GL_FOG_MODE, GL_LINEAR);
			glFogfv (GL_FOG_COLOR, g_iFogColor);
			glFogf (GL_FOG_DENSITY, 1.0f);
			glHint (GL_FOG_HINT, GL_DONT_CARE);
			glFogf (GL_FOG_START, g_iStartDist);
			glFogf (GL_FOG_END, g_iEndDist);
		}
	}
}
In void DLLEXPORT HUD_DrawNormalTriangles( void ) add this:

RenderFog();

In void DLLEXPORT HUD_DrawTransparentTriangles( void ) add this:

BlackFog();

Client: Finishing up

Thats it, we're done with the copy-pasting. We only need to modify the project settings in order for this to work. Go to Project-> cl_dll Settings->Linker->Input->Additional Dependencies and add the following filename: opengl32.lib. Make sure to do this for both debug and release configurations. Now, you should have working fog in your level, but lets not forget the fgd entry, so add this somewhere in your fgd file:

FGD changes

@PointClass size(-16 -16 -16, 16 16 16) base(Targetname) = env_fog : "Client Fog"
[
 	startdist(string) : "Start Distance" : "1"
  	enddist(integer) : "End Distance" : 1500
  	rendercolor(color255) : "Fog Color (R G B)" : "0 0 0"

	spawnflags(Flags) =
	[
		1 : "Start On" : 0
	]
]

Credits

I'd like to say help to those who helped me with this code:

Confused
Sysop
Cause Of Death

They helped me a lot with this fog code.

8 Comments

Commented 4 years ago2020-08-05 14:32:53 UTC Comment #102855
Following this though on a clean build of Solokiller's Half-Life Updated (on VS2019), it throws a single error on compile:

cdll_int.h(38,13): error C2040: 'HSPRITE': 'int' differs in levels of indirection from 'HSPRITE__ *'

Any ideas as to what is causing that?
Commented 3 years ago2021-09-12 13:51:37 UTC Comment #103716
This tutorial is not meant for Solokiller's SDK it seems. Looks like I have to use my brain for this one after all >:[
Probably replacing all instances of vec_3t with Vector and DotProduct with FDotProduct should do the trick
Commented 3 years ago2021-09-12 13:54:30 UTC Comment #103717
Also this tutorial doesn't specify where fog.h and fog.cpp should go
Hold on, it does. I'm blind
Commented 1 year ago2022-11-26 06:59:42 UTC Comment #104907
for Solokiller's Updated SDK users, replace all includes to "windows.h" to "PlatfromHeaders.h"
Commented 10 months ago2023-12-28 05:24:14 UTC Comment #105803
This article is outdated and should be updated for the 25th-anniversary update. Legacy GL functions are not functioning in the latest version of HL engine.
Commented 7 months ago2024-03-10 00:07:59 UTC Comment #106053
@FranticDreamer
That is not true, latest version of HL engine still uses Legacy GL functions but has shader compatibility. The reason why this tutorial doesn't work is that the shaders DON'T know if glEnable(GL_FOG) is run, it just is impossible. To make this tutorial work, theoretically you should replace glEnable(GL_FOG) with something like glUniform1i(glGetUniformLocation(SHADERID, "fogEnabled"), (int)true) but I have ZERO clue on how to find SHADERID atm.
There is also an alternative method, you will have to modify the shader code for your mod though; which should be in "Half-Life/platform/gl_shaders/fs_world.frag". Replace uniform bool fogEnabled with layout(location = 0) uniform bool fogEnabled. Now go back into your code and replace every glEnable(GL_FOG) with glUniform1i(0, (int)true) and obviously replace every glDisable(GL_FOG) with glUniform1i(0, (int)false).
(And yes, I don't know how to reply lol)
Commented 7 months ago2024-03-26 11:52:16 UTC Comment #106089
Update has been made available that fixes the fog tutorial for the HL25 update.
Commented 6 months ago2024-04-20 11:13:05 UTC Comment #106145
This tutorial sends messages to the player on every spawn, which is probably not something we want in multiplayer.

Suggested change, in CBasePlayer::Precache function, add m_bSendMessages here:
if ( gInitHUD )
{
    m_fInitHUD = TRUE;
    m_bSendMessages = TRUE;
}

You must log in to post a comment. You can login or register a new account.