Overview
A guide to scripting your own AI for AOEII HD.
Getting Started
First, you will need to locate your AI folder. This is where we will put our files so the game can detect them. Mine is located at:
A basic AI is composed of two elements, a AINAME.ai file and a AINAME.per file. The .ai file tells the game that this ai is run with the personality file of the same name. Example:
The .ai file remains blank, and is simply points to the personality file. Here, I’ve decided to call the AI “stupid”.
Inside every .per file, rules and constants are defined, both of which are covered in the next section.
Rules and Constants
In AIs, usually we like to use names rather than numbers. This does not help the computer, but us, so we can understand what it does.
If we want to store a number as a name, we can use defconst:
This means that whenever we type my-number, the game will interpret it as 5.
To make the AI do things, we need to provide it with rules. The format of a rule is simple:
The rule will perform each action after the other when all of the conditions are true. Every action must be contained within a rule. Here is an example rule to produce villagers constantly:
can-train checks whether we can train the specified unit (we have the building, the resources…). can-build is the same, but for buildings.
If we want a rule to only run once, we can append (disable-self) to the actions.
Boolean Operators
You may have noticed that in rules, we can only do AND, the rule will only perform the actions when every condition is true.
- OR takes two conditions. A condition can be another or statement.
- AND also takes two conditions, and it’s only real purpose is to be included in ORs.
- NOT only takes one condition, and is true if the condition is false.
Examples:
Note that all the indentation is completely optional. It has only been included so that each rule is easier to read.
Goals
If we want to do anything substantial with our AIs, then we are going to have to remember certain numbers. defconst cannot do that for us, since we cannot change it. This is where the goal system comes in.
The format of a goal action is as follows:
We use an action to set a goal so we can use it in a condition. Example:
This sets goal 1 to have a value of 0. We can check the value of goal 1 using a condition:
When reading through an AI, referencing goal by number can be hard or impossible to understand. This is why we throw constants into the mix (note: anything after a semicolon is a comment):
Timers
If we want to perform a set of actions after a certain amount of time, we can use timers. To set a timer, we can use the enable-timer action:
To unset a timer, we can use the disable-timer action:
This will start the timer of TIMER_NUMBER to count down to SECONDS in seconds. We can check if the timer has finished using the condition timer-triggered:
If we want the AI to shout exactly 30 seconds in, we can do it pretty easily combining all the above:
If we wanted our AI to shout every 30 seconds on loop, we just follow our disable-timer with enable-timer directly afterwards.
load-if Statement
A tool given to us is the load-if statement. It is formatted like so:
This will check if a constant exists. If it does, then activate all the rules enclosed between the load-if-defined and the end-if (the body of the if). This is very useful for check what civ the AI is:
AZTEC-CIV is a constant defined for you if you are the Aztecs. Otherwise, it will not exist. This of course means we can check if constants we’ve defined exist:
In a similar way, we can check the opposite with load-if-not-defined. We could check if we were not the Aztecs.
Strategic Numbers
Strategic number tell the AI how to behave at the most basic level. They are mostly used to task villagers to different resources:
In english, this rule is: “If the current age is the dark age, then task 85% of villagers to food, and 15% to wood. Do this only once.”
Escrow
Sometimes we will want to make the AI save up for something. We can save a percentage of all income by setting an escrow percentage.
Let’s suppose that we want to save 90% of all our food. This means that if a villager drops off 10 food, 9 of it cannot be used until we explicitally release it, and 1 will be used as normal.
To set the escrow percentage, we can use the following action:
This will do what I described above. And to release it:
This will take everything we’ve saved up and throw it in the common resource pool.
Combining everything gives us a nice way to ensure that we train/build/research certain things:
This allows us to save for a barracks if we don’t have one by 15 minutes, if we for some reason wanted to do that. can-build-with-escrow, can-train-with-escrow, or anything similar, adds the escrowed amounts to the non-escrowed amounts to check.
Buildings
To build any kind of building we can use the condition:
and the action:
For example, to build a castle we could do:
It is also important to note that we can build a building forwards using build-forward instead of just build.
We can check if we how many buildings of each type we have using this condition:
Note that the difference between building-type-count and building-type-count-total is that the latter includes buildings queued for construction. (this is the same story for unit counts too)
Also note that instead of the > we can use any comparator. Valid comparators:
- > greater than
- < less than
- >= greater than or equal to
- <= less than of equal to
- == equal to
Units + Researching
Same as buildings except with (can-train UNIT) and (train UNIT), (can-research RESEARCH) and (research RESEARCH). Example:
This example will require the university building, of course.
Houses
I feel this deserves it’s own section. To make the AI build houses we can build the house building as normal, but we might also want to check for a few things:
This checks if we are less than 5 units away from being housed. We want this so that the AI doesn’t just build houses on loop. Something else that might be a good idea:
This checks if we have enough houses to support the max population. Tying this all together gives us something like this:
Dropoff points
Camps are built like any other building, but like houses, we only want to build them when certain conditions are met. This condition is useful:
This checks if the closest RESOURCE is more than NUMBER_TILES away from a dropoff point. Another useful condition is
This checks if the AI has scouted a particular resource. We don’t want to build dropoff points for a resource that doesn’t exist!
Here is an example for a lumber camp that should make things clear:
Basic Example
This will try to show how all these components work with each other with an AI that will try an archer rush, and will stay in an endless feudal war. (I’m not very good at the game so the build will be off, probably.).
Files:
basic.ai:
basic.per:
This AI is very bad and requires a lot of tuning, but as an example, it will suffice.
Finally
Have a look through your CPSB.doc. Mine is located at:
Hopefully it will all make sense, and will give you most things you can do. Pay careful attention to the strategic numbers, they will control how the AIs micro will work.
Do also take a look at Saladin on the workshop (not made by me), it is an excellent example.
Miscellaneous Tips
- You may want to assign more builders to build a type of building. This can be done with up-assign-builders
(up-assign-builders c: castle c: 4)
Remember when doing this to also raise the builders cap:
(set-strategic-number sn-cap-civilian-builders 100) - You can load personality files. This does not go in a rule, and can’t.
(load “stupiddarkage”)
(this is assuming that there is a file called darkage.per in a folder called stupid)
Further details can be obtained from the CPSB. - The camp-max-distance is always a pain to work with, if the AI gains map control, it will not take the resources because they are too far away, even if they have run out of the resources at home leading to a horrible shanty town being built. An easy fix that I like to employ is modifying the sn-camp-max-distance whenever I build a camp.
(defrule (dropsite-min-distance wood > 3) (resource-found wood) (can-build lumber-camp) => (build lumber-camp) (up-modify-sn sn-camp-max-distance c:+ 3) )
- Important Strategic Numbers!
(defrule (true) => (set-strategic-number sn-initial-exploration-required 0) (set-strategic-number sn-defer-dropsite-update 1) (disable-self) )
I consider these two to be very important if sn-initial-exploration-required is not set to 0, then the AI cannot build anything until a certain percentage of the map is explored. This leads to the AI sometimes not building houses at the start of the game. If sn-defer-dropsite-update is set to 0, then if the ai wants to build a mining camp in enemy territory, it will send a ton of villagers to their deaths. If 1, then only the one sent to build it may die.