AI Scripting Continued by Wok

AI Scripting, Continued

Hi guys. A certain large organ that resides inside my head has recently told me that the article entitled "A Newbie's Guide To AI Scripting", by a certain Wok, is certainly incomplete. I feel that it is only my duty to expand where the article left off, and take you one step further to becoming a walking library of AI knowledge. Or some equally dramatic metaphor.


Strategic Numbers:
So, you know rules. You know facts and actions. You know how to import a custom script into your game. What you don't know yet, however, is how to control your workers, which is (in my humble opinion) quite an important thing to know.

So, aside from numbers that are strategic, what exactly are strategic numbers?

Well, Watson, that's what I'm about to tell you! You see, strategic numbers divide up certain types of unit, and give them different jobs. Or tasks, if you will. Strategic numbers work in both numbers and percentages. For example, you could set 98% of your workers to be ore collecters, and 2% to be farmers. But that would just be moronic, so don't.

So how do you use these numbers in your scripts? And my name isn't Watson...

You can change them by using the (set-strategic-number) action. There are two variables in the action: sn-name and value. The name is what task you want the units to do, and the value is the percentage of your total units to do it. Simple? (Hint: Answer "Yes." If not, go back, read up, and catch on)

So, a simple rule containing a stategic number change looks something like this:

(defrule
(current-age == tech-level-1)
=>
(set-strategic-number sn-food-gatherer-percentage 75)
)

Here's a list of the different sn-names you can use:

  • sn-food-gatherer-percentage
  • sn-carbon-gatherer-percentage
  • sn-nova-gatherer-percentage
  • sn-metal-gatherer-percentage
  • sn-percent-civilian-explorers
  • sn-percent-civilian-builders
  • sn-percent-civilian-gatherers
  • sn-cap-civilian-explorers
  • sn-cap-civilian-builders
  • sn-cap-civilian-gatherers
  • sn-minimum-attack-group-size
  • sn-total-number-explorers
  • sn-percent-enemy-sighted-response
  • sn-enemy-sighted-response-distance
  • sn-sentry-distance
  • sn-relic-return-distance
  • sn-minimum-defend-group-size
  • sn-maximum-attack-group-size
  • sn-maximum-defend-group-size
  • sn-minimum-peace-like-level
  • sn-percent-exploration-required
  • sn-zero-priority-distance
  • sn-minimum-civilian-explorers
  • sn-number-attack-groups
  • sn-number-defend-groups
  • sn-attack-group-gathers-pacing
  • sn-number-explore-groups
  • sn-minimum-explore-group-size
  • sn-maximum-explore-group-size
  • sn-gold-defend-priority
  • sn-stone-defend-priority
  • sn-forage-defend-priority
  • sn-relic-defend-priority
  • sn-town-defend-priority
  • sn-defense-distance
  • sn-number-boat-attack-groups
  • sn-minimum-boat-attack-group-size
  • sn-maximum-boat-attack-group-size
  • sn-number-boat-explore-groups
  • sn-minimum-boat-explore-group-size
  • sn-maximum-boat-explore-group-size
  • sn-number-boat-defend-groups
  • sn-minimum-boat-defend-group-size
  • sn-maximum-boat-defend-group-size
  • sn-dock-defend-priority
  • sn-sentry-distance-variation
  • sn-minimum-town-size
  • sn-maximum-town-size
  • sn-group-commander-selection-method
  • sn-consecutive-idle-unit-limit
  • sn-target-evaluation-distance
  • sn-target-evaluation-hitpoints
  • sn-target-evaluation-damage-capability
  • sn-target-evaluation-kills
  • sn-target-evaluation-ally-proximity
  • sn-target-evaluation-rof
  • sn-target-evaluation-randomness
  • sn-camp-max-distance
  • sn-mill-max-distance
  • sn-target-evaluation-attack-attempts
  • sn-target-evaluation-range
  • sn-defend-overlap-distance
  • sn-scale-minimum-attack-group-size
  • sn-scale-maximum-attack-group-size
  • sn-attack-group-size-randomness
  • sn-scaling-frequency
  • sn-maximum-gaia-attack-response
  • sn-build-frequency
  • sn-attack-separation-time-randomness
  • sn-attack-intelligence
  • sn-initial-attack-delay
  • sn-save-scenario-information
  • sn-special-attack-type1
  • sn-special-attack-influence1
  • sn-minimum-water-body-size-for-dock
  • sn-number-build-attempts-before-skip
  • sn-max-skips-per-attempt
  • sn-food-gatherer-percentage
  • sn-gold-gatherer-percentage
  • sn-stone-gatherer-percentage
  • sn-wood-gatherer-percentage
  • sn-target-evaluation-continent
  • sn-target-evaluation-siege-weapon
  • sn-group-leader-defense-distance
  • sn-initial-attack-delay-type
  • sn-blot-exploration-map
  • sn-blot-size
  • sn-intelligent-gathering
  • sn-task-ungrouped-soldiers
  • sn-target-evaluation-boat
  • sn-number-enemy-objects-required
  • sn-number-max-skip-cycles
  • sn-retask-gather-amount
  • sn-max-retask-gather-amount
  • sn-max-build-plan-gatherer-percentage
  • sn-food-dropsite-distance
  • sn-wood-dropsite-distance
  • sn-stone-dropsite-distance
  • sn-gold-dropsite-distance
  • sn-initial-exploration-required
  • sn-random-placement-factor
  • sn-minimum-forest-tiles
  • sn-attack-diplomacy-impact
  • sn-percent-half-exploration
  • sn-target-evaluation-time-kill-ratio
  • sn-target-evaluation-in-progress
  • sn-attack-winning-player
  • sn-coop-share-information
  • sn-attack-winning-player-factor
  • sn-coop-share-attacking
  • sn-coop-share-attacking-interval
  • sn-percentage-explore-exterminators
  • sn-track-player-history
  • sn-minimum-dropsite-buffer
  • sn-use-by-type-max-gathering
  • sn-minimum-boar-hunt-group-size
  • sn-minimum-amount-for-trading
  • sn-easiest-reaction-percentage
  • sn-easier-reaction-percentage
  • sn-hits-before-alliance-change
  • sn-allow-civilian-defense

