source-engine/game/shared/dod/weapon_dodbase.cpp
2022-04-16 12:05:19 +03:00

1417 lines
36 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "in_buttons.h"
#include "takedamageinfo.h"
#include "weapon_dodbase.h"
#include "ammodef.h"
#include "dod_gamerules.h"
#ifdef CLIENT_DLL
extern IVModelInfoClient* modelinfo;
#else
extern IVModelInfo* modelinfo;
#include "ilagcompensationmanager.h"
#endif
#if defined( CLIENT_DLL )
#include "vgui/ISurface.h"
#include "vgui_controls/Controls.h"
#include "c_dod_player.h"
#include "hud_crosshair.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#else
#include "dod_player.h"
#endif
#include "effect_dispatch_data.h"
// ----------------------------------------------------------------------------- //
// Global functions.
// ----------------------------------------------------------------------------- //
bool IsAmmoType( int iAmmoType, const char *pAmmoName )
{
return GetAmmoDef()->Index( pAmmoName ) == iAmmoType;
}
//--------------------------------------------------------------------------------------------------------
//
// Given a weapon ID, return its alias
//
const char *WeaponIDToAlias( int id )
{
if ( (id >= WEAPON_MAX) || (id < 0) )
return NULL;
return s_WeaponAliasInfo[id];
}
// ----------------------------------------------------------------------------- //
// CWeaponDODBase tables.
// ----------------------------------------------------------------------------- //
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponDODBase, DT_WeaponDODBase )
BEGIN_NETWORK_TABLE( CWeaponDODBase, DT_WeaponDODBase )
#ifdef CLIENT_DLL
RecvPropInt( RECVINFO(m_iReloadModelIndex) ),
RecvPropVector( RECVINFO( m_vInitialDropVelocity ) ),
RecvPropTime( RECVINFO( m_flSmackTime ) )
#else
SendPropVector( SENDINFO( m_vInitialDropVelocity ),
20, // nbits
0, // flags
-3000, // low value
3000 // high value
),
SendPropModelIndex( SENDINFO(m_iReloadModelIndex) ),
SendPropTime( SENDINFO( m_flSmackTime ) )
#endif
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( weapon_dod_base, CWeaponDODBase );
#ifdef GAME_DLL
BEGIN_DATADESC( CWeaponDODBase )
DEFINE_FUNCTION( FallThink ),
DEFINE_FUNCTION( Die ),
DEFINE_FUNCTION( Smack )
END_DATADESC()
#else
BEGIN_PREDICTION_DATA( CWeaponDODBase )
DEFINE_PRED_FIELD( m_flSmackTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), // for rifle melee attacks
DEFINE_FIELD( m_bInAttack, FIELD_BOOLEAN )
END_PREDICTION_DATA()
#endif
Vector head_hull_mins( -16, -16, -18 );
Vector head_hull_maxs( 16, 16, 18 );
void FindHullIntersection( const Vector &vecSrc, trace_t &tr, const Vector &mins, const Vector &maxs, CBaseEntity *pEntity )
{
int i, j, k;
float distance;
Vector minmaxs[2] = {mins, maxs};
trace_t tmpTrace;
Vector vecHullEnd = tr.endpos;
Vector vecEnd;
CTraceFilterSimple filter( pEntity, COLLISION_GROUP_NONE );
distance = 1e6f;
vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2);
UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SOLID, &filter, &tmpTrace );
if ( tmpTrace.fraction < 1.0 )
{
tr = tmpTrace;
return;
}
for ( i = 0; i < 2; i++ )
{
for ( j = 0; j < 2; j++ )
{
for ( k = 0; k < 2; k++ )
{
vecEnd.x = vecHullEnd.x + minmaxs[i][0];
vecEnd.y = vecHullEnd.y + minmaxs[j][1];
vecEnd.z = vecHullEnd.z + minmaxs[k][2];
UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, &filter, &tmpTrace );
if ( tmpTrace.fraction < 1.0 )
{
float thisDistance = (tmpTrace.endpos - vecSrc).Length();
if ( thisDistance < distance )
{
tr = tmpTrace;
distance = thisDistance;
}
}
}
}
}
}
// ----------------------------------------------------------------------------- //
// CWeaponDODBase implementation.
// ----------------------------------------------------------------------------- //
CWeaponDODBase::CWeaponDODBase()
{
SetPredictionEligible( true );
m_bInAttack = false;
m_iAltFireHint = 0;
AddSolidFlags( FSOLID_TRIGGER ); // Nothing collides with these but it gets touches.
m_flNextPrimaryAttack = 0;
}
bool CWeaponDODBase::IsPredicted() const
{
return true;
}
bool CWeaponDODBase::PlayEmptySound()
{
CPASAttenuationFilter filter( this );
filter.UsePredictionRules();
EmitSound( filter, entindex(), "Default.ClipEmpty_Rifle" );
return false;
}
CBasePlayer* CWeaponDODBase::GetPlayerOwner() const
{
return dynamic_cast< CBasePlayer* >( GetOwner() );
}
CDODPlayer* CWeaponDODBase::GetDODPlayerOwner() const
{
return dynamic_cast< CDODPlayer* >( GetOwner() );
}
bool CWeaponDODBase::SendWeaponAnim( int iActivity )
{
return BaseClass::SendWeaponAnim( iActivity );
}
bool CWeaponDODBase::CanAttack( void )
{
CDODPlayer *pPlayer = ToDODPlayer( GetPlayerOwner() );
if ( pPlayer )
{
return pPlayer->CanAttack();
}
return false;
}
bool CWeaponDODBase::ShouldAutoReload( void )
{
CDODPlayer *pPlayer = ToDODPlayer( GetPlayerOwner() );
if ( pPlayer )
{
return pPlayer->ShouldAutoReload();
}
return false;
}
void CWeaponDODBase::ItemPostFrame()
{
if ( m_flSmackTime > 0 && gpGlobals->curtime > m_flSmackTime )
{
Smack();
m_flSmackTime = -1;
}
CBasePlayer *pPlayer = GetPlayerOwner();
if ( !pPlayer )
return;
#ifdef _DEBUG
CDODGameRules *mp = DODGameRules();
#endif
assert( mp );
if ((m_bInReload) && (pPlayer->m_flNextAttack <= gpGlobals->curtime))
{
// complete the reload.
int j = MIN( GetMaxClip1() - m_iClip1, pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) );
// Add them to the clip
m_iClip1 += j;
pPlayer->RemoveAmmo( j, m_iPrimaryAmmoType );
m_bInReload = false;
FinishReload();
}
if ((pPlayer->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime))
{
if ( m_iClip2 != -1 && !pPlayer->GetAmmoCount( GetSecondaryAmmoType() ) )
{
m_bFireOnEmpty = TRUE;
}
SecondaryAttack();
pPlayer->m_nButtons &= ~IN_ATTACK2;
}
else if ((pPlayer->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime ) && !m_bInAttack )
{
if ( (m_iClip1 == 0/* && pszAmmo1()*/) || (GetMaxClip1() == -1 && !pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) ) )
{
m_bFireOnEmpty = TRUE;
}
if( CanAttack() )
PrimaryAttack();
}
else if ( pPlayer->m_nButtons & IN_RELOAD && GetMaxClip1() != WEAPON_NOCLIP && !m_bInReload && m_flNextPrimaryAttack < gpGlobals->curtime)
{
// reload when reload is pressed, or if no buttons are down and weapon is empty.
Reload();
}
else if ( !(pPlayer->m_nButtons & (IN_ATTACK|IN_ATTACK2) ) )
{
// no fire buttons down
m_bFireOnEmpty = false;
m_bInAttack = false; //reset semi-auto
if ( !IsUseable() && m_flNextPrimaryAttack < gpGlobals->curtime )
{
// Intentionally blank -- used to switch weapons here
}
else if( ShouldAutoReload() )
{
// weapon is useable. Reload if empty and weapon has waited as long as it has to after firing
if ( m_iClip1 == 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime )
{
Reload();
return;
}
}
WeaponIdle( );
return;
}
}
void CWeaponDODBase::WeaponIdle()
{
if (m_flTimeWeaponIdle > gpGlobals->curtime)
return;
SendWeaponAnim( GetIdleActivity() );
m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
}
Activity CWeaponDODBase::GetIdleActivity( void )
{
return ACT_VM_IDLE;
}
const CDODWeaponInfo &CWeaponDODBase::GetDODWpnData() const
{
const FileWeaponInfo_t *pWeaponInfo = &GetWpnData();
const CDODWeaponInfo *pDODInfo;
#ifdef _DEBUG
pDODInfo = dynamic_cast< const CDODWeaponInfo* >( pWeaponInfo );
Assert( pDODInfo );
#else
pDODInfo = static_cast< const CDODWeaponInfo* >( pWeaponInfo );
#endif
return *pDODInfo;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CWeaponDODBase::GetViewModel( int /*viewmodelindex = 0 -- this is ignored in the base class here*/ ) const
{
if ( GetPlayerOwner() == NULL )
{
return BaseClass::GetViewModel();
}
return GetWpnData().szViewModel;
}
void CWeaponDODBase::Precache( void )
{
// precache base first, it loads weapon scripts
BaseClass::Precache();
PrecacheScriptSound( "Default.ClipEmpty_Rifle" );
PrecacheParticleSystem( "muzzle_pistols" );
PrecacheParticleSystem( "muzzle_fullyautomatic" );
PrecacheParticleSystem( "muzzle_rifles" );
PrecacheParticleSystem( "muzzle_rockets" );
PrecacheParticleSystem( "muzzle_mg42" );
PrecacheParticleSystem( "view_muzzle_pistols" );
PrecacheParticleSystem( "view_muzzle_fullyautomatic" );
PrecacheParticleSystem( "view_muzzle_rifles" );
PrecacheParticleSystem( "view_muzzle_rockets" );
PrecacheParticleSystem( "view_muzzle_mg42" );
const CDODWeaponInfo &info = GetDODWpnData();
int iWpnNameLen = Q_strlen(info.m_szReloadModel);
#ifdef DEBUG
// Make sure that if we declare an alt weapon, that we have criteria to show it
// and vice-versa
//Assert( ((info.m_iAltWpnCriteria & (ALTWPN_CRITERIA_RELOADING | ALTWPN_CRITERIA_FIRING)) > 0 ) ==
// (iWpnNameLen > 0) );
#endif
if( iWpnNameLen > 0 )
m_iReloadModelIndex = CBaseEntity::PrecacheModel( info.m_szReloadModel );
}
bool CWeaponDODBase::DefaultDeploy( char *szViewModel, char *szWeaponModel, int iActivity, char *szAnimExt )
{
CBasePlayer *pOwner = GetPlayerOwner();
if ( !pOwner )
{
return false;
}
pOwner->SetAnimationExtension( szAnimExt );
SetViewModel();
SendWeaponAnim( iActivity );
pOwner->SetNextAttack( gpGlobals->curtime + SequenceDuration() );
m_flNextPrimaryAttack = MAX( m_flNextPrimaryAttack, gpGlobals->curtime );
m_flNextSecondaryAttack = gpGlobals->curtime;
SetWeaponVisible( true );
SetWeaponModelIndex( szWeaponModel );
CBaseViewModel *vm = pOwner->GetViewModel( m_nViewModelIndex );
Assert( vm );
if( vm )
{
//set sleeves to proper team
switch( pOwner->GetTeamNumber() )
{
case TEAM_ALLIES:
vm->m_nSkin = SLEEVE_ALLIES;
break;
case TEAM_AXIS:
vm->m_nSkin = SLEEVE_AXIS;
break;
default:
Assert( !"TEAM_UNASSIGNED or spectator getting a view model assigned" );
break;
}
}
return true;
}
void CWeaponDODBase::SetWeaponModelIndex( const char *pName )
{
m_iWorldModelIndex = modelinfo->GetModelIndex( pName );
}
bool CWeaponDODBase::CanBeSelected( void )
{
if ( !VisibleInWeaponSelection() )
return false;
return true;
}
bool CWeaponDODBase::CanDeploy( void )
{
return BaseClass::CanDeploy();
}
bool CWeaponDODBase::CanHolster( void )
{
return BaseClass::CanHolster();
}
void CWeaponDODBase::Drop( const Vector &vecVelocity )
{
#ifndef CLIENT_DLL
if ( m_iAltFireHint )
{
CDODPlayer *pPlayer = GetDODPlayerOwner();
if ( pPlayer )
{
pPlayer->StopHintTimer( m_iAltFireHint );
}
}
#endif
// cancel any reload in progress
m_bInReload = false;
m_flSmackTime = -1;
m_vInitialDropVelocity = vecVelocity;
BaseClass::Drop( m_vInitialDropVelocity );
}
bool CWeaponDODBase::Holster( CBaseCombatWeapon *pSwitchingTo )
{
#ifndef CLIENT_DLL
CDODPlayer *pPlayer = GetDODPlayerOwner();
if ( pPlayer )
{
pPlayer->SetFOV( pPlayer, 0 ); // reset the default FOV.
if ( m_iAltFireHint )
{
pPlayer->StopHintTimer( m_iAltFireHint );
}
}
#endif
m_bInReload = false;
m_flSmackTime = -1;
return BaseClass::Holster( pSwitchingTo );
}
bool CWeaponDODBase::Deploy()
{
#ifndef CLIENT_DLL
CDODPlayer *pPlayer = GetDODPlayerOwner();
if ( pPlayer )
{
pPlayer->SetFOV( pPlayer, 0 );
if ( m_iAltFireHint )
{
pPlayer->StartHintTimer( m_iAltFireHint );
}
}
#endif
return BaseClass::Deploy();
}
#ifdef CLIENT_DLL
void CWeaponDODBase::PostDataUpdate( DataUpdateType_t updateType )
{
// We need to do this before the C_BaseAnimating code starts to drive
// clientside animation sequences on this model, which will be using bad sequences for the world model.
int iDesiredModelIndex = 0;
C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer();
if ( localplayer && localplayer == GetOwner() && !C_BasePlayer::ShouldDrawLocalPlayer() ) // FIXME: use localplayer->ShouldDrawThisPlayer() instead.
{
iDesiredModelIndex = m_iViewModelIndex;
}
else
{
iDesiredModelIndex = GetWorldModelIndex();
// Our world models never animate
SetSequence( 0 );
}
if ( GetModelIndex() != iDesiredModelIndex )
{
SetModelIndex( iDesiredModelIndex );
}
BaseClass::PostDataUpdate( updateType );
}
void CWeaponDODBase::OnDataChanged( DataUpdateType_t type )
{
if ( m_iState == WEAPON_NOT_CARRIED && m_iOldState != WEAPON_NOT_CARRIED )
{
// we are being notified of the weapon being dropped
// add an interpolation history so the movement is smoother
// Now stick our initial velocity into the interpolation history
CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator();
interpolator.ClearHistory();
float changeTime = GetLastChangeTime( LATCH_SIMULATION_VAR );
// Add a sample 1 second back.
Vector vCurOrigin = GetLocalOrigin() - m_vInitialDropVelocity;
interpolator.AddToHead( changeTime - 1.0, &vCurOrigin, false );
// Add the current sample.
vCurOrigin = GetLocalOrigin();
interpolator.AddToHead( changeTime, &vCurOrigin, false );
Vector estVel;
EstimateAbsVelocity( estVel );
/*Msg( "estimated velocity ( %.1f %.1f %.1f ) initial velocity ( %.1f %.1f %.1f )\n",
estVel.x,
estVel.y,
estVel.z,
m_vInitialDropVelocity.m_Value.x,
m_vInitialDropVelocity.m_Value.y,
m_vInitialDropVelocity.m_Value.z );*/
OnWeaponDropped();
}
BaseClass::OnDataChanged( type );
if ( GetPredictable() && !ShouldPredict() )
ShutdownPredictable();
}
int CWeaponDODBase::GetWorldModelIndex( void )
{
if( m_bUseAltWeaponModel && GetOwner() != NULL )
return m_iReloadModelIndex;
else
return m_iWorldModelIndex;
}
bool CWeaponDODBase::ShouldPredict()
{
if ( GetOwner() && GetOwner() == C_BasePlayer::GetLocalPlayer() )
return true;
return BaseClass::ShouldPredict();
}
void CWeaponDODBase::ProcessMuzzleFlashEvent()
{
CDODPlayer *pPlayer = GetDODPlayerOwner();
if ( pPlayer )
pPlayer->ProcessMuzzleFlashEvent();
BaseClass::ProcessMuzzleFlashEvent();
}
#else
//-----------------------------------------------------------------------------
// Purpose: Get the accuracy derived from weapon and player, and return it
//-----------------------------------------------------------------------------
const Vector& CWeaponDODBase::GetBulletSpread()
{
static Vector cone = VECTOR_CONE_8DEGREES;
return cone;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponDODBase::ItemBusyFrame()
{
if( ShouldAutoReload() && !m_bInReload )
{
// weapon is useable. Reload if empty and weapon has waited as long as it has to after firing
if ( m_iClip1 == 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime )
{
Reload();
}
}
BaseClass::ItemBusyFrame();
}
//-----------------------------------------------------------------------------
// Purpose: Match the anim speed to the weapon speed while crouching
//-----------------------------------------------------------------------------
float CWeaponDODBase::GetDefaultAnimSpeed()
{
return 1.0;
}
bool CWeaponDODBase::ShouldRemoveOnRoundRestart()
{
if ( GetPlayerOwner() )
return false;
else
return true;
}
//=========================================================
// Materialize - make a CWeaponDODBase visible and tangible
//=========================================================
void CWeaponDODBase::Materialize()
{
if ( IsEffectActive( EF_NODRAW ) )
{
RemoveEffects( EF_NODRAW );
DoMuzzleFlash();
}
AddSolidFlags( FSOLID_TRIGGER );
SetThink (&CWeaponDODBase::SUB_Remove);
SetNextThink( gpGlobals->curtime + 1 );
}
//=========================================================
// AttemptToMaterialize - the item is trying to rematerialize,
// should it do so now or wait longer?
//=========================================================
void CWeaponDODBase::AttemptToMaterialize()
{
float time = g_pGameRules->FlWeaponTryRespawn( this );
if ( time == 0 )
{
Materialize();
return;
}
SetNextThink( gpGlobals->curtime + time );
}
//=========================================================
// CheckRespawn - a player is taking this weapon, should
// it respawn?
//=========================================================
void CWeaponDODBase::CheckRespawn()
{
//GOOSEMAN : Do not respawn weapons!
return;
}
//=========================================================
// Respawn- this item is already in the world, but it is
// invisible and intangible. Make it visible and tangible.
//=========================================================
CBaseEntity* CWeaponDODBase::Respawn()
{
// make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code
// will decide when to make the weapon visible and touchable.
CBaseEntity *pNewWeapon = CBaseEntity::Create( GetClassname(), g_pGameRules->VecWeaponRespawnSpot( this ), GetAbsAngles(), GetOwner() );
if ( pNewWeapon )
{
pNewWeapon->AddEffects( EF_NODRAW );// invisible for now
pNewWeapon->SetTouch( NULL );// no touch
pNewWeapon->SetThink( &CWeaponDODBase::AttemptToMaterialize );
UTIL_DropToFloor( this, MASK_SOLID );
// not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement,
// but when it should respawn is based on conditions belonging to the weapon that was taken.
pNewWeapon->SetNextThink( gpGlobals->curtime + g_pGameRules->FlWeaponRespawnTime( this ) );
}
else
{
Msg( "Respawn failed to create %s!\n", GetClassname() );
}
return pNewWeapon;
}
bool CWeaponDODBase::Reload()
{
return BaseClass::Reload();
}
void CWeaponDODBase::Spawn()
{
BaseClass::Spawn();
// Set this here to allow players to shoot dropped weapons
SetCollisionGroup( COLLISION_GROUP_WEAPON );
SetExtraAmmoCount(0); //Start with no additional ammo
CollisionProp()->UseTriggerBounds( true, 10.0f );
}
void CWeaponDODBase::SetDieThink( bool bDie )
{
if( bDie )
SetContextThink( &CWeaponDODBase::Die, gpGlobals->curtime + 45.0f, "DieContext" );
else
SetContextThink( NULL, gpGlobals->curtime, "DieContext" );
}
void CWeaponDODBase::Die( void )
{
UTIL_Remove( this );
}
#endif
void CWeaponDODBase::OnPickedUp( CBaseCombatCharacter *pNewOwner )
{
BaseClass::OnPickedUp( pNewOwner );
#if !defined( CLIENT_DLL )
SetDieThink( false );
#endif
}
bool CWeaponDODBase::DefaultReload( int iClipSize1, int iClipSize2, int iActivity )
{
CBaseCombatCharacter *pOwner = GetOwner();
if (!pOwner)
return false;
// If I don't have any spare ammo, I can't reload
if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 )
return false;
bool bReload = false;
// If you don't have clips, then don't try to reload them.
if ( UsesClipsForAmmo1() )
{
// need to reload primary clip?
int primary = min(iClipSize1 - m_iClip1, pOwner->GetAmmoCount(m_iPrimaryAmmoType));
if ( primary != 0 )
{
bReload = true;
}
}
if ( UsesClipsForAmmo2() )
{
// need to reload secondary clip?
int secondary = min(iClipSize2 - m_iClip2, pOwner->GetAmmoCount(m_iSecondaryAmmoType));
if ( secondary != 0 )
{
bReload = true;
}
}
if ( !bReload )
return false;
CDODPlayer *pPlayer = GetDODPlayerOwner();
if ( pPlayer )
{
#ifdef CLIENT_DLL
PlayWorldReloadSound( pPlayer );
#else
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD );
#endif
}
SendWeaponAnim( iActivity );
// Play the player's reload animation
if ( pOwner->IsPlayer() )
{
( ( CBasePlayer * )pOwner)->SetAnimation( PLAYER_RELOAD );
}
float flSequenceEndTime = gpGlobals->curtime + SequenceDuration();
pOwner->SetNextAttack( flSequenceEndTime );
m_flNextPrimaryAttack = m_flNextSecondaryAttack = flSequenceEndTime;
m_bInReload = true;
return true;
}
#ifdef CLIENT_DLL
void CWeaponDODBase::PlayWorldReloadSound( CDODPlayer *pPlayer )
{
Assert( pPlayer );
const char *shootsound = GetShootSound( RELOAD );
if ( !shootsound || !shootsound[0] )
return;
CSoundParameters params;
if ( !GetParametersForSound( shootsound, params, NULL ) )
return;
// Play weapon sound from the owner
CPASAttenuationFilter filter( pPlayer, params.soundlevel );
filter.RemoveRecipient( pPlayer ); // no local player, that is done in the model
EmitSound( filter, pPlayer->entindex(), shootsound, NULL, 0.0 );
}
#endif
bool CWeaponDODBase::IsUseable()
{
CBasePlayer *pPlayer = GetPlayerOwner();
if ( Clip1() <= 0 )
{
if ( pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0 && GetMaxClip1() != -1 )
{
// clip is empty (or nonexistant) and the player has no more ammo of this type.
return false;
}
}
return true;
}
#ifndef CLIENT_DLL
ConVar dod_meleeattackforcescale( "dod_meleeattackforcescale", "8.0", FCVAR_CHEAT | FCVAR_GAMEDLL );
#endif
void CWeaponDODBase::RifleButt( void )
{
//MeleeAttack( 60, MELEE_DMG_BUTTSTOCK | MELEE_DMG_SECONDARYATTACK, 0.2f, 0.9f );
}
void CWeaponDODBase::Bayonet( void )
{
//MeleeAttack( 60, MELEE_DMG_BAYONET | MELEE_DMG_SECONDARYATTACK, 0.2f, 0.9f );
}
void CWeaponDODBase::Punch( void )
{
MeleeAttack( 60, MELEE_DMG_FIST | MELEE_DMG_SECONDARYATTACK, 0.2f, 0.4f );
}
//--------------------------------------------
// iDamageAmount - how much damage to give
// iDamageType - DMG_ bits
// flDmgDelay - delay between attack and the giving of damage, usually timed to animation
// flAttackDelay - time until we can next attack
//--------------------------------------------
CBaseEntity *CWeaponDODBase::MeleeAttack( int iDamageAmount, int iDamageType, float flDmgDelay, float flAttackDelay )
{
if ( !CanAttack() )
return NULL;
CDODPlayer *pPlayer = ToDODPlayer( GetPlayerOwner() );
#if !defined (CLIENT_DLL)
// Move other players back to history positions based on local player's lag
lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
#endif
Vector vForward, vRight, vUp;
AngleVectors( pPlayer->EyeAngles(), &vForward, &vRight, &vUp );
Vector vecSrc = pPlayer->Weapon_ShootPosition();
Vector vecEnd = vecSrc + vForward * 48;
CTraceFilterSimple filter( pPlayer, COLLISION_GROUP_NONE );
int iTraceMask = MASK_SOLID | CONTENTS_HITBOX | CONTENTS_DEBRIS;
trace_t tr;
UTIL_TraceLine( vecSrc, vecEnd, iTraceMask, &filter, &tr );
const float rayExtension = 40.0f;
UTIL_ClipTraceToPlayers( vecSrc, vecEnd + vForward * rayExtension, iTraceMask, &filter, &tr );
// If the exact forward trace did not hit, try a larger swept box
if ( tr.fraction >= 1.0 )
{
Vector head_hull_mins( -16, -16, -18 );
Vector head_hull_maxs( 16, 16, 18 );
UTIL_TraceHull( vecSrc, vecEnd, head_hull_mins, head_hull_maxs, MASK_SOLID, &filter, &tr );
if ( tr.fraction < 1.0 )
{
// Calculate the point of intersection of the line (or hull) and the object we hit
// This is and approximation of the "best" intersection
CBaseEntity *pHit = tr.m_pEnt;
if ( !pHit || pHit->IsBSPModel() )
FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer );
vecEnd = tr.endpos; // This is the point on the actual surface (the hull could have hit space)
// Make sure it is in front of us
Vector vecToEnd = vecEnd - vecSrc;
VectorNormalize( vecToEnd );
// if zero length, always hit
if ( vecToEnd.Length() > 0 )
{
float dot = DotProduct( vForward, vecToEnd );
// sanity that our hit is within range
if ( abs(dot) < 0.95 )
{
// fake that we actually missed
tr.fraction = 1.0;
}
}
}
}
WeaponSound( MELEE_MISS );
bool bDidHit = ( tr.fraction < 1.0f );
if ( bDidHit ) //if the swing hit
{
// delay the decal a bit
m_trHit = tr;
// Store the ent in an EHANDLE, just in case it goes away by the time we get into our think function.
m_pTraceHitEnt = tr.m_pEnt;
m_iSmackDamage = iDamageAmount;
m_iSmackDamageType = iDamageType;
m_flSmackTime = gpGlobals->curtime + flDmgDelay;
}
SendWeaponAnim( GetMeleeActivity() );
// player animation
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_SECONDARY_ATTACK );
m_flNextPrimaryAttack = gpGlobals->curtime + flAttackDelay;
m_flNextSecondaryAttack = gpGlobals->curtime + flAttackDelay;
m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
#ifndef CLIENT_DLL
IGameEvent * event = gameeventmanager->CreateEvent( "dod_stats_weapon_attack" );
if ( event )
{
event->SetInt( "attacker", pPlayer->GetUserID() );
event->SetInt( "weapon", GetAltWeaponID() );
gameeventmanager->FireEvent( event );
}
lagcompensation->FinishLagCompensation( pPlayer );
#endif //CLIENT_DLL
return tr.m_pEnt;
}
//Think function to delay the impact decal until the animation is finished playing
void CWeaponDODBase::Smack()
{
Assert( GetPlayerOwner() );
if ( !GetPlayerOwner() )
return;
CDODPlayer *pPlayer = ToDODPlayer( GetPlayerOwner() );
if ( !pPlayer )
return;
// Check that we are still facing the victim
Vector vForward, vRight, vUp;
AngleVectors( pPlayer->EyeAngles(), &vForward, &vRight, &vUp );
Vector vecSrc = pPlayer->Weapon_ShootPosition();
Vector vecEnd = vecSrc + vForward * 48;
CTraceFilterSimple filter( pPlayer, COLLISION_GROUP_NONE );
int iTraceMask = MASK_SOLID | CONTENTS_HITBOX | CONTENTS_DEBRIS;
trace_t tr;
UTIL_TraceLine( vecSrc, vecEnd, iTraceMask, &filter, &tr );
const float rayExtension = 40.0f;
UTIL_ClipTraceToPlayers( vecSrc, vecEnd + vForward * rayExtension, iTraceMask, &filter, &tr );
if ( tr.fraction >= 1.0 )
{
Vector head_hull_mins( -16, -16, -18 );
Vector head_hull_maxs( 16, 16, 18 );
UTIL_TraceHull( vecSrc, vecEnd, head_hull_mins, head_hull_maxs, MASK_SOLID, &filter, &tr );
if ( tr.fraction < 1.0 )
{
// Calculate the point of intersection of the line (or hull) and the object we hit
// This is and approximation of the "best" intersection
CBaseEntity *pHit = tr.m_pEnt;
if ( !pHit || pHit->IsBSPModel() )
FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer );
vecEnd = tr.endpos; // This is the point on the actual surface (the hull could have hit space)
}
}
m_trHit = tr;
if ( !m_trHit.m_pEnt || (m_trHit.surface.flags & SURF_SKY) )
return;
if ( m_trHit.fraction == 1.0 )
return;
CPASAttenuationFilter attenuationFilter( this );
attenuationFilter.UsePredictionRules();
if( m_trHit.m_pEnt->IsPlayer() )
{
if ( m_iSmackDamageType & MELEE_DMG_STRONGATTACK )
WeaponSound( SPECIAL1 );
else
WeaponSound( MELEE_HIT );
}
else
WeaponSound( MELEE_HIT_WORLD );
int iDamageType = DMG_CLUB | DMG_NEVERGIB;
#ifndef CLIENT_DLL
//if they hit the bounding box, just assume a chest hit
if( m_trHit.hitgroup == HITGROUP_GENERIC )
m_trHit.hitgroup = HITGROUP_CHEST;
float flDamage = (float)m_iSmackDamage;
CTakeDamageInfo info( pPlayer, pPlayer, flDamage, iDamageType );
if ( m_iSmackDamageType & MELEE_DMG_SECONDARYATTACK )
info.SetDamageCustom( MELEE_DMG_SECONDARYATTACK );
float flScale = (1.0f / flDamage) * dod_meleeattackforcescale.GetFloat();
Vector vecForceDir = vForward;
CalculateMeleeDamageForce( &info, vecForceDir, m_trHit.endpos, flScale );
Assert( m_trHit.m_pEnt != GetPlayerOwner() );
m_trHit.m_pEnt->DispatchTraceAttack( info, vForward, &m_trHit );
ApplyMultiDamage();
#endif
// We've gotten minidumps where this happened.
if ( !GetPlayerOwner() )
return;
CEffectData data;
data.m_vOrigin = m_trHit.endpos;
data.m_vStart = m_trHit.startpos;
data.m_nSurfaceProp = m_trHit.surface.surfaceProps;
data.m_nHitBox = m_trHit.hitbox;
#ifdef CLIENT_DLL
data.m_hEntity = m_trHit.m_pEnt->GetRefEHandle();
#else
data.m_nEntIndex = m_trHit.m_pEnt->entindex();
#endif
CPASFilter effectfilter( data.m_vOrigin );
#ifndef CLIENT_DLL
effectfilter.RemoveRecipient( GetPlayerOwner() );
#endif
data.m_vAngles = GetPlayerOwner()->GetAbsAngles();
data.m_fFlags = 0x1; //IMPACT_NODECAL;
data.m_nDamageType = iDamageType;
bool bHitPlayer = m_trHit.m_pEnt && m_trHit.m_pEnt->IsPlayer();
// don't do any impacts if we hit a teammate and ff is off
if ( bHitPlayer &&
m_trHit.m_pEnt->GetTeamNumber() == GetPlayerOwner()->GetTeamNumber() &&
!friendlyfire.GetBool() )
return;
if ( bHitPlayer )
{
te->DispatchEffect( effectfilter, 0.0, data.m_vOrigin, "Impact", data );
}
else if ( m_iSmackDamageType & MELEE_DMG_EDGE )
{
data.m_nDamageType = DMG_SLASH;
te->DispatchEffect( effectfilter, 0.0, data.m_vOrigin, "KnifeSlash", data );
}
}
#if defined( CLIENT_DLL )
float g_lateralBob = 0;
float g_verticalBob = 0;
static ConVar cl_bobcycle( "cl_bobcycle","0.8" );
static ConVar cl_bob( "cl_bob","0.002" );
static ConVar cl_bobup( "cl_bobup","0.5" );
// Register these cvars if needed for easy tweaking
static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2"/*, FCVAR_UNREGISTERED*/ );
static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5"/*, FCVAR_UNREGISTERED*/ );
static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1"/*, FCVAR_UNREGISTERED*/ );
static ConVar v_iyaw_level( "v_iyaw_level", "0.3"/*, FCVAR_UNREGISTERED*/ );
static ConVar v_iroll_level( "v_iroll_level", "0.1"/*, FCVAR_UNREGISTERED*/ );
static ConVar v_ipitch_level( "v_ipitch_level", "0.3"/*, FCVAR_UNREGISTERED*/ );
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float CWeaponDODBase::CalcViewmodelBob( void )
{
static float bobtime;
static float lastbobtime;
static float lastspeed;
float cycle;
CBasePlayer *player = ToBasePlayer( GetOwner() );
//Assert( player );
//NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it
if ( ( !gpGlobals->frametime ) || ( player == NULL ) )
{
//NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!)
return 0.0f;// just use old value
}
//Find the speed of the player
float speed = player->GetLocalVelocity().Length2D();
float flmaxSpeedDelta = MAX( 0, (gpGlobals->curtime - lastbobtime) * 320.0f );
// don't allow too big speed changes
speed = clamp( speed, lastspeed-flmaxSpeedDelta, lastspeed+flmaxSpeedDelta );
speed = clamp( speed, -320, 320 );
lastspeed = speed;
//FIXME: This maximum speed value must come from the server.
// MaxSpeed() is not sufficient for dealing with sprinting - jdw
float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f );
bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset;
lastbobtime = gpGlobals->curtime;
//Calculate the vertical bob
cycle = bobtime - (int)(bobtime/cl_bobcycle.GetFloat())*cl_bobcycle.GetFloat();
cycle /= cl_bobcycle.GetFloat();
if ( cycle < cl_bobup.GetFloat() )
{
cycle = M_PI * cycle / cl_bobup.GetFloat();
}
else
{
cycle = M_PI + M_PI*(cycle-cl_bobup.GetFloat())/(1.0 - cl_bobup.GetFloat());
}
g_verticalBob = speed*0.005f;
g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle);
g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f );
//Calculate the lateral bob
cycle = bobtime - (int)(bobtime/cl_bobcycle.GetFloat()*2)*cl_bobcycle.GetFloat()*2;
cycle /= cl_bobcycle.GetFloat()*2;
if ( cycle < cl_bobup.GetFloat() )
{
cycle = M_PI * cycle / cl_bobup.GetFloat();
}
else
{
cycle = M_PI + M_PI*(cycle-cl_bobup.GetFloat())/(1.0 - cl_bobup.GetFloat());
}
g_lateralBob = speed*0.005f;
g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle);
g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f );
//NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!)
return 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &origin -
// &angles -
// viewmodelindex -
//-----------------------------------------------------------------------------
void CWeaponDODBase::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles )
{
Vector forward, right;
AngleVectors( angles, &forward, &right, NULL );
CalcViewmodelBob();
// Apply bob, but scaled down to 40%
VectorMA( origin, g_verticalBob * 0.4f, forward, origin );
// Z bob a bit more
origin[2] += g_verticalBob * 0.1f;
// bob the angles
angles[ ROLL ] += g_verticalBob * 0.5f;
angles[ PITCH ] -= g_verticalBob * 0.4f;
angles[ YAW ] -= g_lateralBob * 0.3f;
// VectorMA( origin, g_lateralBob * 0.2f, right, origin );
}
#include "c_te_effect_dispatch.h"
#define NUM_MUZZLE_FLASH_TYPES 4
bool CWeaponDODBase::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options )
{
if( event == 5001 )
{
if ( ShouldDrawMuzzleFlash() )
{
Assert( GetOwnerEntity() == C_BasePlayer::GetLocalPlayer() );
const char *pszMuzzleFlashEffect;
switch( GetDODWpnData().m_iMuzzleFlashType )
{
case DOD_MUZZLEFLASH_PISTOL:
pszMuzzleFlashEffect = "view_muzzle_pistols";
break;
case DOD_MUZZLEFLASH_AUTO:
pszMuzzleFlashEffect = "view_muzzle_fullyautomatic";
break;
case DOD_MUZZLEFLASH_RIFLE:
pszMuzzleFlashEffect = "view_muzzle_rifles";
break;
case DOD_MUZZLEFLASH_MG:
pszMuzzleFlashEffect = "view_muzzle_miniguns";
break;
case DOD_MUZZLEFLASH_ROCKET:
pszMuzzleFlashEffect = "view_muzzle_rockets";
break;
case DOD_MUZZLEFLASH_MG42:
pszMuzzleFlashEffect = "view_muzzle_mg42";
break;
default:
pszMuzzleFlashEffect = NULL;
break;
}
if ( pszMuzzleFlashEffect )
{
pViewModel->ParticleProp()->Create( pszMuzzleFlashEffect, PATTACH_POINT_FOLLOW, 1 );
}
}
return true;
}
else if( event == 6002 )
{
CEffectData data;
data.m_nHitBox = atoi( options );
data.m_hEntity = GetPlayerOwner() ? GetPlayerOwner()->GetRefEHandle() : INVALID_EHANDLE_INDEX;
pViewModel->GetAttachment( 2, data.m_vOrigin, data.m_vAngles );
DispatchEffect( "DOD_EjectBrass", data );
return true;
}
return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options );
}
bool CWeaponDODBase::ShouldAutoEjectBrass( void )
{
// Don't eject brass if further than N units from the local player
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pLocalPlayer )
return true;
float flMaxDistSqr = 250;
flMaxDistSqr *= flMaxDistSqr;
float flDistSqr = pLocalPlayer->EyePosition().DistToSqr( GetAbsOrigin() );
return ( flDistSqr < flMaxDistSqr );
}
bool CWeaponDODBase::GetEjectBrassShellType( void )
{
return 1;
}
void CWeaponDODBase::SetUseAltModel( bool bUseAltModel )
{
m_bUseAltWeaponModel = bUseAltModel;
}
void CWeaponDODBase::CheckForAltWeapon( int iCurrentState )
{
int iCriteria = GetDODWpnData().m_iAltWpnCriteria;
bool bUseAltModel = false;
if( ( iCriteria & iCurrentState ) != 0 )
bUseAltModel = true;
SetUseAltModel( bUseAltModel );
}
Vector CWeaponDODBase::GetDesiredViewModelOffset( C_DODPlayer *pOwner )
{
Vector viewOffset = pOwner->GetViewOffset();
float flPercent = ( viewOffset.z - VEC_PRONE_VIEW_SCALED( pOwner ).z ) / ( VEC_VIEW_SCALED( pOwner ).z - VEC_PRONE_VIEW_SCALED( pOwner ).z );
return ( flPercent * GetDODWpnData().m_vecViewNormalOffset +
( 1.0 - flPercent ) * GetDODWpnData().m_vecViewProneOffset );
}
bool CWeaponDODBase::ShouldDraw( void )
{
if ( GetModel() == NULL )
{
// XXX(johns): Removed, doesn't seem to be the proper spot for this warning given that weapons can call
// ShouldDraw before their properties are filled.
// C_DODPlayer *pPlayer = C_DODPlayer::GetLocalDODPlayer();
// Warning( "BADNESS! Tell Matt that the weapon '%s' tried to draw with a null model ( %d, %d, %s ) \n",
// GetDODWpnData().szClassName,
// m_iWorldModelIndex.Get(), m_iReloadModelIndex.Get(), m_bUseAltWeaponModel ? "alt" : "not alt" );
return false;
}
return BaseClass::ShouldDraw();
}
#else
void CWeaponDODBase::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles )
{
}
float CWeaponDODBase::CalcViewmodelBob( void )
{
return 0.0f;
}
void CWeaponDODBase::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( CanDrop() == false )
return;
CDODPlayer *pPlayer = ToDODPlayer( pActivator );
if ( pPlayer )
{
pPlayer->PickUpWeapon( this );
}
}
#endif