//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #include "basehlcombatweapon.h" #include "player.h" #include "gamerules.h" #include "ammodef.h" #include "mathlib/mathlib.h" #include "in_buttons.h" #include "soundent.h" #include "animation.h" #include "ai_condition.h" #include "basebludgeonweapon.h" #include "ndebugoverlay.h" #include "te_effect_dispatch.h" #include "rumble_shared.h" #include "gamestats.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" IMPLEMENT_SERVERCLASS_ST( CBaseHLBludgeonWeapon, DT_BaseHLBludgeonWeapon ) END_SEND_TABLE() #define BLUDGEON_HULL_DIM 16 static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM); static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM); //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CBaseHLBludgeonWeapon::CBaseHLBludgeonWeapon() { m_bFiresUnderwater = true; } //----------------------------------------------------------------------------- // Purpose: Spawn the weapon //----------------------------------------------------------------------------- void CBaseHLBludgeonWeapon::Spawn( void ) { m_fMinRange1 = 0; m_fMinRange2 = 0; m_fMaxRange1 = 64; m_fMaxRange2 = 64; //Call base class first BaseClass::Spawn(); } //----------------------------------------------------------------------------- // Purpose: Precache the weapon //----------------------------------------------------------------------------- void CBaseHLBludgeonWeapon::Precache( void ) { //Call base class first BaseClass::Precache(); } int CBaseHLBludgeonWeapon::CapabilitiesGet() { return bits_CAP_WEAPON_MELEE_ATTACK1; } int CBaseHLBludgeonWeapon::WeaponMeleeAttack1Condition( float flDot, float flDist ) { if (flDist > 64) { return COND_TOO_FAR_TO_ATTACK; } else if (flDot < 0.7) { return COND_NOT_FACING_ATTACK; } return COND_CAN_MELEE_ATTACK1; } //------------------------------------------------------------------------------ // Purpose : Update weapon //------------------------------------------------------------------------------ void CBaseHLBludgeonWeapon::ItemPostFrame( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) ) { PrimaryAttack(); } else if ( (pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime) ) { SecondaryAttack(); } else { WeaponIdle(); return; } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CBaseHLBludgeonWeapon::PrimaryAttack() { Swing( false ); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CBaseHLBludgeonWeapon::SecondaryAttack() { Swing( true ); } //------------------------------------------------------------------------------ // Purpose: Implement impact function //------------------------------------------------------------------------------ void CBaseHLBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity, bool bIsSecondary ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); //Do view kick AddViewKick(); //Make sound for the AI CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, traceHit.endpos, 400, 0.2f, pPlayer ); // This isn't great, but it's something for when the crowbar hits. pPlayer->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART ); CBaseEntity *pHitEntity = traceHit.m_pEnt; //Apply damage to a hit target if ( pHitEntity != NULL ) { Vector hitDirection; pPlayer->EyeVectors( &hitDirection, NULL, NULL ); VectorNormalize( hitDirection ); CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); if( pPlayer && pHitEntity->IsNPC() ) { // If bonking an NPC, adjust damage. info.AdjustPlayerDamageInflictedForSkillLevel(); } CalculateMeleeDamageForce( &info, hitDirection, traceHit.endpos ); pHitEntity->DispatchTraceAttack( info, hitDirection, &traceHit ); ApplyMultiDamage(); // Now hit all triggers along the ray that... TraceAttackToTriggers( info, traceHit.startpos, traceHit.endpos, hitDirection ); if ( ToBaseCombatCharacter( pHitEntity ) ) { gamestats->Event_WeaponHit( pPlayer, !bIsSecondary, GetClassname(), info ); } } // Apply an impact effect ImpactEffect( traceHit ); } Activity CBaseHLBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ) { int i, j, k; float distance; const float *minmaxs[2] = {mins.Base(), maxs.Base()}; trace_t tmpTrace; Vector vecHullEnd = hitTrace.endpos; Vector vecEnd; distance = 1e6f; Vector vecSrc = hitTrace.startpos; vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace ); if ( tmpTrace.fraction == 1.0 ) { for ( i = 0; i < 2; i++ ) { for ( j = 0; j < 2; j++ ) { for ( k = 0; k < 2; k++ ) { vecEnd.x = vecHullEnd.x + minmaxs[i][0]; vecEnd.y = vecHullEnd.y + minmaxs[j][1]; vecEnd.z = vecHullEnd.z + minmaxs[k][2]; UTIL_TraceLine( vecSrc, vecEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace ); if ( tmpTrace.fraction < 1.0 ) { float thisDistance = (tmpTrace.endpos - vecSrc).Length(); if ( thisDistance < distance ) { hitTrace = tmpTrace; distance = thisDistance; } } } } } } else { hitTrace = tmpTrace; } return ACT_VM_HITCENTER; } //----------------------------------------------------------------------------- // Purpose: // Input : &traceHit - //----------------------------------------------------------------------------- bool CBaseHLBludgeonWeapon::ImpactWater( const Vector &start, const Vector &end ) { //FIXME: This doesn't handle the case of trying to splash while being underwater, but that's not going to look good // right now anyway... // We must start outside the water if ( UTIL_PointContents( start ) & (CONTENTS_WATER|CONTENTS_SLIME)) return false; // We must end inside of water if ( !(UTIL_PointContents( end ) & (CONTENTS_WATER|CONTENTS_SLIME))) return false; trace_t waterTrace; UTIL_TraceLine( start, end, (CONTENTS_WATER|CONTENTS_SLIME), GetOwner(), COLLISION_GROUP_NONE, &waterTrace ); if ( waterTrace.fraction < 1.0f ) { CEffectData data; data.m_fFlags = 0; data.m_vOrigin = waterTrace.endpos; data.m_vNormal = waterTrace.plane.normal; data.m_flScale = 8.0f; // See if we hit slime if ( waterTrace.contents & CONTENTS_SLIME ) { data.m_fFlags |= FX_WATER_IN_SLIME; } DispatchEffect( "watersplash", data ); } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHLBludgeonWeapon::ImpactEffect( trace_t &traceHit ) { // See if we hit water (we don't do the other impact effects in this case) if ( ImpactWater( traceHit.startpos, traceHit.endpos ) ) return; //FIXME: need new decals UTIL_ImpactTrace( &traceHit, DMG_CLUB ); } //------------------------------------------------------------------------------ // Purpose : Starts the swing of the weapon and determines the animation // Input : bIsSecondary - is this a secondary attack? //------------------------------------------------------------------------------ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) { trace_t traceHit; // Try a ray CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( !pOwner ) return; pOwner->RumbleEffect( RUMBLE_CROWBAR_SWING, 0, RUMBLE_FLAG_RESTART ); Vector swingStart = pOwner->Weapon_ShootPosition( ); Vector forward; forward = pOwner->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT, GetRange() ); Vector swingEnd = swingStart + forward * GetRange(); UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); Activity nHitActivity = ACT_VM_HITCENTER; // Like bullets, bludgeon traces have to trace against triggers. CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); triggerInfo.SetDamagePosition( traceHit.startpos ); triggerInfo.SetDamageForce( forward ); TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, forward ); if ( traceHit.fraction == 1.0 ) { float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point // Back off by hull "radius" swingEnd -= forward * bludgeonHullRadius; UTIL_TraceHull( swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); if ( traceHit.fraction < 1.0 && traceHit.m_pEnt ) { Vector vecToTarget = traceHit.m_pEnt->GetAbsOrigin() - swingStart; VectorNormalize( vecToTarget ); float dot = vecToTarget.Dot( forward ); // YWB: Make sure they are sort of facing the guy at least... if ( dot < 0.70721f ) { // Force amiss traceHit.fraction = 1.0f; } else { nHitActivity = ChooseIntersectionPointAndActivity( traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner ); } } } if ( !bIsSecondary ) { m_iPrimaryAttacks++; } else { m_iSecondaryAttacks++; } gamestats->Event_WeaponFired( pOwner, !bIsSecondary, GetClassname() ); // ------------------------- // Miss // ------------------------- if ( traceHit.fraction == 1.0f ) { nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; // We want to test the first swing again Vector testEnd = swingStart + forward * GetRange(); // See if we happened to hit water ImpactWater( swingStart, testEnd ); } else { Hit( traceHit, nHitActivity, bIsSecondary ? true : false ); } // Send the anim SendWeaponAnim( nHitActivity ); //Setup our next attack times m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate(); m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); //Play swing sound WeaponSound( SINGLE ); }