-
Notifications
You must be signed in to change notification settings - Fork 24
Bots avoid grenade-related hazard areas #2020
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ | |
| #include "neo_player.h" | ||
| #include "neo_smokelineofsightblocker.h" | ||
| #include "bot/neo_bot.h" | ||
| #include "bot/neo_bot_path_reservation.h" | ||
| #include "nav_mesh.h" | ||
| #endif | ||
|
|
||
| #include "tier0/vprof.h" | ||
|
|
@@ -794,6 +796,33 @@ bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *vi | |
| UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), firstPosition, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result ); | ||
| if ( result.DidHit() ) | ||
| { | ||
| // Check if first center traceline hit any blocking smoke | ||
| if (neo_bot_path_reservation_enable.GetBool() | ||
| && result.m_pEnt | ||
| && FStrEq(result.m_pEnt->GetClassname(), SMOKELINEOFSIGHTBLOCKER_ENTITYNAME)) | ||
| { | ||
| auto pSmoke = static_cast<CNEOSmokeLineOfSightBlocker*>(result.m_pEnt); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're assuming here that, if GetClassname returns exactly SMOKELINEOFSIGHTBLOCKER_ENTITYNAME, this static_cast is guaranteed to return a valid pointer to a CNEOSmokeLineOfSightBlocker. I think that's a safe assumption to make, Looking at IClassMap it seems to me that it's not possible for two entities to have the same class name (not sure about the case where NO_ENTITY_PREDICTION is defined), I'm just wondering how expensive a dynamic_cast is compared to a string comparison, there's 28 characters in SMOKELINEOFSIGHTBLOCKER_ENTITYNAME after all. Again I have no idea which is more performant and I'm happy with this solution, another thing to ponder I guess, maybe someone with more knowledge can chime in. |
||
| // Assume that threat could be hiding inside navarea where smoke blocked sight | ||
| if (neoBot) | ||
| { | ||
| int team = neoBot->GetTeamNumber(); | ||
|
|
||
| CNavArea *smokeArea = TheNavMesh->GetNearestNavArea(result.endpos); | ||
| if (smokeArea) | ||
| { | ||
| // Register smoke sightlines for team to avoid as hazardous | ||
| CNEOBotPathReservations()->AddSmokeHazard(smokeArea->GetID(), pSmoke->GetNextThink(), team); | ||
| } | ||
|
|
||
| CNavArea *myArea = neoBot->GetLastKnownArea(); | ||
| if (myArea) | ||
| { | ||
| // Also register the area I saw the smoke from as hazardous | ||
| CNEOBotPathReservations()->AddSmokeHazard(myArea->GetID(), pSmoke->GetNextThink(), team, false); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), secondPosition, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result ); | ||
|
|
||
| if ( result.DidHit() ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor | |
| m_threatArea = threat->GetLastKnownArea(); | ||
| m_goalArea = goalArea ? goalArea : m_threatArea; // prioritize movement towards input goal area or threat | ||
| m_myDistToGoalSq = m_goalArea ? ( m_goalArea->GetCenter() - m_me->GetAbsOrigin() ).LengthSqr() : 0; | ||
| m_onStuckPenalty = neo_bot_path_reservation_onstuck_penalty.GetFloat(); | ||
| } | ||
|
|
||
| virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar ) | ||
|
|
@@ -79,10 +80,18 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor | |
| return true; // skip our starting area | ||
| } | ||
|
|
||
| if ( neo_bot_path_reservation_enable.GetBool() && | ||
| ( CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()) > 0 ) ) | ||
| // Skip areas that are hazardous or where bots get stuck | ||
| if ( neo_bot_path_reservation_enable.GetBool() ) | ||
| { | ||
| return true; // skip areas that have had navigation hiccups | ||
| int navAreaId = area->GetID(); | ||
| if (CNEOBotPathReservations()->IsAreaHazardous(navAreaId, m_me)) | ||
| { | ||
| return true; | ||
| } | ||
| if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot I added avoidance penalties for areas where a bot died, so I made the threshold under which a bot got stuck in a location, allowing to minor penalty for death areas to be used as candidates if under the OnStuck threshold. |
||
| { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| if ( m_attackCoverArea ) | ||
|
|
@@ -199,6 +208,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor | |
| const CNavArea *m_myArea; // reference point of myself | ||
| const CNavArea *m_threatArea; // reference point of the threat | ||
| float m_myDistToGoalSq; // the bot's current distance to the threat | ||
| float m_onStuckPenalty; // cache onstuck penalty | ||
| }; | ||
|
|
||
|
|
||
|
|
@@ -450,11 +460,6 @@ QueryResultType CNEOBotAttack::ShouldRetreat( const INextBot *me ) const | |
| return ANSWER_UNDEFINED; | ||
| } | ||
|
|
||
| if ( m_goalArea && m_attackCoverArea ) | ||
| { | ||
| return ANSWER_NO; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While preventing the bots from retreating when they are rushing for a cover area was okay for proving out #2000, it made the bots a bit suicidal when their chosen advancing cover was too exposed, so I think it's better to retreat when there's any incoming fire. This also encourages more grenade throw attempts which is helpful for viewing the smoke and grenade avoidance behavior. |
||
| } | ||
|
|
||
| return ANSWER_UNDEFINED; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| #include "weapon_grenade.h" | ||
| #include "weapon_smokegrenade.h" | ||
|
|
||
| ConVar sv_neo_bot_grenade_polite_frag("sv_neo_bot_grenade_polite_frag", "1", FCVAR_NONE, "Force bots to consider teammates when throwing frag grenades.", true, 0, true, 1); | ||
| ConVar sv_neo_bot_grenade_polite_smoke("sv_neo_bot_grenade_polite_smoke", "1", FCVAR_NONE, "Force bots to consider teammates when throwing smoke grenades.", true, 0, true, 1); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be rare to see a smoke deployment with the default bot deployment decisions, so I added a ConVar to induce bots to throw grenades more readily. This helps observe the hazard avoidance behavior more frequently.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing logic below should be mostly the same despite the indentation changes that caused many diff line changes. |
||
| ConVar sv_neo_bot_grenade_use_frag("sv_neo_bot_grenade_use_frag", "1", FCVAR_NONE, "Allow bots to use frag grenades", true, 0, true, 1); | ||
| ConVar sv_neo_bot_grenade_use_smoke("sv_neo_bot_grenade_use_smoke", "1", FCVAR_NONE, "Allow bots to use smoke grenades", true, 0, true, 1); | ||
| ConVar sv_neo_bot_grenade_throw_cooldown("sv_neo_bot_grenade_throw_cooldown", "10", FCVAR_NONE, "Cooldown in seconds between grenade throws for bots"); | ||
|
|
@@ -78,29 +80,32 @@ Action< CNEOBot > *CNEOBotGrenadeDispatch::ChooseGrenadeThrowBehavior( const CNE | |
| // Should I toss a smoke grenade? | ||
| if ( pSmokeGrenade && (me->GetClass() == NEO_CLASS_SUPPORT) ) | ||
| { | ||
| for ( int i = 1; i <= gpGlobals->maxClients; i++ ) | ||
| if ( sv_neo_bot_grenade_polite_smoke.GetBool() ) | ||
| { | ||
| CNEO_Player *pPlayer = ToNEOPlayer( UTIL_PlayerByIndex( i ) ); | ||
| if ( !pPlayer || !pPlayer->IsAlive() || pPlayer == me ) | ||
| for ( int i = 1; i <= gpGlobals->maxClients; i++ ) | ||
| { | ||
| continue; | ||
| } | ||
| CNEO_Player *pPlayer = ToNEOPlayer( UTIL_PlayerByIndex( i ) ); | ||
| if ( !pPlayer || !pPlayer->IsAlive() || pPlayer == me ) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| if ( pPlayer->InSameTeam( me ) ) | ||
| { | ||
| if ( !pPlayer->IsBot() && pPlayer->GetClass() != NEO_CLASS_SUPPORT ) | ||
| if ( pPlayer->InSameTeam( me ) ) | ||
| { | ||
| // Avoid blocking the vision of a friendly human | ||
| // (Bots benefit from concealment without the disorientation) | ||
| return nullptr; | ||
| if ( !pPlayer->IsBot() && pPlayer->GetClass() != NEO_CLASS_SUPPORT ) | ||
| { | ||
| // Avoid blocking the vision of a friendly human | ||
| // (Bots benefit from concealment without the disorientation) | ||
| return nullptr; | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| if ( pPlayer->GetClass() == NEO_CLASS_SUPPORT ) | ||
| else | ||
| { | ||
| // Avoid giving an enemy with thermal vision a free smoke screen | ||
| return nullptr; | ||
| if ( pPlayer->GetClass() == NEO_CLASS_SUPPORT ) | ||
| { | ||
| // Avoid giving an enemy with thermal vision a free smoke screen | ||
| return nullptr; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -111,9 +116,12 @@ Action< CNEOBot > *CNEOBotGrenadeDispatch::ChooseGrenadeThrowBehavior( const CNE | |
| // Should I toss a frag grenade? | ||
| if ( pFragGrenade ) | ||
| { | ||
| if ( !CNEOBotGrenadeThrowFrag::IsFragSafe( me, threat->GetLastKnownPosition() ) ) | ||
| if ( sv_neo_bot_grenade_polite_frag.GetBool() ) | ||
| { | ||
| return nullptr; | ||
| if ( !CNEOBotGrenadeThrowFrag::IsFragSafe( me, threat->GetLastKnownPosition() ) ) | ||
| { | ||
| return nullptr; | ||
| } | ||
| } | ||
|
|
||
| return new CNEOBotGrenadeThrowFrag( pFragGrenade, threat ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| #include "cbase.h" | ||
| #include "bot/neo_bot.h" | ||
| #include "bot/neo_bot_path_compute.h" | ||
| #include "bot/neo_bot_path_reservation.h" | ||
| #include "bot/behavior/neo_bot_grenade_throw_frag.h" | ||
| #include "neo_gamerules.h" | ||
| #include "neo_player.h" | ||
| #include "weapon_neobasecombatweapon.h" | ||
|
|
||
| #include "nav_mesh.h" | ||
| #include "nav_pathfind.h" | ||
|
|
||
| extern ConVar sv_neo_grenade_blast_radius; | ||
|
|
@@ -106,6 +108,15 @@ CNEOBotGrenadeThrow::ThrowTargetResult CNEOBotGrenadeThrowFrag::UpdateGrenadeTar | |
| return THROW_TARGET_CANCEL; // risk of friendly fire | ||
| } | ||
|
|
||
| // Register explosive hazard at the target position | ||
| // so the throwing bot's team can avoid the trajectory and landing areas | ||
| CNavArea *area = TheNavMesh->GetNearestNavArea(m_vecTarget); | ||
| if (area) | ||
| { | ||
| int team = me->GetTeamNumber(); | ||
| CNEOBotPathReservations()->AddFragHazard(area->GetID(), gpGlobals->curtime + sv_neo_grenade_fuse_timer.GetFloat(), team); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expanding the hazard area around grenades by adjacent Potentially Visible Set is intended as a shorthand for avoid the area along the line through the target, as well as avoid the radiating circle around the idea detonation zone, with some leeway for the trajectory to be off as it usually is. |
||
| } | ||
|
|
||
| return THROW_TARGET_READY; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering what the reasoning behind the existence of FStrEq is, is it better to inline the pointer comparison and save yourself a function call if the two pointers are the same, and also more readable than V_stricmp == 0? In the case where the two pointers aren't the same you're comparing the two pointers twice.
It seems to me like it would be better to remove the pointer comparison from FStrEq and replace all calls to V_stricmp with FStrEq where we don't care which string has a higher numerical value.
Anyway, not suggesting we change that here, just something to ponder.