diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp index cca00ee16..c6e253eef 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp @@ -20,8 +20,12 @@ CNEOBotCtgLoneWolf::CNEOBotCtgLoneWolf( void ) m_bHasRetreatedFromGhost = false; m_vecDropThreatPos = CNEO_Player::VECTOR_INVALID_WAYPOINT; m_closestCapturePoint = CNEO_Player::VECTOR_INVALID_WAYPOINT; + m_pIgnoredWeapons = std::make_unique(); } +//--------------------------------------------------------------------------------------------- +CNEOBotCtgLoneWolf::~CNEOBotCtgLoneWolf() = default; + //--------------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotCtgLoneWolf::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) { @@ -33,6 +37,8 @@ ActionResult< CNEOBot > CNEOBotCtgLoneWolf::OnStart( CNEOBot *me, Action< CNEOBo m_repathTimer.Invalidate(); m_stalemateTimer.Invalidate(); m_capPointUpdateTimer.Invalidate(); + m_scavengeTimer.Invalidate(); + m_pIgnoredWeapons->Reset(); m_vecDropThreatPos = CNEO_Player::VECTOR_INVALID_WAYPOINT; m_closestCapturePoint = CNEO_Player::VECTOR_INVALID_WAYPOINT; m_hPursueTarget = nullptr; @@ -59,7 +65,7 @@ ActionResult< CNEOBot > CNEOBotCtgLoneWolf::Update( CNEOBot *me, float interval // First, ensure we have a weapon. if ( !me->Weapon_GetSlot( 0 ) ) { - return SuspendFor( new CNEOBotSeekWeapon(), "Scavenging for weapon to hunt threat" ); + return SuspendFor( new CNEOBotSeekWeapon(nullptr, m_pIgnoredWeapons.get()), "Scavenging for weapon to hunt threat" ); } // We have a weapon. Investigate the last known location. @@ -204,22 +210,19 @@ ActionResult< CNEOBot > CNEOBotCtgLoneWolf::Update( CNEOBot *me, float interval else { // Enemy is closer to goal (blocking us) or gaining on us. - - // If we see a weapon nearby, drop the ghost and take it - CBaseEntity *pNearbyWeapon = FindNearestPrimaryWeapon( me->GetAbsOrigin(), true ); - if ( pNearbyWeapon ) + if ( m_scavengeTimer.IsElapsed() ) { - CBaseCombatWeapon *pGhostWep = me->Weapon_GetSlot( 0 ); - if ( pGhostWep ) + m_scavengeTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + // If we see a weapon nearby, drop the ghost and take it + CBaseEntity *pNearbyWeapon = FindNearestPrimaryWeapon( me, true, m_pIgnoredWeapons.get() ); + if ( pNearbyWeapon ) { - if ( me->GetActiveWeapon() != pGhostWep ) + CBaseCombatWeapon *pGhostWep = me->Weapon_GetSlot( 0 ); + if ( pGhostWep ) { - me->Weapon_Switch( pGhostWep ); - return Continue(); + return SuspendFor( new CNEOBotSeekWeapon( pNearbyWeapon, m_pIgnoredWeapons.get() ), "Dropping ghost to scavenge nearby weapon" ); } - - me->PressDropButton( 0.1f ); - return ChangeTo( new CNEOBotSeekWeapon(), "Dropping ghost to scavenge nearby weapon" ); } } diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h index 6458c1dc2..5cb1c8c0d 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h @@ -1,12 +1,16 @@ #pragma once #include "bot/neo_bot.h" +#include + +class CNEOIgnoredWeaponsCache; //-------------------------------------------------------------------------------------------------------- class CNEOBotCtgLoneWolf : public Action< CNEOBot > { public: CNEOBotCtgLoneWolf( void ); + virtual ~CNEOBotCtgLoneWolf(); virtual ActionResult< CNEOBot > OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) override; virtual ActionResult< CNEOBot > Update( CNEOBot *me, float interval ) override; @@ -29,6 +33,8 @@ class CNEOBotCtgLoneWolf : public Action< CNEOBot > Vector m_vecDropThreatPos; CHandle m_hPursueTarget; bool m_bPursuingDropThreat; + std::unique_ptr m_pIgnoredWeapons; + CountdownTimer m_scavengeTimer; ActionResult< CNEOBot > UpdateLookAround( CNEOBot *me, const Vector &anchorPos ); CountdownTimer m_lookAroundTimer; diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp index 9c0a4b91e..2a56eea98 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp @@ -1,38 +1,204 @@ #include "cbase.h" -#include "neo_player.h" #include "bot/neo_bot.h" #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/neo_bot_path_compute.h" +#include "weapon_neobasecombatweapon.h" +#include "neo_player_shared.h" +#include "nav_mesh.h" //--------------------------------------------------------------------------------------------- -CBaseEntity *FindNearestPrimaryWeapon( const Vector &searchOrigin, bool bShortCircuit ) +// Mask of non-identity "tag" bits that GetNeoWepBits() may include +static constexpr NEO_WEP_BITS_UNDERLYING_TYPE WEP_TAG_BITS_MASK = + NEO_WEP_SCOPEDWEAPON | NEO_WEP_THROWABLE | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM | NEO_WEP_EXPLOSIVE; + +static constexpr int BOT_WEP_PREF_RANK_UNPREFERRED = -1; +static constexpr int BOT_WEP_PREF_RANK_EMPTY = -2; + +//--------------------------------------------------------------------------------------------- +bool IsUndroppablePrimary( CBaseCombatWeapon *pPrimary ) +{ + if ( !pPrimary ) + { + return false; + } + + CNEOBaseCombatWeapon *pNeoWep = ToNEOWeapon( pPrimary ); + if ( !pNeoWep ) + { + return false; + } + + const NEO_WEP_BITS_UNDERLYING_TYPE wepBits = pNeoWep->GetNeoWepBits(); + return ( wepBits & NEO_WEP_BALC ) != 0; +} + +//--------------------------------------------------------------------------------------------- +int GetBotWeaponPreferenceRank( CNEOBot *me, NEO_WEP_BITS_UNDERLYING_TYPE wepBit ) +{ + // Strip tag bits to get the primary identity bit + const NEO_WEP_BITS_UNDERLYING_TYPE identityBit = wepBit & ~WEP_TAG_BITS_MASK; + + const int playerClass = me->GetClass(); + if ( playerClass < 0 || playerClass >= NEO_CLASS__LOADOUTABLE_COUNT ) + { + return BOT_WEP_PREF_RANK_UNPREFERRED; + } + + for ( int idxRank = NEO_RANK__TOTAL - 1; idxRank >= 0; --idxRank ) + { + if ( me->m_profile.flagsWepPrefs[playerClass][idxRank] & identityBit ) + { + return idxRank; + } + } + + return BOT_WEP_PREF_RANK_UNPREFERRED; +} + +//--------------------------------------------------------------------------------------------- +bool IsWeaponPreferenceUpgrade( CNEOBot *me, CNEOBaseCombatWeapon *pTargetWep, int myPrefRank, bool bHasReserveAmmo ) +{ + if ( !pTargetWep ) + { + return false; + } + + if ( myPrefRank == BOT_WEP_PREF_RANK_EMPTY ) + { + // We have no primary weapon at all, anything is an upgrade + return true; + } + + if ( !bHasReserveAmmo ) + { + if ( pTargetWep->GetPrimaryAmmoCount() > 0 ) + { + return true; + } + } + + const NEO_WEP_BITS_UNDERLYING_TYPE targetWepBits = pTargetWep->GetNeoWepBits(); + const int targetPrefRank = GetBotWeaponPreferenceRank( me, targetWepBits ); + + if ( targetPrefRank <= BOT_WEP_PREF_RANK_UNPREFERRED && bHasReserveAmmo ) + { + return false; + } + + if ( myPrefRank >= BOT_WEP_PREF_RANK_UNPREFERRED && targetPrefRank <= myPrefRank ) + { + return false; + } + + return true; +} + +//--------------------------------------------------------------------------------------------- +CBaseEntity *FindNearestPrimaryWeapon( CNEOBot *me, bool bAllowDropGhost, CNEOIgnoredWeaponsCache *pIgnoredWeapons ) { constexpr float flSearchRadius = 1000.0f; CBaseEntity *pClosestWeapon = nullptr; float flClosestDistSq = FLT_MAX; + int iBestWeaponRank = -999; + + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); + if ( pPrimary ) + { + CNEOBaseCombatWeapon *pNeoPrimary = ToNEOWeapon( pPrimary ); + if ( pNeoPrimary && ( pNeoPrimary->GetNeoWepBits() & NEO_WEP_BALC ) ) + { + // can't switch these weapons + return nullptr; + } + + if ( !bAllowDropGhost ) + { + if ( pNeoPrimary->GetNeoWepBits() & NEO_WEP_GHOST ) + { + // Hold onto the ghost unless we are allowed to by the situation + return nullptr; + } + + if ( me->GetVisionInterface()->GetPrimaryKnownThreat( true ) != nullptr ) + { + // Too exposed to drop current weapon + return nullptr; + } + } + } + + int myPrefRank = BOT_WEP_PREF_RANK_EMPTY; + bool bHasReserveAmmo = false; + if ( pPrimary ) + { + myPrefRank = BOT_WEP_PREF_RANK_UNPREFERRED; + bHasReserveAmmo = pPrimary->GetPrimaryAmmoCount() > 0; + CNEOBaseCombatWeapon *pMyNeoWep = ToNEOWeapon( pPrimary ); + if ( pMyNeoWep ) + { + myPrefRank = GetBotWeaponPreferenceRank( me, pMyNeoWep->GetNeoWepBits() ); + } + } + + // For checking if weapon candidate is in PVS of me + CNavArea *pMyArea = me->GetLastKnownArea(); + if ( !pMyArea ) + { + pMyArea = TheNavMesh->GetNearestNavArea( me->GetAbsOrigin() ); + } // Iterate through all available weapons, looking for the nearest primary CBaseEntity *pEntity = nullptr; - while ( ( pEntity = gEntList.FindEntityByClassnameWithin( pEntity, "weapon_*", searchOrigin, flSearchRadius ) ) ) + while ( ( pEntity = gEntList.FindEntityByClassnameWithin( pEntity, "weapon_*", me->GetAbsOrigin(), flSearchRadius ) ) ) { + if ( pIgnoredWeapons && pIgnoredWeapons->Has( pEntity ) ) + { + continue; + } + CBaseCombatWeapon *pWeapon = pEntity->MyCombatWeaponPointer(); if ( pWeapon && !pWeapon->GetOwner() && pWeapon->HasAnyAmmo() && pWeapon->GetSlot() == 0 ) { - // Prioritize getting any primary class weapon that is NOT the ghost - if ( FStrEq( pEntity->GetClassname(), "weapon_ghost" ) ) + CNEOBaseCombatWeapon *pNeoWeapon = ToNEOWeapon( pWeapon ); + if ( !pNeoWeapon || ( pNeoWeapon->GetNeoWepBits() & NEO_WEP_GHOST ) ) { continue; } + + // only consider weapons in the bot's wishlist that are an upgrade + if ( !IsWeaponPreferenceUpgrade( me, pNeoWeapon, myPrefRank, bHasReserveAmmo ) ) + { + continue; + } + + const int targetPrefRank = GetBotWeaponPreferenceRank( me, pNeoWeapon->GetNeoWepBits() ); + + float flDistSq = me->GetAbsOrigin().DistToSqr( pEntity->GetAbsOrigin() ); - if ( bShortCircuit ) + bool bBetterFound = false; + if ( targetPrefRank > iBestWeaponRank ) { - return pEntity; + bBetterFound = true; + } + else if ( targetPrefRank == iBestWeaponRank && flDistSq < flClosestDistSq ) + { + bBetterFound = true; } - float flDistSq = searchOrigin.DistToSqr( pEntity->GetAbsOrigin() ); - if ( flDistSq < flClosestDistSq ) + if ( bBetterFound ) { + // Check if weapon candidate is in PVS of me + if ( pMyArea ) + { + CNavArea *pWepArea = TheNavMesh->GetNavArea( pEntity->WorldSpaceCenter() ); + if ( pWepArea && !pMyArea->IsPotentiallyVisible( pWepArea ) ) + { + continue; + } + } + flClosestDistSq = flDistSq; + iBestWeaponRank = targetPrefRank; pClosestWeapon = pEntity; } } @@ -42,49 +208,66 @@ CBaseEntity *FindNearestPrimaryWeapon( const Vector &searchOrigin, bool bShortCi } //--------------------------------------------------------------------------------------------- -CNEOBotSeekWeapon::CNEOBotSeekWeapon( void ) +CNEOBotSeekWeapon::CNEOBotSeekWeapon( CBaseEntity *pTargetWeapon, CNEOIgnoredWeaponsCache *pIgnoredWeapons ) { - m_hTargetWeapon = nullptr; + m_hTargetWeapon = pTargetWeapon; + m_pIgnoredWeapons = pIgnoredWeapons; } //--------------------------------------------------------------------------------------------- CBaseEntity *CNEOBotSeekWeapon::FindAndPathToWeapon( CNEOBot *me ) { - CBaseEntity *pClosestWeapon = FindNearestPrimaryWeapon( me->GetAbsOrigin() ); + if ( !m_hTargetWeapon ) + { + m_hTargetWeapon = FindNearestPrimaryWeapon( me, false, m_pIgnoredWeapons ); + } - if (pClosestWeapon) + if ( m_hTargetWeapon ) { - m_hTargetWeapon = pClosestWeapon; - CNEOBotPathCompute(me, m_path, m_hTargetWeapon->GetAbsOrigin(), FASTEST_ROUTE); + if ( !CNEOBotPathCompute( me, m_path, m_hTargetWeapon->GetAbsOrigin(), FASTEST_ROUTE ) || !m_path.IsValid() ) + { + m_hTargetWeapon = nullptr; + m_path.Invalidate(); + } } else { // no weapon found - m_hTargetWeapon = nullptr; m_path.Invalidate(); - } - return pClosestWeapon; + return m_hTargetWeapon; } //--------------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotSeekWeapon::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) { - CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot(0); - if ( pPrimary ) + m_repathTimer.Invalidate(); + m_giveUpTimer.Start( 10.0f ); + m_path.Invalidate(); + + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); + if ( IsUndroppablePrimary( pPrimary ) ) { - if ( pPrimary->HasAnyAmmo() ) - { - // so we don't exit this behavior still equipped with secondary - me->Weapon_Switch(pPrimary); - } - return Done("Already have a primary weapon"); + // Don't scavenge if we have a weapon like the balc + return Done("Equipped with an un-droppable weapon, will not seek"); + } + + if ( !m_hTargetWeapon ) + { + m_hTargetWeapon = FindNearestPrimaryWeapon( me, false, m_pIgnoredWeapons ); + } + + if ( !m_hTargetWeapon ) + { + return Done("No valid replacement primary found"); } - if ( !FindAndPathToWeapon(me) ) + CNEOBaseCombatWeapon *pNeoPrimary = ToNEOWeapon( pPrimary ); + if ( pNeoPrimary && ( pNeoPrimary->GetNeoWepBits() & NEO_WEP_GHOST ) ) { - return Done("No replacement primary found"); + // Try to drop once, but sometimes the environment causes a bounce back + me->DropPrimaryWeapon(); } // found a replacement, go towards it next frame @@ -94,41 +277,100 @@ ActionResult< CNEOBot > CNEOBotSeekWeapon::OnStart( CNEOBot *me, Action< CNEOBot //--------------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotSeekWeapon::Update( CNEOBot *me, float interval ) { - CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot(0); - if ( pPrimary ) + if ( !m_hTargetWeapon ) { - if ( pPrimary->HasAnyAmmo() ) - { - // so we don't exit this behavior still equipped with secondary - me->Weapon_Switch(pPrimary); - } - return Done("Acquired a primary weapon"); + return Done("No weapon to seek"); } - - if (!m_hTargetWeapon) + + if ( m_giveUpTimer.IsElapsed() ) { - return Done("No weapon to seek"); + return Done("Gave up seeking weapon"); + } + + if ( !m_repathTimer.HasStarted() || m_repathTimer.IsElapsed() ) + { + if ( !CNEOBotPathCompute( me, m_path, m_hTargetWeapon->GetAbsOrigin(), FASTEST_ROUTE ) ) + { + return Done("Unable to find a path to the nearest primary weapon"); + } + m_repathTimer.Start( RandomFloat( 0.3f, 0.6f ) ); } if (!m_path.IsValid()) { return Done("Path to weapon is invalid"); } + + // Verify the path actually reaches the weapon + const float flMaxEndpointDistSqr = 150.0f * 150.0f; + if ( m_path.GetEndPosition().DistToSqr( m_hTargetWeapon->GetAbsOrigin() ) > flMaxEndpointDistSqr ) + { + if ( m_pIgnoredWeapons && !m_pIgnoredWeapons->Has( m_hTargetWeapon ) ) + { + m_pIgnoredWeapons->Add( m_hTargetWeapon ); + } + return Done("Weapon is unreachable (path doesn't get close enough)"); + } m_path.Update(me); + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot(0); + if ( pPrimary ) + { + int myPrefRank = BOT_WEP_PREF_RANK_UNPREFERRED; + CNEOBaseCombatWeapon *pMyNeoWep = ToNEOWeapon( pPrimary ); + if ( pMyNeoWep ) + { + myPrefRank = GetBotWeaponPreferenceRank( me, pMyNeoWep->GetNeoWepBits() ); + } + + CNEOBaseCombatWeapon *pTargetNeoWep = ToNEOWeapon( m_hTargetWeapon.Get() ); + if ( !pTargetNeoWep ) + { + return Done( "Target weapon is invalid or not a NEO weapon" ); + } + + const bool bIsUpgrade = IsWeaponPreferenceUpgrade( me, pTargetNeoWep, myPrefRank, pPrimary->GetPrimaryAmmoCount() > 0 ); + + if ( bIsUpgrade ) + { + // We are seeking a better weapon, check if we are close enough to drop ours + const float flDropDistSqr = 100.0f * 100.0f; + if ( me->GetAbsOrigin().DistToSqr( m_hTargetWeapon->GetAbsOrigin() ) <= flDropDistSqr || + ( pMyNeoWep && ( pMyNeoWep->GetNeoWepBits() & NEO_WEP_GHOST ) ) ) + { + me->DropPrimaryWeapon(); + } + } + else if ( pPrimary->HasAnyAmmo() ) // Ensure it's not empty + { + // We already have an equal or better weapon, or we somehow picked up another weapon + // so we don't exit this behavior still equipped with secondary + me->Weapon_Switch(pPrimary); + return Done("Acquired a primary weapon"); + } + } + return Continue(); } //--------------------------------------------------------------------------------------------- -ActionResult< CNEOBot > CNEOBotSeekWeapon::OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) +ActionResult< CNEOBot > CNEOBotSeekWeapon::OnResume( CNEOBot *me, Action< CNEOBot > *priorAction ) { - return OnStart(me, interruptingAction); + m_hTargetWeapon = nullptr; + m_repathTimer.Invalidate(); + if ( !FindAndPathToWeapon( me ) ) + { + return Done( "No weapon found on resume" ); + } + return Continue(); } //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnStuck( CNEOBot *me ) { + m_hTargetWeapon = nullptr; + m_repathTimer.Invalidate(); FindAndPathToWeapon(me); return TryContinue(); } @@ -136,13 +378,14 @@ EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnStuck( CNEOBot *me ) //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnMoveToSuccess( CNEOBot *me, const Path *path ) { - // a weapon should have been picked up return TryDone(); } //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + m_hTargetWeapon = nullptr; + m_repathTimer.Invalidate(); FindAndPathToWeapon(me); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.h b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.h index c55cacaf1..a21add5bb 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.h +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.h @@ -1,20 +1,37 @@ -#ifndef NEO_BOT_SEEK_WEAPON_H -#define NEO_BOT_SEEK_WEAPON_H +#pragma once #include "bot/neo_bot.h" +class CNEOIgnoredWeaponsCache +{ +public: + void Reset( void ) { m_weapons.Purge(); } + bool Has( CBaseEntity *pWep ) const { return m_weapons.HasElement( pWep ); } + void Add( CBaseEntity *pWep ) { m_weapons.AddToTail( pWep ); } + +private: + CUtlVector> m_weapons; +}; + +// Returns the bot's preference rank for a weapon (-1 = not preferred, -2 = empty) +int GetBotWeaponPreferenceRank( CNEOBot *me, NEO_WEP_BITS_UNDERLYING_TYPE wepBit ); + +bool IsWeaponPreferenceUpgrade( CNEOBot *me, CNEOBaseCombatWeapon *pTargetWep, int myPrefRank, bool bHasReserveAmmo ); + +bool IsUndroppablePrimary( CBaseCombatWeapon *pPrimary ); + // General utility that can be used to see if it makes sense to transition into this behavior -CBaseEntity *FindNearestPrimaryWeapon( const Vector &searchOrigin, bool bShortCircuit = false ); +CBaseEntity *FindNearestPrimaryWeapon( CNEOBot *me, bool bAllowDropGhost = false, CNEOIgnoredWeaponsCache *pIgnoredWeapons = nullptr ); //-------------------------------------------------------------------------------------------------------- class CNEOBotSeekWeapon : public Action< CNEOBot > { public: - CNEOBotSeekWeapon( void ); + CNEOBotSeekWeapon( CBaseEntity *pTargetWeapon = nullptr, CNEOIgnoredWeaponsCache *pIgnoredWeapons = nullptr ); 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 ActionResult< CNEOBot > OnResume( CNEOBot *me, Action< CNEOBot > *priorAction ) override; virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me ) override; virtual EventDesiredResult< CNEOBot > OnMoveToSuccess( CNEOBot *me, const Path *path ) override; @@ -25,8 +42,9 @@ class CNEOBotSeekWeapon : public Action< CNEOBot > private: PathFollower m_path; CHandle m_hTargetWeapon; + CNEOIgnoredWeaponsCache *m_pIgnoredWeapons; + CountdownTimer m_repathTimer; + CountdownTimer m_giveUpTimer; CBaseEntity *FindAndPathToWeapon( CNEOBot *me ); }; - -#endif // NEO_BOT_SEEK_WEAPON_H diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp index a1229e7f1..759323888 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp @@ -28,6 +28,9 @@ ConVar neo_bot_force_jump( "neo_bot_force_jump", "0", FCVAR_CHEAT, "Force bots to continuously jump" ); +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 ); + //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -58,6 +61,14 @@ ActionResult< CNEOBot > CNEODespawn::Update( CNEOBot* me, float interval ) //////////////////////////////////////////////////////////////////////////////////////////////////// +CNEOBotTacticalMonitor::CNEOBotTacticalMonitor() +{ + m_pIgnoredWeapons = std::make_unique(); +} + +CNEOBotTacticalMonitor::~CNEOBotTacticalMonitor() = default; + + Action< CNEOBot > *CNEOBotTacticalMonitor::InitialContainedAction( CNEOBot *me ) { return new CNEOBotScenarioMonitor; @@ -67,6 +78,7 @@ Action< CNEOBot > *CNEOBotTacticalMonitor::InitialContainedAction( CNEOBot *me ) //----------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotTacticalMonitor::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) { + m_pIgnoredWeapons->Reset(); return Continue(); } @@ -450,23 +462,27 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter //----------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotTacticalMonitor::ScavengeForPrimaryWeapon( CNEOBot *me ) { - if ( me->Weapon_GetSlot( 0 ) ) + if ( !m_maintainTimer.IsElapsed() ) { return Continue(); } - if ( !m_maintainTimer.IsElapsed() ) + // Avoid swapping weapon in the middle of a fight + CNEO_Player* pBotPlayer = ToNEOPlayer( me->GetEntity() ); + if ( pBotPlayer && pBotPlayer->GetTimeSinceWeaponFired() < 3.0f ) { return Continue(); } - m_maintainTimer.Start( 1.0f ); + + CBaseCombatWeapon *pPrimary = me->Weapon_GetSlot( 0 ); + const bool bHasPrimaryAmmo = ( pPrimary != nullptr && pPrimary->HasAnyAmmo() ); + const float flDelay = bHasPrimaryAmmo ? neo_bot_scavenge_upgrade_delay.GetFloat() : 1.0f; + m_maintainTimer.Start( flDelay ); - // Look for any one valid primary weapon, then dispatch into behavior for more optimal search - // true parameter: short-circuit the search if any valid primary weapon is found - // We just want to sanity check if there's a valid weapon before suspending into the dedicated behavior - if ( FindNearestPrimaryWeapon( me->GetAbsOrigin(), true ) ) + CBaseEntity *pNearestWeapon = FindNearestPrimaryWeapon( me, false, m_pIgnoredWeapons.get() ); + if ( pNearestWeapon ) { - return SuspendFor( new CNEOBotSeekWeapon, "Scavenging for a primary weapon" ); + return SuspendFor( new CNEOBotSeekWeapon( pNearestWeapon, m_pIgnoredWeapons.get() ), "Scavenging for a new primary weapon" ); } return Continue(); diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h index f0e355e04..7b1f741c5 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h @@ -1,10 +1,16 @@ #pragma once +#include + class CObjectTeleporter; +class CNEOIgnoredWeaponsCache; class CNEOBotTacticalMonitor : public Action< CNEOBot > { public: + CNEOBotTacticalMonitor(); + virtual ~CNEOBotTacticalMonitor(); + virtual Action< CNEOBot >* InitialContainedAction(CNEOBot* me); virtual ActionResult< CNEOBot > OnStart(CNEOBot* me, Action< CNEOBot >* priorAction); @@ -26,6 +32,7 @@ class CNEOBotTacticalMonitor : public Action< CNEOBot > CountdownTimer m_acknowledgeAttentionTimer; CountdownTimer m_acknowledgeRetryTimer; CountdownTimer m_attentionTimer; + std::unique_ptr m_pIgnoredWeapons; #if 0 CountdownTimer m_stickyBombCheckTimer; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 791f62c2b..35e277f42 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -1448,6 +1448,24 @@ bool CNEOBot::EquipRequiredWeapon(void) } +//----------------------------------------------------------------------------------------------------- +void CNEOBot::DropPrimaryWeapon(void) +{ + CBaseCombatWeapon *pPrimary = Weapon_GetSlot( 0 ); + if ( pPrimary ) + { + if ( GetActiveWeapon() != pPrimary ) + { + Weapon_Switch( pPrimary ); + } + else + { + PressDropButton(); + } + } +} + + //----------------------------------------------------------------------------------------------------- // Equip the best weapon we have to attack the given threat void CNEOBot::EquipBestWeaponForThreat(const CKnownEntity* threat, const bool bNotPrimary) diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index 01f83b0e3..f9797dd71 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -158,6 +158,8 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener void EquipBestWeaponForThreat(const CKnownEntity* threat, const bool bNotPrimary = false); // equip the best weapon we have to attack the given threat void ReloadIfLowClip(bool bForceReload = false); + void DropPrimaryWeapon(void); + void PushRequiredWeapon(CNEOBaseCombatWeapon* weapon); // force us to equip and use this weapon until popped off the required stack void PopRequiredWeapon(void); // pop top required weapon off of stack and discard diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index d00e539f9..52cad2e03 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -254,4 +254,18 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon }; +inline CNEOBaseCombatWeapon *ToNEOWeapon( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + { + return nullptr; + } + return dynamic_cast< CNEOBaseCombatWeapon* >( pEntity->MyCombatWeaponPointer() ); +} + +inline CNEOBaseCombatWeapon *ToNEOWeapon( CBaseCombatWeapon *pWeapon ) +{ + return dynamic_cast< CNEOBaseCombatWeapon* >( pWeapon ); +} + #endif // WEAPON_NEO_BASECOMBATWEAPON_SHARED_H