Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/game/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,7 @@ set(UNITY_SOURCE_NEO_BOT
neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp
neo/bot/behavior/neo_bot_ctg_seek.cpp
neo/bot/behavior/neo_bot_dead.cpp
neo/bot/behavior/neo_bot_freezetime.cpp
neo/bot/behavior/neo_bot_grenade_dispatch.cpp
neo/bot/behavior/neo_bot_grenade_throw.cpp
neo/bot/behavior/neo_bot_grenade_throw_frag.cpp
Expand Down Expand Up @@ -1595,6 +1596,7 @@ target_sources_grouped(
neo/bot/behavior/neo_bot_ctg_seek.cpp
neo/bot/behavior/neo_bot_ctg_seek.h
neo/bot/behavior/neo_bot_dead.h
neo/bot/behavior/neo_bot_freezetime.h
neo/bot/behavior/neo_bot_grenade_dispatch.h
neo/bot/behavior/neo_bot_grenade_throw.h
neo/bot/behavior/neo_bot_grenade_throw_frag.h
Expand Down
22 changes: 22 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,32 @@ CNEOBotAttack::CNEOBotAttack( const Vector &goalPosition ) : m_chasePath( ChaseP
ActionResult< CNEOBot > CNEOBotAttack::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );

// NEO Jank: Rudimentary communication between paired buddy bots regarding threat
// If this bot was following another bot buddy,
// becoming the commander recalls their buddy to regroup
// and pings when shooting direct the buddy to the enemy's position
if ( CNEO_Player* commander = me->m_hCommandingPlayer.Get() )
{
if ( commander->IsBot() )
{
me->m_hCommandingPlayer.Set( nullptr );
commander->m_hCommandingPlayer.Set( me );
}
}

return Continue();
}


//---------------------------------------------------------------------------------------------
void CNEOBotAttack::OnEnd( CNEOBot *me, Action< CNEOBot > *nextAction )
{
// Clear out any target callouts when disengaged from threat
me->m_vLastPingByStar.GetForModify(me->GetStar()) = CNEO_Player::VECTOR_INVALID_WAYPOINT;
}


//---------------------------------------------------------------------------------------------
// for finding cover closer to our attack destination
class CSearchForAttackCover : public ISearchSurroundingAreasFunctor
Expand Down
3 changes: 2 additions & 1 deletion src/game/server/neo/bot/behavior/neo_bot_attack.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class CNEOBotAttack : public Action< CNEOBot >
CNEOBotAttack( const Vector &goalPosition );
virtual ~CNEOBotAttack() { }

virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction );
virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override;
virtual void OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction ) override;
virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval );

virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me );
Expand Down
166 changes: 166 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_freezetime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include "cbase.h"
#include "neo_player.h"
#include "bot/neo_bot.h"
#include "bot/behavior/neo_bot_freezetime.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// sv_neo_bot_freezetime_planning_start_time_buffer should be longer than sv_neo_bot_freezetime_planning_end_time_buffer
// ideally with enough ticks left to plan next round paths
// used to calibrate throttle timer for checking if a bot is in freezetime in tactical monitor
ConVar sv_neo_bot_freezetime_planning_start_time_buffer( "sv_neo_bot_freezetime_planning_start_time_buffer", "2", FCVAR_NONE,
"Seconds of freezetime that must be available before bots will start planning.", true, 0.0f, false, 0 );

// sv_neo_bot_freezetime_planning_end_time_buffer should leave enough ticks for path planning before the round starts
ConVar sv_neo_bot_freezetime_planning_end_time_buffer( "sv_neo_bot_freezetime_planning_end_time_buffer", "1", FCVAR_NONE,
"Seconds of freezetime left for bots to stop planning prior to round start.", true, 0.0f, false, 0 );

ActionResult< CNEOBot > CNEOBotFreezeTime::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction )
{
m_bBuddyPairingDecisionMade = false;
return Continue();
}

ActionResult< CNEOBot > CNEOBotFreezeTime::Update( CNEOBot* me, float interval )
{
if ( !me || !me->IsAlive() )
{
return Done("Bot is not in valid state for freezetime planning");
}

const float freezeTimeLeft = NEORules()->GetRemainingPreRoundFreezeTime( true );
if ( freezeTimeLeft < sv_neo_bot_freezetime_planning_end_time_buffer.GetFloat() )
{
return Done("Done planning, waiting for freeze time to end");
}

if ( CNEO_Player* buddy = me->m_hCommandingPlayer.Get() )
{
// Check if buddy was reassigned to another partner
if ( buddy->m_hCommandingPlayer.Get() != nullptr )
{
// Retry looking for another buddy
m_bBuddyPairingDecisionMade = false;
me->m_hCommandingPlayer = nullptr;
}
}

if ( !m_bBuddyPairingDecisionMade )
{
const bool foundBuddy = TryFindBuddy( me );
if ( foundBuddy )
{
return ChangeTo( new CNEOBotCommandFollow, "Freeze-time buddy found" );
}
}

return Continue();
}

bool CNEOBotFreezeTime::TryFindBuddy( CNEOBot *me )
{
if ( m_bBuddyPairingDecisionMade )
{
return true;
}

// Check if I already have a commander
// This could happen if a human selects a bot as a follower
if ( me->m_hCommandingPlayer.Get() != nullptr )
{
return false;
}

m_bBuddyPairingDecisionMade = true;

// We shouldn't be pairing up in free for all matches
if ( !NEORules()->IsTeamplay() )
{
return false;
}

CUtlVector<CNEO_Player *> friendlyPlayers;
CollectPlayers( &friendlyPlayers, me->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );

CNEO_Player *bestBuddy = nullptr;
float bestDistanceSq = FLT_MAX;

for ( int i = 0; i < friendlyPlayers.Count(); ++i )
{
CNEO_Player *friendly = friendlyPlayers[i];
if ( !friendly || !WantsToPairWithBuddyCandidate( me, friendly ) )
{
continue;
}

const float distSq = ( friendly->GetAbsOrigin() - me->GetAbsOrigin() ).LengthSqr();
if ( distSq < bestDistanceSq )
{
bestDistanceSq = distSq;
bestBuddy = friendly;
}
}

if ( !bestBuddy )
{
return false;
}

me->m_hCommandingPlayer = bestBuddy;
me->m_hLeadingPlayer = bestBuddy;
return true;
}

bool CNEOBotFreezeTime::WantsToPairWithBuddyCandidate( const CNEO_Player *me, const CNEO_Player *buddy )
{
if ( !me || !buddy || me == buddy )
{
return false;
}

// CollectPlayers should only return teammates
Assert ( buddy->GetTeamNumber() == me->GetTeamNumber() );

// It might confuse human players if bots decide to follow them unprompted
if ( !buddy->IsBot() )
{
return false;
}

// This bot is already paired with a leader
if ( buddy->m_hCommandingPlayer.Get() != nullptr )
{
return false;
}

// Bots in different squad stars should not link with each other
// Humans can rearrange bot squad star membership to influence valid pairings
if ( buddy->GetStar() == me->GetStar())
{
return false;
}

const int myClass = me->GetClass();
const int buddyClass = buddy->GetClass();

if ( myClass == NEO_CLASS_RECON )
{
// Recons only pair with other recons
return buddyClass == NEO_CLASS_RECON;
}
if ( myClass == NEO_CLASS_ASSAULT )
{
// Assaults avoid pairing with Supports and VIPS
return buddyClass == NEO_CLASS_RECON || buddyClass == NEO_CLASS_ASSAULT;
}
if ( myClass == NEO_CLASS_SUPPORT )
{
// Supports avoid following VIPs
return buddyClass != NEO_CLASS_VIP;
}

// VIPs can pair with anyone
// JGR does not exist as a player in freezetime
return true;
}
18 changes: 18 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_freezetime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "bot/neo_bot.h"

class CNEOBotFreezeTime : public Action< CNEOBot >
{
public:
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 "FreezeTime"; }

private:
bool m_bBuddyPairingDecisionMade = false;

