diff options
Diffstat (limited to 'game/shared/tf/tf_weapon_bat.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_bat.cpp | 1294 |
1 files changed, 1294 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_bat.cpp b/game/shared/tf/tf_weapon_bat.cpp new file mode 100644 index 0000000..8413426 --- /dev/null +++ b/game/shared/tf/tf_weapon_bat.cpp @@ -0,0 +1,1294 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "tf_weapon_bat.h" +#include "decals.h" + +// Client specific. +#ifdef CLIENT_DLL +#include "c_basedoor.h" +#include "c_tf_player.h" +#include "IEffects.h" +#include "bone_setup.h" +#include "c_tf_gamestats.h" +// Server specific. +#else +#include "doors.h" +#include "tf_player.h" +#include "tf_ammo_pack.h" +#include "tf_gamestats.h" +#include "ilagcompensationmanager.h" +#include "collisionutils.h" +#include "particle_parse.h" +#include "tf_projectile_base.h" +#include "tf_gamerules.h" +#endif + +const float DEFAULT_ORNAMENT_EXPLODE_RADIUS = 50.0f; +const float DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT = 0.9f; + +//============================================================================= +// +// Weapon Bat tables. +// + +// TFBat -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFBat, DT_TFWeaponBat ) + +BEGIN_NETWORK_TABLE( CTFBat, DT_TFWeaponBat ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFBat ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_bat, CTFBat ); +PRECACHE_WEAPON_REGISTER( tf_weapon_bat ); +// -- TFBat + + +// TFBat_Fish -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Fish, DT_TFWeaponBat_Fish ) + +BEGIN_NETWORK_TABLE( CTFBat_Fish, DT_TFWeaponBat_Fish ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFBat_Fish ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_bat_fish, CTFBat_Fish ); +PRECACHE_WEAPON_REGISTER( tf_weapon_bat_fish ); +// -- TFBat_Fish + + +// TFBat_Wood -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Wood, DT_TFWeaponBat_Wood ) + +BEGIN_NETWORK_TABLE( CTFBat_Wood, DT_TFWeaponBat_Wood ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFBat_Wood ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_bat_wood, CTFBat_Wood ); +PRECACHE_WEAPON_REGISTER( tf_weapon_bat_wood ); +// -- TFBat_Wood + + +// CTFBat_Giftwrap -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Giftwrap, DT_TFWeaponBat_Giftwrap ) + +BEGIN_NETWORK_TABLE( CTFBat_Giftwrap, DT_TFWeaponBat_Giftwrap ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFBat_Giftwrap ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_bat_giftwrap, CTFBat_Giftwrap ); +PRECACHE_WEAPON_REGISTER( tf_weapon_bat_giftwrap ); +// -- CTFBat_Giftwrap + + +// TFStunBall -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFStunBall, DT_TFProjectile_StunBall ) +BEGIN_NETWORK_TABLE( CTFStunBall, DT_TFProjectile_StunBall ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( tf_projectile_stun_ball, CTFStunBall ); +PRECACHE_WEAPON_REGISTER( tf_projectile_stun_ball ); + +#define TF_WEAPON_STUNBALL_VM_MODEL "models/weapons/v_models/v_baseball.mdl" +#define TF_WEAPON_STUNBALL_MODEL "models/weapons/w_models/w_baseball.mdl" + +#if defined( GAME_DLL ) +ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY ); +ConVar tf_scout_stunball_base_speed( "tf_scout_stunball_base_speed", "3000", FCVAR_DEVELOPMENTONLY ); +ConVar sv_proj_stunball_damage( "sv_proj_stunball_damage", "15", FCVAR_DEVELOPMENTONLY ); +#endif +// -- TFStunBall + + +// CTFBall_Ornament -- +IMPLEMENT_NETWORKCLASS_ALIASED( TFBall_Ornament, DT_TFProjectileBall_Ornament ) +BEGIN_NETWORK_TABLE( CTFBall_Ornament, DT_TFProjectileBall_Ornament ) +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( tf_projectile_ball_ornament, CTFBall_Ornament ); +PRECACHE_WEAPON_REGISTER( tf_projectile_ball_ornament ); + +#define TF_WEAPON_BALL_ORNAMENT_VM_MODEL "models/weapons/c_models/c_xms_festive_ornament.mdl" +#define TF_WEAPON_BALL_ORNAMENT_MODEL "models/weapons/c_models/c_xms_festive_ornament.mdl" + +#if defined( GAME_DLL ) +//ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY ); +#endif +// -- CTFBall_Ornament + + + +static string_t s_iszTrainName; + +//============================================================================= +#define STUNBALL_TRAIL_ALPHA 128 + + +//============================================================================= +// +// CTFBat +// + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFBat::CTFBat() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat::Smack( void ) +{ + BaseClass::Smack(); + +#ifdef GAME_DLL + if ( BatDeflects() ) + { +#ifdef TF_RAID_MODE + if ( TFGameRules()->IsRaidMode() ) + { + } + else +#endif // TF_RAID_MODE + { + DeflectProjectiles(); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat::PlayDeflectionSound( bool bPlayer ) +{ + WeaponSound( MELEE_HIT_WORLD ); +} + +//============================================================================= +// +// CTFBat_Wood +// + +CTFBat_Wood::CTFBat_Wood() +{ + m_iEnemyBallID = 0; +#ifdef CLIENT_DLL + m_hStunBallVM = NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar tf_scout_bat_launch_delay( "tf_scout_bat_launch_delay", "0.1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::LaunchBallThink( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return; + + LaunchBall(); + +#ifdef GAME_DLL + pPlayer->SpeakWeaponFire( MP_CONCEPT_BAT_BALL ); + CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); +#endif +#ifdef CLIENT_DLL + C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::SecondaryAttackAnim( CTFPlayer *pPlayer ) +{ + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY ); +} + +// SERVER ONLY -- +#ifdef GAME_DLL + +//----------------------------------------------------------------------------- +// Purpose: Calculate the ball's initial position, angle, and velocity. +//----------------------------------------------------------------------------- +void CTFBat_Wood::GetBallDynamics( Vector& vecLoc, QAngle& vecAngles, Vector& vecVelocity, AngularImpulse& angImpulse, CTFPlayer* pPlayer ) +{ + Vector vecForward, vecUp; + AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp ); + vecLoc = pPlayer->GetAbsOrigin() + pPlayer->GetModelScale() * ( Vector( 0, 0, 50 ) + vecForward * 32.f ); + vecAngles = pPlayer->GetAbsAngles(); + + // Calculate the initial impulse on the item. + vecVelocity = Vector( 0.0f, 0.0f, 0.0f ); + vecVelocity += vecForward * 10; + vecVelocity += vecUp * 1; + VectorNormalize( vecVelocity ); + vecVelocity *= tf_scout_stunball_base_speed.GetInt(); + + angImpulse = AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ); +} + +// -- SERVER ONLY +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::SecondaryAttack( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CanAttack() ) + return; + + if ( m_flNextPrimaryAttack > gpGlobals->curtime ) + return; + + // Do we have any balls? If so, use them. + int iBallCount = pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ); + if ( (iBallCount > 0) && CanCreateBall( pPlayer ) ) + { + SecondaryAttackAnim( pPlayer ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + SetContextThink( &CTFBat_Wood::LaunchBallThink, gpGlobals->curtime + tf_scout_bat_launch_delay.GetFloat(), "LAUNCH_BALL_THINK" ); + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.25; + +#ifdef GAME_DLL + if ( pPlayer->m_Shared.IsStealthed() ) + { + pPlayer->RemoveInvisibility(); + } +#endif // GAME_DLL + } +} + +//----------------------------------------------------------------------------- +// Purpose: Client Only. Show the stunball view model if necessary. +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +void CTFBat_Wood::SetWeaponVisible( bool visible ) +{ + BaseClass::SetWeaponVisible( visible ); + + if ( !m_hStunBallVM ) + return; + + if ( visible ) + { + m_hStunBallVM->RemoveEffects( EF_NODRAW ); + } + else + { + m_hStunBallVM->AddEffects( EF_NODRAW ); + } +} +#endif + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + bool bLocalPlayerAmmo = true; + if ( GetPlayerOwner() == C_BasePlayer::GetLocalPlayer() ) + { + bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0; + } + + if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true ) + { + AddBallChild(); + } + else + { + RemoveBallChild(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Client Only. Show the stunball view model if necessary. +//----------------------------------------------------------------------------- +void CTFBat_Wood::AddBallChild( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !pPlayer->IsLocalPlayer() ) + return; + + if ( !pPlayer->GetViewModel() ) + return; + + if ( m_hStunBallVM ) + return; + + CTFViewModel* pBall = new class CTFViewModel(); + if ( pBall != NULL ) + { + pBall->InitializeAsClientEntity( GetBallViewModelName(), RENDER_GROUP_OPAQUE_ENTITY ); + pBall->SetAbsOrigin( pPlayer->GetViewModel()->GetAbsOrigin() ); + pBall->SetModel( GetBallViewModelName() ); + pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0; + + CStudioHdr *pStudioHdr = pPlayer->GetViewModel()->GetModelPtr(); + if ( pStudioHdr ) + { + int iAttachment = Studio_FindAttachment( pStudioHdr, "weapon_bone_L" ) + 1; + pBall->SetParent( pPlayer->GetViewModel(), iAttachment ); + } + pBall->AddEffects( EF_BONEMERGE ); + pBall->SetMoveType( MOVETYPE_NONE ); + pBall->AddSolidFlags( FSOLID_NOT_SOLID ); + m_hStunBallVM.Set( pBall ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::Drop( const Vector &vecVelocity ) +{ + BaseClass::Drop( vecVelocity ); + + RemoveBallChild(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::WeaponReset( void ) +{ + RemoveBallChild(); + + BaseClass::WeaponReset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::UpdateOnRemove( void ) +{ + RemoveBallChild(); + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::RemoveBallChild() +{ + if ( m_hStunBallVM ) + { + m_hStunBallVM->Remove(); + m_hStunBallVM = NULL; + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Determines if there is space to create a ball. +//----------------------------------------------------------------------------- +bool CTFBat_Wood::CanCreateBall( CTFPlayer* pPlayer ) +{ + int iWeaponMod = 0; + CALL_ATTRIB_HOOK_INT( iWeaponMod, set_weapon_mode ); + if ( iWeaponMod == 0 ) + return false; + + if ( pPlayer->GetWaterLevel() == WL_Eyes ) + return false; + + Vector vecForward, vecUp; + AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp ); + Vector vecBallStart = pPlayer->GetAbsOrigin() + Vector( 0, 0, 50 ); + Vector vecBallEnd = vecBallStart + vecForward * 32.f; + + // Trace out and see if we hit a wall. + trace_t trace; + CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); + UTIL_TraceHull( vecBallStart, vecBallEnd, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); + if ( trace.DidHitWorld() || trace.startsolid ) + return false; + else + { + if ( trace.m_pEnt ) + { + // Don't let the player bat through doors. + CBaseDoor *pDoor = dynamic_cast<CBaseDoor*>( trace.m_pEnt ); + if ( pDoor ) + return false; + } + return true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Wood::LaunchBall( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return; + +#if GAME_DLL + // Make a ball. + CBaseEntity* pBall = CreateBall(); + if ( !pBall ) + return; + + if ( IsCurrentAttackACrit() ) + { + WeaponSound( BURST ); + } + WeaponSound( SPECIAL2 ); + pPlayer->RemoveAmmo( 1, TF_AMMO_GRENADES1 ); +#endif + + StartEffectBarRegen(); +} + +// SERVER ONLY -- +#ifdef GAME_DLL + +//----------------------------------------------------------------------------- +// Purpose: The wooden bat creates a baseball that stuns whomever it hits. +//----------------------------------------------------------------------------- +CBaseEntity* CTFBat_Wood::CreateBall( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return NULL; + + // Do another check here, as the player may have moved to an invalid position + // since the first check (0.1 seconds ago). This fixes the ball sometimes + // going through thin geometry, such as windows and spawn blockers. + if ( !CanCreateBall( pPlayer ) ) + return NULL; + + // Determine the ball's initial location, angles, and velocity. + Vector vecLocation, vecVelocity; + QAngle vecAngles; + AngularImpulse angImpulse; + GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer ); + + // Create a stun ball. + CTFStunBall* pBall = CTFStunBall::Create( vecLocation, vecAngles, pPlayer ); + Assert( pBall ); + if ( !pBall ) + return NULL; + + CalcIsAttackCritical(); + + pBall->m_iOriginalOwnerID = m_iEnemyBallID; + m_iEnemyBallID = 0; + + pBall->SetCritical( IsCurrentAttackACrit() ); + pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() ); + pBall->SetLauncher( this ); + pBall->SetOwnerEntity( pPlayer ); + pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() ); + + return pBall; +} + +// -- SERVER ONLY +#endif + +//----------------------------------------------------------------------------- +// Purpose: Play pickup anim when we grab a new ball. +//----------------------------------------------------------------------------- +void CTFBat_Wood::PickedUpBall( void ) +{ + if ( WeaponState() == WEAPON_IS_ACTIVE ) + { + SendWeaponAnim( ACT_VM_PULLBACK_SPECIAL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play animation appropriate to ball status. +//----------------------------------------------------------------------------- +bool CTFBat_Wood::SendWeaponAnim( int iActivity ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return BaseClass::SendWeaponAnim( iActivity ); + + if ( pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0 ) + { + switch ( iActivity ) + { + case ACT_VM_DRAW: + iActivity = ACT_VM_DRAW_SPECIAL; + break; + case ACT_VM_HOLSTER: + iActivity = ACT_VM_HOLSTER_SPECIAL; + break; + case ACT_VM_IDLE: + iActivity = ACT_VM_IDLE_SPECIAL; + break; + case ACT_VM_PULLBACK: + iActivity = ACT_VM_PULLBACK_SPECIAL; + break; + case ACT_VM_PRIMARYATTACK: + iActivity = ACT_VM_PRIMARYATTACK_SPECIAL; + break; + case ACT_VM_SECONDARYATTACK: + iActivity = ACT_VM_PRIMARYATTACK_SPECIAL; + break; + case ACT_VM_HITCENTER: + iActivity = ACT_VM_HITCENTER_SPECIAL; + break; + case ACT_VM_SWINGHARD: + iActivity = ACT_VM_SWINGHARD_SPECIAL; + break; + case ACT_VM_IDLE_TO_LOWERED: + iActivity = ACT_VM_IDLE_TO_LOWERED_SPECIAL; + break; + case ACT_VM_IDLE_LOWERED: + iActivity = ACT_VM_IDLE_LOWERED_SPECIAL; + break; + case ACT_VM_LOWERED_TO_IDLE: + iActivity = ACT_VM_LOWERED_TO_IDLE_SPECIAL; + break; + default: + break; + } + } + + return BaseClass::SendWeaponAnim( iActivity ); +} + +//============================================================================= +// +// CTFStunBall +// + +// SERVER ONLY -- +#ifdef GAME_DLL + +CTFStunBall::CTFStunBall() +{ + s_iszTrainName = AllocPooledString( "models/props_vehicles/train_enginecar.mdl" ); + m_iOriginalOwnerID = 0; + m_pBallTrail = NULL; + m_flBallTrailLife = 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Static entity factory. +//----------------------------------------------------------------------------- +CTFStunBall* CTFStunBall::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner ) +{ + CTFStunBall* pBall = static_cast<CTFStunBall*>( CBaseAnimating::CreateNoSpawn( "tf_projectile_stun_ball", vecOrigin, vecAngles, pOwner ) ); + if ( pBall ) + { + DispatchSpawn( pBall ); + } + + return pBall; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStunBall::Precache( void ) +{ + PrecacheModel( GetBallModelName() ); + PrecacheModel( GetBallViewModelName() ); + PrecacheModel( "effects/baseballtrail_red.vmt" ); + PrecacheModel( "effects/baseballtrail_blu.vmt" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +const char *CTFStunBall::GetBallModelName( void ) const +{ + return TF_WEAPON_STUNBALL_MODEL; +} + + +//----------------------------------------------------------------------------- +const char *CTFStunBall::GetBallViewModelName( void ) const +{ + return TF_WEAPON_STUNBALL_VM_MODEL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets up initial properties. +//----------------------------------------------------------------------------- +void CTFStunBall::Spawn( void ) +{ + BaseClass::Spawn(); + + SetModel( GetBallModelName() ); + VPhysicsDestroyObject(); + VPhysicsInitNormal( SOLID_BBOX, 0, false ); + + AddSolidFlags( FSOLID_TRIGGER ); + AddFlag( FL_GRENADE ); + + SetCollisionGroup( COLLISION_GROUP_PROJECTILE ); + m_takedamage = DAMAGE_NO; + + SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 15, "DieContext" ); + + // Draw the trail for the Baseball on spawn + if ( !m_pBallTrail ) + { + const char *pTrailTeamName = ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/baseballtrail_red.vmt" : "effects/baseballtrail_blu.vmt"; + CSpriteTrail *pTempTrail = NULL; + + pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true ); + pTempTrail->FollowEntity( this ); + pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, STUNBALL_TRAIL_ALPHA, kRenderFxNone ); + pTempTrail->SetStartWidth( 9 ); + pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); + pTempTrail->SetLifeTime( 0.4 ); + pTempTrail->TurnOn(); + pTempTrail->SetAttachment( this, 0 ); + m_pBallTrail = pTempTrail; + SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 3, "FadeBallTrail"); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStunBall::Explode( trace_t *pTrace, int bitsDamageType ) +{ + if ( !IsAllowedToExplode() ) + return; + + BaseClass::Explode( pTrace, bitsDamageType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Stun the person we smashed into. +//----------------------------------------------------------------------------- +#define FLIGHT_TIME_TO_MAX_STUN 1.f +void CTFStunBall::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) + return; + + CTFPlayer* pPlayer = ToTFPlayer( pOther ); + if ( !pPlayer ) + return; + + CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + return; + + if ( m_bTouched ) + return; + + // Can't stun an invul player. + if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) + return; + + // We have a more intense stun based on our travel time. + float flLifeTime = MIN( gpGlobals->curtime - m_flCreationTime, FLIGHT_TIME_TO_MAX_STUN ); + float flLifeTimeRatio = flLifeTime / FLIGHT_TIME_TO_MAX_STUN; + if ( flLifeTimeRatio > 0.1f ) + { + + float flStun = 0.5f; + float flStunDuration = tf_scout_stunball_base_duration.GetFloat() * flLifeTimeRatio; + if ( IsCritical() ) + flStunDuration += 2.0; // Extra two seconds of effect time if we're a critical hit. + int iStunFlags = TF_STUN_LOSER_STATE | TF_STUN_MOVEMENT; + if ( flLifeTimeRatio >= 1.f ) + { + flStunDuration += 1.0; + iStunFlags = TF_STUN_CONTROLS; + iStunFlags |= TF_STUN_SPECIAL_SOUND; + CTF_GameStats.Event_PlayerStunBall( pOwner, true ); + } + else + { + CTF_GameStats.Event_PlayerStunBall( pOwner, false ); + } + + // Adjust stun amount and flags if we're hitting a boss or scaled enemy + if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && ( pPlayer->IsMiniBoss() || pPlayer->GetModelScale() > 1.0f ) ) + { + // If max range, freeze them in place - otherwise adjust it based on distance + flStun = flLifeTimeRatio >= 1.f ? 1.f : RemapValClamped( flLifeTimeRatio, 0.1f, 0.99f, 0.5f, 0.75 ); + iStunFlags = flLifeTimeRatio >= 1.f ? ( TF_STUN_SPECIAL_SOUND | TF_STUN_MOVEMENT ) : TF_STUN_MOVEMENT; + } + + if ( pPlayer->GetWaterLevel() != WL_Eyes ) + { + pPlayer->m_Shared.StunPlayer( flStunDuration, flStun, iStunFlags, pOwner ); + if ( pPlayer->GetUserID() == m_iOriginalOwnerID ) + { + // Holy crap! We just stunned a scout with their own ball. + // Give the player an achievement for this. + if ( pOwner->IsPlayerClass( TF_CLASS_SCOUT ) ) + { + pOwner->AwardAchievement( ACHIEVEMENT_TF_SCOUT_STUN_SCOUT_WITH_THEIR_BALL ); + } + } + } + } + + // Give 'em a love tap. + const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); + trace_t *pNewTrace = const_cast<trace_t*>( pTrace ); + + CBaseEntity *pInflictor = GetLauncher(); + CTakeDamageInfo info; + info.SetAttacker( GetOwnerEntity() ); + info.SetInflictor( pInflictor ); + info.SetWeapon( pInflictor ); + info.SetDamage( GetDamage() ); + info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL ); + info.SetDamageForce( GetDamageForce() ); + info.SetDamagePosition( GetAbsOrigin() ); + int iDamageType = GetDamageType(); + if ( IsCritical() ) + iDamageType |= DMG_CRITICAL; + info.SetDamageType( iDamageType ); + + // Hurt 'em. + Vector dir; + AngleVectors( GetAbsAngles(), &dir ); + pPlayer->DispatchTraceAttack( info, dir, pNewTrace ); + ApplyMultiDamage(); + + // Make this ball fade faster now that it's hit something. + SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 4, "DieContext" ); + + m_bTouched = true; +} + +float CTFStunBall::GetDamage( void ) +{ + return sv_proj_stunball_damage.GetFloat(); +} + +Vector CTFStunBall::GetDamageForce( void ) +{ + Vector vecVelocity = GetAbsVelocity(); + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->GetVelocity( &vecVelocity, NULL ); + VectorNormalize( vecVelocity ); + } + + return (vecVelocity * GetDamage()); +} + +//----------------------------------------------------------------------------- +// Purpose: We hit something. +//----------------------------------------------------------------------------- +void CTFStunBall::PipebombTouch( CBaseEntity *pOther ) +{ + CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + return; + + if ( !pOther || !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) + { + pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED ); + return; + } + + // Go away if we're hit by a moving train. + if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) ) + { + UTIL_Remove( this ); + return; + } + + // Go away if we hit the skybox. + trace_t pTrace; + Vector velDir = GetAbsVelocity(); + VectorNormalize( velDir ); + Vector vecSpot = GetAbsOrigin() - velDir * 32; + UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); + if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY ) + { + UTIL_Remove( this ); + return; + } + + // Ignore things that aren't players. + if ( !pOther->IsPlayer() ) + return; + + // If we hit a scout, pickup as ammo + if ( m_bTouched ) + { + CTFPlayer* pPlayer = ToTFPlayer( pOther ); + if ( pPlayer && pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) && + (pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) < pPlayer->GetMaxAmmo( TF_AMMO_GRENADES1 )) ) + { + pPlayer->GiveAmmo( 1, TF_AMMO_GRENADES1 ); + RemoveBallTrail(); + UTIL_Remove( this ); + + CTFBat_Wood *pBat = (CTFBat_Wood *) pPlayer->Weapon_OwnsThisID( TF_WEAPON_BAT_WOOD ); + if ( pBat ) + { + // If this ball came from an enemy scout, remember who they were... + if ( pPlayer->GetTeamNumber() != GetTeamNumber() ) + { + if ( pOwner ) + { + pBat->m_iEnemyBallID = pOwner->GetUserID(); + } + } + + // If we have the bat up, we need to play the correct anim. + pBat->PickedUpBall(); + } + + // Say something. + pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_GRAB_BALL, (pOther->GetTeamNumber() == GetTeamNumber()) ? "my_team:1" : "my_team:0" ); + } + return; + } + + if ( pOther == GetThrower() ) + return; + + if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO ) + { + ApplyBallImpactEffectOnVictim( pOther ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: We hit something. +//----------------------------------------------------------------------------- +void CTFStunBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() ); + bool bWasTouched = m_bTouched; + BaseClass::VPhysicsCollision( index, pEvent ); + if ( pOwner && !bWasTouched && m_bTouched ) + { + pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fade and kill the trail +//----------------------------------------------------------------------------- +void CTFStunBall::RemoveBallTrail( void ) +{ + if (!m_pBallTrail) + return; + + if (m_pBallTrail) + { + if (m_flBallTrailLife <= 0) + { + UTIL_Remove( m_pBallTrail); + m_flBallTrailLife = 1.0f; + } + else + { + float fAlpha = STUNBALL_TRAIL_ALPHA * m_flBallTrailLife; + + CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pBallTrail.Get() ); + + if ( pTempTrail ) + { + pTempTrail->SetBrightness( int(fAlpha) ); + } + + m_flBallTrailLife = m_flBallTrailLife - 0.1f; + SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 0.05, "FadeBallTrail"); + } + } +} + +// -- SERVER ONLY +#endif + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFStunBall::GetTrailParticleName( void ) +{ + int iTeamNumber = GetTeamNumber(); + + if ( GetDeflected() ) + { + CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() ); + + if ( pOwner ) + { + iTeamNumber = pOwner->GetTeamNumber(); + } + } + if ( iTeamNumber == TF_TEAM_BLUE ) + { + return "stunballtrail_blue"; + } + else + { + return "stunballtrail_red"; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStunBall::CreateTrailParticles( void ) +{ + if ( pEffectTrail ) + { + ParticleProp()->StopEmission( pEffectTrail ); + } + if ( pEffectCrit ) + { + ParticleProp()->StopEmission( pEffectCrit ); + } + pEffectTrail = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW ); + int iTeamNumber = GetTeamNumber(); + + if ( GetDeflected() ) + { + CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() ); + + if ( pOwner ) + { + iTeamNumber = pOwner->GetTeamNumber(); + } + } + if ( m_bCritical ) + { + if ( iTeamNumber == TF_TEAM_BLUE ) + { + pEffectCrit = ParticleProp()->Create( "stunballtrail_blue_crit", PATTACH_ABSORIGIN_FOLLOW ); + + } + else + { + pEffectCrit = ParticleProp()->Create( "stunballtrail_red_crit", PATTACH_ABSORIGIN_FOLLOW ); + } + } +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBat_Giftwrap::Spawn( void ) +{ + BaseClass::Spawn(); + + m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0; +} + + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CTFBat_Giftwrap::CreateBall( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return NULL; + + // Do another check here, as the player may have moved to an invalid position + // since the first check (0.1 seconds ago). This fixes the ball sometimes + // going through thin geometry, such as windows and spawn blockers. + if ( !CanCreateBall( pPlayer ) ) + return NULL; + + // Determine the ball's initial location, angles, and velocity. + Vector vecLocation, vecVelocity; + QAngle vecAngles; + AngularImpulse angImpulse; + GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer ); + + // Create the ornament ball. + CTFBall_Ornament *pBall = CTFBall_Ornament::Create( vecLocation, vecAngles, pPlayer ); + Assert( pBall ); + if ( !pBall ) + return NULL; + + CalcIsAttackCritical(); + + pBall->m_iOriginalOwnerID = m_iEnemyBallID; + m_iEnemyBallID = 0; + + pBall->SetCritical( IsCurrentAttackACrit() ); + pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() ); + pBall->SetLauncher( this ); + pBall->SetOwnerEntity( pPlayer ); + pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() ); + pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0; + + return pBall; +} + + +//----------------------------------------------------------------------------- +void CTFBall_Ornament::Precache( void ) +{ + PrecacheScriptSound( "BallBuster.OrnamentImpactRange" ); + PrecacheScriptSound( "BallBuster.OrnamentImpact" ); + PrecacheScriptSound( "BallBuster.HitBall" ); + PrecacheScriptSound( "BallBuster.HitFlesh" ); + PrecacheScriptSound( "BallBuster.HitWorld" ); + PrecacheScriptSound( "BallBuster.DrawCatch" ); + PrecacheScriptSound( "BallBuster.Ornament_DrawCatch" ); + PrecacheScriptSound( "BallBuster.Ball_HitWorld" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +CTFBall_Ornament *CTFBall_Ornament::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner ) +{ + CTFBall_Ornament* pBall = static_cast< CTFBall_Ornament * >( CBaseAnimating::CreateNoSpawn( "tf_projectile_ball_ornament", vecOrigin, vecAngles, pOwner ) ); + if ( pBall ) + { + DispatchSpawn( pBall ); + } + + return pBall; +} + + +//----------------------------------------------------------------------------- +const char *CTFBall_Ornament::GetBallModelName( void ) const +{ + return TF_WEAPON_BALL_ORNAMENT_MODEL; +} + + +//----------------------------------------------------------------------------- +const char *CTFBall_Ornament::GetBallViewModelName( void ) const +{ + return TF_WEAPON_BALL_ORNAMENT_VM_MODEL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFBall_Ornament::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) + return; + + CTFPlayer* pPlayer = ToTFPlayer( pOther ); + if ( !pPlayer ) + return; + + CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + return; + + if ( m_bTouched ) + return; + + // Can't bleed an invul player. + if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) + return; + + bool bIsCriticalHit = IsCritical(); + float flBleedTime = 5.0f; + bool bIsLongRangeHit = false; + + // long distance hit is always a crit + float flLifeTime = gpGlobals->curtime - m_flCreationTime; + if ( flLifeTime >= FLIGHT_TIME_TO_MAX_STUN ) + { + bIsCriticalHit = true; + bIsLongRangeHit = true; + } + + // just do the bleed effect directly since the bleed + // attribute comes from the inflictor, which is the bat. + pPlayer->m_Shared.MakeBleed( pOwner, (CTFBat_Giftwrap *)GetLauncher(), flBleedTime ); + + // Apply particle effect to victim (the remaining effects happen inside Explode) + DispatchParticleEffect( "xms_ornament_glitter", PATTACH_POINT_FOLLOW, pPlayer, "head" ); + + // Give 'em a love tap. + const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); + trace_t *pNewTrace = const_cast<trace_t*>( pTrace ); + + CBaseEntity *pInflictor = GetLauncher(); + CTakeDamageInfo info; + info.SetAttacker( GetOwnerEntity() ); + info.SetInflictor( pInflictor ); + info.SetWeapon( pInflictor ); + info.SetDamage( GetDamage() ); + info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL ); + info.SetDamageForce( GetDamageForce() ); + info.SetDamagePosition( GetAbsOrigin() ); + int iDamageType = GetDamageType(); + if ( bIsCriticalHit ) + iDamageType |= DMG_CRITICAL; + info.SetDamageType( iDamageType ); + + // Hurt 'em. + Vector dir; + AngleVectors( GetAbsAngles(), &dir ); + pPlayer->DispatchTraceAttack( info, dir, pNewTrace ); + ApplyMultiDamage(); + + // the ball shatters + UTIL_Remove( this ); + + m_bTouched = true; +} + +void CTFBall_Ornament::PipebombTouch( CBaseEntity *pOther ) +{ + CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + return; + + // Go away if we're hit by a moving train. + if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) ) + { + UTIL_Remove( this ); + return; + } + + // Go away if we hit the skybox. + trace_t pTrace; + Vector velDir = GetAbsVelocity(); + VectorNormalize( velDir ); + Vector vecSpot = GetAbsOrigin() - velDir * 32; + UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); + if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY ) + { + UTIL_Remove( this ); + return; + } + + if ( pOther == GetThrower() ) + return; + + // Explode (does radius damage, triggers particles and sound effects). + Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE ); + + if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO ) + { + ApplyBallImpactEffectOnVictim( pOther ); + } +} + +void CTFBall_Ornament::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + BaseClass::VPhysicsCollision( index, pEvent ); + + int otherIndex = !index; + CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; + + if ( !pHitEntity ) + return; + + // Break if we hit the world. + if ( pHitEntity->IsWorld() ) + { + // Explode immediately next frame. (Can't explode in the collision callback.) + m_vCollisionVelocity = pEvent->preVelocity[index]; + SetContextThink( &CTFBall_Ornament::VPhysicsCollisionThink, gpGlobals->curtime, "OrnamentCollisionThink" ); + } +} + +void CTFBall_Ornament::VPhysicsCollisionThink( void ) +{ + trace_t pTrace; + Vector velDir = m_vCollisionVelocity; + VectorNormalize( velDir ); + Vector vecSpot = GetAbsOrigin() - velDir * 16; + UTIL_TraceLine( vecSpot, vecSpot + velDir * 32, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); + + Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE ); +} + +void CTFBall_Ornament::Explode( trace_t *pTrace, int bitsDamageType ) +{ + // Create smashed glass particles when we explode + CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( pOwner && pOwner->GetTeamNumber() == TF_TEAM_RED ) + { + DispatchParticleEffect( "xms_ornament_smash_red", GetAbsOrigin(), GetAbsAngles() ); + } + else + { + DispatchParticleEffect( "xms_ornament_smash_blue", GetAbsOrigin(), GetAbsAngles() ); + } + + Vector vecOrigin = GetAbsOrigin(); + + // sound effects + EmitSound_t params; + params.m_flSoundTime = 0; + params.m_pflSoundDuration = 0; + params.m_pSoundName = "BallBuster.OrnamentImpact"; + CPASFilter filter( vecOrigin ); + filter.RemoveRecipient( pOwner ); + EmitSound( filter, entindex(), params ); + CSingleUserRecipientFilter attackerFilter( pOwner ); + EmitSound( attackerFilter, pOwner->entindex(), params ); + + // Explosion damage is some fraction of our base damage + float flExplodeDamage = GetDamage() * DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT; + + // Do radius damage + Vector vecBlastForce(0.0f, 0.0f, 0.0f); + CTakeDamageInfo info( this, GetThrower(), m_hLauncher, vecBlastForce, GetAbsOrigin(), flExplodeDamage, bitsDamageType, TF_DMG_CUSTOM_BASEBALL, &vecOrigin ); + CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, DEFAULT_ORNAMENT_EXPLODE_RADIUS, nullptr, 0.0f, 0.0f ); + TFGameRules()->RadiusDamage( radiusinfo ); + + UTIL_Remove( this ); +} + + + +#endif + |