Tutorial: Coding Fog Last edited 7 months ago2019-04-11 07:58:45 UTC by Penguinboy Penguinboy

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
	]
]

Complete source code

The complete source code for this tutorial can be found in this Github repository: https://github.com/SamVanheer/halflife-fog
All of the required changes can be found in this commit: https://github.com/SamVanheer/halflife-fog/commit/1c45a30f15c076a9e02fd6597a8451b3b9fdb474

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.

7 Comments

Commented 11 years ago2008-09-06 03:48:49 UTC Comment #100782
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... :))
Commented 11 years ago2008-09-06 03:49:32 UTC Comment #100783
Oh. Right. No HTML... :-(
Commented 11 years ago2008-10-02 08:00:13 UTC Comment #100784
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.

Hugs!
Commented 9 years ago2010-01-09 22:09:12 UTC Comment #100786
Would this work the same way if I just wanted to make a normal map?
Commented 9 years ago2010-03-05 10:54:04 UTC Comment #100787
@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...
Commented 9 years ago2010-03-05 11:04:24 UTC Comment #100788
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!
Commented 3 weeks ago2019-10-24 07:28:28 UTC Comment #102374
Thank you for sharing with fog. I really want excuse @SamVanheer ( SoloKiller ) because I don't know before that I read github readme "Don't insult, harass or demean.." Yeah I will respect you because I hesitate before for problem with Ubuntu. I promise you that I never harass again. I will keep cool. That is calling welcome and nice meet to you. And waste your ignorance again me! I want forget problem and ignorance. Thanks for hard work! I think Penguinboy want hold me...

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