SV_Invisible_To_Client (Anti-Wallhack)
Moderator: InsideQC Admins
45 posts
• Page 1 of 3 • 1, 2, 3
SV_Invisible_To_Client (Anti-Wallhack)
Quake had always had a weakness in that it was vulnerable to "stick models", textures being removed in maps, external cheater opengl32.dll libraries that allow walls to be toggled don't draw walls (etc.)
DarkPlaces add a sv_cullentities_trace feature to screen out entities that can't be see so Nexuiz would run faster and this had the side effect of being a strong cheat-protection measure.
Last year, this functionality was added to FTEQW and later in the year I ported the FTEQW version to ProQuake server.
Later Rook, would test and perfect a streamlined version that condensed it down to a single function.
Adding the Code:
The code was written by Rook.
It is very easily to implement. It is a single function with 1 supporting cvar we need to register and 1 instance where our new function gets called.
And it's all in sv_main.c!
1. sv_main.c
DarkPlaces add a sv_cullentities_trace feature to screen out entities that can't be see so Nexuiz would run faster and this had the side effect of being a strong cheat-protection measure.
Last year, this functionality was added to FTEQW and later in the year I ported the FTEQW version to ProQuake server.
Later Rook, would test and perfect a streamlined version that condensed it down to a single function.
Summary: Anti-Wallhack
Using SV_Invisible_To_Client, you can screen out entities that a client shouldn't be able to see and have the server simply not send them.
Then you don't have to worry much about cheaters with wallhacks or altered player models: there is no model to draw because the client is never sent data for what they cannot see.
Other Benefits
This reduces the amount of data sent to the client by the server.
In single player, this visibility screening can speed up rendering and increase frames per second.
The downside is that doing this check for 300-600 entities for every frame is a little CPU intensive. To deal with this, there are 2 options:
1. sv_cullentities 1 - only checks the player entities. So it is only checking at most 16 entities per frame.
2. sv_cullentities 2 - check everything
Culling Weakness
If I recall correctly, this function has a very minor weakness in that if ONLY the middle of a model is visible and not extremities, the function falsely returns that the entity can't be seen.
Not typically a problem except for lifts, platforms and other entities that are large. As a reult, it the function skips those entities.
Adding the Code:
The code was written by Rook.
It is very easily to implement. It is a single function with 1 supporting cvar we need to register and 1 instance where our new function gets called.
And it's all in sv_main.c!
1. sv_main.c
a. Let's create the sv_cullentities cvar; we will be default it to 1 (players only screen).
At the top of sv_main.c, add the yellow text:char localmodels[MAX_MODELS][5]; // inline model names for precache
cvar_t sv_cullentities = {"sv_cullentities","1", false, true};
//============================================================================
b. We need to register the cvar. Go 5 or 6 lines downad to SV_Init.c and add the yellow text.Cvar_RegisterVariable (&sv_cull_entities);
Cvar_RegisterVariable (&sv_maxvelocity)
Cvar_RegisterVariable (&sv_gravity);
Cvar_RegisterVariable (&sv_friction);
Cvar_RegisterVariable (&sv_edgefriction);
Cvar_RegisterVariable (&sv_stopspeed);
c. Now we need to add our new function, find this code:
- Code: Select all
/*
=============
SV_WriteEntitiesToClient
=============
*/
And above it paste our new function:
- Code: Select all
extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);
qboolean SV_InvisibleToClient(edict_t *viewer, edict_t *seen)
{
int i;
trace_t tr;
vec3_t start;
vec3_t end;
if (seen->v.movetype == MOVETYPE_PUSH )//dont cull doors and plats :(
{
return false;
}
if (sv_cullentities.value == 1) //1 only check player models, 2 = check all ents
if (strcmp(pr_strings + seen->v.classname, "player"))
return false;
memset (&tr, 0, sizeof(tr));
tr.fraction = 1;
start[0] = viewer->v.origin[0];
start[1] = viewer->v.origin[1];
start[2] = viewer->v.origin[2] + viewer->v.view_ofs[2];
//aim straight at the center of "seen" from our eyes
end[0] = 0.5 * (seen->v.mins[0] + seen->v.maxs[0]);
end[1] = 0.5 * (seen->v.mins[1] + seen->v.maxs[1]);
end[2] = 0.5 * (seen->v.mins[2] + seen->v.maxs[2]);
tr = SV_ClipMoveToEntity (sv.edicts, start, vec3_origin, vec3_origin, end);
if (tr.fraction == 1)// line hit the ent
return false;
//last attempt to eliminate any flaws...
if ((!strcmp(pr_strings + seen->v.classname, "player")) || (sv_cullentities.value > 1))
{
for (i = 0; i < 64; i++)
{
end[0] = seen->v.origin[0] + offsetrandom(seen->v.mins[0], seen->v.maxs[0]);
end[1] = seen->v.origin[1] + offsetrandom(seen->v.mins[1], seen->v.maxs[1]);
end[2] = seen->v.origin[2] + offsetrandom(seen->v.mins[2], seen->v.maxs[2]);
tr = SV_ClipMoveToEntity (sv.edicts, start, vec3_origin, vec3_origin, end);
if (tr.fraction == 1)// line hit the ent
{
Con_DPrintf (va("found ent in %i hits\n", i));
return false;
}
}
}
return true;
}
4. Finally we need to make sure this screening process gets called:
Go to SV_WriteEntitiesToClient and find this code and add the yellow text:if (!ent->v.modelindex || !pr_strings[ent->v.model])
continue;
for (i=0 ; i < ent->num_leafs ; i++)
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) ))
break;
if (i == ent->num_leafs)
continue; // not visible
if (sv_cullentities.value && SV_InvisibleToClient(clent,ent))
continue; // Baker: bail out and skip to next entity because we are not going to send data on this one
Last edited by Baker on Mon Dec 08, 2008 12:09 am, edited 3 times in total.
-
Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Can you add a entity view distance?
For example you can see only entites max 1000 units away from
your player model
i need something like that for singleplayer.
and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);
For example you can see only entites max 1000 units away from
your player model
i need something like that for singleplayer.
and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);
- Stealth Kill
- Posts: 83
- Joined: Fri Dec 29, 2006 12:34 pm
Stealth Kill wrote:Can you add a entity view distance?
For example you can see only entites max 1000 units away from
your player model
i need something like that for singleplayer.
and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);
Just modify this so if the distance between the player and the entity is >= 1000, it returns the same as if they were behind an object. Easy

