bool CTFWeaponBaseMelee::DoSwingTrace( trace_t &trace ) { // Setup a volume for the melee weapon to be swung - approx size, so all melee behave the same. static Vector vecSwingMins( -18, -18, -18 ); static Vector vecSwingMaxs( 18, 18, 18 ); // Get the current player. CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return false; // Setup the swing range. Vector vecForward; AngleVectors( pPlayer->EyeAngles(), &vecForward ); Vector vecSwingStart = pPlayer->Weapon_ShootPosition(); Vector vecSwingEnd = vecSwingStart + vecForward * 48; // See if we hit anything. UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); if ( trace.fraction >= 1.0 ) { UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); if ( trace.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 = trace.m_pEnt; if ( !pHit || pHit->IsBSPModel() ) { // Why duck hull min/max? FindHullIntersection( vecSwingStart, trace, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer ); } // This is the point on the actual surface (the hull could have hit space) vecSwingEnd = trace.endpos; } } return ( trace.fraction < 1.0f ); }
// ----------------------------------------------------------------------------- // Purpose: // Note: Think function to delay the impact decal until the animation is finished // playing. // ----------------------------------------------------------------------------- void CTFWeaponBaseMelee::Smack( void ) { trace_t trace; CBasePlayer *pPlayer = GetPlayerOwner(); if ( !pPlayer ) return; #if !defined (CLIENT_DLL) // Move other players back to history positions based on local player's lag lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() ); #endif // We hit, setup the smack. if ( DoSwingTrace( trace ) ) { // Hit sound - immediate. if( trace.m_pEnt->IsPlayer() ) { WeaponSound( MELEE_HIT ); } else { WeaponSound( MELEE_HIT_WORLD ); } // Get the current player. CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; Vector vecForward; AngleVectors( pPlayer->EyeAngles(), &vecForward ); Vector vecSwingStart = pPlayer->Weapon_ShootPosition(); Vector vecSwingEnd = vecSwingStart + vecForward * 48; #ifndef CLIENT_DLL // Do Damage. int iCustomDamage = TF_DMG_CUSTOM_NONE; float flDamage = GetMeleeDamage( trace.m_pEnt, iCustomDamage ); int iDmgType = DMG_BULLET | DMG_NEVERGIB | DMG_CLUB; if ( IsCurrentAttackACrit() ) { // TODO: Not removing the old critical path yet, but the new custom damage is marking criticals as well for melee now. iDmgType |= DMG_CRITICAL; } CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon(); CTFUbersaw *pUbersaw = dynamic_cast<CTFUbersaw*>(pWpn); CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun*>(pPlayer->Weapon_OwnsThisID(TF_WEAPON_MEDIGUN)); if (pMedigun && pUbersaw) { if(trace.m_pEnt->IsPlayer() && trace.m_pEnt->GetTeamNumber() != pPlayer->GetTeamNumber()) { pMedigun->AddCharge(); } } CWeaponKritzkrieg *pKritzkrieg = static_cast<CWeaponKritzkrieg*>(pPlayer->Weapon_OwnsThisID(TF_WEAPON_KRITZKRIEG)); if (pKritzkrieg && pUbersaw) { if(trace.m_pEnt->IsPlayer() && trace.m_pEnt->GetTeamNumber() != pPlayer->GetTeamNumber()) { pKritzkrieg->AddCharge(); } } CTakeDamageInfo info( pPlayer, pPlayer, flDamage, iDmgType, iCustomDamage ); CalculateMeleeDamageForce( &info, vecForward, vecSwingEnd, 1.0f / flDamage * tf_meleeattackforcescale.GetFloat() ); trace.m_pEnt->DispatchTraceAttack( info, vecForward, &trace ); ApplyMultiDamage(); OnEntityHit( trace.m_pEnt ); #endif // Don't impact trace friendly players or objects if ( trace.m_pEnt && trace.m_pEnt->GetTeamNumber() != pPlayer->GetTeamNumber() ) { #ifdef CLIENT_DLL UTIL_ImpactTrace( &trace, DMG_CLUB ); #endif m_bConnected = true; } } #if !defined (CLIENT_DLL) lagcompensation->FinishLagCompensation( pPlayer ); #endif }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::SecondaryAttack() { if ( !tf2c_airblast.GetBool() ) return; int iNoAirblast = 0; CALL_ATTRIB_HOOK_FLOAT( iNoAirblast, set_flamethrower_push_disabled ); if ( iNoAirblast ) return; // Get the player owning the weapon. CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); if ( !pOwner ) return; if ( !CanAttack() ) { m_iWeaponState = FT_STATE_IDLE; return; } #ifdef CLIENT_DLL StopFlame(); #endif m_iWeaponState = FT_STATE_AIRBLASTING; SendWeaponAnim( ACT_VM_SECONDARYATTACK ); WeaponSound( WPN_DOUBLE ); #ifdef CLIENT_DLL if ( prediction->IsFirstTimePredicted() ) { StartFlame(); } #else // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation. pOwner->NoteWeaponFired(); pOwner->SpeakWeaponFire(); CTF_GameStats.Event_PlayerFiredWeapon( pOwner, false ); // Move other players back to history positions based on local player's lag lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); Vector vecDir; QAngle angDir = pOwner->EyeAngles(); AngleVectors( angDir, &vecDir ); const Vector vecBlastSize = Vector( 128, 128, 64 ); // Picking max out of length, width, height for airblast distance. float flBlastDist = max( max( vecBlastSize.x, vecBlastSize.y ), vecBlastSize.z ); Vector vecOrigin = pOwner->Weapon_ShootPosition() + vecDir * flBlastDist; CBaseEntity *pList[64]; int count = UTIL_EntitiesInBox( pList, 64, vecOrigin - vecBlastSize, vecOrigin + vecBlastSize, 0 ); if ( tf2c_debug_airblast.GetBool() ) { NDebugOverlay::Box( vecOrigin, -vecBlastSize, vecBlastSize, 0, 0, 255, 100, 2.0 ); } for ( int i = 0; i < count; i++ ) { CBaseEntity *pEntity = pList[i]; if ( !pEntity ) continue; if ( pEntity == pOwner ) continue; if ( !pEntity->IsDeflectable() ) continue; // Make sure we can actually see this entity so we don't hit anything through walls. trace_t tr; UTIL_TraceLine( pOwner->Weapon_ShootPosition(), pEntity->WorldSpaceCenter(), MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr ); if ( tr.fraction != 1.0f ) continue; if ( pEntity->IsPlayer() ) { if ( !pEntity->IsAlive() ) continue; CTFPlayer *pTFPlayer = ToTFPlayer( pEntity ); Vector vecPushDir; QAngle angPushDir = angDir; // Push them at least 45 degrees up. angPushDir[PITCH] = min( -45, angPushDir[PITCH] ); AngleVectors( angPushDir, &vecPushDir ); DeflectPlayer( pTFPlayer, pOwner, vecPushDir ); } else { // Deflect projectile to the point that we're aiming at, similar to rockets. Vector vecPos = pEntity->GetAbsOrigin(); Vector vecDeflect; GetProjectileReflectSetup( GetTFPlayerOwner(), vecPos, &vecDeflect, false ); DeflectEntity( pEntity, pOwner, vecDeflect ); } } lagcompensation->FinishLagCompensation( pOwner ); #endif float flAmmoPerSecondaryAttack = TF_FLAMETHROWER_AMMO_PER_SECONDARY_ATTACK; CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecondaryAttack, mult_airblast_cost ); pOwner->RemoveAmmo( flAmmoPerSecondaryAttack, m_iPrimaryAmmoType ); // Don't allow firing immediately after airblasting. m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 0.75f; }
//----------------------------------------------------------------------------- // Purpose: Find a place in the world where we should try to build this object //----------------------------------------------------------------------------- bool CBaseObject::CalculatePlacementPos( void ) { CTFPlayer *pPlayer = GetOwner(); if ( !pPlayer ) return false; // Calculate build angles Vector forward; QAngle vecAngles = vec3_angle; vecAngles.y = pPlayer->EyeAngles().y; QAngle objAngles = vecAngles; SetAbsAngles( objAngles ); UpdateDesiredBuildRotation( gpGlobals->frametime ); objAngles.y = objAngles.y + m_flCurrentBuildRotation; SetLocalAngles( objAngles ); AngleVectors(vecAngles, &forward ); // Adjust build distance based upon object size Vector2D vecObjectRadius; vecObjectRadius.x = max( fabs( m_vecBuildMins.m_Value.x ), fabs( m_vecBuildMaxs.m_Value.x ) ); vecObjectRadius.y = max( fabs( m_vecBuildMins.m_Value.y ), fabs( m_vecBuildMaxs.m_Value.y ) ); Vector2D vecPlayerRadius; Vector vecPlayerMins = pPlayer->WorldAlignMins(); Vector vecPlayerMaxs = pPlayer->WorldAlignMaxs(); vecPlayerRadius.x = max( fabs( vecPlayerMins.x ), fabs( vecPlayerMaxs.x ) ); vecPlayerRadius.y = max( fabs( vecPlayerMins.y ), fabs( vecPlayerMaxs.y ) ); float flDistance = vecObjectRadius.Length() + vecPlayerRadius.Length() + 4; // small safety buffer Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + forward * flDistance; m_vecBuildOrigin = vecBuildOrigin; Vector vErrorOrigin = vecBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins; Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins; Vector vHalfBuildDims = vBuildDims * 0.5; Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 ); // Here, we start at the highest Z we'll allow for the top of the object. Then // we sweep an XY cross section downwards until it hits the ground. // // The rule is that the top of to box can't go lower than the player's feet, and the bottom of the // box can't go higher than the player's head. // // To simplify things in here, we treat the box as though it's symmetrical about all axes // (so mins = -maxs), then reoffset the box at the very end. Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f; float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z; float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z; // First, find the ground (ie: where the bottom of the box goes). trace_t tr; float bottomZ = 0; int nIterations = 8; float topZ = flBoxTopZ; float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1); int iIteration; for ( iIteration = 0; iIteration < nIterations; iIteration++ ) { UTIL_TraceHull( Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ), Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ), -vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); bottomZ = tr.endpos.z; // If there is no ground, then we can't place here. if ( tr.fraction == 1 ) { m_vecBuildOrigin = vErrorOrigin; return false; } // if we found enough space to fit our object, place here if ( topZ - bottomZ > vBuildDims.z && !tr.startsolid ) { break; } topZ += topZInc; } if ( iIteration == nIterations ) { m_vecBuildOrigin = vErrorOrigin; return false; } // Now see if the range we've got leaves us room for our box. if ( topZ - bottomZ < vBuildDims.z ) { m_vecBuildOrigin = vErrorOrigin; return false; } // Verify that it's not on too much of a slope by seeing how far the corners are from the ground. Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ ); if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) || !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) || !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) || !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) ) { m_vecBuildOrigin = vErrorOrigin; return false; } // Ok, now we know the Z range where this box can fit. Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims; vBottomLeft.z = bottomZ; m_vecBuildOrigin = vBottomLeft - m_vecBuildMins; return true; }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::PrimaryAttack() { // Are we capable of firing again? if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; // Get the player owning the weapon. CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); if ( !pOwner ) return; if ( !CanAttack() ) { #if defined ( CLIENT_DLL ) StopFlame(); #endif m_iWeaponState = FT_STATE_IDLE; return; } CalcIsAttackCritical(); // Because the muzzle is so long, it can stick through a wall if the player is right up against it. // Make sure the weapon can't fire in this condition by tracing a line between the eye point and the end of the muzzle. trace_t trace; Vector vecEye = pOwner->EyePosition(); Vector vecMuzzlePos = GetVisualMuzzlePos(); CTraceFilterIgnoreObjects traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecEye, vecMuzzlePos, MASK_SOLID, &traceFilter, &trace ); if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) ) { // there is something between the eye and the end of the muzzle, most likely a wall, don't fire, and stop firing if we already are if ( m_iWeaponState > FT_STATE_IDLE ) { #if defined ( CLIENT_DLL ) StopFlame(); #endif m_iWeaponState = FT_STATE_IDLE; } return; } switch ( m_iWeaponState ) { case FT_STATE_IDLE: case FT_STATE_AIRBLASTING: { // Just started, play PRE and start looping view model anim pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE ); SendWeaponAnim( ACT_VM_PRIMARYATTACK ); m_flStartFiringTime = gpGlobals->curtime + 0.16; // 5 frames at 30 fps m_iWeaponState = FT_STATE_STARTFIRING; } break; case FT_STATE_STARTFIRING: { // if some time has elapsed, start playing the looping third person anim if ( gpGlobals->curtime > m_flStartFiringTime ) { m_iWeaponState = FT_STATE_FIRING; m_flNextPrimaryAttackAnim = gpGlobals->curtime; } } break; case FT_STATE_FIRING: { if ( gpGlobals->curtime >= m_flNextPrimaryAttackAnim ) { pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); m_flNextPrimaryAttackAnim = gpGlobals->curtime + 1.4; // fewer than 45 frames! } } break; default: break; } #ifdef CLIENT_DLL // Restart our particle effect if we've transitioned across water boundaries if ( m_iParticleWaterLevel != -1 && pOwner->GetWaterLevel() != m_iParticleWaterLevel ) { if ( m_iParticleWaterLevel == WL_Eyes || pOwner->GetWaterLevel() == WL_Eyes ) { RestartParticleEffect(); } } #endif #ifdef CLIENT_DLL // Handle the flamethrower light if (tf2c_muzzlelight.GetBool()) { dlight_t *dl = effects->CL_AllocDlight(LIGHT_INDEX_MUZZLEFLASH + index); dl->origin = vecMuzzlePos; dl->color.r = 255; dl->color.g = 100; dl->color.b = 10; dl->die = gpGlobals->curtime + 0.01f; dl->radius = 240.f; dl->decay = 512.0f; dl->style = 5; } #endif #if !defined (CLIENT_DLL) // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation. pOwner->NoteWeaponFired(); pOwner->SpeakWeaponFire(); CTF_GameStats.Event_PlayerFiredWeapon( pOwner, m_bCritFire ); // Move other players back to history positions based on local player's lag lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); #endif float flFiringInterval = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; CALL_ATTRIB_HOOK_FLOAT( flFiringInterval, mult_postfiredelay ); // Don't attack if we're underwater if ( pOwner->GetWaterLevel() != WL_Eyes ) { // Find eligible entities in a cone in front of us. Vector vOrigin = pOwner->Weapon_ShootPosition(); Vector vForward, vRight, vUp; QAngle vAngles = pOwner->EyeAngles() + pOwner->GetPunchAngle(); AngleVectors( vAngles, &vForward, &vRight, &vUp ); #define NUM_TEST_VECTORS 30 #ifdef CLIENT_DLL bool bWasCritical = m_bCritFire; #endif // Burn & Ignite 'em int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ]; m_bCritFire = IsCurrentAttackACrit(); if ( m_bCritFire ) { iDmgType |= DMG_CRITICAL; } #ifdef CLIENT_DLL if ( bWasCritical != m_bCritFire ) { RestartParticleEffect(); } #endif #ifdef GAME_DLL // create the flame entity int iDamagePerSec = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage; float flDamage = (float)iDamagePerSec * flFiringInterval; CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg ); CTFFlameEntity::Create( GetFlameOriginPos(), pOwner->EyeAngles(), this, iDmgType, flDamage ); #endif } #ifdef GAME_DLL // Figure how much ammo we're using per shot and add it to our remainder to subtract. (We may be using less than 1.0 ammo units // per frame, depending on how constants are tuned, so keep an accumulator so we can expend fractional amounts of ammo per shot.) // Note we do this only on server and network it to client. If we predict it on client, it can get slightly out of sync w/server // and cause ammo pickup indicators to appear float flAmmoPerSecond = TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK; CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecond, mult_flame_ammopersec ); m_flAmmoUseRemainder += flAmmoPerSecond * flFiringInterval; // take the integer portion of the ammo use accumulator and subtract it from player's ammo count; any fractional amount of ammo use // remains and will get used in the next shot int iAmmoToSubtract = (int) m_flAmmoUseRemainder; if ( iAmmoToSubtract > 0 ) { pOwner->RemoveAmmo( iAmmoToSubtract, m_iPrimaryAmmoType ); m_flAmmoUseRemainder -= iAmmoToSubtract; // round to 2 digits of precision m_flAmmoUseRemainder = (float) ( (int) (m_flAmmoUseRemainder * 100) ) / 100.0f; } #endif m_flNextPrimaryAttack = gpGlobals->curtime + flFiringInterval; m_flTimeWeaponIdle = gpGlobals->curtime + flFiringInterval; #if !defined (CLIENT_DLL) lagcompensation->FinishLagCompensation( pOwner ); #endif }