Skip to content

Instantly share code, notes, and snippets.

@Elmuti
Created September 8, 2020 03:57
Show Gist options
  • Save Elmuti/cd3fff2dad9fa1cc5bfe68c2c03bfd2a to your computer and use it in GitHub Desktop.
Save Elmuti/cd3fff2dad9fa1cc5bfe68c2c03bfd2a to your computer and use it in GitHub Desktop.
local char = script.Parent
local Root = char:WaitForChild("HumanoidRootPart")
local map = workspace.CurrentMap
local Abyss = require(game.ReplicatedStorage.Abyss.Main)
local npcLib = Abyss.LoadModule("NpcLib")
local pathLib = Abyss.LoadModule("PathLib")
local assetLib = Abyss.LoadModule("AssetLib")
local physLib = Abyss.LoadModule("PhysLib")
local curStatus = "Wandering"
local BrokenLineOfSightEndChaseTimeout = 1.25
local destReachRange = 1.5
local AttackRange = 4
local losRange = 512
local ai_max_range = math.huge
local maxTimeouts = 3 --the amount of times the NPC can fail to walk to a node
local alwaysChase = true --For testing purposes only
local remotes = game.ReplicatedStorage.Remotes
local timeouts = 0
--TODO: !!!!!!!! ONLY COMPILE NODEGRAPH ONCE PER MAP !!!!!!!
local masterTable, mnt_index = pathLib.CollectNodes(workspace.CurrentMap.Nodes)
local Stats = {
Health = 100;
MaxHealth = 100;
WalkSpeed = 12;
}
char.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
local gyro = Instance.new("BodyGyro", Root)
gyro.D = 1
gyro.P = 400000
gyro.MaxTorque = Vector3.new(0, 4000, 0)
function GetState()
return curStatus
end
function ShouldBeActive()
local target, dist = ChooseTarget()
if target ~= nil then
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
if dist <= 55 and playerSeen then
return true
end
end
return false
end
function RotateTowards(goal)
if gyro ~= nil then
gyro.CFrame = CFrame.new(Root.Position, goal) - Root.Position
end
end
function HasReachedDestination(dest)
local torsoPos = Root.Position - Vector3.new(0, 2, 0)
if (torsoPos - dest.Position).magnitude <= destReachRange then
return true
else
return false
end
end
function ChooseTarget()
local plrs = game.Players:GetPlayers()
if #plrs > 0 then
if plrs[1].Character ~= nil then
local nearest = workspace.Players[plrs[1].Name]
local finalDist = (workspace.Players[plrs[1].Name].Position - Root.Position).magnitude
for p = 2, #plrs do
if plrs[p].Character.Humanoid.Health > 0 then
local dist = (workspace.Players[plrs[p].Name].Position - Root.Position).magnitude
local oldDist = (nearest.Position - Root.Position).magnitude
if dist < oldDist then
nearest = workspace.Players[plrs[p].Name]
finalDist = dist
end
end
end
return nearest, finalDist
end
end
return nil, nil
end
function CheckObstacles(target)
local ray = Ray.new(Root.Position, (target - Root.Position).unit * 2)
local hit, pos = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent, workspace.Ignore})
if hit ~= nil then
local ws = char.Humanoid.WalkSpeed
char.Humanoid.WalkSpeed = 0
wait(0.5)
char.Humanoid.WalkSpeed = ws
end
end
function Pathfind(target, startPos, goalPos)
curStatus = "Pathfinding"
local abortPathfind = false
--GetNearestNode(position, returnBrick, dir, masterTable)
--GetNearestNodeVis(subject, position, visRange, returnBrick, dir, masterTable, ignore)
local startID = pathLib.GetNearestNodeVis(Root, startPos, losRange, false, map.Nodes, masterTable, {char, workspace.NPCs})
local goalID = NEW_GOAL_NODE or pathLib.GetNearestNodeVis(target, goalPos, losRange, false, map.Nodes, masterTable, {char, workspace.NPCs})
local path = pathLib.AStar(masterTable, startID, goalID)
NEW_GOAL_NODE = nil
spawn(function()
while true do
wait(2)
if target ~= nil then
if target.Parent ~= nil then
if target.Parent:FindFirstChild("Humanoid") then
local newGoal = target.Position
local goalnode = pathLib.GetNearestNode(goalPos, false, map.Nodes, masterTable)
if goalnode ~= goalID then
abortPathfind = true
NEW_GOAL_NODE = goalnode
end
end
end
end
end
end)
for nodeNum, node in pairs(path) do
local eta = npcLib.EstimatedPathTime(Root.Position, node.Position, Stats.WalkSpeed, 0.5)
local timeWalked = 0
while true do
if abortPathfind then
--warn("Adjusting new path")
return false
end
Root.Parent.Humanoid:MoveTo(node.Position)
local rotTarget
local nextNode = path[nodeNum + 1]
if nextNode == nil then
rotTarget = node.Position
else
rotTarget = nextNode.Position
end
CheckObstacles(node.Position)
RotateTowards(rotTarget)
local act = wait(1/20)
timeWalked = timeWalked + act
local newTarget = ChooseTarget()
--Reached node, Move to next node
if HasReachedDestination(node) then
--if node.Jump.Value then
--hum.Jump = true
--end
if timeouts > 0 then
timeouts = 0
end
break
end
--Target changed!
if newTarget ~= target and newTarget ~= nil then
--warn("-> Target changed from "..tostring(target).." to "..tostring(newTarget)..", aborting pathfind!")
return false
end
--Timed out! Return false, acquire new path
if timeWalked > eta then
--warn("-> Timed out!")
--char.Humanoid.Jump = true
timeouts = timeouts + 1
if timeouts >= maxTimeouts then
timeouts = 0
warn("NPC stuck at {"..tostring(Root.Position).."} !")
--char.Humanoid.Health = 0
break
end
return false
end
--Got line of sight on target, Stop finding the path and just walk there
local playerSeen = npcLib.LineOfSight(Root, newTarget, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
local cdist = (Root.Position - target.Position).magnitude
if playerSeen and cdist <= losRange then
--warn("-> Got line of sight on target, Stop pathfinding")
return false
end
end
end
return true
end
--[[spawn(function()
while wait(1/10) do
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore})
end
end)]]
spawn(function()
while wait(1/10) do
local target = ChooseTarget()
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
remotes.SendNPCStatus:FireAllClients(curStatus, playerSeen)
char.Head.BillboardGui.TextLabel.Text = curStatus
if curStatus == "Wandering" then
char.Head.BillboardGui.TextLabel.TextColor3 = Color3.new(191/255, 255/255, 161/255)
elseif curStatus == "Pathfinding" then
char.Head.BillboardGui.TextLabel.TextColor3 = Color3.new(167/255, 194/255, 255/255)
elseif curStatus == "Chasing" then
char.Head.BillboardGui.TextLabel.TextColor3 = Color3.new(255/255, 153/255, 153/255)
elseif curStatus == "Attacking" then
char.Head.BillboardGui.TextLabel.TextColor3 = Color3.new(255/255, 50/255, 50/255)
end
end
end)
function Attack(target)
local oldWs = char.Humanoid.WalkSpeed
char.Humanoid.WalkSpeed = 0
curStatus = "Attacking"
wait(1)
char.Humanoid.WalkSpeed = oldWs
end
function Chase(target)
local t = 0
while true do
print("Line of sight acquired, beginning chase")
curStatus = "Chasing"
local dt = wait(1/4)
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
if not playerSeen then
t = t + dt
else
t = 0
end
if t >= BrokenLineOfSightEndChaseTimeout then
print("Line of sight broken, ending chase, beginning pathfind")
break
end
if (Root.Position - target.Position).magnitude <= AttackRange then
Attack(target)
end
Root.Parent.Humanoid:MoveTo(target.Position)
end
end
while true do
if ShouldBeActive() then
local target = ChooseTarget()
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
if playerSeen then
Chase(target)
end
local success = Pathfind(target, Root.Position, target.Position)
if success then
target = ChooseTarget()
local playerSeen = npcLib.LineOfSight(Root, target, losRange, {script.Parent, workspace.Ignore, workspace.NPCs})
if playerSeen then
Chase(target)
end
end
else
--WANDER
curStatus = "Wandering"
local node = pathLib.GetNearestNodeVis(Root, Root.Position, losRange, true, map.Nodes, masterTable, {char})
if node ~= nil then
local walkPos = node.Position + Vector3.new(math.random(-3, 3), 0, math.random(-3, 3))
RotateTowards(walkPos)
Root.Parent.Humanoid:MoveTo(walkPos)
wait(math.random(1, 4))
else
wait(1/2)
end
end
wait(1/20)
end
warn(script.Parent.Name.."- Main AI loop was exited. Was this done on purpose?")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment