Adding new GameBots message and commands (UnrealScript)

GameBots messages are text strings exported from Unreal Tournament through socket connection. GameBots commands are text strings sent from Pogamut to Unreal Tournament. Here we will provide you with some basic information on how to create a new GameBots message or command. Afterwards you may also want to check the topic: Adding new GameBots message to Pogamut JAVA.

This article was written for Pogamut 3.2.5 with UDK in mind. But the general idea is applicable to UT2004 as well. This article was written by Martin Černý and Michal Bída.

To edit UnrealScript it is best to use nFringe + Visual Studio - see udk_development_tutorial for installation instructions.

What to modify

There are two possible scenarios:

  1. Adding a new command/message to the Gamebots core.
  2. Adding a new project-specific command message without modifying the Gamebots core (currently fully supported only for Pogamut UDK)

Messages in general

All GameBots messages are sent out of Unreal Tournament through GameBots function SendLine(string text). This function is defined in GBClientClass.uc. This means that when you want to export any new message from GameBots, your class will have to have acces to function SendLine (through inheritance or reference). Once you have acces to SendLine function, you can start sending text messages. In GameBots, it's important, that all messages share common text format. This then allows correct parsing. We will show you how to create correct format of message in GameBots on a simple example.

"TIMEDATE {Time " $ Level.TimeSeconds $ "} {Date " $ Level.Date $ "}"

The character $ is operator, that is used in UnrealScript to connect two string variables.

In GameBots code the line above would be written as follows (in function SendLine()):

SendLine("TIMEDATE {Time " $ Level.TimeSeconds $ "} {Date " $ Level.Date $ "}"); 

Note that it is important to minimize number of $ operators as string concatenation is rather expensive in UnrealScript.

Now, how we would parse such a message from example above. We got a message with Id “TIMEDATE”, which has two attributes - “Time” and “Date”. Value of attribute “Time” is Level.TimeSeconds and value of attribute “Date” is Level.Date. The types of Level.TimeSeconds and Level.Date will be automatically converted to string format. For correct parsing you will have to know, what type your parameters are to convert them correctly.

So the universal GameBots message could look like this:

SendLine("MESSAGE_ID {Attribute1_Name " $ Attribute1_Value $ "} {Attribute2_Name " $ Attribute2_Value $ "} {Attribute3_Name " $ Attribute3_Value $ "}"); ... etc. (with a limit of maximum string length in UnrealScript)  

For more information about GameBots text format see GameBots user documentation at Pogamut3 documentation page.

To summarize, to create new GameBots message, you need to have access to SendLine() function and of course, there has to be a working connection to the GameBots server. Then you can write some code somewhere, that will export some information through SendLine() function in a correct format.

Messages from Kismet Actions

One easy and useful way to send messages is to set up your UDK levels to fire a custom kismet action, that sends message to Pogamut. First Google a little bit for “UDK custom kismet action” to get hang of what it means. Than you create the action. Example action class code follows:

class SeqAction_MessageAction extends SequenceAction;

event Activated()
{
	class'YourConnectionClass'.static.SendNotifyMessage();	
}

Only thing it does is calling a static method of your connection class (if you are extending GameBots core, it will be BotConnection, otherwise somethink else :-) . See below.)

And here is a code for the method in connection class:

static function SendNotifyMessage(){
	message = "MYMESSAGE {StringParam bagr}";

        SendMessageToAll("MYMESSAGE {StringParam bagr}");
        SendMessageToAllBots("BOTMESSAGE {IntParam 23}");
        SendMessageToAllControlConnections("CONTROLMESSAGE {FloatParam 1.5}");

}

This method sends the one message to all bot and control server connections, another only to all bots and yet another only to control connections. Note that while this method is quite simple it is good practice not to embed this in your action code, but keep it in the connection class so that the vocabulary of gamebots commands and messages is not scattered in many classes.

Of course you might want to have a message that is sent for example only to the bot that triggered something. Than you need to get the bot object from the event and use it's connection.

Receiving commands from Pogamut

