Here I will demonstrate loosely the structure of NTai as it currently stands (XE9). I’ll explain the basic logic, and provide UML diagrams of the critical object interfaces within the AI and how they interact at an abstract level.
Base Object Structure Principles
NTai re-factors have been moving towards a more object oriented system. Most objects now implement the IModule interface, which provides a set method of initialising an object and a common data type to deal with AI objects that have logic. Methods for events are required to be implemented, and events are passed around using CMessage objects.
All objects in NTai are linked to the CGlobal class in some manner, and the instance of CGlobal is representative of the entire AI. This class takes events passed from the Spring Engine and translates them into NTai CMessage objects. It holds all the objects in the AI and distributes these messages. It provides pointers to some common ly used objects, most of which are utilitarian or are related to management of the engine AI interface. An example of this being the COrderRouter class, which caches commands to be sent to the engine as too many commands can overflow the network buffer as well as cause slowdown.
Memory Management & Discarding Resources
IModule objects all have methods and member variables indicating their validity. Invalid objects do not receive events, and an object can declare itself as invalid at the end of its life, or upon a fatal error or invalid input, such as a null pointer in a constructor parameter.
When an object declares itself as invalid, and its life is ended, the CGlobal class will routinely check the list of objects it manages. Any objects that are found to be invalid are deleted and discarded on the next frame. This is because if they were discarded on the current frame it could cause issues with objects executing logic upon the death of that object attempting to refer to it while doing housekeeping. It also allows the destruction of objects to be staggered and throttled under periods of stressed performance.
Thus for the moment, objects may be created anywhere, however, destruction of objects implementing IModule are the responsibility of the CGlobal class, and determining when an objects life has ended and destruction is required, is normally the objects own responsibility or that of its creator should invalidation occur in the scope of its creation.
Objects not implementing IModule are the responsibility of their parent object.
The 3 Main unit Handling Systems
Units in NTai are handled by three major systems depending on their role:
There are a multitude of other mechanisms and systems that compliment this triumvirate, but I shall mention those elsewhere. It may be of note that as of versions after XE9, scouting was disabled in favour of a map hack ( NTai can see the whole map regardless of Line of sight/fog of war ). This disabling of scouting was due to a major refactor of certain areas of the AI, requiring a rewrite of the scouting code I did not have time for.
In older versions of NTai, construction management was performed by a superclass called CFactor. This was later rewritten to become CManufacturer. In latest development however the concept of task lists and task management has been divorced from construction and applied in a more generic manner, as a general unit task system.
In classic NTai, a unit was handled by one and only one of the three systems, or not at all as was commonly the case with structures that were not factories. Scouters and attackers would be detected based on an algorithm involving speed, but it was recommended that you define a list of unit types that were attackers and one for scouters, so that no problems arose with auto-detection.
It should be noted that while the scouting and attacking classes are old, the attacking class was retrofitted to implement and use the IModule interface, whereas the associated refactor of event management meant the scouting class required heavy modification and rewriting. The attack group class CChaser however still has a pointer in CGlobal for compatibility purposes despite being ported to the new object oriented management system.
Other AI developers relied mostly on influence mapping or randomness to decide where to scout, or implemented a map hack. I decided I would instead take a hybrid approach. To this end I implemented four different algorithms.
- Random locations on the map
I would randomly choose somewhere on the map within a quadrant. This way the scouter would move somewhere randomly, yet they would always move a meaningful distance.
- Resource points
Since to win the game one must have resources, I inferred that the most likely places on the map to contain enemy units, would be resource points, aswell as strategic positions to be aware of. Thus the first algorithm was modified to send scouters to random resource points rather than locations. This had the added benefit of armed scouters attacking enemy resource extractors
- Influence Maps
An influence map containing the number of frames since a position was last sighted was kept up to date. Scouters running under this algorithm would move towards the highest value within a certain range.
- Starting Positions
Maps define several starting locations for players. Although these locations are not always used, these positions normally have a collection of starting resources, and open space to build a base, and are in strategic positions. Thus within the first few minutes of the game, scouters could be assigned a fourth algorithm that made them randomly visit these locations. This algorithm was disabled because changes in the AI interface meant the positions could not be read.
It is my intention to re-enable scouting at some point in the near future, and remove the map hack. Scouting may make it easier to beat the AI but it makes for a more realistic playing style, and allows various assumptions. Code for scouting can also be applied in reverse for predictive purposes.
Attack & Assault
When a units construction was finished, if that unit was classed as an assault unit, it would be picked up by a class referred to as CChaser. This class would flag the unit in an array by its ID and then collect a list fo these IDs until a group of attackers of sufficient threat/power had been amassed.
When this occurred, the attack group would be formed and a new temporary group would be created. A target would be assigned to the attack group, and the units would commence navigating to the target position. The position of the target would be determined using another system that has an influence map at its core, referred to mostly as the threat matrix.
Every frame the values of this map would depreciate. Twice a second the values of all visible enemy units would be calculated and added to the respective map square, increasing the threat at that location. The same would be done for allied units, however their values would be subtracted, negating that of any present enemy units. A target would be chosen by multiplying the array values by their distance from the starting position, and choosing the square with the highest resulting value. Targets that had been acquired in soem versions of NTai also had a halving or some other reduction of threat in order to prevent multiple groups merging into one pseudo attack group, with all having the same targets.
A slew of other simple behaviours were implemented in this system, such as attacking nearby units, and some flocking algorithms to prevent unit groups becoming too fragmented.
Since NTai is a configuration based AI at its core, lists of keywords telling it the unit names of what to build drove early versions. This was originally limited to construction units, however, more generic task keywords were added, implementing algorithms and behaviours that were dependant on the current context and circumstances. An example being the b_rule_extreme keyword which only ran under exceptional economic circumstances, in order to stabilize swings by building energy in low energy conditions, or preventing resources being wasted when storage was full by building factories.
Originally task management was in yet another monolithic superclass or two. In the last year (2009) however, I have re-factored this area of NTai heavily. Now units have an associated CUnit object implementing IModule, which is held in the CGlobal class along with all other logic classes implementing IModule for event distribution.
I intend that this newly re-factored architecture that replaced construction be used to replace the other two systems, merging all three into an object oriented orderly yet flexible system.
The CUnit object also has an ITaskManager object, which is onbtained from an object implementing ITaskManagerfactory. This object implementing ITaskManager handles the task list. It only has one current implementation, to read from the configuration data and generate tasks from a randomly selected list, but it could be implemented in various other ways.
The ITaskManager object deals in task objects which are objects implementing the IModule interface. When a task is completed, it flags itself as an invalid object. The CUnit object then realises the task object is invalid on the next update and asks the ITaskManager object for the next task, forgetting about the task object in the process. The CGlobal class will then delete and discard the task object.
In addition to tasks, an IBehaviour interface is present. An IBehaviourManager interface is also planned, and the CUnit object refers to these also. Several behaviours that were present in the 2 other major systems were re-implemented as objects implementing the IBehaviour interface. These include kamikaze behaviour, d-gun logic, metal maker management and several others.
It is intended that smart unti behaviours be implemented this way, rather than various classes interfering with a units behaviour with little regard for what other classes are doing. I also envisage that scouting would be re-implemented using this mechanism, and that attacking would also act via this mechanism.
Behaviours can take control of the unit and disrupt or insert tasks into the current queue, although I have not strictly defined the rules behind how behaviours and tasks interact yet.
Other Objects and Systems
Most other classes are utilitarian in nature, or track information such as the CEfficiency class which manages unit type efficiency values and the saving and loading of their values. It also includes the algorithms that are executed when units are killed that modify the efficiency values.
Most of these objects implement the IModule interface, and those that do not are intended to eventually do so or be obsoleted.