The AI Development Kit

This is the documentation of the AI development kit that comes with the C-evo standard package. The kit is meant to be a starting point for developing your own AI module for C-evo. Please, always consider that the whole C-evo project is still in the course of development, which counts for this text as well. If you have ideas how to improve the content or the structure of this document, or in case you notice any problem or have a question, contact me! There's also a web forum for C-evo AI developers.

The programming language of this kit is Pascal. In order to work with it, you need a Pascal compiler capable of generating Win32-DLLs, for example:

Note that using this kit is not the only way to make an AI for C-evo. You can alternatively build an AI DLL from the scratch using your programming language of choice. The DLL interface documentation is to be found here. There is also another, completely different AI template (using C++) available from the files section of the project homepage.

Important

Trying to program a C-evo AI makes no sense if you don't have good knowledge in two fields:


Files of the Kit


Debug and Release Version

The AI can be compiled in a debug or in a release version. The debug version does no optimization and adds several checks. It should be used during development. The release version of the AI contains only what is necessary for playing with it, thus being smaller and faster. The release version is the one that you should publish.

In Delphi, switch between debug and release by the Optimization checkbox in the project settings: Optimization means release, no optimization means debug. In FreePascal, make the debug version by defining the symbol DEBUG (for example, by passing the command line option -dDEBUG to the compiler). Without this symbol, FreePascal compiles the release version. (Unfortunately, this is only theory since FreePascal doesn't seem to generate a valid DLL with assertions enabled, so you have to debug the AI in the release version.)

Concepts

First, you have to say goodbye to something that you're used to from playing the game: Names. For example, you can't define or even find out the names of your cities. The same counts for the names of your unit classes, not to speak of their pictures. This is because the game core does not support names. All objects are indentified by numbers only. Names and pictures are generated by the user interface in its own estimation, because humans like playing with names and pictures more than with numbers. AI likes numbers more, so be happy that you're spared by the names...

The source code files listed above are not meant to be edited. You're doing the AI implementation by creating a new unit AI.pas with a new class TAI derived from TCustomAI. (The names are fixed because AIProject.dpr relies on them.) Your class TAI has nothing to do but to override virtual methods of the base class by own implementations. You can choose which methods to override. Even if your class doesn't implement any method, the compiled AI works. (But, of course, does almost nothing.)

Each nation has its own AI object, so inside the TCustomAI and TAI classes it's all about only one nation. The whole AI module is capable of controlling more than one nation, but this is implemented by the kit infrastructure, you don't have to care for it.

Apart from that, the code is not object oriented. Units, unit classes (called models), cities and other items are not modelled as Object Pascal classes. Instead, the items of the game are identified by numbers (always zero-based) and managed in arrays of records.

Playground Coordinates

You have to deal with three types of playground coordinates: location codes, (a,b)-coordinates and vicinity codes. A location code is a unique integer code for a tile. For example, you can use it to find the city that is located at a certain tile, or to find all units which are located inside a certain city (because they have the same location code as the city). All location codes in the range 0..MapSize-1 are valid, all other location codes are invalid. Whenever there is a field or parameter that refers to a single tile resp. location in the communication with the game, it contains a location code.

The other coordinate types are relative, they always relate to a base tile. This base tile can be chosen freely. (a,b)-coordinates are universal relative coordinates that are capable of addressing any other tile in relation to the base tile. Such a coordinate is a pair of two components, a and b, which both count the distance to the base tile. The a-component steps south-east and the b-component south-west:

The base tile always has the (a,b)-coordinate (0,0).

The other relative coordinates, vicinity codes, can only address the vicinity of the base tile. Vicinity codes are small integers. There are two subtypes: Vicinity-21 and Vicinity-8. Vicinity-21 codes contain the base tile and 20 surrounding tiles, in a shape equal to a city exploitation radius or to an enhanced unit visibility range. The vicinity has 21 tiles, they have codes in the range 1..26. The base tile has the code 13 (= constant CityOwnTile). One application of Vicinity-21 is addressing tiles inside a city's exploitation radius - the city own tile is the base tile then. Sometimes, the codes are used as indices for bit arrays. For example, if the exploited tiles of a city are 11000000000010 (binary), this means that the city exploits the tiles with the Vicinity-21 codes 1, 12 and 13:

Vicinity-8 codes are the most limited coordinates, they address only the 8 adjacent tiles of the base tile. The base tile itself has no Vicinity-8 code:

(a,b)-coordinates and vicinity-8 codes are introduced by this kit in order to make AI programming easier. You must not use them, it's up to you which coordinate types to use in which AI calculation. However, they can be very valuable for AI algorithms that consider geographic neighbourhood and distances. The Sample AI contains several applications that you can take for examples.

The unit CustomAI provides some conversion functions between the different coordinate types:

procedure ab_to_Loc(Loc0,a,b: integer; var Loc: integer);
This function calculates the location code of the tile which has the passed (a,b)-coordinates in relation to the base tile with location code Loc0. If there is no such tile because the relative coordinates are beyond the northern resp. southern end of the world, the code returned is less than 0 resp. greater than MapSize-1. The calculation respects the cylindrical world (no left or right end).

procedure Loc_to_ab(Loc0,Loc: integer; var a,b: integer);
This function calculates the (a,b)-coordinates of the tile with location code Loc in relation to the base tile with location code Loc0. Because of the cylindrical world, this calculation is ambiguous. The function always returns the absolutely smallest possible values for a and b, e.g. (-1,1) instead of (lx-1,-lx+1).

procedure ab_to_V8(a,b: integer; var V8: integer);
Converts an (a,b)-coordinate into a Vicinity-8 code.

procedure V8_to_ab(V8: integer; var a,b: integer);
Converts a Vicinity-8 code into an (a,b)-coordinate.

procedure ab_to_V21(a,b: integer; var V21: integer);
Converts an (a,b)-coordinate into a Vicinity-21 code.

procedure V21_to_ab(V21: integer; var a,b: integer);
Converts a Vicinity-21 code into an (a,b)-coordinate.

procedure V8_to_Loc(Loc0: integer; var VicinityLoc: TVicinity8Loc);
Returns the location codes of all tiles adjacent to the base tile at Loc0. The array index in VicinityLoc is the Vicinity-8 code. Tiles beyound a pole are indicated by an invalid location code.

procedure V21_to_Loc(Loc0: integer; var VicinityLoc: TVicinity21Loc);
Returns the location codes of all tiles in the 21-tile vicinity of the base tile at Loc0. The array index in VicinityLoc is the Vicinity-21 code. Array indices which are not a valid Vicinity-21 code are set to an invalid location code. The same counts for tiles beyound a pole.


The Overridables

There are three ways of control and information flow between the game and your AI: Overridables, Functions and the ReadOnly-block, described within this section and the following ones.

A few of the methods of TCustomAI are overridable. By overriding such a method, you can handle a call to your AI, for example the call to make your turn. Each overridable has already a default implementation in TCustomAI that remains effective if you do not override it in TAI. This default implementation does nothing.

The overridables WantNegotiation and DoNegotiation are described under Diplomacy. Two others, SetDataDefaults and SetDataRandom, will be explained in the section Saving Data. Apart from those four, the overridables are:

OverridableEventTo do
DoTurnIt's your turn. Move units, manage cities etc.
ChooseResearchAdvanceResearch completed. Return next advance to research. Prerequisites must be researched! Return -1 for random choice. If you return adMilitary, you must define the model to develop within this overridable.
ChooseStealAdvanceEnemy city captured while owning the Temple of Zeus. Return advance to steal from captured city. Return -1 for random choice.
ChooseGovernmentAnarchy is over.Return desired new government form.
OnNegoRejected_CancelTreatyOther nation has rejected your contact request. The Opponent field tells which nation that was.Return true if you want to cancel the treaty that you have with this nation.
OnBeforeEnemyAttackEnemy unit will attack you. This unit is not necessarily in your enemy unit list, because it might be covered by a stronger unit at the same tile. Get information about the unit from the UnitInfo parameter of the overridable. Additional parameters also tell the exact outcome of the battle, although it has not yet happened.
This overridable is also called when enemies bombard one of your cities or expel a commando.
Whatever you like, but don't call in-turn functions.
OnAfterEnemyAttackAlways follows to OnBeforeEnemyAttack. Attack has been performed now, either the attacking unit or your defender is destroyed. An attacked city might be destroyed.Whatever you like, but don't call in-turn functions.
OnBeforeEnemyCaptureEnemy unit is going to capture one of your cities. Capturing unit is already removed from origin tile, thus not in your enemy unit list, only specified by the UnitInfo parameter of the overridable. The city is still in your city list.Whatever you like, but don't call in-turn functions.
OnAfterEnemyCaptureAlways follows to OnBeforeEnemyCapture. Capture has been performed now, city is destroyed or in enemy city list, unit is in enemy unit list and located at city tile.Whatever you like, but don't call in-turn functions.

The overridables marked blue are the in-turn overridables, only they can call in-turn functions.


The Functions

Functions are the tools to implement the play of the nation, call them in order to give commands like moving a unit etc. Functions are methods declared by the base class TCustomAI.

Most of the functions return a server return code as result. These return codes are described in the file Protocol.pas. If this code doesn't have the rExecuted bit set, it's an error code, means the function has not been executed. Common server return codes are:
eNoTurn - function can not be called from this overridable
eInvalid - you have called the function with invalid parameters
eViolation - function can't be executed, because this operation does not comply with the rules of the game
eNotChanged - function was executed, but didn't change anything
Other return codes are function-specific and listed below.

All available functions are described in the tables below. Parameters are integers except where stated different. Most functions can only be called from in-turn overridables. These functions are marked blue in the table.

General Functions

FunctionParametersUsage
IsResearchedAdvance Returns whether a specific advance is already researched or not. The function returns false also for advances that are traded from other nations but not researched yet (tsSeen).
ResearchCost-Returns the research points necessary to complete the current research, independent on the points that are already collected (RO.Research).
ChangeAttitudeNation
Attitude
Change your nation's attitude to another nation.
Return code: eOK
Revolution-Do the revolution!
Return code: eOK
ChangeRatesTax
Lux
Change tax and luxury rate, both must be multiples of 10.
Return code: eOK
PrepareNewModelDomainPrepares military research. Pass the domain of the new model that you want to develop.
Makes only sense from overridable ChooseResearchAdvance!
Return Codes: eOK, eNoPreq
SetNewModelFeatureFeature
Count
Lets you specify a feature or capacity of the new model that you want to develop. PrepareNewModel must have been called before in this turn! To turn on binary features like Stealth, pass 1 as count. The result of the change, including strength and cost, can be read from RO.DevModel.
Makes only sense from overridable ChooseResearchAdvance!
Return Codes: eOK, eNoModel, eNoPreq, eDomainMismatch
AdvanceResearchableAdvanceReturns true if the specified advanced can be researched now.
Makes only sense from overridable ChooseResearchAdvance!
AdvanceStealableAdvanceReturns true if the specified advanced can be stolen now.
Makes only sense from overridable ChooseStealAdvance!


Unit Functions

Most of these functions have an integer uix as first parameter. These functions can only be applied to one of your units, not to foreign ones. The uix parameter specifies the unit which to apply the function to and is not explicitely listed in the table below.

Function (Unit_...)ParametersUsage
FindMyDefenderLoc
var uix
Determines, which of your units would defend the tile with the specified location, when attacked. The unit index is returned in uix. It's a value <0 when your nation has no units at this location.
FindEnemyDefenderLoc
var euix
Determines, which enemy unit would defend the tile with the specified location, when attacked. The enemy unit index is returned in euix. It's a value <0 when there is no known enemy unit at this location.
MoveToLocMove unit to another tile, as fast as possible. Be aware that the unit can die during this operation due to hostile terrain. This is indicated by the function return code having the rUnitRemoved flag set. Usually, the unit is moved to the specified tile (ToLoc). Only in the following two situations, it's moved to the most convenient adjacent tile:
- ToLoc is occupied by a unit of another nation
- an undefended foreign city is at ToLoc, but the unit is not able to capture cities
So you can also use this function in preparation of attacks, bombardments and covert operations.
If it takes more than one turn to reach the destination, the unit starts movement but will not remember the destination in the next turn. You have to call Unit_Move again in each turn, until the unit reaches the destination tile. The return code eNoWay tells that the unit can not at all move there on its own.
If formerly unknown foreign units or cities appear along the way, the function stops immediately, even if the destination is not reached yet.
Alternatively to a location code, you can pass maNextCity as destination, which causes the unit to move to the nearest of your cities.
Return codes (complete move done): eOK+rLocationReached, eOK+rMoreTurns, eEnemySpotted+rLocationReached, eEnemySpotted+rMoreTurns, eLoaded+rLocationReached, eLoaded+rMoreTurns
Return codes (move not or not completely done): eNoWay, eEnemySpotted, eDied, eEnemySpotted_Died, eHiddenUnit, eStealthUnit, eZOC_EnemySpotted, eNoCapturer
AttackToLocLet the unit attack an enemy unit, bombard an enemy city or expel a commando at the specified adjacent tile.
Return codes: eLost, eWon, eBloody, eBombarded, eExpelled, eHiddenUnit, eStealthUnit, eNoTime_Attack, eNoTime_Bombard, eNoTime_Expel, eDomainMismatch, eTreaty, eNoBombarder
DoMissionMissionType
ToLoc
Let special commando do covert operation in adjacent foreign city at ToLoc.
Return codes: eMissionDone, eNoTime_Move, eTreaty
MoveForecastToLoc
var RemainingMovement
Calculates the movement points the unit would have left for the current turn after moving to ToLoc. Returns false if the unit can't reach this tile within the turn.
AttackForecastToLoc
AttackMovement
var RemainingHealth
Calculates the health that the unit would have left after attacking the enemy unit at ToLoc. The movement points left for the attack must be specified, values <100 reduce the attack power. If the attacker would be lost, the calculated health value is the negative remaining health of the defender.
Returning false indicates that this unit can't attack there.
DefenseForecasteuix
ToLoc
var RemainingHealth
Calculates the health that the defender at ToLoc would have left after an attack of the enemy unit with index euix in the enemy unit list. If the defender would be lost, the calculated health value is the negative remaining health of the attacker.
Returning false indicates that this unit can't attack there.
Disband-Disband the unit. If it's located in a city with a project it can be utilized for, utilize it.
Return codes: eUtilized, eRemoved
StartJobNewJobLet settlers start/change terrain improvement job. Pass jNone to cancel the current job only. Be aware that the unit can die during this operation due to hostile terrain. This is indicated by the function return code having the rUnitRemoved flag set.
Return Codes: eOK, eDied, eJobDone, eJobDone_Died, eCity, eNoPreq, eTreaty, eDeadLands, eNoCityTerrain, eNoBridgeBuilding
SetHomeHere-Change the home city of the unit to the city it's located in.
Return code: eOK
Load-Load the unit to a transport unit at the same tile.
Return codes: eOK, eNoTime_Load, eNoLoadCapacity
Unload-Unload the unit from its transport.
Return codes: eOK, eNoTime_Load, eDomainMismatch
SelectTransport-Prefer this transport when loading units. Voids any former call to this function.
Return codes: eOK
AddToCity-Add settlers, slaves or conscripts to the city they're located in.
Return codes: eOK, eMaxSize


City Functions

Most of these functions have an integer cix as first parameter. These functions can only be applied to one of your cities, not to foreign ones. The cix parameter specifies the city which to apply the function to and is not explicitely listed in the table below.

Function (City_...)ParametersUsage
FindMyCityLoc
var cix
Find the city at the specified location. The city index is returned in cix. It's a value <0 when your nation has no city at this location.
FindEnemyCityLoc
var ecix
Find enemy city at the specified location. The enemy city index is returned in ecix. It's a value <0 when there is no known enemy city at this location.
HasProject-Returns true whenever the city is producing something different from trade goods.
CurrentImprovementProject-Returns the city improvement, national project or wonder that is currently in production. If the city is producing a unit or trade goods, the function returns a value <0.
CurrentUnitProject-Returns the model index of the unit that is currently in production. If the city is not producing a unit, the function returns a value <0.
GetTileInfoTileLoc
var TileInfo: TTileInfo
Fills the fields of TileInfo with the resource production of this tile when it would be exploited by this city.
Return code: eOK
GetReportvar Report: TCityReport Get a detailed report of this city. (Similar to what a player sees on the city screen.) The information is returned in the fields of Report:
Working - number of working citizens
Happy - number of happy citizens, can be higher than number of working citizens if more working citizens would be happy
FoodRep, ProdRep, Trade - total per turn collected food, material and trade points after improvement effects
PollRep - pollution
Corruption, Tax, Lux, Science - corruption, tax, luxury and science output of city
Support, Eaten - production and food taken for citizens and unit support
Storage - size of food storage
Deployed - number of deployed units
Return code: eOK
GetHypoReportHypoTiles
HypoTax
HypoLux
var Report: TCityReport
Same as City_GetReport, but hypothecial: Report as if the city would exploit different tiles, and if tax and luxury rates were different.
Calling City_GetHypoReport(cix, MyCity[cix].Tiles, RO.TaxRate, RO.LuxRate) results in the same report as City_GetReport.
GetAreaInfovar AreaInfo: TCityAreaInfoFills AreaInfo with availability information about all tiles in the exploitation radius of the city. Index in AreaInfo.Available is the Vicinity-21 code.
Return code: eOK
StartUnitProductionmixChange city's project to a unit with the specified model index.
Return code: eOK
StartEmigrationmix
AllowDisbandCity: boolean
AsConscripts: boolean
Same as StartUnitProduction, but additionally allows producing conscripts and/or disbanding the city.
StartImprovementiixChange city's project to city improvement, national project or wonder.
Return codes: eOK, eNoPreq, eObsolete
ImprovableiixReturns true if this improvement, national project or wonder can be built in this city. This includes the check if this improvement already exists in this city (resp. in the world in case of a wonder).
StopProduction-Cancel the city's project, change the collected material to money and let the city produce trade goods instead.
Return code: eOK
BuyProject-Buy the city's project, so that it's complete the next turn.
Return codes: eOK, eOutOfControl
SellImprovementiixSell the specified city improvement or national project.
Return codes: eOK, eOutOfControl, eOnlyOnce
RebuildImprovementiixRebuild the specified city improvement or national project for the building currently in production.
Return codes: eOK, eOutOfControl, eOnlyOnce
SetTilesNewTilesSet tiles to exploit by a city. The parameter NewTiles is a bit array with the Vicinity-21 code as index. Currently unexploited tiles with 1-bits will be exploited, currently exploited tiles with 0-bits will be unexploited.
This function does not work partially. If not all tiles can be set as requested, the function does nothing and returns an error.
Return codes: eOK, eTileNotAvailable, eNoWorkerAvailable
OptimizeTilesResourceWeightsOptimize the exploitation of tiles in the city area. Food supply and unit support are ensured, if possible. Extra food, material, tax and science are maximized according to the parameter, which can be one of the values below "resource weights" in Protocol.pas. The optimization also works in connection with Luxury or Michelangelo's Chapel.


Negotiation Functions

FunctionParametersUsage
Nego_CheckMyAction-Lets you check whether the currently set MyAction/MyOffer is valid. The return value does not contain the rExecuted flags if not.
Only allowed from overridable DoNegotiation!


Development Support Functions

DebugMessageLevel
Text: string
Display an AI debug message. Note that debug messages are turned off by default. Open the debug message popup menu with a right mouse click into the AI debug message window, choose there which message levels to display.
SetDebugMapvar DebugMapSet a map of debug numbers, one for each tile. In the game, you can turn the number display on using the menu. The values are directly read from the array (32 bit) passed to this function everytime the map display is updated, means you only have to call this function once.
Never set the debug map to a local variable, only to memory that lives as long as your AI, e.g. fields of the AI object.


The ReadOnly-Block

The TCustomAI class contains a field RO, which is a pointer to a complex data structure. This data structure contains a lot of information about your nation and its view of the world. This is an important source of information for you, but it is read-only! The data is managed by the game, so please never change it directly by writing it. This would mean to cheat! This counts for all structures and fields pointed directly or indirectly by RO, as well as for the fields Map, MyUnit, MyCity and MyModel of the AI class, which are actually nothing but shortcuts for parts of RO. The only exception to the read-only rule are the fields Data and Status, which are described under Saving Data.

The data structure which the RO pointer refers to has the type TPlayerContext, defined in the file Protocol.pas. Please see this file for the definition details of this structure and the structures that are pointed to from it. The fields of these structures are commented inside the Protocol.pas.

Unit and City Lists: Beware of Gaps!

The unit and city lists (MyUnit/RO.Un, MyCity/RO.City, RO.EnemyUn, RO.EnemyCity) might have gaps, means non-existing objects at an array position between existing objects. Such non-existing objects are indicated by a Location <0. Commands will not work for them. Furthermore, the items in these lists might change their position within the list whenever the server prepares your turn. So you can not identify cities and units of former turns by their array index! (You could use their ID instead.) The enemy units might even change their indices while the enemies are moving. However, indices never change while your AI is processing an overridable.

The Map

The playground map information (AI.Map or AI.RO.Map) is represented as a list of 32-bit values. The value at array index x provides information about the tile at location x. In order to save memory, several blocks of bits of these 32-bit values are used for different purpose. Means, you can not use the 32-bit value of a tile directly, you always have to extract the bits containing the information that you need. You're doing this by applying an AND operation with the appropriate bit mask or single flag to the value. The table below shows the bit structure of the map values in detail and gives some examples of usage:

BitsBit mask/flagContentExample of usage: test whether tile at location...
0..4fTerrain terrain type if tile is already discovered,
fUNKNOWN if not
...is ground tile:
if (Map[Loc] and fTerrain)>=fGrass
5..6fSpecial 1 or 2 if tile has special resource,
0 if not
...is a wheat tile:
if ((Map[Loc] and fTerrain)=fPrairie) and ((Map[Loc] and fSpecial)=fSpecial1)
or shorter:
if (Map[Loc] and (fTerrain or fSpecial))=(fPrairie or fSpecial1)
7fRiverriver 
8fRoadroad ...has road or railroad:
if (Map[Loc] and (fRoad or fRR))<>0
9fRRrailroad
10fCanalcanal 
11fPollpollution 
12..15fTerImp terrain improvement code if tile has
Irrigation, Farm, Mine, Fort or Base,
tiNone if not
...has fort:
if (Map[Loc] and fTerImp)=tiFort
16fGrWalltile is protected by great wall 
17fSpiedOutenemy city resp. enemy unit stack has been investigated by a special commando or spy plane 
18fStealthUnittile is occupied by enemy stealth plane 
19fHiddenUnittile is occupied by enemy submarine 
20fObservedtile information is from this turn 
21fOwnedunit/city here is own one ...has foreign unit:
if ((Map[Loc] and fUnit)<>0) and ((Map[Loc] and fOwned)=0)
or shorter:
if (Map[Loc] and (fUnit or fOwned))=fUnit
22fUnitunit present
23fCitycity present
24fDeadLandsdead lands 
25..26fModern fCobalt, fUranium or fMercury,
0 if no modern resource
...has a modern resource:
if (Map[Loc] and fModern)<>0
28fOwnZoCUnitown ZoC unit present at this tile 
29fInEnemyZoCtile is in zone of control of known unit of foreign nation that you're not allied with
this information is only valid during your own turn
test whether at least one of two tiles is not in foreign zone of control:
if ((Map[Loc1] and fEnemyControl)=0) or ((Map[Loc2] and fEnemyControl)=0)
or shorter:
if (Map[Loc1] and Map [Loc2] and fEnemyControl)=0
30fPeacetile belongs to territory of nation that you're in peace with but not allied
this information is only valid during your own turn
 

Concerning terrain types, note that there are small differences between the software internal and the player view. Jungle and Forest are the same internally, named forest. Plains do not exist as terrain type, they are grassland with a special resource of type 1 (fSpecial1). Dead Lands also not exist as terrain type. They always have the terrain type Desert, also having the same properties as desert in every aspect, except that settlers can't work there. To distinguish dead land tiles from desert, these tiles have the fDeadLands flag set. The special resources of dead lands (modern resources) are specified by the fModern bits, while the fSpecial bits are always 0.


Diplomacy

The diplomacy implementation of an AI based on this kit has two parts. One part (overridable WantNegotiation) is the decision whether to contact another nation or not. This overridable is called by the kit framework for each known foreign nation in the beginning of your turn (before DoTurn) and again in the end of your turn (after DoTurn). The NegoTime parameter tells you which case it is. You should return true if you wish to ask this nation for negotiation. For example, if you'd like to contact Enemy p after moving your units, return false from WantNegotiation(p,BeginOfTurn) but true from WantNegotiation(p,EndOfTurn). The kit does not support negotiations in the middle of your turn. The third possible value of the NegoTime parameter, EnemyCalled, tells that the other nation is asking you for contact, means it's their turn, not yours. With the EnemyCalled parameter, this is not an in-turn overridable, otherwise it is.

The second part of your diplomacy implementation is the negotiation (overridable DoNegotiation). Negotiations are built around making and accepting offers. An offer contains prices which will be delivered when the offer gets accepted as well as prices which have to be paid in order to accept the offer. Each price is represented by a 32 bit code, depending on its kind:

PriceCode
Your nation's state reportopCivilReport + me shl 16
Opponent's state reportopCivilReport + Opponent shl 16
Your nation's military reportopMilReport + me shl 16
Opponent's military reportopMilReport + Opponent shl 16
World mapopMap
Next closer treatyopTreaty + tr+1, tr = current treaty
End current treatyopTreaty + tr-1, tr = current treaty
Colony Ship PartsopShipParts + t shl 16 + n, t = part type, n = number
MoneyopMoney + v, v = value
TributeopTribute + v, v = value
AdvanceopTech + ad, ad = advance
All advancesopAllTech
Unit designopModel + mix, mix = model index
All unit designsopAllModel
Price of choiceopChoose

Offering to deliver things you do not have is not allowed. Also, offers containing more than one treaty price are not possible. But even if an offer is allowed, it is not necessarily useful, for example demanding a price of choice. Please consider that offers the opponent does not understand are wasted, because he will never accept them.

Offers are represented by data records of the type TOffer, containing these fields:
nDeliver - number of prices delivered if offer is accepted
nCost - number of prices required to pay to accept this offer
Price - codes of the prices delivered in [0..nDeliver-1], codes of the prices to pay in [nDeliver..nDeliver+nCost-1]
An offer can not contain more than 12 prices in total.

A special kind of offers are null-offers, containing no prices. Such an offer means the player has no more offers to make for this negotiation. If null-offers of both players immediately follow one another, the negotiation ends in agreement (in contrast to one player breaking it).

DoNegotiation has no parameters, you can read the negotiation opponent and his last action from the fields Opponent and OppoAction of the AI. In case his action was an offer, the field OppoOffer is valid additionally. Your DoNegotiation implementation must define your next negotiation action in the field MyAction. If it's an offer, you should fill the field MyOffer, too. After DoNegotiation ended, the game will call the other nation and then, maybe, call DoNegotiation again, with the field OppoAction now filled with the other nation's response. See the Sample AI for an example of usage.

The negotiation actions are:

ActionUsageValid as MyAction if OppoAction is...
scDipStart-Never! This is the OppoAction, when the negotiation starts and the opponent didn't have the chance to speak yet.
scDipOfferMake offer. Always fill the MyOffer field when you set this action.scDipStart, scDipOffer, scDipAccept, scDipNotice
scDipAcceptAccept opponent's offer.scDipOffer
scDipNoticeNotice latest opponent decision.scDipCancelTreaty, scDipBreak
scDipCancelTreatyCancel current treaty with opponent.any
scDipBreakBreak negotiation (unagreed).any except scDipBreak


Saving Data

Like most other games, C-evo offers the player to break a game, save it and resume it later from the saved file. If your AI has information that it collects in order to use it in later turns, this information would normally be lost after breaking and reloading the game. In particular, all information that you store in local memory, e.g. in fields of the AI object, is undefined in the beginning of a turn, because the game could have been loaded from a file right before. You should check your AI object occasionally whether you're trying to transport information from turn to turn with it. If so, this will probably fail as soon as a game is saved and resumed. (BTW, the same rule counts for base classes like TCustomAI and TToolAI. Just in case you intend to modify them.)

There are only a few cases in which data exchange between subsequent overridables using local memory in the AI is safe:

The C-evo AI interface offers two ways to make storing long-term information possible: Status fields and the ReadWrite-block.

Status Fields

Status fields are easy to use. Units, cities and models of your nation have a field Status, enemy cities have it too. It's not used by the game and exists only for the needs of AI programming. You can simply write information to these fields and rely on it any number of turns later. When resuming a saved game, the game infrastructure will automatically restore the values that were actual in the year in which the game is loaded. But it's only 32 bit, so consider carefully what to do with them...

The status of newly appearing objects is always initialized with 0. This happens also when a city is captured, means when an enemy city becomes an own city or vice versa. The status content is lost then, it's not copied to the new city object in the other list. (Of course, if this is a problem for your AI, you can implement OnBeforeEnemyCapture / OnAfterEnemyCapture so that it's solved.)

