Making An AI With Echo (part 1)From Globulation2ContentsIntroductionEcho is the new subsystem for AI's. While Echo does not replace all of the existing AI system, it does provide a new interface to Glob2, and a large set of tools for making AI's. It can save allot of time, you would understand if you've made an AI without Echo. You should read New AI System to get a general view of how Echo works before proceeding. You can also view nightly generated code documentation (Available from the Developer Center), which includes all of Echo's classes. Getting StartedFirst of all, you have to create your AI files. In glob2/src, you can add AI*.h and AI*.cpp, where * is whatever your AI is called. Next, you need to add copyright statements to your files. Find any other source file, and copy&paste the copyright statements from the top of it to the top of your AI*.h and AI*.cpp files, replacing the name on the copyright statements with your name. Next, open up Makefile.am in glob2/src. You'll notice a list of source files, add your AI*.h and AI*.cpp to their correct position alphabetically. Add in header guards to your header file, and your ready to move on. Now, we need to copy your AI structure over. Open up AIEcho.h, scroll about 3/4 of the way down. Your looking for a class called "EchoAI". When you find it, copy it over to your AI*.h. Remove all of the virtuals and =0's from the functions, and replace EchoAI with the name of your AI. Publicly derive your class from AIEcho::EchoAI, and add #include "AIEcho.h" to the top of your file. Now, implement all of the functions in your AI in the source file, as simple, empty functions (you won't be adding code yet). Finally, we need to make your AI recognized by Glob2. You can use ReachToInfinity as an example Echo AI here. Open up AI.h and AI.cpp. At the top of AI.h, you'll find an enum with all of the AI names in uppercase. Add yours after all of the AI's, but before the "SIZE" variable. In AI.cpp, add the #include "AI*.h" at the top for your AI. Now, you're going to change the AI constructor. You'll see a switch statement at the top of the constructor (which should be at the top of the file). Add in a case statement for your AI. Then, set up a your initializer like the following: aiImplementation=new AIEcho::Echo(new your_ai_name, player); Remember to replace your_ai_name with the name of you're actual AI. Now, scroll down the file further until you see another switch statement in AI::load. Add you're AI to this one as well, with the same initializer as above. Except, this time, after you're initializer, add the following: aiImplementation->load(stream, player, versionMinor); We have one last thing to do, and thats add your AI name to the translations. in glob2/data, you will see a bunch of texts.*.txt files for all of the translations. Open up texts.keys.txt, and scroll down to where the AI's are (you can search for [AI] in your text editor to make this faster). Add your AI name as a key, however, be very carefull to put the key in the exact position that you're AI is in the enum in AI.h. As of now (July 10'th 2006), [AIToubid] is an experiemental AI, so you have to put you're AI's key before his (since his was in the expiremental section of the enum in AI.h). Now, for the tedious bit, you have to add you're AI to each of the translations. You will have to open them up, one by one, and add you're AI name to the same position you added it in texts.keys.txt. Not only do you add your AI name in brackets, you also add it in text. Ignore the fact that you can't translate you're own AI name to every language, its important that atleast the key exists, so when translators do come along, they can translate it. That was allot of work just to make a new AI, however, you'll find its well worth your time. You can compile everything, load the game up, and see your now-empty AI do its work. Its time to start implementing your AI! Its reccomended you add "using namespace " to the top of your source file for all of the namespaces located in AIEcho.h (they are conviently prototyped at the top of the file). Constructing Buildings and FlagsHow toFor starters, you need to make a BuildingOrder. This is an order that will be passed to Echo once you have provided all of the information to construct a building. A BuildingOrder takes two things in its constructor, the building type (available from IntBuildingType.h, remember to do IntBuildingType::SWARM_BUILDING, instead of just SWARM_BUILDING, and the same for other buildings), and the number of workers that will be used to construct the building. After you've created the building order, you now need to add constraints to the order to choose where the building will be located. Constraints are added via BuildingOrder::add_constraint. There are six constraints to choose from:
To use one of the first 4 constraints, however, there is something you must do first. You must set the gradient information for that constraint. Gradient information is set with the GradientInfo class. Gradients are Echo's way of computing the distance to various objects. With a GradientInfo class at hand, you add sources and obstacles for the gradient. A source is something that distance will be counted from. An obstacle is an object that may get in the way of the distance compuation. For example, you want you're building to be placed close to other buildings. However, you don't want direct distance to be counted, because that distance could be going straight through ressources. Instead, you want it to count distance arround the ressources, in which case, you would add an obstacle for the ressources. Here is a list of entities that can count as sources and obstacles:
Be carefull, however. If you use an Source that does not exist on the map anywhere (for example, fruits don't exist on some maps), then the Gradient will fail at every point. If you create an Inn that is positioned relative to Fruit tress andf there are no fruit trees, the Inn will never be created. In the case of fruit, there is a convenient function, Echo::is_fruit_on_map() that does it for you. There is another way you can create a GradientInfo, it is somewhat more convenient. Its called make_gradient_info, and make_gradient_info_obstacle. These two convenience functions take Entities as their arguments and return a GradientInfo using them. make_gradient_info can take one or two entities, both refering to sources. make_gradint_info_obstacle can take one or two sources, and one obstacle. You can obtain your team number from echo.player->team->teamNumber. You can use the functions GradientInfo::add_source and GradientInfo::add_obstacle to add sources and obstacles to your gradient information. When you're finally ready to add your order to echo, use Echo::add_building_order. This function returns an ID that you can use to add ManagementOrders to the function (described later in Managing Buildings). If, however, Echo can't find a place to put your building, this function will return INVALID_BUILDING. Annotated ExampleHeres an example of ordering the cosntruction of a racetrack, with some very specific requirements on the position. //The main order for the race track, to be constructed using 6 workers. BuildingOrder bo(IntBuildingType::WALKSPEED_BUILDING, 6); Here is the same code, except using make_gradient_info. //The main order for the race track, to be constructed using 6 workers. BuildingOrder bo(IntBuildingType::WALKSPEED_BUILDING, 6); Its shorter, but it can also cause for some fairly long lines. You can use which-ever one you prefer. FlagsFlags are created in the same manner as other buildings. However, since flags can be placed on top of other buildings, its sometimes usefull have a MaximumDistance constraint set to 0, causing the flag to be put directly on top of another object. This would not be valid for other buildings. Conditional ConstructionThere are ways for Buildings to be automatically constructed when certain condtions are passed. This is called the condition system. Conditions are described in further detail in the Managing Buildings section. To use conditions with a BuildingOrder, simply use the function BuildingOrder::add_condition in the same manner as you would for a ManagementOrder. This can be used, for example, in chained construction, meaning after one building finishes being constructed, the next building is instantly started. This kind of a system could also be done using the Internal Messaging system. How you do things is up to you.
SummaryWhile constructing a building can be a lengthy ammount of code, you will be happy that your building is placed in a very intelligent location, and that its construction is managed for you. There are other things you can do after the code to order the construction of a building, such as add management orders, which will be explained in the next section. This becomes very convienent and usefull. Managing BuildingsHow toManaging buildings is a somewhat simpler task than constructing them. To manage a building, you first need to create a ManagementOrder. ManagementOrders come in a variety of types, some of them change the number of units assigned to a building, others change the size of a flag. The management orders that operate on a building (all but the last 4) take the buildings id number as their last argument. Here is a list of them:
What makes ManagementOrders so usefull is the fact that they can have **Conditions** attached to them. A condition is attached using ManagementOrder::add_condition. The order will not be executed untill the condition has been satisfied. A prime example of this would be to change the number of units assigned after a building has finished construction. For this, you would attach a NotUnderConstruction condition to the AssignWorkers management order and send it right after you order construction of the building. When the building is done being constructed, the NotUnderConstruction condition will match, and then the AssignWorkers order will follow through.
There are numerous conditions that operate on buildings. These conditions are used to both count and find buildings that match these conditions (explained later), or in this case, they can match when the properties of one particular building meet the requirements. To use them like you would the above conditions, you must wrap them in ParticularBuilding and provide the ID of the building that is to be tested. ParticularBuilding takes the BuildingCondition as its first argument, and the ID of the building to be tested as its second. The building ID is an integer ID, you can get it from the return of Echo::add_building_order for example. Other ways of getting an ID are explained in Obtaining Information.
Any of these conditions can be attached to an Order, but only certain ones (such as NotUnderConstruction) make any sense to do so, because their values can change. Other ones, like SpecificBuildingType, are more for sorting through buildings as explained later, since a building's type doesn't change during its existance. Once you have created your management order, and added any conditions that you desire, its finally time to pass it into Echo. You can use the function Echo::add_management_order to do this. add_management_order takes only one argument, the management order. Some ExamplesHere is an example where you want several things to be done after the construction of a swarm: BuildingOrder* bo = new BuildingOrder(IntBuildingType::SWARM_BUILDING, 3); //....... //Add the building order to the list of orders unsigned int id=echo.add_building_order(bo);
unsigned int enemy_building_id=*ebi; BuildingOrder* bo = new BuildingOrder(IntBuildingType::EXPLORATION_FLAG, 1); bo->add_constraint(new CenterOfBuilding(enemy_building_id)); unsigned int id=echo.add_building_order(bo); SummaryManagementOrders are very important for manipulating the Glob2 universe, and the ability to attach a condition to them makes them much more convienent and usefull.
Proceed to Making An AI With Echo (part 2) | ||