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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,5 @@ shaders\fxc\sdk_screenspaceeffect_vs20.vcs
* Updated as of the TF2 SDK update: [Commit 0759e2e8e179d5352d81d0d4aaded72c1704b7a9](https://github.com/ValveSoftware/source-sdk-2013/commit/0759e2e8e179d5352d81d0d4aaded72c1704b7a9)
* [Nbc66/source-sdk-2013-ce](https://github.com/Nbc66/source-sdk-2013-ce) - Community Edition for additional fixes prior to the TF2 SDK update
* [tonysergi/source-sdk-2013](https://github.com/tonysergi/source-sdk-2013) - tonysergi's commits that were missing from the original SDK
* [Dragoteryx/drgbase](https://github.com/Dragoteryx/drgbase) - Reference for NextBot ladder climbing behavior
4 changes: 4 additions & 0 deletions src/game/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,10 @@ target_sources_grouped(
neo/bot/behavior/neo_bot_jgr_juggernaut.h
neo/bot/behavior/neo_bot_jgr_seek.cpp
neo/bot/behavior/neo_bot_jgr_seek.h
neo/bot/behavior/neo_bot_ladder_approach.cpp
neo/bot/behavior/neo_bot_ladder_approach.h
neo/bot/behavior/neo_bot_ladder_climb.cpp
neo/bot/behavior/neo_bot_ladder_climb.h
neo/bot/behavior/neo_bot_melee_attack.cpp
neo/bot/behavior/neo_bot_melee_attack.h
neo/bot/behavior/neo_bot_move_to_vantage_point.cpp
Expand Down
4 changes: 2 additions & 2 deletions src/game/server/neo/bot/behavior/neo_bot_command_follow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ bool CNEOBotCommandFollow::FollowCommandChain(CNEOBot* me)
// Anti-collision: follow neighbor in snake chain
for (int idx = 1; idx <= gpGlobals->maxClients; ++idx)
{
CNEO_Player* pOther = static_cast<CNEO_Player*>(UTIL_PlayerByIndex(idx));
CNEO_Player* pOther = ToNEOPlayer(UTIL_PlayerByIndex(idx));
if (!pOther || !pOther->IsBot() || pOther == me
|| (pOther->m_hLeadingPlayer.Get() != me->m_hLeadingPlayer.Get()))
{
Expand Down Expand Up @@ -272,7 +272,7 @@ bool CNEOBotCommandFollow::FanOutAndCover(CNEOBot* me, Vector& movementTarget, b
if (pPlayer->GetTeamNumber() != me->GetTeamNumber())
continue;

CNEO_Player* pOther = static_cast<CNEO_Player*>(pPlayer);
CNEO_Player* pOther = ToNEOPlayer(pPlayer);
if (!pOther)
continue;

Expand Down
151 changes: 151 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "cbase.h"
#include "bot/behavior/neo_bot_ladder_approach.h"
#include "bot/behavior/neo_bot_ladder_climb.h"
#include "bot/behavior/neo_bot_attack.h"
#include "nav_ladder.h"

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

//---------------------------------------------------------------------------------------------
CNEOBotLadderApproach::CNEOBotLadderApproach( const CNavLadder *ladder, bool goingUp )
: m_ladder( ladder ), m_bGoingUp( goingUp )
{
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderApproach::OnStart( CNEOBot *me, Action<CNEOBot> *priorAction )
{
if ( !m_ladder )
{
return Done( "No ladder specified" );
}

// Timeout for approach phase
m_timeoutTimer.Start( 3.0f );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder approach (%s), length %.1f\n",
me->GetDebugIdentifier(),
m_bGoingUp ? "up" : "down",
m_ladder->m_length );
}

return Continue();
}

//---------------------------------------------------------------------------------------------
// Implementation based on ladder climbing implementation in https://github.com/Dragoteryx/drgbase/
ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval )
{
if ( m_timeoutTimer.IsElapsed() )
{
return Done( "Ladder approach timeout" );
}

const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Threat detected during ladder approach - engaging\n", me->GetDebugIdentifier() );
}
// ChangeTo: We may move away from ladder when fighting, so don't want to get stuck in ladder approach behavior
return ChangeTo( new CNEOBotAttack(), "Engaging enemy before climbing" );
}

ILocomotion *mover = me->GetLocomotionInterface();
IBody *body = me->GetBodyInterface();

Vector myPos = mover->GetFeet();

// Are we climbing up or down the ladder?
Vector targetPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top;

// Calculate 2D vector from bot to ladder mount point
Vector2D to = ( targetPos - myPos ).AsVector2D();
float range = to.NormalizeInPlace();

// Get ladder's outward normal in 2D
Vector2D ladderNormal2D = m_ladder->GetNormal().AsVector2D();
float dot = DotProduct2D( ladderNormal2D, to );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
NDebugOverlay::Line( myPos, targetPos, 255, 255, 0, true, 0.1f );
}

// Are we aligned and close enough to mount the ladder?
if ( range >= ALIGN_RANGE )
{
// Far from ladder - just approach the target position
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.5f, nullptr, "Moving toward ladder" );
mover->Approach( targetPos );
}
else if ( range >= MOUNT_RANGE )
{
// Within alignment range but not mount range - need to align with ladder
if ( dot >= ALIGN_DOT_THRESHOLD )
{
// Not aligned - rotate around the ladder to get aligned
Vector2D myPerp( -to.y, to.x );
Vector2D ladderPerp2D( -ladderNormal2D.y, ladderNormal2D.x );

Vector goal = targetPos;

// Calculate offset to circle around
float alignRange = MOUNT_RANGE + (1.0f + dot) * (ALIGN_RANGE - MOUNT_RANGE);
goal.x -= alignRange * to.x;
goal.y -= alignRange * to.y;

// Choose direction to circle based on perpendicular alignment
if ( DotProduct2D( to, ladderPerp2D ) < 0.0f )
{
goal.x += 10.0f * myPerp.x;
goal.y += 10.0f * myPerp.y;
}
else
{
goal.x -= 10.0f * myPerp.x;
goal.y -= 10.0f * myPerp.y;
}

body->AimHeadTowards( goal, IBody::CRITICAL, 0.3f, nullptr, "Aligning with ladder" );
mover->Approach( goal );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
NDebugOverlay::Cross3D( goal, 5.0f, 255, 0, 255, true, 0.1f );
}
}
else
{
// Aligned - approach the ladder base directly
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" );
mover->Approach( targetPos );
}
}
else
{
// Within mount range - check if aligned to start climbing
if ( dot < ALIGN_DOT_THRESHOLD )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder climb\n", me->GetDebugIdentifier() );
}

// ChangeTo: if something goes wrong during climb, reevaluate situation
return ChangeTo( new CNEOBotLadderClimb( m_ladder, m_bGoingUp ), "Mounting ladder" );
}
else
{
// Close but not aligned - continue approaching to align
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" );
mover->Approach( targetPos );
}
}

