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:
- PrimaryAttack is called when the player press the left click button(IN_ATTACK) were some accuracy checks are done.
- A weapon fire function(AK47Fire in our case) is called from PrimaryAttack.
- 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(0, 1) == 1) ? "weapons/ric_metal-1.wav" : "weapons/ric_metal-2.wav", VOL_NORM, ATTN_NORM);
UTIL_Sparks(tr.vecEndPos);
pEntity->pev->punchangle.x = iCurrentDamage * RANDOM_FLOAT(-0.15, 0.15);
pEntity->pev->punchangle.z = iCurrentDamage * RANDOM_FLOAT(-0.15, 0.15);
if (pEntity->pev->punchangle.x < 4)
{
pEntity->pev->punchangle.x = -4;
}
if (pEntity->pev->punchangle.z < -5)
{
pEntity->pev->punchangle.z = -5;
}
else if (pEntity->pev->punchangle.z > 5)
{
pEntity->pev->punchangle.z = 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 i = 0; i < sizeof EventNames; i++)
{
ShotEventBitSum |= 1<< engfunc(EngFunc_PrecacheEvent, 1, EventNames[i])
}
register_forward(FM_PlaybackEvent, "pfnPlaybackEvent")
RegisterHam(Ham_TraceAttack, "player", "CBasePlayer_TraceAttack")
}
public pfnPlaybackEvent(Flags, id, m_usFireEvent, Float:Delay, Float:Origin[3], Float:Angles[3], Float:DirectionX, Float:DirectionY, PunchAngleX, PunchAngleY, bool:Dummy1, bool:Dummy2)
{
if(is_user_connected(id))
{
if(ShotEventBitSum & (1 << m_usFireEvent))
{
new Float:PlayerOrigin[3], Float:PlayerViewOffset[3]
pev(id, pev_origin, PlayerOrigin)
pev(id, pev_view_ofs, PlayerViewOffset)
xs_vec_add(PlayerOrigin, PlayerViewOffset, PlayerOrigin)
new Float:PlayerViewAngle[3], Float:EndPosition[3]
pev(id, pev_v_angle, PlayerViewAngle)
engfunc(EngFunc_MakeVectors, PlayerViewAngle)
global_get(glb_v_forward, PlayerViewAngle)
xs_vec_mul_scalar(PlayerViewAngle, 8192.0, EndPosition)
xs_vec_add(PlayerOrigin, EndPosition, EndPosition)
engfunc(EngFunc_TraceLine, PlayerOrigin, EndPosition, DONT_IGNORE_MONSTERS, id, 0)
if(get_tr2(0,TR_iHitgroup) == HITGROUP_SHIELD)
{
PlayerShootShield(id)
}
}
}
}
public CBasePlayer_TraceAttack(victim, attacker, Float:Damage, Float:Direction[3], TraceHandle, DamageBits)
{
if(is_user_connected(attacker))
{
if(get_tr2(TraceHandle, TR_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.
__________________