How to make enemies prioritize a certain Humanoid

I’m working on a game where I want the enemies to prioritize attacking a specific humanoid because it represents food they want to eat. However, I also want the enemies to attack nearby players. My challenge is figuring out how to make the enemies focus primarily on the humanoid but still attack players if they’re close.

Does anyone have any ideas on how I would achieve this? Here is the current enemy script that attacks nearby players:

--// SERVICES
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
--------



local humanoid = script.Parent
local root = humanoid.Parent.PrimaryPart
root:SetNetworkOwner(nil)

local targetDistance = script.Parent:GetAttribute("TargetDistance")
local stopDistance = script.Parent:GetAttribute("StopDistance") --Stop moving when close to player
local damage = script.Parent:GetAttribute("Damage")
local attackDistance = script.Parent:GetAttribute("AttackDistance")
local attackWait = script.Parent:GetAttribute("AttackWait")
local lastAttack = tick() --the seconds of time it is since last attack

--//ANIMATIONS
local animator = humanoid:WaitForChild("Animator")
local runAnim = script:WaitForChild("Chase")

local runTrack = animator:LoadAnimation(runAnim)
-------



function FindNearestPlayer()
	local plrs = Players:GetPlayers()
	
	local nearestPlr = nil
	local distance = nil
	local direction = nil
	
	
	for _, player in pairs(plrs) do
		local character = player.Character
		if character then
			local distanceVector = player.Character.HumanoidRootPart.Position - root.Position
			if not nearestPlr then
				nearestPlr = player
				distance = distanceVector.Magnitude
				direction = distanceVector.Unit
			elseif distanceVector.Magnitude < distance then
				nearestPlr = player
				distance = distanceVector.Magnitude
				direction = distanceVector.Unit

			end
		end
		end
		
	
	return nearestPlr, distance, direction
end

RunService.Heartbeat:Connect(function()
	local nearestPlr, distance, direction = FindNearestPlayer()

	if nearestPlr then
		if distance <= targetDistance and distance > stopDistance then
			--runs when moving
			if not runTrack.IsPlaying then
				runTrack:Play()
			end
			
			humanoid:Move(direction)
		else
			--runs when standing still
			if runTrack.IsPlaying then
				runTrack:Stop()
			end
			humanoid:Move(Vector3.new())
		end
		
		if distance <= attackDistance and tick() - lastAttack >= attackWait then
			lastAttack = tick()
			nearestPlr.Character.Humanoid:TakeDamage(damage)
		end
	end
	
	
end)
1 Like

You could implement a tier-priority system like this.

--// SERVICES
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
--------



local humanoid = script.Parent
local root = humanoid.Parent.PrimaryPart
root:SetNetworkOwner(nil)

local targetDistance = script.Parent:GetAttribute("TargetDistance")
local stopDistance = script.Parent:GetAttribute("StopDistance") --Stop moving when close to player
local damage = script.Parent:GetAttribute("Damage")
local attackDistance = script.Parent:GetAttribute("AttackDistance")
local attackWait = script.Parent:GetAttribute("AttackWait")
local lastAttack = tick() --the seconds of time it is since last attack

--//ANIMATIONS
local animator = humanoid:WaitForChild("Animator")
local runAnim = script:WaitForChild("Chase")

local runTrack = animator:LoadAnimation(runAnim)
-------

type Race = {

    Name : string,
    MaxDistance : number,
    Priorities : {[string] : number},

}


local Monster : Race = {

    Name = "Monster",
    MaxDistance = 50,
    Priorities = {

        ["Animal 1"] = 1, -- Lower for higher priority
        ["Lesser Animal 2"] = 2

    }

}

function FindNearestPlayer()
	local plrs = Players:GetPlayers()
	
    local playersnearest = {}

	
	
	for _, player in pairs(plrs) do
		local character = player.Character
		if character then
			local distanceVector = player.Character.HumanoidRootPart.Position - root.Position
            playersnearest[player.Type --[[ You could have a type embedded into either the player or the character. ]] ] = {
                ["Player"] = player, 
                ["DistanceVector"] = distanceVector
            }
		end
	end
		
	
	return playersnearest
end

