aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/weapon_crossbow.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/weapon_crossbow.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/weapon_crossbow.cpp')
-rw-r--r--mp/src/game/server/hl2/weapon_crossbow.cpp1964
1 files changed, 982 insertions, 982 deletions
diff --git a/mp/src/game/server/hl2/weapon_crossbow.cpp b/mp/src/game/server/hl2/weapon_crossbow.cpp
index 0ed6910c..badd50d8 100644
--- a/mp/src/game/server/hl2/weapon_crossbow.cpp
+++ b/mp/src/game/server/hl2/weapon_crossbow.cpp
@@ -1,982 +1,982 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "npcevent.h"
-#include "basehlcombatweapon_shared.h"
-#include "basecombatcharacter.h"
-#include "ai_basenpc.h"
-#include "player.h"
-#include "gamerules.h"
-#include "in_buttons.h"
-#include "soundent.h"
-#include "game.h"
-#include "vstdlib/random.h"
-#include "engine/IEngineSound.h"
-#include "IEffects.h"
-#include "te_effect_dispatch.h"
-#include "Sprite.h"
-#include "SpriteTrail.h"
-#include "beam_shared.h"
-#include "rumble_shared.h"
-#include "gamestats.h"
-#include "decals.h"
-
-#ifdef PORTAL
- #include "portal_util_shared.h"
-#endif
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//#define BOLT_MODEL "models/crossbow_bolt.mdl"
-#define BOLT_MODEL "models/weapons/w_missile_closed.mdl"
-
-#define BOLT_AIR_VELOCITY 2500
-#define BOLT_WATER_VELOCITY 1500
-
-extern ConVar sk_plr_dmg_crossbow;
-extern ConVar sk_npc_dmg_crossbow;
-
-void TE_StickyBolt( IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin );
-
-#define BOLT_SKIN_NORMAL 0
-#define BOLT_SKIN_GLOW 1
-
-//-----------------------------------------------------------------------------
-// Crossbow Bolt
-//-----------------------------------------------------------------------------
-class CCrossbowBolt : public CBaseCombatCharacter
-{
- DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter );
-
-public:
- CCrossbowBolt() { };
- ~CCrossbowBolt();
-
- Class_T Classify( void ) { return CLASS_NONE; }
-
-public:
- void Spawn( void );
- void Precache( void );
- void BubbleThink( void );
- void BoltTouch( CBaseEntity *pOther );
- bool CreateVPhysics( void );
- unsigned int PhysicsSolidMaskForEntity() const;
- static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner = NULL );
-
-protected:
-
- bool CreateSprites( void );
-
- CHandle<CSprite> m_pGlowSprite;
- //CHandle<CSpriteTrail> m_pGlowTrail;
-
- DECLARE_DATADESC();
- DECLARE_SERVERCLASS();
-};
-LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt );
-
-BEGIN_DATADESC( CCrossbowBolt )
- // Function Pointers
- DEFINE_FUNCTION( BubbleThink ),
- DEFINE_FUNCTION( BoltTouch ),
-
- // These are recreated on reload, they don't need storage
- DEFINE_FIELD( m_pGlowSprite, FIELD_EHANDLE ),
- //DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ),
-
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CCrossbowBolt, DT_CrossbowBolt )
-END_SEND_TABLE()
-
-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;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CCrossbowBolt::~CCrossbowBolt( void )
-{
- if ( m_pGlowSprite )
- {
- UTIL_Remove( m_pGlowSprite );
- }
-}
-
-//-----------------------------------------------------------------------------
-// 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;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CCrossbowBolt::CreateSprites( void )
-{
- // Start up the eye glow
- m_pGlowSprite = CSprite::SpriteCreate( "sprites/light_glow02_noz.vmt", GetLocalOrigin(), false );
-
- if ( m_pGlowSprite != NULL )
- {
- m_pGlowSprite->FollowEntity( this );
- m_pGlowSprite->SetTransparency( kRenderGlow, 255, 255, 255, 128, kRenderFxNoDissipation );
- m_pGlowSprite->SetScale( 0.2f );
- m_pGlowSprite->TurnOff();
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CCrossbowBolt::Spawn( void )
-{
- Precache( );
-
- SetModel( "models/crossbow_bolt.mdl" );
- SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
- UTIL_SetSize( this, -Vector(0.3f,0.3f,0.3f), Vector(0.3f,0.3f,0.3f) );
- SetSolid( SOLID_BBOX );
- SetGravity( 0.05f );
-
- // Make sure we're updated if we're underwater
- UpdateWaterState();
-
- SetTouch( &CCrossbowBolt::BoltTouch );
-
- SetThink( &CCrossbowBolt::BubbleThink );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- CreateSprites();
-
- // Make us glow until we've hit the wall
- m_nSkin = BOLT_SKIN_GLOW;
-}
-
-
-void CCrossbowBolt::Precache( void )
-{
- PrecacheModel( BOLT_MODEL );
-
- // This is used by C_TEStickyBolt, despte being different from above!!!
- PrecacheModel( "models/crossbow_bolt.mdl" );
-
- PrecacheModel( "sprites/light_glow02_noz.vmt" );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CCrossbowBolt::BoltTouch( CBaseEntity *pOther )
-{
- if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
- {
- // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
- if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
- return;
- }
-
- if ( pOther->m_takedamage != DAMAGE_NO )
- {
- trace_t tr, tr2;
- tr = BaseClass::GetTouchTrace();
- Vector vecNormalizedVel = GetAbsVelocity();
-
- ClearMultiDamage();
- VectorNormalize( vecNormalizedVel );
-
-#if defined(HL2_EPISODIC)
- //!!!HACKHACK - specific hack for ep2_outland_10 to allow crossbow bolts to pass through her bounding box when she's crouched in front of the player
- // (the player thinks they have clear line of sight because Alyx is crouching, but her BBOx is still full-height and blocks crossbow bolts.
- if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->Classify() == CLASS_PLAYER_ALLY_VITAL && FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") )
- {
- // Change the owner to stop further collisions with Alyx. We do this by making her the owner.
- // The player won't get credit for this kill but at least the bolt won't magically disappear!
- SetOwnerEntity( pOther );
- return;
- }
-#endif//HL2_EPISODIC
-
- if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC() )
- {
- CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_NEVERGIB );
- dmgInfo.AdjustPlayerDamageInflictedForSkillLevel();
- CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
- dmgInfo.SetDamagePosition( tr.endpos );
- pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
-
- CBasePlayer *pPlayer = ToBasePlayer( GetOwnerEntity() );
- if ( pPlayer )
- {
- gamestats->Event_WeaponHit( pPlayer, true, "weapon_crossbow", dmgInfo );
- }
-
- }
- else
- {
- CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_BULLET | DMG_NEVERGIB );
- CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
- dmgInfo.SetDamagePosition( tr.endpos );
- pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
- }
-
- ApplyMultiDamage();
-
- //Adrian: keep going through the glass.
- if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
- return;
-
- if ( !pOther->IsAlive() )
- {
- // We killed it!
- const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
- if ( pdata->game.material == CHAR_TEX_GLASS )
- {
- return;
- }
- }
-
- 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_BLOCKLOS, 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 );
-
- if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
- {
- CEffectData data;
-
- data.m_vOrigin = tr2.endpos;
- data.m_vNormal = vForward;
- data.m_nEntIndex = tr2.fraction != 1.0f;
-
- DispatchEffect( "BoltImpact", data );
- }
- }
-
- SetTouch( NULL );
- SetThink( NULL );
-
- if ( !g_pGameRules->IsMultiplayer() )
- {
- UTIL_Remove( this );
- }
- }
- else
- {
- trace_t tr;
- tr = BaseClass::GetTouchTrace();
-
- // See if we struck the world
- if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
- {
- EmitSound( "Weapon_Crossbow.BoltHitWorld" );
-
- // if what we hit is static architecture, can stay around for a while.
- Vector vecDir = GetAbsVelocity();
- float speed = VectorNormalize( vecDir );
-
- // See if we should reflect off this surface
- float hitDot = DotProduct( tr.plane.normal, -vecDir );
-
- if ( ( hitDot < 0.5f ) && ( speed > 100 ) )
- {
- Vector vReflection = 2.0f * tr.plane.normal * hitDot + vecDir;
-
- QAngle reflectAngles;
-
- VectorAngles( vReflection, reflectAngles );
-
- SetLocalAngles( reflectAngles );
-
- SetAbsVelocity( vReflection * speed * 0.75f );
-
- // Start to sink faster
- SetGravity( 1.0f );
- }
- else
- {
- SetThink( &CCrossbowBolt::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 2.0f );
-
- //FIXME: We actually want to stick (with hierarchy) to what we've hit
- SetMoveType( MOVETYPE_NONE );
-
- Vector vForward;
-
- AngleVectors( GetAbsAngles(), &vForward );
- VectorNormalize ( vForward );
-
- CEffectData data;
-
- data.m_vOrigin = tr.endpos;
- data.m_vNormal = vForward;
- data.m_nEntIndex = 0;
-
- DispatchEffect( "BoltImpact", data );
-
- UTIL_ImpactTrace( &tr, DMG_BULLET );
-
- AddEffects( EF_NODRAW );
- SetTouch( NULL );
- SetThink( &CCrossbowBolt::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 2.0f );
-
- if ( m_pGlowSprite != NULL )
- {
- m_pGlowSprite->TurnOn();
- m_pGlowSprite->FadeAndDie( 3.0f );
- }
- }
-
- // Shoot some sparks
- if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
- {
- g_pEffects->Sparks( GetAbsOrigin() );
- }
- }
- else
- {
- // Put a mark unless we've hit the sky
- if ( ( tr.surface.flags & SURF_SKY ) == false )
- {
- UTIL_ImpactTrace( &tr, DMG_BULLET );
- }
-
- UTIL_Remove( this );
- }
- }
-
- if ( g_pGameRules->IsMultiplayer() )
- {
-// SetThink( &CCrossbowBolt::ExplodeThink );
-// SetNextThink( gpGlobals->curtime + 0.1f );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CCrossbowBolt::BubbleThink( void )
-{
- QAngle angNewAngles;
-
- VectorAngles( GetAbsVelocity(), angNewAngles );
- SetAbsAngles( angNewAngles );
-
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- // Make danger sounds out in front of me, to scare snipers back into their hole
- CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, GetAbsOrigin() + GetAbsVelocity() * 0.2, 120.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
-
- if ( GetWaterLevel() == 0 )
- return;
-
- UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
-}
-
-
-//-----------------------------------------------------------------------------
-// CWeaponCrossbow
-//-----------------------------------------------------------------------------
-
-class CWeaponCrossbow : public CBaseHLCombatWeapon
-{
- DECLARE_CLASS( CWeaponCrossbow, CBaseHLCombatWeapon );
-public:
-
- CWeaponCrossbow( void );
-
- virtual void Precache( void );
- virtual void PrimaryAttack( void );
- virtual void SecondaryAttack( void );
- virtual bool Deploy( void );
- virtual void Drop( const Vector &vecVelocity );
- virtual bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
- virtual bool Reload( void );
- virtual void ItemPostFrame( void );
- virtual void ItemBusyFrame( void );
- virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
- virtual bool SendWeaponAnim( int iActivity );
- virtual bool IsWeaponZoomed() { return m_bInZoom; }
-
- bool ShouldDisplayHUDHint() { return true; }
-
-
- DECLARE_SERVERCLASS();
- DECLARE_DATADESC();
-
-private:
-
- void StopEffects( void );
- void SetSkin( int skinNum );
- void CheckZoomToggle( void );
- void FireBolt( void );
- void ToggleZoom( void );
-
- // Various states for the crossbow's charger
- enum ChargerState_t
- {
- CHARGER_STATE_START_LOAD,
- CHARGER_STATE_START_CHARGE,
- CHARGER_STATE_READY,
- CHARGER_STATE_DISCHARGE,
- CHARGER_STATE_OFF,
- };
-
- void CreateChargerEffects( void );
- void SetChargerState( ChargerState_t state );
- void DoLoadEffect( void );
-
-private:
-
- // Charger effects
- ChargerState_t m_nChargeState;
- CHandle<CSprite> m_hChargerSprite;
-
- bool m_bInZoom;
- bool m_bMustReload;
-};
-
-LINK_ENTITY_TO_CLASS( weapon_crossbow, CWeaponCrossbow );
-
-PRECACHE_WEAPON_REGISTER( weapon_crossbow );
-
-IMPLEMENT_SERVERCLASS_ST( CWeaponCrossbow, DT_WeaponCrossbow )
-END_SEND_TABLE()
-
-BEGIN_DATADESC( CWeaponCrossbow )
-
- DEFINE_FIELD( m_bInZoom, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bMustReload, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_nChargeState, FIELD_INTEGER ),
- DEFINE_FIELD( m_hChargerSprite, FIELD_EHANDLE ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//-----------------------------------------------------------------------------
-CWeaponCrossbow::CWeaponCrossbow( void )
-{
- m_bReloadsSingly = true;
- m_bFiresUnderwater = true;
- m_bAltFiresUnderwater = true;
- m_bInZoom = false;
- m_bMustReload = false;
-}
-
-#define CROSSBOW_GLOW_SPRITE "sprites/light_glow02_noz.vmt"
-#define CROSSBOW_GLOW_SPRITE2 "sprites/blueflare1.vmt"
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::Precache( void )
-{
- UTIL_PrecacheOther( "crossbow_bolt" );
-
- PrecacheScriptSound( "Weapon_Crossbow.BoltHitBody" );
- PrecacheScriptSound( "Weapon_Crossbow.BoltHitWorld" );
- PrecacheScriptSound( "Weapon_Crossbow.BoltSkewer" );
-
- PrecacheModel( CROSSBOW_GLOW_SPRITE );
- PrecacheModel( CROSSBOW_GLOW_SPRITE2 );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::PrimaryAttack( void )
-{
- if ( m_bInZoom && g_pGameRules->IsMultiplayer() )
- {
-// FireSniperBolt();
- FireBolt();
- }
- else
- {
- FireBolt();
- }
-
- // Signal a reload
- m_bMustReload = true;
-
- SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration( ACT_VM_PRIMARYATTACK ) );
-
- CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
- if ( pPlayer )
- {
- m_iPrimaryAttacks++;
- gamestats->Event_WeaponFired( pPlayer, true, GetClassname() );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::SecondaryAttack( void )
-{
- //NOTENOTE: The zooming is handled by the post/busy frames
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponCrossbow::Reload( void )
-{
- if ( BaseClass::Reload() )
- {
- m_bMustReload = false;
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::CheckZoomToggle( void )
-{
- CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
-
- if ( pPlayer->m_afButtonPressed & IN_ATTACK2 )
- {
- ToggleZoom();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::ItemBusyFrame( void )
-{
- // Allow zoom toggling even when we're reloading
- CheckZoomToggle();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::ItemPostFrame( void )
-{
- // Allow zoom toggling
- CheckZoomToggle();
-
- if ( m_bMustReload && HasWeaponIdleTimeElapsed() )
- {
- Reload();
- }
-
- BaseClass::ItemPostFrame();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-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;
-
- pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART );
-
- Vector vecAiming = pOwner->GetAutoaimVector( 0 );
- Vector vecSrc = pOwner->Weapon_ShootPosition();
-
- QAngle angAiming;
- VectorAngles( vecAiming, angAiming );
-
-#if defined(HL2_EPISODIC)
- // !!!HACK - the other piece of the Alyx crossbow bolt hack for Outland_10 (see ::BoltTouch() for more detail)
- if( FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") )
- {
- trace_t tr;
- UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 24.0f, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr );
-
- if( tr.m_pEnt != NULL && tr.m_pEnt->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // If Alyx is right in front of the player, make sure the bolt starts outside of the player's BBOX, or the bolt
- // will instantly collide with the player after the owner of the bolt is switched to Alyx in ::BoltTouch(). We
- // avoid this altogether by making it impossible for the bolt to collide with the player.
- vecSrc += vecAiming * 24.0f;
- }
- }
-#endif
-
- CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, pOwner );
-
- if ( pOwner->GetWaterLevel() == 3 )
- {
- pBolt->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY );
- }
- else
- {
- pBolt->SetAbsVelocity( vecAiming * BOLT_AIR_VELOCITY );
- }
-
- m_iClip1--;
-
- pOwner->ViewPunch( QAngle( -2, 0, 0 ) );
-
- WeaponSound( SINGLE );
- WeaponSound( SPECIAL2 );
-
- CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 );
-
- SendWeaponAnim( ACT_VM_PRIMARYATTACK );
-
- if ( !m_iClip1 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
- {
- // HEV suit - indicate out of ammo condition
- pOwner->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
- }
-
- m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 0.75;
-
- DoLoadEffect();
- SetChargerState( CHARGER_STATE_DISCHARGE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponCrossbow::Deploy( void )
-{
- if ( m_iClip1 <= 0 )
- {
- return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() );
- }
-
- SetSkin( BOLT_SKIN_GLOW );
-
- return BaseClass::Deploy();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pSwitchingTo -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo )
-{
- StopEffects();
- return BaseClass::Holster( pSwitchingTo );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::ToggleZoom( void )
-{
- CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
-
- if ( pPlayer == NULL )
- return;
-
- if ( m_bInZoom )
- {
- if ( pPlayer->SetFOV( this, 0, 0.2f ) )
- {
- m_bInZoom = false;
- }
- }
- else
- {
- if ( pPlayer->SetFOV( this, 20, 0.1f ) )
- {
- m_bInZoom = true;
- }
- }
-}
-
-#define BOLT_TIP_ATTACHMENT 2
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::CreateChargerEffects( void )
-{
- CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
-
- if ( m_hChargerSprite != NULL )
- return;
-
- m_hChargerSprite = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE, GetAbsOrigin(), false );
-
- if ( m_hChargerSprite )
- {
- m_hChargerSprite->SetAttachment( pOwner->GetViewModel(), BOLT_TIP_ATTACHMENT );
- m_hChargerSprite->SetTransparency( kRenderTransAdd, 255, 128, 0, 255, kRenderFxNoDissipation );
- m_hChargerSprite->SetBrightness( 0 );
- m_hChargerSprite->SetScale( 0.1f );
- m_hChargerSprite->TurnOff();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : skinNum -
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::SetSkin( int skinNum )
-{
- CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
-
- if ( pOwner == NULL )
- return;
-
- CBaseViewModel *pViewModel = pOwner->GetViewModel();
-
- if ( pViewModel == NULL )
- return;
-
- pViewModel->m_nSkin = skinNum;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::DoLoadEffect( void )
-{
- SetSkin( BOLT_SKIN_GLOW );
-
- CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
-
- if ( pOwner == NULL )
- return;
-
- CBaseViewModel *pViewModel = pOwner->GetViewModel();
-
- if ( pViewModel == NULL )
- return;
-
- CEffectData data;
-
- data.m_nEntIndex = pViewModel->entindex();
- data.m_nAttachmentIndex = 1;
-
- DispatchEffect( "CrossbowLoad", data );
-
- CSprite *pBlast = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE2, GetAbsOrigin(), false );
-
- if ( pBlast )
- {
- pBlast->SetAttachment( pOwner->GetViewModel(), 1 );
- pBlast->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
- pBlast->SetBrightness( 128 );
- pBlast->SetScale( 0.2f );
- pBlast->FadeOutFromSpawn();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : state -
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::SetChargerState( ChargerState_t state )
-{
- // Make sure we're setup
- CreateChargerEffects();
-
- // Don't do this twice
- if ( state == m_nChargeState )
- return;
-
- m_nChargeState = state;
-
- switch( m_nChargeState )
- {
- case CHARGER_STATE_START_LOAD:
-
- WeaponSound( SPECIAL1 );
-
- // Shoot some sparks and draw a beam between the two outer points
- DoLoadEffect();
-
- break;
-
- case CHARGER_STATE_START_CHARGE:
- {
- if ( m_hChargerSprite == NULL )
- break;
-
- m_hChargerSprite->SetBrightness( 32, 0.5f );
- m_hChargerSprite->SetScale( 0.025f, 0.5f );
- m_hChargerSprite->TurnOn();
- }
-
- break;
-
- case CHARGER_STATE_READY:
- {
- // Get fully charged
- if ( m_hChargerSprite == NULL )
- break;
-
- m_hChargerSprite->SetBrightness( 80, 1.0f );
- m_hChargerSprite->SetScale( 0.1f, 0.5f );
- m_hChargerSprite->TurnOn();
- }
-
- break;
-
- case CHARGER_STATE_DISCHARGE:
- {
- SetSkin( BOLT_SKIN_NORMAL );
-
- if ( m_hChargerSprite == NULL )
- break;
-
- m_hChargerSprite->SetBrightness( 0 );
- m_hChargerSprite->TurnOff();
- }
-
- break;
-
- case CHARGER_STATE_OFF:
- {
- SetSkin( BOLT_SKIN_NORMAL );
-
- if ( m_hChargerSprite == NULL )
- break;
-
- m_hChargerSprite->SetBrightness( 0 );
- m_hChargerSprite->TurnOff();
- }
- break;
-
- default:
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEvent -
-// *pOperator -
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
-{
- switch( pEvent->event )
- {
- case EVENT_WEAPON_THROW:
- SetChargerState( CHARGER_STATE_START_LOAD );
- break;
-
- case EVENT_WEAPON_THROW2:
- SetChargerState( CHARGER_STATE_START_CHARGE );
- break;
-
- case EVENT_WEAPON_THROW3:
- SetChargerState( CHARGER_STATE_READY );
- break;
-
- default:
- BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the desired activity for the weapon and its viewmodel counterpart
-// Input : iActivity - activity to play
-//-----------------------------------------------------------------------------
-bool CWeaponCrossbow::SendWeaponAnim( int iActivity )
-{
- int newActivity = iActivity;
-
- // The last shot needs a non-loaded activity
- if ( ( newActivity == ACT_VM_IDLE ) && ( m_iClip1 <= 0 ) )
- {
- newActivity = ACT_VM_FIDGET;
- }
-
- //For now, just set the ideal activity and be done with it
- return BaseClass::SendWeaponAnim( newActivity );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stop all zooming and special effects on the viewmodel
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::StopEffects( void )
-{
- // Stop zooming
- if ( m_bInZoom )
- {
- ToggleZoom();
- }
-
- // Turn off our sprites
- SetChargerState( CHARGER_STATE_OFF );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponCrossbow::Drop( const Vector &vecVelocity )
-{
- StopEffects();
- BaseClass::Drop( vecVelocity );
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "npcevent.h"
+#include "basehlcombatweapon_shared.h"
+#include "basecombatcharacter.h"
+#include "ai_basenpc.h"
+#include "player.h"
+#include "gamerules.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "game.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "IEffects.h"
+#include "te_effect_dispatch.h"
+#include "Sprite.h"
+#include "SpriteTrail.h"
+#include "beam_shared.h"
+#include "rumble_shared.h"
+#include "gamestats.h"
+#include "decals.h"
+
+#ifdef PORTAL
+ #include "portal_util_shared.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//#define BOLT_MODEL "models/crossbow_bolt.mdl"
+#define BOLT_MODEL "models/weapons/w_missile_closed.mdl"
+
+#define BOLT_AIR_VELOCITY 2500
+#define BOLT_WATER_VELOCITY 1500
+
+extern ConVar sk_plr_dmg_crossbow;
+extern ConVar sk_npc_dmg_crossbow;
+
+void TE_StickyBolt( IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin );
+
+#define BOLT_SKIN_NORMAL 0
+#define BOLT_SKIN_GLOW 1
+
+//-----------------------------------------------------------------------------
+// Crossbow Bolt
+//-----------------------------------------------------------------------------
+class CCrossbowBolt : public CBaseCombatCharacter
+{
+ DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter );
+
+public:
+ CCrossbowBolt() { };
+ ~CCrossbowBolt();
+
+ Class_T Classify( void ) { return CLASS_NONE; }
+
+public:
+ void Spawn( void );
+ void Precache( void );
+ void BubbleThink( void );
+ void BoltTouch( CBaseEntity *pOther );
+ bool CreateVPhysics( void );
+ unsigned int PhysicsSolidMaskForEntity() const;
+ static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner = NULL );
+
+protected:
+
+ bool CreateSprites( void );
+
+ CHandle<CSprite> m_pGlowSprite;
+ //CHandle<CSpriteTrail> m_pGlowTrail;
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+};
+LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt );
+
+BEGIN_DATADESC( CCrossbowBolt )
+ // Function Pointers
+ DEFINE_FUNCTION( BubbleThink ),
+ DEFINE_FUNCTION( BoltTouch ),
+
+ // These are recreated on reload, they don't need storage
+ DEFINE_FIELD( m_pGlowSprite, FIELD_EHANDLE ),
+ //DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CCrossbowBolt, DT_CrossbowBolt )
+END_SEND_TABLE()
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCrossbowBolt::~CCrossbowBolt( void )
+{
+ if ( m_pGlowSprite )
+ {
+ UTIL_Remove( m_pGlowSprite );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CCrossbowBolt::CreateSprites( void )
+{
+ // Start up the eye glow
+ m_pGlowSprite = CSprite::SpriteCreate( "sprites/light_glow02_noz.vmt", GetLocalOrigin(), false );
+
+ if ( m_pGlowSprite != NULL )
+ {
+ m_pGlowSprite->FollowEntity( this );
+ m_pGlowSprite->SetTransparency( kRenderGlow, 255, 255, 255, 128, kRenderFxNoDissipation );
+ m_pGlowSprite->SetScale( 0.2f );
+ m_pGlowSprite->TurnOff();
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrossbowBolt::Spawn( void )
+{
+ Precache( );
+
+ SetModel( "models/crossbow_bolt.mdl" );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ UTIL_SetSize( this, -Vector(0.3f,0.3f,0.3f), Vector(0.3f,0.3f,0.3f) );
+ SetSolid( SOLID_BBOX );
+ SetGravity( 0.05f );
+
+ // Make sure we're updated if we're underwater
+ UpdateWaterState();
+
+ SetTouch( &CCrossbowBolt::BoltTouch );
+
+ SetThink( &CCrossbowBolt::BubbleThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CreateSprites();
+
+ // Make us glow until we've hit the wall
+ m_nSkin = BOLT_SKIN_GLOW;
+}
+
+
+void CCrossbowBolt::Precache( void )
+{
+ PrecacheModel( BOLT_MODEL );
+
+ // This is used by C_TEStickyBolt, despte being different from above!!!
+ PrecacheModel( "models/crossbow_bolt.mdl" );
+
+ PrecacheModel( "sprites/light_glow02_noz.vmt" );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CCrossbowBolt::BoltTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
+ {
+ // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
+ if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
+ return;
+ }
+
+ if ( pOther->m_takedamage != DAMAGE_NO )
+ {
+ trace_t tr, tr2;
+ tr = BaseClass::GetTouchTrace();
+ Vector vecNormalizedVel = GetAbsVelocity();
+
+ ClearMultiDamage();
+ VectorNormalize( vecNormalizedVel );
+
+#if defined(HL2_EPISODIC)
+ //!!!HACKHACK - specific hack for ep2_outland_10 to allow crossbow bolts to pass through her bounding box when she's crouched in front of the player
+ // (the player thinks they have clear line of sight because Alyx is crouching, but her BBOx is still full-height and blocks crossbow bolts.
+ if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->Classify() == CLASS_PLAYER_ALLY_VITAL && FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") )
+ {
+ // Change the owner to stop further collisions with Alyx. We do this by making her the owner.
+ // The player won't get credit for this kill but at least the bolt won't magically disappear!
+ SetOwnerEntity( pOther );
+ return;
+ }
+#endif//HL2_EPISODIC
+
+ if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC() )
+ {
+ CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_NEVERGIB );
+ dmgInfo.AdjustPlayerDamageInflictedForSkillLevel();
+ CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
+ dmgInfo.SetDamagePosition( tr.endpos );
+ pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
+
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwnerEntity() );
+ if ( pPlayer )
+ {
+ gamestats->Event_WeaponHit( pPlayer, true, "weapon_crossbow", dmgInfo );
+ }
+
+ }
+ else
+ {
+ CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_BULLET | DMG_NEVERGIB );
+ CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
+ dmgInfo.SetDamagePosition( tr.endpos );
+ pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
+ }
+
+ ApplyMultiDamage();
+
+ //Adrian: keep going through the glass.
+ if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
+ return;
+
+ if ( !pOther->IsAlive() )
+ {
+ // We killed it!
+ const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
+ if ( pdata->game.material == CHAR_TEX_GLASS )
+ {
+ return;
+ }
+ }
+
+ 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_BLOCKLOS, 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 );
+
+ if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
+ {
+ CEffectData data;
+
+ data.m_vOrigin = tr2.endpos;
+ data.m_vNormal = vForward;
+ data.m_nEntIndex = tr2.fraction != 1.0f;
+
+ DispatchEffect( "BoltImpact", data );
+ }
+ }
+
+ SetTouch( NULL );
+ SetThink( NULL );
+
+ if ( !g_pGameRules->IsMultiplayer() )
+ {
+ UTIL_Remove( this );
+ }
+ }
+ else
+ {
+ trace_t tr;
+ tr = BaseClass::GetTouchTrace();
+
+ // See if we struck the world
+ if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
+ {
+ EmitSound( "Weapon_Crossbow.BoltHitWorld" );
+
+ // if what we hit is static architecture, can stay around for a while.
+ Vector vecDir = GetAbsVelocity();
+ float speed = VectorNormalize( vecDir );
+
+ // See if we should reflect off this surface
+ float hitDot = DotProduct( tr.plane.normal, -vecDir );
+
+ if ( ( hitDot < 0.5f ) && ( speed > 100 ) )
+ {
+ Vector vReflection = 2.0f * tr.plane.normal * hitDot + vecDir;
+
+ QAngle reflectAngles;
+
+ VectorAngles( vReflection, reflectAngles );
+
+ SetLocalAngles( reflectAngles );
+
+ SetAbsVelocity( vReflection * speed * 0.75f );
+
+ // Start to sink faster
+ SetGravity( 1.0f );
+ }
+ else
+ {
+ SetThink( &CCrossbowBolt::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 2.0f );
+
+ //FIXME: We actually want to stick (with hierarchy) to what we've hit
+ SetMoveType( MOVETYPE_NONE );
+
+ Vector vForward;
+
+ AngleVectors( GetAbsAngles(), &vForward );
+ VectorNormalize ( vForward );
+
+ CEffectData data;
+
+ data.m_vOrigin = tr.endpos;
+ data.m_vNormal = vForward;
+ data.m_nEntIndex = 0;
+
+ DispatchEffect( "BoltImpact", data );
+
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+
+ AddEffects( EF_NODRAW );
+ SetTouch( NULL );
+ SetThink( &CCrossbowBolt::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 2.0f );
+
+ if ( m_pGlowSprite != NULL )
+ {
+ m_pGlowSprite->TurnOn();
+ m_pGlowSprite->FadeAndDie( 3.0f );
+ }
+ }
+
+ // Shoot some sparks
+ if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
+ {
+ g_pEffects->Sparks( GetAbsOrigin() );
+ }
+ }
+ else
+ {
+ // Put a mark unless we've hit the sky
+ if ( ( tr.surface.flags & SURF_SKY ) == false )
+ {
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+ }
+
+ UTIL_Remove( this );
+ }
+ }
+
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+// SetThink( &CCrossbowBolt::ExplodeThink );
+// SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCrossbowBolt::BubbleThink( void )
+{
+ QAngle angNewAngles;
+
+ VectorAngles( GetAbsVelocity(), angNewAngles );
+ SetAbsAngles( angNewAngles );
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Make danger sounds out in front of me, to scare snipers back into their hole
+ CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, GetAbsOrigin() + GetAbsVelocity() * 0.2, 120.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
+
+ if ( GetWaterLevel() == 0 )
+ return;
+
+ UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
+}
+
+
+//-----------------------------------------------------------------------------
+// CWeaponCrossbow
+//-----------------------------------------------------------------------------
+
+class CWeaponCrossbow : public CBaseHLCombatWeapon
+{
+ DECLARE_CLASS( CWeaponCrossbow, CBaseHLCombatWeapon );
+public:
+
+ CWeaponCrossbow( void );
+
+ virtual void Precache( void );
+ virtual void PrimaryAttack( void );
+ virtual void SecondaryAttack( void );
+ virtual bool Deploy( void );
+ virtual void Drop( const Vector &vecVelocity );
+ virtual bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+ virtual bool Reload( void );
+ virtual void ItemPostFrame( void );
+ virtual void ItemBusyFrame( void );
+ virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
+ virtual bool SendWeaponAnim( int iActivity );
+ virtual bool IsWeaponZoomed() { return m_bInZoom; }
+
+ bool ShouldDisplayHUDHint() { return true; }
+
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+private:
+
+ void StopEffects( void );
+ void SetSkin( int skinNum );
+ void CheckZoomToggle( void );
+ void FireBolt( void );
+ void ToggleZoom( void );
+
+ // Various states for the crossbow's charger
+ enum ChargerState_t
+ {
+ CHARGER_STATE_START_LOAD,
+ CHARGER_STATE_START_CHARGE,
+ CHARGER_STATE_READY,
+ CHARGER_STATE_DISCHARGE,
+ CHARGER_STATE_OFF,
+ };
+
+ void CreateChargerEffects( void );
+ void SetChargerState( ChargerState_t state );
+ void DoLoadEffect( void );
+
+private:
+
+ // Charger effects
+ ChargerState_t m_nChargeState;
+ CHandle<CSprite> m_hChargerSprite;
+
+ bool m_bInZoom;
+ bool m_bMustReload;
+};
+
+LINK_ENTITY_TO_CLASS( weapon_crossbow, CWeaponCrossbow );
+
+PRECACHE_WEAPON_REGISTER( weapon_crossbow );
+
+IMPLEMENT_SERVERCLASS_ST( CWeaponCrossbow, DT_WeaponCrossbow )
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CWeaponCrossbow )
+
+ DEFINE_FIELD( m_bInZoom, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bMustReload, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_nChargeState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hChargerSprite, FIELD_EHANDLE ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CWeaponCrossbow::CWeaponCrossbow( void )
+{
+ m_bReloadsSingly = true;
+ m_bFiresUnderwater = true;
+ m_bAltFiresUnderwater = true;
+ m_bInZoom = false;
+ m_bMustReload = false;
+}
+
+#define CROSSBOW_GLOW_SPRITE "sprites/light_glow02_noz.vmt"
+#define CROSSBOW_GLOW_SPRITE2 "sprites/blueflare1.vmt"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::Precache( void )
+{
+ UTIL_PrecacheOther( "crossbow_bolt" );
+
+ PrecacheScriptSound( "Weapon_Crossbow.BoltHitBody" );
+ PrecacheScriptSound( "Weapon_Crossbow.BoltHitWorld" );
+ PrecacheScriptSound( "Weapon_Crossbow.BoltSkewer" );
+
+ PrecacheModel( CROSSBOW_GLOW_SPRITE );
+ PrecacheModel( CROSSBOW_GLOW_SPRITE2 );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::PrimaryAttack( void )
+{
+ if ( m_bInZoom && g_pGameRules->IsMultiplayer() )
+ {
+// FireSniperBolt();
+ FireBolt();
+ }
+ else
+ {
+ FireBolt();
+ }
+
+ // Signal a reload
+ m_bMustReload = true;
+
+ SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration( ACT_VM_PRIMARYATTACK ) );
+
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+ if ( pPlayer )
+ {
+ m_iPrimaryAttacks++;
+ gamestats->Event_WeaponFired( pPlayer, true, GetClassname() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::SecondaryAttack( void )
+{
+ //NOTENOTE: The zooming is handled by the post/busy frames
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponCrossbow::Reload( void )
+{
+ if ( BaseClass::Reload() )
+ {
+ m_bMustReload = false;
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::CheckZoomToggle( void )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( pPlayer->m_afButtonPressed & IN_ATTACK2 )
+ {
+ ToggleZoom();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::ItemBusyFrame( void )
+{
+ // Allow zoom toggling even when we're reloading
+ CheckZoomToggle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::ItemPostFrame( void )
+{
+ // Allow zoom toggling
+ CheckZoomToggle();
+
+ if ( m_bMustReload && HasWeaponIdleTimeElapsed() )
+ {
+ Reload();
+ }
+
+ BaseClass::ItemPostFrame();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+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;
+
+ pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART );
+
+ Vector vecAiming = pOwner->GetAutoaimVector( 0 );
+ Vector vecSrc = pOwner->Weapon_ShootPosition();
+
+ QAngle angAiming;
+ VectorAngles( vecAiming, angAiming );
+
+#if defined(HL2_EPISODIC)
+ // !!!HACK - the other piece of the Alyx crossbow bolt hack for Outland_10 (see ::BoltTouch() for more detail)
+ if( FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") )
+ {
+ trace_t tr;
+ UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 24.0f, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.m_pEnt != NULL && tr.m_pEnt->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // If Alyx is right in front of the player, make sure the bolt starts outside of the player's BBOX, or the bolt
+ // will instantly collide with the player after the owner of the bolt is switched to Alyx in ::BoltTouch(). We
+ // avoid this altogether by making it impossible for the bolt to collide with the player.
+ vecSrc += vecAiming * 24.0f;
+ }
+ }
+#endif
+
+ CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, pOwner );
+
+ if ( pOwner->GetWaterLevel() == 3 )
+ {
+ pBolt->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY );
+ }
+ else
+ {
+ pBolt->SetAbsVelocity( vecAiming * BOLT_AIR_VELOCITY );
+ }
+
+ m_iClip1--;
+
+ pOwner->ViewPunch( QAngle( -2, 0, 0 ) );
+
+ WeaponSound( SINGLE );
+ WeaponSound( SPECIAL2 );
+
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 );
+
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+
+ if ( !m_iClip1 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ {
+ // HEV suit - indicate out of ammo condition
+ pOwner->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
+ }
+
+ m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 0.75;
+
+ DoLoadEffect();
+ SetChargerState( CHARGER_STATE_DISCHARGE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponCrossbow::Deploy( void )
+{
+ if ( m_iClip1 <= 0 )
+ {
+ return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() );
+ }
+
+ SetSkin( BOLT_SKIN_GLOW );
+
+ return BaseClass::Deploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pSwitchingTo -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ StopEffects();
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::ToggleZoom( void )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( pPlayer == NULL )
+ return;
+
+ if ( m_bInZoom )
+ {
+ if ( pPlayer->SetFOV( this, 0, 0.2f ) )
+ {
+ m_bInZoom = false;
+ }
+ }
+ else
+ {
+ if ( pPlayer->SetFOV( this, 20, 0.1f ) )
+ {
+ m_bInZoom = true;
+ }
+ }
+}
+
+#define BOLT_TIP_ATTACHMENT 2
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::CreateChargerEffects( void )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( m_hChargerSprite != NULL )
+ return;
+
+ m_hChargerSprite = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE, GetAbsOrigin(), false );
+
+ if ( m_hChargerSprite )
+ {
+ m_hChargerSprite->SetAttachment( pOwner->GetViewModel(), BOLT_TIP_ATTACHMENT );
+ m_hChargerSprite->SetTransparency( kRenderTransAdd, 255, 128, 0, 255, kRenderFxNoDissipation );
+ m_hChargerSprite->SetBrightness( 0 );
+ m_hChargerSprite->SetScale( 0.1f );
+ m_hChargerSprite->TurnOff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : skinNum -
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::SetSkin( int skinNum )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ CBaseViewModel *pViewModel = pOwner->GetViewModel();
+
+ if ( pViewModel == NULL )
+ return;
+
+ pViewModel->m_nSkin = skinNum;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::DoLoadEffect( void )
+{
+ SetSkin( BOLT_SKIN_GLOW );
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ CBaseViewModel *pViewModel = pOwner->GetViewModel();
+
+ if ( pViewModel == NULL )
+ return;
+
+ CEffectData data;
+
+ data.m_nEntIndex = pViewModel->entindex();
+ data.m_nAttachmentIndex = 1;
+
+ DispatchEffect( "CrossbowLoad", data );
+
+ CSprite *pBlast = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE2, GetAbsOrigin(), false );
+
+ if ( pBlast )
+ {
+ pBlast->SetAttachment( pOwner->GetViewModel(), 1 );
+ pBlast->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ pBlast->SetBrightness( 128 );
+ pBlast->SetScale( 0.2f );
+ pBlast->FadeOutFromSpawn();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : state -
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::SetChargerState( ChargerState_t state )
+{
+ // Make sure we're setup
+ CreateChargerEffects();
+
+ // Don't do this twice
+ if ( state == m_nChargeState )
+ return;
+
+ m_nChargeState = state;
+
+ switch( m_nChargeState )
+ {
+ case CHARGER_STATE_START_LOAD:
+
+ WeaponSound( SPECIAL1 );
+
+ // Shoot some sparks and draw a beam between the two outer points
+ DoLoadEffect();
+
+ break;
+
+ case CHARGER_STATE_START_CHARGE:
+ {
+ if ( m_hChargerSprite == NULL )
+ break;
+
+ m_hChargerSprite->SetBrightness( 32, 0.5f );
+ m_hChargerSprite->SetScale( 0.025f, 0.5f );
+ m_hChargerSprite->TurnOn();
+ }
+
+ break;
+
+ case CHARGER_STATE_READY:
+ {
+ // Get fully charged
+ if ( m_hChargerSprite == NULL )
+ break;
+
+ m_hChargerSprite->SetBrightness( 80, 1.0f );
+ m_hChargerSprite->SetScale( 0.1f, 0.5f );
+ m_hChargerSprite->TurnOn();
+ }
+
+ break;
+
+ case CHARGER_STATE_DISCHARGE:
+ {
+ SetSkin( BOLT_SKIN_NORMAL );
+
+ if ( m_hChargerSprite == NULL )
+ break;
+
+ m_hChargerSprite->SetBrightness( 0 );
+ m_hChargerSprite->TurnOff();
+ }
+
+ break;
+
+ case CHARGER_STATE_OFF:
+ {
+ SetSkin( BOLT_SKIN_NORMAL );
+
+ if ( m_hChargerSprite == NULL )
+ break;
+
+ m_hChargerSprite->SetBrightness( 0 );
+ m_hChargerSprite->TurnOff();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+// *pOperator -
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
+{
+ switch( pEvent->event )
+ {
+ case EVENT_WEAPON_THROW:
+ SetChargerState( CHARGER_STATE_START_LOAD );
+ break;
+
+ case EVENT_WEAPON_THROW2:
+ SetChargerState( CHARGER_STATE_START_CHARGE );
+ break;
+
+ case EVENT_WEAPON_THROW3:
+ SetChargerState( CHARGER_STATE_READY );
+ break;
+
+ default:
+ BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the desired activity for the weapon and its viewmodel counterpart
+// Input : iActivity - activity to play
+//-----------------------------------------------------------------------------
+bool CWeaponCrossbow::SendWeaponAnim( int iActivity )
+{
+ int newActivity = iActivity;
+
+ // The last shot needs a non-loaded activity
+ if ( ( newActivity == ACT_VM_IDLE ) && ( m_iClip1 <= 0 ) )
+ {
+ newActivity = ACT_VM_FIDGET;
+ }
+
+ //For now, just set the ideal activity and be done with it
+ return BaseClass::SendWeaponAnim( newActivity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop all zooming and special effects on the viewmodel
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::StopEffects( void )
+{
+ // Stop zooming
+ if ( m_bInZoom )
+ {
+ ToggleZoom();
+ }
+
+ // Turn off our sprites
+ SetChargerState( CHARGER_STATE_OFF );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrossbow::Drop( const Vector &vecVelocity )
+{
+ StopEffects();
+ BaseClass::Drop( vecVelocity );
+}