You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
982 lines
26 KiB
982 lines
26 KiB
//========= 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 ); |
|
}
|
|
|