Grapple Hook
< De
![Underlinked - Logo.png](/w/images/9/91/Underlinked_-_Logo.png)
This article needs more
links to other articles to help
integrate it into the encyclopedia. Please help improve this article by adding links
that are relevant to the context within the existing text.
January 2024
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
January 2024
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.
![Note.png](/w/images/thumb/c/cc/Note.png/10px-Note.png)
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
}
}
}