As someone working on custom campaign AI scripts, I thought I could share with you the three non-DUC way to have the AI attack on a loop.
Method 1: Attack-Now
In the code snippet below, the AI will attack every 60 seconds:
(defrule
(true)
=>
(set-strategic-number sn-number-explore-groups 1)
(set-strategic-number sn-relic-return-distance 0)
(set-strategic-number sn-wall-targeting-mode 1)
(disable-self)
)
;attack now loop
(defrule
(up-timer-status 1 != timer-running)
=>
(attack-now)
(enable-timer 1 60)
)
Of the three attack methods, this one uses the fewest lines. However, only a fraction of the AI’s soldiers will attack, even if sn-percent-attack-soldiers is set to 100. If you’re trying to get warships to attack, though, you should use the attack-now method, because the other two methods (attack-groups and town size attack) only apply to land units.
Method 2: Attack-Groups
In the code snippet below, the AI will not attack for 40 seconds and then attack for 20 seconds.
(defrule
(true)
=>
(set-strategic-number sn-number-explore-groups 1)
(set-strategic-number sn-relic-return-distance 0)
(set-strategic-number sn-wall-targeting-mode 1)
(disable-self)
)
;attack groups
(defrule
(true)
=>
(set-strategic-number sn-number-attack-groups 0)
(enable-timer 1 0)
(disable-self)
)
(defrule
(timer-triggered 1)
=>
(disable-timer 1)
(set-strategic-number sn-number-attack-groups 0)
(chat-to-all "sn-number-attack-groups 0")
(enable-timer 2 40)
)
(defrule
(timer-triggered 2)
=>
(set-strategic-number sn-number-attack-groups 200)
(chat-to-all "sn-number-attack-groups 200")
(disable-timer 2)
(enable-timer 1 20)
)
Using this method, the AI will attack when sn-number-attack-groups is set to a non-zero value. Unlike with attack-now, attack-groups sends all its the soldiers to attack. I also found that soldiers attacking with attack-groups get less stuck attacking walls than attack-now. This is why, of these three methods, I prefer using attack-groups for land units.
Method 3:Town Size Attack
The AI is programmed to defend their town. The AI’s definition of what counts as their town is determined the strategic number sn-maximum-town-size. If any enemy units are inside the area defined by sn-maximum-town-size, the AI will attack said enemy units. By changing the size of sn-maximum-town-size to cover the whole map, we can get the AI to attack enemy bases far away. This attack method is known as town size attack.
In the code snippet below, the AI increases its town size every 5 seconds. When it finds an enemy in its town, the AI will attack and reset its town size to 24.
(defrule
(true)
=>
(set-strategic-number sn-relic-return-distance 0)
(set-strategic-number sn-maximum-town-size 24)
(set-strategic-number sn-number-explore-groups 1)
(set-strategic-number sn-wall-targeting-mode 1)
(set-goal 1 0)
(enable-timer 2 5)
(disable-self)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 124)
=>
(set-strategic-number sn-maximum-town-size 144)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 104)
=>
(set-strategic-number sn-maximum-town-size 124)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 84)
=>
(set-strategic-number sn-maximum-town-size 104)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 64)
=>
(set-strategic-number sn-maximum-town-size 84)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 44)
=>
(set-strategic-number sn-maximum-town-size 64)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(timer-triggered 2)
(not (enemy-buildings-in-town))
(strategic-number sn-maximum-town-size == 24)
=>
(set-strategic-number sn-maximum-town-size 44)
(disable-timer 2)
(enable-timer 2 5)
)
(defrule
(goal 1 0)
(enemy-buildings-in-town)
=>
(enable-timer 1 14)
(disable-timer 2)
(set-goal 1 1)
)
(defrule
(goal 1 1)
(timer-triggered 1)
=>
(disable-timer 1)
(enable-timer 2 5)
(set-goal 1 0)
(set-strategic-number sn-maximum-town-size 24)
)
Town size attack is great if you want the AI to prioritize attacking the closest buildings. However, as you can see, town size attack requires the most lines among the three methods.
For all three methods, the AI will only attack explored enemy objects. Therefore, make sure sn-number-explore-groups is set to at least 1.
Bonus Section: Randomizing Attack Intervals
Say you want to make the attack intervals less predictable. Perhaps you want the AI attack every 20 to 40 seconds, instead of exactly 40 seconds every time. To do this, you will need to use goals, UserPatch commands and be familiar with mathOp, compareOp and typeOp operators.
In the code snippet below, the AI, using the attack-groups method, will wait 20 to 40 seconds, and then attack for 20 seconds:
(defconst gl-attack-time-range 6)
(defconst gl-attack-interval 7)
(defconst gl-game-time 8)
(defrule
(true)
=>
(set-strategic-number sn-number-explore-groups 1)
(set-strategic-number sn-relic-return-distance 0)
(set-strategic-number sn-wall-targeting-mode 1)
(set-goal gl-attack-interval 20)
(disable-self)
)
;attack groups
(defrule
(true)
=>
(set-strategic-number sn-number-attack-groups 0)
(enable-timer 1 0)
(disable-self)
)
(defrule
(timer-triggered 1)
=>
(disable-timer 1)
(set-strategic-number sn-number-attack-groups 0)
(up-get-fact game-time 0 gl-game-time)
(up-chat-data-to-self "sn-number-attack-groups 0 at %d seconds" g: gl-game-time)
(generate-random-number 20)
(up-get-fact random-number 0 gl-attack-time-range)
(up-modify-goal gl-attack-interval g:+ gl-attack-time-range)
(up-chat-data-to-self "gl-attack interval is %d" g: gl-attack-interval)
(up-set-timer c: 2 g: gl-attack-interval)
(set-goal gl-attack-interval 20)
)
(defrule
(timer-triggered 2)
=>
(set-strategic-number sn-number-attack-groups 200)
(up-get-fact game-time 0 gl-game-time)
(up-chat-data-to-self "Attack at %d seconds" g: gl-game-time)
(disable-timer 2)
(enable-timer 1 20)
)
In the first block of code after the defconst stuff, (set-goal gl-attack-interval 20)
initially sets goal gl-attack-interval to 20, which is the lower bound of the attack interval (goals can be thought of as variables). When timer 1 is triggered, the AI goes into non-attack mode and determines how long timer 2 will be triggered. First, a random number between 1 and 20 is generated with the command (generate-random-number 20)
. (up-get-fact random-number 0 gl-attack-time-range)
then places the number generated by (generate-random-number 20)
into the goal gl-attack-time-range. (up-modify-goal gl-attack-interval g:+ gl-attack-time-range)
adds gl-attack-time-range to gl-attack-interval; the goal value of gl-attack-interval should now be a number between 20 and 40. (up-set-timer c: 2 g: gl-attack-interval)
then sets timer 2 to trigger at gl-attack-interval’s goal value, in seconds. (set-goal gl-attack-interval 20)
resets gl-attack-interval back to 20, so that it can be resued when timer 1 is re-triggered. When timer 2 is triggered, the AI will attack.
And one more thing: you’ll see in that code that I use up-chat-data-to-self commands for debugging purposes. This up-chat-data-to-self command, however, will only work if you play as an AI in a skirmish match. If you start the match as a human player or use the scenario editor to test your scenario, the AI will not say anything even if you use CTRL-Shift-Fnumber to take control of them. The only up-chat commands that will work if you don’t play as an AI are up-chat-data-to-all or up-chat-data-to-player 1. Obviously you don’t want the AI giving away its strategy to you, so those two commands aren’t exactly the what we want in a “serious” AI.