Difference between revisions of "AiWML"

From The Battle for Wesnoth Wiki
m (Minor wording changes)
(Update description of aggression)
Line 60: Line 60:
 
* '''ai_algorithm''': (string) Allows an alternate AI algorithm (cannot be created with WML) to be used. Besides the default, the game only comes with ''idle_ai'', which makes the AI do nothing and can be used to create a passive, unmoving side. Cannot be applied only to a set of turns or a given time of day using the keys ''turns'' and ''time_of_day'', but must be given either in an [ai] tag without the aforementioned keys or in the [side] tag outside all [ai] tags.
 
* '''ai_algorithm''': (string) Allows an alternate AI algorithm (cannot be created with WML) to be used. Besides the default, the game only comes with ''idle_ai'', which makes the AI do nothing and can be used to create a passive, unmoving side. Cannot be applied only to a set of turns or a given time of day using the keys ''turns'' and ''time_of_day'', but must be given either in an [ai] tag without the aforementioned keys or in the [side] tag outside all [ai] tags.
  
* '''aggression'''=0.4: (double) (value ranging from -infinity to 1.0) [TODO: rewrite this] This key affects how an AI side fights.  Applies to combat CA only.
+
* '''aggression'''=0.4: (double: ranging from -infinity to 1.0)  This key affects how the AI selects its attack targets.  The higher the value, the more likely the AI is to attack even if odds are not in its favor.  Applies to combat CA only.
** In the following formulas of type ''X - Y = Z'', X is always 1 and Z is the value that aggression should be set to: aggression=Z.
+
** In the attack evaluation, each attack (this includes attack combinations of several AI units against the same enemy unit) is assigned a score.  The attack with the highest score is done first.  Then the next attack is evaluated, until no attack with a score greater than 0 is found any more.  (Note that this ''attack score'' is different from the ''combat CA score'', which is always 100,000 as long as an individual attack score >0 is found.  The combat CA score is zero if the highest attack score is <=0).
** It determines how an AI considers the difference between its units and its opponents by taking the value ''1 - AI unit value in proportion to opponent unit value''.  ('Unit value' here means that the AI weights its decision on the chance to kill a unit or be killed, using a combination of its own units.)  So, to make an AI which considers its units worthless, i.e. only cares about how much damage attacks inflict, set aggression at ''1 - 0 = 1.0''.  This is the highest meaningful value for aggression; although it is insane, it is used on many HttT levels.  If an AI set on this value can inflict 1 damage and take 0, or inflict 2 damage and take 20 himself, he'll take the latter option. To make an AI which considers its units equally as valuable as its opponent's, i.e. only attacks if he feels he can inflict more damage than he receives, set aggression at ''1 - 1 = 0.0''.
+
** The attack score is a complex combination of many different aspects of the attacksPositive (additive) contributions to the score are things like the value (cost and XP) of the target, the chance to kill the target, whether it is already wounded, how much damage the attack is likely to inflict, etc. Negative (additive) factors include how much damage the AI's units are likely to take, how valuable they are, how exposed they will be after the attack, etc.  There are also multiplicative factors that are used if the attack target is a threat to the AI's leader etc.
** Although an AI which considers its opponent's units worthless is impossible under this scheme, an AI which, for example, considers its units twice as valuable as its opponent's can be represented by aggression ''1 - 2 = -1.0''.
+
** All the negative contributions to the score are multiplied by '(1-aggression)'.  This means that:
** The default is ''1 - 1/2 = 0.5'', which means the AI is content with dealing only half the damage to his opponent of that which he himself takes.
+
*** If 'aggression=1', no negative contributions are added to the scoreThus, the AI disregards damage done to its own units and selects attacks based solely on the damage it can do to enemy units.  If the AI can inflict 1 damage and take 0, or inflict 2 damage and take 20, it will take the latter option.
** Or to put this whole thing into context, aggression=1.0. means it deals the maximum possible damage, aggression=-10000000000000000.0. means it never attacks unless it will receive no damage in exchange.
+
*** The smaller the value of ''aggression'', the more weight is put on value and potential damage to the AI's units as compared to the attack target.
 +
*** Roughly speaking, 'aggression=0' results in the AI valuing its units approximately as much as the enemy units.  This is not a one-to-one relation, but can be used as an approximate guideline.
 +
*** Very large negative values of ''aggression'' mean that the value of the AI units is much more important than that of the enemy units. As a result, the AI never attacks unless it will receive no damage in exchange.
 +
