539 lines
15 KiB
Lua
539 lines
15 KiB
Lua
local NAV_LOOPS_PER_FRAME = CreateConVar('sv_dlib_nav_loops_per_frame', '50', {
|
|
FCVAR_ARCHIVE,
|
|
FCVAR_NOTIFY
|
|
}, 'A* Searcher iterations per frame')
|
|
local NAV_OPEN_LIMIT = CreateConVar('sv_dlib_nav_open_limit', '700', {
|
|
FCVAR_REPLICATED,
|
|
FCVAR_ARCHIVE,
|
|
FCVAR_NOTIFY
|
|
}, 'A* Searcher "open" nodes limit (at same time)')
|
|
local NAV_LIMIT = CreateConVar('sv_dlib_nav_limit', '4000', {
|
|
FCVAR_REPLICATED,
|
|
FCVAR_ARCHIVE,
|
|
FCVAR_NOTIFY
|
|
}, 'A* Searcher total iterations limit')
|
|
local FRAME_THERSOLD = CreateConVar('sv_dlib_nav_frame_limit', '5', {
|
|
FCVAR_ARCHIVE,
|
|
FCVAR_NOTIFY
|
|
}, 'A* Searcher time limit (in milliseconds) per calculation per frame')
|
|
local TIME_LIMIT = CreateConVar('sv_dlib_nav_time_limit', '2500', {
|
|
FCVAR_REPLICATED,
|
|
FCVAR_ARCHIVE,
|
|
FCVAR_NOTIFY
|
|
}, 'A* Searcher total time limit (in milliseconds) per one search')
|
|
local AStarNode
|
|
do
|
|
local _class_0
|
|
local _base_0 = {
|
|
SetG = function(self, val)
|
|
if val == nil then
|
|
val = 0
|
|
end
|
|
self.g = val
|
|
self.f = self.g + self.h
|
|
end,
|
|
SetH = function(self, val)
|
|
if val == nil then
|
|
val = 0
|
|
end
|
|
self.h = val
|
|
self.f = self.g + self.h
|
|
end,
|
|
SetFrom = function(self, val)
|
|
self.from = val
|
|
end,
|
|
__tostring = function(self)
|
|
return "[DLib:AStarNode:" .. tostring(self.nav) .. "]"
|
|
end,
|
|
GetG = function(self)
|
|
return self.g
|
|
end,
|
|
GetH = function(self)
|
|
return self.h
|
|
end,
|
|
GetF = function(self)
|
|
return self.f
|
|
end,
|
|
GetPos = function(self)
|
|
return self.pos
|
|
end,
|
|
GetFrom = function(self)
|
|
return self.from
|
|
end,
|
|
HasParent = function(self)
|
|
return self.from ~= nil
|
|
end,
|
|
GetParent = function(self)
|
|
return self.from
|
|
end,
|
|
GetAdjacentAreas = function(self)
|
|
return self.nav:GetAdjacentAreas()
|
|
end,
|
|
Underwater = function(self)
|
|
return self.nav:IsUnderwater()
|
|
end
|
|
}
|
|
_base_0.__index = _base_0
|
|
_class_0 = setmetatable({
|
|
__init = function(self, nav, g, target)
|
|
if g == nil then
|
|
g = 0
|
|
end
|
|
if target == nil then
|
|
target = Vector(0, 0, 0)
|
|
end
|
|
self.nav = nav
|
|
self.pos = self.nav:GetCenter()
|
|
self.positions = {
|
|
self.pos
|
|
}
|
|
self.target = target
|
|
self.g = g
|
|
self.h = target:DistToSqr(self.pos)
|
|
self.f = self.g + self.h
|
|
end,
|
|
__base = _base_0,
|
|
__name = "AStarNode"
|
|
}, {
|
|
__index = _base_0,
|
|
__call = function(cls, ...)
|
|
local _self_0 = setmetatable({}, _base_0)
|
|
cls.__init(_self_0, ...)
|
|
return _self_0
|
|
end
|
|
})
|
|
_base_0.__class = _class_0
|
|
local self = _class_0
|
|
self.NORTH_WEST = 0
|
|
self.NORTH_EAST = 1
|
|
self.SOUTH_EAST = 2
|
|
self.SOUTH_WEST = 3
|
|
self.SIDES = {
|
|
self.NORTH_EAST,
|
|
self.NORTH_WEST,
|
|
self.SOUTH_EAST,
|
|
self.SOUTH_WEST
|
|
}
|
|
AStarNode = _class_0
|
|
end
|
|
local AStarTracer
|
|
do
|
|
local _class_0
|
|
local _base_0 = {
|
|
GetOpenNodes = function(self)
|
|
return self.opened
|
|
end,
|
|
GetOpenNodesCount = function(self)
|
|
return #self.opened
|
|
end,
|
|
CopyOpenNodes = function(self)
|
|
local _accum_0 = { }
|
|
local _len_0 = 1
|
|
local _list_0 = self.opened
|
|
for _index_0 = 1, #_list_0 do
|
|
local node = _list_0[_index_0]
|
|
_accum_0[_len_0] = node
|
|
_len_0 = _len_0 + 1
|
|
end
|
|
return _accum_0
|
|
end,
|
|
GetClosedNodes = function(self)
|
|
return self.closed
|
|
end,
|
|
GetClosedNodesCount = function(self)
|
|
return #self.closed
|
|
end,
|
|
CopyClosedNodes = function(self)
|
|
local _accum_0 = { }
|
|
local _len_0 = 1
|
|
local _list_0 = self.closed
|
|
for _index_0 = 1, #_list_0 do
|
|
local node = _list_0[_index_0]
|
|
_accum_0[_len_0] = node
|
|
_len_0 = _len_0 + 1
|
|
end
|
|
return _accum_0
|
|
end,
|
|
GetTotalNodes = function(self)
|
|
return self.database
|
|
end,
|
|
GetTotalNodesCount = function(self)
|
|
return #self.database
|
|
end,
|
|
CopyTotalNodes = function(self)
|
|
local _accum_0 = { }
|
|
local _len_0 = 1
|
|
local _list_0 = self.database
|
|
for _index_0 = 1, #_list_0 do
|
|
local node = _list_0[_index_0]
|
|
_accum_0[_len_0] = node
|
|
_len_0 = _len_0 + 1
|
|
end
|
|
return _accum_0
|
|
end,
|
|
GetIterations = function(self)
|
|
return self.iterations
|
|
end,
|
|
GetTotalIterations = function(self)
|
|
return self.iterations
|
|
end,
|
|
GetCalculationTime = function(self)
|
|
return self.totalTime
|
|
end,
|
|
GetCurrentG = function(self)
|
|
return self.currentG
|
|
end,
|
|
GetLeftDistance = function(self)
|
|
return math.max(self.distToEnd - math.sqrt(self.currentG), 0)
|
|
end,
|
|
GetDistance = function(self)
|
|
return self.distToEnd
|
|
end,
|
|
Distance = function(self)
|
|
return self.distToEnd
|
|
end,
|
|
IsStopped = function(self)
|
|
return self.stop
|
|
end,
|
|
IsWorking = function(self)
|
|
return self.working
|
|
end,
|
|
IsSuccess = function(self)
|
|
return self.success
|
|
end,
|
|
IsFailure = function(self)
|
|
return self.failure
|
|
end,
|
|
IsFinished = function(self)
|
|
return self.hasfinished
|
|
end,
|
|
HasFinished = function(self)
|
|
return self.hasfinished
|
|
end,
|
|
SetSuccessCallback = function(self, val)
|
|
if val == nil then
|
|
val = (function(self) end)
|
|
end
|
|
self.callbackSuccess = val
|
|
end,
|
|
SetFailCallback = function(self, val)
|
|
if val == nil then
|
|
val = (function(self) end)
|
|
end
|
|
self.callbackFail = val
|
|
end,
|
|
SetFailureCallback = function(self, val)
|
|
if val == nil then
|
|
val = (function(self) end)
|
|
end
|
|
self.callbackFail = val
|
|
end,
|
|
SetStopCallback = function(self, val)
|
|
if val == nil then
|
|
val = (function(self) end)
|
|
end
|
|
self.callbackStop = val
|
|
end,
|
|
__tostring = function(self)
|
|
return "[DLib:AStarTracer:" .. tostring(self.ID) .. "]"
|
|
end,
|
|
GetNode = function(self, nav)
|
|
local _list_0 = self.database
|
|
for _index_0 = 1, #_list_0 do
|
|
local data = _list_0[_index_0]
|
|
if data.nav == nav then
|
|
return data
|
|
end
|
|
end
|
|
end,
|
|
AddNode = function(self, node)
|
|
return table.insert(self.database, node)
|
|
end,
|
|
GetPath = function(self)
|
|
return self.points
|
|
end,
|
|
GetPoints = function(self)
|
|
return self.points
|
|
end,
|
|
RecalcPath = function(self)
|
|
if not self.hasfinished then
|
|
return self.points
|
|
end
|
|
if self.failure then
|
|
return self.points
|
|
end
|
|
self.points = {
|
|
self.endPos
|
|
}
|
|
local current = self.lastNode
|
|
while current do
|
|
table.insert(self.points, current:GetPos())
|
|
current = current:GetFrom()
|
|
end
|
|
table.insert(self.points, self.startPos)
|
|
return self.points
|
|
end,
|
|
Stop = function(self)
|
|
if not self.working then
|
|
return
|
|
end
|
|
self.status = self.__class.NAV_STATUS_INTERRUPT
|
|
self.working = false
|
|
self.stop = true
|
|
self.hasfinished = true
|
|
hook.Remove('Think', tostring(self))
|
|
return self:callbackStop()
|
|
end,
|
|
OnSuccess = function(self, node)
|
|
self.status = self.__class.NAV_STATUS_SUCCESS
|
|
self.lastNode = node
|
|
self.working = false
|
|
self.success = true
|
|
self.hasfinished = true
|
|
hook.Remove('Think', tostring(self))
|
|
self:RecalcPath()
|
|
return self:callbackSuccess()
|
|
end,
|
|
OnFailure = function(self, status)
|
|
if status == nil then
|
|
status = self.__class.NAV_STATUS_GENERIC_FAILURE
|
|
end
|
|
self.working = false
|
|
self.failure = true
|
|
self.hasfinished = true
|
|
self.status = status
|
|
hook.Remove('Think', tostring(self))
|
|
return self:callbackFail(status)
|
|
end,
|
|
GetStatus = function(self)
|
|
return self.status
|
|
end,
|
|
Start = function(self)
|
|
self.lastNodeNav = navmesh.Find(self.endPos, 1, 20, 20)[1]
|
|
self.firstNodeNav = navmesh.Find(self.startPos, 1, 20, 20)[1]
|
|
self.status = self.__class.NAV_STATUS_WORKING
|
|
if not self.lastNodeNav or not self.firstNodeNav then
|
|
self:OnFailure(self.__class.NAV_STATUS_FAILURE_NO_OPEN_NODES)
|
|
return
|
|
end
|
|
self.working = true
|
|
self.iterations = 0
|
|
self.totalTime = 0
|
|
local newNode = AStarNode(self.firstNodeNav, self.startPos:DistToSqr(self.firstNodeNav:GetCenter()), self.endPos)
|
|
self.opened = {
|
|
newNode
|
|
}
|
|
self.database = {
|
|
newNode
|
|
}
|
|
return hook.Add('Think', tostring(self), function()
|
|
return self:ThinkHook()
|
|
end)
|
|
end,
|
|
GetNearestNode = function(self)
|
|
local current
|
|
local min
|
|
local index
|
|
for i = 1, #self.opened do
|
|
local data = self.opened[i]
|
|
if not min or data.f < min then
|
|
min = data.f
|
|
current = data
|
|
index = i
|
|
end
|
|
end
|
|
if index then
|
|
table.remove(self.opened, index)
|
|
end
|
|
if current then
|
|
table.insert(self.closed, current)
|
|
end
|
|
return current
|
|
end,
|
|
ThinkHook = function(self)
|
|
local status = xpcall(self.Think, self.__class.OnError, self)
|
|
if not status then
|
|
return self:OnFailure()
|
|
end
|
|
end,
|
|
Think = function(self)
|
|
if not self.working then
|
|
hook.Remove('Think', tostring(self))
|
|
return
|
|
end
|
|
if #self.opened == 0 then
|
|
self:OnFailure(self.__class.NAV_STATUS_FAILURE_NO_OPEN_NODES)
|
|
return
|
|
end
|
|
if #self.opened >= self.nodesLimit then
|
|
self:OnFailure(self.__class.NAV_STATUS_FAILURE_OPEN_NODES_LIMIT)
|
|
return
|
|
end
|
|
local calculationTime = 0
|
|
for i = 1, self.loopsPerIteration do
|
|
local sTime = SysTime()
|
|
self.iterations = self.iterations + 1
|
|
if self.hasLimit and self.iterations > self.limit then
|
|
self:OnFailure(self.__class.NAV_STATUS_FAILURE_LOOPS_LIMIT)
|
|
return
|
|
end
|
|
local nearest = self:GetNearestNode()
|
|
if not nearest then
|
|
break
|
|
end
|
|
if nearest.nav == self.lastNodeNav then
|
|
self:OnSuccess(nearest)
|
|
return
|
|
end
|
|
self.currentG = nearest:GetG()
|
|
local _list_0 = nearest:GetAdjacentAreas()
|
|
for _index_0 = 1, #_list_0 do
|
|
local _continue_0 = false
|
|
repeat
|
|
local node = _list_0[_index_0]
|
|
local hitClosed = false
|
|
local _list_1 = self.closed
|
|
for _index_1 = 1, #_list_1 do
|
|
local cl = _list_1[_index_1]
|
|
if cl.nav == node then
|
|
hitClosed = true
|
|
break
|
|
end
|
|
end
|
|
if hitClosed then
|
|
_continue_0 = true
|
|
break
|
|
end
|
|
local nodeObject = self:GetNode(node)
|
|
if nodeObject then
|
|
local dist = nodeObject:GetPos():DistToSqr(nearest:GetPos())
|
|
local lengthMultiplier = 1
|
|
if nodeObject:Underwater() then
|
|
lengthMultiplier = lengthMultiplier + 20
|
|
end
|
|
local deltaZ = nodeObject:GetPos().z - nearest:GetPos().z
|
|
if deltaZ > 0 then
|
|
lengthMultiplier = lengthMultiplier + math.max(0, deltaZ)
|
|
end
|
|
if deltaZ < 0 then
|
|
lengthMultiplier = lengthMultiplier + math.max(0, -deltaZ)
|
|
end
|
|
local distG = nearest:GetG() + dist * lengthMultiplier
|
|
if nodeObject:GetG() > distG then
|
|
nodeObject:SetG(distG)
|
|
nodeObject:SetFrom(nearest)
|
|
end
|
|
else
|
|
nodeObject = AStarNode(node, nearest:GetG() + node:GetCenter():DistToSqr(nearest:GetPos()), self.endPos)
|
|
nodeObject:SetFrom(nearest)
|
|
self:AddNode(nodeObject)
|
|
if node:IsValid() then
|
|
table.insert(self.opened, nodeObject)
|
|
else
|
|
table.insert(self.closed, nodeObject)
|
|
end
|
|
end
|
|
_continue_0 = true
|
|
until true
|
|
if not _continue_0 then
|
|
break
|
|
end
|
|
end
|
|
local cTime = (SysTime() - sTime) * 1000
|
|
calculationTime = calculationTime + cTime
|
|
self.totalTime = self.totalTime + cTime
|
|
if self.totalTime >= self.timeThersold then
|
|
self:OnFailure(self.__class.NAV_STATUS_FAILURE_TIME_LIMIT)
|
|
return
|
|
end
|
|
if calculationTime >= self.frameThersold then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
}
|
|
_base_0.__index = _base_0
|
|
_class_0 = setmetatable({
|
|
__init = function(self, startPos, endPos, loopsPerIteration, nodesLimit, limit, frameThersold, timeThersold)
|
|
if startPos == nil then
|
|
startPos = Vector(0, 0, 0)
|
|
end
|
|
if endPos == nil then
|
|
endPos = Vector(0, 0, 0)
|
|
end
|
|
if loopsPerIteration == nil then
|
|
loopsPerIteration = NAV_LOOPS_PER_FRAME:GetInt()
|
|
end
|
|
if nodesLimit == nil then
|
|
nodesLimit = NAV_OPEN_LIMIT:GetInt()
|
|
end
|
|
if limit == nil then
|
|
limit = NAV_LIMIT:GetInt()
|
|
end
|
|
if frameThersold == nil then
|
|
frameThersold = FRAME_THERSOLD:GetFloat()
|
|
end
|
|
if timeThersold == nil then
|
|
timeThersold = TIME_LIMIT:GetFloat()
|
|
end
|
|
self.ID = self.__class.nextID
|
|
self.__class.nextID = self.__class.nextID + 1
|
|
self.working = false
|
|
self.hasfinished = false
|
|
self.failure = false
|
|
self.success = false
|
|
self.stop = false
|
|
self.opened = { }
|
|
self.closed = { }
|
|
self.database = { }
|
|
self.points = {
|
|
startPos,
|
|
endPos
|
|
}
|
|
self.startPos = startPos
|
|
self.endPos = endPos
|
|
self.distToEnd = endPos:Distance(startPos)
|
|
self.loopsPerIteration = loopsPerIteration
|
|
self.limit = limit
|
|
self.hasLimit = limit ~= 0
|
|
self.frameThersold = frameThersold
|
|
self.timeThersold = timeThersold
|
|
self.totalTime = 0
|
|
self.iterations = 0
|
|
self.nodesLimit = nodesLimit
|
|
self.callbackFail = function(self) end
|
|
self.callbackSuccess = function(self) end
|
|
self.callbackStop = function(self) end
|
|
self.status = self.__class.NAV_STATUS_IDLE
|
|
self.currentG = 0
|
|
end,
|
|
__base = _base_0,
|
|
__name = "AStarTracer"
|
|
}, {
|
|
__index = _base_0,
|
|
__call = function(cls, ...)
|
|
local _self_0 = setmetatable({}, _base_0)
|
|
cls.__init(_self_0, ...)
|
|
return _self_0
|
|
end
|
|
})
|
|
_base_0.__class = _class_0
|
|
local self = _class_0
|
|
self.nextID = 1
|
|
self.NAV_STATUS_IDLE = -1
|
|
self.NAV_STATUS_SUCCESS = 0
|
|
self.NAV_STATUS_GENERIC_FAILURE = 1
|
|
self.NAV_STATUS_WORKING = 2
|
|
self.NAV_STATUS_FAILURE_TIME_LIMIT = 3
|
|
self.NAV_STATUS_FAILURE_OPEN_NODES_LIMIT = 4
|
|
self.NAV_STATUS_FAILURE_LOOPS_LIMIT = 5
|
|
self.NAV_STATUS_FAILURE_NO_OPEN_NODES = 6
|
|
self.NAV_STATUS_INTERRUPT = 7
|
|
self.OnError = function(err)
|
|
print('[DLib AStarTracer ERROR]: ', err)
|
|
return print(debug.traceback())
|
|
end
|
|
AStarTracer = _class_0
|
|
end
|
|
DLib.AStarTracer = AStarTracer
|
|
DLib.AStarNode = AStarNode
|