Bots

From Fortress Forever Wiki
Jump to navigationJump to search

Intro

Ok, the goal with bot support is not only to provide a full featured bot available to play against normally, but to provide a powerful platform to build training scripts and other aids to help new and veteran players alike get introduced to the features of Team Fortress, Fortress Forever, and the various maps available.

I don't want FF bots to just be another bot. I want them to be an integral part of the players introduction to FF, there to provide the player with the overview of all the games features, classes, and possibly even maps.

Basic Bot Implementation

My bot framework (Omni-bot) is built in a seperate dll and gets fed data from the game in a rather generic format using bitfields to represent various flags/states of the various entities, enumerated events to notify the bot of various game happenings, and an interface framework that exposes functionality common to any bot, such as the ability to draw debug lines, trace lines, get position, velocity, facing of entities, etc.

All the bot files are located in the SourceMods\FortressForever\omni-bot folders and subfolders.

SourceMods\FortressForever\omni-bot - Dynamic Libraries for Windows and Linux

SourceMods\FortressForever\omni-bot\ff\nav - Navigation(waypoint) files and map scripts

SourceMods\FortressForever\omni-bot\ff\scripts - Bot scripts

Also, in the working folder of the game, in this case steam\SteamApps\you@wherever.com\half-life 2, the log file for the bot is created. Various information about initialization can be found in there.

Bot Scripting

Game Monkey Script

Scripting for the bot is done in Game Monkey Script. It has several key features over lua that are extremely useful in the domain of AI programming. Omni-bot utilizes these features extensively in its scripting system.

I recommend anyone that is to be helping script for the bots download the distribution of Game Monkey Script, specifically for gmsrc\doc\GameMonkeyScriptReference.pdf so you have a reference to the types, structure and syntax of it. Programmers should notice a close similarity to C, minus the variable declarations.

SciTE

Also, for convienence, you can download SciTE that I've set up with highlighting and a compiler option for GM script so that you can check syntax in the text editor before running the game. I do all my script editing in this. It also has highlighting and such for lua, so you can use it for that as well if you want. If you have a GM script open you can go to Tools/Build to compile the script with a gm compiler. This will tell you of any syntax errors in the script, but doesn't catch logic errors.

SciTE

Omni-bot Script Reference

Omni-bot Code Documentation Reference

For reference, check out the information under Script Bindings and under TeamFortressLib check out the scripting references there. These areas will likely be frequently updated as I add more functionality to script.

About Game Monkey Script

GM script, like lua is a dynamically typed scripting language, with a syntax close to that of C. GM script is built around the concept of threads. GM threads are conceptually similar to lua co-routines, however threads have several key advantages that make them more useful, easier to use, and more powerful. Every time the bot recieves an update(every server frame), the scripting system is updated, which will update all the currently running script threads. Remember, script threads are NOT real cooperative multi-threading threads, meaning if a badly written script runs an infinite loop, it will freeze the game. This also means the typical multi-threading issues are avoided. There are no deadlocks, race conditions, etc... The threads must explicitly yield control back to the bot in order for execution to continue. There are 3 methods for yielding control back to the bot dll.

Thread control functions

  • yield(); Yield control back to the bot for this frame, the script will run again next frame. Only use this if you need your script to be polled every frame.
  • sleep(seconds); Puts the thread to sleep for the specified number of seconds. At the end of this time the script resumes execution at the line after the call to sleep().
  • block(); The most powerful and optimal way to yield a script. This allows you to block execution of the script on certain events, when one of the specified events are recieved by the bot, it will signal the thread to continue execution.

Every time the bot recieves a team chat message the text is passed to the signal function, if for example that text is ever "what's up bitch", it will cause the blocked thread to resume, and the bot would respond with "Not much douche". Kindof a wierd example, but it demonstrates how multiple script threads can be set up to cooperate with each other with signals. Typically you will block mostly on events.

Every time a script is executed, it is essentially run in a thread. This is normally invisible to the user, but it opens up some interesting options to the scripts that the scripter can take advantage of easily. Consider this example:

death_func = function(_source, _params)
{
	this.Say( GetEntityName(_params.inflictor) + "You killed me bitch!" );	
};
this.Events[EVENT.DEATH] = death_func;

This code sets up a script callback for the DEATH event. Whenever a bot dies, they recieve these events, and the scripts are easily set up to take advantage of these events to do stuff. This is a very simple function, but due to the way GM executes its script in threads, one could easily expand these functions to do stuff over time, such as.

death_func = function(_source, _params)
{
	// wait for a few seconds before we respond
	sleep(3.0);
	this.Say( GetEntityName(_params.inflictor) + "You killed me bitch!" );

	// the bot is pissed now, so keep track of who killed him
	whoKilledMe = GetGameEntityFromId( _params.inflictor );

	// now we're going to keep an eye out for this guy, simple implementation
	while(true)
	{
		if(this.GetTarget() == whoKilledMe)
		{
			this.Say( GetEntityName(whoKilledMe) + ", Revenge is sweet bitch!" );
			break;
		}
		// prevent an infinite loop locking the game, so yield control
		sleep(0.5);
		// could also use yield(); if we only want to yield this frame and run the next frame
	}
};

An example of the usage of block() can be seen in sample.gm, which is shown here, slightly condensed:

this.GoTo(playerEnt.GetEntityPosition());
if( block(EVENT.GOAL_SUCCESS, EVENT.GOAL_FAILED) == EVENT.GOAL_SUCCESS )
{
	// we made it, so do something else.
}

In this example, the script is given 2 events to block on. One of these events will fire in the process of executing the GoTo() function. Any number of events can be passed to block, and they don't have to be events, they can be strings or any other type. This may seem odd, but suppose a script created a thread for a bot like this:

mythread = function()
{
	block("what's up bitch"); // blocks this thread on the specified string
	this.Say("Not much douche");
};
this:thread(mythread); // creates a thread, sends 'this' to the thread so that it will be the 'this' inside the thread function.

then you make an event for the chat message that has.

team_chatmsg_func = function(_source, _params)
{
	this.Signal(_params.msg);
};
this.Events[EVENT.TEAM_CHAT_MSG] = team_chatmsg_func;


The fact that everything runs in its own script thread opens up many possibilities to do things over time like this with very simple scripts. To do this without threads would likely result in more code where an 'update' function would then have to poll the bot. This hides all that complexity.

Another example of serial processing and use of the threading functionality of GM can be seen in sample.gm in the scripts folder. In there is an example script that causes the bot to first wait until it is spawned, and then go into a fully script controlled mode, go to the players location, and when he gets there, attempt to run after the player and stab them with a knife. It should be obvious that the actual script to do such relatively large behaviour is relatively small and high level, with the comments taking up most of the space.

Script Controlled Bots

As demonstrated in the sample.gm, scripts can optionally take complete control over a bot. This is shown on the line:

this.SetScriptControlled(true); // Disables any goal selection, targeting still happens normally, the bot will stand there and shoot at targets it sees.
this.IsScriptControlledWeapons(true); // Disables targeting, bot will ignore enemies.

These functions basically disable internal AI for the bot. The bot will still recieve visual and sound perceptions, which will allow usage of the GetNearest, GetNearestAlly, GetNearestEnemy functions, but any logic in the bot for choosing goals/weapons will be disabled. At this point the bot is fully at the mercy of the script. If the script does nothing, the bot will do nothing. These systems are seperated into 2 functions for flexibility. Often you may want to control a bots navigation while still allowing his targeting to run, while other times you may want to take control of his weapon system to do custom targeting, aiming, looking, etc...

TODO: more info