diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index d58937b7b..d1c0d0348 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1438,6 +1438,16 @@ target_sources_grouped( neo/bot/behavior/neo_bot_behavior.h neo/bot/behavior/neo_bot_dead.cpp neo/bot/behavior/neo_bot_dead.h + neo/bot/behavior/neo_bot_jgr_capture.cpp + neo/bot/behavior/neo_bot_jgr_capture.h + neo/bot/behavior/neo_bot_jgr_enemy.cpp + neo/bot/behavior/neo_bot_jgr_enemy.h + neo/bot/behavior/neo_bot_jgr_escort.cpp + neo/bot/behavior/neo_bot_jgr_escort.h + neo/bot/behavior/neo_bot_jgr_juggernaut.cpp + neo/bot/behavior/neo_bot_jgr_juggernaut.h + neo/bot/behavior/neo_bot_jgr_seek.cpp + neo/bot/behavior/neo_bot_jgr_seek.h neo/bot/behavior/neo_bot_melee_attack.cpp neo/bot/behavior/neo_bot_melee_attack.h neo/bot/behavior/neo_bot_move_to_vantage_point.cpp diff --git a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp index 8725e2f91..40336fdbd 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp @@ -777,6 +777,7 @@ void CNEOBotMainAction::FireWeaponAtEnemy( CNEOBot *me ) { // Minimum viable firing BALC // TODO: Proper heat management for higher difficulty bots + me->ReleaseWalkButton(); // NEO Jank: this actually cancels sprint me->PressFireButton(GetFireDurationByDifficulty(me)); return; } diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.cpp new file mode 100644 index 000000000..8280e1c06 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.cpp @@ -0,0 +1,151 @@ +#include "cbase.h" +#include "bot/behavior/neo_bot_jgr_capture.h" +#include "bot/behavior/neo_bot_retreat_to_cover.h" +#include "bot/neo_bot_path_compute.h" +#include "neo_gamerules.h" +#include "neo_juggernaut.h" + +//--------------------------------------------------------------------------------------------- +CNEOBotJgrCapture::CNEOBotJgrCapture( CNEO_Juggernaut *pObjective ) +{ + m_hObjective = pObjective; +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotJgrCapture::OnStart( CNEOBot *me, Action *priorAction ) +{ + m_useAttemptTimer.Invalidate(); + m_path.Invalidate(); + m_repathTimer.Invalidate(); + + if ( !m_hObjective ) + { + return Done( "No capture target specified." ); + } + + if ( NEORules()->GetGameType() != NEO_GAME_TYPE_JGR ) + { + return Done( "Not in JGR game mode" ); + } + + if ( !FStrEq( m_hObjective->GetClassname(), "neo_juggernaut" ) ) + { + return Done( "Capture target is not the juggernaut" ); + } + + // Ignore enemies while capturing juggernaut + me->StopLookingAroundForEnemies(); + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +void CNEOBotJgrCapture::OnEnd( CNEOBot *me, Action *nextAction ) +{ + me->ReleaseUseButton(); + me->StartLookingAroundForEnemies(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotJgrCapture::OnSuspend( CNEOBot *me, Action *interruptingAction ) +{ + // CNEOBotTacticalMonitor -> CNEOBotRetreatToCover will handle reacting to enemies if under fire + // Debatably, maybe bots should just ignore enemies, but that would require a change to CNEOBotTacticalMonitor + // Also it might be more fun for humans if they can interrupt bots from taking the Juggernaut. + me->ReleaseUseButton(); + me->StartLookingAroundForEnemies(); + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotJgrCapture::OnResume( CNEOBot *me, Action *interruptingAction ) +{ + me->StopLookingAroundForEnemies(); + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult CNEOBotJgrCapture::Update( CNEOBot *me, float interval ) +{ + if ( me->IsDead() ) + { + return Done( "I am dead" ); + } + + if ( !m_hObjective ) + { + return Done( "Capture target gone" ); + } + + if ( me->GetClass() == NEO_CLASS_JUGGERNAUT ) + { + return Done( "Became the Juggernaut" ); + } + + const int iJuggernautPlayer = NEORules()->GetJuggernautPlayer(); + if ( iJuggernautPlayer > 0 ) + { + CNEO_Player *pJuggernautPlayer = ToNEOPlayer( UTIL_PlayerByIndex( iJuggernautPlayer ) ); + if ( pJuggernautPlayer && pJuggernautPlayer != me ) + { + return Done( "Juggernaut captured by someone else" ); + } + } + + if ( me->GetAbsOrigin().DistToSqr( m_hObjective->GetAbsOrigin() ) < CNEO_Juggernaut::GetUseDistanceSquared() ) + { + CNEO_Juggernaut *pJuggernaut = m_hObjective.Get(); + if ( NEORules()->IsJuggernautLocked() || (pJuggernaut && pJuggernaut->m_bLocked) ) + { + Assert( NEORules()->IsJuggernautLocked() == (pJuggernaut && pJuggernaut->m_bLocked) ); + me->ReleaseUseButton(); + return SuspendFor( new CNEOBotRetreatToCover( 2.0f ), "Juggernaut is locked, taking cover to wait for it to unlock" ); + } + + // Stop moving while using + m_path.Invalidate(); + me->ReleaseForwardButton(); + me->ReleaseBackwardButton(); + me->ReleaseLeftButton(); + me->ReleaseRightButton(); + + const Vector vecObjectiveCenter = m_hObjective->WorldSpaceCenter(); + Vector vecToTargetDir = vecObjectiveCenter - me->EyePosition(); + vecToTargetDir.NormalizeInPlace(); + + // Ensure we are facing the target before attempting to use + Vector vecEyeDirection; + me->EyeVectors( &vecEyeDirection ); + const float flDot = vecEyeDirection.Dot( vecToTargetDir ); + const bool bIsFacing = flDot > 0.9f; + + me->GetBodyInterface()->AimHeadTowards( vecObjectiveCenter, IBody::CRITICAL, 0.1f, NULL, "Looking at Juggernaut objective to use" ); + + if ( m_useAttemptTimer.HasStarted() ) + { + if ( m_useAttemptTimer.IsElapsed() ) + { + return Done( "Use timer elapsed, failed to capture" ); + } + } + else if ( bIsFacing && me->GetBodyInterface()->IsHeadAimingOnTarget() ) + { + m_useAttemptTimer.Start( CNEO_Juggernaut::GetUseDuration() + 1.0f ); + me->PressUseButton( CNEO_Juggernaut::GetUseDuration() + 1.0f ); + } + } + else + { + // Objective is farther than we can reach for cpature + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 5.0f ) ); + if ( !CNEOBotPathCompute( me, m_path, m_hObjective->GetAbsOrigin(), FASTEST_ROUTE ) ) + { + return Done( "Unable to find a path to the Juggernaut objective" ); + } + } + m_path.Update( me ); + } + + return Continue(); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.h b/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.h new file mode 100644 index 000000000..984697a65 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_capture.h @@ -0,0 +1,29 @@ +#ifndef NEO_BOT_CAPTURE_JGR_H +#define NEO_BOT_CAPTURE_JGR_H + +#include "NextBotBehavior.h" +#include "bot/neo_bot.h" + +//---------------------------------------------------------------------------------------------------------------- +class CNEOBotJgrCapture : public Action +{ +public: + CNEOBotJgrCapture( CNEO_Juggernaut *pObjective ); + virtual ~CNEOBotJgrCapture() { } + + virtual const char *GetName() const override { return "jgrCapture"; } + + virtual ActionResult OnStart( CNEOBot *me, Action *priorAction ) override; + virtual ActionResult Update( CNEOBot *me, float interval ) override; + virtual void OnEnd( CNEOBot *me, Action *nextAction ) override; + virtual ActionResult OnSuspend( CNEOBot *me, Action *interruptingAction ) override; + virtual ActionResult OnResume( CNEOBot *me, Action *interruptingAction ) override; + +private: + CHandle m_hObjective; + CountdownTimer m_useAttemptTimer; + PathFollower m_path; + CountdownTimer m_repathTimer; +}; + +#endif // NEO_BOT_CAPTURE_JGR_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp new file mode 100644 index 000000000..38c1e0ef4 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp @@ -0,0 +1,79 @@ +#include "cbase.h" +#include "neo_player.h" +#include "bot/neo_bot.h" +#include "bot/behavior/neo_bot_jgr_enemy.h" +#include "bot/neo_bot_path_compute.h" +#include "neo_gamerules.h" +#include "bot/behavior/neo_bot_attack.h" + +//--------------------------------------------------------------------------------------------- +CNEOBotJgrEnemy::CNEOBotJgrEnemy( void ) +{ +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEnemy::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) +{ + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEnemy::Update( CNEOBot *me, float interval ) +{ + const int iJuggernautPlayer = NEORules()->GetJuggernautPlayer(); + if ( iJuggernautPlayer <= 0 ) + { + return Done( "No juggernaut player" ); + } + + CNEO_Player* pJuggernaut = ToNEOPlayer( UTIL_PlayerByIndex( iJuggernautPlayer ) ); + if ( !pJuggernaut ) + { + return Done( "Juggernaut is invalid" ); + } + + if ( !pJuggernaut->IsAlive() ) + { + return Done( "Juggernaut is dead" ); + } + + if ( pJuggernaut->GetTeamNumber() == me->GetTeamNumber() ) + { + return Done( "Juggernaut is friendly" ); + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && !threat->IsObsolete() && me->GetIntentionInterface()->ShouldAttack( me, threat ) ) + { + return SuspendFor( new CNEOBotAttack, "Attacking threat" ); + } + + // Chase the Juggernaut + CNEOBotPathUpdateChase( me, m_chasePath, pJuggernaut, DEFAULT_ROUTE ); + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEnemy::OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) +{ + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEnemy::OnStuck( CNEOBot *me ) +{ + return TryContinue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEnemy::OnMoveToSuccess( CNEOBot *me, const Path *path ) +{ + return TryContinue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEnemy::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) +{ + return TryContinue(); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.h b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.h new file mode 100644 index 000000000..f5f8f11f8 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.h @@ -0,0 +1,27 @@ +#ifndef NEO_BOT_JGR_ENEMY_H +#define NEO_BOT_JGR_ENEMY_H + +#include "bot/neo_bot.h" +#include "Path/NextBotChasePath.h" + +//-------------------------------------------------------------------------------------------------------- +class CNEOBotJgrEnemy : public Action< CNEOBot > +{ +public: + CNEOBotJgrEnemy( void ); + + virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override; + virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ) override; + virtual ActionResult< CNEOBot > OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) override; + + virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me ) override; + virtual EventDesiredResult< CNEOBot > OnMoveToSuccess( CNEOBot *me, const Path *path ) override; + virtual EventDesiredResult< CNEOBot > OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) override; + + virtual const char *GetName( void ) const override { return "JgrEnemy"; } + +private: + ChasePath m_chasePath; +}; + +#endif // NEO_BOT_JGR_ENEMY_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp new file mode 100644 index 000000000..d01d86dd9 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp @@ -0,0 +1,113 @@ +#include "cbase.h" +#include "neo_player.h" +#include "bot/neo_bot.h" +#include "bot/behavior/neo_bot_attack.h" +#include "bot/behavior/neo_bot_jgr_escort.h" +#include "bot/behavior/neo_bot_retreat_to_cover.h" +#include "bot/neo_bot_path_compute.h" +#include "neo_gamerules.h" + +//--------------------------------------------------------------------------------------------- +CNEOBotJgrEscort::CNEOBotJgrEscort( void ) +{ +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEscort::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) +{ + m_chasePath.Invalidate(); + return Continue(); +} + + + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEscort::Update( CNEOBot *me, float interval ) +{ + const int iJuggernautPlayer = NEORules()->GetJuggernautPlayer(); + if ( iJuggernautPlayer <= 0 ) + { + return Done( "No juggernaut player" ); + } + + CNEO_Player* pJuggernaut = ToNEOPlayer( UTIL_PlayerByIndex( iJuggernautPlayer ) ); + if ( !pJuggernaut ) + { + return Done( "Juggernaut is invalid" ); + } + + if ( !pJuggernaut->IsAlive() ) + { + return Done( "Juggernaut is dead" ); + } + + if ( pJuggernaut->GetTeamNumber() != me->GetTeamNumber() ) + { + return Done( "Juggernaut is not friendly" ); + } + + // Check if we can assist the Juggernaut (if they are a bot) + CNEOBot *pBotJuggernaut = ToNEOBot( pJuggernaut ); + if ( pBotJuggernaut ) + { + const CKnownEntity *juggernautThreat = pBotJuggernaut->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( juggernautThreat ) + { + // Check if the threat has a clear shot at our friend + if ( me->IsLineOfFireClear( juggernautThreat->GetLastKnownPosition(), pJuggernaut, CNEOBot::LINE_OF_FIRE_FLAGS_DEFAULT ) ) + { + return SuspendFor( new CNEOBotAttack, "Assisting Juggernaut with their threat" ); + } + } + } + + // Check for own threats + const CKnownEntity *myThreat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( myThreat ) + { + return SuspendFor( new CNEOBotRetreatToCover, "Retreating to let Juggernaut get the kill" ); + } + + // Just follow the Juggernaut + // If too close, stop moving to avoid crowding + constexpr float flMinFollowDistSq = 200.0f * 200.0f; + float flDistSq = me->GetAbsOrigin().DistToSqr( pJuggernaut->GetAbsOrigin() ); + + if ( flDistSq < flMinFollowDistSq ) + { + // Too close, stop moving + if ( m_chasePath.IsValid() ) + { + m_chasePath.Invalidate(); + } + return Continue(); + } + + CNEOBotPathUpdateChase( me, m_chasePath, pJuggernaut, DEFAULT_ROUTE ); + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrEscort::OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) +{ + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnStuck( CNEOBot *me ) +{ + return TryContinue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnMoveToSuccess( CNEOBot *me, const Path *path ) +{ + return TryContinue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) +{ + return TryContinue(); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.h b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.h new file mode 100644 index 000000000..8a05b44e8 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.h @@ -0,0 +1,27 @@ +#ifndef NEO_BOT_JGR_ESCORT_H +#define NEO_BOT_JGR_ESCORT_H + +#include "bot/neo_bot.h" +#include "Path/NextBotChasePath.h" + +//-------------------------------------------------------------------------------------------------------- +class CNEOBotJgrEscort : public Action< CNEOBot > +{ +public: + CNEOBotJgrEscort( void ); + + virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override; + virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ) override; + virtual ActionResult< CNEOBot > OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) override; + + virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me ) override; + virtual EventDesiredResult< CNEOBot > OnMoveToSuccess( CNEOBot *me, const Path *path ) override; + virtual EventDesiredResult< CNEOBot > OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) override; + + virtual const char *GetName( void ) const override { return "jgrEscort"; } + +private: + ChasePath m_chasePath; +}; + +#endif // NEO_BOT_JGR_ESCORT_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp new file mode 100644 index 000000000..2ea54f612 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp @@ -0,0 +1,190 @@ +#include "cbase.h" +#include "neo_player.h" +#include "neo_gamerules.h" +#include "bot/neo_bot.h" +#include "bot/neo_bot_path_compute.h" +#include "bot/behavior/neo_bot_jgr_juggernaut.h" + +//--------------------------------------------------------------------------------------------- +static CSound* SearchGunfireSounds(CNEOBot* me) +{ + CSound* pClosestSound = NULL; + float flClosestDistSqr = FLT_MAX; + const Vector& vecMyOrigin = me->GetAbsOrigin(); + + int iSound = CSoundEnt::ActiveList(); + while (iSound != SOUNDLIST_EMPTY) + { + CSound* pSound = CSoundEnt::SoundPointerForIndex(iSound); + if (!pSound) + break; + + if ((pSound->SoundType() & (SOUND_COMBAT | SOUND_BULLET_IMPACT)) && pSound->ValidateOwner()) + { + // Don't listen to sounds I was responsible for + if (pSound->m_hOwner.Get() == me->GetEntity()) + { + iSound = pSound->NextSound(); + continue; + } + + // Search for the closest gunfire sounds + float distSqr = (pSound->GetSoundOrigin() - vecMyOrigin).LengthSqr(); + if (distSqr < flClosestDistSqr) + { + flClosestDistSqr = distSqr; + pClosestSound = pSound; + } + } + + iSound = pSound->NextSound(); + } + + return pClosestSound; +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrJuggernaut::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) +{ + m_jgrSpawns.RemoveAll(); + + CBaseEntity* pSearch = NULL; + while ((pSearch = gEntList.FindEntityByClassname(pSearch, "neo_juggernautspawnpoint")) != NULL) + { + m_jgrSpawns.AddToTail(pSearch); + } + + m_stuckTimer.Invalidate(); + m_bStuckCrouchPhase = false; + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrJuggernaut::Update( CNEOBot *me, float interval ) +{ + ActionResult< CNEOBot > result = UpdateCommon( me, interval ); + if ( result.IsRequestingChange() || result.IsDone() ) + return result; + + // Listen for gunfire + if ( !m_soundSearchTimer.HasStarted() || m_soundSearchTimer.IsElapsed() ) + { + m_soundSearchTimer.Start( 0.25f ); + + CSound* pBestSound = SearchGunfireSounds(me); + if (pBestSound) + { + // Only change goal if recent gunfire is radically different than where I was going + constexpr float flThresholdSqr = 200.0f * 200.0f; + if (m_vGoalPos.DistToSqr(pBestSound->GetSoundOrigin()) > flThresholdSqr) + { + m_vGoalPos = pBestSound->GetSoundOrigin(); + // gunfire is not an entity target + m_bGoingToTargetEntity = false; + + if (CNEOBotPathCompute(me, m_path, m_vGoalPos, FASTEST_ROUTE)) + { + return Continue(); + } + } + } + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +void CNEOBotJgrJuggernaut::RecomputeSeekPath( CNEOBot *me ) +{ + m_hTargetEntity = NULL; + m_bGoingToTargetEntity = false; + m_vGoalPos = vec3_origin; + + // Listen for gunfights + CSound* pBestSound = SearchGunfireSounds(me); + if (pBestSound) + { + m_vGoalPos = pBestSound->GetSoundOrigin(); + m_bGoingToTargetEntity = false; + + if (CNEOBotPathCompute(me, m_path, m_vGoalPos, FASTEST_ROUTE) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) + { + return; + } + } + + // If unaware of gunfights, patrol spawn points + if (m_jgrSpawns.Count() > 0) + { + CBaseEntity* pTargetSpawn = m_jgrSpawns[ RandomInt(0, m_jgrSpawns.Count() - 1) ]; + if (pTargetSpawn) + { + m_vGoalPos = pTargetSpawn->GetAbsOrigin(); + m_bGoingToTargetEntity = false; + + if (CNEOBotPathCompute(me, m_path, m_vGoalPos, DEFAULT_ROUTE) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) + { + return; + } + } + } + + // Fallback to base behavior + CNEOBotSeekAndDestroy::RecomputeSeekPath( me ); +} + + +//--------------------------------------------------------------------------------------------- +// Adds a periodic crouch attempt on top of CNEOBotMainAction::OnStuck for height clearance +EventDesiredResult< CNEOBot > CNEOBotJgrJuggernaut::OnStuck( CNEOBot *me ) +{ + UTIL_LogPrintf( "\"%s<%i><%s>\" stuck (position \"%3.2f %3.2f %3.2f\") (duration \"%3.2f\") ", + me->GetNeoPlayerName(), + me->GetUserID(), + me->GetNetworkIDString(), + me->GetAbsOrigin().x, me->GetAbsOrigin().y, me->GetAbsOrigin().z, + me->GetLocomotionInterface()->GetStuckDuration() ); + + const PathFollower *path = me->GetCurrentPath(); + if ( path && path->GetCurrentGoal() ) + { + UTIL_LogPrintf( " path_goal ( \"%3.2f %3.2f %3.2f\" )\n", + path->GetCurrentGoal()->pos.x, + path->GetCurrentGoal()->pos.y, + path->GetCurrentGoal()->pos.z ); + } + else + { + UTIL_LogPrintf( " path_goal ( \"NULL\" )\n" ); + } + + if ( !m_stuckTimer.HasStarted() || m_stuckTimer.IsElapsed() ) + { + m_stuckTimer.Start( 1.0f ); + m_bStuckCrouchPhase = !m_bStuckCrouchPhase; + } + + // Try to crouch under doorways/gates + if ( m_bStuckCrouchPhase ) + { + me->PressCrouchButton(); + } + // ... in addition to usual OnStuck behavior + else + { + me->GetLocomotionInterface()->Jump(); + } + + if ( RandomInt( 0, 1 ) ) + { + me->PressLeftButton(); + } + else + { + me->PressRightButton(); + } + + return TryContinue(); +} + diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.h b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.h new file mode 100644 index 000000000..bfc1e1532 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.h @@ -0,0 +1,28 @@ +#ifndef NEO_BOT_JGR_JUGGERNAUT_H +#define NEO_BOT_JGR_JUGGERNAUT_H +#pragma once + +#include "bot/behavior/neo_bot_seek_and_destroy.h" + +class CNEOBotJgrJuggernaut : public CNEOBotSeekAndDestroy +{ +public: + CNEOBotJgrJuggernaut( float duration = -1.0f ) : CNEOBotSeekAndDestroy( duration ), m_bStuckCrouchPhase( false ) {} + + virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override; + virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ) override; + virtual const char *GetName( void ) const override { return "jgrJuggernaut"; } + + virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me ) override; + +protected: + virtual void RecomputeSeekPath( CNEOBot *me ) override; + + CountdownTimer m_stuckTimer; + bool m_bStuckCrouchPhase; + +private: + CUtlVector> m_jgrSpawns; + CountdownTimer m_soundSearchTimer; +}; +#endif // NEO_BOT_JGR_JUGGERNAUT_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.cpp new file mode 100644 index 000000000..33f89244a --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.cpp @@ -0,0 +1,114 @@ +#include "cbase.h" +#include "neo_player.h" +#include "neo_gamerules.h" +#include "bot/neo_bot.h" +#include "bot/neo_bot_path_compute.h" +#include "bot/behavior/neo_bot_jgr_seek.h" +#include "bot/behavior/neo_bot_jgr_escort.h" +#include "bot/behavior/neo_bot_jgr_enemy.h" +#include "bot/behavior/neo_bot_jgr_capture.h" +#include "bot/behavior/neo_bot_jgr_juggernaut.h" +#include "neo_juggernaut.h" + + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotJgrSeek::Update( CNEOBot *me, float interval ) +{ + if (NEORules()->GetGameType() != NEO_GAME_TYPE_JGR) + { + return Done( "Game mode is no longer JGR" ); + } + + if ( me->GetClass() != NEO_CLASS_JUGGERNAUT ) + { + int iJuggernautPlayer = NEORules()->GetJuggernautPlayer(); + if (iJuggernautPlayer > 0) + { + CNEO_Player* pJuggernaut = ToNEOPlayer(UTIL_PlayerByIndex(iJuggernautPlayer)); + Assert( pJuggernaut != me ); + if (pJuggernaut && pJuggernaut != me && pJuggernaut->IsAlive()) + { + if (pJuggernaut->GetTeamNumber() == me->GetTeamNumber()) + { + return SuspendFor(new CNEOBotJgrEscort, "Escorting the Juggernaut"); + } + else + { + return SuspendFor(new CNEOBotJgrEnemy, "Attacking the Juggernaut"); + } + } + } + } + + ActionResult< CNEOBot > result = UpdateCommon( me, interval ); + if ( result.IsRequestingChange() || result.IsDone() ) + return result; + + if ( me->GetClass() == NEO_CLASS_JUGGERNAUT ) + { + return SuspendFor( new CNEOBotJgrJuggernaut, "I am the Juggernaut now" ); + } + + // Juggernaut objective capture logic + if (m_bGoingToTargetEntity && m_hTargetEntity) + { + const float useRangeSq = CNEO_Juggernaut::GetUseDistanceSquared() * 0.8f; + if ( me->GetAbsOrigin().DistToSqr( m_hTargetEntity->GetAbsOrigin() ) < useRangeSq ) + { + if ( !m_hTargetEntity->IsPlayer() ) + { + if ( me->IsLineOfSightClear( m_hTargetEntity, CBaseCombatCharacter::IGNORE_ACTORS ) ) + { + const char *classname = m_hTargetEntity->GetClassname(); + + if ( FStrEq( classname, "neo_juggernaut" ) ) + { + return SuspendFor( new CNEOBotJgrCapture( static_cast(m_hTargetEntity.Get()) ), "Capturing Juggernaut" ); + } + } + } + } + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +void CNEOBotJgrSeek::RecomputeSeekPath( CNEOBot *me ) +{ + if (NEORules()->GetGameType() != NEO_GAME_TYPE_JGR) + { + CNEOBotSeekAndDestroy::RecomputeSeekPath( me ); + return; + } + + m_hTargetEntity = NULL; + m_bGoingToTargetEntity = false; + m_vGoalPos = vec3_origin; + + if (NEORules()->JuggernautItemExists()) + { + const int iJuggernautPlayer = NEORules()->GetJuggernautPlayer(); + + // If it's on the ground (no owner) + if (iJuggernautPlayer <= 0) + { + CBaseEntity* pJuggernaut = gEntList.FindEntityByClassname(NULL, "neo_juggernaut"); + if (pJuggernaut) + { + m_vGoalPos = pJuggernaut->WorldSpaceCenter(); + m_bGoingToTargetEntity = true; + m_hTargetEntity = pJuggernaut; + + if (CNEOBotPathCompute(me, m_path, m_vGoalPos, FASTEST_ROUTE) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) + { + return; + } + } + } + + } + + // Fallback to base behavior (roaming spawn points) + CNEOBotSeekAndDestroy::RecomputeSeekPath( me ); +} diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.h b/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.h new file mode 100644 index 000000000..a8ede5723 --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_seek.h @@ -0,0 +1,21 @@ +#ifndef NEO_BOT_JGR_SEEK_H +#define NEO_BOT_JGR_SEEK_H +#pragma once + +#include "bot/behavior/neo_bot_seek_and_destroy.h" + +// +// JGR game mode behavior dispatcher +// +class CNEOBotJgrSeek : public CNEOBotSeekAndDestroy +{ +public: + CNEOBotJgrSeek( float duration = -1.0f ) : CNEOBotSeekAndDestroy( duration ) { } + + virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ); + virtual const char *GetName( void ) const { return "jgrSeek"; }; + +protected: + virtual void RecomputeSeekPath( CNEOBot *me ); +}; +#endif // NEO_BOT_JGR_SEEK_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index b67c4cefc..8094c5ec3 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -5,6 +5,7 @@ #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_attack.h" #include "bot/behavior/neo_bot_seek_and_destroy.h" +#include "bot/behavior/neo_bot_jgr_seek.h" #include "bot/neo_bot_path_compute.h" #include "nav_mesh.h" #include "neo_ghost_cap_point.h" @@ -51,6 +52,22 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::OnStart( CNEOBot *me, Action< CNE //--------------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotSeekAndDestroy::Update( CNEOBot *me, float interval ) +{ + ActionResult< CNEOBot > result = UpdateCommon( me, interval ); + if ( result.IsRequestingChange() || result.IsDone() ) + return result; + + // Check for Game Type Specific behaviors and suspend for them + if (NEORules()->GetGameType() == NEO_GAME_TYPE_JGR) + { + return SuspendFor( new CNEOBotJgrSeek, "Switching to Juggernaut-related Seek and Destroy" ); + } + + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +ActionResult< CNEOBot > CNEOBotSeekAndDestroy::UpdateCommon( CNEOBot *me, float interval ) { if ( m_giveUpTimer.HasStarted() && m_giveUpTimer.IsElapsed() ) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h index 39950bfa7..a90522153 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h @@ -31,7 +31,11 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > virtual const char *GetName( void ) const { return "SeekAndDestroy"; }; -private: +protected: + virtual ActionResult< CNEOBot > UpdateCommon( CNEOBot *me, float interval ); + + virtual void RecomputeSeekPath( CNEOBot *me ); + PathFollower m_path; CountdownTimer m_repathTimer; CountdownTimer m_itemStolenTimer; @@ -39,8 +43,6 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > bool m_bGoingToTargetEntity = false; Vector m_vGoalPos = vec3_origin; bool m_bTimerElapsed = false; - void RecomputeSeekPath( CNEOBot *me ); - bool m_bOverrideApproach = false; Vector m_vOverrideApproach = vec3_origin; diff --git a/src/game/shared/neo/neo_juggernaut.cpp b/src/game/shared/neo/neo_juggernaut.cpp index c3591fe81..70d916841 100644 --- a/src/game/shared/neo/neo_juggernaut.cpp +++ b/src/game/shared/neo/neo_juggernaut.cpp @@ -15,6 +15,16 @@ LINK_ENTITY_TO_CLASS(neo_juggernaut, CNEO_Juggernaut); +float CNEO_Juggernaut::GetUseDuration() +{ + return USE_DURATION; +} + +float CNEO_Juggernaut::GetUseDistanceSquared() +{ + return USE_DISTANCE_SQUARED; +} + #ifdef GAME_DLL IMPLEMENT_SERVERCLASS_ST(CNEO_Juggernaut, DT_NEO_Juggernaut) SendPropBool(SENDINFO(m_bLocked)), diff --git a/src/game/shared/neo/neo_juggernaut.h b/src/game/shared/neo/neo_juggernaut.h index fe921a509..c9b38e291 100644 --- a/src/game/shared/neo/neo_juggernaut.h +++ b/src/game/shared/neo/neo_juggernaut.h @@ -15,6 +15,9 @@ class CNEO_Juggernaut : public CBaseAnimating { public: DECLARE_CLASS(CNEO_Juggernaut, CBaseAnimating); + + static float GetUseDuration(); + static float GetUseDistanceSquared(); #ifdef GAME_DLL virtual ~CNEO_Juggernaut(); DECLARE_SERVERCLASS();