-
Downsider - Posts: 621
- Joined: Tue Sep 16, 2008 1:35 am
Re: SV_Invisible_To_Client (Anti-Wallhack)
Hmmm. This looks like it needs improvement really:
It should go like this:
(Due to C's short-circuit evaluation, if sv_cullentities has a non-zero value the second part, SV_InvisibleToClient, doesn't even get called and it really shouldn't get called unless it has a non-zero value. And I don't see anything in SV_InvisibleToClient that returns early is sv_cull_entities is 0.)
if ((SV_InvisibleToClient(clent,ent)) && (sv_cullentities.value))
continue;
It should go like this:
if (sv_cullentities.value && SV_InvisibleToClient(clent, ent))
continue;
(Due to C's short-circuit evaluation, if sv_cullentities has a non-zero value the second part, SV_InvisibleToClient, doesn't even get called and it really shouldn't get called unless it has a non-zero value. And I don't see anything in SV_InvisibleToClient that returns early is sv_cull_entities is 0.)
-
Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Stealth Kill wrote:Can you add a entity view distance?
I haven't played around with calculating distances yet, but it probably goes something like this:
If ( fabs ( Length(viewer->v.origin, seen->v.origin)) > 1000.0 )
continue;
And would go before this:
if (sv_cullentities.value && SV_InvisibleToClient(clent, ent))
continue;
I haven't mess around with that kind of stuff and am not looking at the source a the moment, so I don't know if that code works "as-is" or needs slight touching.
-
Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
You meant to say?
- Code: Select all
VectorSubtract(clent->v.origin, ent->v.origin, edist);
if (VectorLength(edist) > 1024)
continue;
if (sv_cullentities.value)
{
if (SV_InvisibleToClient(clent,ent))
continue;
}
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
r00k wrote:You meant to say?
- Code: Select all
VectorSubtract(clent->v.origin, ent->v.origin, edist);
if (VectorLength(edist) > 1024)
continue;
if (sv_cullentities.value)
{
if (SV_InvisibleToClient(clent,ent))
continue;
}
^^^ is better than me

I'm still learning.
-
Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
r00k wrote:WEll, I meant the distance check is in SV_WriteEntitiesToClient not in
SV_InvisibleToClient.
Hehe, I should have only quoted the relevant part. If I didn't know recognize the equivalence of the latter part, I'd be in a whole world of trouble

/The dangers of self-deprecating humor
-
Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
You forgot to define offsetrandom.
Also using 64 traces is exceedingly slow - DarkPlaces uses only a few and simply renews a timer in the edict_t whenever one hits the entity, as long as the timer is in the future it shows the entity, the timer has to be set low (like 0.2) to prevent wallhacks from being useful, but it's much lower cpu usage.
I should also mention that I got the idea from Unreal1, a developer note mentioned the technique, and that the lag/flicker problems of it are lost in the noise of multiplayer gameplay, it looks like some kind of network lag rather than a major annoyance.
UnrealEngine3 however just uses the single trace to the center, and modders have complained about how much trouble this causes, it seems UE3 does one thing worse than UE1
Also using 64 traces is exceedingly slow - DarkPlaces uses only a few and simply renews a timer in the edict_t whenever one hits the entity, as long as the timer is in the future it shows the entity, the timer has to be set low (like 0.2) to prevent wallhacks from being useful, but it's much lower cpu usage.
I should also mention that I got the idea from Unreal1, a developer note mentioned the technique, and that the lag/flicker problems of it are lost in the noise of multiplayer gameplay, it looks like some kind of network lag rather than a major annoyance.
UnrealEngine3 however just uses the single trace to the center, and modders have complained about how much trouble this causes, it seems UE3 does one thing worse than UE1

- LordHavoc
- Posts: 322
- Joined: Fri Nov 05, 2004 3:12 am
- Location: western Oregon, USA
- Code: Select all
#define offsetrandom(MIN,MAX) (((double)(rand() + 0.5) / ((double)RAND_MAX + 1)) * ((MAX)-(MIN)) + (MIN))
The equivalent to DarkPlaces lhrandom courtesy of LordHavoc.
So about the 64 traces being slow.
I would set it to lets say 32?
- Team Xlink
- Posts: 368
- Joined: Thu Jun 25, 2009 4:45 am
- Location: Michigan
Downsider wrote:It shouldn't make a dent on a decent dedicated server.A
You haven't run helm18.bsp (10000 knights)

Also you probably haven't played dpmod with the cvar spawnmonsters set to 2000 (and multiple players)

Note the cost goes up with player count exponentially (each player is producing more entities to cull, and each player runs culling code on all of them).
- LordHavoc
- Posts: 322
- Joined: Fri Nov 05, 2004 3:12 am
- Location: western Oregon, USA
45 posts
• Page 1 of 3 • 1, 2, 3
Return to Programming Tutorials
Who is online
Users browsing this forum: No registered users and 1 guest