Grapple Hook:de

From Valve Developer Community
Jump to: navigation, search
English

Dies beschreibt die Erstellung des Greifhakens, wie er in der Obsidian Conflict Modifikation zu finden ist. Der code wurde von Hyperjag3 geschrieben.

Wenn jemand dies fortführen und eine Dokumentation über die Funktionsweise schreiben will, wäre es nett. Ich habe leider nicht die Zeit, das zu tun.

Die Waffe wurde für einen Multiplayer entwurfen, sollte aber gut in einem Singleplayer funktionieren, wenn Referenzen zu hl2mp entfernt werden. Es verwendet das Armbrust-Modell.

Bemerkung:
Wenn du den Quellcode in deiner Modifikation einbinden willst, dann dem Obsidian Conflict Team entsprechende Credits in der Readme-Datei deiner Modifikation. Danke. :)

weapon_grapple.h

#ifndef WEAPON_GRAPPLE_H
#define WEAPON_GRAPPLE_H

#ifdef _WIN32
#pragma once
#endif

#include "weapon_hl2mpbasehlmpcombatweapon.h"

#ifndef CLIENT_DLL
	#include "rope.h"
	#include "props.h"
#endif

#include "rope_shared.h"

#ifndef CLIENT_DLL

class CWeaponGrapple;

//-----------------------------------------------------------------------------
// Grapple Hook
//-----------------------------------------------------------------------------
class CGrappleHook : public CBaseCombatCharacter
{
	DECLARE_CLASS( CGrappleHook, CBaseCombatCharacter );

public:
	CGrappleHook() { };
	~CGrappleHook();

	Class_T Classify( void ) { return CLASS_NONE; }

public:
	void Spawn( void );
	void Precache( void );
	void FlyThink( void );
	void HookedThink( void );
	void HookTouch( CBaseEntity *pOther );
	bool CreateVPhysics( void );
	unsigned int PhysicsSolidMaskForEntity() const;
	static CGrappleHook *HookCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );

protected:

	DECLARE_DATADESC();

private:

	void UpdatePlayerConstraint( void );

	CHandle<CWeaponGrapple>		m_hOwner;
	CHandle<CBasePlayer>		m_hPlayer;
	CHandle<CDynamicProp>		m_hBolt;
	IPhysicsSpring				*m_pSpring;
	float						m_fSpringLength;
	bool						m_bPlayerWasStanding;
};

#endif

//-----------------------------------------------------------------------------
// CWeaponGrapple
//-----------------------------------------------------------------------------

#ifdef CLIENT_DLL
#define CWeaponGrapple C_WeaponGrapple
#endif

class CWeaponGrapple : public CBaseHL2MPCombatWeapon
{
	DECLARE_CLASS( CWeaponGrapple, CBaseHL2MPCombatWeapon );
public:

	CWeaponGrapple( void );
	
	virtual void	Precache( void );
	virtual void	PrimaryAttack( void );
	virtual void	SecondaryAttack( void );
	virtual bool	Deploy( void );
	bool			CanHolster( void );
	virtual bool	Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
	void			Drop( const Vector &vecVelocity );
	virtual bool	Reload( void );
	virtual void	ItemPostFrame( void );
	virtual void	ItemBusyFrame( void );
	virtual bool	SendWeaponAnim( int iActivity );

	void			NotifyHookDied( void );

	bool			HasAnyAmmo( void );

	CBaseEntity		*GetHook( void ) { return m_hHook; }

#ifndef CLIENT_DLL
	virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
#endif

	DECLARE_NETWORKCLASS(); 
	DECLARE_PREDICTABLE();

private:

	void	SetSkin( int skinNum );
	void	CheckZoomToggle( void );
	void	FireHook( 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 );

#ifndef CLIENT_DLL
	bool	CreateRope( void );

	DECLARE_ACTTABLE();
#endif

private:
	
	// Charger effects
	ChargerState_t		m_nChargeState;

#ifndef CLIENT_DLL
	CHandle<CSprite>	m_hChargerSprite;
	CHandle<CRopeKeyframe>		m_hRope;
#endif

	CNetworkVar( bool,	m_bInZoom );
	CNetworkVar( bool,	m_bMustReload );

	CNetworkHandle( CBaseEntity, m_hHook );

	CWeaponGrapple( const CWeaponGrapple & );
};



#endif // WEAPON_GRAPPLE_H

weapon_grapple.cpp

#include "cbase.h"
#include "npcevent.h"
#include "in_buttons.h"
#include "weapon_grapple.h"

#ifdef CLIENT_DLL
	#include "c_hl2mp_player.h"
	#include "c_te_effect_dispatch.h"
#else
	#include "game.h"
	#include "hl2mp_player.h"
	#include "te_effect_dispatch.h"
	#include "IEffects.h"
	#include "Sprite.h"
	#include "SpriteTrail.h"
	#include "beam_shared.h"
	#include "explode.h"

	#include "vphysics/constraints.h"
	#include "physics_saverestore.h"
	
#endif

//#include "effect_dispatch_data.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define HOOK_MODEL			"models/props_junk/rock001a.mdl"
#define BOLT_MODEL			"models/crossbow_bolt.mdl"

#define BOLT_AIR_VELOCITY	3500
#define BOLT_WATER_VELOCITY	1500
#define	BOLT_SKIN_NORMAL	0
#define BOLT_SKIN_GLOW		1


#ifndef CLIENT_DLL

LINK_ENTITY_TO_CLASS( grapple_hook, CGrappleHook );

BEGIN_DATADESC( CGrappleHook )
	// Function Pointers
	DEFINE_THINKFUNC( FlyThink ),
	DEFINE_THINKFUNC( HookedThink ),
	DEFINE_FUNCTION( HookTouch ),

	DEFINE_PHYSPTR( m_pSpring ),
	DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hBolt, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bPlayerWasStanding, FIELD_BOOLEAN ),

END_DATADESC()

CGrappleHook *CGrappleHook::HookCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
{
	// Create a new entity with CGrappleHook private data
	CGrappleHook *pHook = (CGrappleHook *)CreateEntityByName( "grapple_hook" );
	UTIL_SetOrigin( pHook, vecOrigin );
	pHook->SetAbsAngles( angAngles );
	pHook->Spawn();

	CWeaponGrapple *pOwner = (CWeaponGrapple *)pentOwner;
	pHook->m_hOwner = pOwner;
	pHook->SetOwnerEntity( pOwner->GetOwner() );
	pHook->m_hPlayer = (CBasePlayer *)pOwner->GetOwner();

	return pHook;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CGrappleHook::~CGrappleHook( void )
{
	if ( m_pSpring )
	{
		physenv->DestroySpring( m_pSpring );
		m_pSpring = NULL;
	}

	if ( m_hBolt )
	{
		UTIL_Remove( m_hBolt );
		m_hBolt = NULL;
	}

	// Revert Jay's gai flag
	if ( m_hPlayer )
		m_hPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CGrappleHook::CreateVPhysics( void )
{
	// Create the object in the physics system
	VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned int CGrappleHook::PhysicsSolidMaskForEntity() const
{
	return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGrappleHook::Spawn( void )
{
	Precache( );

	SetModel( HOOK_MODEL );
	SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
	UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
	SetSolid( SOLID_BBOX );
	SetGravity( 0.05f );

	// The rock is invisible, the crossbow bolt is the visual representation
	AddEffects( EF_NODRAW );
	
	// Make sure we're updated if we're underwater
	UpdateWaterState();

	SetTouch( &CGrappleHook::HookTouch );

	SetThink( &CGrappleHook::FlyThink );
	SetNextThink( gpGlobals->curtime + 0.1f );

	m_pSpring		= NULL;
	m_fSpringLength = 0.0f;
	m_bPlayerWasStanding = false;

	// Create bolt model and parent it
	CBaseEntity *pBolt = CBaseEntity::CreateNoSpawn( "prop_dynamic", GetAbsOrigin(), GetAbsAngles(), this );
	pBolt->SetModelName( MAKE_STRING( BOLT_MODEL ) );
	pBolt->SetModel( BOLT_MODEL );
	DispatchSpawn( pBolt );
	pBolt->SetParent( this );
}


void CGrappleHook::Precache( void )
{
	PrecacheModel( HOOK_MODEL );
	PrecacheModel( BOLT_MODEL );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CGrappleHook::HookTouch( CBaseEntity *pOther )
{
	if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) )
		return;

	if ( (pOther != m_hOwner) && (pOther->m_takedamage != DAMAGE_NO) )
	{
		m_hOwner->NotifyHookDied();

		SetTouch( NULL );
		SetThink( NULL );

		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();
			
			//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( "Impact", data );
			
			UTIL_ImpactTrace( &tr, DMG_BULLET );

		//	AddEffects( EF_NODRAW );
			SetTouch( NULL );
			
			// Shoot some sparks
			if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
			{
				g_pEffects->Sparks( GetAbsOrigin() );
			}

			VPhysicsDestroyObject();
			VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_NOT_STANDABLE, false );
			AddSolidFlags( FSOLID_NOT_SOLID );
		//	SetMoveType( MOVETYPE_NONE );

			if ( !m_hPlayer )
			{
				Assert( 0 );
				return;
			}

			// Set Jay's gai flag
			m_hPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, true );

			IPhysicsObject *pPhysObject = m_hPlayer->VPhysicsGetObject();
			IPhysicsObject *pRootPhysObject = VPhysicsGetObject();
			Assert( pRootPhysObject );
			Assert( pPhysObject );

			pRootPhysObject->EnableMotion( false );

			// Root has huge mass, tip has little
			pRootPhysObject->SetMass( VPHYSICS_MAX_MASS );
		//	pPhysObject->SetMass( 100 );
		//	float damping = 3;
		//	pPhysObject->SetDamping( &damping, &damping );

			Vector origin = m_hPlayer->GetAbsOrigin();
			Vector rootOrigin = GetAbsOrigin();
			m_fSpringLength = (origin - rootOrigin).Length();

			springparams_t spring;
			spring.constant = 8000;
			spring.damping = 400;
			spring.naturalLength = m_fSpringLength;
			spring.relativeDamping = 2;
			spring.startPosition = origin;
			spring.endPosition = rootOrigin;
			spring.useLocalPositions = false;
			spring.onlyStretch = false;		
			m_pSpring = physenv->CreateSpring( pPhysObject, pRootPhysObject, &spring );

			m_bPlayerWasStanding = ( ( m_hPlayer->GetFlags() & FL_DUCKING ) == 0 );

			SetThink( &CGrappleHook::HookedThink );
			SetNextThink( gpGlobals->curtime + 0.1f );
		}
		else
		{
			// Put a mark unless we've hit the sky
			if ( ( tr.surface.flags & SURF_SKY ) == false )
			{
				UTIL_ImpactTrace( &tr, DMG_BULLET );
			}

			SetTouch( NULL );
			SetThink( NULL );

			m_hOwner->NotifyHookDied();
			UTIL_Remove( this );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGrappleHook::HookedThink( void )
{
/*	static bool omgWtf = false;

	if ( omgWtf )
	{
		omgWtf = false; // only run once

		// Destroy the current spring.
		physenv->DestroySpring( m_pSpring );
		m_pSpring = NULL;

		IPhysicsObject *pPhysObject = m_hPlayer->VPhysicsGetObject();
		IPhysicsObject *pRootPhysObject = VPhysicsGetObject();
		Assert( pRootPhysObject );
		Assert( pPhysObject );

		Vector origin = m_hPlayer->GetAbsOrigin();
		Vector rootOrigin = GetAbsOrigin();
		m_fSpringLength = (origin - rootOrigin).Length();

		springparams_t spring;
		spring.constant = 6000; //400
		spring.damping = 100;
		spring.naturalLength = m_fSpringLength;
		spring.relativeDamping = 2;
		spring.startPosition = origin;
		spring.endPosition = rootOrigin;
		spring.useLocalPositions = false;
		spring.onlyStretch = false;		
		m_pSpring = physenv->CreateSpring( pPhysObject, pRootPhysObject, &spring );
	}
*/
	UpdatePlayerConstraint();

/*	if( m_hPlayer->GetFlags() & FL_ONGROUND )
	{
		// Try to fight OnGround
		m_hPlayer->SetGravity( 0 );
		m_hPlayer->RemoveFlag( FL_ONGROUND );
	}
*/
	//set next globalthink
	SetNextThink( gpGlobals->curtime + 0.1f );

	m_fSpringLength -= 20;

	if ( m_fSpringLength < 1 )
		return;

	if ( m_pSpring )
		m_pSpring->SetSpringLength( m_fSpringLength );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGrappleHook::UpdatePlayerConstraint( void )
{
	// Check to see if the player's standing/ducking state has changed.
	bool bStanding = ( ( m_hPlayer->GetFlags() & FL_DUCKING ) == 0 );
	if ( bStanding == m_bPlayerWasStanding )
		return;

	// Destroy the current spring.
	physenv->DestroySpring( m_pSpring );
	m_pSpring = NULL;

	// Set Jay's gai flag
//	m_hPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, true );

	// Create the new constraint for the standing/ducking player physics object.
	IPhysicsObject *pPhysObject = m_hPlayer->VPhysicsGetObject();
	IPhysicsObject *pRootPhysObject = VPhysicsGetObject();
	Assert( pRootPhysObject );
	Assert( pPhysObject );

	Vector origin = m_hPlayer->GetAbsOrigin();
	Vector rootOrigin = GetAbsOrigin();
	m_fSpringLength = (origin - rootOrigin).Length();

	springparams_t spring;
	spring.constant = 6000; //400
	spring.damping = 100;
	spring.naturalLength = m_fSpringLength;
	spring.relativeDamping = 2;
	spring.startPosition = origin;
	spring.endPosition = rootOrigin;
	spring.useLocalPositions = false;
	spring.onlyStretch = false;		
	m_pSpring = physenv->CreateSpring( pPhysObject, pRootPhysObject, &spring );

	// Save state for the next check.
	m_bPlayerWasStanding = bStanding;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGrappleHook::FlyThink( void )
{
	QAngle angNewAngles;

	VectorAngles( GetAbsVelocity(), angNewAngles );
	SetAbsAngles( angNewAngles );

	SetNextThink( gpGlobals->curtime + 0.1f );
}

#endif

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponGrapple, DT_WeaponGrapple )

#ifdef CLIENT_DLL
void RecvProxy_HookDied( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	CWeaponGrapple *pGrapple = ((CWeaponGrapple*)pStruct);

	RecvProxy_IntToEHandle( pData, pStruct, pOut );

	CBaseEntity *pNewHook = pGrapple->GetHook();

	if ( pNewHook == NULL )
	{
		if ( pGrapple->GetOwner() && pGrapple->GetOwner()->GetActiveWeapon() == pGrapple )
		{
			pGrapple->NotifyHookDied();
		}
	}
}
#endif

BEGIN_NETWORK_TABLE( CWeaponGrapple, DT_WeaponGrapple )
#ifdef CLIENT_DLL
	RecvPropBool( RECVINFO( m_bInZoom ) ),
	RecvPropBool( RECVINFO( m_bMustReload ) ),
	RecvPropEHandle( RECVINFO( m_hHook ), RecvProxy_HookDied ),
#else
	SendPropBool( SENDINFO( m_bInZoom ) ),
	SendPropBool( SENDINFO( m_bMustReload ) ),
	SendPropEHandle( SENDINFO( m_hHook ) ),
#endif
END_NETWORK_TABLE()

#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CWeaponGrapple )
	DEFINE_PRED_FIELD( m_bInZoom, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bMustReload, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
#endif

LINK_ENTITY_TO_CLASS( weapon_grapple, CWeaponGrapple );

PRECACHE_WEAPON_REGISTER( weapon_grapple );

#ifndef CLIENT_DLL

acttable_t	CWeaponGrapple::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 },
	// Valve never made a crossbow reload anim for playermodels, I guess use the ar2 one for now
//	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_CROSSBOW,			false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_AR2,				false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_CROSSBOW,					false },
};

IMPLEMENT_ACTTABLE(CWeaponGrapple);

#endif

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponGrapple::CWeaponGrapple( void )
{
	m_bReloadsSingly	= true;
	m_bFiresUnderwater	= true;
	m_bInZoom			= false;
	m_bMustReload		= false;
#ifndef CLIENT_DLL
	m_hRope				= NULL;
#endif
}

#define	CROSSBOW_GLOW_SPRITE	"sprites/light_glow02_noz.vmt"
#define	CROSSBOW_GLOW_SPRITE2	"sprites/blueflare1.vmt"

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::Precache( void )
{
#ifndef CLIENT_DLL
	UTIL_PrecacheOther( "grapple_hook" );
#endif

//	PrecacheScriptSound( "Weapon_Crossbow.BoltHitBody" );
	PrecacheScriptSound( "Weapon_Crossbow.BoltHitWorld" );
//	PrecacheScriptSound( "Weapon_Crossbow.BoltSkewer" );

	PrecacheModel( CROSSBOW_GLOW_SPRITE );
	PrecacheModel( CROSSBOW_GLOW_SPRITE2 );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponGrapple::PrimaryAttack( void )
{
	// Can't have an active hook out
	if ( m_hHook != NULL )
		return;

	FireHook();

	SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration( ACT_VM_PRIMARYATTACK ) );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponGrapple::SecondaryAttack( void )
{
	//NOTENOTE: The zooming is handled by the post/busy frames
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CWeaponGrapple::Reload( void )
{
	if ( ( m_bMustReload ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) )
	{
		//Redraw the weapon
		SendWeaponAnim( ACT_VM_RELOAD );

		//Update our times
		m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();

		//Mark this as done
		m_bMustReload = false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::CheckZoomToggle( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	
	if ( pPlayer->m_afButtonPressed & IN_ATTACK2 )
	{
		ToggleZoom();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::ItemBusyFrame( void )
{
	// Allow zoom toggling even when we're reloading
	CheckZoomToggle();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::ItemPostFrame( void )
{
	// Allow zoom toggling
	CheckZoomToggle();

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if ( ( pOwner->m_nButtons & IN_ATTACK ) )
	{
		if ( m_flNextPrimaryAttack < gpGlobals->curtime )
		{
			PrimaryAttack();
		}
	}
	else if ( m_bMustReload ) //&& HasWeaponIdleTimeElapsed() )
	{
		Reload();
	}

#ifndef CLIENT_DLL
	if ( m_hHook )
	{
		if ( m_hRope )
			m_hRope->RecalculateLength();

		if ( !(pOwner->m_nButtons & IN_ATTACK) )
		{
			m_hHook->SetTouch( NULL );
			m_hHook->SetThink( NULL );

			UTIL_Remove( m_hHook );
			m_hHook = NULL;

			NotifyHookDied();

			m_bMustReload = true;
		}
	}
#endif

//	BaseClass::ItemPostFrame();
}

#ifndef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CWeaponGrapple::CreateRope( void )
{
	if ( !m_hHook )
	{
		Assert( 0 );
		return false;
	}

	m_hRope = CRopeKeyframe::Create( this, m_hHook, 1, 0 );

	if ( m_hRope )
	{
		m_hRope->m_Width = 2;
		m_hRope->m_nSegments = ROPE_MAX_SEGMENTS / 2;
		m_hRope->EnableWind( false );
	//	m_hRope->EnableCollision(); // Collision looks worse than no collision
		m_hRope->SetupHangDistance( 0 );
	}

	return true;
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::FireHook( void )
{
	if ( m_bMustReload )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
	
	if ( pOwner == NULL )
		return;

#ifndef CLIENT_DLL
	Vector vecAiming	= pOwner->GetAutoaimVector( 0 );	
	Vector vecSrc		= pOwner->Weapon_ShootPosition();

	QAngle angAiming;
	VectorAngles( vecAiming, angAiming );

	CGrappleHook *pHook = CGrappleHook::HookCreate( vecSrc, angAiming, this );

	if ( pOwner->GetWaterLevel() == 3 )
	{
		pHook->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY );
	}
	else
	{
		pHook->SetAbsVelocity( vecAiming * BOLT_AIR_VELOCITY );
	}

	m_hHook = pHook;
	CreateRope();

#endif

	pOwner->ViewPunch( QAngle( -2, 0, 0 ) );

	WeaponSound( SINGLE );
	WeaponSound( SPECIAL2 );

	SendWeaponAnim( ACT_VM_PRIMARYATTACK );

	m_flNextPrimaryAttack = m_flNextSecondaryAttack	= gpGlobals->curtime + 0.75;

	DoLoadEffect();
	SetChargerState( CHARGER_STATE_DISCHARGE );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CWeaponGrapple::Deploy( void )
{
	if ( m_bMustReload )
	{
		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 CWeaponGrapple::Holster( CBaseCombatWeapon *pSwitchingTo )
{
#ifndef CLIENT_DLL
	if ( m_hHook )
	{
		m_hHook->SetTouch( NULL );
		m_hHook->SetThink( NULL );

		UTIL_Remove( m_hHook );
		m_hHook = NULL;

		NotifyHookDied();

		m_bMustReload = true;
	}
#endif

	if ( m_bInZoom )
	{
		ToggleZoom();
	}

	SetChargerState( CHARGER_STATE_OFF );

	return BaseClass::Holster( pSwitchingTo );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::ToggleZoom( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	
	if ( pPlayer == NULL )
		return;

#ifndef CLIENT_DLL

	if ( m_bInZoom )
	{
		if ( pPlayer->SetFOV( this, 0, 0.2f ) )
		{
			m_bInZoom = false;
		}
	}
	else
	{
		if ( pPlayer->SetFOV( this, 20, 0.1f ) )
		{
			m_bInZoom = true;
		}
	}
#endif
}

#define	BOLT_TIP_ATTACHMENT	2

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::CreateChargerEffects( void )
{
#ifndef CLIENT_DLL
	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();
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : skinNum - 
//-----------------------------------------------------------------------------
void CWeaponGrapple::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 CWeaponGrapple::DoLoadEffect( void )
{
	SetSkin( BOLT_SKIN_GLOW );

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if ( pOwner == NULL )
		return;

	CBaseViewModel *pViewModel = pOwner->GetViewModel();

	if ( pViewModel == NULL )
		return;

	CEffectData	data;

#ifdef CLIENT_DLL
	data.m_hEntity = pViewModel->GetRefEHandle();
#else
	data.m_nEntIndex = pViewModel->entindex();
#endif
	data.m_nAttachmentIndex = 1;

	DispatchEffect( "CrossbowLoad", data );

#ifndef CLIENT_DLL

	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();
	}
#endif
	
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : state - 
//-----------------------------------------------------------------------------
void CWeaponGrapple::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;
#ifndef CLIENT_DLL
	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;
#endif
	case CHARGER_STATE_OFF:
		{
			SetSkin( BOLT_SKIN_NORMAL );

#ifndef CLIENT_DLL
			if ( m_hChargerSprite == NULL )
				break;
			
			m_hChargerSprite->SetBrightness( 0 );
			m_hChargerSprite->TurnOff();
#endif
		}
		break;

	default:
		break;
	}
}

#ifndef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEvent - 
//			*pOperator - 
//-----------------------------------------------------------------------------
void CWeaponGrapple::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;
	}
}

#endif

//-----------------------------------------------------------------------------
// Purpose: Set the desired activity for the weapon and its viewmodel counterpart
// Input  : iActivity - activity to play
//-----------------------------------------------------------------------------
bool CWeaponGrapple::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: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::Drop( const Vector &vecVelocity )
{
#ifndef CLIENT_DLL
	if ( m_hHook )
	{
		m_hHook->SetTouch( NULL );
		m_hHook->SetThink( NULL );

		UTIL_Remove( m_hHook );
		m_hHook = NULL;

		NotifyHookDied();

		m_bMustReload = true;
	}
#endif

	if ( m_bInZoom )
	{
		ToggleZoom();
	}

	SetChargerState( CHARGER_STATE_OFF );

	BaseClass::Drop( vecVelocity );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponGrapple::HasAnyAmmo( void )
{
	if ( m_hHook != NULL )
		return true;

	return BaseClass::HasAnyAmmo();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponGrapple::CanHolster( void )
{
	//Can't have an active hook out
	if ( m_hHook != NULL )
		return false;

	return BaseClass::CanHolster();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponGrapple::NotifyHookDied( void )
{
	m_hHook = NULL;

#ifndef CLIENT_DLL
	if ( m_hRope )
	{
		UTIL_Remove( m_hRope );
		m_hRope = NULL;
	}
#endif
}

Weapon script

WeaponData
{
    printname        "GRAPPLE HOOK"
    viewmodel        models/weapons/v_crossbow.mdl
    playermodel    models/weapons/w_crossbow.mdl
    anim_prefix    bow
    bucket            5
    bucket_position    2


//    clip_size        1
//    default_clip        5
//    primary_ammo        XBowBolt
//    secondary_ammo    None

    clip_size                -1
    clip2_size            -1
    primary_ammo            None
    secondary_ammo        None
    default_clip            -1
    default_clip2            -1

    autoswitchto            0
    autoswitchfrom        0
    weight        0
    item_flags        0


    // Sounds for the weapon. There is a max of 16 sounds per category (i.e.
max 16 single_shot sounds)
    SoundData
    {
        single_shot    Weapon_Crossbow.Single
        reload    Weapon_Crossbow.Reload
        special1    Weapon_Crossbow.BoltElectrify
        special2    Weapon_Crossbow.BoltFly
    }

    TextureData
    {
        weapon
        {
                font        WeaponIcons
                character    g
        }
        weapon_s
        {
                font        WeaponIconsSelected
                character    g
        }
        ammo
        {
                font        WeaponIcons
                character    w
        }
        crosshair
        {
                font        Crosshairs
                character    Q
        }
        autoaim
        {
            file        sprites/crosshairs
            x        0
            y        96
            width        24
            height    24
        }
        zoom
        {
                font        Crosshairs
                character    Q
        }
        zoom_autoaim
        {
            file        sprites/crosshairs
            x        24
            y        112
            width        104
            height    16
        }
    }
}