User Tools

Site Tools



behaviortree

Behavior Tree Framwork

Behavior Trees are a wonderful thing when it comes to writing any kind of AI logic. It is currently in its “first working version” and there are still improvements coming in the future.

FFXIVMinion is not making use of this, but it *could* be used as well. Instead, it uses the 'older' Cause&Effect framework. Example Code how to build your own addon : newaddon.zip

BT Basics, How they work: Behavior trees for AI

If you want to write your own LUA Addon for GW2Minion, this framework is what you need to use.

Tutorials

These Tutorials will show you how to make your own Lua Addon and create some logic with our Behavior Framework.

1. Creating a new Addon

  • Create a new Folder under MinionApp/Bots/…/ with your addon name (I named mine sillyjumper)
  • Create two empty files: module.def and a lua file with your addon name.
  • Copy paste the code into your module.def:
[Module]
Name=sillyjumper
Dependencies=minionlib,GW2Minion
Version=1
Files=sillyjumper.lua
Enabled=1
  • Name - has to match your foldername
  • Dependencies - need to be set in order to be able to use functions defined by these two other “addons”
  • Version - not used yet
  • Files - a comma seperated list of your lua files that should be loaded. The order is important!.
  • Enabled - take a wild guess

Now open your addon lua file (sillyjumper.lua in my case) in a text editor and add this code (ofc. rename it to your addon name):

-- Your Addon should always be a LOCAL table, else anyone could just steal your code
local sillyjumper = {}
 
 
-- We need to defiune a (callback)-function, which enables the BehaviorTree Manager to load the files from your local addon folder.
function sillyjumper.LoadBehaviorFiles()
 
	-- Load all our local "bot/addon" BTree files
	BehaviorManager:LoadBehaviorFromFolder(GetLuaModsPath()  .. "\\\SillyJumper")
 
end
-- Registering our function for the event that is being called by the BT-Manager to refresh the "internal" list of available Behaviors.
RegisterEventHandler("RefreshBehaviorFiles", sillyjumper.LoadBehaviorFiles)


2. Creating a Behavior Tree

We now need to create a new Behavior Tree for our new addon.


3. Behavior Tree Basics

The Root-BehaviorTree-Node is being called about 4-5 times a second. It always starts from the top and goes downwards through the tree until an Action-Node returns self:fail() / self:running() or self:success().


  • How to add your Addon GUI Elements to the Main Menu
  • How to save and load addon settings

EDIT: Above the lines where you add the Checkbox, you need to make sure the used variable is NOT nil, so put this above:

if (not Settings.sillyjumper.jump ) then Settings.sillyjumper.jump = false end


5. Let's get Moving - Subtrees

  • How to use existing Subtrees
  • What is the global node context and how to use it
  • Accessing game data and functions




6. Making the Addon Private

At this point, you can freely share your addon with anyone else, they just need to copy the folder into their …/LuaMods/.. folder.
But in the case that you want to keep your code private and put your addon into the minionapp store, the next steps are required.

  • Read how private addons are working in general ⇒ Private Addon API
  • Open the addon lua file we created in the 1st Tutorial (sillyjumper.lua) in the text editor.
  • Instead of just loading “all BehaviorTree files” from our folder like before, we will now change this to load only a specific file, our main Behavior Tree:
-- Your Addon should always be a LOCAL table, else anyone could just steal your code
local sillyjumper = {}
-- Aquire the Private Addon Functions, so we can use them later.
sillyjumper.modulefunctions = GetPrivateModuleFunctions()
 
 
-- We need to defiune a (callback)-function, which enables the BehaviorTree Manager to load our files.
function sillyjumper.LoadBehaviorFiles()
 
	-- The BehaviorTree Framework requires specific information in order to use an "external  private BTree":
	local btreeinfo = {
			-- Our Main Behaviortree filename:
			filename = "sillyjumper.bt",
			-- Our Foldername and a mandatory subfolder where our "sillyjumper.bt" will be loaded from:
			filepath = GetLuaModsPath()  .. "\\\SillyJumper\\data",				
			-- Callback function, when the BTree was changed and saved in the BT-Editor. Reload all addon bt files here, including private subtrees.
			Reload = function() sillyjumper.LoadBehaviorFiles() end,				
			-- if set, it will be treated as a private addon, loadable from the addon store
			private = false,
 
		}
 
 
	-- At this point we now have to branch between loading the files as developer (which has the original lua and .bt files in the local folder)
	-- And the user who just has the precompiled .paf file from the Addon Store in his folder.
	if ( FileExists(GetLuaModsPath()  .. "\\\SillyJumper\\\data\\\sillyjumper.bt")) then 
		d("[SillyJumper] - Loading Developer Version")
		-- Loads the BTree data from the locally present .bt file
		sillyjumper.btree = BehaviorManager:LoadBehavior( btreeinfo )					
 
	else
		d("[SillyJumper] - Loading SillyJumper Addon")
 
		-- We need to load the BTree data by ourself from our local .paf and pass that to the LoadBehavior() to create an instance of it.
		-- Get all files in our ..LuaMods\SillyJumper\data\ folder, find our sillyjumper.bt and load that into the BehavioerManager:
		local files = sillyjumper.modulefunctions.GetModuleFiles("data")
		if(table.valid(files)) then
			for _,filedata in pairs(files) do
				if( btreeinfo.filename == filedata.f) then
					local fileString = sillyjumper.modulefunctions.ReadModuleFile(filedata)
					if(fileString) then						
						local fileFunction, errorMessage = loadstring(fileString)
						if (fileFunction) then
							btreeinfo.data = fileFunction()					
						end
					end
					break
				end
			end
		end
 
		if (table.valid(btreeinfo.data)) then
			sillyjumper.btree = BehaviorManager:LoadBehavior( btreeinfo )
		end
	end
 
	-- At this point we now have a local instance of our BehaviorTree => sillyjumper.btree
	if ( not table.valid(sillyjumper.btree)) then
		ml_error("[SillyJumper] - Failed to load SillyJumper behaviortree")
	end	
 
end
-- Registering our function for the event that is being called by the BT-Manager to refresh its intern list of available Behaviors.
RegisterEventHandler("RefreshBehaviorFiles", sillyjumper.LoadBehaviorFiles)
  • Don't forget to move your .bt file into the mandatory “data” folder (you can use any other folder name ofc, but your code needs to be changed as well then):

  • Now reload your lua modules ingame and you should see that your Addon is loading:

  • Your Addon is now secured and if distributed through the MinionApp-Store, noone is able to read or steal your code.


7. Extending Node Context

As you learned earlier, all BehaviorTree Nodes are sharing a “context” table which can be accessed at any time by all Nodes.
If you have large functions or code parts which are often reused and would clutter the Nodes, you can just move the code into a lua file.
We create a custom “context” table, define the functions and pass that to the BehaviorTree. All Nodes can then access the functions / code.

  • Add the following changes to your code:
  1. Create the sillyjumper.btreecontext = {} table
  2. Extend the local btreeinfo with the new “LoadContext = function() return sillyjumper.GetContext() end,” function
-- Your Addon should always be a LOCAL table, else anyone could just steal your code
local sillyjumper = {}
-- Aquire the Private Addon Functions, so we can use them later.
sillyjumper.modulefunctions = GetPrivateModuleFunctions()
 
-- Our custom context which will hold code and functions and make these available inside the BT-Nodes
sillyjumper.btreecontext = {}
 
 
-- We need to defiune a (callback)-function, which enables the BehaviorTree Manager to load our files.
function sillyjumper.LoadBehaviorFiles()
 
	-- The BehaviorTree Framework requires specific information in order to use an "external  private BTree":
	local btreeinfo = {
			-- Our Main Behaviortree filename:
			filename = "sillyjumper.bt",
			-- Our Foldername and a mandatory subfolder where our "sillyjumper.bt" will be loaded from:
			filepath = GetLuaModsPath()  .. "\\\SillyJumper\\data",				
			-- Callback function, when the BTree was changed and saved in the BT-Editor. Reload all addon bt files here, including private subtrees.
			Reload = function() sillyjumper.LoadBehaviorFiles() end,				
			-- if set, it will be treated as a private addon, loadable from the addon store
			private = false,
 
                        -- (optional) Callback function, is called by the BTree Framework when the BTree is started. Allows us to supply a custom "context" table to the BTree:
			LoadContext = function() return sillyjumper.GetContext() end,
 
                        -- (optional) Function to draw the menu elements that always appears on the "Main menu" interface.
                        DrawMenuCode = function(btree) end,
 
                        -- (optional) Function to draw the menu elements that appears on the "Main menu" interface when the btree is selected.
                        -- This locks the edit field inside the btree editor.
                        DrawMainMenuCode = function(btree) end,
 
                        -- (optional) Function to draw the menu elements that appears on the subtree node. This is also used for drawing custom properties in the taskmanager.
                        -- This locks the edit field inside the btree editor.
                        DrawSubMenuCode = function(btree) end
		}
  • Now lets create the Callback function in your lua file where your custom “context” table is being created and all your functions defined.
  • Additionally I'll create a simple test function which will then be made available in the “context” table.
-- Create a function which will then be usable inside the BT-Nodes
sillyjumper.LetsJump = function()
	Player:Jump()
end
 
-- Is called when the BTree is started. Allows us to supply a custom context table to the BTree
function sillyjumper.GetContext()
	sillyjumper.btreecontext.Jump = sillyjumper.LetsJump
 
	return sillyjumper.btreecontext
end
  • And now you can access this function in any node in your BTree with context.Jump()


8. Creating Private SubTrees

In order to create and use a private subtree, you will need to extend the loading code again by a new function:

