[How to] Detect if player is visible.

For scripting examples for learning how to use AMXX.
User avatar
Archive-Poster
Posts: 1189
Gender:
United States of America

[How to] Detect if player is visible.

Post by Archive-Poster »

Before reading.
This tutorial is made specially for scripters that know how to work with vectors.
I will try to explain the code as much as possible so that even users that know a little bit about them will be able to understand!
This "how to" was made specially for people that want to implement this in their plugins.
Note: This is a very efficient method, the only problem is that there are rare situations when you don't see a player. But that will not be a problem.
Note: This tutorial applies to CS but can be done in other mods! All that you need to do is just change the constants to your fitting!

Code and description:

Firstly we need some constants:
First we need the defines of some constants that will come in handy!
These constants are used as multiplication constants. We use them to create some arrays that will be used when calculating if a player is visible!
These are the ones that need to be modified if you aren't satisfied by the results that you are getting!

Code: Select all

#define GENERAL_X_Y_SORROUNDING             18.5         // 16.0
#define CONSTANT_Z_CROUCH_UP                 31.25         // 32.0
#define CONSTANT_Z_CROUCH_DOWN                 17.5         // 16.0
#define CONSTANT_Z_STANDUP_UP                 34.0         // 36.0
#define CONSTANT_Z_STANDUP_DOWN             35.25         // 36.0

#define GENERAL_X_Y_SORROUNDING_HALF        9.25         // 8.0
#define GENERAL_X_Y_SORROUNDING_HALF2        12.0         // 8.0
#define CONSTANT_Z_CROUCH_UP_HALF             15.5         // 16.0
#define CONSTANT_Z_CROUCH_DOWN_HALF            8.75         // 8.0
#define CONSTANT_Z_STANDUP_UP_HALF            17.0         // 18.0
#define CONSTANT_Z_STANDUP_DOWN_HALF        17.5         // 18.0

#define ANGLE_COS_HEIGHT_CHECK                0.7071        // cos(45  degrees) 
Now we need some arrays that offer the plugin the order of the calculations.

Code: Select all

// This is used to determine the weapon head point
new const Float:weapon_edge_point[CSW_P90+1] =
{
    0.00, 35.5, 0.00, 42.0, 0.00, 35.5, 0.00, 37.0, 37.0, 0.00, 35.5, 35.5, 32.0, 41.0, 32.0, 36.0, 41.0, 35.5, 41.0, 32.0, 37.0, 35.5, 42.0, 41.0, 44.0, 0.00, 35.5, 37.0, 32.0, 0.00, 32.0
}

// This is used as lateral multiplication array
new const Float:vec_multi_lateral[] = 
{
    GENERAL_X_Y_SORROUNDING,
    -GENERAL_X_Y_SORROUNDING,
    GENERAL_X_Y_SORROUNDING_HALF2,
    -GENERAL_X_Y_SORROUNDING_HALF
}

// This is used as height multiplicator if the user crouches!
new const Float:vec_add_height_crouch[] =
{
    CONSTANT_Z_CROUCH_UP,
    -CONSTANT_Z_CROUCH_DOWN,
    CONSTANT_Z_CROUCH_UP_HALF,
    -CONSTANT_Z_CROUCH_DOWN_HALF
}

// This is used as height multiplicator if the user stands up!
new const Float:vec_add_height_standup[] =
{
    CONSTANT_Z_STANDUP_UP,
    -CONSTANT_Z_STANDUP_DOWN,
    CONSTANT_Z_STANDUP_UP_HALF,
    -CONSTANT_Z_STANDUP_DOWN_HALF
} 
Secondly we need some variables:
A trace handle to save the fraction results (for point visibility)
This is needed because we don't want the plugin to interfere with other plugins results and vice versa!

Code: Select all

new thld

public plugin_init()
{
    // We create it in plugin_init
    thdl = create_tr2()
}

