Skip to content
Open
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 @@ -1483,6 +1483,8 @@ target_sources_grouped(
neo/bot/behavior/neo_bot_melee_attack.h
neo/bot/behavior/neo_bot_move_to_vantage_point.cpp
neo/bot/behavior/neo_bot_move_to_vantage_point.h
neo/bot/behavior/neo_bot_path_clear_breakable.cpp
neo/bot/behavior/neo_bot_path_clear_breakable.h
neo/bot/behavior/neo_bot_pause.cpp
neo/bot/behavior/neo_bot_pause.h
neo/bot/behavior/neo_bot_retreat_to_cover.cpp
Expand Down
67 changes: 0 additions & 67 deletions src/game/server/neo/bot/behavior/neo_bot_behavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ ConVar neo_bot_path_lookahead_range( "neo_bot_path_lookahead_range", "300" );
ConVar neo_bot_sniper_aim_error( "neo_bot_sniper_aim_error", "0.01", FCVAR_CHEAT );
ConVar neo_bot_sniper_aim_steady_rate( "neo_bot_sniper_aim_steady_rate", "10", FCVAR_CHEAT );
ConVar neo_bot_debug_sniper( "neo_bot_debug_sniper", "0", FCVAR_CHEAT );
ConVar neo_bot_fire_at_breakable_weapon_min_time( "neo_bot_fire_at_breakable_weapon_min_time", "0.4", FCVAR_CHEAT, "Minimum time to fire at breakables", true, 0.0f, true, 60.0f );

ConVar neo_bot_notice_backstab_chance( "neo_bot_notice_backstab_chance", "25", FCVAR_CHEAT );
ConVar neo_bot_notice_backstab_min_range( "neo_bot_notice_backstab_min_range", "100", FCVAR_CHEAT );
Expand Down Expand Up @@ -650,78 +649,12 @@ void CNEOBotMainAction::FireWeaponAtEnemy( CNEOBot *me )
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
const bool bIgnoreThreat = (threat == nullptr || !threat->GetEntity() || !threat->IsVisibleRecently());

if (me->GetNeoFlags() & NEO_FL_FREEZETIME)
{
me->GetLocomotionInterface()->m_bBreakBreakableInPath = false;
}

if (me->GetLocomotionInterface()->m_bBreakBreakableInPath)
{
if (me->GetDifficulty() >= CNEOBot::HARD && !m_bPrevBreakBreakableInPath && bIgnoreThreat)
{
auto *secondaryWep = static_cast<CNEOBaseCombatWeapon *>(me->Weapon_GetSlot(1));
if (secondaryWep != myWeapon && secondaryWep &&
((secondaryWep->Clip1() + secondaryWep->m_iPrimaryAmmoCount) > 0))
{
me->Weapon_Switch(secondaryWep);
}
}

if (myWeapon->Clip1() <= 0)
{
me->ReloadIfLowClip(true);
m_isWaitingForFullReload = true;
}

if (m_isWaitingForFullReload)
{
m_isWaitingForFullReload = (myWeapon->Clip1() < myWeapon->GetMaxClip1());
}

if (!m_isWaitingForFullReload)
{
if (me->IsContinuousFireWeapon(myWeapon))
{
me->PressFireButton(neo_bot_fire_at_breakable_weapon_min_time.GetFloat());
}
else
{
if (me->m_nButtons & IN_ATTACK)
{
me->ReleaseFireButton();
}
else
{
me->PressFireButton();
}
}
}
m_bPrevBreakBreakableInPath = true;
return;
}

// ignore non-visible threats here so we don't force a premature weapon switch if we're doing something else
if (bIgnoreThreat)
{
return;
}

// After bIgnoreThreat so it doesn't go at a continous cycle switching weapons
if (!me->GetLocomotionInterface()->m_bBreakBreakableInPath && m_bPrevBreakBreakableInPath)
{
if (me->GetDifficulty() >= CNEOBot::HARD &&
(!myWeapon ||
(myWeapon->GetNeoWepBits() & (NEO_WEP_MILSO | NEO_WEP_TACHI | NEO_WEP_KYLA | NEO_WEP_KNIFE | NEO_WEP_THROWABLE))))
{
auto *primaryWeapon = static_cast<CNEOBaseCombatWeapon *>(me->Weapon_GetSlot(0));
if (primaryWeapon && (primaryWeapon->Clip1() + primaryWeapon->m_iPrimaryAmmoCount) > 0)
{
me->Weapon_Switch(primaryWeapon);
}
}
m_bPrevBreakBreakableInPath = false;
}

CNEOBot::LineOfFireFlags lofFlags = CNEOBot::LINE_OF_FIRE_FLAGS_DEFAULT;
auto *neoThreat = ToNEOPlayer(threat->GetEntity());

Expand Down
3 changes: 0 additions & 3 deletions src/game/server/neo/bot/behavior/neo_bot_behavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,4 @@ class CNEOBotMainAction : public Action< CNEOBot >, public CNEOBotContextualQuer
void Dodge( CNEOBot *me );

IntervalTimer m_undergroundTimer;

CountdownTimer m_reevaluateClassTimer;
bool m_bPrevBreakBreakableInPath = false;
};
287 changes: 287 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_path_clear_breakable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
#include "cbase.h"

