//========= Copyright 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; }