Whew... that took a while to format. And no, I'm not going to pretend I know what they all mean, because that would make me a lier. I should also imagine that you'll never use most of them. They're just here for reference.

How can we detect what value a strategic number has?

Strategic numbers can be used in facts, too. It's fairly simple, it just uses the same syntax as any other fact. Here's an example:

(defrule
(nova-amount < 200)
(strategic-number sn-nova-gatherer-percentage < 20)
=>
(set-strategic-number sn-nova-gatherer-percentage 20)
)

So, that's pretty much how you use strategic numbers. Knowing me, I've probably missed something out, and if you think that I have, or need help, then don't hesitate to contact me. However, I do not guarantee friendliness...

  • Just one more little pointer: It's usually best to open your AI script with one big rule that sets all of your strategic numbers. That way, you can have easy access an sn's value at any time, rather than having to try and figure out its default value.

The Defconst and Load Commands:
I should imagine that your AI teaching so far has consisted of the rule that all commands in your scripts must start with (defrule. Well, if it has, then disregard it. While I'll be the first to admit that (defrule is the most used and important command, there are others, as I'm about to show you...

The Defconst Command:

For those of you who have programmed before, a constant in SWGB AI is the same as a constant in other languages (was that a pun?). For those of you who are just starting out on the journey of scripting and coding, then don't fret, because I'm going to explain it so simply that you'll be... oh crap, where's a metaphor when you need one?

Okay, as you've probably gathered, scripting can get quite confusing at times. You might forget how the rule you just wrote fits in with the rest of the script. You might forget what a rule was supposed to do, and just get confused by the mass of numbers and funny characters. Wouldn't it be great if we could make it a bit... simpler?

Well, gather round folks. You can! With the defconst command, it's possible to make your scripts a whole lot more readable. Observe:

(defrule
(current-age == tech-level-1)
(civilian-population >= 27)
(strategic-number sn-food-gatherer-percentage >= 40)
(strategic-number sn-food-gatherer-percentage < 75)
=>
(research tech-level-2)
)

Now, it's fairly obvious what this does. However, wouldn't it be much easier if you could refer to the numbers in the rule by name instead? Instead of typing '40', typing 'T1-Min-Gatherer-Percentage'? Well, guess what I'm about to show you...

The (defconst command is used to assign values to special names of your choice. Then, during the script, you can substitute the value for the name, and achieve the same effect. Simple, and much easier. The basic layout for a constant definition is as follows:

(defconst T1-Total-Workers 27)

This would assign the string 'T1-Total-Workers' to the value 27. Then, if you used the string anywhere in the script, the game would substitute it for the value. Meaning that:

(defconst T1-Total-Workers 27)

(defrule
(current-age == tech-level-1)
(civilian-population >= T1-Total-Workers)
(strategic-number sn-food-gatherer-percentage >= 40)
(strategic-number sn-food-gatherer-percentage < 75)
=>
(research tech-level-2)
)

Is essentially the same as:

(defrule
(current-age == tech-level-1)
(civilian-population >= 27)
(strategic-number sn-food-gatherer-percentage >= 40)
(strategic-number sn-food-gatherer-percentage < 75)
=>
(research tech-level-2)
)

Okay Einstein, what's the point? That doesn't look easier to me...

Sure, if you're using the constant in just one rule, then there's little time gained from using it. But if you were planning on using the constant in multiple rules, then it's definitely worthwhile. You see, if you had 70 occurances of a number in your script, imagine how hard it would be to change your mind on the value of that number. You would have to search through the script, and find and edit every occurance of it. It would be very easy to miss things and make mistakes, let alone get very bored in the process. If you were using a constant value 70 times in the script, however, you would need only to change one occurance, and that would be the definition of the constant. All the values would automatically be different, with just one small change.

And there you have the (defconst command. How about the other one I mentioned, (load?

The Load Command:

The (load command is very simple. All it does is load another .per file into the current AI script. While it doesn't really have any major effect of your script, it can make things much easier to read. For example, you could have one main .per file that loads all of your sub scripts, like so:

(load "Workers")
(load "Age-Advancement")
(load "Troopers")
(load "Trading")

etc...

The way it works is that when the game does a pass over the script and encounters a (load command, it will look for a file with the name specified in the command, plus a .per extension. So, the following:

(load "wonder")

...will look for a script called 'wonder.per.' It's a simple concept, and it's one that will commonly be used in large scripts.

  • You can also load files that are in a subdirectory of the Game/AI folder. Just specify "folder/file," or whatever, as the file to load. You can't load files that are outside the root AI directory, however.

    Using the (load command also means that you won't have to create a .ai file for each of your sub scripts. You will still need one for your main script that you want to be selectable in-game.

    There is another command which is similar to the (load command, which I will include in this part of the article. The (load-random command can be (pretty obviously, I should think) used to load a random .per file, instead of loading the same one each time. Observe the following example:

    (load-random 25 "rush" 25 "boom" 25 "turtle" "default")

    Firstly, you open a (load-random command by typing (duh) '(load-random .' Then, you specify a number of filenames that have a chance of being loaded. Before each filename, you state the chance (out of 100) of the file being loaded. You may also specify another file after these files, which will be the default file to load if none of the others are loaded. Here's another example:

    (load-random 50 "air-attack" "ground-attack")

    In this case, the file 'air-attack.per' would have a 50/100 chance of loading (1/2), and if it was not loaded, then the file 'ground-attack.per' would be loaded instead.

  • It is possible to not specify a default file. In this case, no script will be loaded instead of a default script. Also, only one script can be loaded per (load-random command.

    And there you have the (defconst and (load commands. There is one more thing I want to discuss before we're through; The use of AI script goals.


    AI Script Goals
    AI script goals are pretty much the closest you can get to programming variables in SWGB. For the nerdtalk-illiterate among you, that basically means that they're very poweful.

    The primary use of AI script goals is to divide your script up into specific 'tasks,' and 'switch on' various rules at different times.

    Sounds saucy. How do I use them?

    Well, first off, if that really sounded saucy to you, then you need to get a life. Secondly, you need to understand that all goals have an identifier and a value. The indentifier is basically the name of the goal (although it can't be a string; integer names only I'm afraid). The value is (and you would never have guessed this) the value that the goal currently has. In a way, goals are similar to constants, although the value of a goal can be changed (which is where its power comes from).

    The value of a goal can be set in an action of a rule, and tested in a fact. Observe:

    (defrule
    (goal 1 0)
    =>
    (set-goal 1 1)
    )

    This rule tests whether goal 1 has the value of 0. If it does, it sets the value of goal 1 to 1. Obviously, this rule on its own is pretty darn pointless, but when goals are combined with various actions and rules, you can get some nice effects. Try this:

    (defrule
    (true)
    =>
    (set-goal 1 0)
    (disable-self)
    )

    (defrule
    (goal 1 0)
    (can-train UNIT-FASTBIKE-LINE)
    =>
    (train UNIT-FASTBIKE-LINE)
    )

    (defrule
    (players-unit-type-count 1 UNIT-FASTBIKE-LINE >= 5)
    =>
    (set-goal 1 1)
    )

    (defrule
    (current-age =< TECH-LEVEL-3)
    =>
    (set-goal 1 1)
    )

    This particular script would firstly set the value of goal 1 to 0. Then, it would start producing scouts so long as goal 1's value was still 0. It would change the value of goal 1 to 1 if the player had 5 or more scouts or reached tech level 3.

    Goals, while seemingly humble at first, are extremely useful in your scripts. Believe me, if you try to make a working script for regular games, you're not going to get very far without the use of goals. Imagine them as being the AI equivalent of the 'Activate Trigger' effect in the scenario builder.


    Well, that pretty much sums up everything I planned to explain. Of course, if you have a question, or think I've left something out, then don't hesitate to contact me, or post in SWGBH's forums, but just remember that my knowledge of AI doesn't extend very far beyond the contents of this guide. I am by no means a master. In fact, I would probably recommend forwarding any questions you might have to Age of Kings Heaven, as their forumers are a lot more knowledgeable about the topic of AI scripting.

    And remember, just because you know how AI scripting works, it doesn't make you an expert. To be a really good scripter, you will need to study the game, work out the specifics of your strategies, and spend days coding. The knowledge of the AI syntax only gives you the basis with which you can achieve this with. It doesn't do it for you.

    ~ Wok

    Contact the author, Wok at walk_the_wok@hotmail.com.


  •