public plugin_end()
{
    // We free it on plugin end
    free_tr2(thdl)
} 
We also need a list where we can save the entity types if our entities are transparent or not!
This part is done using in this case bitsums, for extra information about them read this!
This is needed when our plugin checks for player visibility offen!

Code: Select all

// These bitsums allow 2048 entities storage. I think that it is enough :P.
new bs_array_transp[64]                // BitSum, This is equal to 64*32 bools (good for quick search)
new bs_array_solid[64]                 // BitSum, This is equal to 64*32 bools (good for quick search)


To handle those bitsums easily I have made some macro functions that will make the code readable!

Code: Select all

#define add_transparent_ent(%1)         bs_array_transp[((%1 - 1) / 32)] |= (1<<((%1 - 1) % 32))
#define del_transparent_ent(%1)         bs_array_transp[((%1 - 1) / 32)] &= ~(1<<((%1 - 1) % 32))
#define  is_transparent_ent(%1)        (bs_array_transp[((%1 - 1) / 32)] & (1<<((%1 - 1) % 32)))
#define add_solid_ent(%1)               bs_array_solid[((%1 - 1) / 32)] |= (1<<((%1 - 1) % 32))
#define del_solid_ent(%1)               bs_array_solid[((%1 - 1) / 32)] &= ~(1<<((%1 - 1) % 32))
#define  is_solid_ent(%1)              (bs_array_solid[((%1 - 1) / 32)] & (1<<((%1 - 1) % 32))) 
Thirdly we need some useful functions, that will be used in our tests:
The trace functions

Code: Select all

// This function checks if a point is visible from start to point! It will ignore glass and players! Also it will ignore the ignore_ent!
bool:is_point_visible(const Float:start[3], const Float:point[3], ignore_ent)
{
    engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)

    static Float:fraction
    get_tr2(thdl, TR_flFraction, fraction)
    
    // We will return the fraction
    // If 1.0 that means that we didn't hit anything
    return (fraction == 1.0)
}

// This function is the same as above but will also check if the entity that we hited can be seen through
bool:is_point_visible_texture(const Float:start[3], const Float:point[3], ignore_ent)
{
    engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
    
    // We save also the entity
    static ent
    ent = get_tr2(thdl, TR_pHit)

    static Float:fraction
    get_tr2(thdl, TR_flFraction, fraction)
    
    // If we hit something start the checks
    if (fraction != 1.0 && ent > 0)
    {
        // That means that we didn't know what we hited
        if (!is_transparent_ent(ent) && !is_solid_ent(ent))
        {
            static texture_name[2]
            static Float:vec[3]
            // These calculations are made for security measures
            // TraceTexture function will crash the server if used on short distances
            xs_vec_sub(point, start, vec)
            xs_vec_mul_scalar(vec, (5000.0 / xs_vec_len(vec)), vec)
            xs_vec_add(start, vec, vec)
            
            engfunc(EngFunc_TraceTexture, ent, start, vec, texture_name, charsmax(texture_name))
            
            // If texture_name begins with { that means that we have a trasnaparent texture, 
            // if yes we will retrace and add that entity to our list as a transparent! 
            // If not then we add id as solid so it will not be needed to do the checks again!
            if (equal(texture_name, "{"))
            {
                add_transparent_ent(ent)
                ignore_ent = ent
                engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
                get_tr2(thdl, TR_flFraction, fraction)
                return (fraction == 1.0)
            }
            else
            {
                add_solid_ent(ent)
                return (fraction == 1.0)
            }
        }
        // This means that the entity is registered as solid or transparent so on with the checks
        else
        {
            if (is_solid_ent(ent))
            {
                return (fraction == 1.0)
            }
            else
            {
                ignore_ent = ent
                engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
                get_tr2(thdl, TR_flFraction, fraction)
                return (fraction == 1.0)
            }
        }
    }
    
    return (fraction == 1.0)
} 
Vector functions [taken from xs.inc]