** Note: ''aggression'' is always set to 1.0 for attacks on units that pose a direct threat to the AI's leader.  Currently this only means units adjacent to the leader.
  
 
*'''[attacks]'''="": Filters the units considered for combat, both on the AI and the enemy sides.  Applies to the combat CA only.  It cannot be set in the same way as the other aspects and is therefore described in a [[AiWML#Filtering_Combat_with_the_.27attacks.27_Aspect|separate section]] below.
 
*'''[attacks]'''="": Filters the units considered for combat, both on the AI and the enemy sides.  Applies to the combat CA only.  It cannot be set in the same way as the other aspects and is therefore described in a [[AiWML#Filtering_Combat_with_the_.27attacks.27_Aspect|separate section]] below.

Revision as of 21:23, 6 March 2012

[edit]WML Tags

A:

abilities, about, achievement, achievement_group, add_ai_behavior, advanced_preference, advancefrom, advancement, advances, affect_adjacent, ai, allied_with, allow_end_turn, allow_extra_recruit, allow_recruit, allow_undo, and, animate, animate_unit, animation, aspect, attack (replay, weapon), attack_anim, attacks (special, stats), avoid;

B:

base_unit, background_layer, berserk, binary_path, break, brush;

C:

campaign, cancel_action, candidate_action, capture_village, case, chance_to_hit, change_theme, chat, checkbox, choice, choose, clear_global_variable, clear_menu_item, clear_variable, color_adjust, color_palette, color_range, command (action, replay), continue, core, credits_group, criteria;

D:

damage, damage_type, death, deaths, default, defend, defends, defense, delay, deprecated_message, destination, difficulty, disable, disallow_end_turn, disallow_extra_recruit, disallow_recruit, do, do_command, drains, draw_weapon_anim;

E:

editor_group, editor_music, editor_times, effect, else (action, animation), elseif, endlevel, end_turn (action, replay), enemy_of, engine, entry (credits, options), era, event, experimental_filter_ability, experimental_filter_ability_active, experimental_filter_specials, extra_anim;

F:

facet, facing, fake_unit, false, feedback, female, filter (concept, event), filter_adjacent, filter_adjacent_location, filter_attack, filter_attacker, filter_base_value, filter_condition, filter_defender, filter_enemy, filter_location, filter_opponent, filter_own, filter_owner, filter_radius, filter_recall, filter_second, filter_second_attack, filter_self, filter_side, filter_student, filter_vision, filter_weapon, filter_wml, find_path, fire_event, firststrike, floating_text, fonts, for, foreach, found_item, frame;

G:

game_config, get_global_variable, goal, gold, gold_carryover;

H:

harm_unit, has_ally, has_attack, has_unit, has_achievement, have_location, have_unit, heal_on_hit, heal_unit, healed_anim, healing_anim, heals, hide_help, hide_unit, hides;

I:

idle_anim, if (action, animation, intro), illuminates, image (intro, terrain), init_side, insert_tag, inspect, item, item_group;

J:

jamming_costs, join;

K:

kill, killed;

L:

label, language, leader, leader_goal, leadership, leading_anim, levelin_anim, levelout_anim, lift_fog, limit, literal, load_resource, locale, lock_view, lua;

M:

male, menu_item, message, micro_ai, missile_frame, modification, modifications, modify_ai, modify_side, modify_turns, modify_unit, modify_unit_type, move, move_unit, move_unit_fake, move_units_fake, movement_anim, movement costs, movetype, multiplayer, multiplayer_side, music;

N:

not, note;

O:

object, objective, objectives, on_undo, open_help, option, options, or;

P:

part, petrifies, petrify, place_shroud, plague, poison, post_movement_anim, pre_movement_anim, primary_attack, primary_unit, print, progress_achievement, put_to_recall_list;

R:

race, random_placement, recall (action, replay), recalls, recruit, recruit_anim, recruiting_anim, recruits, redraw, regenerate, remove_event, remove_item, remove_object, remove_shroud, remove_sound_source, remove_time_area, remove_trait, remove_unit_overlay, repeat, replace_map, replace_schedule, replay, replay_start, reset_fog, resistance (ability, unit), resistance_defaults, resolution, resource, return, role, rule;

S:

save, scenario, screen_fade, scroll, scroll_to, scroll_to_unit, secondary_attack, secondary_unit, section, select_unit, sequence, set_achievement, set_extra_recruit, set_global_variable, set_menu_item, set_recruit, set_specials, set_variable, set_variables, sheath_weapon_anim, show_if (message, objective, set_menu_item), show_objectives, side, skirmisher, slider, slow, snapshot, sound, sound_source, source (replay, teleport), special_note, specials, split, stage, standing_anim, statistics, status, store_gold, store_items, store_locations, store_map_dimensions, store_reachable_locations, store_relative_direction, store_side, store_starting_location, store_time_of_day, store_turns, store_unit, store_unit_defense, store_unit_defense_on, store_unit_type, store_unit_type_ids, store_villages, story, swarm, sub_achievement, switch, sync_variable;

T:

target, team, teleport (ability, action), teleport_anim, terrain, terrain_defaults, terrain_graphics, terrain_mask, terrain_type, test, test_condition, test_do_attack_by_id, text_input, textdomain, theme, then, tile, time, time_area, topic, toplevel, trait, transform_unit, traveler, true, tunnel;

U:

unhide_unit, unit (action, scenario), unit_overlay, unit_type, unit_worth, units, unlock_view, unpetrify, unstore_unit, unsynced;

V:

value, variable, variables, variant, variation, victory_anim, village, vision_costs, volume;

W:

while, wml_message, wml_schema;

Z:

zoom;

As of Wesnoth 1.9, the default AI is the RCA AI, which stands for Register Candidate Action AI. During the AI turn, the RCA AI evaluates a number of potential actions (called candidate actions) for each move. The candidate action with the highest evaluation score is executed. Then the next move is evaluated in the same way, until no more valid moves are found. This ends the AI turn.

The RCA AI is highly configurable in three different ways (sorted by increasing complexity):

  • Changing the parameters/weights by which the AI moves (candidate actions) are evaluated. This can be done easily in the WML code by setting aspects and goals and is the content of this page.

Notes on available AIs:

  • The default AI used through Wesnoth 1.8 was named 'Default AI' (note the capital D). This might cause confusion, as the current default AI (RCA AI) is not the Default AI.
  • Besides the RCA AI, the only other AI available in the core distribution is idle_ai, which does nothing at all (see ai_algorithm below).
  • The fallback stage, which used to fall back to the old Default AI, now falls back to the RCA AI.


Evaluating AI Moves -- Candidate Actions (CAs)

When the AI does its turn, a number of potential actions (called candidate actions, CAs) are evaluated for each move. The CA with the highest evaluation score is executed and the process is repeated, until no more valid moves are found, which ends the AI turn. The following candidate actions are evaluated for each move:

  • Goto CA: Move units toward the coordinates set by goto_x, goto_y in their unit WML.
  • Recruitment CA: Recruit or recall units.
  • Move leader to goals CA: Move the leader toward goals set by [leader_goal].
  • Move leader to keep CA: Move the leader toward the closest available keep.
  • Combat CA: Attack enemy units that are in range. Note that this CA includes the move to a hex adjacent to the enemy unit as well as the attack itself.
  • Healing CA: Move units onto healing locations (generally villages).
  • Villages CA: Move units onto villages that are unoccupied or owned by an enemy.
  • Retreat CA: Evaluate if any units are in grave danger or hopelessly outnumbered, and if so, retreat them.
  • Move to targets CA: Evaluate potential targets and move units toward them. The evaluation takes into account how valuable the targets are (this is configurable by setting goals), how easily the AI's units can get there, how exposed to attacks they will be, etc. Targets are enemy leaders, enemy units that pose a thread to the AI leader and villages, as well as anything defined as a goal. It is important to understand that targets only apply to the move-to-targets CA. If a target is within range of the AI's unit(s) on the current turn, other CAs, such as combat, healing and villages have a higher evaluation score and are (usually) executed first. Thus, a target is specifically not an attack target, but a move target.
  • Passive leader shares keep CA: Move the leader off the keep to let an allied leader recruit.

If several candidate actions are possible for the next move, they are prioritized in the order given above. For example, the Goto CA is always the first move that is done (if goto_x and goto_y are defined and a move toward them is possible). If a valid attack is found in the combat CA, it is always executed before any healing, village grabbing or move-to-target moves. One of the effects this has is that attacks always have priority over any goals/targets of the move-to-target phase.

Aspects and Goals Overview

'Aspects' are configurable parameters used in the candidate action score evaluation. Most commonly, aspects are set using the [ai] tag, but there are several other methods as well, as described here and here. There are simple aspects which take on an individual value, and composite aspects that might contain several keys or sub-tags. The sub-tags of aspects are called 'facets'.

'Goals', which define AI 'targets', are composite aspects that influence the behavior of the AI in the move-to-targets candidate action. As there a several different goals available, they are given their own subsection below.

The [ai] Tag: Defining Aspects

The [ai] tag is used inside a [side] or [modify_side] tag. Its contents are the AI aspects and define how the AI controlling that side acts. You can use multiple [ai] tags and have them apply during different turns or times of day, for example to set an undead side's caution higher and aggression lower during the day, or to boost the aggressiveness of a side when it receives reinforcements on a specific turn.

The following keys/tags can be used in an [ai] tag:

  • time_of_day: (string) The time(s) of day when the AI should use the parameters given in this [ai] tag. Possible values are listed in data/core/macros/schedules.cfg (See also TimeWML).
  • turns: (string) During which turns the AI should use the parameters given in this [ai] tag. This takes the same syntax of dashes (-) and commas (,) as is described under Filtering Locations in FilterWML, except of course they apply to turns not locations.
  • ai_algorithm: (string) Allows an alternate AI algorithm (cannot be created with WML) to be used. Besides the default, the game only comes with idle_ai, which makes the AI do nothing and can be used to create a passive, unmoving side. Cannot be applied only to a set of turns or a given time of day using the keys turns and time_of_day, but must be given either in an [ai] tag without the aforementioned keys or in the [side] tag outside all [ai] tags.
  • aggression=0.4: (double: ranging from -infinity to 1.0) This key affects how the AI selects its attack targets. The higher the value, the more likely the AI is to attack even if odds are not in its favor. Applies to combat CA only.
    • In the attack evaluation, each attack (this includes attack combinations of several AI units against the same enemy unit) is assigned a score. The attack with the highest score is done first. Then the next attack is evaluated, until no attack with a score greater than 0 is found any more. (Note that this attack score is different from the combat CA score, which is always 100,000 as long as an individual attack score >0 is found. The combat CA score is zero if the highest attack score is <=0).
    • The attack score is a complex combination of many different aspects of the attacks. Positive (additive) contributions to the score are things like the value (cost and XP) of the target, the chance to kill the target, whether it is already wounded, how much damage the attack is likely to inflict, etc. Negative (additive) factors include how much damage the AI's units are likely to take, how valuable they are, how exposed they will be after the attack, etc. There are also multiplicative factors that are used if the attack target is a threat to the AI's leader etc.
    • All the negative contributions to the score are multiplied by '(1-aggression)'. This means that:
      • If 'aggression=1', no negative contributions are added to the score. Thus, the AI disregards damage done to its own units and selects attacks based solely on the damage it can do to enemy units. If the AI can inflict 1 damage and take 0, or inflict 2 damage and take 20, it will take the latter option.
      • The smaller the value of aggression, the more weight is put on value and potential damage to the AI's units as compared to the attack target.
      • Roughly speaking, 'aggression=0' results in the AI valuing its units approximately as much as the enemy units. This is not a one-to-one relation, but can be used as an approximate guideline.
      • Very large negative values of aggression mean that the value of the AI units is much more important than that of the enemy units. As a result, the AI never attacks unless it will receive no damage in exchange.
    • Note: aggression is always set to 1.0 for attacks on units that pose a direct threat to the AI's leader. Currently this only means units adjacent to the leader.
  • [attacks]="": Filters the units considered for combat, both on the AI and the enemy sides. Applies to the combat CA only. It cannot be set in the same way as the other aspects and is therefore described in a separate section below.
  • [avoid]="": Makes the AI avoid specific locations. The AI never moves a unit to these locations except for trying to move its leader to a keep or toward [leader_goal]s, and thus applies to all CAs except move-leader-to-goals and move-leader-to-keep.
  • caution=0.25: (double) [TODO: rewrite this] A number 0.0 or higher that affects how cautious the AI is in various ways. If it is higher, the AI wants to retreat more (especially to a better defensive terrain), it dislikes moving out of a defensive terrain to attack more, and, if grouping is enabled, it forms bigger groups to protect from enemies when going to a target. Applies to most CAs.
    • Considering a retreat to be skipped (during the retreat phase) if caution is set to 0 or lower. If caution is greater than 0, there is an evaluation of forces for the map location a unit stands on. This is basically the sum of damage that can be done to that location, reduced by terrain defense and relative hitpoints if attackers don't have full health. A retreat takes place, if
      caution * their_power > our_power
      There is also a terrain factor involved if the attacker is not on optimal terrain, but i'll skip this here for simplicity.
      So let's say the AI has its default caution of 0.25. Then the enemy forces have to be 4 times as strong at least before the unit retreats. For a caution of 1, as soon as the enemy is considered to be stronger, the unit retreats.
    • Caution is also considered within the attack analysis to evaluate the rating of attacks. The mathematics for that is rather complicated, so let's just state that the caution value directly and proportionally influences a value for reducing the rating of an attack. A caution of 0 has no influence (the rating won't be reduced at all).
    • If grouping for the AI is enabled and the path to move the group along is considered to be dangerous, caution has an influence, too. "Dangerous" mainly means there is a possibility for the best attacker of a primary target to be severely harmed, for example if he can be killed by enemies if not protected. In that case, the AI compares our group with the enemy and based on the result moves forward or not. "Group", from what i understood, means every unit of that side, including the leader. So it's not really a coherent group necessarily. Our side is rated which is mainly the sum of maximum damage * defense modifiers for every unit. Then this is compared to the enemy strength by determining a relation of both. A value of 1 means both sides have equal strength. Higher values indicate our side is stronger, lower values likewise. The formula for deciding if a group moves on is:
      our_strength / their_strength > 0.5 + caution
      So if caution is 0.5, our side needs to be at least as strong as the enemy. If it is 0, we go on, even if the enemy is up to twice as strong as we are. Setting caution to 1.5 means we need to be at least twice as strong to have the group keep moving.
    • Caution also influences putting units onto villages. However, i haven't understood the code well enough yet to make backed up statements about it. Probably it's an estimation about how exposed a unit will be on that village, based on how many damage the enemy can do to it.
    • Caution often won't influence the AI if the leader is involved, because for many actions it is hardcoded with a value of 2.0.
  • [goal]="": Defines units or locations as targets for the AI. See the dedicated section on the [goal] tag below for details. Applies to move-to-targets CA only.
  • grouping="offensive": (string) How the AI should try to group units. Applies to move-to-targets CA only. Possible values:
    • offensive: Makes the AI try to group units together before attacking.
    • defensive: Makes the AI group units together very conservatively, only advancing them much beyond its castle if it has overwhelming force.
    • no: Makes the AI use no grouping behavior.
  • leader_aggression="-4.0": Exactly as aggression, but for units which can recruit. Applies to combat CA only. Note that the move-leader-to-keep CA has a higher score than the combat CA. A leader therefore usually only attacks if he is on his keep at the beginning of the turn, otherwise he moves toward the closest keep instead, even with leader_aggression=1.
  • [leader_goal]="": Makes the AI try to move its leader to a specific location. Applies to move-leader-to-goals CA only.
    • x, y: The location toward which the AI should move its leader.
  • leader_value=3: (double) A number 0 or higher which determines the value of enemy leaders as targets. Affects move-to-targets CA only (and therefore specifically does not apply to attacks).
  • passive_leader=no: (bool) If 'yes', the AI leader never moves or attacks (not even to move back to the keep or to attack adjacent units), except to obey [leader_goal]s. Affects all CAs except recruitment and move-leader-to-goals.
  • passive_leader_shares_keep=no: (bool) If 'yes', this has most of the effects of passive_leader=yes, but the AI leader moves 1 hex from the keep to share it with allied players (if they can reach it next turn). He also returns to keep to recruit when possible and attacks adjacent enemy units.
  • recruitment="": To be added.
  • recruitment_ignore_bad_combat=no: (bool) If 'yes', the AI does not analyze the enemy units on the map to see if the unit to be recruited is suitable for fighting them. Affects recruitment CA only.
  • recruitment_pattern="": (string) This key takes a comma separated list containing the usages of the units that can be recruited. Common usages are: 'scout', 'fighter', 'archer', 'healer' and 'mixed fighter'. This tells the AI with what probability it should recruit different types of units. The AI considers all units with the specified usage(s) and only those, so make sure the units you want recruited are really covered by the pattern, or use an empty pattern (the default) to have it consider all available units. The usage is listed in the unit type config files (see data/core/units/ for mainline units; see also UnitTypeWML). Affects recruitment CA only.
    • For example, "recruitment_pattern=fighter,fighter,archer" means that the AI recruits on average twice as many fighters as archers, and does not recruit scouts (other than scouts for capturing villages, which are recruited independently), healers or mixed fighters. It does not mean that it recruits two fighters first, then an archer, then two fighters again, etc.
  • scout_village_targeting=3: (double) The AI multiplies the value of village targets for scouts by this value. Affects move-to-targets CA only.
  • simple_targeting=no: (bool) If 'yes', the AI moves its units toward targets one by one (sequentially), without considering whether another unit might be better suited for the current move or target. If 'no' (the default), all units are considered for all targets. This is slower, but might result in better moves. Affects move-to-targets CA only.
  • support_villages=no: (bool) Trigger a code path that tries to protect those villages that are threatened by the enemy. This seems to cause the AI to 'sit around' a lot, so it's only used if it's explicitly enabled. Affects move-to-targets CA only.
  • village_value=1: (double) A number 0 or higher which determines how much the AI tries to go for villages as targets. Affects move-to-targets CA only.
  • villages_per_scout=4: (int) A number 0 or higher which determines how many scouts the AI recruits. If 0, the AI doesn't recruit scouts to capture villages. Affects recruitment CA only.

Deprecated AI Targeting Aspects

The following AI targeting parameters (aspects) currently still work, but have been superseded by the [goal] tag. They should not be used any more as they will likely be removed at some point. These tags specify targets and only apply to the move-to-targets CA.

  • [target]="": Deprecated. Any number of [target] tags can be used to set targets for the AI. For anything related to 'values', set them relative to other targets. An AI is willing to dedicate twice as many resources and travel twice as far to get to a target worth '2.0' as for a target worth '1.0'. Applies to move-to-targets CA only.
    • StandardUnitFilter: Do not use a [filter] tag.
    • value=1: (double) A number greater than 0 (default=1) which determines how much the AI tries to move toward units which pass the filter.
  • [protect_location]="": Deprecated. Gives the AI a location to protect. Note that the AI does not station any units around the location, it only sends units to attack any enemy units that come within the guarding radius of the target. Applies to move-to-targets CA only.
    • x, y: Standard coordinates. These indicate the location the AI is protecting.
    • radius: The radius around it to protect (0 indicates a single hex).
    • value: The importance of protecting this location.
  • [protect_unit]="": Deprecated. Gives the AI a set of units to protect. Note once again that the AI does not place units around the protected units if there are no enemies nearby. Applies to move-to-targets CA only.
    • StandardUnitFilter: The unit(s) to protect. Do not use a [filter] tag.
    • radius: The radius around it to protect (0 indicates a single hex).
    • value: The importance of protecting this unit.
  • protect_leader=2.0 and protect_leader_radius=10: Deprecated. Target any enemy units that come within 'protect_leader_radius' of the AI leader with a value of 'protect_leader'. Applies to move-to-targets CA only.

Removed AI Aspects

The following AI parameters (aspects) can still be set, their values can be retrieved, and they can be viewed in the gamestate inspector dialog, but they do not seem to have an effect in the RCA AI code any more. Some other parameters will also likely be removed in the future. We will update this list accordingly.

  • attack_depth=5: (int)
  • number_of_possible_recruits_to_force_recruit=3.1: (double)
  • recruitment_ignore_bad_movement=no: (bool)

AI Targets and Goals

AI targets are used in the move-to-targets candidate action (CA) to move the AI's units toward selected units or locations. The AI engine automatically selects all enemy leaders, enemy units that pose a thread to the AI leader and unowned or enemy-owned villages as targets and assigns them certain base values. A score is then assigned to each target based on this base value, as well as on the movement cost required to get to the target, whether moving there would put the units involved in danger, etc.

It is possible to define additional targets or influence the relative ratings of the default targets. This is done with the [goal] tag, in which we can set criteria selecting targets and define their base values (which are then evaluated in the same way as the values of the default targets). Values set with the [goal] tag should always be relative to each other. The AI is willing to dedicate twice as many resources and travel twice as far to get to a target worth '2.0' as for a target worth '1.0'.

We stress again that these targets apply to the move-to-targets CA only and have no influence on other CAs. This is significant since CAs that deal with, for example, combat or village-grabbing have a higher score than the move-to-targets CA and are therefore always executed first. In practice that means that targets set with the [goal] tag only affect the AI behavior for targets that it cannot reach during the current turn, and only after combat, village-grabbing etc. are finished.

Five types of targets can be set using the [goal] tag: target (the default), target_location, protect_unit, protect_my_unit, and protect_location.

The [goal] Tag

The [goal] tag specifies target units or locations toward which the AI should move its units in the move-to-targets CA. The following keys/tags can be used:

  • name="target": (string) The following values are possible and result in different types of targets:
    • target: The (default) target goal specifies target units (not necessarily enemy units) toward which the AI should move its units.
    • target_location: Specifies target locations toward which the AI should move its units.
    • protect_location: Specifies locations that the AI should protect. Enemy units within the specified distance (protect_radius) of one of these locations are marked as targets with the provided value. Note that the AI will not station any units around the protected locations. It will only send units toward enemy units that come within protect_radius of them.
    • protect_unit: Specifies units (of all sides) that the AI should protect. Enemy units within protect_radius of one of these units are marked as targets with the provided value. Note once again that the AI will not place units around the protected units if there are no enemies nearby.
    • protect_my_unit: Specifies units from the AI's own side that the AI should protect. (This is basically the protect_unit goal with an implied side= in the filter, restricting matching units to the AI's side.) Enemy units within protect_radius of one of these units are marked as targets with the provided value. Note once again that the AI will not place units around the protected units if there are no enemies nearby.
  • value=0: (int) The value of the goal.
  • protect_radius=20: (int) The protection radius. Applies to protect_location, protect_unit and protect_my_unit.

Examples of [goal] Tag Usage

target:

[goal]
    [criteria] #NOTE: this is a SUF, because we're targeting a unit
        side=3
    [/criteria]
    value=5
[/goal]

target_location:

[goal]
    name=target_location
    [criteria] #NOTE: this is a SLF, because we're targeting a location
        x,y=42,20
    [/criteria]
    value=5
[/goal]

protect_location:

[goal]
    name=protect_location
    [criteria] #NOTE: this is a SLF, because we're protecting a location
        x,y=42,20
    [/criteria]
    protect_radius=16
    value=5
[/goal]

protect_unit:

[goal]
    name=protect_unit
    [criteria] #NOTE: this is a SUF, because we're protecting a unit
        side=3
    [/criteria]
    protect_radius=16
    value=5
[/goal]

protect_my_unit:

[goal]
    name=protect_my_unit
    [criteria] #NOTE: this is a SUF, because we're protecting a unit
        canrecruit=yes
    [/criteria]
    protect_radius=9
    value=10
[/goal]

To modify goals from WML events, several helper macros are available in data/core/macros/ai.cfg.

Filtering Combat with the 'attacks' Aspect

The 'attacks' aspect lets us filter the units considered during the combat candidate action. Units on the AI side can be selected with the [filter_own] tag and enemy units are filtered via [filter_enemy]. Only units defined in these tags are considered as attacker/target pairs. To define, for example, an 'attacks' aspect in which units from the elvish sorceresses are the only attackers, and undead units are the only targets, use either

[ai]
    [aspect]
        id=attacks
        [facet]
            invalidate_on_gamestate_change=yes
            [filter_own]
                type=Elvish Sorceress,Elvish Enchantress,Elvish Sylph
            [/filter_own]
            [filter_enemy]
                race=undead
            [/filter_enemy]
        [/facet]
    [/aspect]
[/ai]

or

[ai]
    [modify_ai]
        side=2
        action=add
        path=aspect[attacks].facet[]
        [facet]
            invalidate_on_gamestate_change=yes
            [filter_own]
                type=Elvish Sorceress,Elvish Enchantress,Elvish Sylph
            [/filter_own]
            [filter_enemy]
                race=undead
            [/filter_enemy]
        [/facet]
    [/modify_ai]
[/ai]

Several important notes:

  • See A Bit More on Simple vs. Composite Aspects and Adding and Deleting Aspects with the [modify_ai] Tag below for explanations of the syntax used in these examples.
  • This only works if 'invalidate_on_gamestate_change=yes' is set, so that the available attacker/target pairs are recalculated after each move.
  • If [filter_own] or [filter_enemy] are omitted, the selection defaults to all units of the respective sides.
  • Most importantly: The above examples result in the sorceresses-on-undead attacks being the only attacks done by the AI. No other attacks are executed, no matter how advantageous their outcomes might be. There is no simple way to get around that. An example of dealing with this (executing all attacks of a certain kind first, then doing whatever possible attacks are left afterward) by setting up an additional Lua candidate action can be found in scenario 'Protect the Wizard' of campaign 'AI modification demos'. This example will also be added to the Lua_AI_Code_Library at some point.

A Bit More on Simple vs. Composite Aspects

We stated above that aspects can be either simple or composite, simple aspects being those that take on an single (scalar) value, while composite aspects have a more complex structure containing subtags called 'facets'. That is only partially true in that, internally, all aspects are set up as composite aspects. For example, if a simple aspect is defined like this in a [side] or [modify_side] tag

[ai]
    aggression=0.765
[/ai]

the AI engine turns that into

[aspect]
    engine=cpp
    id=aggression
    name=composite_aspect
    [facet]
        engine=cpp
        name=standard_aspect
        value=0.765
    [/facet]
    [default]
        engine=cpp
        name=standard_aspect
        value=0.4
    [/default]
[/aspect]

This can be seen, for example, in savefiles. This shows the aspect id, 'aggression', and the engine for which it is defined, the default 'cpp' engine. It then shows two facets, the one we just defined with the new value of 0.765, and the facet containing the default settings.

Taking this one step further, if we look at 'ai config full' for a given side using the gamestate inspector dialog in debug mode, it shows all keys that can be set for this aspect (with their default values for those that we did not specify):

[aspect]
    engine=cpp
    id=aggression
    invalidate_on_gamestate_change=no
    invalidate_on_minor_gamestate_change=no
    invalidate_on_tod_change_change=yes
    invalidate_on_turn_start=yes
    name=composite_aspect
    [facet]
        engine=cpp
        id=
        invalidate_on_gamestate_change=no
        invalidate_on_minor_gamestate_change=no
        invalidate_on_tod_change_change=yes
        invalidate_on_turn_start=yes
        name=standard_aspect
        time_of_day=
        turns=
        value=0.765
    [/facet]
    [default]
        ...  (omitted for brevity, same as above)
        value=0.4
    [/default]
[/aspect]

Thus, we see, that the facet may also contain an id (important if one wants to delete it selectively), the time of day and turns for which it is valid, and several invalidate_on_... keys. The latter ... (TODO: need to be explained).

Long story short, the definition of a simple aspect like

[ai]
    aggression=0.765
[/ai]

is a syntactical shortcut for (to be used in [side] or [modify_side] tags)

[ai]
    [aspect]
        id=aggression
        [facet]
            value=0.987
        [/facet]
    [/aspect]
[/ai]

The former syntax is simpler for simple aspects without facet ids, but the latter is needed if a facet id is required, or for the definition of some more complex aspects such as attacks. Alternatively, composite aspects can also be defined using the [modify_ai] tag or with the help of several macros available in data/core/macros/ai.cfg.

Adding and Deleting Aspects with the [modify_ai] Tag

Aspects can also be changed, defined or deleted with the [modify_ai] tag, which can be used either in [side][ai] or in an [event]. [modify_ai] has the following keys for modifying aspects:

  • side: If used in an [event] tag, the side of the AI to be modified. Not needed if used in [side][ai].
  • action: The action to take concerning the aspect. The following values are allowed:
    • add: Add the aspect.
    • delete: Delete the aspect.
    • try_delete: Delete the aspect if it exists. This does not produce a warning if [modify_ai] fails to change the AI.
  • path: Defines the aspect to be modified. Its values are of form aspect[id_of_aspect].facet[facet_identifier] and the [facet] subtag needs to be defined:
    • [facet]="": Definition of the facet (the details about the aspect and the conditions under which it applies) in the same format as shown above.

Here, 'id_of_aspect' is the id/name of the aspect (such as 'aggression'). The 'facet_identifier' is meaningless for adding an aspect (in fact, it can be empty for that; it does not set the facet id, that needs to be done with the id key inside the [facet] tag), but needs to be defined when deleting aspects, in which case it can take on the following values:

  • *: Deletes all facets of an aspect, except the [default] facet.
  • A non-negative integer: This identifies the facets in the order in which they were created.
  • The facet id as defined in the [facet] subtag.

Let's illustrate all this with a few examples. In order to define aggression in the same way as above, we could use

[modify_ai]
    side=1
    action=add
    path=aspect[aggression].facet[]
    [facet]
        value=0.765
    [/facet]
[/modify_ai]

This version of the aggression aspect can then be deleted using

[modify_ai]
    side=1
    action=delete  # or try_delete
    path=aspect[aggression].facet[0]
[/modify_ai]

if we know that it is the first non-default facet of the aggression aspect or with

[modify_ai]
    side=1
    action=delete  # or try_delete
    path=aspect[aggression].facet[*]
[/modify_ai]

along with all other user-defined aggression facets that might have been added. (Note that the aspect itself and its default facet cannot be deleted.) These two methods of deleting the facet also work if the value of aggression was simply set by using 'aggression=0.765' in the [ai] tag.

Sometimes we might want to define several facets of the same aspect, but don't know in advance in which order they will be created. If we then only want to delete some of them selectively, we need to use facets with specific id's, such as

[modify_ai]
    side=1
    action=add
    path=aspect[aggression].facet[]
    [facet]
        id=aggression_dusk
        time_of_day=dusk
        value=0.765
    [/facet]
[/modify_ai]

This can be deleted by referencing that id

[modify_ai]
    side=1
    action=delete  # or try_delete
    path=aspect[aggression].facet[aggression_dusk]
[/modify_ai]

It is, of course, unnecessary to add or delete a simple aspect like aggression in this way, as it can simply be reset by using another 'aggression=value' statement in an [ai] tag. In fact, this can be done with any aspect. If several facets are set for the same aspect for the same (or overlapping) turns and times of day, the one set up last takes effect. Note, however, that this is not accomplished by modifying the existing facets, but by adding an additional facet to the aspect. Deleting a facet is, in principle, a cleaner method of dealing with this, although that usually won't make much of a difference unless the same aspect is changed many times throughout a scenario.

A large number of helper macros for AI modifications is available in data/core/macros/ai.cfg.

Using [modify_ai] to Change Goals, Candidate Actions and Stages

For completeness, we mention here that [modify_ai] can also be used to add and delete AI goals, stages and candidate actions by using the following values for the path key:

  • goal[id_of_goal]: Goal with specified id
  • goal[0]: Goal 0 (first)
  • goal[1]: Goal 1
  • goal[*]: All goals
  • stage[id_of_stage]: Stage with specified id
  • stage[0]: Stage 0 (first)
  • stage[1]: Stage 1
  • stage[*]: All stages
  • stage[id_of_candidate_action_evaluation_loop_stage].candidate_action[id_of_candidate_action]: Candidate action with specified id of stage with specified id.
  • stage[id_of_candidate_action_evaluation_loop_stage].candidate_action[*]: All candidate actions of stage with specified id.

Also see the helper macros in data/core/macros/ai.cfg.

Finally note that [modify_ai] in [side][ai] is activated when the AI is first initialized. It is guaranteed to happen before the AI acts for the first time, but otherwise it can happen at any time, for example, when the game is saved. So, for example, you might see the uninitialized version of the config (with unapplied [modify_ai] tags) if using ':inspect' right after the scenario start.

See Also