function sillyjumper.LoadBehaviorFiles()
 
	-- The BehaviorTree Framework requires specific information in order to use an "external  private BTree":
	local btreeinfo = {
			-- Our Main Behaviortree filename:
			filename = "sillyjumper.bt",
			-- Our Foldername and a mandatory subfolder where our "sillyjumper.bt" will be loaded from:
			filepath = GetLuaModsPath()  .. "\\\SillyJumper\\data",				
			-- Callback function, when the BTree was changed and saved in the BT-Editor. Reload all addon bt files here, including private subtrees.
			Reload = function() sillyjumper.LoadBehaviorFiles() end,				
			-- if set, it will be treated as a private addon, loadable from the addon store
			private = false,
			-- (optional) Callback function, is called by the BTree Framework when the BTree is started. Allows us to supply a custom "context" table to the BTree:
			LoadContext = function() return sillyjumper.GetContext() end,
 
			-- Required for private addons with additional private sub-behavior trees
			LoadSubTree = function(filename) return sillyjumper.LoadSubtreeData(filename) end,
		}
  • And then define this new Callback function (in my case, sillyjumper.LoadSubtreeData(filename)). This will load the requested file from your private addon folder.
-- Required for private addons with additional private sub-behavior trees
function sillyjumper.LoadSubtreeData(filename)
	if ( FileExists(GetLuaModsPath()  .. "\\SillyJumper\\data\\"..filename)) then 
		return FileLoad(GetLuaModsPath()  .. "\\SillyJumper\\data\\"..filename)
 
	else
 
		local files = sillyjumper.modulefunctions.GetModuleFiles()
		if(table.valid(files)) then
			for _,filedata in pairs(files) do
				if( btreeinfo.filename == filedata.f) then
					local fileString = sillyjumper.modulefunctions.ReadModuleFile(filedata)
					if(fileString) then
						local fileFunction, errorMessage = loadstring(fileString)
						if (fileFunction) then
							return fileFunction()
						end
					end
					break
				end
			end
		end
	end
end
  • You should now be all setup for creating and loading private SubTrees and building your own Addon.


9. Creating a private Subtree

  • Todo …it works, but this is a larger time consuming part I'll do when I have time :D

10. Common Errors and Infos

  • You ALWAYS need to be aware that if you save for example a “target”, which is a metatable and directly linked to the game memory, in a context-table variable or anywhere else, you cannot access or use that in a later call / pulse of the Behaviortree. You always need to make sure that the metatable you try to access still exists, else you will experiement the weirdest crashes.
  • You ALWAYS need to return a self:success(), self:fail() or self:running() in every if-else-end case inside your action nodes. If you miss even one case, your whole Tree will not work.
  • You can access each node's data by using self.settings, self.variables, self.info and ofcourse through the shared context table.
  • There are additional BehaviorManager functions that might be helpful to you:

11. BTreeinfo

local btreeinfo = {
	-- Our Main Behaviortree filename:
	filename = string,
 
	-- Our Foldername and a mandatory subfolder where our btree will be loaded from:
	filepath = string,
 
	-- Callback function, when the BTree was changed and saved in the BT-Editor. Reload all addon bt files here, including private subtrees.
	Reload = function() end,
 
	-- if set, it will be treated as a private addon, loadable from the addon store
	private = false,
 
	-- if set, the btree will not appear in the botmode dropdown
        internal = false,
 
        -- Preloaded task data. Important when using a private addon.
        data = table,
 
	-- (optional) Callback function, is called by the BTree Framework when the BTree is started. Allows us to supply a custom "context" table to the BTree:
	LoadContext = function() end,
 
	-- (optional) Function to draw the menu elements that always appears on the "Main menu" interface. (Requires internal)
	DrawMenuCode = function(btree) end,
 
	-- (optional) Function to draw the menu elements that appears on the "Main menu" interface when the btree is selected.
	-- This locks the edit field inside the btree editor.
	DrawMainMenuCode = function(btree) end,
 
	-- (optional) Function to draw the menu elements that appears on the subtree node. This is also used for drawing custom properties in the taskmanager.
	-- This locks the edit field inside the btree editor.
	DrawSubMenuCode = function(btree) end,
 
	-- Required for private addons with additional private sub-behavior trees
	LoadSubTree = function(filename) end,
 
}

12. BehaviorManager functions

BehaviorManager:ToggleMenu()
BehaviorManager:Ready()
BehaviorManager:Running()
BehaviorManager:Start()
BehaviorManager:Stop()
BehaviorManager:GetTicksThreshold()  -- How often (in ms) the BTree is being called.
BehaviorManager:SetTicksThreshold(milliseconds)
BehaviorManager:GetLastTick()
BehaviorManager:SetLastTick(ticks)
BehaviorManager:LoadBehaviorFromFolder(folderpath)
BehaviorManager:LoadBehavior(btreeinfo)
BehaviorManager:LoadSubBehavior(btreeinfo)
BehaviorManager:Paused(p)
BehaviorManager:CurrentBTreeName()
BehaviorManager:SetBtreeFile(botmode_name)
behaviortree.txt · Last modified: 2022/07/18 09:27 by fxfire