474 lines
13 KiB
Lua
474 lines
13 KiB
Lua
AddCSLuaFile()
|
|
|
|
ENT.Base = "base_nextbot"
|
|
ENT.Spawnable = true
|
|
ENT.BennyNPC = true
|
|
|
|
ENT.AutomaticFrameAdvance = true
|
|
|
|
function ENT:Nick()
|
|
return "GEN#" .. self:EntIndex()
|
|
end
|
|
|
|
if CLIENT then
|
|
DebugNextbot = {}
|
|
net.Receive( "Benny_DebugNextbot", function()
|
|
local ent = net.ReadEntity()
|
|
if !DebugNextbot[ent] then
|
|
DebugNextbot[ent] = {}
|
|
end
|
|
local t = DebugNextbot[ent]
|
|
|
|
t.State = net.ReadString()
|
|
t.Team = net.ReadString()
|
|
t.Faction = net.ReadString()
|
|
t.Rank = net.ReadUInt(8)
|
|
|
|
t.Seeing = {}
|
|
for i=1, net.ReadUInt(8) do
|
|
t.Seeing[net.ReadEntity()] = true
|
|
end
|
|
|
|
t.Memory = {}
|
|
for i=1, net.ReadUInt(8) do
|
|
local memt = {}
|
|
t.Memory[net.ReadEntity()] = memt
|
|
for i=1, net.ReadUInt(8) do
|
|
memt[net.ReadString()] = net.ReadType()
|
|
end
|
|
end
|
|
end)
|
|
local s = ScreenScaleH
|
|
local function DST( text, font, x, y, color )
|
|
|
|
for O=1, 1 do
|
|
draw.SimpleText( text, font, x-O, y-O, color_black )
|
|
draw.SimpleText( text, font, x, y-O, color_black )
|
|
draw.SimpleText( text, font, x+O, y-O, color_black )
|
|
|
|
draw.SimpleText( text, font, x-O, y, color_black )
|
|
--draw.SimpleText( text, font, x, y, color_black )
|
|
draw.SimpleText( text, font, x+O, y, color_black )
|
|
|
|
draw.SimpleText( text, font, x-O, y+O, color_black )
|
|
draw.SimpleText( text, font, x, y+O, color_black )
|
|
draw.SimpleText( text, font, x+O, y+O, color_black )
|
|
end
|
|
|
|
draw.SimpleText( text, font, x, y, color )
|
|
end
|
|
surface.CreateFont("DNB_14", {
|
|
font = "Bahnschrift",
|
|
size = s(14),
|
|
})
|
|
surface.CreateFont("DNB_10", {
|
|
font = "Bahnschrift",
|
|
size = s(10),
|
|
})
|
|
surface.CreateFont("DNB_8", {
|
|
font = "Bahnschrift",
|
|
size = s(8),
|
|
})
|
|
surface.CreateFont("DNB_6", {
|
|
font = "Bahnschrift",
|
|
size = s(6),
|
|
})
|
|
local boost = Vector( 0, 0, 48 )
|
|
hook.Add( "HUDPaint", "Benny_SpecialDebugNextbotView", function()
|
|
for ent, data in pairs( DebugNextbot ) do
|
|
if !IsValid( ent ) then
|
|
DebugNextbot[ent] = nil
|
|
continue
|
|
end
|
|
local ts = ent:GetPos()
|
|
ts:Add(boost)
|
|
local ts = ts:ToScreen()
|
|
local Ox, Oy = math.floor(ts.x-s(16)), math.floor(ts.y)
|
|
|
|
do
|
|
local x, y = Ox, Oy
|
|
|
|
DST( ent:Nick(), "DNB_14", x, y, color_white )
|
|
y = y + s(12)
|
|
|
|
DST( "RANK " .. data.Rank, "DNB_6", x, y, color_white )
|
|
y = y + s(6)
|
|
|
|
DST( data.Faction .. " - " .. data.Team, "DNB_6", x, y, color_white )
|
|
y = y + s(6)
|
|
|
|
DST( data.State, "DNB_10", x, y, color_white )
|
|
y = y + s(10)
|
|
end
|
|
|
|
do
|
|
local x, y = Ox, Oy + s(36)
|
|
DST( "Memory:", "DNB_8", x, y, color_white )
|
|
y = y + s(8)
|
|
for i, v in pairs( data.Memory ) do
|
|
if !IsValid( i ) then
|
|
data.Memory[i] = nil
|
|
continue
|
|
end
|
|
local line_y = y
|
|
DST( "- " .. i:Nick(), "DNB_6", x+s(4), line_y, color_white )
|
|
line_y = line_y + s(6)
|
|
for key, value in pairs( v ) do
|
|
local nicevalue
|
|
if key == "LastSeenTime" then
|
|
nicevalue = string.NiceTime( CurTime() - value ) .. " ago"
|
|
elseif isvector( value ) then
|
|
nicevalue = "Vector(" .. math.Round(value.x) .. ", " .. math.Round(value.y) .. ", " .. math.Round(value.z) .. ")"
|
|
else
|
|
nicevalue = value
|
|
end
|
|
DST( key, "DNB_6", x+s(4*2), line_y, color_white )
|
|
line_y = line_y + s(4)
|
|
DST( nicevalue, "DNB_6", x+s(4*3), line_y, color_white )
|
|
line_y = line_y + s(6)
|
|
end
|
|
x = x + s(54)
|
|
end
|
|
end
|
|
|
|
do
|
|
local x, y = Ox, Oy + s(36*3)
|
|
DST( "Seeing:", "DNB_8", x, y, color_white )
|
|
y = y + s(8)
|
|
for i, v in pairs( data.Seeing ) do
|
|
if !IsValid( i ) then
|
|
data.Seeing[i] = nil
|
|
continue
|
|
end
|
|
DST( "- " .. i:Nick(), "DNB_6", x+s(8), y, color_white )
|
|
y = y + s(6)
|
|
end
|
|
end
|
|
|
|
end
|
|
end)
|
|
net.Receive("Benny_DebugNextbotChat", function()
|
|
chat.AddText( Color( 200, 200, 255 ), "[" .. net.ReadEntity():Nick() .. "] ", net.ReadColor( false ), net.ReadString() )
|
|
end)
|
|
return
|
|
else
|
|
util.AddNetworkString("Benny_DebugNextbot")
|
|
util.AddNetworkString("Benny_DebugNextbotChat")
|
|
end
|
|
|
|
function ENT:DebugChat( text, color )
|
|
net.Start("Benny_DebugNextbotChat")
|
|
net.WriteEntity( self )
|
|
net.WriteColor( color or Color( 200, 255, 255 ), false )
|
|
net.WriteString( text )
|
|
net.Broadcast()
|
|
end
|
|
|
|
function ENT:SetState( state )
|
|
if self.State != state then
|
|
self:RunCurrentState( "Disable", state )
|
|
local oldstate = self.State
|
|
self.State = state
|
|
self:RunCurrentState( "Enable", oldstate )
|
|
end
|
|
end
|
|
|
|
function ENT:GetState()
|
|
return self.State
|
|
end
|
|
|
|
function ENT:RunCurrentState( func, ... )
|
|
if self:GetState() then
|
|
self.States[self:GetState()][func]( self, ... )
|
|
end
|
|
end
|
|
|
|
ENT.States = {
|
|
["idle"] = {
|
|
RunBehavior = function( self )
|
|
self:StartActivity( ACT_HL2MP_IDLE_PASSIVE )
|
|
coroutine.yield()
|
|
end,
|
|
Enable = function( self, from )
|
|
-- Holster
|
|
self:AddGestureSequence( self:SelectWeightedSequence(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) )
|
|
if from then
|
|
self:DebugChat( "Entering idle from " .. from, Color( 100, 255, 100 ) )
|
|
end
|
|
end,
|
|
Disable = function( self, to )
|
|
-- Draw
|
|
self:AddGestureSequence( self:SelectWeightedSequence(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) )
|
|
self:DebugChat( "Entering " .. to .. " from idle", Color( 100, 255, 100 ) )
|
|
end,
|
|
},
|
|
["combat"] = {
|
|
RunBehavior = function( self )
|
|
local re = self:RecentEnemy()
|
|
if re then
|
|
if self.bSeeing[re] then
|
|
self.loco:FaceTowards( re:GetPos() )
|
|
self:StartActivity( ACT_HL2MP_IDLE_AR2 )
|
|
|
|
if (self.NextFire or 0) <= CurTime() then
|
|
local rp = RecipientFilter()
|
|
rp:AddAllPlayers()
|
|
self:EmitSound("benny/weapons/usp/01.ogg", 80, 100, 0.2, nil, nil, nil, rp )
|
|
--self:FireBullets( {
|
|
-- Attacker = self,
|
|
-- Inflictor = self,
|
|
-- Damage = 3,
|
|
-- Dir = self:EyeAngles():Forward(),
|
|
-- Src = self:EyePos()
|
|
--} )
|
|
self.NextFire = CurTime() + 0.5
|
|
end
|
|
else
|
|
self.loco:SetDesiredSpeed( 200 )
|
|
self:StartActivity( ACT_HL2MP_WALK_RPG )
|
|
|
|
local em = self.bEnemyMemory[re]
|
|
if em and em.GoToLastKnown and em.GoToLastKnown != true then
|
|
local result = self:ChaseEnemy( { re = re, draw = true } )
|
|
--local result = self:MoveToPos( em.GoToLastKnown, { lookahead = 100, tolerance = 32, draw = true, repath = 0.5 } )
|
|
if result == "ok" then
|
|
em.GoToLastKnown = true
|
|
self:DebugChat("Went to last known position of " .. re:Nick())
|
|
elseif result == "stuck" then
|
|
em.GoToLastKnown = true
|
|
self:DebugChat("Failed to go to last known position of " .. re:Nick() .. " at " .. tostring(self:GetPos()))
|
|
elseif result == "cancelled" then
|
|
em.GoToLastKnown = false
|
|
self:DebugChat("Reengaging " .. re:Nick() .. "!!")
|
|
self:EmitSound( "benny/dev/reengaging.wav", 90, 100, 1 )
|
|
else
|
|
em.GoToLastKnown = true
|
|
self:DebugChat("Unknown state " .. result .. " while going to " .. re:Nick() .. " at " .. tostring(self:GetPos()))
|
|
end
|
|
elseif em and em.GoToLastKnown == true then
|
|
self:DebugChat("Investigated " .. re:Nick() .. "'s last known position, it's clear." )
|
|
self:SetState("idle")
|
|
self:EmitSound( "benny/dev/at last point of interest.wav", 90, 100, 1 )
|
|
end
|
|
end
|
|
end
|
|
|
|
coroutine.yield()
|
|
end,
|
|
Enable = function( self, from )
|
|
end,
|
|
Disable = function( self, to )
|
|
end,
|
|
},
|
|
}
|
|
|
|
function ENT:ChaseEnemy( options )
|
|
local options = options or {}
|
|
local path = Path( "Follow" )
|
|
path:SetMinLookAheadDistance( options.lookahead or 300 )
|
|
path:SetGoalTolerance( options.tolerance or 20 )
|
|
|
|
local em = self.bEnemyMemory[options.re]
|
|
if !em.GoToLastKnown then return "cancelled" end
|
|
if !isvector( em.GoToLastKnown ) then return "cancelled" end
|
|
path:Compute( self, em.GoToLastKnown ) -- Compute the path towards the enemy's position
|
|
|
|
if ( !path:IsValid() ) then return "failed" end
|
|
|
|
while ( path:IsValid() ) do
|
|
if !em.GoToLastKnown then return "cancelled" end
|
|
if !isvector( em.GoToLastKnown ) then return "cancelled" end
|
|
if ( path:GetAge() > 0.1 ) then -- Since we are following the player we have to constantly remake the path
|
|
path:Compute(self, em.GoToLastKnown )-- Compute the path towards the enemy's position again
|
|
end
|
|
path:Update( self ) -- This function moves the bot along the path
|
|
|
|
if ( options.draw ) then path:Draw() end
|
|
-- If we're stuck then call the HandleStuck function and abandon
|
|
if ( self.loco:IsStuck() ) then
|
|
self:HandleStuck()
|
|
return "stuck"
|
|
end
|
|
coroutine.yield()
|
|
end
|
|
return "ok"
|
|
end
|
|
|
|
function ENT:BodyUpdate()
|
|
local target = self:RecentEnemy()
|
|
if target then
|
|
-- Without this +64z, they stare straight up. It's eye level now, I guess.
|
|
local a = self:EyePos() - (self.bSeeing[target] and target:EyePos() or self.bEnemyMemory[target].LastPos + Vector(0,0,64))
|
|
a:Normalize()
|
|
a = a:Angle()
|
|
self:SetPoseParameter( "aim_yaw", 0 or a.y )
|
|
local r1, r2 = self:GetPoseParameterRange( self:LookupPoseParameter( "aim_pitch" ) )
|
|
self:SetPoseParameter( "aim_pitch", math.Clamp( -a.p, r1, r2 ) )
|
|
end
|
|
--self:SetPoseParameter( "move_x", 1 )
|
|
--self:SetPoseParameter( "move_y", 1 )
|
|
--self:SetPlaybackRate(1)
|
|
--self:FrameAdvance()
|
|
self:BodyMoveXY()
|
|
return
|
|
end
|
|
|
|
function ENT:OnEntitySight( ent )
|
|
if !self.bSeeing[ent] then
|
|
if ent.BennyNPC and ent.Faction == self.Faction then
|
|
if self.bEnemyMemory[ent] then
|
|
else
|
|
self:DebugChat( "Hello " .. ent:Nick() .. ".", Color( 200, 255, 200 ) )
|
|
end
|
|
else
|
|
if self.bEnemyMemory[ent] then
|
|
local em = self.bEnemyMemory[ent]
|
|
if CurTime()-em.LastSeenTime > 5 then
|
|
self:DebugChat( "Been a while " .. ent:Nick() .. "!! " .. string.NiceTime(CurTime()-em.LastSeenTime), Color( 255, 200, 100 ) )
|
|
self.NextFire = CurTime() + 0.5
|
|
self:EmitSound( "benny/dev/there he is.wav", 90, 100, 1 )
|
|
else
|
|
self:DebugChat( "Resighted " .. ent:Nick() .. "! " .. string.NiceTime(CurTime()-em.LastSeenTime), Color( 255, 200, 100 ) )
|
|
self.NextFire = CurTime() + 0.25
|
|
end
|
|
else
|
|
self:DebugChat( "New target " .. ent:Nick() .. "!!", Color( 255, 200, 100 ) )
|
|
self:EmitSound( "benny/dev/new target.wav", 90, 100, 1 )
|
|
end
|
|
self:SetState("combat")
|
|
end
|
|
end
|
|
self.bSeeing[ent] = true
|
|
end
|
|
|
|
function ENT:OnEntitySightLost( ent )
|
|
self.bSeeing[ent] = nil
|
|
end
|
|
|
|
local wide, tall = 16/2, 64
|
|
local b1 = Vector( wide, wide, tall )
|
|
local b2 = Vector( -wide, -wide, 0 )
|
|
|
|
local wide, tall = 48/2, 80
|
|
local s1 = Vector( wide, wide, tall )
|
|
local s2 = Vector( -wide, -wide, 0 )
|
|
|
|
function ENT:Initialize()
|
|
self:SetModel( "models/player/infoplayerrealism.mdl" )
|
|
self:SetCollisionBounds( b1, b2 )
|
|
self:SetSurroundingBounds( s1, s2 )
|
|
self.loco:SetDesiredSpeed( 100 ) -- Walk speed
|
|
self.loco:SetStepHeight( 22 )
|
|
self:SetShouldServerRagdoll( false )
|
|
self:SetFOV( 90 )
|
|
|
|
self:SetMaxHealth( 100 )
|
|
self:SetHealth( 100 )
|
|
|
|
self:SetCollisionGroup( COLLISION_GROUP_NPC )
|
|
|
|
if SERVER then self:PhysicsInitShadow( true, true ) end
|
|
|
|
self:SetState("idle")
|
|
|
|
self.Team = nil
|
|
self.Faction = "ALPHA"
|
|
|
|
self.bEnemyMemory = {}
|
|
self.bSeeing = {}
|
|
|
|
self.Rank = math.random( 0, 255 )
|
|
end
|
|
|
|
function ENT:RunBehaviour()
|
|
while ( true ) do
|
|
self:RunCurrentState( "RunBehavior", self )
|
|
end
|
|
end
|
|
|
|
function ENT:OnContact( ent )
|
|
end
|
|
|
|
function ENT:TopEnemy()
|
|
for ent, _ in pairs( self.bSeeing ) do
|
|
if ent.BennyNPC and ent.Faction != self.Faction or !ent.BennyNPC then
|
|
return ent
|
|
end
|
|
end
|
|
end
|
|
|
|
function ENT:RecentEnemy()
|
|
for ent, data in SortedPairsByMemberValue( self.bEnemyMemory, "LastSeenTime" ) do
|
|
if ent.BennyNPC and ent.Faction != self.Faction or !ent.BennyNPC then
|
|
return ent
|
|
end
|
|
end
|
|
end
|
|
|
|
function ENT:Think()
|
|
for ent, _ in pairs( self.bSeeing ) do
|
|
if !IsValid(ent) or !_ then
|
|
self.bSeeing[ent] = nil
|
|
continue
|
|
end
|
|
if !self.bEnemyMemory[ent] then
|
|
self.bEnemyMemory[ent] = {}
|
|
end
|
|
local t = self.bEnemyMemory[ent]
|
|
t.LastPos = ent:GetPos()
|
|
t.LastSeenTime = CurTime()
|
|
t.GoToLastKnown = false
|
|
|
|
if ent.BennyNPC then
|
|
if !self.Team and !ent.Team and self.Rank >= ent.Rank then
|
|
self:DebugChat( "Duoing with " .. ent:Nick() )
|
|
self:EmitSound( "benny/dev/stick with me.wav", 90, 100, 1 )
|
|
self.Team = "ALPHA_Duo_1"
|
|
ent.Team = "ALPHA_Duo_1"
|
|
end
|
|
end
|
|
end
|
|
|
|
if self:GetState() == "combat" then
|
|
for ent, data in pairs( self.bEnemyMemory ) do
|
|
if !IsValid(ent) then
|
|
self.bEnemyMemory[ent] = nil
|
|
continue
|
|
end
|
|
if self.Faction != ent.Faction and !data.GoToLastKnown and data.LastSeenTime + 2 < CurTime() then
|
|
data.GoToLastKnown = Vector(data.LastPos)
|
|
self:DebugChat( "Investigating " .. ent:Nick() .. "'s last known position" )
|
|
self:EmitSound( "benny/dev/moving.wav", 90, 100, 1 )
|
|
--self:SetState("idle")
|
|
end
|
|
end
|
|
end
|
|
|
|
net.Start( "Benny_DebugNextbot", true )
|
|
net.WriteEntity(self)
|
|
|
|
net.WriteString( self.State )
|
|
net.WriteString( self.Team or "TEAMLESS" )
|
|
net.WriteString( self.Faction )
|
|
net.WriteUInt( self.Rank, 8 )
|
|
|
|
net.WriteUInt( table.Count( self.bSeeing ), 8 )
|
|
for ent, _ in pairs( self.bSeeing ) do
|
|
net.WriteEntity( ent )
|
|
end
|
|
|
|
net.WriteUInt( table.Count( self.bEnemyMemory ), 8 )
|
|
for ent, data in pairs( self.bEnemyMemory ) do
|
|
net.WriteEntity( ent )
|
|
net.WriteUInt( table.Count(data), 8 )
|
|
for key, value in pairs( data ) do
|
|
net.WriteString( key )
|
|
net.WriteType( value )
|
|
end
|
|
end
|
|
net.SendPVS(self:GetPos())
|
|
|
|
self:NextThink( CurTime() )
|
|
if CLIENT then self:SetNextClientThink( CurTime() ) end
|
|
return true
|
|
end |