The ReadWrite-Block

In order to collect information that is not related to units, cities or models, e.g. general information about foreign nations, you should use the ReadWrite block. This is a freely definable collection of information that has to be maintained by the AI and is being restored just like the Status fields whenever a saved game is resumed. Define a record type for the structure of this block, and, in the initialization section of AI.pas, assign the size of this structure (in bytes) to RWDataSize. The game will allocate the memory for this data structure and pass the pointer to it in the Data field of the ReadOnly-block.

However, the ReadWrite-block is pretty small: The maximum size is 4096 Bytes. So you should only save there what you can't calculate from other information. (The limitation has to do with the size of saved C-evo books. Since the changes must be saved in each turn, even 4k of information can cause huge book files.)

There are two overridables related to the ReadWrite-block.

SetDataDefaults
Initialize the ReadWrite-block here. This initialization must not depend on anything but the map size and the values from the ReadOnly-block. Particularly, you should not generate random values here, because this overridable is also executed when resuming a saved game - random values would not be generated in the same way as before.

SetDataRandom
Well, if you have random values to set in the beginning of a game (e.g. basic orientation of behavior), do this here. This overridable is called after SetDataDefaults, when a new game is started. When loading games, it is not executed - the values generated here before are being restored instead.

The kit does not yet support updates of the ReadWrite-block. If you change the structure of the data, loading games that were initially played with an older version of your AI will cause problems.


