CTP2 Bureau : AI Customization
CTP2 Bureau
The Modding
Apolyton CTP2 forums
Apolyton CTP2 archives
Source Code Project SVN

AI Customization

Following is a description of how to use the AI SLIC functions available in Call to Power II to customize the behavior of AI players and the mayors that can control human cities.

by Joe Rumsey

Getting Started

You should create a new scenario that will include the modifications you make. Specific directions for creating a new scenario can be found below in Appendix A. Most of the files that you will modify to make AI changes can be found in the default\aidata subdirectory of your scenario. You will also create/modify the scenario.slc file in the default\gamedata subdirectory of your scenario.

Strategic State

The parameters defined in the strategies.txt file are used to control almost every aspect of how an AI controls their empire and tasks their units. The details of what each parameter does has been described elsewhere; the purpose of this section is to explain how to change these parameters to customize the game.
The strategic state of each player (AI or human) is initialized when the game begins based on hard coded rules that load one of the six default strategies (see table 1). You can specify a different default strategy to initialize a player with by defining a SLIC function that handles the InitStrategicState event. For example:

HandleEvent (InitStrategicStateEvent) ‘Example_StrategicStateInit’ post {
 // player[0] contains the player being initialized
 // code that ends up calling VOID SetStrategicState(player, strategyDBindex)

You can also change the initial strategic state after the game has begun by resetting the strategic state from within a SLIC function that handles the NextStrategicState event and calls the SetStrategicState function.
You can also define any number of SLIC functions to handle the NextStrategicState event that modify situation dependent elements of a player’s strategic state. For example, if you wanted the AI to completely stop building settlers when they had built 20 cities, you would create a function that tests the current number of cities, and when the number exceeded 20 calls the ConsiderStrategicState function to load a new strategy record in which the SettlerUnitsCount is set to 0. Changing the strategy for the human player affects the behavior of the Mayors that can be used to control the players’ cities (see APPENDIX B).
When a new strategy is “considered” using ConsiderStrategicState, parameters defined by the new strategy record overwrite the values defined in the base strategy (set by SetStrategicState). As strategies are merged in values are overwritten in an order determined by their priority; lower priority strategic state records being overwritten by records that were considered with a higher priority. In this way you can have two handlers fire that load strategy records that modify the same parameter. The changes caused by the strategy record with the higher priority will be the one used.
Not all parameters can be changed using the ConsiderStrategicState function. The following structure elements are only changed by a SetStrategicState function call: PopAssignmentElement elements, the order of Government elements and SliderElement elements.
The following structure elements are merged based on the primary reference in the structure: GoalElement elements and BuildListSequenceElement elements. In the former case, the elements will overwrite all existing elements with the same Goal field; see the STRATEGY_ATTACK record for an example that redefines only the GoalElement elements defined for GOAL_DEFEND, GOAL_SEIGE, GOAL_ATTACK, GOAL_HARASS, GOAL_BOMBARD, GOAL_HARASS_CITY and GOAL_PILLAGE. The latter case is similarly redefined, except for being keyed on the BuildListSequence field.

Diplomatic State

The parameters in the diplomacy.txt file are used to define unique parameters for how each AI player reacts diplomatically to each other player in the game. Unlike strategic states, each player maintains multiple diplomatic states that define their relationship with each foreigner. Also unlike strategic states, diplomatic states are taken all or nothing. After all diplomatic states have been considered in a turn, only the one with the highest priority is used. However, a diplomatic state might define other states that it inherits from in order reduce the redundancy in diplomatic state records. If the Inherit field is set, then the referenced diplomacy record is loaded first and the current record is merged in overwriting any variables that it redefines.
The initial diplomatic state record load for a player can be redefined by writing a SLIC function that handles the InitDiplomaticState event and calls ConsiderDiplomaticState followed by a call to ChangeDiplomaticState. For example:

HandleEvent (InitDiplomaticState) ‘Example_DiplomaticStateInit’ post {
 // player[0] is the player being initialized
 // player[1] is the foreigner
 // code that ends up calling ConsiderDiplomaticState
 // code that calls ChangeDiplomaticState

At the beginning of every turn the NextDiplomaticState event will be triggered for every foreigner that has been contacted. You can define SLIC functions to handle this event and change which diplomacy record should be loaded by calling the ConsiderDiplomaticState function. Do not call the ChangeDiplomaticState function from within these handlers; it will be called after all handlers have been executed and cause a change to the one with the highest priority.

HandleEvent (NextDiplomaticState) ‘Example_NextDiplomaticState’ pre {
 // player[0] is the player being whose turn it is
 // player[1] is the foreigner
 // code that ends up calling ConsiderDiplomaticState


At the beginning of every player turn the ReactionMotivation and possibly one or both of the FearMotivation or DesireMotivation events will be triggered. To add a rule which can trigger the sending of a new proposal you should write a SLIC function to handle one of these events. For simplicity it is best to write your handles to be triggered by the ReactionMotivation event. For all of these events, player[0] is the player whose turn it is, and player[1] is the foreigner that would receive the new proposal.
You should access the current diplomacy state for the player using the GetNewProposalPriority function and also call any other functions needed to determine whether or not the player should send a particular new proposal. If it is determined that the proposal should be sent, then call the function ConsiderNewProposal. If multiple calls to ConsiderNewProposal are made, then the proposal with the highest priority will be sent.
If an AI has received a new proposal, then the NewProposal event will be triggered. You should write functions to handle this event and respond to it. The parameter player[0] will be the player that sent the proposal and player[1] will be the player that received it. You should call the functions GetLastNewProposalType, GetLastNewProposalArg and GetLastNewProposalTone to figure out how to respond; other functions might also be useful when deciding how to respond. You should use the function ConsiderResponse if you wish to accept or counter the new proposal. By default the new proposal will be rejected. If multiple calls to ConsiderResponse are made then the response with the highest priority will be sent.
If an AI has received a response to their new proposal other than accept, then the Counter event will be triggered if the receiver of the proposal countered or a Reject event if they rejected it outright. The parameter player[0] will be the player that sent the original new proposal and player[1] will be the player that was the original receiver. You should call the functions GetLastResponseType, GetLastCounterResponseType, GetLastCounterResponseArg before determining how to respond to a counter proposal or rejection. You can then call the ConsiderResponse function (with the receiver and sender id’s swapped if accepting or rejecting) to respond. You can also call ConsiderResponse to threaten.
If an AI has received a threat from a proposal sender, then the Threaten event will be triggered. The parameter player[0] will be the player that send the original new proposal and player[1] will be the player that was the original receiver. You can call the GetLastNewProposal* and GetLastResponse* functions to determine how to respond to a threat. You can then call the ConsiderResponse function to accept or reject the original proposal after a threat.
All diplomacy event handler functions should be of type pre so that multiple responses or new proposals can be considered before selecting one to be sent.


The personality.txt file defines parameters that are used to determine which strategic state and diplomatic states will automatically be used for each AI player. As a mod maker you can call the function GetPersonalityType to determine the personality of a particular player and then use the DB accessor functions built into SLIC2 to determine the settings of a particular personality. These personality settings are used as a reference by other parts of the system but do not alone have direct control over how the AI behaves. Just because you give an Evil personality to an AI does not mean it will “act evil.” You also should define strategic and diplomatic event handlers that check for this personality setting and load in strategic and diplomatic states that cause evil behaviors.

Player initialization strategies based on personality:


Always loaded for player 0
STRATEGY_MILITARIST_DEFAULT: Loaded for players with personality in which DiscoveryScientist bit set
STRATEGY_ECONOMIC_DEFAULT: Loaded for players with personality in which DiscoveryEconomic bit set
STRATEGY_ECOTOPIAN_DEFAULT: Loaded for players with personality in which DiscoveryEcotopian bit set
STRATEGY_DIPLOMATIC_DEFAULT: Loaded for players with personality in which DiscoveryDiplomatic bit set
STRATEGY_DEFAULT: All other personalities

APPENDIX A – How to create a Mod Pack

From Joe Rumsey’s November 29th description on Apolyton:

This is a good opportunity to describe the right way to make a mod pack. The first step is to create a new "scenario" using the editor: Open the editor and click Save Scenario. Click the New button, and fill in the details for the new mod pack (which may contain multiple mods, but will only contain one for this example.) Click OK, and then click New again (you should be viewing an empty list of scenarios before clicking New the second time.) Fill in the details for the new scenario. At this point, you will have a complete pack and scenario, which if loaded will have no effect at all on gameplay.
So now it's time to change something. There are two parts to Richard's PW mod, a change to strategies.txt and an added event handler. To change strategies.txt, you need to first replicate the standard version in your mod. Assuming you named your mod pack directory "PWModPack", find it in [installdir]/scenarios/PWModPack. Inside that, there will be a scen0000 directory. Inside the scen0000 directory, you can recreate any part of the standard data. For this example, you need to create .../PWModPack/scen0000/default/aidata/strategies.txt. The "default" directory should already exist (creating the scenario created it) but you need to make the aidata directory. Then copy the standard strategies.txt into that directory, and add to it Richard's suggested record.
Finally, open .../PWModPack/scen0000/default/gamedata/scenario.slc (which should already exist as an empty file) and copy the event handler to it.
Then to play your Mod, just click the Select Scenario button on the launch screen, and choose it like you would any scenario.
Just to finish this with an example of how to distribute a mod, I've zipped up everything needed for this mod and placed the zip here. If you unzip this to [installdir]/scenarios (which should create a directory named [installdir]/scenarios/PWModPack), you'll have everything you need to play this mod.

APPENDIX B – Example of using SLIC to customize the Mayor system

The scripting system can be used to customize the game. Begin by creating a new scenario tree (see APPENDIX A) and put a copy of the standard strategies.txt file in the default\aidata\ subdirectory of the scenario. Also create a new scenario.slc file in the default\gamedata subdirectory of the scenario.
This example will show how to change the game so mayors will use PW only if there is over 5000 stored. To do this, we need to change the currently loaded strategy for the player so that the PublicWorksReserve field is set to 5000. The solution would be to add a new strategy record to the end of the scenarios strategies.txt that looks like:

PublicWorksReserve 5000

When this strategy record is loaded, it will increase the public works reserve so that mayors (or AI) will only spend PW that is above this limit.
You must then use SLIC to load this strategy explicitly for human players. This should be done by adding to the new scenario.slc the following event handler:

HandleEvent(NextStrategicState) 'ChangeHumanMayorStrategy' pre {
 // Increase PW reserve for human players
 int_t db_index;
 int_t ss_priority;
 db_index = StrategyDB(STRATEGY_LARGE_PW_RESERVE);
 ss_priority = 500;
 if (db_index >= 0 && IsHumanPlayer(player[0])) {
  ConsiderStrategicState(player[0], ss_priority, db_index);
This site is currently maintained by: BureauBert (Webmaster)  Maquiladora (Manager)  Legal Notice: Legal Notice Statistics: 82965 unique users since 02.05.2004 (» Site Statistics) Hosted by: WebhostOne
Share this page:  VK Facebook Twitter LiveJournal OK MR Google+ LinkedIn tumblr Pinterest Blogger Digg Evernote