SV_Invisible_To_Client (Anti-Wallhack)

Post tutorials on how to do certain tasks within game or engine code here.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

SV_Invisible_To_Client (Anti-Wallhack)

Post by Baker »

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.
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 >> 3] & (1 << (ent->leafnums&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.
Stealth Kill
Posts: 83
Joined: Fri Dec 29, 2006 12:34 pm

Post by Stealth Kill »

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);
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

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 :o.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);
Didn't catch that, heh. Fixed. Thanks!
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: SV_Invisible_To_Client (Anti-Wallhack)

Post by Baker »

Hmmm. This looks like it needs improvement really:

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

Post by Baker »

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.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

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


Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

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 :D

I'm still learning.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

WEll, I meant the distance check is in SV_WriteEntitiesToClient not in
SV_InvisibleToClient.

;)
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

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 :D

/The dangers of self-deprecating humor
LordHavoc
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA
Contact:

Post by LordHavoc »

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 :)
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

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?
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

It shouldn't make a dent on a decent dedicated server.A
LordHavoc
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA
Contact:

Post by LordHavoc »

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).
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

lol well I was talking more in context to what XLink needs for his project, but no, I did not think of that xD
Post Reply