Raised This Month: $32 Target: $400
 8% 

[TUT]Properly detecting shield hit


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
HamletEagle
AMX Mod X Plugin Approver
Join Date: Sep 2013
Location: Romania
Old 07-24-2017 , 07:03   [TUT]Properly detecting shield hit
Reply With Quote #1

I'm going to show how to properly detect a shield hit and also explain why I've chosen this method and why other methods won't work or may produce false positive results.
If you are not interested on how things work then skip to the end of the tutorial where the full code snippet will be.

Okay, so the first thought would be to hook Ham_TraceAttack and retrieve the TR_iHitgroup member from the trace, then checking for HITGROUP_SHIELD( 8 ). In theory it's nice and easy, but if you try that soon you'll see that it's not going to work, at least not for bullets. And if we check the code we'll get even more confused, because there's a check for shield(if (bHitShield)).

If we pick a random gun, let's say ak47 and we look at it's code we can see the order of calls for firing bullets:
  1. PrimaryAttack is called when the player press the left click button(IN_ATTACK) were some accuracy checks are done.
  2. A weapon fire function(AK47Fire in our case) is called from PrimaryAttack.
  3. Now things get interesting, from the above function game calls FireBullets3 and PLAYBACK_EVENT_FULL.

If we look at FireBullets3 we see this code:
PHP Code:
            if (tr.iHitgroup == HITGROUP_SHIELD)
            {
                
EMIT_SOUND(pEntity->edict(), CHAN_VOICE, (RANDOM_LONG(01) == 1) ? "weapons/ric_metal-1.wav" "weapons/ric_metal-2.wav"VOL_NORMATTN_NORM);
                
UTIL_Sparks(tr.vecEndPos);

                
pEntity->pev->punchangle.iCurrentDamage RANDOM_FLOAT(-0.150.15);
                
pEntity->pev->punchangle.iCurrentDamage RANDOM_FLOAT(-0.150.15);

                if (
pEntity->pev->punchangle.4)
                {
                    
pEntity->pev->punchangle.= -4;
                }

                if (
pEntity->pev->punchangle.< -5)
                {
                    
pEntity->pev->punchangle.= -5;
                }
                else if (
pEntity->pev->punchangle.5)
                {
                    
pEntity->pev->punchangle.5;
                }

                break;
            } 
This tells us that if the trace hits a shield then game stops right there and it never reaches the trace attack call: pEntity->TraceAttack(pevAttacker, iCurrentDamage, vecDir, &tr, (DMG_BULLET | DMG_NEVERGIB));.

It means that for bullets hitting a shield TraceAttack is not going to be called. As a workaround we could try to use few methods:
  • Hooking PrimaryAttack and doing manually a trace
  • Hooking pfnTraceLine and checking if it hits a shield
  • Hooking pfnPlayBackEvent and doing manually a trace

The first method is not good because if the player holds m1 then the forward will be called. PrimaryAttack does not get called on each actual shot, but on any shot attemp. It's called from ItemPostFrame, where the button check is done.

The second method is even worse, you could simply aim at the shield and not shoot it and it will detect a hit.

The only reliable method is the 3rd one, because pfnPlayBackEvent is called on actual shots(remember that it's called from WeaponFire(in our case AK47Fire). From here we'll do a traceline and check what we hit.

Now that we have a reliable way of detecting when a bullet hits a shield we need a way to know when a shotgun/knife hits the shield. Let's look how knife attack works: PrimaryAttack -> Swing. In CKnife::Swing we see that it calls TraceAttack, so for knife hits TraceAttack will work. This also explains why TraceAttack has a check for shield, when apparently it's not called when a shield is hit.
Shotguns use FireBullets function so TraceAttack is going to work for them.

As a summary:
CBaseEntity:FireBullets3 is called for weapons that fire bullets and that function does not call CBasePlayer::TraceAttack when a bullet hits a shield, it stops before that. Initially, I wanted to hook FireBullets3, but since it has Vector params we can't do that with orpheu/okapi, it will crash.
CKnife::Swing calls TraceAttack.

The code will be something like:
PHP Code:
#include <amxmodx>
#include <hamsandwich>
#include <fun>
#include <fakemeta>
#include <xs>

new const EventNames[][] = 
{
    
"events/p228.sc",
    
"events/scout.sc",
    
"events/mac10.sc",
    
"events/aug.sc",
    
"events/elite_right.sc",
    
"events/elite_left.sc",
    
"events/fiveseven.sc",
    
"events/ump45.sc",
    
"events/sg550.sc",
    
"events/galil.sc",
    
"events/famas.sc",
    
"events/usp.sc",
    
"events/glock18.sc",
    
"events/awp.sc",
    
"events/mp5n.sc",
    
"events/m249.sc",
    
"events/m4a1.sc",
    
"events/tmp.sc",
    
"events/g3sg1.sc",
    
"events/deagle.sc",
    
"events/sg552.sc",
    
"events/ak47.sc",
    
"events/p90.sc"
}

const 
HITGROUP_SHIELD 8
new ShotEventBitSum

public plugin_init()
{
    for(new 
0sizeof EventNamesi++)
    {
        
ShotEventBitSum |= 1<< engfunc(EngFunc_PrecacheEvent1EventNames[i])
    }

    
register_forward(FM_PlaybackEvent"pfnPlaybackEvent")
    
RegisterHam(Ham_TraceAttack"player""CBasePlayer_TraceAttack")
}

public 
pfnPlaybackEvent(Flagsidm_usFireEventFloat:DelayFloat:Origin[3], Float:Angles[3], Float:DirectionXFloat:DirectionYPunchAngleXPunchAngleYbool:Dummy1bool:Dummy2)
{
    if(
is_user_connected(id))
    {
        if(
ShotEventBitSum & (<< m_usFireEvent))
        {
            new 
Float:PlayerOrigin[3], Float:PlayerViewOffset[3]
            
pev(idpev_originPlayerOrigin)
            
pev(idpev_view_ofsPlayerViewOffset)
            
xs_vec_add(PlayerOriginPlayerViewOffsetPlayerOrigin)
            
            new 
Float:PlayerViewAngle[3], Float:EndPosition[3]
            
pev(idpev_v_anglePlayerViewAngle)
            
engfunc(EngFunc_MakeVectorsPlayerViewAngle)
            
global_get(glb_v_forwardPlayerViewAngle)
            
            
xs_vec_mul_scalar(PlayerViewAngle8192.0EndPosition)
            
xs_vec_add(PlayerOriginEndPositionEndPosition)
            
            
engfunc(EngFunc_TraceLinePlayerOriginEndPositionDONT_IGNORE_MONSTERSid0)
            if(
get_tr2(0,TR_iHitgroup) == HITGROUP_SHIELD)
            {
                
PlayerShootShield(id)
            }
        }
    }
}  

public 
CBasePlayer_TraceAttack(victimattackerFloat:DamageFloat:Direction[3], TraceHandleDamageBits)
{
    if(
is_user_connected(attacker))
    {
        if(
get_tr2(TraceHandleTR_iHitgroup) == HITGROUP_SHIELD)
        {
            
PlayerShootShield(attacker)
        }
    }
}

PlayerShootShield(id)
{
    
//player "id" shoot a shield

From my test this method is accurate and does not produce false positives.
__________________

Last edited by HamletEagle; 07-24-2017 at 11:33.
HamletEagle is offline
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 18:59.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode