Hey devs,
Recently I have been working on a monster AI algorithm that will hunt players and communicate with each other (hive mind). I want some feedback on this and see if it has any issues. Thank you!
(for code that is not built-in luau, what they do should be self-explantory)
local SSS = game:GetService("ServerScriptService")
local RS = game:GetService("ReplicatedStorage")
local CS = game:GetService("CollectionService")
local Players = game:GetService("Players")
local commonModules = require(RS.Modules)
local signal = commonModules.Signal
local serverModules = require(SSS.Modules)
local SimplePath = serverModules.SimplePath
local EntityService = serverModules.EntityService
-->> Variables
local detectionFov_chase = 175
local fovForCalc = math.rad(detectionFov_chase) / 2
local random = math.random
local clamp = math.clamp
local abs = math.abs
local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = CS:GetTagged("RoomBoundingBox")
-->> Communication mechanic registry
local SharedInfo = {}
local function angleBetween(vectorA, vectorB)
return math.deg(math.acos(math.clamp(vectorA:Dot(vectorB), -1, 1)))
end
local function offset()
return Vector3.new(random(-60, 60), 0, random(-60, 60))
end
math.randomseed(math.random(1, 1000))
return function(entity: Model)
-->> Instances
local root = entity.HumanoidRootPart :: BasePart
local head = entity.Head :: BasePart
local subject = EntityService.Register(entity)
-->> Variables
local memoryTime = math.random(15,30)/10
local patrolCooldown = math.random(2,4)
local isPatrolling = false
local nextPatrolTime = 0
local lastPatrol = 0
local targetSnapshots = {}
subject.StartedMoving:Connect(function()
isPatrolling = true
lastPatrol = os.clock()
end)
subject.FinishedMoving:Connect(function()
nextPatrolTime = os.clock() + patrolCooldown
isPatrolling = false
end)
while task.wait(0.06) do
local hasMoved = false
-->> CHASE IF ANY
for i, v in subject:GetPotentialTargets() do
local canSee = subject:CanSeeTarget(v, fovForCalc)
if canSee or targetSnapshots[v] then
if canSee then
-->> Refresh memory
SharedInfo[entity] = {v, true}
targetSnapshots[v] = os.clock()
else
-->> If memory is too old, then forget about it
SharedInfo[entity] = {v, true}
if os.clock() - (targetSnapshots[v] or 0) >= memoryTime then
SharedInfo[entity] = nil
targetSnapshots[v] = nil
end
end
hasMoved = subject:MoveTo(v)
if hasMoved then
break
end
end
end
-->> SEE IF ANY OTHER INDIVIDUAL SEES THE TARGET (HIVE MIND)
if not hasMoved then
for otherEntity, info in pairs(SharedInfo) do
local target = info[1]
local isSeeing = info[2]
if otherEntity == entity then
continue
end
local ICanSee = subject:CanSeeTarget(target, fovForCalc)
if target then
if (otherEntity:GetPivot().Position - root.Position).Magnitude < 100 then
warn("SHARED INFO")
if os.clock() - (targetSnapshots[target] or 0) > memoryTime then
SharedInfo[entity] = nil
targetSnapshots[target] = nil
else
SharedInfo[entity] = {target, isSeeing or ICanSee or false}
if isSeeing or ICanSee then
targetSnapshots[target] = os.clock()
end
end
hasMoved = subject:MoveTo(target)
if hasMoved then
break
end
end
end
end
end
-->> PATROL
if not hasMoved then
if os.clock() - lastPatrol > patrolCooldown and os.clock() > nextPatrolTime and subject.Path.Status == "Idle" then
patrolCooldown = random(2, 4)
-- ALGORITHM:
-- Prefers to move in similar directions + Farther distances
local randomPos = {} do
local rootDir = root.CFrame.LookVector
local rootPos = root.Position
for i = 1, 8 do
-->> Variables
local newPos = root.Position + offset()
local dist = (rootPos - newPos).Magnitude
local angle = angleBetween(rootDir, (newPos - rootPos).Unit)
-->> Randomizers
local distRandom = random() * 2.5
local angleRandom = random()
-- Calculate total score (lower = better)
-- CRITERIAS:
local critA = -dist * distRandom
local critB = math.abs(angle) * angleRandom
local critC = 6 -
(if subject:CanSeeTarget(newPos, fovForCalc) then
random(2,6)
else
random(0, 4))
local critD = next(workspace:GetPartBoundsInBox(CFrame.new(newPos), Vector3.zero, params)) and 0 or 999
-->> FINAL SCORE:
local score = critA * 0.5 + critB * 0.3 + critC * 0.2 + critD
table.insert(randomPos, {position = newPos, score = score})
end
table.sort(randomPos, function(a, b)
return a.score < b.score
end)
for i, data in ipairs(randomPos) do
randomPos[i] = data.position
end
end
for i, v in randomPos do
if subject:MoveTo(v) then
break
end
end
end
end
end
end
Feedback is highly appreicated!