Skip to content
53 changes: 48 additions & 5 deletions lua/Action/TBotBaseAction.lua
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,11 @@ function TBotBehaviorMeta:Update( me, interval )

self.m_action = self.m_action:ApplyResult( me, self, self.m_action:InvokeUpdate( me, self, interval ) )

--[[if false and GetConVar( "developer" ):GetBool() then
if self.m_action and GetConVar( "developer" ):GetBool() then

-- TODO: Implement debug code from TF2 source code!
debugoverlay.Text( me:WorldSpaceCenter(), string.format( "%s: %s", self:GetName(), self.m_action:DebugString() ), interval, true )

end]]
end

-- Delete any dead Actions
for k, action in ipairs( self.m_deadActionVector ) do
Expand Down Expand Up @@ -938,7 +938,7 @@ function RegisterTBotActionHook( newHook )
-- since it will just "override" the old hook function with the "new" one.
hook.Add( newHook, "TBotAction" .. newHook, function( ... )

for action, _ in pairs( TBotActionTable ) do
for action in pairs( TBotActionTable ) do

action:ProcessHookEvent( newHook, ... )

Expand Down Expand Up @@ -966,7 +966,8 @@ RegisterTBotActionHook( "PlayerSilentDeath" )
RegisterTBotActionHook( "EntityEmitSound" )
RegisterTBotActionHook( "EntityTakeDamage" )
RegisterTBotActionHook( "PlayerHurt" )
RegisterTBotActionHook( "player_say" ) -- NOTE: I changed this to player_say, since it will accept the modified returns from PlayerSay
RegisterTBotActionHook( "PlayerSay" )
RegisterTBotActionHook( "player_say" ) -- NOTE: I added player_say since it will accept the modified returns from PlayerSay
RegisterTBotActionHook( "PlayerSpawn" )
RegisterTBotActionHook( "OnPlayerJump" )
RegisterTBotActionHook( "OnPlayerHitGround" )
Expand Down Expand Up @@ -1491,4 +1492,46 @@ function TBotBaseActionMeta:NextContainedResponder( current )

return

end

function TBotBaseActionMeta:DebugString()

local str = {}
-- Find root
local root = self
while root.m_parent do

root = root.m_parent

end

return self:BuildDecoratedName( str, root )

end

function TBotBaseActionMeta:BuildDecoratedName( name, action )

-- Add the name of the given function!
table.insert( name, action:GetName() )

-- Add any contained actions
local child = action:GetActiveChildAction()
if child then

table.insert( name, "( " )
self:BuildDecoratedName( name, child )
table.insert( name, " )" )

end

local buried = action:GetActionBuriedUnderMe()
if buried then

table.insert( name, "<<" )
self:BuildDecoratedName( name, buried )

end

return table.concat( name )

end
81 changes: 74 additions & 7 deletions lua/Action/TBotFollowGroupLeader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ function TBotFollowGroupLeader()
tbotfollowgroupleader.m_chasePath = TBotChasePath()
tbotfollowgroupleader.m_repathTimer = util.Timer( math.Rand( 3.0, 5.0 ) )

-- HACKHACK: We create our own IntervalTimer here, I should probably create a pull request and have this added to the base game.
local intervalTimer = {}
intervalTimer.m_timestamp = -1.0
intervalTimer.Reset = function( self ) self.m_timestamp = CurTime() end
intervalTimer.Start = function( self ) self.m_timestamp = CurTime() end
intervalTimer.Invalidate = function( self ) self.m_timestamp = -1.0 end
intervalTimer.HasStarted = function( self ) return self.m_timestamp > 0 end
intervalTimer.GetElapsedTime = function( self ) return Either( self:HasStarted(), CurTime() - self.m_timestamp, 99999.9 ) end
intervalTimer.IsLessThen = function( self, duration ) return CurTime() - self.m_timestamp < duration end
intervalTimer.IsGreaterThen = function( self, duration ) return CurTime() - self.m_timestamp > duration end
intervalTimer.__index = intervalTimer
tbotfollowgroupleader.m_teleportTimer = intervalTimer

setmetatable( tbotfollowgroupleader, TBotFollowGroupLeaderMeta )

return tbotfollowgroupleader
Expand All @@ -46,6 +59,7 @@ end

function TBotFollowGroupLeaderMeta:OnStart( me, priorAction )

self.m_teleportTimer:Invalidate()
return self:Continue()

end
Expand Down Expand Up @@ -86,25 +100,78 @@ function TBotFollowGroupLeaderMeta:Update( me, interval )

end

self.m_chasePath:Update( me, groupLeader )

-- Repath every now and then to keep the path fresh
if self.m_repathTimer:Elapsed() then

self.m_repathTimer:Start( math.Rand( 3.0, 5.0 ) )
self.m_chasePath:Invalidate()

-- Don't recreate the path if Update just recomputed it.
if self.m_chasePath:GetAge() > 0.0 then
end

self.m_chasePath:Update( me, groupLeader )

-- Check if the bot is stuck or unable to path to the player
local mover = me:GetTBotLocomotion()
if !self.m_chasePath:IsValid() or mover:IsStuck() then

if !self.m_teleportTimer:HasStarted() then

self.m_chasePath:Invalidate()
self.m_teleportTimer:Start()

end

-- After 5 seconds the bot should attempt to teleport now!
if self.m_teleportTimer:IsGreaterThen( 5.0 ) then

local playerFOV = math.cos( 0.5 * owner:GetFOV() * math.pi / 180 )
local navareas = navmesh.Find( owner:GetPos(), 2000, mover:GetMaxJumpHeight(), mover:GetMaxJumpHeight() )
local lastKnownArea = owner:GetLastKnownArea()
for _, area in ipairs( navareas ) do

-- We don't want to teleport infront of the player as that would ruin the immersion a bit
local teleportPos = area:GetCenter()
if !area:IsPotentiallyVisible( lastKnownArea ) and !self:CanPlayerPotentiallySeeUsTeleport( owner, teleportPos ) then

if !me:IsSpotOccupied( teleportPos ) then

me:SetPos( teleportPos )
mover:ClearStuckStatus()
self.m_chasePath:Invalidate() -- Just in case the bot was stuck!
break

end

end

end

self.m_teleportTimer:Invalidate()

end

elseif self.m_teleportTimer:HasStarted() then

self.m_teleportTimer:Invalidate()

end

return self:Continue()

end

function TBotFollowGroupLeaderMeta:CanPlayerPotentiallySeeUsTeleport( player, pos, cosTolerance )

cosTolerance = cosTolerance or math.cos( 0.5 * player:GetFOV() * math.pi / 180 )
local to = pos - player:GetPos()
local diff = player:GetAimVector():Dot( to )
if diff < 0 then return false end

local length = to:LengthSqr()

return diff^2 > length * cosTolerance^2

end

function TBotFollowGroupLeaderMeta:OnEnd( me, nextAction )

return
Expand Down Expand Up @@ -201,4 +268,4 @@ function TBotFollowGroupLeaderMeta:SelectMoreDangerousThreat( me, threat1, threa

return

end
end
77 changes: 72 additions & 5 deletions lua/Action/TBotFollowOwner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ function TBotFollowOwner()
tbotfollowowner.m_chasePath = TBotChasePath()
tbotfollowowner.m_repathTimer = util.Timer( math.Rand( 3.0, 5.0 ) )

-- HACKHACK: We create our own IntervalTimer here, I should probably create a pull request and have this added to the base game.
local intervalTimer = {}
intervalTimer.m_timestamp = -1.0
intervalTimer.Reset = function( self ) self.m_timestamp = CurTime() end
intervalTimer.Start = function( self ) self.m_timestamp = CurTime() end
intervalTimer.Invalidate = function( self ) self.m_timestamp = -1.0 end
intervalTimer.HasStarted = function( self ) return self.m_timestamp > 0 end
intervalTimer.GetElapsedTime = function( self ) return Either( self:HasStarted(), CurTime() - self.m_timestamp, 99999.9 ) end
intervalTimer.IsLessThen = function( self, duration ) return CurTime() - self.m_timestamp < duration end
intervalTimer.IsGreaterThen = function( self, duration ) return CurTime() - self.m_timestamp > duration end
intervalTimer.__index = intervalTimer
tbotfollowowner.m_teleportTimer = intervalTimer

setmetatable( tbotfollowowner, TBotFollowOwnerMeta )

return tbotfollowowner
Expand All @@ -46,6 +59,7 @@ end

function TBotFollowOwnerMeta:OnStart( me, priorAction )

self.m_teleportTimer:Invalidate()
return self:Continue()

end
Expand Down Expand Up @@ -79,25 +93,78 @@ function TBotFollowOwnerMeta:Update( me, interval )

end

self.m_chasePath:Update( me, owner )

-- Repath every now and then to keep the path fresh
if self.m_repathTimer:Elapsed() then

self.m_repathTimer:Start( math.Rand( 3.0, 5.0 ) )
self.m_chasePath:Invalidate()

-- Don't recreate the path if Update just recomputed it.
if self.m_chasePath:GetAge() > 0.0 then
end

self.m_chasePath:Update( me, owner )

-- Check if the bot is stuck or unable to path to the player
local mover = me:GetTBotLocomotion()
if !self.m_chasePath:IsValid() or mover:IsStuck() then

if !self.m_teleportTimer:HasStarted() then

self.m_chasePath:Invalidate()
self.m_teleportTimer:Start()

end

-- After 5 seconds the bot should attempt to teleport now!
if self.m_teleportTimer:IsGreaterThen( 5.0 ) then

local playerFOV = math.cos( 0.5 * owner:GetFOV() * math.pi / 180 )
local navareas = navmesh.Find( owner:GetPos(), 2000, mover:GetMaxJumpHeight(), mover:GetMaxJumpHeight() )
local lastKnownArea = owner:GetLastKnownArea()
for _, area in ipairs( navareas ) do

-- We don't want to teleport infront of the player as that would ruin the immersion a bit
local teleportPos = area:GetCenter()
if !area:IsPotentiallyVisible( lastKnownArea ) and !self:CanPlayerPotentiallySeeUsTeleport( owner, teleportPos ) then

if !me:IsSpotOccupied( teleportPos ) then

me:SetPos( teleportPos )
mover:ClearStuckStatus()
self.m_chasePath:Invalidate() -- Just in case the bot was stuck!
break

end

end

end

self.m_teleportTimer:Invalidate()

end

elseif self.m_teleportTimer:HasStarted() then

self.m_teleportTimer:Invalidate()

end

return self:Continue()

end

function TBotFollowOwnerMeta:CanPlayerPotentiallySeeUsTeleport( player, pos, cosTolerance )

cosTolerance = cosTolerance or math.cos( 0.5 * player:GetFOV() * math.pi / 180 )
local to = pos - player:GetPos()
local diff = player:GetAimVector():Dot( to )
if diff < 0 then return false end

local length = to:LengthSqr()

return diff^2 > length * cosTolerance^2

end

function TBotFollowOwnerMeta:OnEnd( me, nextAction )

return
Expand Down
Loading