//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Stun Stick- beating stick with a zappy end // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "npcevent.h" #include "npc_metropolice.h" #include "weapon_stunstick.h" #include "IEffects.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar sk_plr_dmg_stunstick("sk_plr_dmg_stunstick", "0"); ConVar sk_npc_dmg_stunstick("sk_npc_dmg_stunstick", "0"); extern ConVar metropolice_move_and_melee; //----------------------------------------------------------------------------- // CWeaponStunStick //----------------------------------------------------------------------------- IMPLEMENT_SERVERCLASS_ST(CWeaponStunStick, DT_WeaponStunStick) SendPropInt(SENDINFO(m_bActive), 1, SPROP_UNSIGNED), END_SEND_TABLE() //#ifndef HL2MP LINK_ENTITY_TO_CLASS(weapon_stunstick, CWeaponStunStick); PRECACHE_WEAPON_REGISTER(weapon_stunstick); //#endif acttable_t CWeaponStunStick::m_acttable[] = { { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, true }, }; IMPLEMENT_ACTTABLE(CWeaponStunStick); BEGIN_DATADESC(CWeaponStunStick) DEFINE_FIELD(m_bActive, FIELD_BOOLEAN), END_DATADESC() //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CWeaponStunStick::CWeaponStunStick(void) { // HACK: Don't call SetStunState because this tried to Emit a sound before // any players are connected which is a bug m_bActive = false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CWeaponStunStick::Spawn() { Precache(); BaseClass::Spawn(); AddSolidFlags(FSOLID_NOT_STANDABLE); } void CWeaponStunStick::Precache() { BaseClass::Precache(); PrecacheScriptSound("Weapon_StunStick.Activate"); PrecacheScriptSound("Weapon_StunStick.Deactivate"); } //----------------------------------------------------------------------------- // Purpose: Get the damage amount for the animation we're doing // Input : hitActivity - currently played activity // Output : Damage amount //----------------------------------------------------------------------------- float CWeaponStunStick::GetDamageForActivity(Activity hitActivity) { if ((GetOwner() != NULL) && (GetOwner()->IsPlayer())) return sk_plr_dmg_stunstick.GetFloat(); return sk_npc_dmg_stunstick.GetFloat(); } //----------------------------------------------------------------------------- // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) //----------------------------------------------------------------------------- extern ConVar sk_crowbar_lead_time; int CWeaponStunStick::WeaponMeleeAttack1Condition(float flDot, float flDist) { // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); CBaseEntity *pEnemy = pNPC->GetEnemy(); if (!pEnemy) return COND_NONE; Vector vecVelocity; AngularImpulse angVelocity; pEnemy->GetVelocity(&vecVelocity, &angVelocity); // Project where the enemy will be in a little while, add some randomness so he doesn't always hit float dt = sk_crowbar_lead_time.GetFloat(); dt += random->RandomFloat(-0.3f, 0.2f); if (dt < 0.0f) dt = 0.0f; Vector vecExtrapolatedPos; VectorMA(pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos); Vector vecDelta; VectorSubtract(vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta); if (fabs(vecDelta.z) > 70) { return COND_TOO_FAR_TO_ATTACK; } Vector vecForward = pNPC->BodyDirection2D(); vecDelta.z = 0.0f; float flExtrapolatedDot = DotProduct2D(vecDelta.AsVector2D(), vecForward.AsVector2D()); if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) { return COND_NOT_FACING_ATTACK; } float flExtrapolatedDist = Vector2DNormalize(vecDelta.AsVector2D()); if (pEnemy->IsPlayer()) { //Vector vecDir = pEnemy->GetSmoothedVelocity(); //float flSpeed = VectorNormalize( vecDir ); // If player will be in front of me in one-half second, clock his arse. Vector vecProjectEnemy = pEnemy->GetAbsOrigin() + (pEnemy->GetAbsVelocity() * 0.35); Vector vecProjectMe = GetAbsOrigin(); if ((vecProjectMe - vecProjectEnemy).Length2D() <= 48.0f) { return COND_CAN_MELEE_ATTACK1; } } /* if( metropolice_move_and_melee.GetBool() ) { if( pNPC->IsMoving() ) { flTargetDist *= 1.5f; } } */ float flTargetDist = 48.0f; if ((flDist > flTargetDist) && (flExtrapolatedDist > flTargetDist)) { return COND_TOO_FAR_TO_ATTACK; } return COND_CAN_MELEE_ATTACK1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponStunStick::ImpactEffect(trace_t &traceHit) { //Glowing spark effect for hit //UTIL_DecalTrace( &m_trLineHit, "PlasmaGlowFade" ); //FIXME: need new decals UTIL_ImpactTrace(&traceHit, DMG_CLUB); } void CWeaponStunStick::Operator_HandleAnimEvent(animevent_t *pEvent, CBaseCombatCharacter *pOperator) { switch (pEvent->event) { case EVENT_WEAPON_MELEE_HIT: { // Trace up or down based on where the enemy is... // But only if we're basically facing that direction Vector vecDirection; AngleVectors(GetAbsAngles(), &vecDirection); CBaseEntity *pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; if (pEnemy) { Vector vecDelta; VectorSubtract(pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta); VectorNormalize(vecDelta); Vector2D vecDelta2D = vecDelta.AsVector2D(); Vector2DNormalize(vecDelta2D); if (DotProduct2D(vecDelta2D, vecDirection.AsVector2D()) > 0.8f) { vecDirection = vecDelta; } } Vector vecEnd; VectorMA(pOperator->Weapon_ShootPosition(), 32, vecDirection, vecEnd); // Stretch the swing box down to catch low level physics objects CBaseEntity *pHurt = pOperator->CheckTraceHullAttack(pOperator->Weapon_ShootPosition(), vecEnd, Vector(-16, -16, -40), Vector(16, 16, 16), GetDamageForActivity(GetActivity()), DMG_CLUB, 0.5f, false); // did I hit someone? if (pHurt) { // play sound WeaponSound(MELEE_HIT); CBasePlayer *pPlayer = ToBasePlayer(pHurt); CNPC_MetroPolice *pCop = dynamic_cast(pOperator); bool bFlashed = false; if (pCop != NULL && pPlayer != NULL) { // See if we need to knock out this target if (pCop->ShouldKnockOutTarget(pHurt)) { float yawKick = random->RandomFloat(-48, -24); //Kick the player angles pPlayer->ViewPunch(QAngle(-16, yawKick, 2)); color32 white = { 255, 255, 255, 255 }; UTIL_ScreenFade(pPlayer, white, 0.2f, 1.0f, FFADE_OUT | FFADE_PURGE | FFADE_STAYOUT); bFlashed = true; pCop->KnockOutTarget(pHurt); break; } else { // Notify that we've stunned a target pCop->StunnedTarget(pHurt); } } // Punch angles if (pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE)) { float yawKick = random->RandomFloat(-48, -24); //Kick the player angles pPlayer->ViewPunch(QAngle(-16, yawKick, 2)); Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); // If the player's on my head, don't knock him up if (pPlayer->GetGroundEntity() == pOperator) { dir = vecDirection; dir.z = 0; } VectorNormalize(dir); dir *= 500.0f; //If not on ground, then don't make them fly! if (!(pPlayer->GetFlags() & FL_ONGROUND)) dir.z = 0.0f; //Push the target back pHurt->ApplyAbsVelocityImpulse(dir); if (!bFlashed) { color32 red = { 128, 0, 0, 128 }; UTIL_ScreenFade(pPlayer, red, 0.5f, 0.1f, FFADE_IN); } // Force the player to drop anyting they were holding pPlayer->ForceDropOfCarriedPhysObjects(); } // do effect? } else { WeaponSound(MELEE_MISS); } } break; default: BaseClass::Operator_HandleAnimEvent(pEvent, pOperator); break; } } //----------------------------------------------------------------------------- // Purpose: Sets the state of the stun stick //----------------------------------------------------------------------------- void CWeaponStunStick::SetStunState(bool state) { m_bActive = state; if (m_bActive) { //FIXME: START - Move to client-side Vector vecAttachment; GetAttachment(1, vecAttachment); g_pEffects->Sparks(vecAttachment); //FIXME: END - Move to client-side EmitSound("Weapon_StunStick.Activate"); } else { EmitSound("Weapon_StunStick.Deactivate"); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponStunStick::Deploy(void) { SetStunState(true); return BaseClass::Deploy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CWeaponStunStick::Holster(CBaseCombatWeapon *pSwitchingTo) { if (BaseClass::Holster(pSwitchingTo) == false) return false; SetStunState(false); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : &vecVelocity - //----------------------------------------------------------------------------- void CWeaponStunStick::Drop(const Vector &vecVelocity) { SetStunState(false); BaseClass::Drop(vecVelocity); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponStunStick::GetStunState(void) { return m_bActive; }