//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Crossbow // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "npcevent.h" #include "hl1mp_basecombatweapon_shared.h" //#include "basecombatcharacter.h" //#include "AI_BaseNPC.h" #ifdef CLIENT_DLL #include "c_baseplayer.h" #else #include "player.h" #endif //#include "player.h" #include "gamerules.h" #include "in_buttons.h" #ifdef CLIENT_DLL #else #include "soundent.h" #include "entitylist.h" #include "game.h" #endif #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "IEffects.h" #ifdef CLIENT_DLL #include "c_te_effect_dispatch.h" #else #include "te_effect_dispatch.h" #endif #define BOLT_MODEL "models/crossbow_bolt.mdl" #define BOLT_AIR_VELOCITY 2000 #define BOLT_WATER_VELOCITY 1000 extern ConVar sk_plr_dmg_xbow_bolt_plr; extern ConVar sk_plr_dmg_xbow_bolt_npc; extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the fireball #if !defined(CLIENT_DLL) //----------------------------------------------------------------------------- // Crossbow Bolt //----------------------------------------------------------------------------- class CCrossbowBolt : public CBaseCombatCharacter { DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter ); public: CCrossbowBolt() { m_bExplode = true; } Class_T Classify( void ) { return CLASS_NONE; } public: void Spawn( void ); void Precache( void ); void BubbleThink( void ); void BoltTouch( CBaseEntity *pOther ); void ExplodeThink( void ); void SetExplode( bool bVal ) { m_bExplode = bVal; } bool CreateVPhysics( void ); unsigned int PhysicsSolidMaskForEntity() const; static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner = NULL ); DECLARE_DATADESC(); private: bool m_bExplode; }; LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt ); BEGIN_DATADESC( CCrossbowBolt ) // Function Pointers DEFINE_FUNCTION( BubbleThink ), DEFINE_FUNCTION( BoltTouch ), DEFINE_FUNCTION( ExplodeThink ), END_DATADESC() CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner ) { // Create a new entity with CCrossbowBolt private data CCrossbowBolt *pBolt = (CCrossbowBolt *)CreateEntityByName( "crossbow_bolt" ); UTIL_SetOrigin( pBolt, vecOrigin ); pBolt->SetAbsAngles( angAngles ); pBolt->Spawn(); pBolt->SetOwnerEntity( pentOwner ); return pBolt; } void CCrossbowBolt::Spawn( ) { Precache( ); SetModel( BOLT_MODEL ); UTIL_SetSize( this, -Vector(1, 1, 1), Vector(1, 1, 1) ); SetSolid( SOLID_BBOX ); SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); SetGravity( UTIL_ScaleForGravity( 40 ) ); // use a really low gravity (40 in/s^2) SetTouch( &CCrossbowBolt::BoltTouch ); SetThink( &CCrossbowBolt::BubbleThink ); SetNextThink( gpGlobals->curtime + 0.2 ); m_bExplode = true; } void CCrossbowBolt::Precache( ) { PrecacheModel( BOLT_MODEL ); PrecacheScriptSound( "BaseGrenade.Explode" ); } void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) { if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) ) return; SetTouch( NULL ); SetThink( NULL ); if ( pOther->m_takedamage != DAMAGE_NO ) { trace_t tr, tr2; tr = BaseClass::GetTouchTrace( ); Vector vecNormalizedVel = GetAbsVelocity(); ClearMultiDamage( ); VectorNormalize( vecNormalizedVel ); if ( pOther->IsPlayer() ) { float m_fDamage = sk_plr_dmg_xbow_bolt_plr.GetFloat() * g_pGameRules->GetDamageMultiplier(); // If multiplayer sniper bolt, multiply damage by 4 if ( g_pGameRules->IsMultiplayer() && !m_bExplode ) m_fDamage *= 4; CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_fDamage,DMG_NEVERGIB ); CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos ); pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); } else { CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_xbow_bolt_npc.GetFloat() * g_pGameRules->GetDamageMultiplier(), DMG_BULLET | DMG_NEVERGIB ); CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos ); pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); } ApplyMultiDamage(); SetAbsVelocity( Vector( 0, 0, 0 ) ); // play body "thwack" sound EmitSound( "Weapon_Crossbow.BoltHitBody" ); Vector vForward; AngleVectors( GetAbsAngles(), &vForward ); VectorNormalize ( vForward ); UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_OPAQUE, pOther, COLLISION_GROUP_NONE, &tr2 ); if ( tr2.fraction != 1.0f ) { // NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 ); // NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 ); CEffectData data; data.m_vOrigin = tr2.endpos; data.m_vNormal = vForward; data.m_nEntIndex = tr2.fraction != 1.0f; DispatchEffect( "BoltImpact", data ); } if ( !g_pGameRules->IsMultiplayer() || !m_bExplode ) { UTIL_Remove( this ); } } else { EmitSound( "Weapon_Crossbow.BoltHitWorld" ); SetThink( &CCrossbowBolt::SUB_Remove ); SetNextThink( gpGlobals->curtime );// this will get changed below if the bolt is allowed to stick in what it hit. if ( m_bExplode == false ) { Vector vForward; AngleVectors( GetAbsAngles(), &vForward ); VectorNormalize ( vForward ); CEffectData data; data.m_vOrigin = GetAbsOrigin(); data.m_vNormal = vForward; data.m_nEntIndex = 0; DispatchEffect( "BoltImpact", data ); } if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER) { g_pEffects->Sparks( GetAbsOrigin() ); } } // Set up an explosion in one tenth of a second if ( g_pGameRules->IsMultiplayer() && m_bExplode ) { SetThink( &CCrossbowBolt::ExplodeThink ); SetNextThink( gpGlobals->curtime + 0.1f ); } } void CCrossbowBolt::ExplodeThink( void ) { // int iContents = UTIL_PointContents( pev->origin ); CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_xbow_bolt_npc.GetFloat() * g_pGameRules->GetDamageMultiplier(), DMG_BLAST ); ::RadiusDamage( dmgInfo, GetAbsOrigin(), 128, CLASS_NONE, NULL ); #if !defined( CLIENT_DLL ) CPASFilter filter( GetAbsOrigin() ); te->Explosion( filter, /* filter */ 0.0, /* delay */ &GetAbsOrigin(), /* pos */ g_sModelIndexFireball, /* modelindex */ 0.2, /* scale */ 25, /* framerate */ TE_EXPLFLAG_NONE, /* flags */ 128, /* radius */ 64, /* magnitude */ NULL, /* normal */ 'C' ); /* materialType */ //CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 ); EmitSound( "BaseGrenade.Explode" ); #endif SetThink( &CCrossbowBolt::SUB_Remove ); SetNextThink( gpGlobals->curtime ); AddEffects( EF_NODRAW ); } void CCrossbowBolt::BubbleThink( void ) { QAngle angNewAngles; VectorAngles( GetAbsVelocity(), angNewAngles ); SetAbsAngles( angNewAngles ); SetNextThink( gpGlobals->curtime + 0.1f ); if ( GetWaterLevel() == 0 ) return; UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1, GetAbsOrigin(), 1 ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CCrossbowBolt::CreateVPhysics( void ) { // Create the object in the physics system VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false ); return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- unsigned int CCrossbowBolt::PhysicsSolidMaskForEntity() const { return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE; } #endif // crossbowbolt //----------------------------------------------------------------------------- // CWeaponCrossbow //----------------------------------------------------------------------------- #ifdef CLIENT_DLL #define CWeaponCrossbow C_WeaponCrossbow #endif class CWeaponCrossbow : public CBaseHL1MPCombatWeapon { DECLARE_CLASS( CWeaponCrossbow, CBaseHL1MPCombatWeapon ); public: DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); CWeaponCrossbow( void ); void Precache( void ); void PrimaryAttack( void ); void SecondaryAttack( void ); bool Reload( void ); void WeaponIdle( void ); bool Deploy( void ); bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); // DECLARE_SERVERCLASS(); DECLARE_DATADESC(); #ifndef CLIENT_DLL // DECLARE_ACTTABLE(); #endif private: void FireBolt( void ); void ToggleZoom( void ); private: // bool m_fInZoom; CNetworkVar( bool, m_fInZoom ); }; IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrossbow, DT_WeaponCrossbow ); BEGIN_NETWORK_TABLE( CWeaponCrossbow, DT_WeaponCrossbow ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_fInZoom ) ), #else SendPropBool( SENDINFO( m_fInZoom ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CWeaponCrossbow ) #ifdef CLIENT_DLL DEFINE_PRED_FIELD( m_fInZoom, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), #endif END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( weapon_crossbow, CWeaponCrossbow ); PRECACHE_WEAPON_REGISTER( weapon_crossbow ); //IMPLEMENT_SERVERCLASS_ST( CWeaponCrossbow, DT_WeaponCrossbow ) //END_SEND_TABLE() BEGIN_DATADESC( CWeaponCrossbow ) END_DATADESC() #if 0 #ifndef CLIENT_DLL acttable_t CWeaponCrossbow::m_acttable[] = { { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_CROSSBOW, false }, // { ACT_HL2MP_RUN, ACT_HL2MP_RUN_CROSSBOW, false }, // { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_CROSSBOW, false }, // { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_CROSSBOW, false }, // { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW, false }, // { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_CROSSBOW, false }, // { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_CROSSBOW, false }, }; IMPLEMENT_ACTTABLE(CWeaponCrossbow); #endif #endif //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CWeaponCrossbow::CWeaponCrossbow( void ) { m_bReloadsSingly = false; m_bFiresUnderwater = true; m_fInZoom = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponCrossbow::Precache( void ) { #ifndef CLIENT_DLL UTIL_PrecacheOther( "crossbow_bolt" ); #endif PrecacheScriptSound( "Weapon_Crossbow.BoltHitBody" ); PrecacheScriptSound( "Weapon_Crossbow.BoltHitWorld" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponCrossbow::PrimaryAttack( void ) { if ( m_fInZoom && g_pGameRules->IsMultiplayer() ) { // FireSniperBolt(); FireBolt(); } else { FireBolt(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponCrossbow::SecondaryAttack( void ) { ToggleZoom(); m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; } void CWeaponCrossbow::FireBolt( void ) { if ( m_iClip1 <= 0 ) { if ( !m_bFireOnEmpty ) { Reload(); } else { WeaponSound( EMPTY ); m_flNextPrimaryAttack = 0.15; } return; } CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; Vector vecAiming = pOwner->GetAutoaimVector( AUTOAIM_2DEGREES ); Vector vecSrc = pOwner->Weapon_ShootPosition(); QAngle angAiming; VectorAngles( vecAiming, angAiming ); #ifndef CLIENT_DLL CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, pOwner ); // In multiplayer, secondary fire is instantaneous. if ( g_pGameRules->IsMultiplayer() && m_fInZoom ) { Vector vecEnd = vecSrc + ( vecAiming * MAX_TRACE_LENGTH ); trace_t trace; UTIL_TraceLine( vecSrc, vecEnd, MASK_SHOT, GetOwner(), COLLISION_GROUP_NONE, &trace ); pBolt->SetAbsOrigin( trace.endpos ); // We hit someone if ( trace.m_pEnt && trace.m_pEnt->m_takedamage ) { pBolt->SetExplode( false ); pBolt->BoltTouch( trace.m_pEnt ); return; } } else { if ( pOwner->GetWaterLevel() == 3 ) { pBolt->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY ); } else { pBolt->SetAbsVelocity( vecAiming * BOLT_AIR_VELOCITY ); } } pBolt->SetLocalAngularVelocity( QAngle( 0, 0, 10 ) ); if ( m_fInZoom || !g_pGameRules->IsMultiplayer() ) { pBolt->SetExplode( false ); } #endif m_iClip1--; pOwner->ViewPunch( QAngle( -2, 0, 0 ) ); WeaponSound( SINGLE ); WeaponSound( RELOAD ); #ifdef CLIENT_DLL #else CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 ); #endif if ( m_iClip1 > 0 ) { SendWeaponAnim( ACT_VM_PRIMARYATTACK ); } else if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) > 0 ) { SendWeaponAnim( ACT_VM_SECONDARYATTACK ); } if ( !m_iClip1 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) { // HEV suit - indicate out of ammo condition pOwner->SetSuitUpdate("!HEV_AMO0", FALSE, 0); } m_flNextPrimaryAttack = gpGlobals->curtime + 0.75; m_flNextSecondaryAttack = gpGlobals->curtime + 0.75; if ( m_iClip1 > 0 ) { SetWeaponIdleTime( gpGlobals->curtime + 5.0 ); } else { SetWeaponIdleTime( gpGlobals->curtime + 0.75 ); } } bool CWeaponCrossbow::Reload( void ) { bool fRet; fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { if ( m_fInZoom ) { ToggleZoom(); } WeaponSound( RELOAD ); } return fRet; } void CWeaponCrossbow::WeaponIdle( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( pPlayer ) { pPlayer->GetAutoaimVector( AUTOAIM_2DEGREES ); } if ( !HasWeaponIdleTimeElapsed() ) return; int iAnim; float flRand = random->RandomFloat( 0, 1 ); if ( flRand <= 0.75 ) { if ( m_iClip1 <= 0 ) iAnim = ACT_CROSSBOW_IDLE_UNLOADED; else iAnim = ACT_VM_IDLE; } else { if ( m_iClip1 <= 0 ) iAnim = ACT_CROSSBOW_FIDGET_UNLOADED; else iAnim = ACT_VM_FIDGET; } SendWeaponAnim( iAnim ); } bool CWeaponCrossbow::Deploy( void ) { if ( m_iClip1 <= 0 ) { return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() ); } return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_DRAW, (char*)GetAnimPrefix() ); } bool CWeaponCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo ) { if ( m_fInZoom ) { ToggleZoom(); } return BaseClass::Holster( pSwitchingTo ); } void CWeaponCrossbow::ToggleZoom( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( !pPlayer ) { return; } #if !defined(CLIENT_DLL) if ( m_fInZoom ) { if ( pPlayer->SetFOV( this, 0 ) ) { m_fInZoom = false; } } else { if ( pPlayer->SetFOV( this, 20 ) ) { m_fInZoom = true; } } #endif }