No categories assigned

AI:BrainTutorialLua

Creating a new brain in lua will allow you to override the default AI, it will not allow you to extend the default AI however. You can change the script and reload it without having to recompile and restart the server however. LUA AI needs to cover every aspect of combat, including putting the NPC into combat. In this tutorial I will show you how to create a lua AI that will duplicate the default AI code in the server core.

Set Up the Spawn Script

First thing you need to do is tell the spawn to use the AI defined in the lua script, this is done by calling SetLuaBrain(), you can also tell the script how often the AI code needs to run with SetBrainTick()

function spawn(NPC)
	SetLuaBrain(NPC)
	SetBrainTick(NPC, 200)
end

You will also want to make sure the brain is set when they respawn

function respawn(NPC)
	spawn(NPC)
end

Finally you need to add the "Think" function, this will be called every time the AI code needs to be run. The server will pass 2 parameters to this function, the first is the NPC this spawn script is attached to, the second is the NPC's current target.

function Think(NPC, Target)

end

We are now ready to program the AI.

Programing The New Brain

The first thing we need to do is check to see if the NPC can do anything, if they are mezzed or stunned then they can't do anything and we can skip the rest of the code. We can check if this is the case by calling IsMezzedOrStunned(), this will return true if the NPC is mezzed or stunned

-- Make sure the NPC is not mezzed or stunned
if not IsMezzedOrStunned(NPC) then

end

If the NPC is not mezzed or stunned the code inside the if will execute, if it is all the code will be skipped. We will now check the NPC's hate list and get the spawn that this NPC hates the most

-- Get the most hated spawn
local mostHated = GetMostHated(NPC)

We will also need to get the distance the spawn is to its run back location, the run back location is set to the position the NPC is at when they are flagged for combat

-- Get runback distance
local runback_distance = GetRunbackDistance(NPC)

Now we should check to see if we got a hate target, if we don't have a hate target we will still do some stuff so we will use an else as well

-- Check to see if we got a mostHated
if mostHated ~= nil then

else
    -- nothing in hate list

end

We will work with the mostHated spawn first, so lets check to see if we have a current target, if we do we will compare it to the mostHated spawn with CompareSpawns(), if the spawns are not the same, or we don't have a target, we want to set the NPC's target to the mostHated spawn

-- Check to see if the target and the most hated are the same
if Target == nil or not CompareNPCs(mostHated, Target) then
		-- Not the same so lets make them the same
		SetTarget(NPC, mostHated)
		FaceTarget(NPC, mostHated)
end

We should now check to see if the NPC is flagged in combat with IsInCombat(), if it isn't we should put it into combat with SetInCombat(), also note that setting the NPC into combat will suspend scripted movement until it is no longer in combat.

-- We have a hate target so lets check if we are already in combat
if not IsInCombat(NPC) then
		-- Not in combat so lets put the NPC in combat
		SetInCombat(NPC, true)
end

Now lets check the runback_distance to see if it exceeds the max distance. First we should define the max distance at the top of the script, we will set it to 80 to match the server core.

local MAX_CHASE_DISTANCE = 80

Back to where we were we can now check the distance, if the distance is greater then the value we define we should clear the hate list with ClearHate() and clear the encounter list with ClearEncounter(), this will bring the NPC out of combat.

-- Check distance to run back location
if runback_distance > MAX_CHASE_DISTANCE then
	-- Clear the hate list
	ClearHate(NPC)
	-- Clear the encounter list
	ClearEncounter(NPC)
else

If we haven't exceeded the max distance we can now do combat stuff, first we should check to see if the NPC is casting a spell with IsCasting(), if not casting we should check to see if it has recovered from casting with HasRecovered() and finally we should try to cast a spell with ProcessSpell() if we failed to cast a spell we should try a melee attack with ProcessMelee(), this will also cause the spawn to move closer to its target if it is outside melee range. We will check all but melee in a single if. We also need to get the distance between two spawns with GetDistance().

else
	-- Get the distance between the spawns for combat calcs
	local distance = GetDistance(NPC, Target, 1)
				
	-- Check if not currently casting and the NPC has not recovered from casting or can cast a spell
	if not IsCasting(NPC) and (not HasRecovered(NPC) or not ProcessSpell(NPC, mostHated, distance)) then
		FaceTarget(NPC, mostHated)
		-- Attempt a melee attack, will also make the NPC move close to its target if it is out of range
		ProcessMelee(NPC, mostHated, distance)
	end
end

And that is it for the "if mostHated ~= nil then", we will now go into the else portion of the code for when there is no one in the hate list. First we should check to see if the NPC is still in combat, if it is we should bring it out of combat. The NPC should be set to full health if it is not the pet of a player. To set the HP to full we can use SetHP() and get the max HP for the NPC with GetMaxHP(). To check if it is a pet we would use IsPet(), if it is a pet we can get the owner with GetOwner() and check if the owner is a player with IsPlayer().

else
	-- nothing in hate list

	-- check to see if the NPC is still flagged in combat
	if IsInCombat(NPC) then
		-- Still in combat, lets take the NPC out of combat
		SetInCombat(NPC, false)

		-- Check if not a pet, or if a pet and the owner is not a player
		if not IsPet(NPC) or not IsPlayer(GetOwner(NPC)) then
			-- Set HP to max
			SetHP(NPC, GetMaxHP(NPC))
		end
	end

Now lets check to see if the NPC has a runback location by checking if runback_distance is greater then 0, if it is we should run back with Runback()

-- Check to see if we have a run back location
if runback_distance > 0 then
	-- We have a run back location so lets go to it
	Runback(NPC)
end

Finally lets check the encounter size with GetEncounterSize() to ensure it is empty, after all if this NPC is not in combat then there should not be any one in its encounter list.

-- Nothing in the hate list so we are not in combat so no one should be in our encounter list
if GetEncounterSize(NPC) > 0 then
	ClearEncounter(NPC)
end

And that is it, this script will replicate the default AI the server core uses for the vast majority of spawns.

Complete Script

local MAX_CHASE_DISTANCE = 80

function Think(NPC, Target)
	-- Make sure the NPC is not mezzed or stunned
	if not IsMezzedOrStunned(NPC) then
		-- Get the most hated spawn
		local mostHated = GetMostHated(NPC)
		
		-- Get runback distance
		local runback_distance = GetRunbackDistance(NPC)
		-- Check to see if we got a mostHated
		if mostHated ~= nil then
			
			-- Check to see if the target and the most hated are the same
			if Target == nil or not CompareNPCs(mostHated, Target) then
				-- Not the same so lets make them the same
				SetTarget(NPC, mostHated)
				FaceTarget(NPC, mostHated)
			end
			
			-- We have a hate target so lets check if we are already in combat
			if not IsInCombat(NPC) then
				-- Not in combat so lets put the NPC in combat
				SetInCombat(NPC, true)
			end
			
			-- Check distance to run back location
			if runback_distance > MAX_CHASE_DISTANCE then
				-- Clear the hate list
				ClearHate(NPC)
				-- Clear the encounter list
				ClearEncounter(NPC)
			else
				-- Get the distance between the spawns for combat calcs
				local distance = GetDistance(NPC, Target, 1)
				
				-- Check if not currently casting and the NPC has not recovered from casting or can cast a spell
				if not IsCasting(NPC) and (not HasRecovered(NPC) or not ProcessSpell(NPC, mostHated, distance)) then
					FaceTarget(NPC, mostHated)
					-- Attempt a melee attack, will also make the NPC move close to its target if it is out of range
					ProcessMelee(NPC, mostHated, distance)
				end
			end
		else
			-- nothing in hate list
			
			-- check to see if the NPC is still flagged in combat
			if IsInCombat(NPC) then
				-- Still in combat, lets take the NPC out of combat
				SetInCombat(NPC, false)
				
				-- Check if not a pet, or if a pet and the owner is not a player
				if not IsPet(NPC) or not IsPlayer(GetOwner(NPC)) then
					-- Set HP to max
					SetHP(NPC, GetMaxHP(NPC))
				end
			end
			
			-- Check to see if we have a run back location
			if runback_distance > 0 then
				-- We have a run back location so lets go to it
				Runback(NPC)
			end
			
			-- Nothing in the hate list so we are not in combat so no one should be in our encounter list
			if GetEncounterSize(NPC) > 0 then
				ClearEncounter(NPC)
			end
			
		end
	end		
		
end

function spawn(NPC)
	SetLuaBrain(NPC)
	SetBrainTick(NPC, 200)
end

function respawn(NPC)
	spawn(NPC)
end