Skip to content
Closed
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
76 changes: 76 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,82 @@ QueryResultType CNEOBotSeekAndDestroy::ShouldHurry( const INextBot *me ) const
return ANSWER_UNDEFINED;
}


//---------------------------------------------------------------------------------------------
// Should I slow down to let my teammates catch up?
QueryResultType CNEOBotSeekAndDestroy::ShouldWalk( const CNEOBot *me, const QueryResultType qShouldAimQuery ) const
{
// ShouldAim query shorts cuts ShouldWalk to ANSWER_YES as aiming and running blocks each other
if (qShouldAimQuery == ANSWER_YES)
{
return ANSWER_YES;
}

const CKnownEntity* threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if (threat != NULL)
{
// I need to decide how to deal with a threat
return ANSWER_UNDEFINED;
}

if (NEORules()->GetGameType() == NEO_GAME_TYPE_CTG)
{
if (NEORules()->GhostExists())
{
// For comparing my distance to the ghost compared to my teammates
const Vector &vGhostPos = NEORules()->GetGhostPos();
float flMySqDistToGhost = (me->GetAbsOrigin() - vGhostPos).LengthSqr();
int iAliveTeammates = 0;

for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );

if (!pPlayer || !pPlayer->IsAlive() || pPlayer->GetTeamNumber() != me->GetTeamNumber() || pPlayer == me->GetEntity())
{
continue;
}

iAliveTeammates++;

const CNEO_Player *pNeoPlayer = ToNEOPlayer(pPlayer);
if (pNeoPlayer && pNeoPlayer->IsCarryingGhost())
{
// Maybe I can catch up to my team's ghost carrier
return ANSWER_UNDEFINED;
}

// Check if this teammate is closer to the ghost
float flTeammateSqDistToGhost = (pPlayer->GetAbsOrigin() - vGhostPos).LengthSqr();
if (flTeammateSqDistToGhost < flMySqDistToGhost)
{
// Maybe I can catch up to my teammate up ahead
return ANSWER_UNDEFINED;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I'm reading this correctly then only one bot per team can ever be walking at a time because they are close to the objective. Perhaps an early return here only if any team mate is closer to the objective than "me" by some tweakable value, that way some number of bots closest to the ghost can all walk at once.

If you wanted to add something like that you would probably also need to count how many alive bots are within that tolerance where a bot is considered to be the same distance from the ghost as "me", so in the case where all the bots on a team are close together they don't all walk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many bots should be slowing down is also a good design question. In experimenting with CTG tactics in another branch, I'm wondering if slowing down any of the bots is a generally optimal strategy. For example, if the ghost was spawned closer to one team, it could make sense to just try to rush, but on the other team the same approach would lead to getting picked off one by one. I think it might be a good idea to let this PR wait until we decide on a default bot strategy for CTG, so that we have some behavior to compare with before and after this tweak.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one potential heuristic we could try when the other PRs slot into place is whether we should have teams that are closer to the objective should rush, but the other team that is calculated to be farther should engage in this logic of slowing down, so they can attack together in force.

Also, while this patch currently only has 1 bot slow down for the second up front bot on the team, perhaps we could calculate a ratio of how lopsided team counts are, so teams that are evenly stacked will have fewer bots slowing down than teams that are greatly outnumbered by the other team.

}
}

if (iAliveTeammates == 0)
{
// I'm the sole survivor, don't need to wait for anyone
return ANSWER_UNDEFINED;
}

// I should slow down to let my teammates catch up
return ANSWER_YES;
}
}

return ANSWER_UNDEFINED;
}


//---------------------------------------------------------------------------------------------
QueryResultType CNEOBotSeekAndDestroy::ShouldAim(const CNEOBot* me, const bool bWepHasClip) const
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed to allow CNEOBotSeekAndDestroy::ShouldWalk to instantiate the abstract class.

I decided to revert my original experiment with aiming to slow down since walking already was enough to let teammates catch up with the recent related bot updates.

