mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
439 lines
11 KiB
Lua
439 lines
11 KiB
Lua
--[[
|
|
| This file was obtained through the combined efforts
|
|
| of Madbluntz & Plymouth Antiquarian Society.
|
|
|
|
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
|
| Maloy, DrPepper10 @ RIP, Atle!
|
|
|
|
|
| Visit for more: https://plymouth.thetwilightzone.ru/
|
|
--]]
|
|
|
|
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
|
-- All Rights Reserved. Not allowed to be reuploaded.
|
|
local NikNaks = NikNaks
|
|
local CurTime, setmetatable, IsValid = CurTime, setmetatable, IsValid
|
|
local min, max, cos = math.min, math.max, math.cos
|
|
|
|
--- @class LPathFollower
|
|
--- @field _segments LPathFollowerSegment[]
|
|
local meta = {}
|
|
meta.__index = meta
|
|
meta.__tostring = function( self )
|
|
return "LPathFollower Age: " .. self:GetAge()
|
|
end
|
|
NikNaks.__metatables["LPathFollower"] = meta
|
|
|
|
--[[
|
|
t._segments = {}
|
|
t._entity = nil -- Overrides the last position in the segments
|
|
t._age = CurTime()
|
|
|
|
]]
|
|
--- Creates an empty path-follower
|
|
--- @param start_pos Vector
|
|
--- @return LPathFollower
|
|
function meta.CreatePathFollower( start_pos )
|
|
--- @class LPathFollower
|
|
local t = {}
|
|
t._segments = {}
|
|
t._start = start_pos
|
|
t._length = 0
|
|
t._age = CurTime()
|
|
t._valid = true
|
|
t._cursor = 1
|
|
t._cursor_dis = 0
|
|
t._cursor_lef = 0
|
|
|
|
return setmetatable( t, meta )
|
|
end
|
|
|
|
--- Adds a segment.
|
|
--- @param from Vector
|
|
--- @param to Vector
|
|
--- @param curvature number
|
|
--- @param move_type number
|
|
--- @return LPathFollowerSegment
|
|
function meta:AddSegment( from, to, curvature, move_type )
|
|
--- @class LPathFollowerSegment
|
|
local seg = {}
|
|
seg.curvature = curvature or 0
|
|
seg.move_type = move_type or 1
|
|
seg.distanceFromStart = from:Distance( to )
|
|
|
|
local n = ( to - from ):GetNormalized()
|
|
seg.forward = n
|
|
seg.yaw = ( -n ):Angle().y
|
|
|
|
if n.z > 0.7 or n.z < -0.7 then
|
|
self.how = 9
|
|
else
|
|
if n.y < -0.5 then -- North
|
|
seg.how = 0
|
|
elseif n.y > 0.5 then -- South
|
|
seg.how = 2
|
|
elseif n.x > 0.5 then -- East
|
|
seg.how = 1
|
|
else -- West
|
|
seg.how = 3
|
|
end
|
|
end
|
|
|
|
seg.length = from:Distance( to )
|
|
seg.s_lengh = self._length
|
|
seg.pos = to
|
|
--seg.ladder
|
|
--seg.node
|
|
--seg.area
|
|
--seg.nna
|
|
|
|
self._length = self._length + seg.length
|
|
self._segments[#self._segments + 1] = seg
|
|
return seg
|
|
end
|
|
|
|
-- Default easy functions
|
|
do
|
|
--- Returns the length of the path.
|
|
function meta:GetLength()
|
|
return self._length or 0
|
|
end
|
|
|
|
--- Returns the first segment.
|
|
function meta:FirstSegment()
|
|
return self._segments[1]
|
|
end
|
|
|
|
--- Returns the last segment.
|
|
function meta:LastSegment()
|
|
return self._segments[#self._segments]
|
|
end
|
|
|
|
--- Returns all segments.
|
|
function meta:GetAllSegments()
|
|
return self._segments
|
|
end
|
|
|
|
--- Returns the age of the path.
|
|
function meta:GetAge()
|
|
return CurTime() - self._age
|
|
end
|
|
|
|
--- Resets the age of the path.
|
|
function meta:ResetAge()
|
|
self._age = CurTime()
|
|
end
|
|
|
|
--- Returns true if the path is valid.
|
|
function meta:IsValid()
|
|
return self._valid or false
|
|
end
|
|
|
|
--- Invalidates the path.
|
|
function meta:Invalidate()
|
|
self._valid = false
|
|
end
|
|
|
|
--- Returns the starting position of the path.
|
|
function meta:GetStart()
|
|
return self._start
|
|
end
|
|
|
|
--- Returns the ending position of the path (Note, will update if the target is an entity).
|
|
function meta:GetEnd()
|
|
if IsValid( self.target_ent ) then
|
|
return self.target_ent:GetPos()
|
|
end
|
|
|
|
return self._segments[#self._segments].pos
|
|
end
|
|
end
|
|
|
|
-- Cursor
|
|
local findClosestSeg
|
|
do
|
|
--- Returns the cursor position.
|
|
--- @return number
|
|
--- @return number
|
|
local function findCursor( self, distance )
|
|
local q = self._segments
|
|
distance = min( self._length, distance )
|
|
|
|
for i = 1, #q do
|
|
if distance <= q[i].s_lengh + q[i].length then
|
|
return i, distance - ( q[i].s_lengh + q[i].length )
|
|
end
|
|
end
|
|
|
|
return 1, distance
|
|
end
|
|
|
|
--- Returns the closest segment and its index.
|
|
--- @param position Vector
|
|
--- @return LPathFollowerSegment
|
|
--- @return number
|
|
findClosestSeg = function( self, position )
|
|
local c, d, q
|
|
|
|
for i, seg in pairs( self._segments ) do
|
|
local dis = seg.pos:DistToSqr( position )
|
|
if not c or c > dis then
|
|
c = dis
|
|
d = seg
|
|
q = i
|
|
end
|
|
end
|
|
|
|
return d, q
|
|
end
|
|
|
|
--- Returns the closest position along the path to said position.
|
|
--- @param position Vector
|
|
--- @return Vector
|
|
local function findClosestBetween( A, dir, position, maxLength )
|
|
local v = position - A
|
|
local d = v:Dot( dir )
|
|
return A + dir * max( 0, min( d, maxLength ) )
|
|
end
|
|
|
|
--- Returns the position on the path by given distance
|
|
--- @param distance number
|
|
--- @return Vector
|
|
function meta:GetPositionOnPath( distance )
|
|
local seg_id, lef = findCursor( self, distance )
|
|
local t = self._segments
|
|
local seg = t[seg_id]
|
|
return seg.pos + seg.forward * lef
|
|
end
|
|
|
|
--- Returns the closest position along the path to said position.
|
|
--- @param position Vector
|
|
--- @return Vector
|
|
function meta:GetClosestPosition( position )
|
|
-- Locate the closest points
|
|
local seg, seg_id = findClosestSeg( self, position )
|
|
if not seg then return self:GetStart() end -- Fallback to the start pos
|
|
|
|
local max_seg = #self._segments
|
|
|
|
if seg_id <= 1 then
|
|
local n_seg = self._segments[seg_id + 1]
|
|
local v1 = findClosestBetween( self:GetStart(), seg.forward, position, seg.length )
|
|
local v2 = findClosestBetween( seg.pos, n_seg.forward, position, n_seg.length )
|
|
if v1:DistToSqr( position ) > v2:DistToSqr( position ) then
|
|
return v2
|
|
else
|
|
return v1
|
|
end
|
|
elseif seg_id >= max_seg then
|
|
local s_seg = self._segments[max_seg - 1]
|
|
return findClosestBetween( s_seg.pos, seg.forward, position, seg.length )
|
|
else
|
|
local p_seg = self._segments[seg_id - 1]
|
|
local n_seg = self._segments[seg_id + 1]
|
|
local v1 = findClosestBetween( p_seg.pos, seg.forward, position, seg.length )
|
|
local v2 = findClosestBetween( seg.pos, n_seg.forward, position, n_seg.length )
|
|
if v1:DistToSqr( position ) > v2:DistToSqr( position ) then
|
|
return v2
|
|
else
|
|
return v1
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Moves the cursor to the start of the path.
|
|
function meta:MoveCursorToStart()
|
|
self._cursor_dis = 0
|
|
self._cursor_lef = 0
|
|
self._cursor = 1
|
|
end
|
|
|
|
--- Moves the cursor to the end of the path.
|
|
function meta:MoveCursorToEnd()
|
|
self._cursor_dis = self:GetLength()
|
|
self._cursor = #self._segments
|
|
self._cursor_lef = self._segments[self._cursor].length
|
|
end
|
|
|
|
--- Returns the cursor progress along the path
|
|
--- @return number
|
|
function meta:GetCursorPosition()
|
|
return self._cursor_dis
|
|
end
|
|
|
|
--- Moves the cursor to said distance.
|
|
--- @param distance number
|
|
function meta:MoveCursorTo( distance )
|
|
self._cursor_dis = distance
|
|
local seg_id, lef = findCursor( self, distance )
|
|
self._cursor = seg_id
|
|
self._cursor_lef = lef
|
|
end
|
|
|
|
--- Moves the cursor said distance
|
|
--- @param distance number
|
|
function meta:MoveCursor( distance )
|
|
self._cursor_dis = self._cursor_dis + distance
|
|
local nd = self._cursor_lef + distance
|
|
local seg = self._segments[self._cursor]
|
|
if nd < 0 or nd > seg.length then -- New segment
|
|
local seg_id, lef = findCursor( self, self._cursor_dis )
|
|
self._cursor = seg_id
|
|
self._cursor_lef = lef
|
|
else
|
|
self._cursor_lef = nd
|
|
end
|
|
end
|
|
|
|
--- Returns the closest sequence.
|
|
--- @param position Vector
|
|
--- @return LPathFollowerSegment
|
|
--- @return number
|
|
function meta:FindClosestSeg( position )
|
|
return findClosestSeg( self, position )
|
|
end
|
|
|
|
--- Returns the distance from the path.
|
|
--- @param position Vector
|
|
--- @return number
|
|
function meta:FindDistanceFromPath( position )
|
|
return self:GetClosestPosition( position ):Distance( position )
|
|
end
|
|
|
|
end
|
|
|
|
-- NPC stuff
|
|
do
|
|
--[[
|
|
By default in Gmod, you're reuired to create a pathfind object, and then compute it.
|
|
However I've found many situations where I could reuse a path. I.e a group of NPC's.
|
|
So to stick to the closest "Gmod way", path:Update can be called on multiple entities and got a goal argument.
|
|
|
|
C function here: https://github.com/Joshua-Ashton/Source-PlusPlus/blob/4056819cea889d73626a1cbc09518b2f8ba5dda4/src/game/shared/cstrike/bot/nav_path.cpp#L472
|
|
]]
|
|
function meta:Update( ent, toleranceSqrt )
|
|
-- Make sure it is a valid entity and it has loco
|
|
if not IsValid( ent ) or not ent.loco then return true end
|
|
|
|
local entPos = ent:GetPos()
|
|
local seq, id = nil, ent._cursor
|
|
|
|
-- Located the closest segment, and use that
|
|
if not id then
|
|
seq, id = findClosestSeg( self, entPos )
|
|
ent._cursor = id
|
|
else
|
|
seq = self._segments[id]
|
|
end
|
|
|
|
if not seq then return true end -- No segment, we must have reached the end
|
|
|
|
local goal = seq.pos
|
|
if goal:DistToSqr( entPos ) < toleranceSqrt then
|
|
if id >= #self._segments then return true end -- Reached the end
|
|
id = id + 1
|
|
seq = self._segments[id]
|
|
goal = seq.pos
|
|
ent._cursor = ent._cursor + 1
|
|
end
|
|
|
|
ent.loco:Approach( goal, 1 )
|
|
ent.loco:FaceTowards( goal )
|
|
ent:SetAngles( Angle( 0, ( ent:GetPos() - goal ):Angle().y, 0 ) )
|
|
end
|
|
|
|
end
|
|
|
|
-- NET
|
|
do
|
|
function NikNaks.net.WritePath( path )
|
|
local n = #path._segments
|
|
net.WriteUInt( n, 16 )
|
|
net.WriteFloat( path._age )
|
|
net.WriteVector( path._start )
|
|
|
|
for i = 1, n do
|
|
local seg = path._segments[i]
|
|
net.WriteVector( seg.pos - seg.length * seg.forward )
|
|
net.WriteVector( seg.pos )
|
|
net.WriteFloat( seg.curvature )
|
|
net.WriteUInt( seg.move_type, 8 )
|
|
end
|
|
end
|
|
|
|
function NikNaks.net.ReadPath()
|
|
local n = net.ReadUInt( 16 )
|
|
local age = net.ReadFloat()
|
|
local path = meta.CreatePathFollower( net.ReadVector() )
|
|
path._age = age
|
|
|
|
for _ = 1, n do
|
|
path:AddSegment( net.ReadVector(), net.ReadVector(), net.ReadFloat(), net.ReadUInt( 8 ) )
|
|
end
|
|
|
|
return path
|
|
end
|
|
end
|
|
|
|
-- Debug
|
|
do
|
|
local mat_goal = Material("editor/assault_rally")
|
|
local mat_start = Material("effects/powerup_agility_hud")
|
|
|
|
local tMat = Material("effects/bluelaser1")
|
|
local m = Material("hud/arrow_big")
|
|
|
|
local point = Material("hud/freezecam_callout_arrow")
|
|
local cir = Material("hud/cart_point_neutral_opaque")
|
|
local mat_arrow = Material("vgui/glyph_expand")
|
|
|
|
local mat_solider = Material("hud/bomb_carried")
|
|
local jump_man = Material("hud/death_wheel_1")
|
|
|
|
local col_walk = color_white
|
|
local col_climb = Color(155,55,155)
|
|
local col_fly = Color(55,55,255)
|
|
local col_jump = Color(125, 55, 255)
|
|
|
|
local mov = bit.bor(NikNaks.CAP_MOVE_GROUND, NikNaks.CAP_MOVE_CLIMB, NikNaks.CAP_MOVE_JUMP)
|
|
local point_b = Material("hud/cart_point_blue")
|
|
local point_c = Material("hud/expanding_vert_middle_blue_bg")
|
|
|
|
function meta:DebugRender()
|
|
local l
|
|
for i, seg in ipairs(self:GetAllSegments()) do
|
|
render.SetMaterial(cir)
|
|
render.DrawSprite( seg.pos, 16, 16 )
|
|
if l then
|
|
local col = color_white
|
|
if seg.move_type == NikNaks.CAP_MOVE_CLIMB then
|
|
col = col_climb
|
|
elseif seg.move_type == NikNaks.CAP_MOVE_FLY then
|
|
col = col_fly
|
|
elseif seg.move_type == NikNaks.CAP_MOVE_JUMP then
|
|
col = col_jump
|
|
end
|
|
local d = seg.pos:Distance(l)
|
|
local n = (SysTime() * 2) % 1
|
|
m:SetVector("$color",Vector(col.r,col.g,col.b) / 255)
|
|
render.SetMaterial(m)
|
|
render.DrawBeam( seg.pos, l, 30, n, d / 40 + n, col )
|
|
render.SetMaterial(point)
|
|
render.DrawBeam( l + seg.forward * 10,l + seg.forward * 30, 20, 0, 1)
|
|
end
|
|
l = seg.pos
|
|
end
|
|
|
|
local num = max(1, self:GetLength() / 800)
|
|
for i = 1, num do
|
|
local dis = (CurTime() * 100 + i * 800) % self:GetLength()
|
|
local pos = self:GetPositionOnPath( dis )
|
|
local q = 1 + cos(SysTime() * 10 + i) * 0.1
|
|
render.SetMaterial(mat_arrow)
|
|
render.DrawBeam( pos + Vector(0,0,16), pos, 16, q - 1, q, color_white )
|
|
render.SetMaterial(mat_solider)
|
|
render.DrawSprite( pos + Vector(0,0,20), 16, 16 )
|
|
end
|
|
end
|
|
end
|
|
|