Tutorial: Coding Fog Last edited 5 years ago2018-07-13 09:05:22 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, but cannot be switched on and off. I may update this, but not now: I'm too busy these days. You'll need the following to do this: 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;
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;

	pev->nextthink = gpGlobals->time + 0.5;
	SetThink( FogThink );
}
void CClientFog :: FogThink ( void )
{
	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();
}
LINK_ENTITY_TO_CLASS( env_fog, CClientFog );

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

IMPLEMENT_SAVERESTORE(CClientFog,CBaseEntity);
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 );

	float m_iStartDist;
	float m_iEndDist;

	virtual int		Save( CSave &save; );
	virtual int		Restore( CRestore &restore; );
	static	TYPEDESCRIPTION m_SaveData[];

public:
	static CClientFog *FogCreate( void );
};
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 copy the following line and replace the old one with it:

extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle, 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...

// Already taken care of?
if ( gmsgSelAmmo )
{
return;
}

...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" );
		if ( pEntity )
			{
				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 );
			}
		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 )
			{
				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;
	}
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

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

DLL_GLOBAL int gLevelLoaded;

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;

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 );

We're done with hud.cpp, so lets 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;
}
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;
}
There should be an util.h in your client workspace, if not make a new one and add the following code:
/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
*	This product contains software technology licensed from Id
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc.
*	All Rights Reserved.
*
*   Use, distribution, and modification of this source code and/or resulting
*   object code is restricted to non-commercial enhancements to products from
*   Valve LLC.  All other use, distribution, or modification is prohibited
*   without written permission from Valve LLC.
*
****/
//  Vector.h
// A subset of the extdll.h in the project HL Entity DLL
//

// Misc C-runtime library headers
#include "STDIO.H"
#include "STDLIB.H"
#include "MATH.H"

float TransformColor ( float color );
If there otherwise is, then simply add the Transformcolor function. 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;

extern engine_studio_api_t IEngineStudio;
After the void Draw_Triangles( void ) function add the following two functions:
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();

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:
@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"
]
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 3 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 2 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 2 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 3 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 1 month 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 3 weeks ago2024-03-26 11:52:16 UTC Comment #106089
Update has been made available that fixes the fog tutorial for the HL25 update.

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