Code: Select all

// This will add two vectors
stock xs_vec_add(const Float:in1[], const Float:in2[], Float:out[])
{
    out[0] = in1[0] + in2[0];
    out[1] = in1[1] + in2[1];
    out[2] = in1[2] + in2[2];
}

// This will substract two vectors
stock xs_vec_sub(const Float:in1[], const Float:in2[], Float:out[])
{
    out[0] = in1[0] - in2[0];
    out[1] = in1[1] - in2[1];
    out[2] = in1[2] - in2[2];
}

// This will multiply a vector with a scalar
stock xs_vec_mul_scalar(const Float:vec[], Float:scalar, Float:out[])
{
    out[0] = vec[0] * scalar;
    out[1] = vec[1] * scalar;
    out[2] = vec[2] * scalar;
}

// This will return the vector length
stock Float:xs_vec_len(const Float:vec[3])
{
    return floatsqroot(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
}

// This will make a scalar multiplication between two vectors
stock Float:xs_vec_dot(const Float:vec[], const Float:vec2[])
{
    return (vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2])
}

// This will check whether two vectors are equal
bool:xs_vec_equal(const Float:vec1[], const Float:vec2[])
{
    return (vec1[0] == vec2[0]) && (vec1[1] == vec2[1]) && (vec1[2] == vec2[2]);
} 
Fourthly we need to add some entities that we already know that can be seen through this can be done using this:

Code: Select all

public plugin_precache()
{
    register_forward(FM_Spawn, "fw_spawn", 1)
}

public fw_spawn(ent)
{
    if (!pev_valid(ent))
        return FMRES_IGNORED
    
    static rendermode, Float:renderamt
    
    rendermode = pev(ent, pev_rendermode)
    pev(ent, pev_renderamt, renderamt)
    
    // If transparent then add it to the list!
    if (((rendermode == kRenderTransColor || rendermode == kRenderGlow || rendermode == kRenderTransTexture) && renderamt < 255.0) || (rendermode == kRenderTransAdd))
    {
        add_transparent_ent(ent)
        return FMRES_IGNORED
    }
    
    return FMRES_IGNORED
} 
Now having all the constants, variables and functions we can get on creating the stock that will show us whether a player is visible:

Code: Select all