{
return ANSWER_UNDEFINED;
}


class CNextSpawnFilter : public IEntityFindFilter
{
public:
Expand Down
6 changes: 4 additions & 2 deletions src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#pragma once

#include "Path/NextBotChasePath.h"

#include "bot/neo_bot_contextual_query_interface.h"

//
// Roam around the map attacking enemies
//
class CNEOBotSeekAndDestroy : public Action< CNEOBot >
class CNEOBotSeekAndDestroy : public Action< CNEOBot >, public CNEOBotContextualQueryInterface
{
public:
CNEOBotSeekAndDestroy( float duration = -1.0f );
Expand All @@ -22,6 +22,8 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot >

virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldWalk( const CNEOBot *me, const QueryResultType qShouldAimQuery ) const override; // are we to walk?
virtual QueryResultType ShouldAim(const CNEOBot *me, const bool bWepHasClip) const override;

virtual EventDesiredResult< CNEOBot > OnTerritoryCaptured( CNEOBot *me, int territoryID );
virtual EventDesiredResult< CNEOBot > OnTerritoryLost( CNEOBot *me, int territoryID );
Expand Down
42 changes: 26 additions & 16 deletions src/game/server/neo/bot/neo_bot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2657,24 +2657,29 @@ QueryResultType CNEOBotBehavior::ShouldWalk(const CNEOBot *me, const QueryResult
{
QueryResultType result = ANSWER_UNDEFINED;

auto *neoAction = static_cast<CNEOBotMainAction *>(m_action);
if ( neoAction )
Action<CNEOBot> *action = m_action;
if ( action )
{
// find innermost child action
CNEOBotMainAction *action;
for( action = neoAction; action->m_child; action = static_cast<CNEOBotMainAction *>(action->m_child) )
;
while( action->GetActiveChildAction() )
{
action = action->GetActiveChildAction();
}

// work our way through our containers
while( action && result == ANSWER_UNDEFINED )
{
CNEOBotMainAction *containingAction = static_cast<CNEOBotMainAction *>(action->m_parent);
Action<CNEOBot> *containingAction = action->GetParentAction();

// work our way up the stack
while( action && result == ANSWER_UNDEFINED )
{
result = action->ShouldWalk(me, qShouldAimQuery);
action = static_cast<CNEOBotMainAction *>(action->GetActionBuriedUnderMe());
auto *query = dynamic_cast<const CNEOBotContextualQueryInterface *>(action);
if (query)
{
result = query->ShouldWalk(me, qShouldAimQuery);
}
action = action->GetActionBuriedUnderMe();
}

action = containingAction;
Expand All @@ -2693,24 +2698,29 @@ QueryResultType CNEOBotBehavior::ShouldAim(const CNEOBot *me, const bool bWepHas
{
QueryResultType result = ANSWER_UNDEFINED;

auto *neoAction = static_cast<CNEOBotMainAction *>(m_action);
if ( neoAction )
Action<CNEOBot> *action = m_action;
if ( action )
{
// find innermost child action
CNEOBotMainAction *action;
for( action = neoAction; action->m_child; action = static_cast<CNEOBotMainAction *>(action->m_child) )
;
while( action->GetActiveChildAction() )
{
action = action->GetActiveChildAction();
}

// work our way through our containers
while( action && result == ANSWER_UNDEFINED )
{
CNEOBotMainAction *containingAction = static_cast<CNEOBotMainAction *>(action->m_parent);
Action<CNEOBot> *containingAction = action->GetParentAction();

// work our way up the stack
while( action && result == ANSWER_UNDEFINED )
{
result = action->ShouldAim(me, bWepHasClip);
action = static_cast<CNEOBotMainAction *>(action->GetActionBuriedUnderMe());
auto *query = dynamic_cast<const CNEOBotContextualQueryInterface *>(action);
if (query)
{
result = query->ShouldAim(me, bWepHasClip);
}
action = action->GetActionBuriedUnderMe();
}

action = containingAction;
Expand Down