Cheating

I don't have the time for any effort to make the AI interface swindler proof. The game's internal memory is wide open for AI DLLs to read and even to write, leaving several ways to cheat. Some of them will lead to corrupted books, e.g. simple writing to RO data. Other tricks work fine, technically. For example, a nation can calculate the addresses of the other nation's RO blocks and read information from there, such as current unit positions. If you'd like to make use of these monster security leaks, I cannot prevent you from that, but please don't call the result a C-evo AI.


Installing Your AI

There are some steps necessary in order to make the game recognize and use your AI. First, you should choose a name for your AI - let's take MyAI as example. Modify the file names, project settings or compiler command line options so that the AI DLL has the name MyAI.dll instead of AIProject.dll.

Then you need an AI description file, this is a small text file. The kit contains a template, the file AIProject.ai.txt. Rename it to MyAI.ai.txt, move it to the folder where the cevo.exe is located and edit it. An AI description file can contain the following statements, each on the beginning of a separate line (take care for the capitals!):

It's also possible (and appreciated) to create a picture for an AI, to represent it on the start screen. This picture must have a size of 64x64 pixels and be present as MyAI.bmp in the main C-evo folder.


Publishing Your AI

If you'd like to make the AI public, simply upload it to the files section of the project homepage. But please, only do this if you invested a considerable amount of work. Do not publish the Sample AI after you changed three lines of code in it...