Except for special messages (handshake with server, password), the message handling is done in BotConnection.ProcessRegularAction method. If you are extending Gamebots core you write a handler function and add another case to the switch statement in this method. If you are adding project specific command, you should subclass BotConnection and override this method in your subclass and delegate parsing to super implementation if you cannot recognize the message. Example of such an overriden method follows:

function ProcessRegularAction(string cmdType)
{
	switch(cmdType){
			/* This is our custom command name*/
			case "CHANGEDOOR":
				ReceivedChangeDoorState(); //call handler method
			break;
		default:
			super.ProcessRegularAction(cmdType);
	}
 
}

In your handler method, you are free to use method GetArgVal(argName) to parse command arguments (it is automagically filled with arguments from most recent command.

If you want to handle commands for the control connection, the process is the same, but use ControlConnection instead of BotConnection.

Triggering Kismet event with a command

One nice thing to do with a message is to fire a custom Kismet event when a command is received. Here is an example of such a handler function (from SpyVsSpy project).

function ReceivedChangeDoorState(){
    local Sequence GameSeq;
    local array<SequenceObject> AllDoorEvents;
    local array<int> ActivateIndices;
    local int i;
	local string doorFrom;
	local string doorTo;
	local bool open;
	local SeqEvent_ExternalDoorStateChange doorChangeEvent;
 
	doorFrom = GetArgVal("DoorFrom");
	doorTo = GetArgVal("DoorTo");
	open = bool(GetArgVal("Open"));
 
 
 
    GameSeq = WorldInfo.GetGameSequence();
    if (GameSeq != None)
    {
 
        // find all instance of our event
        GameSeq.FindSeqObjectsByClass(class'SeqEvent_ExternalDoorStateChange', true, AllDoorEvents);
 
        //choose the right activation link number
        if(open){
                ActivateIndices[0] = 0; 
        } else {
                ActivateIndices[0] = 1;
        }
        for (i = 0; i < AllDoorEvents.Length; i++)
        {
			doorChangeEvent = SeqEvent_ExternalDoorStateChange(AllDoorEvents[i]); //convert to appropriate class
                        //Check custom activation condition
			if( (doorChangeEvent.DoorFrom == doorFrom && doorChangeEvent.DoorTo == doorTo) 
				|| (doorChangeEvent.DoorFrom == doorTo && doorChangeEvent.DoorTo == doorFrom)
				){
                                 //trigger the event
				doorChangeEvent.CheckActivate(WorldInfo, None, false, ActivateIndices);
			}
        }
    }
}

Here we have a custom kismet event with two output links. This method gets all event object of appropriate class, performs custom checking, whether the message should trigger the event and then triggers the appropriate activation link on them.

Adding project-specific messages and commands

This part expects that you are familiar with gamebots_maven and udk_extension_packages.

As noted above, if you are adding project specific messages and/or commands, you should subclass BotConnection and add your handling code there. You should then put your connection subclass and other file into a freshly created udk extension package. Now the question is, how to force GameBots to use my connection instead of the default one.

Well this is not as difficult as it may seem. The only thing that you need is to overwrite a file called 'DefaultBotServer.ini' in UDK/UDKGame/Config by your UDK extension package and specify your class there. Example contents of DefaulBotServer.ini follows:

[GameBotsUDK.BotServer]
MaxConnections=32
ConnectionClass="GameBotsUDK.SpyVsSpyBotConnection" 

The ConnectionClass is a string containing fully qualified name of the connection class. If you are not trying to achieve something complicated, it is usually good idea to add your extension classes to GameBotsUDK package, since it does not require to modify any other ini file (adding a package requires you to modify some of the files that are modified by gamebots, so that could lead to some trouble with synchronizing your extension development with the main gamebots extension).

If you have done that, try running UDK with GameBots as usual you should see “Using YOURCLASS for bot connection” in the UDK log. If you are not, something is wrong.

In a similar way you can replace the default implementation of ControlConnection to parse custom messages. You extend the ControlConnection class, override ProcessRegularAction there, create file called DefaultControlServer.ini and specify your class's name in the ConnectionClass property.