Check out Half-Life Re-imagined competition results!
Check out Skewing textures in Hammer, our newest tutorial!
Say hello to kesizewi, our newest member!


Site Stuff






Feeling Blue

What's your favourite shade of blue?














9 mins


11 mins


14 mins


54 mins

Dr. Orange

56 mins


1 hours


1 hours

The Mad Carrot


A gaming and technology blog by TWHL admins Penguinboy and Ant. A music blog by TWHL users Ant and Hugh.

Coding Fog

Page: [1]

Tutorials > Half-Life > Advanced > Coding Fog


By Highlander (More from this user)
27th February, 2007
star star (2 votes)

Coding Fog

Add Fog to your Mod

by darkphoenix_68

(The mod originally posted by Highlander formed the basis of my work, but as originally posted it contained several errors that prevented it from working. The TWHL Admins kindly agreed to allow me to revise his tutorial. Needless to say, without his original tutorial as a starting point, I would never have gotten this code to work as it now does. Additional credit (as originally extended by Highlander) goes to Confused, Sysop, and Cause of Death.)

If you want to add fog to one or more maps in your Half-Life mod, this tutorial will show you how to do it.


This fog is a visual effect, controlled by a point entity (an env_fog) which you can insert into your level. It can be toggled on or off as required. It can be any colour you choose, and its apparent density is controlled by setting the start and end distances over which the effect is applied.

Sadly, the code also has its limitations:

So, if you want to go ahead and add support for this client-based point-entity fog to your mod, read on.

General Comments

The following tutorial is quite code-intensive, and unfortunately the bbCode used here on TWHL does not preserve indentation, so it may look a little messy. However, it does work (I've tested it extensively) and it will work whether it is indented or not. Obviously, for your own sanity, you may wish to reindent as appropriate! The other artifact of the site itself of which you should be aware is that some lines of code wrap when they reach the edge of the page; it should be obvious, but watch out for it anyway!

I have scattered additional // explanatory comments throughout the code. These form part of the tutorial, but are (obviously) not necessary to the compiler; however, I hope they will assist in explaining what is going on. (Or, I guess, make it quite clear that I do not know what is going on...)

Existing code (shown for reference and/or context) is in blue italics. In many cases I have given approximate line numbers; these are only approximate, because my files contain other code changes not relevant to this tutorial... If the line number does not help you locate the piece of code I am changing, your Find... tool is your friend!

Files to be edited are shown in red.

Server-side: The ClientFog Class

First we need to create support for the env_fog entity. This is done server-side (in the dlls directory, and the associated Project/Workspace.)

Open the dlls/effects.cpp file in your favourite editor and add the following code at the end of the file:

// ClientFog
extern int gmsgSetFog;

// This is the flag defined by the env_fog entity
#define SF_FOG_STARTOFF 0x0001
LINK_ENTITY_TO_CLASS( env_fog, CClientFog );

// While a CClientFog constructor is not actually necessary for
// this code, you might find use for one down the track,
// in which case it may look like this:
CClientFog *CClientFog::FogCreate( void )
CClientFog *pFog = GetClassPtr( (CClientFog *)NULL );
pFog->pev->classname = MAKE_STRING("env_fog");
return pFog;

// Spawns our env_fog entity and sets the m_active flag.
void CClientFog :: Spawn ( void )
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
pev->effects = 0;
if ( pev->spawnflags & SF_FOG_STARTOFF )
m_active = 0;
SetThink( FogThink );
pev->nextthink = gpGlobals->time + 0.01;
m_active = 1;

// Reads salient values from the env_fog entity
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;
CBaseEntity::KeyValue( pkvd );

// Essentially turns our fog effect on/off by sending a message to the client.
// Anything which changes m_active should force a FogThink.

void CClientFog :: FogThink ( void )
if ( m_active )
WRITE_SHORT ( pev->rendercolor.x );
WRITE_SHORT ( pev->rendercolor.y );
WRITE_SHORT ( pev->rendercolor.z );
WRITE_SHORT ( m_iStartDist );
WRITE_SHORT ( m_iEndDist );

// Called when the entity is triggered.
void CClientFog::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
if ( ShouldToggle( useType, m_active ) )
m_active = !m_active;
SetThink( FogThink );
pev->nextthink = gpGlobals->time + 0.01;

// These macros set up the save/restore code for the CClientFog object
TYPEDESCRIPTION CClientFog::m_SaveData[] =
DEFINE_FIELD( CClientFog, m_active, FIELD_BOOLEAN ),
DEFINE_FIELD( CClientFog, m_iStartDist, FIELD_INTEGER ),


The declarations for the above code need to be added to effects.h (you'll note some of the classes defined in effects.cpp have their declarations in the .cpp file, but we need to make our fog visible to other files!) Place the following code at the bottom of the dlls/effects.h file, just above the final #endif line:

// ClientFog
class CClientFog : public CBaseEntity
// As stated above, this constructor is not needed; add if required!
static CClientFog *FogCreate( void );

void Spawn( void );
void KeyValue( KeyValueData *pkvd );
void EXPORT FogThink( void );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
int m_iStartDist;
int m_iEndDist;
bool m_active;
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];

This gives us the code we need to initialise our fog to the values set in the env_fog entity. However there are still a couple of minor problems to tackle...

Server-side: Fog Status Messages

Whenever a new player joins the server, it needs to send a fog status message to the client. There are three possible situations in which the client needs to be updated, and we need to cover them all:

Our first problem is that we cannot directly detect a change of map. Let's change that by hooking into code which is called at every level change.

First we shall add our variable declaration to the bottom of dlls/globals.cpp:

DLL_GLOBAL int gLevelLoaded;

Now we set it at the appropriate moment; let's edit dlls/world.cpp:

Somewhere around line #43, you will find:

extern DLL_GLOBAL int gDisplayTitle;

Add the following line below that:

extern DLL_GLOBAL int gLevelLoaded;

Now, down around line #479 you should find the CWorld :: Precache function, to which you need to add the following line:

void CWorld :: Precache( void )
g_pLastSpawn = NULL;

gLevelLoaded = TRUE; // Handles "level loaded" case

With that out of the way, let's handle the player spawning cases. First we'll declare our variable in dlls/player.h (somewhere around line #90):

class CBasePlayer : public CBaseMonster

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

Now we can modify our player code to pull all of this together. Be brave; there are many small changes scattered through this next file. Open dlls/player.cpp -- and here we go!

First we need to find the following line (at around line #45):

extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle;

and change it to look like this:

extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle, gLevelLoaded;

A little further down you will find a whole list of int initialisation lines; look for:

int gmsgTeamNames = 0;

and add the following on the next line:

int gmsgSetFog = 0;

Just below that you will find LinkUserMessages function; add gmsgSetFog somewhere near the top, like so:

void LinkUserMessages( void )
// Already taken care of?
if ( gmsgSelAmmo )

// This basically registers "SetFog" as a known message to be sent to the server
gmsgSetFog = REG_USER_MSG("SetFog", -1 );
gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo));

Now you want to jump down to the CBasePlayer::PlayerDeathThink function; this will be somewhere around line #1150 to #1200. Right at the bottom of the function, just before the respawn call, you want to add the following:

//ALERT(at_console, "Respawn\n");
m_bRespawned = TRUE; // Handles "player respawned" case
respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam.

Down around line #2700 to #2800 is CBasePlayer::Spawn; add the following line right near the bottom of the function:

m_flNextChatTime = gpGlobals->time;
m_bRespawned = TRUE; // Handles "new player" case
g_pGameRules->PlayerSpawn( this );

We're almost done. Now that we have detected all of the cases for which we need to send our fog status message, all we need to do is actually process that information. The best place to do that is in the CBasePlayer::UpdateClientData function, down around line #3800. You can probably insert the following snippet of code at any convenient point in this function; I slotted mine between the if ( m_iFOV != m_iClientFOV ) and the if (gDisplayTitle):

if ( m_iFOV != m_iClientFOV )

if ( m_bRespawned || gLevelLoaded )
CBaseEntity *pEntity = NULL;
pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
if ( pEntity )
CClientFog *pFog = (CClientFog *)pEntity;
// CClientFog::FogThink handles the actual message sending, so let's force a think
pFog->pev->nextthink = gpGlobals->time + 0.01;
m_bRespawned = gLevelLoaded = FALSE;

// HACKHACK -- send the message to display the game title
if (gDisplayTitle)

That's it for the server-side code. Before we leave the dlls directory, we can probably compile the server dll -- or leave it till we're fully finished; whatever works best for you!

Client-side: Incoming Messages

At this point, the server has initialised the fog, detected everything it needs to detect, and sent a fog status message hurtling through the void (sorry, bad pun!) towards the client(s). Now we just need to convince the client to catch them! To do that, we'll leave the safety of dlls and move over to the cl_dlls directory (and associated Project/Workspace!)

First we'll ask the HUD to catch the incoming message. Open cl_dlls/hud.cpp and look for __MsgFunc_GameMode at around line #130. Add the following:

int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf )
return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf );

int __MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf )
return gHUD.MsgFunc_SetFog( pszName, iSize, pbuf );

Further down, around line #300, you will find a whole list of HOOK_MESSAGE macros in no particular order. Add:


to the list. (I added it between HOOK_MESSAGE(TeamInfo); and HOOK_MESSAGE(Spectator);, but I don't suppose its placement is critical. It might almost be nice if the list was sorted alphabetically, but that's up to you -- and more to the point, it may break everything, so let's leave that for another time! :-))

To follow through on our changes to the .cpp file, open the cl_dlls/hud.h file; look for

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

somewhere near line #640, and after it add:

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

Now that we've caught the message from the server, let's process it. To do this we need cl_dlls/hud_msg.cpp. Before we do anything else, let's declare the variables we're going to use. After the initial #includes and #defines, add the following:

float g_fFogColor[3];
float g_fStartDist;
float g_fEndDist;

A couple of lines down from there, we shall reset our fog distance values when we reset the HUD:

int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf )
ASSERT( iSize == 0 );

g_fStartDist = 0.0;
g_fEndDist = 0.0;

Right at the bottom of the file, we'll add our SetFog function which actually interprets the received message:

int CHud :: MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf )
BEGIN_READ( pbuf, iSize );
g_fFogColor[0] = (float)READ_SHORT(); // R
g_fFogColor[1] = (float)READ_SHORT(); // G
g_fFogColor[2] = (float)READ_SHORT(); // B
g_fStartDist = (float)READ_SHORT();
g_fEndDist = (float)READ_SHORT();
return 1;

Where The Magic Happens