local function returnHighestPriority(players : any, race : Race)
    local maxdistance, prioritytier = race.MaxDistance, race.Priorities

    local priorities = {}
    
    local priority

    for _, player in players do
        -- The higher the more prioritised it is
        local priorityvalue = prioritytier[player.Type]
        local calc = math.floor((maxdistance - player.DistanceVector.Magnitude) / priorityvalue)
        priorities[player.Player.Name] = player

        priorities[player.Player.Name].Calc = calc
        if priority.Calc > calc then
            priority = priorities[player.Player.Name]
        end
    end

    return priority
end

RunService.Heartbeat:Connect(function()
	local nearest_players = FindNearestPlayer()

    local nearest_player = returnHighestPriority(nearest_players, Monster)

    local distance = nearest_player.DistanceVector.Magnitude
    local direction = nearest_player.DistanceVector.Unit

	if nearest_players then
		if distance <= targetDistance and distance > stopDistance then
			--runs when moving
			if not runTrack.IsPlaying then
				runTrack:Play()
			end
			
			humanoid:Move(direction)
		else
			--runs when standing still
			if runTrack.IsPlaying then
				runTrack:Stop()
			end
			humanoid:Move(Vector3.new())
		end
		
		if distance <= attackDistance and tick() - lastAttack >= attackWait then
			lastAttack = tick()
			nearest_player.Player.Character.Humanoid:TakeDamage(damage)
		end
	end
	
	
end)

Using a tier of priorities, you could just infer based off the calculation where the npc should move next.

The higher the “Calc” value, the more willing the npc is towards going towards the npc.

2 Likes

If you want to prioritize them but also attack nearby players, maybe some sort of weighted chance system like so? This has not been tested but I wrote it, the isSpecial function will make it more likely to choose them over another player if they have whatever trait you’re looking for.

local players = game:GetService("Players")
local root = ...

local isSpecial = function(player) 
	-- handle logic here
end

local generateAvailable = function(info)
	local weights = {}
	for _, data in pairs(info) do
		local transformed = 1 / (1 + (data.distance / 100) ^ 0.5)
		table.insert(weights, {
			data.player,  -- Changed info to data
			transformed + (isSpecial(data.player) and (1 - transformed)/2 or 0)
		})
	end
	return weights
end

local getPlayer = function(playersAvailableToAttack)
	local c = 0
	for i,v in pairs(playersAvailableToAttack) do
		c += playersAvailableToAttack[i][2]
	end
	local chosenValue = Random.new():NextNumber(0,c)
	for i,v in pairs(playersAvailableToAttack) do
		c -= playersAvailableToAttack[i][2]
		if chosenValue > c then
			return playersAvailableToAttack[i][1]
		end
	end
end

local findNearestPlayer = function()
	local plrs = players:GetPlayers()
	local playerDistances = {}
	
	for _, player in pairs(plrs) do
		local character = player.Character
		if character then
			local distanceVector = player.Character.HumanoidRootPart.Position - root.Position
			table.insert(playerDistances,{
				player = player,
				distance = distanceVector.Magnitude,
				direction = distanceVector.Unit
			})
		end
	end

	table.sort(playerDistances,function(a,b)
		return(a.distance < b.distance)
	end)
	
	return getPlayer(generateAvailable(playerDistances))
end

In this, the ‘transformed’ function just takes their distance and makes it the larger it is, the smaller of a chance it’ll choose them basically, then it’ll add basically whatever the difference between that number and 1 divided by 2 is if they have the trait you want, such as:

Player1 50 studs away, 0.2% chance of choosing
Player2 30 studs away, 0.5% chance of choosing

But if say Player1 has the trait you want the enemy to choose, it’ll make Player1 go from 0.2% to 0.6%, 0.2 + (0.8 / 2)

Lowkey not even sure if this would work as expected, but it’s worth a shot I suppose, thought it would be fun.


Disclaimer

Edit: Actually, just realized, if you’re checking every heartbeat, the poor damn enemy would probably run all over the place as a result of this. Maybe sort the chances and return the highest chance?

4 Likes

Hmm ok, I’ll test this to see if it works

1 Like

Ooh ok, I will test this to see how it works

1 Like

I don’t know if I’m doing it right because it’s causing a lot of errors because of the type, I’m not sure how to add types to players.

1 Like

Type was just an example I made up so you could implement it. You could just add a string value named “Type” and set it to Human. I can give you a working example if you’re still struggling.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.