return Continue();
}
34 changes: 34 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "NextBotBehavior.h"
#include "bot/neo_bot.h"

class CNavLadder;

//----------------------------------------------------------------------------------------------------------------
/**
* Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/
* Behavior that handles approaching a ladder and aligning with it.
* Uses ChangeTo for climb transition so knocked-off bots reevaluate situation.
* Recommended to use SuspendFor to transition to the behavior or else scenario behavior may be stopped.
*/
class CNEOBotLadderApproach : public Action<CNEOBot>
{
public:
CNEOBotLadderApproach( const CNavLadder *ladder, bool goingUp );
virtual ~CNEOBotLadderApproach() = default;

virtual const char *GetName() const override { return "LadderApproach"; }

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

private:
const CNavLadder *m_ladder;
bool m_bGoingUp;
CountdownTimer m_timeoutTimer;

static constexpr float MOUNT_RANGE = 25.0f; // Distance to start climbing
static constexpr float ALIGN_RANGE = 50.0f; // Distance to start alignment behavior
static constexpr float ALIGN_DOT_THRESHOLD = -0.9f; // cos(~25 degrees) alignment tolerance
};
126 changes: 126 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "cbase.h"
#include "bot/behavior/neo_bot_ladder_climb.h"
#include "nav_ladder.h"

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

