@PointClass base(Targetname) size(-16 -16 -16, 16 16 16) = env_fog : "Client Fog"
[
startdist(integer) : "Start Distance" : 1
enddist(integer) : "End Distance" : 500
rendercolor(color255) : "Fog Color (R G B)" : "0 0 0"
blendtime(integer): "Blend time" : 0
spawnflags(flags) =
[
1 : "Start On" : 0
]
]
Open up your solution for your mod in Visual Studio, and go to the server library. First of all we're going to add the entity definition, so open up effects.cpp, and at the bottom add the following piece of code:
//=========================================================
// Spawnflags
//=========================================================
#define SF_FOG_ACTIVE 1
//=========================================================
// Global variables for fog
//=========================================================
int CEnvFog::g_iCurrentEndDist = 0;
int CEnvFog::g_iIdealEndDist = 0;
float CEnvFog::g_flBlendDoneTime = 0;
//=========================================================
// Externals
//=========================================================
extern int gmsgFog;
//=========================================================
// CEnvFog
//=========================================================
LINK_ENTITY_TO_CLASS( env_fog, CEnvFog );
TYPEDESCRIPTION CEnvFog::m_SaveData[] =
{
DEFINE_FIELD( CEnvFog, m_iStartDist, FIELD_INTEGER ),
DEFINE_FIELD( CEnvFog, m_iEndDist, FIELD_INTEGER ),
DEFINE_FIELD( CEnvFog, m_bActive, FIELD_BOOLEAN ),
DEFINE_FIELD( CEnvFog, m_flBlendTime, FIELD_FLOAT ),
};
IMPLEMENT_SAVERESTORE( CEnvFog, CBaseEntity );
void CEnvFog::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 if (FStrEq(pkvd->szKeyName, "blendtime"))
{
m_flBlendTime = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else
CBaseEntity::KeyValue( pkvd );
}
void CEnvFog::Spawn( void )
{
Precache();
pev->solid = SOLID_NOT;
pev->effects |= EF_NODRAW;
pev->movetype = MOVETYPE_NONE;
if(FBitSet(pev->spawnflags, SF_FOG_ACTIVE)
|| FStringNull(pev->targetname))
m_bActive = TRUE;
else
m_bActive = FALSE;
}
void CEnvFog::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
BOOL prevState = m_bActive;
switch(useType)
{
case USE_OFF:
m_bActive = FALSE;
break;
case USE_ON:
m_bActive = TRUE;
break;
default:
m_bActive = !m_bActive;
break;
}
// Only update if it was changed
if(prevState != m_bActive)
{
// Update fog msg
UpdateFog(m_bActive, TRUE, NULL);
if(m_bActive || !m_flBlendTime)
{
// Set globalvars for target fog
CEnvFog::SetCurrentEndDist(m_bActive ? m_iEndDist : 0, m_flBlendTime);
}
}
}
void CEnvFog::UpdateFog( BOOL isOn, BOOL doBlend, CBaseEntity* pPlayer )
{
if(isOn)
{
if(pPlayer)
MESSAGE_BEGIN(MSG_ONE, gmsgFog, NULL, pPlayer->pev);
else
MESSAGE_BEGIN(MSG_ALL, gmsgFog, NULL);
WRITE_COORD(m_iStartDist);
WRITE_COORD(m_iEndDist);
WRITE_BYTE(pev->rendercolor.x);
WRITE_BYTE(pev->rendercolor.y);
WRITE_BYTE(pev->rendercolor.z);
if(doBlend)
WRITE_COORD(m_flBlendTime);
else
WRITE_COORD(0);
MESSAGE_END();
}
else if(!m_flBlendTime)
{
if(pPlayer)
MESSAGE_BEGIN(MSG_ONE, gmsgFog, NULL, pPlayer->pev);
else
MESSAGE_BEGIN(MSG_ALL, gmsgFog, NULL);
WRITE_COORD(0);
WRITE_COORD(0);
WRITE_BYTE(0);
WRITE_BYTE(0);
WRITE_BYTE(0);
WRITE_COORD(0);
MESSAGE_END();
}
}
void CEnvFog::SendInitMessages ( CBaseEntity* pPlayer )
{
if(!m_bActive)
return;
UpdateFog(TRUE, FALSE, pPlayer);
CEnvFog::SetCurrentEndDist(m_iEndDist, m_flBlendTime);
}
void CEnvFog::SetCurrentEndDist( int enddist, float blendtime )
{
if((!blendtime || g_iCurrentEndDist < enddist) || !g_iCurrentEndDist)
{
g_iCurrentEndDist = enddist;
g_iIdealEndDist = 0;
g_flBlendDoneTime = 0;
}
else
{
g_iIdealEndDist = enddist;
g_flBlendDoneTime = gpGlobals->time + blendtime;
}
}
void CEnvFog::FogThink( void )
{
if(!g_flBlendDoneTime || !g_iIdealEndDist)
return;
if(g_flBlendDoneTime <= gpGlobals->time)
{
g_iCurrentEndDist = g_iIdealEndDist;
g_iIdealEndDist = 0;
g_flBlendDoneTime = 0;
}
}
BOOL CEnvFog::CheckBBox( edict_t* pplayer, edict_t* pedict )
{
if(!g_iCurrentEndDist)
return FALSE;
// Don't let fog cull underwater
if(pplayer->v.waterlevel == 3)
return FALSE;
// Calculate distance to edge
Vector boxTotal = Vector(g_iCurrentEndDist, g_iCurrentEndDist, g_iCurrentEndDist);
float edgeLength = boxTotal.Length();
// Set the fog bbox mins maxs
Vector viewOrigin = pplayer->v.origin + pplayer->v.view_ofs;
Vector fogMins, fogMaxs;
for(int i = 0; i < 3; i++)
{
fogMins[i] = viewOrigin[i] - edgeLength;
fogMaxs[i] = viewOrigin[i] + edgeLength;
}
// Set the entity mins/maxs
vec3_t entMins, entMaxs;
entMins = pedict->v.origin+pedict->v.mins;
entMaxs = pedict->v.origin+pedict->v.maxs;
if (fogMins[0] > entMaxs[0])
return TRUE;
if (fogMins[1] > entMaxs[1])
return TRUE;
if (fogMins[2] > entMaxs[2])
return TRUE;
if (fogMaxs[0] < entMins[0])
return TRUE;
if (fogMaxs[1] < entMins[1])
return TRUE;
if (fogMaxs[2] < entMins[2])
return TRUE;
return FALSE;
}
Next up, open up the effects.h header and before the #endif at the end, we'll add the class definition:
//=========================================================
// CEnvFog
//=========================================================
class CEnvFog : public CBaseEntity
{
public:
void Spawn( void );
void SendInitMessages( CBaseEntity* pPlayer = NULL );
void KeyValue( KeyValueData *pkvd );
void UpdateFog( BOOL isOn, BOOL doBlend, CBaseEntity* pPlayer );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
public:
static void SetCurrentEndDist( int enddist, float blendtime );
static int GetCurrentEndDist( void ) { return g_iCurrentEndDist; }
static void FogThink( void );
static BOOL CheckBBox( edict_t* pplayer, edict_t* pedict );
private:
static TYPEDESCRIPTION m_SaveData[];
int m_iStartDist;
int m_iEndDist;
float m_flBlendTime;
BOOL m_bActive;
private:
static int g_iCurrentEndDist;
static int g_iIdealEndDist;
static float g_flBlendDoneTime;
};
These define the env_fog entity, and some helper functions that are used on the server side. As you can see in the class definition, we're adding a function called "SendInitMessages". This will be called by each player upon spawning, so that the entities send their data to that client.void CBasePlayer::TabulateAmmo( void );
And add this:
virtual void InitializeEntities( void );
Next up, find this line:
float m_flNextChatTime;
Add this new line after:
BOOL m_bSendMessages;
Next up, open up player.cpp, and before this line of code:
void LinkUserMessages( void )
Add this:
int gmsgFog = 0;
Within the LinkUserMessages function, add this new line:
gmsgFog = REG_USER_MSG("Fog", 9);
Next up, look up the CBasePlayer :: Precache function, and at the bottom of the function definition, add this:
m_bSendMessages = TRUE;
This will inform the player code, so that whenever a player spawns, it will send through the init messages for all the entities. Following this, go to the CBasePlayer :: UpdateClientData function, and at the top, add the following piece of code:
if( m_bSendMessages )
{
InitializeEntities();
m_bSendMessages = FALSE;
}
Now before the CBasePlayer :: FBecomeProne function, add this new function:
//=========================================================
// InitializeEntities
//=========================================================
void CBasePlayer :: InitializeEntities ( void )
{
edict_t* pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 );
CBaseEntity* pEntity;
for(int i = 0; i < gpGlobals->maxEntities; i++, pEdict++)
{
if(pEdict->free)
continue;
pEntity = CBaseEntity::Instance( pEdict );
if(!pEntity)
break;
pEntity->SendInitMessages(this);
}
}
We'll need to venture over to the client project for a momment. Open up hl_baseentity.cpp, and before the definition of the ClearMultiDamage blank function, add this line:
void CBasePlayer::InitializeEntities( void ) {};
Next, open up cbase.h, and find this line in the class definition of CBaseEntity:
virtual const char *TeamID( void ) { return ""; }
And add this new function definition:
virtual void SendInitMessages( CBaseEntity* pPlayer = NULL ) {};
Next, open up client.cpp and add a new include at the top with the others:
#include "effects.h"
Go to the StartFrame function, and at the bottom, add this call:
CEnvFog::FogThink();
After this, we will add the piece of code that does fog culling on packet entities. This will allow you to save on visible and rendered entities using the fog effect. Look up this piece of code:
if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) )
{
return 0;
}
And right after, add this:
if ( CEnvFog::CheckBBox( host, ent ) )
{
return 0;
}
Find the ServerActivate function, and at the very bottom of the function add this call:
// Reset fog when reloading
CEnvFog::SetCurrentEndDist(0, 0);
Now that that is done, we will be adding the fog distance checks to the monster code. Open up monsters.cpp, and in the MonsterInit function, remove these two lines:
m_flDistTooFar = 1024.0;
m_flDistLook = 2048.0;
Next up, go to the MonsterThink function, and add this new bit of code before the call to RunAI:
int fogEndDist = CEnvFog::GetCurrentEndDist();
if(fogEndDist > 0)
{
m_flDistTooFar = fogEndDist - 100;
m_flDistLook = fogEndDist + 300;
}
else
{
m_flDistTooFar = 1024.0;
m_flDistLook = 2048.0;
}
With that, we're done with the server side implementation. Open up the client library, and first of all, copy the fog.cpp and fog.h files from the tutorial files zip, and add them to our cl_dll folder in your source code library, then add these files to your client project file.#include "fog.h"
Now go to the HUD_CreateEntities, and at the bottom of the function add this call:
gFog.HUD_CreateEntities();
Open up hud.cpp, and at the top, add the following include to the next to others:
#include "fog.h"
In the CHud :: Init function, add the following call at the end:
gFog.Init();
In the CHud :: VidInit function, add this at the end also:
gFog.VidInit();
Open up studio_util.cpp, and at the bottom add this function definition:
/*
====================
VectorRotate
====================
*/
void VectorRotate (const float *in1, float in2[3][4], float *out)
{
out[0] = DotProduct(in1, in2[0]);
out[1] = DotProduct(in1, in2[1]);
out[2] = DotProduct(in1, in2[2]);
}
Open the header for this file, and add this line at the bottom before the #endif:
void VectorRotate (const float *in1, float in2[3][4], float *out);
Next we'll add culling on rendered models. Open up StudioModelRenderer.cpp and add this include at the top next to the others:
#include "fog.h"
We'll be adding a new function to the CStudioModelRenderer class, which will calculate a bounding box for us based on the current sequence info. At the bottom of the file, add this new function:
/*
====================
StudioGetMinsMaxs
====================
*/
void CStudioModelRenderer::StudioGetMinsMaxs ( vec3_t& outMins, vec3_t& outMaxs )
{
if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq)
m_pCurrentEntity->curstate.sequence = 0;
// Build full bounding box
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence;
vec3_t vTemp;
static vec3_t vBounds[8];
for (int i = 0; i < 8; i++)
{
if ( i & 1 ) vTemp[0] = pseqdesc->bbmin[0];
else vTemp[0] = pseqdesc->bbmax[0];
if ( i & 2 ) vTemp[1] = pseqdesc->bbmin[1];
else vTemp[1] = pseqdesc->bbmax[1];
if ( i & 4 ) vTemp[2] = pseqdesc->bbmin[2];
else vTemp[2] = pseqdesc->bbmax[2];
VectorCopy( vTemp, vBounds[i] );
}
float rotationMatrix[3][4];
m_pCurrentEntity->angles[PITCH] = -m_pCurrentEntity->angles[PITCH];
AngleMatrix(m_pCurrentEntity->angles, rotationMatrix);
m_pCurrentEntity->angles[PITCH] = -m_pCurrentEntity->angles[PITCH];
for (int i = 0; i < 8; i++ )
{
VectorCopy(vBounds[i], vTemp);
VectorRotate(vTemp, rotationMatrix, vBounds[i]);
}
// Set the bounding box
outMins = Vector(9999, 9999, 9999);
outMaxs = Vector(-9999, -9999, -9999);
for(int i = 0; i < 8; i++)
{
// Mins
if(vBounds[i][0] < outMins[0]) outMins[0] = vBounds[i][0];
if(vBounds[i][1] < outMins[1]) outMins[1] = vBounds[i][1];
if(vBounds[i][2] < outMins[2]) outMins[2] = vBounds[i][2];
// Maxs
if(vBounds[i][0] > outMaxs[0]) outMaxs[0] = vBounds[i][0];
if(vBounds[i][1] > outMaxs[1]) outMaxs[1] = vBounds[i][1];
if(vBounds[i][2] > outMaxs[2]) outMaxs[2] = vBounds[i][2];
}
VectorAdd(outMins, m_pCurrentEntity->origin, outMins);
VectorAdd(outMaxs, m_pCurrentEntity->origin, outMaxs);
}
We also need to add the function declaration in the header file, so open up the header for this file, and in the class declaration of CStudioModelRenderer, after the following bit:
virtual void StudioProcessGait ( entity_state_t *pplayer );
Add this:
// Sets bounding box
virtual void StudioGetMinsMaxs ( vec3_t& outMins, vec3_t& outMaxs );
We'll add fog culling to the view culling bit of the renderer in the cpp file. Look up two instances of the following code:
// see if the bounding box lets us trivially reject, also sets
if (!IEngineStudio.StudioCheckBBox ())
return 0;
And after each of these, add the following:
vec3_t mins, maxs;
StudioGetMinsMaxs(mins, maxs);
if (gFog.CullFogBBox(mins, maxs))
return 0;
This is mainly useful to cull out temporary entities like bullet shells, gibs, pieces of metal fragments and wood chunks, etc, because the packet fog culling on the server side should already take care of this for the map entities.#include "fog.h"
In the HUD_DrawNormalTriangles function, add the following call:
gFog.HUD_DrawNormalTriangles();
And in the HUD_DrawTransparentTriangles call, add this:
gFog.HUD_DrawTransparentTriangles();
Open up view.cpp, and add this include at the top:
#include "fog.h"
Look up the V_CalcRefdef function, and at the bottom of the function add this call:
gFog.V_CalcRefDef(pparams);
As a last thing, in your solution explorer, you need to right click on the cl_dll project and select Properties. There, go into Linker->Input, and in "Additional Dependencies", add "opengl32.lib". You need to do this for both your Release and Debug builds.You must log in to post a comment. You can login or register a new account.
cdll_int.h(38,13): error C2040: 'HSPRITE': 'int' differs in levels of indirection from 'HSPRITE__ *'
Any ideas as to what is causing that?
Probably replacing all instances of vec_3t with Vector and DotProduct with FDotProduct should do the trick
Hold on, it does. I'm blind
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 replaceglEnable(GL_FOG)
with something likeglUniform1i(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
withlayout(location = 0) uniform bool fogEnabled
. Now go back into your code and replace everyglEnable(GL_FOG)
withglUniform1i(0, (int)true)
and obviously replace everyglDisable(GL_FOG)
withglUniform1i(0, (int)false)
.(And yes, I don't know how to reply lol)
Suggested change, in
CBasePlayer::Precache
function, addm_bSendMessages
here: