[green]// explanatory comments[/green]
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...)[val][i]blue italics[/i][/val]
. 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 [ins]Find...[/ins] tool is your friend![red][b]red[/b][/red]
.dlls
directory, and the associated Project/Workspace.)[red][b]dlls/effects.cpp[/b][/red]
file in your favourite editor and add the following code at the end of the file:
//=======================
// ClientFog
//=======================
extern int gmsgSetFog;
[green]// This is the flag defined by the env_fog entity[/green]
[/pre][pre]#define SF_FOG_STARTOFF 0x0001
LINK_ENTITY_TO_CLASS( env_fog, CClientFog );
[/pre][pre]
[green]/*
// 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");
pFog->Spawn();
return pFog;
}
/*[/green]
[/pre][pre]
[green]// Spawns our env_fog entity and sets the m_active flag.[/green]
void CClientFog :: Spawn ( void )
{
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
pev->effects = 0;
if ( pev->spawnflags & SF_FOG_STARTOFF )
m_active = 0;
else
{
SetThink( FogThink );
pev->nextthink = gpGlobals->time + 0.01;
m_active = 1;
}
}
[/pre][pre]
[green]// Reads salient values from the env_fog entity[/green]
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 );
}
[/pre][pre]
[green]// Essentially turns our fog effect on/off by sending a message to the client.
// Anything which changes m_active should force a FogThink.[/green]
void CClientFog :: FogThink ( void )
{
if ( m_active )
{
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();
}
else
{
MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
WRITE_SHORT ( 0 );
WRITE_SHORT ( 0 );
WRITE_SHORT ( 0 );
WRITE_SHORT ( 0 );
WRITE_SHORT ( 0 );
MESSAGE_END();
}
}
[/pre][pre]
[green]// Called when the entity is triggered.[/green]
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;
}
[/pre][pre]
[green]// These macros set up the save/restore code for the CClientFog object[/green]
TYPEDESCRIPTION CClientFog::m_SaveData[] =
{
DEFINE_FIELD( CClientFog, m_active, FIELD_BOOLEAN ),
DEFINE_FIELD( CClientFog, m_iStartDist, FIELD_INTEGER ),
DEFINE_FIELD( CClientFog, m_iEndDist,FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE(CClientFog,CBaseEntity);
The declarations for the above code need to be added toeffects.h
(you'll note some of the classes defined ineffects.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[red][b]dlls/effects.h[/b][/red]
file, just above the final[val][i]#endif[/i][/val]
line:
//=======================
// ClientFog
//=======================
class CClientFog : public CBaseEntity
{
public:
[green]/*
// As stated above, this constructor is not needed; add if required!
static CClientFog *FogCreate( void );
*/[/green]
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 [ent]env_fog[/ent] entity. However there are still a couple of minor problems to tackle...[red][b]dlls/globals.cpp[/b][/red]
:
DLL_GLOBAL int gLevelLoaded;
Now we set it at the appropriate moment; let's edit[red][b]dlls/world.cpp[/b][/red]
:[val][i]extern DLL_GLOBAL int gDisplayTitle;[/i][/val]
Add the following line below that:
extern DLL_GLOBAL int gLevelLoaded;
Now, down around line #479 you should find the[val][i]CWorld :: Precache[/i][/val]
function, to which you need to add the following line:
[val][i]void CWorld :: Precache( void )
{
g_pLastSpawn = NULL;[/i][/val]
gLevelLoaded = TRUE; [green]// Handles "level loaded" case[/green]
With that out of the way, let's handle the player spawning cases. First we'll declare our variable in[red][b]dlls/player.h[/b][/red]
(somewhere around line #90):
[val][i]class CBasePlayer : public CBaseMonster
{
public:[/i][/val]
BOOL m_bRespawned; [green]// True when fog update msg needs to be sent[/green]
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[red][b]dlls/player.cpp[/b][/red]
-- and here we go![val][i]extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle;[/i][/val]
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[val][i]int[/i][/val]
initialisation lines; look for:
[val][i]int gmsgTeamNames = 0;[/i][/val]
and add the following on the next line:
int gmsgSetFog = 0;
Just below that you will find[val][i]LinkUserMessages[/i][/val]
function; addgmsgSetFog
somewhere near the top, like so:
[val][i]void LinkUserMessages( void )
{
// Already taken care of?
if ( gmsgSelAmmo )
{
return;
}[/i][/val]
[green]// This basically registers "SetFog" as a known message to be sent to the server[/green]
gmsgSetFog = REG_USER_MSG("SetFog", -1 );
[val][i] gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo));[/i][/val]
Now you want to jump down to the[val][i]CBasePlayer::PlayerDeathThink[/i][/val]
function; this will be somewhere around line #1150 to #1200. Right at the bottom of the function, just before the[val][i]respawn[/i][/val]
call, you want to add the following:
[val][i] //ALERT(at_console, "Respawn\n");[/i][/val]
m_bRespawned = TRUE; [green]// Handles "player respawned" case[/green]
[val][i] respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam.[/i][/val]
Down around line #2700 to #2800 is[val][i]CBasePlayer::Spawn[/i][/val]
; add the following line right near the bottom of the function:
[val][i] m_flNextChatTime = gpGlobals->time;[/i][/val]
m_bRespawned = TRUE; [green]// Handles "new player" case[/green]
[val][i] g_pGameRules->PlayerSpawn( this );[/i][/val]
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[val][i]CBasePlayer::UpdateClientData[/i][/val]
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[val][i]if ( m_iFOV != m_iClientFOV )[/i][/val]
and the[val][i]if (gDisplayTitle)[/i][/val]
:
[val][i]
if ( m_iFOV != m_iClientFOV )
{
:
:
}
[/i][/val]
if ( m_bRespawned || gLevelLoaded )
{
CBaseEntity *pEntity = NULL;
pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
if ( pEntity )
{
CClientFog *pFog = (CClientFog *)pEntity;
[green]// CClientFog::FogThink handles the actual message sending, so let's force a think[/green]
pFog->pev->nextthink = gpGlobals->time + 0.01;
}
m_bRespawned = gLevelLoaded = FALSE;
}
[val][i]
// HACKHACK -- send the message to display the game title
if (gDisplayTitle)
{
:
:
}
[/i][/val]
That's it for the server-side code. Before we leave thedlls
directory, we can probably compile the server dll -- or leave it till we're fully finished; whatever works best for you!dlls
and move over to thecl_dlls
directory (and associated Project/Workspace!)[red][b]cl_dlls/hud.cpp[/b][/red]
and look for[val][i]__MsgFunc_GameMode[/i][/val]
at around line #130. Add the following:
[val][i]int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf )
{
return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf );
}
[/i][/val]
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[val][i]HOOK_MESSAGE[/i][/val]
macros in no particular order. Add:
HOOK_MESSAGE( SetFog );
to the list. (I added it between[val][i]HOOK_MESSAGE(TeamInfo);[/i][/val]
and[val][i]HOOK_MESSAGE(Spectator);[/i][/val]
, 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! ).cpp
file, open the[red][b]cl_dlls/hud.h[/b][/red]
file; look for
[val][i] int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf );[/i][/val]
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[red][b]cl_dlls/hud_msg.cpp[/b][/red]
. Before we do anything else, let's declare the variables we're going to use. After the initial[val][i]#include[/i][/val]
s and[val][i]#define[/i][/val]
s, 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:
[val][i]int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf )
{
ASSERT( iSize == 0 );[/val][/i]
g_fStartDist = 0.0;
g_fEndDist = 0.0;
Right at the bottom of the file, we'll add ourSetFog
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;
}
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[red][b]cl_dlls/tri.cpp[/b][/red]
. Just after the initial[val][i]#include[/i][/val]
s and[val][i]#define[/i][/val]
s, we'll access the variables we set up previously:
extern float g_fFogColor[3];
extern float g_fStartDist;
extern float g_fEndDist;
After the[val][i]extern "C" { ... }[/i][/val]
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 );
else
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[val][i]HUD_DrawNormalTriangles[/i][/val]
and[val][i]HUD_DrawTransparentTriangles[/i][/val]
. Extensive testing led me to the inescapable conclusion that each apparently does the other's job:[val][i]HUD_DrawNormalTriangles[/i][/val]
handles "transparent" faces and[val][i]HUD_DrawTransparentTriangles[/i][/val]
handles -- well, everything. Of course, all you really need to know is to change the latter function as follows:
[i][val]/*
=================
HUD_DrawTransparentTriangles
[/val]
[green]Despite the name, it appears that this actually controls
non-transparent entities and world brushes[/green][val]
=================
*/
void DLLEXPORT HUD_DrawTransparentTriangles( void )
{[/val][/i]
RenderFog();
[val][i]#if defined( TEST_IT )
// Draw_Triangles();
#endif
}[/i][/val]
That's it! Compile that, and if all goes according to plan you'll have working fog. [val][i]#if defined( TEST_IT ) ... #endif[/i][/val]
? While playing with this file I compiled in the[val][i]Draw_Triangles()[/i][/val]
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...)@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 [ent]env_explosion[/ent] and [ent]env_global[/ent] -- ie, alphabetically! You will now be able to add [ent]env_fog[/ent]s 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!You must log in to post a comment. You can login or register a new account.
And yeah, I could play with SOHL -- but where's the fun in that?!
darkphoenix, lol
tie.
After Skals' comment I went looking for SOHL. I found the Mod download, but could not find the source-code download anywhere. If I ever want to do something which is covered by SOHL I guess I'll go hunting for the code again (as it is, I suspect that the original code of this tutorial was probably lifted from SOHL, but until I find it I can't be sure...) Until then, though, I much prefer to play in the SDK myself...