-
Notifications
You must be signed in to change notification settings - Fork 5
Developer Guide
This is a long guide for developers to create extensions or to contribute to the source code. I recommend you review the built-in index on the right side and select the section you are interested.
If you are making an extension to this mod, please consider putting in a PR. I will approve most requests as long as a.) the code is well-written and b.) you explain thoroughly what the PR is for. That said, you don't have to be a master to submit one!!!
I will assume you have a basic understanding of GLua or can use Google effectively. This guide will not cover fundamental Lua or GLua concepts.
This is a quick reference for what I mean by various terms referenced in the code and this guide.
A component is an object attached to a bot with a few main functions: :Think
, :Initialize
and :New
. Every tick (typically 5/sec), every bot's component's :Think
function is called on itself. This allows for neat management of data. There is currently no officially supported way of creating this in a separate extension, but you probably won't ever need to.
Some built-in components are:
-
Locomotor
- An extremely important component that controls bot movement and all direct interaction with the game world. -
ObstacleTracker
- A component that tracks the locations of breakable and non-breakable obstacles in the world. -
Chatter
- A component specialized in managing chat messages sent thru bots. The:On
function is particularly useful. -
Inventory
- A component used in managing the bot's inventory, mostly automatically. -
Memory
- Used in allowing bots to remember where they saw/heard something. While not always used, this is still crucial. -
Morality
- Dictates when a bot should fight back against someone else. Particularly necessary for innocent roles. -
Personality
- Gives the bots their own special "flavor." Impacts a large, large variety of things that I won't bother to list comprehensively here.
A behavior is a node of the behavior tree with several key functions. If you are familiar with behavior trees, just know that these are a little special, and they are all designed to be interruptible by default. This can be disabled explicitly, but I wouldn't recommend it.
local Bhvr = {}
-- Return true/false if we can start working on this node (or continue if interruptible)
function Bhvr.Validate(bot) end
-- Return a STATUS enum for our next state
function Bhvr.OnStart(bot) end
-- Return a STATUS enum for our next state
function Bhvr.OnRunning(bot) end
-- This is mostly unused in the base code, but you may find it useful. Only called when SUCCESS is last returned.
function Bhvr.OnSuccess(bot) end
-- This is mostly unused in the base code, but you may find it useful. Only called when FAILURE Is last returned.
function Bhvr.OnFailure(bot) end
-- This is called every time the behavior exits (e.g., either on FAILURE or SUCCESS)
function Bhvr.OnEnd(bot) end
You may be wondering, "what is a 'status?' Running? What does that mean?" Here is how it's defined in the code:
local STATUS = {
RUNNING = 1, -- We can call OnRunning next tick
SUCCESS = 2, -- We are done, and can call OnSuccess and OnEnd
FAILURE = 3, -- We failed, and can call OnFailed and OnEnd
}
In other words, if you return RUNNING
, the bot will continue performing this behavior for the next tick until you return anything else, or your Validate
check fails. If you return SUCCESS
, the tree halts after running your code. On FAILURE
(or a failed validate), the tree continues past your node as if you failed to validate.
This is a very basic data structure that is literally just a table of Behaviors. Here is an example as defined in the base code:
innocent = {
b.ClearBreakables,
b.AttackTarget,
b.Defuse,
b.InvestigateCorpse,
b.FindWeapon,
b.InvestigateNoise,
b.UseHealthStation,
b.Follow,
b.Wander,
}
A behavior tree will call the nodes from top-to-bottom until it reaches a RUNNING
or SUCCESS
return code.
To put it briefly, translations are a pain in the ass. I've tried to make the process as simple as possible for the sake of everyone's sanity. There are two important distinctions between various texts in the mod. "Localized chats" are different in this mod to "localized strings."
A "localized chat" is a string that supports variability on for same event, and has built-in support with the Chatter system. What this means is that the game can randomly select between a range of strings, most commonly used for bots to type in chat. This uses a different system than localized strings, in that it uses substitution like {{variable}}
. Below is an example of what I mean by this. I use this system as it is far more flexible considering the dynamic nature of this kind of text. You do not have to include every possible variable in each string.
RegisterCategory(f("Plan.%s", ATTACKANY), P.CRITICAL) -- When a traitor bot is going to attack a player/bot.
Line("I'm going to attack {{player}}.", A.Default)
Line("I've got {{player}}.", A.Default)
Line("I'll take {{player}}.", A.Default)
-- and so on
You will notice RegisterCategory
-- you must register each event type before you may actually utilize it in the code. You must also register each chat line to a particular Archetype
, a personality category assigned to each bot based on its traits. A bot of the Casual
archetype will utilize any casual lines, if any exist, before using any default lines.
Truthfully, the best way to learn how to do this for yourself is to look at how I did it in sh_chats.lua
. Same with Localized Strings.
The other type, "localized strings," are non-variable bits of text that only support lua text substitution (i.e., %s or %d). They are typically used for GUI and for various warnings and print statements. These are far simpler than chats. Below is a simple definition of the no.kickname
localized string for the French localization:
local function loc(id, content)
local lang = "fr"
TTTBots.Locale.AddLocalizedString(id, content, lang)
end
loc("no.kickname", "Vous devez fournir un nom de bot à expulser.")
Unlike localized chat messages, you do not need to register categories for localized strings.
TODO: write this
TODO: write this
TODO: write this
TODO: write this