diff --git a/src/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp b/src/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp index 166dbc305c4..7ec18807d99 100644 --- a/src/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp +++ b/src/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp @@ -277,7 +277,7 @@ ActionResult< CTFBot > CTFBotDeliverFlag::Update( CTFBot *me, float interval ) if ( UpgradeOverTime( me ) ) { - return SuspendFor( new CTFBotTaunt, "Taunting for our new upgrade" ); + return SuspendFor( new CTFBotTaunt( NULL ), "Taunting for our new upgrade" ); } return Continue(); diff --git a/src/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp b/src/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp index f80117d6a92..dd9b02556f7 100644 --- a/src/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp +++ b/src/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp @@ -107,7 +107,7 @@ ActionResult< CTFBot > CTFBotMobRush::Update( CTFBot *me, float interval ) if ( !m_victim->IsAlive() && me->IsRangeLessThan( m_victim, tf_bot_taunt_range.GetFloat() ) ) { // we got 'em! - return ChangeTo( new CTFBotTaunt, "Taunt their corpse" ); + return ChangeTo( new CTFBotTaunt( NULL ), "Taunt their corpse" ); } if ( m_vocalizeTimer.IsElapsed() ) diff --git a/src/game/server/tf/bot/behavior/tf_bot_behavior.cpp b/src/game/server/tf/bot/behavior/tf_bot_behavior.cpp index 6f41fd7aa76..324e59f2f54 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_behavior.cpp +++ b/src/game/server/tf/bot/behavior/tf_bot_behavior.cpp @@ -37,7 +37,7 @@ ConVar tf_bot_sniper_aim_error( "tf_bot_sniper_aim_error", "0.01", FCVAR_CHEAT ) ConVar tf_bot_sniper_aim_steady_rate( "tf_bot_sniper_aim_steady_rate", "10", FCVAR_CHEAT ); ConVar tf_bot_debug_sniper( "tf_bot_debug_sniper", "0", FCVAR_CHEAT ); ConVar tf_bot_fire_weapon_min_time( "tf_bot_fire_weapon_min_time", "1", FCVAR_CHEAT ); -ConVar tf_bot_taunt_victim_chance( "tf_bot_taunt_victim_chance", "20" ); // community requested this not be a cheat cvar +ConVar tf_bot_taunt_victim_chance( "tf_bot_taunt_victim_chance", "20", FCVAR_NONE, "Percent chance a bot will taunt a victim after a kill", true, 0.f, true, 100.f ); // community requested this not be a cheat cvar ConVar tf_bot_notice_backstab_chance( "tf_bot_notice_backstab_chance", "25", FCVAR_CHEAT ); ConVar tf_bot_notice_backstab_min_range( "tf_bot_notice_backstab_min_range", "100", FCVAR_CHEAT ); @@ -51,7 +51,7 @@ ConVar tf_bot_hitscan_range_limit( "tf_bot_hitscan_range_limit", "1800", FCVAR_C ConVar tf_bot_always_full_reload( "tf_bot_always_full_reload", "0", FCVAR_CHEAT ); ConVar tf_bot_fire_weapon_allowed( "tf_bot_fire_weapon_allowed", "1", FCVAR_CHEAT, "If zero, TFBots will not pull the trigger of their weapons (but will act like they did)" ); -ConVar tf_bot_reevaluate_class_in_spawnroom( "tf_bot_reevaluate_class_in_spawnroom", "1", FCVAR_CHEAT, "If set, bots will opportunisticly switch class while in spawnrooms if their current class is no longer their first choice." ); +ConVar tf_bot_reevaluate_class_in_spawnroom( "tf_bot_reevaluate_class_in_spawnroom", "1", FCVAR_CHEAT, "If set, bots will opportunistically switch class while in spawnrooms if their current class is no longer their first choice." ); //--------------------------------------------------------------------------------------------- @@ -107,9 +107,10 @@ ActionResult< CTFBot > CTFBotMainAction::Update( CTFBot *me, float interval ) } // Should I accept taunt from my partner? - if ( me->FindPartnerTauntInitiator() ) + CTFPlayer* initiator = me->FindPartnerTauntInitiator(); + if ( initiator ) { - return SuspendFor( new CTFBotTaunt, "Responding to teammate partner taunt" ); + return SuspendFor( new CTFBotTaunt( initiator ), "Responding to teammate partner taunt" ); } // make sure our vision FOV matches the player's @@ -571,18 +572,18 @@ EventDesiredResult< CTFBot > CTFBotMainAction::OnOtherKilled( CTFBot *me, CBaseC if ( !ToTFPlayer( victim )->IsBot() && me->IsEnemy( victim ) && me->IsSelf( info.GetAttacker() ) ) { - bool isTaunting = !me->HasTheFlag() && RandomFloat( 0.0f, 100.0f ) <= tf_bot_taunt_victim_chance.GetFloat(); + bool shouldTaunt = !me->HasTheFlag() && RandomFloat( 0.0f, 100.0f ) < tf_bot_taunt_victim_chance.GetFloat(); if ( TFGameRules()->IsMannVsMachineMode() && me->IsMiniBoss() ) { // Bosses don't taunt puny humans - isTaunting = false; + shouldTaunt = false; } - if ( isTaunting ) + if ( shouldTaunt ) { // we just killed a human - taunt! - return TrySuspendFor( new CTFBotTaunt, RESULT_IMPORTANT, "Taunting our victim" ); + return TrySuspendFor( new CTFBotTaunt( NULL ), RESULT_IMPORTANT, "Taunting our victim" ); } } } diff --git a/src/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp b/src/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp index d5c0efc7f98..1f84636c6a6 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp +++ b/src/game/server/tf/bot/behavior/tf_bot_tactical_monitor.cpp @@ -238,7 +238,7 @@ ActionResult< CTFBot > CTFBotTacticalMonitor::Update( CTFBot *me, float interval // don't ack again for awhile m_acknowledgeRetryTimer.Start( RandomFloat( 10.0f, 20.0f ) ); - return SuspendFor( new CTFBotTaunt, "Acknowledging friendly human attention" ); + return SuspendFor( new CTFBotTaunt( NULL ), "Acknowledging friendly human attention" ); } } } @@ -419,7 +419,7 @@ EventDesiredResult< CTFBot > CTFBotTacticalMonitor::OnCommandString( CTFBot *me, } else if ( FStrEq( command, "taunt" ) ) { - return TrySuspendFor( new CTFBotTaunt(), RESULT_TRY, "Received command to taunt" ); + return TrySuspendFor( new CTFBotTaunt( NULL ), RESULT_TRY, "Received command to taunt" ); } else if ( FStrEq( command, "cloak" ) ) { diff --git a/src/game/server/tf/bot/behavior/tf_bot_taunt.cpp b/src/game/server/tf/bot/behavior/tf_bot_taunt.cpp index b0034fa5c0e..2671f60b1db 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_taunt.cpp +++ b/src/game/server/tf/bot/behavior/tf_bot_taunt.cpp @@ -9,12 +9,24 @@ #include "bot/behavior/tf_bot_taunt.h" +ConVar tf_bot_taunt_stop_chance( "tf_bot_taunt_stop_chance", "30", FCVAR_NONE, "Percent chance a bot will stop taunting for looping/mimic taunts, checked every 5 seconds except in setup", true, 0.f, true, 100.f ); + + +CTFBotTaunt::CTFBotTaunt( CTFPlayer *partner ) +{ + m_partner = partner; +} + + //--------------------------------------------------------------------------------------------- ActionResult< CTFBot > CTFBotTaunt::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) { // wait a short random time so entire mob doesn't taunt in unison - m_tauntTimer.Start( RandomFloat( 0, 1.0f ) ); + m_tauntTimer.Start( RandomFloat( 0, 1.f ) ); m_didTaunt = false; + m_currTauntTurnSpeed = 0.f; + m_targetTauntYaw = 0.f; + m_hasTauntYaw = false; return Continue(); } @@ -25,29 +37,126 @@ ActionResult< CTFBot > CTFBotTaunt::Update( CTFBot *me, float interval ) { if ( m_tauntTimer.IsElapsed() ) { + const float tauntStopCheckInterval = 5.f; if ( m_didTaunt ) { - // Stop taunting after a while - if ( m_tauntEndTimer.IsElapsed() && me->m_Shared.GetTauntIndex() == TAUNT_LONG ) + // Stop taunting when the timer is up + if ( m_tauntEndTimer.HasStarted() && m_tauntEndTimer.IsElapsed() && me->m_Shared.GetTauntIndex() == TAUNT_LONG ) { + me->ReleaseForwardButton(); me->EndLongTaunt(); } - if ( me->m_Shared.InCond( TF_COND_TAUNTING ) == false ) + if ( !me->IsTaunting() ) { return Done( "Taunt finished" ); - } + } + + if ( m_partner != NULL && m_partner->IsTaunting() && me->CanMoveDuringTaunt() ) + { + if ( m_tauntStopCheckTimer.HasStarted() && m_tauntStopCheckTimer.IsElapsed() ) + { + // if not in setup, roll to stop taunting early + if ( !TFGameRules()->InSetup() && RandomFloat( 0.0f, 100.0f ) < tf_bot_taunt_stop_chance.GetFloat() ) + { + m_tauntEndTimer.Start( RandomFloat( 0.8f, 2.f ) ); + m_tauntStopCheckTimer.Invalidate(); + } + else + { + m_tauntStopCheckTimer.Start( tauntStopCheckInterval ); + } + } + + // store taunt yaw now that we are actually taunting + if ( !m_hasTauntYaw ) + { + m_targetTauntYaw = me->GetTauntYaw(); + m_hasTauntYaw = true; + } + + const float nearbyRange = 50.f; + const float maxFacingAngle = 10.f; + + // if outside range of the target, move towards them and turn to face them + if ( me->IsRangeGreaterThan( m_partner, nearbyRange ) ) + { + // Force move forwards + me->PressForwardButton( interval ); + + Vector toPartner = m_partner->GetAbsOrigin() - me->GetAbsOrigin(); + toPartner.z = 0.f; + // diff > maxFacingAngle or < -maxFacingAngle requires turning + float diff = UTIL_AngleDiff( UTIL_VecToYaw( toPartner ), m_targetTauntYaw ); + + float flTauntTurnAccelerationTime = me->GetTauntTurnAccelerationTime(); + // Ported from C_TFPlayer::CreateMove + float flSign = fabsf( diff ) > maxFacingAngle ? 1.f : -1.f; + float flMaxTurnSpeed = me->GetTauntTurnSpeed(); + if ( flTauntTurnAccelerationTime > 0.f ) + { + m_currTauntTurnSpeed = clamp( m_currTauntTurnSpeed + flSign * ( interval / flTauntTurnAccelerationTime ) * flMaxTurnSpeed, 0.f, flMaxTurnSpeed ); + } + else + { + m_currTauntTurnSpeed = flMaxTurnSpeed; + } + + float flSmoothTurnSpeed = 0.f; + if ( flMaxTurnSpeed > 0.f ) + { + flSmoothTurnSpeed = SimpleSpline( m_currTauntTurnSpeed / flMaxTurnSpeed ) * flMaxTurnSpeed; + } + + // mimics angMoveAngle handling from C_TFPlayer::CreateMove + // target yaw accumulates even if current taunt yaw/AimHeadTowards hasn't caught up + if ( diff > maxFacingAngle ) + { + m_targetTauntYaw += flSmoothTurnSpeed * interval; + } + else if ( diff < -maxFacingAngle ) + { + m_targetTauntYaw -= flSmoothTurnSpeed * interval; + } + + // calculates a world point to look at based on target yaw so we get smooth turning + Vector aimDir; + AngleVectors( QAngle( 0.f, m_targetTauntYaw, 0.f ), &aimDir ); + me->GetBodyInterface()->AimHeadTowards( me->EyePosition() + 100.0f * aimDir, IBody::CRITICAL, interval, NULL, "Taunt partner" ); + } + else + { + m_currTauntTurnSpeed = 0.f; + m_targetTauntYaw = me->GetTauntYaw(); + me->ReleaseForwardButton(); + } + } + else + { + m_currTauntTurnSpeed = 0.f; + me->ReleaseForwardButton(); + } + + const float maxRange = 600.f; + + // Start a timer to end our taunt if partner missing, partner not taunting, or can't see/too far from partner + if ( !m_tauntEndTimer.HasStarted() + && ( m_partner == NULL + || !m_partner->IsTaunting() + || !me->GetVisionInterface()->IsLineOfSightClearToEntity( m_partner ) + || me->IsRangeGreaterThan( m_partner, maxRange ) ) ) + { + m_tauntEndTimer.Start( RandomFloat( 0.8f, 2.f ) ); + m_tauntStopCheckTimer.Invalidate(); + } } else { me->HandleTauntCommand(); - // Start a timer to end our taunt in case we're still going after awhile - m_tauntEndTimer.Start( RandomFloat( 3.f, 5.f ) ); - m_didTaunt = true; + m_tauntStopCheckTimer.Start( tauntStopCheckInterval ); } } return Continue(); } - diff --git a/src/game/server/tf/bot/behavior/tf_bot_taunt.h b/src/game/server/tf/bot/behavior/tf_bot_taunt.h index 37a90745330..149a2c7ade4 100644 --- a/src/game/server/tf/bot/behavior/tf_bot_taunt.h +++ b/src/game/server/tf/bot/behavior/tf_bot_taunt.h @@ -6,20 +6,26 @@ #ifndef TF_BOT_TAUNT_H #define TF_BOT_TAUNT_H - //----------------------------------------------------------------------------- class CTFBotTaunt : public Action< CTFBot > { public: + CTFBotTaunt( CTFPlayer *partner ); + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); virtual const char *GetName( void ) const { return "Taunt"; }; private: + CHandle< CTFPlayer > m_partner; CountdownTimer m_tauntTimer; CountdownTimer m_tauntEndTimer; + CountdownTimer m_tauntStopCheckTimer; bool m_didTaunt; + float m_currTauntTurnSpeed; + float m_targetTauntYaw; + bool m_hasTauntYaw; }; diff --git a/src/game/server/tf/tf_player.h b/src/game/server/tf/tf_player.h index d2affa28511..f69a587efaf 100644 --- a/src/game/server/tf/tf_player.h +++ b/src/game/server/tf/tf_player.h @@ -735,6 +735,7 @@ class CTFPlayer : public CBaseMultiplayerPlayer, public IHasAttributes, public I bool IsTauntForceMovingForward() const { return m_bTauntForceMoveForward; } float GetTauntMoveAcceleration() const { return m_flTauntMoveAccelerationTime; } float GetTauntMoveSpeed() const { return m_flTauntForceMoveForwardSpeed; } + float GetTauntTurnSpeed() const { return m_flTauntTurnSpeed; } float GetTauntTurnAccelerationTime() const { return m_flTauntTurnAccelerationTime; } virtual int GetAllowedTauntPartnerTeam() const; CEconItemView *GetTauntEconItemView() { return m_TauntEconItemView.IsValid() ? &m_TauntEconItemView : NULL; } diff --git a/src/game/shared/tf/tf_gamemovement.cpp b/src/game/shared/tf/tf_gamemovement.cpp index 476601634c2..63802e61b1e 100644 --- a/src/game/shared/tf/tf_gamemovement.cpp +++ b/src/game/shared/tf/tf_gamemovement.cpp @@ -657,11 +657,20 @@ bool CTFGameMovement::TauntMove( void ) flMoveDir += mv->m_flForwardMove / cl_backspeed.GetFloat(); } - // No need to read buttons explicitly anymore, since that input is already included in m_flForwardMove - /* if ( mv->m_nButtons & IN_FORWARD ) - flMoveDir += 1.f; - if ( mv->m_nButtons & IN_BACK ) - flMoveDir += -1.f; */ + // Bots set m_flForwardMove to PlayerLocomotion::GetRunSpeed, not cl_forwardspeed. + // Fix their values so that they can TauntMove at the correct speed + if ( m_pTFPlayer->IsBot() ) + { + // Bots never end up pressing both of these at once + if ( mv->m_nButtons & IN_FORWARD ) + { + flMoveDir = 1.f; + } + else if ( mv->m_nButtons & IN_BACK ) + { + flMoveDir = -1.f; + } + } // Clamp to [0,1], just in case. if ( flMoveDir > 1.0f ) diff --git a/src/game/shared/tf/tf_playeranimstate.cpp b/src/game/shared/tf/tf_playeranimstate.cpp index dc8414c1db5..c431f02059c 100644 --- a/src/game/shared/tf/tf_playeranimstate.cpp +++ b/src/game/shared/tf/tf_playeranimstate.cpp @@ -380,9 +380,6 @@ void CTFPlayerAnimState::Update( float eyeYaw, float eyePitch ) // Compute the player sequences. ComputeSequences( pStudioHdr ); - CTFPlayer *pTauntPartner = pTFPlayer->GetTauntPartner(); - - Vector vPositionToFace = ( pTauntPartner ? pTauntPartner->GetAbsOrigin() : vec3_origin ); bool bInTaunt = pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ); bool bInKart = pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ); bool bIsImmobilized = bInTaunt || pTFPlayer->m_Shared.IsControlStunned();