The Sample AI

The kit contains a sample AI demonstrating some unit movement, city management and simple diplomacy. This code is made for demonstration, so in contrast to the kit files, it's no infrastructure for development, means it does not have a settled interface. Be aware of that! Of course, you can use the Sample AI as starting point for AI development, or you can copy parts of it to your AI. But when future versions of C-evo come with an improved version of the Sample AI, your AI does not automatically benefit from this. You only have the option then to merge the changes manually.

Files of the Sample AI

The Sample AI also shows a possibility to structure AI code built with this kit: By making the TAI class not base directly on TCustomAI but introducing intermediate class layer(s).


FAQ

Q1. The rules of the game are not exactly specified. I need more information than what is written in the manual.

Answer: Sometimes an AI programmer needs very exact information about calculations or about the behavior of the game in special situations. This exact information often is not contained in the in-game manual, because this manual is for players. Players usually don't need and don't want that precision overkill. If you need more information, please ask me or go to the AI forum. (Or maybe try to analyze the sources of the game...)

Q2. How can my AI...

Answer: All of these things are not part of the actual game. The user interface implements these mechanisms in order to make the game better playable by human players. If you think something similar could be helpful in your AI, you must implement it. The means described in this manual are enough for that.

Q3. How can I debug my AI?

Answer: