Using goals to clean up AI scripts

I recently learned about goals and I thought I could share with you guys how they can simplify your AI code.

Suppose you want an AI that trains 10 knights, 10 villagers and 2 rams on Standard; 20 knights, 20 vils and 4 rams in Moderate; and 30 knights, 30 villagers and 6 rams on Hard. Without goals, your code would look like this:

;standard training
(defrule
	(difficulty > moderate)
	(unit-type-count-total knight-line < 10)
	(can-train knight-line)
=>
	(train knight-line)
)
(defrule
	(difficulty > moderate)
	(unit-type-count-total villager < 10)
	(can-train villager)
=>
	(train villager)
)
(defrule
 	(difficulty > moderate)
	(unit-type-count-total battering-ram-line < 2)
	(can-train battering-ram-line)
=>
	(train battering-ram-line)
)

;moderate training
(defrule
	(difficulty == moderate)
	(unit-type-count-total knight-line < 20)
	(can-train knight-line)
=>
	(train knight-line)
)
(defrule
	(difficulty == moderate)
	(unit-type-count-total villager < 20)
	(can-train villager)
=>
	(train villager)
)
(defrule
 	(difficulty == moderate)
	(unit-type-count-total battering-ram-line < 4)
	(can-train battering-ram-line)
=>
	(train battering-ram-line)
)

;hard training
(defrule
	(difficulty < moderate)
	(unit-type-count-total knight-line < 30)
	(can-train knight-line)
=>
	(train knight-line)
)
(defrule
	(difficulty < moderate)
	(unit-type-count-total villager < 30)
	(can-train villager)
=>
	(train villager)
)
(defrule
 	(difficulty < moderate)
	(unit-type-count-total battering-ram-line < 6)
	(can-train battering-ram-line)
=>
	(train battering-ram-line)
)

And with goals, your code will look like this:

(defconst gl-knight-count 1)
(defconst gl-villager-count 2)
(defconst gl-ram-count 3)

(defrule
	(difficulty > moderate)
=>
	(set-goal gl-knight-count 10)
	(set-goal gl-villager-count 10)
	(set-goal gl-ram-count 2)
)
(defrule
	(difficulty == moderate)
=>
	(set-goal gl-knight-count 20)
	(set-goal gl-villager-count 20)
	(set-goal gl-ram-count 4)
)
(defrule
	(difficulty < moderate)
=>
	(set-goal gl-knight-count 30)
	(set-goal gl-villager-count 30)
	(set-goal gl-ram-count 6)
)

(defrule
	(unit-type-count-total knight-line g:< gl-knight-count)
	(can-train knight-line)
=>
	(train knight-line)
)
(defrule
	(unit-type-count-total villager g:< gl-villager-count)
	(can-train villager)
=>
	(train villager)
)
(defrule
	(unit-type-count-total battering-ram-line g:< gl-ram-count)
	(can-train battering-ram-line)
=>
	(train battering-ram-line)
)

The non-goal snippet has 65 lines of code (not counting comments), while the goal snippet has 44 lines of code. But not only is the goal snippet shorter, the training numbers are easier to edit, given that the training numbers are all grouped together in a single section.

Goals in AoE2 AI scripting are essentially variables. By default, goals are named (or more accurately, have ID numbers of) 1, 2, 3… all the way to 512. What the defconst lines are doing are renaming goals 1, 2 and 3 to gl-knight-count, gl-villager-count and gl-ram-count respectively. This is done for readability purposes. What the set-goal stuff are doing is setting the value inside of each goal to a specific number.

Unlike in random map scripting, you cannot redefine constants in AI scripting; the constant value for gl-knight-count will always be equal to 1. As mentioned earlier, though, the goal value for gl-knight-count can be changed with the set-goal command. For example, in Standard difficulty, the command “set-goal gl-knight-count 10” is not changing gl-knight-count’s goal ID from 1 to 10. Instead, it’s setting the value inside goal ID 1 (which defconst named “gl-knight-count”) to 10.

Also note that these two methods use different comparison operator. The non-goal comparison operator uses the standard less-than (<) operator, because it is comparing unit-type-count-total to constants. In contrast, when you need to compare goal values, you need to use g: (g colon) in front of the operator. If you don’t use g:, then the AI will treat the gl- stuff as constants. So, you write something like this:

(defconst gl-knight-count 1)
(defrule
	(true)
=>
	(set-goal gl-knight-count 10)
)

(defrule
	(unit-type-count-total knight-line < gl-knight-count)
	(can-train knight-line)
=>
	(train knight-line)
)

The AI will only train 1 knight instead of 10 knights.

2 Likes