Created
September 8, 2020 03:57
-
-
Save Elmuti/cd3fff2dad9fa1cc5bfe68c2c03bfd2a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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