#include "bot/neo_bot.h"
#include "bot/behavior/neo_bot_path_clear_breakable.h"
#include "neo_gamerules.h"

extern ConVar neo_bot_fire_weapon_allowed;

ConVar neo_bot_fire_at_breakable_weapon_min_time( "neo_bot_fire_at_breakable_weapon_min_time", "0.2", FCVAR_CHEAT,
"Minimum time to fire at breakables", true, 0.0f, true, 60.0f );

//--------------------------------------------------------------------------------------------------------
CBaseEntity *CNEOBotPathClearBreakable::GetBreakableInPath( CNEOBot *me )
{
if ( !me || !me->IsAlive() )
{
return nullptr;
}

if ( me->GetNeoFlags() & NEO_FL_FREEZETIME )
{
return nullptr;
}

if ( !neo_bot_fire_weapon_allowed.GetBool() )
{
return nullptr;
}

// Only enter this behavior if there is no visible threat
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->GetEntity() && threat->IsVisibleRecently() )
{
return nullptr;
}

const PathFollower *path = me->GetCurrentPath();
if ( !path || !path->IsValid() )
{
return nullptr;
}

const Path::Segment *goal = path->GetCurrentGoal();
if ( !goal )
{
return nullptr;
}

// Trace forward along the path to look for breakables
// We use a hull trace to match the bot's collision size
trace_t tr;
UTIL_TraceHull( me->GetAbsOrigin() + Vector( 0, 0, 10 ),
goal->pos + Vector( 0, 0, 10 ),
me->GetBodyInterface()->GetHullMins(),
me->GetBodyInterface()->GetHullMaxs(),
MASK_NPCSOLID,
me->GetEntity(),
COLLISION_GROUP_NONE,
&tr );

if ( tr.DidHit() && tr.m_pEnt )
{
if ( me->IsAbleToBreak( tr.m_pEnt ) )
{
return tr.m_pEnt;
}
}

return nullptr;
}


//--------------------------------------------------------------------------------------------------------
CNEOBotPathClearBreakable::CNEOBotPathClearBreakable( CBaseEntity *breakable )
{
m_hBreakable = breakable;
}


//--------------------------------------------------------------------------------------------------------
ActionResult< CNEOBot > CNEOBotPathClearBreakable::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction )
{
m_isWaitingForFullReload = false;
m_bDidSwitchWeapon = false;
m_bSkipKnife = false;
m_bStrafeRight = RandomInt( 0, 1 ) == 0;
m_giveUpTimer.Start( 10.0f );
m_meleeTimeoutTimer.Start( 2.0f );

if ( !m_hBreakable.Get() )
{
return Done( "No breakable target" );
}

// Prioritize weapons for clearing breakables:
// 1. Knife (Slot 2)
// 2. Secondary (Slot 1) if it has ammo
// 3. Current/Primary (Slot 0) as fallback

auto *myWeapon = static_cast<CNEOBaseCombatWeapon *>( me->GetActiveWeapon() );
auto *knife = static_cast<CNEOBaseCombatWeapon *>( me->Weapon_GetSlot( 2 ) );
auto *secondaryWep = static_cast<CNEOBaseCombatWeapon *>( me->Weapon_GetSlot( 1 ) );

if ( !m_bSkipKnife && knife && knife != myWeapon )
{
me->Weapon_Switch( knife );
m_bDidSwitchWeapon = true;
}
else if ( secondaryWep && secondaryWep != myWeapon &&
( ( secondaryWep->Clip1() + secondaryWep->m_iPrimaryAmmoCount ) > 0 ) )
{
me->Weapon_Switch( secondaryWep );
m_bDidSwitchWeapon = true;
}

return Continue();
}


//--------------------------------------------------------------------------------------------------------
ActionResult< CNEOBot > CNEOBotPathClearBreakable::Update( CNEOBot *me, float interval )
{
CBaseEntity *breakable = m_hBreakable.Get();

// If the breakable is destroyed or invalid, we're done
if ( !breakable || !breakable->IsAlive() || breakable->GetHealth() <= 0 )
{
return Done( "Path is clear" );
}

if ( m_giveUpTimer.IsElapsed() )
{
return Done( "Give up timer elapsed" );
}

// Double check distance - if we've moved too far away or the breakable is too far, stop
if ( me->GetRangeSquaredTo( breakable ) > Square( 500.0f ) )
{
return Done( "Breakable is too far" );
}

// If freeze time started, bail out
if ( me->GetNeoFlags() & NEO_FL_FREEZETIME )
{
return Done( "Freeze time" );
}

// If a visible threat appeared, bail out and let the normal combat system handle it
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->GetEntity() && threat->IsVisibleRecently() )
{
return Done( "Threat appeared" );
}

auto *myWeapon = static_cast<CNEOBaseCombatWeapon *>( me->GetActiveWeapon() );
if ( !myWeapon )
{
return Done( "No weapon" );
}

// Aim at the breakable
me->GetBodyInterface()->AimHeadTowards(
breakable->WorldSpaceCenter(), IBody::MANDATORY, 1.0f, nullptr,
"Attacking breakable in path" );

// Handle reloading if clip is empty
bool bUsesClips = myWeapon->UsesClipsForAmmo1();
if ( bUsesClips && myWeapon->Clip1() <= 0 )
{
me->ReleaseFireButton();
me->PressReloadButton();
m_isWaitingForFullReload = true;
}

if ( m_isWaitingForFullReload )
{
m_isWaitingForFullReload = ( bUsesClips && myWeapon->Clip1() < myWeapon->GetMaxClip1() );
}

// Fire at the breakable if aim is ready
if ( !m_isWaitingForFullReload && me->GetBodyInterface()->IsHeadAimingOnTarget() )
{
// First check if friendly in line of fire
trace_t aimTr;
Vector vForward;
me->EyeVectors( &vForward );
Vector vStart = me->EyePosition();
Vector vEnd = vStart + vForward * 500.0f;
UTIL_TraceLine( vStart, vEnd, MASK_SHOT_HULL, me->GetEntity(), COLLISION_GROUP_NONE, &aimTr );

if ( aimTr.m_pEnt )
{
if ( aimTr.m_pEnt->IsPlayer() )
{
CNEO_Player *pPlayer = ToNEOPlayer( aimTr.m_pEnt );
if ( pPlayer && pPlayer->InSameTeam( me->GetEntity() ) )
{
// Avoid friendly and allow them to break breakable if same path
me->ReleaseFireButton();
me->ReleaseForwardButton();
me->PressBackwardButton();
if ( m_bStrafeRight )
{
me->PressRightButton();
}
else
{
me->PressLeftButton();
}

return Continue();
}
}

// Distance check for knife prioritization
if ( aimTr.m_pEnt == breakable )
{
float flDist = ( aimTr.endpos - vStart ).Length();
if ( flDist > 50.0f ) // NEO_WEP_KNIFE_RANGE
{
m_bSkipKnife = true;
}
}
}

// If we've been trying to melee for too long, or the target is too far for a knife
if ( ( m_meleeTimeoutTimer.IsElapsed() || ( m_bSkipKnife && ( myWeapon->GetNeoWepBits() & NEO_WEP_KNIFE ) ) ) &&
( myWeapon->GetNeoWepBits() & NEO_WEP_KNIFE ) )
{
auto *secondaryWep = static_cast<CNEOBaseCombatWeapon *>( me->Weapon_GetSlot( 1 ) );
auto *primaryWeapon = static_cast<CNEOBaseCombatWeapon *>( me->Weapon_GetSlot( 0 ) );

if ( secondaryWep && ( ( secondaryWep->Clip1() + secondaryWep->m_iPrimaryAmmoCount ) > 0 ) )
{
me->Weapon_Switch( secondaryWep );
m_bDidSwitchWeapon = true;
}
else if ( primaryWeapon && ( ( primaryWeapon->Clip1() + primaryWeapon->m_iPrimaryAmmoCount ) > 0 ) )
{
me->Weapon_Switch( primaryWeapon );
m_bDidSwitchWeapon = true;
}
}

// Prevent bot from missing breakable because of leaning
me->ReleaseLeanLeftButton();
me->ReleaseLeanRightButton();

if ( me->IsContinuousFireWeapon( myWeapon ) )
{
me->PressFireButton( neo_bot_fire_at_breakable_weapon_min_time.GetFloat() );
}
else
{
if ( me->m_nButtons & IN_ATTACK )
{
me->ReleaseFireButton();
}
else
{
me->PressFireButton();
}
}
}

return Continue();
}


//--------------------------------------------------------------------------------------------------------
void CNEOBotPathClearBreakable::OnEnd( CNEOBot *me, Action< CNEOBot > *nextAction )
{
// If we switched weapon, try to switch back to primary
if ( m_bDidSwitchWeapon )
{
auto *myWeapon = static_cast<CNEOBaseCombatWeapon *>( me->GetActiveWeapon() );
if ( !myWeapon ||
( myWeapon->GetNeoWepBits() & ( NEO_WEP_MILSO | NEO_WEP_TACHI | NEO_WEP_KYLA | NEO_WEP_KNIFE | NEO_WEP_THROWABLE ) ) )
{
auto *primaryWeapon = static_cast<CNEOBaseCombatWeapon *>( me->Weapon_GetSlot( 0 ) );
if ( primaryWeapon && ( primaryWeapon->Clip1() + primaryWeapon->m_iPrimaryAmmoCount ) > 0 )
{
me->Weapon_Switch( primaryWeapon );
}
}
}
}
Loading
Loading