We're on the home straight now. We've done everything except actually display the fog. (Well, almost everything, but I'll get to that!) The file we want to edit now is tri.cpp. This is the file which confused me the most when I was originally trying the get the code to work -- but it was only once I started looking further afield that I solved the puzzle. Anyway, without further ado, let's open cl_dlls/tri.cpp. Just after the initial #includes and #defines, we'll access the variables we set up previously:

extern float g_fFogColor[3];
extern float g_fStartDist;
extern float g_fEndDist;

After the extern "C" { ... } declarations, we'll add the heart of the whole thing:

void RenderFog ( void )
static float fColorBlack[3] = {0,0,0};
if (g_fStartDist > 0.0 && g_fEndDist > 0.0)
gEngfuncs.pTriAPI->Fog ( g_fFogColor, g_fStartDist, g_fEndDist, 1 );
gEngfuncs.pTriAPI->Fog ( fColorBlack, g_fStartDist, g_fEndDist, 0 );

And now for the thing that really confused me. At the bottom of the file we have HUD_DrawNormalTriangles and HUD_DrawTransparentTriangles. Extensive testing led me to the inescapable conclusion that each apparently does the other's job: HUD_DrawNormalTriangles handles "transparent" faces and HUD_DrawTransparentTriangles handles -- well, everything. Of course, all you really need to know is to change the latter function as follows:


Despite the name, it appears that this actually controls
non-transparent entities and world brushes

void DLLEXPORT HUD_DrawTransparentTriangles( void )

#if defined( TEST_IT )
// Draw_Triangles();

That's it! Compile that, and if all goes according to plan you'll have working fog.

(Well, there's still that "almost" to take care of. I'll get there in a second. Just as an aside, though, while we're here: see that #if defined( TEST_IT ) ... #endif? While playing with this file I compiled in the Draw_Triangles() code. The whole thing (ie, Half-Life) fell over while trying to start a map, so I hastily got rid of it again. As soon as I'm done with this tutorial I shall probably clean all that code out to make the file a little neater...)

The Final Step

So what remains to be done? Adding the env_fog entity to your map, of course. If you use Valve Hammer Editor, you can add the following to whichever FGD file you are mapping with:

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

Since VHE does no sorting of its own, it probably makes sense to add this between env_explosion and env_global -- ie, alphabetically! You will now be able to add env_fogs to your maps -- although be warned, I'm not sure what will happen if you have more than one per level. I'd imagine if you had several with different colours, you could trigger them off and on to change the colour of your fog -- but I have not tested that!

Unfortunately I am not familiar with any other editors used to make HL maps. No doubt they have a way of adding new entities, similar to the FGD file in Hammer -- but you'll need to figure that out for yourself! Sorry...

Wrap Up

What I have done is produce a small (and exceedingly ugly) test map for you to check out. I guess you could play with that to add multiple env_fog entities, or whatever else you wanted to try. In the map you will find several transparent objects ("texture"-rendered and "solid"-rendered) because that was what I was having the most difficulty with initially. You will also find a large target which, if shot, will toggle the fog on and off.

It is quite a bit of coding to get to this point, but I hope my directions have been clear enough that we all arrived here with code which compiles and, y'know, actually works! If you have any problems let me know and I'll see if I can help out.

Have fun!


avatar darkphoenix_68 says: star star star 5th September 2008, 20:48 PM
Thanks a lot for this tutorial. I have only recently started playing with the SDK, and the ability to add fog to my latest level seemed too good to pass up, so I took a shot at applying this. Ultimately I got it to compile, after quite a bit of tweaking and head-scratching -- but I'm sorry to report that there are errors in your tutorial.

The first problem I ran into was around where you say "<i>This is the code that needs to be updated in player.cpp. Replace the old version of this code with this one.</i>" That had me quite baffled -- replace <i>what</i> old version? -- until I decided that the whole section from there down to "<i>So, we're done with this. Head over to player.cpp.</i>" appeared to be a duplication of the "<i>CBasePlayer :: UpdateClientData</i>" code further down. I reasoned that it was a copy-paste error that you forgot to clean up, and the whole thing seemed to make a lot more sense once I deleted that section completely.

The second problem I had was around your discussion of the "util.h" file (for the <i>transform_color</i> declaration.) This was apparently related to numerous compile errors I had, most of which were resolved when I used "cl_util.h" instead of "util.h". Only today did I happen to find a mention of this very problem in Botman's FAQ: at some point in the SDK history, util.h was renamed to cl_util.h... So this wasn't actually a problem with this tutorial, but it is something worth noting for anybody else attempting to apply this code.

Once I finally got it to compile, and had the env_fog entity added to my map, I found that it was behaving most strangely. Specifically, all transparent entities were fogging correctly (made quite apparent by the fact I was using orange fog) but solid entities and world brushes were not fogging at all.

I have done some experimentation with the code, and it appears to me that the HL engine is actually calling <i>HUD_DrawNormalTriangles()</i> to draw transparent triangles, and <i>HUD_DrawTransparentTriangles()</i> to draw solid triangles -- which is just bizarre! I've also discovered that even if I call <i>RenderFog</i> from <i>both</i> of the <i>HUD_Draw...Triangles</i> it will not work as expected; in that case it will correctly fog solid faces but leave transparent ones un-fogged.

Having a closer look at the code, I have also noticed a couple of peculiarities which I must question. First, it seems that BlackFog does not call any OpenGL code? Second, I'm wondering why the startdist declaration in the FGD is a string rather than an integer? I shall attempt to answer these questions myself -- although I may not have time to play with the code for a few weeks now...

Anyway, despite its problems, this <i>has</i> been a helpful tutorial -- and it is teaching me a lot about diving into the code! So thanks for posting it! (Although, having gotten this far, I'm no longer sure if I'll be using it in my new level. Even once it's working, it doesn't entirely behave as I would like... :-))
avatar darkphoenix_68 says: 5th September 2008, 20:49 PM
Oh. Right. No HTML... :-(
avatar SysOp says: star 2nd October 2008, 01:00 AM
Dear darkphoenix_68:

Thanks for modify the precious Highlander's tutorial. You have stripped out all the necessary fixes to make it work properly.
Also I want to thank to the TWHL Admins to let you allow screw up the whole code.

Without offense, take a C++ book and read again your _own_ words and think twice before complain about something. We had a workeable tutorial that fix the problem. Now the code is missing.

avatar Highlander says: 9th October 2008, 17:12 PM
Nice to see that you have reworked the tutorial.

I'm not the one who messed the original code up,
but one of the admins who I had asked to update
the code with my support for triggering.

I must raise my concern on how you have, as SysOp
kindly pointed it out, some necessary functions for
the code.

Let me explain what each of these did:

BlackFog : This handles fogging for additive triangles,
wich mainly sprites. This was necessary, otherwise your
additive triangles are incorrectly fogged.

I call this func from DrawTransparentTriangles, and it
gets the job done. This must be a TriAPI call, wich HL
seems to handle in a different way from the OGL calls.

The OpenGL fog calls: These were necessary for Steam.
As I've mentioned earlier, HL handles TriAPI fog differently
from the usual OGL calls, so does the Steam version from WON.

I always use OGL fog in RenderFog, and call it from V_CalcRefDef
and DrawNormalTriangles. This way, it works both in Steam and WON
HL. Your current code won't support fog in Steam HL.

Anyhow, I'm happy that you've taken time to fix the admin's mistake.
avatar Suparsonik says: 9th January 2010, 14:09 PM
Would this work the same way if I just wanted to make a normal map?
avatar darkphoenix_68 says: 5th March 2010, 02:54 AM
@SysOp and @Highlander

My apologies if I have in fact helped to trash a tut which was working. I posted my original comment here in an attempt to point out some of the problems I was having getting the code, as presented here at the time, to work.

The problem, I guess -- apart from the formatting glitches and code duplication which made it so hard to follow -- was that yes, I am coding for the WON version of HL, not the STEAM version (for much the same reasons that I'm still playing with HL at all!) To be honest, this distinction never actually occurred to me, and perhaps it should have.

As far as that goes: mea culpa!

When I submitted my version, well, as far as I knew (based on what I was told) my tutorial would not be replacing the original but added as a separate tutorial (hence my title of "Coding Fog Addendum") so when it was added as a separate tut, AND put in place of this one, I was as surprised as anyone...
avatar darkphoenix_68 says: 5th March 2010, 03:04 AM
Incidentally, @Highlander and/or @SysOp, I *do* have a text copy (unformatted, unfortunately) of the original tutorial which was here before I tried to help. If either of you want a copy so you can resubmit it and return this original tut to its original state, I'll be more than happy to send it to you!
You must be logged in to comment.