//---------------------------------------------------------------------------------------------
CNEOBotLadderClimb::CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp )
: m_ladder( ladder ), m_bGoingUp( goingUp ), m_bHasBeenOnLadder( false )
{
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderClimb::OnStart( CNEOBot *me, Action<CNEOBot> *priorAction )
{
if ( !m_ladder )
{
return Done( "No ladder specified" );
}

// Ignore enemies while climbing
me->StopLookingAroundForEnemies();

// Timeout based on ladder length
float estimatedClimbTime = m_ladder->m_length / 100.0f + 2.0f;
m_timeoutTimer.Start( estimatedClimbTime );

m_bHasBeenOnLadder = false;

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder climb (%s), length %.1f\n",
me->GetDebugIdentifier(),
m_bGoingUp ? "up" : "down",
m_ladder->m_length );
}

return Continue();
}

//---------------------------------------------------------------------------------------------
// Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/
ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float interval )
{
if ( m_timeoutTimer.IsElapsed() )
{
return Done( "Ladder climb timeout" );
}

ILocomotion *mover = me->GetLocomotionInterface();
IBody *body = me->GetBodyInterface();

// Check if we're on the ladder (MOVETYPE_LADDER or locomotion says so)
bool onLadder = ( me->GetMoveType() == MOVETYPE_LADDER ) ||
mover->IsUsingLadder() ||
mover->IsAscendingOrDescendingLadder();

if ( onLadder )
{
m_bHasBeenOnLadder = true;
}
else if ( m_bHasBeenOnLadder )
{
// We were on the ladder but got knocked off - return to reevaluate situation
// Since ladder approach should use ChangeTo climbing behavior, this Done will return to the state BEFORE ladder approach
return Done( "Knocked off ladder - reevaluating situation" );
}

// Get current position and target
Vector myPos = mover->GetFeet();
float currentZ = me->GetAbsOrigin().z;
float targetZ = m_bGoingUp ? m_ladder->m_top.z : m_ladder->m_bottom.z;

if ( m_bGoingUp )
{
if ( currentZ >= targetZ - mover->GetStepHeight() )
{
return Done( "Reached top of ladder" );
}

// Climb up: look up and push into ladder
Vector goal = myPos + 100.0f * ( -m_ladder->GetNormal() + Vector( 0, 0, 2 ) );
body->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, nullptr, "Climbing ladder" );
mover->Approach( goal, 9999999.9f );
}
else
{
if ( currentZ <= targetZ + mover->GetStepHeight() )
{
return Done( "Reached bottom of ladder" );
}

// Climb down: Stare at bottom of ladder while moving forward to it
Vector goal = m_ladder->m_bottom;
body->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, nullptr, "Descending ladder fast" );
mover->Approach( goal, 9999999.9f );
}

return Continue();
}

//---------------------------------------------------------------------------------------------
void CNEOBotLadderClimb::OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction )
{
me->StartLookingAroundForEnemies();

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Finished ladder climb\n", me->GetDebugIdentifier() );
}
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderClimb::OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
me->StartLookingAroundForEnemies();
return Continue();
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderClimb::OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
me->StopLookingAroundForEnemies();
return Continue();
}
34 changes: 34 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "NextBotBehavior.h"
#include "bot/neo_bot.h"

class CNavLadder;

//----------------------------------------------------------------------------------------------------------------
/**
* Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/
* Behavior that handles the actual ladder climbing.
* DOES disable enemy awareness while climbing.
* Checks if still on ladder - returns Done if knocked off to reevaluate situation.
*/
class CNEOBotLadderClimb : public Action<CNEOBot>
{
public:
CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp );
virtual ~CNEOBotLadderClimb() = default;

virtual const char *GetName() const override { return "LadderClimb"; }

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

private:
const CNavLadder *m_ladder;
bool m_bGoingUp;
CountdownTimer m_timeoutTimer;
bool m_bHasBeenOnLadder; // Track if we ever got on the ladder
};
Loading