stock is_player_visible(visor, target)
{
    // Declarations, bla bla
    static Float:origin[3], Float:start[3], Float:end[3], Float:addict[3], Float:plane_vec[3], Float:normal[3], ignore_ent
    
    // The ignore entity is our visor at first (if needed we will change it!
    // This will be used in the point tests!
    ignore_ent = visor
    
    // First of all we check if the player is behind the visor or not
    // We get the visor origin
    pev(visor, pev_origin, origin)
    
    // We get the view angles of the visor
    pev(visor, pev_v_angle, normal)
    // We turn the angle vector into a direction vector
    angle_vector(normal, ANGLEVECTOR_FORWARD, normal)
    
    // We get the targets origin, this will be used to deremine if it the player is behind us
    pev(target, pev_origin, end)
    // We substract the two vectors to obtain a directional vector!
    xs_vec_sub(end, origin, plane_vec)
    // We normalize the vector so that we will be able to obtain the real angle between the vectors
    xs_vec_mul_scalar(plane_vec,  (1.0/xs_vec_len(plane_vec)), plane_vec)
    
    // When we multiply two vectors that are normalized (their length is 1)
    // We will obtain the cosinus of the angle between them, if this is negative then that means that the player is behind, so we return false!
    if (xs_vec_dot(plane_vec, normal) < 0)
    {
        return false
    }
    
    // We get the view offsets and we add them to the origin to obtain the eye origin
    pev(visor, pev_view_ofs, start)
    xs_vec_add(start, origin, start)
    
    // Origin becomes end
    origin = end
    
    // Until now we have 2 important vectors
    // start - that is the eye origin of the visor
    // origin - that is the target origin (usefull later)
    
    // This is used only once to update the ignore_ent
    // If point is visible then guess what, returning true!
    if (is_point_visible_texture(start, origin, ignore_ent))
        return true
    
    // This will get the view offsets of the target, we will add them to obtain the eye origin
    pev(target, pev_view_ofs, end)
    xs_vec_add(end, origin, end)
    
    // If eye origin is visible return true!
    if (is_point_visible(start, end, ignore_ent))
        return true
    
    // Check weapon point, first we need to check if it is no equal to 0
    if (weapon_edge_point[get_user_weapon(target)] != 0.00)
    {
        // We get the view angles and turn them in directional vectors
        pev(target, pev_v_angle, addict)
        angle_vector(addict, ANGLEVECTOR_FORWARD, addict)
        // We multiply it to obtain the weapon headpoint
        xs_vec_mul_scalar(addict, weapon_edge_point[get_user_weapon(target)], addict)
        // We add it to the end origin to obtain the weapon headpoint
        xs_vec_add(end, addict, end)
        
        // If weapon head is visible true!
        if (is_point_visible(start, end, ignore_ent))
            return true
    }
    
    // We subtract them to obtain o directional vector that will be used in plane calculations!
    xs_vec_sub(start, origin, normal)
    
    // We have now an extra important vector
    // normal - a directional vector between the start vector and the target origin
    
    // This is the moment when we move on to plane checks
    // These functions will create a plane that will rotate based on player position
    // The checks will be done on points that will exist on this plane!
    
    // First of all we normalize the normal vector
    // This will be important when we have height problems.
    xs_vec_mul_scalar(normal, 1.0/(xs_vec_len(normal)), normal)
    // We turn the vector into an angle vector
    vector_to_angle(normal, plane_vec)
    // This will create a vector that will be orthogonal/perpendicular with the normal vector!
    // This must be done to easily create the plane!
    angle_vector(plane_vec, ANGLEVECTOR_RIGHT, plane_vec)
    
    // This will check if we are almost at the same level with the target
    if (floatabs(normal[2]) <= ANGLE_COS_HEIGHT_CHECK)
    {
        // We check whether the target crouches
        // This is important to determine what multiplicator vector to use!
        // This will create the plane
        if (pev(target, pev_flags) & FL_DUCKING)
        {
            for (new i=0;i<4;i++)
            {
                if (i<2)
                {
                    for (new j=0;j<2;j++)
                    {
                        // We multiply the directional vector and add it to the origin after that we check if it is visible
                        // The same happens in the other places so this one will be the only one that will be commented!
                        xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                        addict[2] = vec_add_height_crouch[j]
                        xs_vec_add(origin, addict, end)
                        
                        if (is_point_visible(start, end, ignore_ent))
                            return true
                    }
                }
                else
                {
                    for (new j=2;j<4;j++)
                    {
                        xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                        addict[2] = vec_add_height_crouch[j]
                        xs_vec_add(origin, addict, end)
                        
                        if (is_point_visible(start, end, ignore_ent))
                            return true
                    }
                }
            }
        }
        else
        {
            for (new i=0;i<4;i++)
            {
                if (i<2)
                {
                    for (new j=0;j<2;j++)
                    {
                        xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                        addict[2] = vec_add_height_standup[j]
                        xs_vec_add(origin, addict, end)
                        
                        if (is_point_visible(start, end, ignore_ent))
                            return true
                    }
                }
                else
                {
                    for (new j=2;j<4;j++)
                    {
                        xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                        addict[2] = vec_add_height_standup[j]
                        xs_vec_add(origin, addict, end)
                        
                        if (is_point_visible(start, end, ignore_ent))
                            return true
                    }
                }
            }
        }
    }
    else
    {
        // This is the same as the one above
        // The only difference is that it also uses normal vector to move the points in front and in behind!
        // Here is checked if you are above the player
        if (normal[2] > 0.0)
        {
            normal[2] = 0.0
            xs_vec_mul_scalar(normal, 1/(xs_vec_len(normal)), normal)
        
            if (pev(target, pev_flags) & FL_DUCKING)
            {
                for (new i=0;i<4;i++)
                {
                    if (i<2)
                    {
                        for (new j=0;j<2;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_crouch[j]
                            xs_vec_add(origin, addict, end)
                            xs_vec_mul_scalar(normal, (j == 0) ? (-GENERAL_X_Y_SORROUNDING) : (GENERAL_X_Y_SORROUNDING), addict)
                            xs_vec_add(end, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                    else
                    {
                        for (new j=2;j<4;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_crouch[j]
                            xs_vec_add(origin, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                }
            }
            else
            {
                for (new i=0;i<4;i++)
                {
                    if (i<2)
                    {
                        for (new j=0;j<2;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_standup[j]
                            xs_vec_add(origin, addict, end)
                            xs_vec_mul_scalar(normal, (j == 0) ? (-GENERAL_X_Y_SORROUNDING) : (GENERAL_X_Y_SORROUNDING), addict)
                            xs_vec_add(end, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                    else
                    {
                        for (new j=2;j<4;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_standup[j]
                            xs_vec_add(origin, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                }
            }
        }
        else
        {
            normal[2] = 0.0
            xs_vec_mul_scalar(normal, 1/(xs_vec_len(normal)), normal)
            
            if (pev(target, pev_flags) & FL_DUCKING)
            {
                for (new i=0;i<4;i++)
                {
                    if (i<2)
                    {
                        for (new j=0;j<2;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_crouch[j]
                            xs_vec_add(origin, addict, end)
                            xs_vec_mul_scalar(normal, (j == 0) ? GENERAL_X_Y_SORROUNDING : (-GENERAL_X_Y_SORROUNDING), addict)
                            xs_vec_add(end, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                    else
                    {
                        for (new j=2;j<4;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_crouch[j]
                            xs_vec_add(origin, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                }
            }
            else
            {
                for (new i=0;i<4;i++)
                {
                    if (i<2)
                    {
                        for (new j=0;j<2;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_standup[j]
                            xs_vec_add(origin, addict, end)
                            xs_vec_mul_scalar(normal, (j == 0) ? GENERAL_X_Y_SORROUNDING : (-GENERAL_X_Y_SORROUNDING), addict)
                            xs_vec_add(end, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                    else
                    {
                        for (new j=2;j<4;j++)
                        {
                            xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
                            addict[2] = vec_add_height_standup[j]
                            xs_vec_add(origin, addict, end)
                            
                            if (is_point_visible(start, end, ignore_ent))
                                return true
                        }
                    }
                }
            }
        }
    }
    
    // None visible? 
    return false
} 
How to see the points that are tested?
Well you just need to do add this to the code in the plugin.

Code: Select all

stock spr_bomb

public plugin_precache()
{
    spr_bomb = precache_model("sprites/ledglow.spr")
}

stock bomb_led(const Float:point[3])
{
    message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
    write_byte(TE_GLOWSPRITE)
    engfunc(EngFunc_WriteCoord, point[0])
    engfunc(EngFunc_WriteCoord, point[1])
    engfunc(EngFunc_WriteCoord, point[2])
    write_short(spr_bomb)
    write_byte(1)
    write_byte(3)
    write_byte(255)
    message_end()
} 
And change the functions "is_point_visible" and "is_point_visible_texture" into this:

Code: Select all

bool:is_point_visible(const Float:start[3], const Float:point[3], ignore_ent)
{
    bomb_led(point)
    return false
}

bool:is_point_visible_texture(const Float:start[3], const Float:point[3], ignore_ent)
{
    bomb_led(point)
    return false
}

Who is online

Users browsing this forum: No registered users and 0 guests