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 cross-platform libraries:
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.
Trying to program a C-evo AI makes no sense if you don't have good knowledge in two fields:
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 Lazarus IDE, switch between Debug and Release by the Build mode drop down menu in the project
settings.
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.
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.
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:
Overridable | Event | To do |
DoTurn | It's your turn. | Move units, manage cities etc. |
ChooseResearchAdvance | Research 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. |
ChooseStealAdvance | Enemy city captured while owning the Temple of Zeus. | Return advance to steal from captured city. Return -1 for random choice. |
ChooseGovernment | Anarchy is over. | Return desired new government form. |
OnNegoRejected_CancelTreaty | Other 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. |
OnBeforeEnemyAttack | Enemy 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. |
OnAfterEnemyAttack | Always 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. |
OnBeforeEnemyCapture | Enemy 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. |
OnAfterEnemyCapture | Always 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.
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.
Function | Parameters | Usage |
IsResearched | Advance | 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). |
ChangeAttitude | Nation Attitude | Change your nation's attitude to another nation. Return code: eOK |
Revolution | - | Do the revolution! Return code: eOK |
ChangeRates | Tax Lux | Change tax and luxury rate, both must be multiples of 10. Return code: eOK |
PrepareNewModel | Domain | Prepares military research. Pass the domain of the new model that you want to develop.
Makes only sense from overridable ChooseResearchAdvance! Return Codes: eOK, eNoPreq |
SetNewModelFeature | Feature 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 |
AdvanceResearchable | Advance | Returns true if the specified advanced can be researched now. Makes only sense from overridable ChooseResearchAdvance! |
AdvanceStealable | Advance | Returns true if the specified advanced can be stolen now. Makes only sense from overridable ChooseStealAdvance! |
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_...) | Parameters | Usage |
FindMyDefender | Loc 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. |
FindEnemyDefender | Loc 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. |
Move | ToLoc | Move 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 |
Attack | ToLoc | Let 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 |
DoMission | MissionType ToLoc | Let special commando do covert operation in adjacent foreign city at ToLoc.
Return codes: eMissionDone, eNoTime_Move, eTreaty |
MoveForecast | ToLoc 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. |
AttackForecast | ToLoc 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. |
DefenseForecast | euix 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 |
StartJob | NewJob | Let 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 |
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_...) | Parameters | Usage |
FindMyCity | Loc 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. |
FindEnemyCity | Loc 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. |
GetTileInfo | TileLoc 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 |
GetReport | var 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 |
GetHypoReport | HypoTiles 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. |
GetAreaInfo | var AreaInfo: TCityAreaInfo | Fills 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 |
StartUnitProduction | mix | Change city's project to a unit with the specified model index. Return code: eOK |
StartEmigration | mix AllowDisbandCity: boolean AsConscripts: boolean | Same as StartUnitProduction, but additionally allows producing conscripts and/or disbanding the city. |
StartImprovement | iix | Change city's project to city improvement, national project or wonder. Return codes: eOK, eNoPreq, eObsolete |
Improvable | iix | Returns 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 |
SellImprovement | iix | Sell the specified city improvement or national project. Return codes: eOK, eOutOfControl, eOnlyOnce |
RebuildImprovement | iix | Rebuild the specified city improvement or national project for the building currently in production. Return codes: eOK, eOutOfControl, eOnlyOnce |
SetTiles | NewTiles | Set 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 |
OptimizeTiles | ResourceWeights | Optimize 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. |
Function | Parameters | Usage |
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! |
DebugMessage | Level 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. |
SetDebugMap | var DebugMap | Set 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 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.
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 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:
Bits | Bit mask/flag | Content | Example of usage: test whether tile at location... |
0..4 | fTerrain | terrain type if tile is already discovered, fUNKNOWN if not |
...is ground tile: if (Map[Loc] and fTerrain)>=fGrass |
5..6 | fSpecial | 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) |
7 | fRiver | river | |
8 | fRoad | road | ...has road or railroad: if (Map[Loc] and (fRoad or fRR))<>0 |
9 | fRR | railroad | |
10 | fCanal | canal | |
11 | fPoll | pollution | |
12..15 | fTerImp | terrain improvement code if tile has Irrigation, Farm, Mine, Fort or Base, tiNone if not |
...has fort: if (Map[Loc] and fTerImp)=tiFort |
16 | fGrWall | tile is protected by great wall | |
17 | fSpiedOut | enemy city resp. enemy unit stack has been investigated by a special commando or spy plane | |
18 | fStealthUnit | tile is occupied by enemy stealth plane | |
19 | fHiddenUnit | tile is occupied by enemy submarine | |
20 | fObserved | tile information is from this turn | |
21 | fOwned | unit/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 |
22 | fUnit | unit present | |
23 | fCity | city present | |
24 | fDeadLands | dead lands | |
25..26 | fModern | fCobalt, fUranium or fMercury, 0 if no modern resource |
...has a modern resource: if (Map[Loc] and fModern)<>0 |
28 | fOwnZoCUnit | own ZoC unit present at this tile | |
29 | fInEnemyZoC | tile 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 |
30 | fPeace | tile 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.
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:
Price | Code |
Your nation's state report | opCivilReport + me shl 16 |
Opponent's state report | opCivilReport + Opponent shl 16 |
Your nation's military report | opMilReport + me shl 16 |
Opponent's military report | opMilReport + Opponent shl 16 |
World map | opMap |
Next closer treaty | opTreaty + tr+1, tr = current treaty |
End current treaty | opTreaty + tr-1, tr = current treaty |
Colony Ship Parts | opShipParts + t shl 16 + n, t = part type, n = number |
Money | opMoney + v, v = value |
Tribute | opTribute + v, v = value |
Advance | opTech + ad, ad = advance |
All advances | opAllTech |
Unit design | opModel + mix, mix = model index |
All unit designs | opAllModel |
Price of choice | opChoose |
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:
Action | Usage | Valid 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. |
scDipOffer | Make offer. Always fill the MyOffer field when you set this action. | scDipStart, scDipOffer, scDipAccept, scDipNotice |
scDipAccept | Accept opponent's offer. | scDipOffer |
scDipNotice | Notice latest opponent decision. | scDipCancelTreaty, scDipBreak |
scDipCancelTreaty | Cancel current treaty with opponent. | any |
scDipBreak | Break negotiation (unagreed). | any except scDipBreak |
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 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.)
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.
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.
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.
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 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).
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: