Tutorial: Coding Fog Last edited 6 years ago2018-07-13 10:37:57 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.

Editors Note: This tutorial doesn't work as-is!

This tutorial has had a bit of an unfortunate past, and has been mangled a bit over the years. I have restored it to what I believe the author intended it to be based on the information I have, but unfortunately it still doesn't work in Steam HL. If you have HL programming experience and know what needs to be fixed, please edit the tutorial and remove this message.

For everybody else: Consider using Spirit of Half-Life as it has an implementation of this tutorial that works (seriously, the code is almost identical, it's probably by the same author).
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;

#define SF_FOG_STARTON 1

CClientFog *CClientFog::FogCreate( void )
{
	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 ( void )
{
	pev->effects |= EF_NODRAW;

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

	pev->nextthink = gpGlobals->time + 0.01;

    SetThink( &CClientFog::FogThink );
}
void CClientFog :: FogThink ( void )
{
	if ( !(pev->spawnflags & SF_FOG_STARTON) )
	{
		m_fActive = FALSE;
	}
	else
	{
		m_fActive = TRUE;
		MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
			WRITE_SHORT ( pev->rendercolor.x );
			WRITE_SHORT ( pev->rendercolor.y );
			WRITE_SHORT ( pev->rendercolor.z );
			WRITE_SHORT ( m_iStartDist );
			WRITE_SHORT ( m_iEndDist );
		MESSAGE_END();
	}
};
void CClientFog::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( !m_fActive )
	{
		m_fActive = TRUE;
		MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
		WRITE_SHORT ( pev->rendercolor.x );
		WRITE_SHORT ( pev->rendercolor.y );
		WRITE_SHORT ( pev->rendercolor.z );
		WRITE_SHORT ( m_iStartDist );
		WRITE_SHORT ( m_iEndDist );
		MESSAGE_END();
		return;
	}
	else
	{
		m_fActive = FALSE;
		MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
		WRITE_SHORT ( NULL );
		WRITE_SHORT ( NULL );
		WRITE_SHORT ( NULL );
		WRITE_SHORT ( NULL );
		WRITE_SHORT ( NULL );
		MESSAGE_END();
		return;
	}
};
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);

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 );
	void KeyValue( KeyValueData *pkvd );
	void EXPORT FogThink( void );
	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[];

public:
	static CClientFog *FogCreate( void );
};

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_fRespawned = TRUE;

Now, in CBasePlayer :: UpdateClientData add the following code:
if ( m_fRespawned )
{
	m_fRespawned = FALSE;
	MESSAGE_BEGIN( MSG_ONE, gmsgSetFog, NULL, pev );
	CBaseEntity *pEntity = NULL;
	pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
	CClientFog *pFog = (CClientFog *)pEntity;
	if ( pEntity && pFog->m_fActive == TRUE )
	{
		WRITE_SHORT ( pFog->pev->rendercolor.x );
		WRITE_SHORT ( pFog->pev->rendercolor.y );
		WRITE_SHORT ( pFog->pev->rendercolor.z );
		WRITE_SHORT ( pFog->m_iStartDist );
		WRITE_SHORT ( pFog->m_iEndDist );
	}
	MESSAGE_END();
}

if (gLevelLoaded)
{
	MESSAGE_BEGIN( MSG_ONE, gmsgSetFog, NULL, pev );
	CBaseEntity *pEntity = NULL;
	pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
	CClientFog *pFog = (CClientFog *)pEntity;
	if ( pEntity && pFog->m_fActive == TRUE )
	{
		// CClientFog *pFog = (CClientFog *)pEntity;
		WRITE_SHORT ( pFog->pev->rendercolor.x );
		WRITE_SHORT ( pFog->pev->rendercolor.y );
		WRITE_SHORT ( pFog->pev->rendercolor.z );
		WRITE_SHORT ( pFog->m_iStartDist );
		WRITE_SHORT ( pFog->m_iEndDist );
	}
	else
	{
		WRITE_SHORT ( 0 );
		WRITE_SHORT ( 0 );
		WRITE_SHORT ( 0 );
		WRITE_SHORT ( 0 );
		WRITE_SHORT ( 0 );
	}
	MESSAGE_END();
	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 : public CBaseMonster:

BOOL m_fRespawned; // 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;

Im 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: util.cpp

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 util.cpp and add this at the bottom:
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: cl_util.h

Open cl_util.h, and add this line to the end:

float TransformColor ( float color );

Client: tri.cpp

Now, open up tri.cpp, and add the following code at the top:
#include <windows.h>
#include <gl/gl.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;

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. Now, you should have working fog in your level, but lets not forged the fgd entry, so add this somewhere in your fgd file:

FGD changes

@PointClass size(-16 -16 -16, 16 16 16) = env_fog : "Client Fog"
[
 	startdist(string) : "Start Distance" : "1"
  	enddist(integer) : "End Distance" : 1500
  	rendercolor(color255) : "Fog Color (R G B)" : "0 0 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 2 years 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 8 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 7 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.