EDIT: Someone already posted with some great info! I will keep this up in case it may help, but their solution will probably be better! 
I’m not a pro scripter, and in fact, I was about to research this myself. However, I can help you the best I’m able to with some tips!!
Please keep in mind: this will not be a perfect set of tips/examples, and will not show the entire process of how you do these things.
Unfortunately, I’ve never looked into parallel lua. So I can’t help here
But some good news… I am very familiar with pathfinding! I’m not totally sure on how to make this work, but something you could research is “How to make all NPCs share a pathfind” or something like that. So the point of this option is to make every NPC you spawn all share a pathfind to the same position. No idea on how to do this, but I’ve heard it is really good for optimization with large numbers, including 100 enemies (from what I know)!
When it comes to making certain states work, that’s actually simpler than you may think! So you can use task.spawn for this! Here is an example of what I do (it’s just cancelling a pathfind to make this possible):
- The part that includes waiting for the pathfind, put it in this:
local pathfindTask = task.spawn(function() end)
. This will continue your code below this task and allow you to cancel the pathfind, and any other code in this task, at any given moment, vs just waiting for the pathfind to finish.
- Let’s go with the enemy heading for cover, like you mentioned in your post. What this does is check for the running task that contains the pathfind and cancels it if the NPC should take cover:
local attack = true
local takeCover = false
local npcHumanoid = script.Parent:WaitForChild("Humanoid") --Or where the npc's humanoid is located
local pathfindTask
local takeCoverTask
--Anything you add above the loop below, make sure it is in a task.spawn, or something else that allows other code below it to run at the same time.
--Make something to check if the npc has taken damage. If it has taken damage, then do this: takeCover = true
--Also, implement a way to make attack = true when the npc has finished taking cover.
--A different loop would probably be more optimized, but for this example, we'll just use a while loop.
while task.wait() do
if attack then
if takeCoverTask then task.cancel(takeCoverTask) takeCoverTask = false end --It's important to check if the task exists. And if it does, then cancel it. For some odd reason, I've always had to make the variable false to check if it no longer exists, despite cancelling it.
if not pathfindTask then
pathfindTask = task.spawn(function()
--Do the pathfind stuff
end)
end
elseif takeCover then
if pathfindTask then task.cancel(pathfindTask) pathfindTask = false end --It's important to check if the task exists. And if it does, then cancel it. For some odd reason, I've always had to make the variable false to check if it no longer exists, despite cancelling it.
if not takeCoverTask then
takeCoverTask = task.spawn(function()
--Do the take cover stuff
end)
end
end
end
I hope this reply helps you! As I can’t really answer your main questions, I apologize for that. Please take this advice, research it, and move forward to create something amazing! I’ll be praying you find a good solution to this post. Also, if you have any questions or need a more in-depth version of the supplied code, let me know and I will write a better one that contains more with pathfinding! It may take me some time to write it though, as I’ll be quite busy in the upcoming days. But if needed, I’ll try and find a moment to write a full version as quick as possible!
Have a blessed day/night, friend!