bool TryFindBuddy( CNEOBot *me );
bool WantsToPairWithBuddyCandidate( const CNEO_Player *me, const CNEO_Player *buddy );
};
22 changes: 22 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,32 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::OnStart( CNEOBot *me, Action< CNE

m_waitInCoverTimer.Start( m_hideDuration );


// NEO Jank: Rudimentary communication between paired buddy bots regarding threat
// If this bot was following another bot buddy,
// becoming the commander recalls their buddy to regroup
// and pings when shooting direct the buddy to the enemy's position
if ( CNEO_Player* commander = me->m_hCommandingPlayer.Get() )
{
if ( commander->IsBot() )
{
me->m_hCommandingPlayer.Set( nullptr );
commander->m_hCommandingPlayer.Set( me );
}
}

return Continue();
}


//---------------------------------------------------------------------------------------------
void CNEOBotRetreatToCover::OnEnd( CNEOBot *me, Action< CNEOBot > *nextAction )
{
// Clear out any target callouts when disengaged from threat
me->m_vLastPingByStar.GetForModify(me->GetStar()) = CNEO_Player::VECTOR_INVALID_WAYPOINT;
}


//---------------------------------------------------------------------------------------------
ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interval )
{
Expand Down
5 changes: 3 additions & 2 deletions src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ class CNEOBotRetreatToCover : public Action< CNEOBot >
CNEOBotRetreatToCover( float hideDuration = -1.0f );
CNEOBotRetreatToCover( Action< CNEOBot > *actionToChangeToOnceCoverReached );

virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction );
virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval );
virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override;
virtual void OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction ) override;
virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ) override;

virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me );
virtual EventDesiredResult< CNEOBot > OnMoveToSuccess( CNEOBot *me, const Path *path );
Expand Down
19 changes: 19 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "bot/behavior/neo_bot_tactical_monitor.h"
#include "bot/behavior/neo_bot_scenario_monitor.h"

#include "bot/behavior/neo_bot_freezetime.h"
#include "bot/behavior/neo_bot_seek_and_destroy.h"
#include "bot/behavior/neo_bot_seek_weapon.h"
#include "bot/behavior/neo_bot_retreat_to_cover.h"
Expand All @@ -33,6 +34,11 @@ ConVar neo_bot_force_jump( "neo_bot_force_jump", "0", FCVAR_CHEAT, "Force bots t
ConVar neo_bot_scavenge_upgrade_delay( "neo_bot_scavenge_upgrade_delay", "4", FCVAR_GAMEDLL,
"Delay in seconds between checking for a better weapon if the bot already has a primary weapon.", true, 1, false, 0 );

extern ConVar mp_chattime;
extern ConVar sv_neo_bot_freezetime_planning_start_time_buffer;
extern ConVar sv_neo_bot_freezetime_planning_end_time_buffer;
extern ConVar sv_neo_preround_freeze_time;

////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -327,6 +333,19 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter
me->ReloadIfLowClip();
}

// if we are in freezetime, make plans before next round
if ( !m_freezeTimeDecisionTimer.HasStarted() || m_freezeTimeDecisionTimer.IsElapsed() )
{
const float freezeCheckDelay = mp_chattime.GetFloat() + Max( 0.0f, sv_neo_preround_freeze_time.GetFloat() - sv_neo_bot_freezetime_planning_start_time_buffer.GetFloat() );
m_freezeTimeDecisionTimer.Start( freezeCheckDelay );

const float freezeTimeLeft = NEORules()->GetRemainingPreRoundFreezeTime( true );
if ( freezeTimeLeft > sv_neo_bot_freezetime_planning_end_time_buffer.GetFloat() )
{
return SuspendFor( new CNEOBotFreezeTime, "Planning next round during freezetime" );
}
}

me->UpdateDelayedThreatNotices();

return Continue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CNEOBotTacticalMonitor : public Action< CNEOBot >

private:
CountdownTimer m_maintainTimer;
CountdownTimer m_freezeTimeDecisionTimer;

CountdownTimer m_acknowledgeAttentionTimer;
CountdownTimer m_acknowledgeRetryTimer;
Expand Down