STAR WARS™ Empire at War: Gold Pack

STAR WARS™ Empire at War: Gold Pack

56 ratings
Advanced Modding: Empire at War AI
By evilbobthebob
This is an overview of the AI system in the game, designed for use by modders wishing to improve the base game's AI or allow the AI to correctly use their mod's features.
   
Award
Favorite
Favorited
Unfavorite
Introduction
This guide is designed to be an introduction to the AI systems in the game Star Wars: Empire at War and its expansion. The game runs in the Alamo engine, and so the general outline of the AI written here may be helpful for other games using the same engine.

This guide may never be fully completed. However, it will hopefully aid modders wishing to make changes major or minor to the way the AI in the game works.

I recommend you have your favourite XML/Lua editor(s) ready to look through files as you read this guide.

Getting the AI to Actually Work

So you wrote all the fancy stuff already and you want to get it working? Easy. Put your modded perceptual equations and evaluator scripts into a MEG file. Make sure the internal MEG file directories are set up the same as if they were extracted, except with the root folder as Data. Put the MEG file name into MegaFiles.XML. Now your mod has edited AI! Hopefully Petroglyph makes it so the game reads modded perceptions soon.


Drieck created a guide back in 2008 that covers much of this groundwork, you may find it useful as well: http://www.petrolution.net/item-206?apage=191
Overview: AI framework
The AI in EAW is a finite state machine: it exists in a particular state that changes based on input. It is built on a basic flow:

  • Budget
  • Goal
  • Plan

A budget is the amount of credits (or in the case of tactical mode, effort) that the AI will expend upon a set of Goals that are part of the budget category. A budget may be "how much will I spend on buildings?"

A Goal is an individual task like "build a unit" or "attack that planet". Goals are grouped into categories that are controlled by the budget for each category.

A Plan is a method by which the AI will attempt to achieve its Goal. Plans are what you, the player, sees the AI do based on its Goals and Budgets.

Budgets, Goals and Plans are controlled by the AI's eyes on the game: Perceptual Equations. These are algebraic/logic switches that tell the AI what to prioritise and what to ignore.

Modding Note

Budgets and Goals are controlled in XML via the Data/XML/AI folder and subfolders. Plans are Lua scripts, located in Data/Scripts/AI/

The Player
The Player is the ultimate control for the AI. It compiles the budgets and goals using a Template and handles enacting plans. The Player defines the difficulty adjustment settings for each difficulty from DifficultyAdjustments.XML. They also control the FreeStore script used by the AI (see later chapters).

The Player assigned to a faction is controlled via the Galactic Conquest XML tag <AI_Player_Control>

Player Files

Located in: Data/XML/AI/Players

Each Player has a Galactic FreeStore script, and Space/Land tactical FreeStore scripts defined in their XML files with appropriate tags.

The GoalProposalFunctionSet tag decides what sets of goal-function pairs the AI player will use to control its activity. These are a list of names of the XML files located in Data/XML/AI/GoalFunctions that you want the AI to use. More on Goal-Function pairs later.

The Templates tag decides what templates each major mode will be using. See below for information about Templates.

The Difficulty_Adjustment tag chooses the difficulty levels from the DifficultyAdjustments XML file. This allows you to set up different difficulty scaling for different players.

Template Files

Located in: Data/XML/AI/Templates

Templates lay out the Budgets, Goals and Plans for the Player that uses them.

The Priority tag controls when the template should fire- Priority 1 before 2, etc.

The Trigger tag turns entire templates on or off based on a perceptual equation. An example of this is the Empire's default template. It has a switch for tech advancement/normal behaviour.

The Budget tag contains a list of tags for possible budget categories used by the template. The possible categories are below, taken from the AIGoalCategoryType.XML file found in Data/XML/Enum/. The amount of value in each budget is controlled by a perceptual equation inside the goal category type tag.

<Offensive>0</Offensive>
<Defensive>1</Defensive>
<Information>2</Information>
<Infrastructure>3</Infrastructure>
<Tactical_Targeted>5</Tactical_Targeted>
<Tactical_Untargeted>6</Tactical_Untargeted>
<NoBudget>7</NoBudget>
<Hero>8</Hero>
<Always>9</Always>
<Macro_Goal>10</Macro_Goal>
<MajorItem>11</MajorItem>
<StoryArc>12</StoryArc>
<StoryArcHuman>13</StoryArcHuman>
<Interventions>14</Interventions>
<High_Priority>15</High_Priority>
<Sandbox_Events>16</Sandbox_Events>
<Med_Priority>17</Med_Priority>
<Map_Control>18</Map_Control>

Following the Budget are the controls for which goal and plan categories can be used. This is done via the Turn_On and Turn_Off tags. This can be used to selectively toggle goal categories after a template switch, to make the AI become dormant or active as necessary.

As always, it is recommended to look at the vanilla files for context.
Budgets
Budgets control goal categories. They determine how many credits the AI should expend in a given category. They are assigned via Templates to Players and their value is decided by perceptual equations.

Budgets pull from a fraction of the current credit pool. For example:

If the Infrastructure budget is evaluated at a value of 40, and the Offensive budget is evaluated at a value of 60, then the AI with 1000 credits will spend 400 credits on infrastructure goals and 600 credits on offensive goals.

Budgets allow the AI to react to the situation, changing which goals it will fund. For example, you probably want the AI to build some structures on its newly acquired planet, so you write the Infrastructure budget equation to increase in value when the AI has empty base slots.

Remember: budgets are galaxy (or map) wide: they shouldn't be focused on individual units or planets.
Goals
Location: Data/XML/AI/Goals

Goals are the aims of the Player. They are grouped into categories, which are activated by the player template. The value of a Goal to the Player is determined by a perceptual equation, which is linked to the goal using a GoalFunction XML file.

The AI ranks goals according to their value in each category independently of the other categories. The goal cost is checked against the budget for the category. If the goal is within budget, it is passed. This process repeats until the budget is empty. If there is spare budget in the category, goals that did not make the value cutoff are randomly chosen.

Goal XML Tag Details

AIGoalApplicationFlags

The application of the goal, in an identify Friend-Foe sense: enemy, neutral, friendly, global, tactical_location, enemy_unit, friendly_unit, friendly_reinforcement_point, enemy_reinforcement_point, enemy_structure, friendly_build_pad

This limits the potential targets of the goal. This is important when you consider the perceptual equation linked to the goal, because the target may not match the equation's considerations.

GameMode

Galactic, Space, Land: the game mode that the goal applies to.

Category

Budget category of the goal.

Reachability

Connection to the AI- in galactic context, this is planetary, in tactical, it is "threat" (see later for more about threat).

Galactic mode reachability:
  • Any - Any location
  • Friendly - Any friendly location
  • Enemy_destination - Any directly connected enemy location
  • Single_Hop_Disconnected - A planet that separates two friendly planets
  • Enemy_Undefended - Enemy planets that can be pathed to without engaging forces. e.g. with no space defences. Also applies to stealth unit/raid fleet pathing

Tactical mode reachability:

These are threat levels defined in GameConstants.xml. They most likely correspond to the combat power of the given goal target, be it an entire map region or an individual unit.

  • Any_Threat
  • Low_Threat
  • Medium_Threat
  • High_Threat

Time_Limit

This seems to be the time allowed to spend trying the goal before giving up. Very difficult to test.

Build_Time_Delay_Tolerance

This is a multiplier of the total TaskForce build time, and lets the goal pass if production can be enacted within the total time. So if a speeder takes 20s to build, the tolerance is 1.5, the goal will allow a wait of up to 10s before production commences.

Is_Like

Similar goals within the same category, preventing the AI from activating goals that achieve similar things on the same target. e.g. all building construction Is_Like all other building construction.

Tracking_Duration

Time spent tracking the target of the goal in the case of failure e.g.
"if the stealth unit failed in their plan (was killed) within the last 10 minutes the AI won't try that target again."

Per_Failure_Desire_Adjust


Reduce the value of the goal perceptual equation by this amount every time the plan fails.

Activation_Tracking_Duration

How long to "track" the goal once it is started, to allow for failure desire reduction to apply.

Per_Activation_Failure_Desire_Adjust


Reduce the value of the goal perceptual equation by this amount every time the plan fails to even begin.

Global_Exclusions


Completely prevent goals in this list from being attempted while this goal is active.
Plans
Location: Data/Scripts/AI/[LandMode/SpaceMode]

Plans are where Goals are put into action. Plans are Lua Scripts that run when a goal has been activated. The AI uses the Plan script to produce a TaskForce to achieve the goal, and then execute movements or production as necessary.

Plans are linked to Goals via the Category variable in the Definitions function of the script. Multiple plan scripts can be linked to the same Goal, however I am unsure as to how the AI decides which plan to use.

Modding Note:

The Set_As_Goal_System_Removable function in Plan scripts is very important: it tells the AI that once the goal has been activated and the plan has started it cannot be stopped until the script completes. This can be useful if you have goals that may rapidly become obsolete, but that you still want to run their course once activated.
Perceptual Equations
Location: Data/XML/AI/PerceptualEquations

Perceptual equations are how the AI "sees" the game. They are algebraic/logic functions that return a value (or nil if they cannot evaluate).

The syntax consists of:

<Perceptual_Function_Name>

The tag for the function name. Must be unique.

Operators

+ the OR operator
* the AND operator
- reduce the equation value, NOT operator
/ division
# generate a random value between two numbers e.g. (1 # 2)
() evaluate this first
> greater than operator. DO NOT USE < IT IS XML RESERVED
{ } Used to add parameters to tokens

Tokens

These will be broken into sections for clarity. The full list is in Data/XML/AI/Enum/PerceptionTokenType.XML

Tokens go as:

Variable.Literal.Final_Literal {Parameters}

and can return any number, but often are normalized to the range 0-1

Important Tokens

Script_ScriptName.Evaluate evaluates the script ScriptName that is placed in the Data/Scripts/Evaluators folder. These scripts need a function called Evaluate that returns the value to the perception.

Function_FunctionName.Evaluate evaluates the perceptual equation FunctionName and returns its value.

REMEMBER: perceptual equations and evaluator scripts only work from inside a MEG file!


Main variables:

These are the top-level of a token, and must be related to either the Goal application or the variable(s) sent from a Lua script's EvaluatePerception (more on that later).

e.g. if the goal application is Friendly_Only then Variable_Self would evaluate to the Player trying the goal.

<Variable_Self> </Variable_Self> PlayerObject evaluating perception
<Variable_Location> </Variable_Location> Unused
<Variable_Target> </Variable_Target> Target object evaluating perception e.g. planet
<Variable_Enemy> </Variable_Enemy> ALL enemies based on PlayerObject evaluating perception
<Variable_Human> </Variable_Human> Human playerobject

To see more tokens, see the Token sections at the end of the guide.
Perceptual Equations: Working Examples
Let's take some examples from Petroglyph and deconstruct them in plain language. Petroglyph has done this themselves in some commentsin the XML, so look around yourself!

<Should_Conquer_Opponent_Planet> ( 1.25 * Function_Is_Good_Invasion_Target.Evaluate * Variable_Target.IsHumanControlled * ((Variable_Self.HasUnit {Parameter_Type = "DEATH_STAR"} == 0.0) + (Function_Want_To_Fire_DS.Evaluate == 0.0)) ) * Function_First_Attack_Allowed.Evaluate * (1.0 - Function_Should_Perform_Unrestricted_Grab_Space.Evaluate) </Should_Conquer_Opponent_Planet>

This is the equation that controls the Goal Conquer_Opponent_Planet. This is for when the AI wants to fight you!

Here we begin with a constant scaling factor of 1.25, followed by Function_Is_Good_Invasion_Target.Evaluate. This tells the game to evaluate ANOTHER equation, called "Is_Good_Invasion_Target" and return its value.

So we have:

Scaled by 1.25
AND
Is a good target to invade
AND
Is human controlled (note that the Variable_Target here is the enemy planet, and this token returns 0 or 1)
AND
I don't have the Death Star, nor do I want to fire it at this planet
AND
I'm allowed to attack
AND
I shouldn't try to take the space over this planet quickly

Here the 1 - [equation] acts like a NOT operator.

Example 2: Need Research Facility

<Need_Research_Facility> Variable_Self.IsFaction {Parameter_Faction = "Empire"} * (1 - Variable_Target.HasStructure {Parameter_Type = "E_Ground_Research_Facility"}) * Function_Has_Enough_Bases_For_Tech.Evaluate * (Variable_Target.MaxStarbaseLevel == 5) * ( 20 * (1 - (Variable_Self.StructureCount {Parameter_Type = "E_Ground_Research_Facility", Parameter_Only_Consider_Complete = 1.0} / 2)) + 10 * Function_Empire_Should_Upgrade_Tech.Evaluate * (1 - Variable_Self.HasStructure {Parameter_Type = "E_Ground_Research_Facility", Parameter_Count = 1}) + 3 * Function_Defense_Level.Evaluate + 3 * Function_Needs_Groundbase_Upgrade.Evaluate ) </Need_Research_Facility>

This equation tells the Empire that it needs a research facility at a particular planet.

Am I the Empire?
AND
I DON'T have a research facility present (note the parameter_type limiting HasStructure)
AND
I have enough bases that I should tech up
AND
The planet (the target) can support a level 5 starbase
AND
(
Reduce desire if I have more than 2 research facilities [1]
OR
Do I want to upgrade tech AND I have NO single research facility (here parameter count means that HasStructure returns 1 if there is only 1 Research Facility)
OR
Is the planet well defended?
OR
Does the planet need a groundbase upgrade?
)

[1] Count how many complete research facilities I own (Parameter_Only_Consider_Complete = 1.0 means that only completed objects are counted), divide by 2, and then reduce the value if I have more than 2 (using "1 -" as a NOT again)
Lua + XML: EvaluatePerception
The Lua script function EvaluatePerception allows your scripts to "see" outside of their own variables. The function accepts arguments of: perception name (string), player object (PlayerObject variable), AI target location (a game "cell") or AI target (a unit, planet, or appropriate).

These arguments are based on the content of the perception you want to evaluate. You should always pass PlayerObject, but the other variables are perception-dependent. For example:

EvaluatePerception("Friendly_Land_Unit_Raw_Total", PlayerObject, priority_planet)

from GalacticFreeStore.lua. The perception is:

<Friendly_Land_Unit_Raw_Total> Variable_Target.FriendlyForce.GroundTotalUnnormalized </Friendly_Land_Unit_Raw_Total>

So to evaluate this perception, the game needs a valid Variable_Target, in this case a planet, and the PlayerObject so it knows what counts as Friendly.

Note that here priority_planet is a GameObject taken from

priority_planet = FindTarget.Reachable_Target(PlayerObject, "Ground_Priority_Defense_Score", "Friendly", "Friendly_Only", 0.1, object) if priority_planet then priority_planet = priority_planet.Get_Game_Object() end

because Reachable_Target doesn't return a GameObject. Note that Reachable_Target uses parameters that are the same as those used for Goals!

For reference, Reachable_Target's parameters are:

  • PlayerObject - the player calling the perception
  • Perception name - perception used to focus the target
  • AIGoalApplication type - See Goals section
  • Reachability type - See Goals section. Note reachability should be appropriate for the game mode
  • Probability of selecting the target of highest desire - how likely the best perception result will be picked, with static-random number generation (i.e. same "random" result would be picked at the same time in the same situation)
  • Source from which the find target should search - GameObject
  • Maximum distance from source to target - in Alamo units, probably breaks in galactic mode
Lua + XML: Evaluator Scripts
Location: Data/Scripts/Evaluators

Evaluator scripts are used to get information from Lua into Perceptual Equations. The Evaluator scripts are called in XML using Script_ScriptName.Evaluate. Arguments can be passed to the script function using Parameter_Script_String and Parameter_Script_Number. The script must contain a function called Evaluate, and usually a function called Clean_Up.

Example: GetDistanceToNearestWithProperty.lua

require("PGBaseDefinitions") function Clean_Up() -- any temporary object pointers need to be set to nil in this function. -- ie: Target = nil nearest_obj = nil end -- Receives: -- property_flag_name as defined in GameObjectPropertiesType.xml -- affiliation_type is optional qualifier of "enemy" or "friendly" function Evaluate(property_flag_name, affiliation_type) if affiliation_type == "ENEMY" then nearest_obj = Find_Nearest(Target, property_flag_name, PlayerObject, false) elseif affiliation_type == "FRIENDLY" then nearest_obj = Find_Nearest(Target, property_flag_name, PlayerObject, true) else nearest_obj = Find_Nearest(Target, property_flag_name) end if TestValid(nearest_obj) then return Target.Get_Distance(nearest_obj) else return BIG_FLOAT end end

This evaluator finds the nearest GameObject with the property flag passed to the function using Parameter_Script_String. As noted in the comments, the affiliation_type limits the GameObject search to enemies or friendlies relative to the PlayerObject calling the script (which is the same as the PlayerObject calling the perception).

Then the script returns either the distance to the target in alamo units or a BIG_FLOAT value, which would usually be larger than the map size.

Modding Notes

Notice that the string comparison is capitalized. Strings sent from XML to Lua always end up capitalized.

The script EvaulateInGalacticContext allows you to evaluate equations in tactical mode as though you are in GC mode. The vanilla game uses this to decide if a planet is worth too much to retreat from, as an example. There are many other powerful uses of this script.

It is imperative that object pointers (i.e. Lua variables that point to game objects) are set to nil in the Clean_Up() function, or memory errors can result.

Unlike other scripts, Evaluators must be inside a MEG file to be read!
Lua + XML: Goal Management
I have previously mentioned TaskForce.Set_As_Goal_System_Removable(false): this sets a TaskForce plan permanently "on" so that the goal system cannot re-evaluate it based on changes in perceptions. There are other functions that act as Goal - Lua go betweens.


Purge_Goals(PlayerObject)


This resets every goal for the PlayerObject. If production is underway and remains affordable under the budgets after re-evaluation, the production should continue.

TaskForce.Set_Plan_Result(boolean)

This tells the goal system that the plan was succesfully executed. This allows for per failure desire adjusts to affect perceptual equation results based on the number of failures. Note that calling ScriptExit() will usually tell the AI that the plan has failed unless the result is set to True using TaskForce.Set_Plan_Result(true) first.

Budget.Flush_Category("goal_category_name")

This Lua function empties the budget of a goal category so that it is forced to be reset to its current perceptual equation-defined fraction of the total budget. The vanilla game uses this to clean out the Empire player's tech budget if either they are upgrading tech (Can_Reclaim_Excess_MajorItem_Budget equation, Reclaim_Excess_MajorItem_Budget goal) or if they need to free up the budget for more space forces, or they already have the Death Star (Need_to_Flush_MajorItem_Budget perception, Flush_MajorItem_Budget goal)
FreeStores
The FreeStore scripts, Galactic and BusyTactical, are located in Data/Scripts/Freestore. They control what the AI does with units that aren't assigned to goals. In the case of the galactic freestore, for example, it tries to put heroes somewhere good, and tries to put other units on the front line as defence.

The rate at which the AI "services" (i.e. runs through) the script is defined in the Definitions function (surprising, right?). The values are how many seconds between each evaluation. The FreeStore has two rates: UnitServiceRate and ServiceRate. The former controls how often On_Unit_Service is called. The latter controls how often FreeStoreService is called.

Modding Note

Sometimes Petroglyph has misspelled variables that are like that because they are hardcoded. In this case, the vanilla GalacticFreeStore has a serious error. The UnitServiceRate variable is misspelled as UnitServciceRate. This prevents units from naturally moving from planet to planet using the interval.
TaskForce
A TaskForce is a grouping of units used to enact a plan. It is controlled by the TaskForce table in the Definitions function of a Plan script. A TaskForce is produced by:

  • Enumerating every unit the AI can build or has in the FreeStore that matches the TaskForce categories/unit names
  • Choosing units that match the contrast with the target or otherwise fulfill the goal
  • Determining the best place to build the units and how long they will take

If the time cost is less than the goal's build time delay tolerance, then the TaskForce is considered valid and is queued up for production or assembly.

As TaskForce can be defined with:

  • A name, such as MainForce, SpaceForce, or whatever
  • DenyHeroAttach, DenySpecialWeaponAttach prevents heroes or special weapons from being used as part of the task force unless explicitly listed (see hero attachment section for more information)
  • MinimumTotalSize (to control the minimum count of units)
  • MinimumTotalForce (to control the minimum combat power of the TaskForce
  • A set of unit categories (as per unit category masks) that are followed by:
  • A range of numbers e.g. "Fighter = 0, 2" so build between 0 and 2 fighter-class units OR
  • A percentage e.g. "Fighter|Corvette = 100%" so there can be any number of the different unit types but they must constitute the whole TaskForce
  • Specific unit types, e.g. "Corellian_Corvette = 0,1". You can also prevent specific units or unit categories from being used in plans using the - symbol e.g. "-F9TZ_Cloaking_Transport_Company" (the negative doesn't seem to work with percentage based TaskForces)
  • RequiredCategories, a separate table to TaskForce that defines the unit types that MUST be included for the plan to activate
  • TaskForceRequired is used to setup a non-specific TaskForce when you want to have the TaskForce thread build the TaskForce manually using e.g. Collect_All_Free_Units() function.

However, the default definitions of all TaskForce variables are in PGTaskForce.lua located in Data/Scripts/Library

Contrast

I mentioned Contrast earlier. Contrast is the relative AI Combat Power of the TaskForce compared to the Target. This can be unit vs unit as well. It can be adjusted with MinContrastScale and MaxContrastScale, which set the minimum and maximum relative combat power, respectively. There is also GlobalContrastScale, which multiplies all contrast modifiers by that amount.

Contrast scaling can also be adjusted in the DifficultyAdjustments XML file.
Galactic Markup
Location: Data/XML/AI/GalacticMarkup

Galactic Markup is an easy way to make particular planets more valuable to the AI in various ways. For example, maybe a planet is the mission goal of a GC. You want the AI to really try to attack it. So you use a Galactic Markup file. It is accessed in the GC using the Markup_Filename tag, and applied to each faction in the GC (since mission objectives may be different per faction).

Tags

PriorityTarget: defines a planet that you want to be an important offensive and defensive target for the AI

Chokepoint: defines a planet that you want to be an important defensive target for the AI.

Note that the usage of these values is tied entirely to Perceptual Equations, and in vanilla they are mostly used in

<Is_Good_Invasion_Target> 15.0 * (Variable_Target.EnemyForce.HasGroundForce == 0.0) * (Variable_Target.EnemyForce.HasSpaceForce == 0.0) * (Function_System_Recon_Relevance.Evaluate) + 5.0 * Function_Is_Neglected_By_My_Opponent_Space.Evaluate + 8.0 * Function_GenericPlanetValue.Evaluate + 5.0 * Variable_Target.Hints.Chokepoint + 2.0 * Variable_Target.ConnectsIsolatedPlanetsByForce + 3.0 * Variable_Target.TradeRoutes + Variable_Target.Markup + Variable_Target.Hints.PriorityTarget - 5.0 * (Function_Is_AI_And_Should_Be_Ignored.Evaluate) - (5.0 + 5.0 * Variable_Self.AnyCurrentThreats) * Function_Opens_New_Front.Evaluate + (10.0 * Variable_Self.IsFaction {Parameter_Faction = "UNDERWORLD"} * (Variable_Target.IsConnectedToCorruption * (Function_Is_Connected_To_Me.Evaluate == 0.0) - Variable_Target.IsCorrupted)) </Is_Good_Invasion_Target>

The markup is called using Variable_Target.Hints.Hintname. See that PriorityTarget and Chokepoint are additional scaling values added to the perception, making planets gain more value to the AI. The markup is used in other vanilla equations as well, so search for examples in the files for more information.

You may also notice Variable_Target.Markup. This is a completely separate system from the XML markup, and is based on the Lua script PathBonusForDistantTargets.lua. That script sends out a "dummy" TaskForce, finding valuable planets that are poorly defended and adding value to a table that contains each planet.
GameConstants AI Settings
GameConstants.XML has a few interesting AI settings. Aside from the pathfinding controls, the pathfinding itself (for space units) can be exposed with ShouldDisplayPredictionPaths set to True. This shows a green line in front of units for their tracking path, most notably used in the mod AOTR.

There are other settings:

AIUsesFogOfWarGalactic
AIUsesFogOfWarSpace
AIUsesFogOfWarLand


All false by default, these tags turn the Fog of War on for the AI if set to true. In galactic mode, this prompts the AI to construct scouts. In tactical mode, it can activate a number of usually dormant plans. However, proceed with caution: these settings can seriously nerf the AI.

ShowUnitAIPlanAttachment

Probably the most useful setting to put as True is this. It will show you exactly what TaskForce, target and destination each unit has, in its encyclopedia popup. This can let you diagnose plans and AI behaviour, specifically in tactical mode. In Galactic mode, units tend to remain in FreeStore unit immediately required by a TaskForce; furthermore, the plan attachment only displays for landed ground units in GC.
Using Heroes with Plans: HeroPlanAttach
Location: Data/Scripts/GameObject

Heroes in EAW that are specified with only the LandHero or SpaceHero category masks will not be added to vanilla TaskForces unless the LandHero/SpaceHero categories are specified. Instead, heroes can be "attached" to plans based on their cost (in credits). Heroes can be prevented from this behaviour by using DenyHeroAttach in the plan TaskForce definition.

The basis for this is the script HeroPlanAttach. It uses PGCommands as a require to call Base_Definitions() and set the service rate to once every 10s. It also runs the functions HeroService (in hero scripts) and calculates target contrast weights.

Hero Script

For a hero to attach, its Lua script needs to require("HeroPlanAttach"). In Definitions(), MinPlanAttachCost and MaxPlanAttachCost define the minimum and maximum plan cost (in credits) that the hero will attach to. This makes a big difference if your mod has changed unit costs significantly from vanilla.

Following that, Attack and Escort tables are created to determine what units the hero should be used against and what it should avoid; as well as what units it should prefer TaskForces with. See below for an example. Note the use of the BAD_WEIGHT global variable. You can weight different unit types differently.

-- only join plans that meet our expense requirements. MinPlanAttachCost = 5000 MaxPlanAttachCost = 0 -- Commander hit list. Attack_Ability_Type_Names = { "Infantry", -- Attack these types. "Darth_Team" ,"Boba_Fett_Team" -- Stay away from these types. } Attack_Ability_Weights = { 10, -- attack type weights. BAD_WEIGHT, BAD_WEIGHT -- feared type weights. } Attack_Ability_Types = WeightedTypeList.Create() Attack_Ability_Types.Parse(Attack_Ability_Type_Names, Attack_Ability_Weights) -- Prefer task forces with these units. Escort_Ability_Type_Names = { "Infantry", "Vehicle", "Air", "Fleet_Com_Rebel_Team", "Fleet_Com_Empire_Team" } Escort_Ability_Weights = { 3, 10, 3, BAD_WEIGHT, BAD_WEIGHT } Escort_Ability_Types = WeightedTypeList.Create() Escort_Ability_Types.Parse(Escort_Ability_Type_Names, Escort_Ability_Weights)

The hero script must also include two other functions:

function Evaluate_Attack_Ability(target, goal) return Get_Target_Weight(target, Attack_Ability_Types, Attack_Ability_Weights) end function Get_Escort_Ability_Weights(goal) return Escort_Ability_Types end

These shouldn't change from hero to hero.

Modding Notes

If your hero is defined as a Unique Unit without a hero company container, and it has any additional category mask (e.g. Fighter | SpaceHero), it will join TaskForces as though it were a normal unit.
Tokens: Parameters
Parameter tokens go inside { } after the main token to limit the scope of the token further.

<!-- Parameters --> Used to limit scope of "final literals"
<Parameter_Category> </Parameter_Category> GameObjectCategoryType or AIGoalCategoryType restriction
<Parameter_Attenuator> </Parameter_Attenuator> Scales returned value of final literal?
<Parameter_Type> </Parameter_Type> GameObjectType e.g. actual unit or planet name
<Parameter_Hard_Point_Type> </Parameter_Hard_Point_Type> HardPointType e.g. Shield_Generator
<Parameter_Faction> </Parameter_Faction> Faction e.g. "Empire"
<Parameter_Level> </Parameter_Level> For "leveled" objects (starbases)
<Parameter_Count> </Parameter_Count> Number of objects
<Parameter_Name> </Parameter_Name> Name of story trigger event
<Parameter_Queue_Type> </Parameter_Queue_Type> Unused, presumably limits scope of build queue perceptions
<Parameter_Script_String> </Parameter_Script_String> Sends a string variable to the Evaluate function in an Evaluator script
<Parameter_Script_Number> </Parameter_Script_Number> Same as above, except can be float
<Parameter_Range> </Parameter_Range> Unused. Possibly weapon/unit attack range?
<Parameter_Only_Consider_Complete></Parameter_Only_Consider_Complete> When counting GameObjectTypes, this can be used to only count completed objects
<Parameter_Time> </Parameter_Time> A maximum amount of time within which a perception has fired? Used with HasTakenDamage
<Parameter_Difficulty_Level_Type></Parameter_Difficulty_Level_Type> DifficultyLevelType restriction e.g. Easy
Tokens: Literals
"Literals" are tokens that control other tokens, similar to that of Variables.

<!-- Non-automatic root literals -->
<Game> </Game> Game-level perceptions e.g. Age, PlanetsCorrupted

<!-- Intermediate literals (cross-object) -->
<Type> </Type> Used with IsType to find specific individual GameObjectType, or Token to find a specific token
<FriendlyForce> </FriendlyForce> Limits to friendly forces
<EnemyForce> </EnemyForce> Limits to enemy forces
<Hints> </Hints> Pulls from GC hint XML

<!-- Final literals -->
<Evaluate> </Evaluate> Returns the value from a perception (Function_Perception_Name.Evaluate) or a script (Script_Script_Name.Evaluate)
<Token> </Token> Returns the token value of a GameObject (only used for home planet detection)

<Timeline> </Timeline> Here and down are Game literals. This one should return the "Timeline" measure but the functionality was probably cut
<Markup> </Markup> This one isn't Game, but reads from a Lua Markup table giving planets bonuses (PathToDistantTarget)
<Age> </Age> Time in seconds since game start
<IsCampaignGame> </IsCampaignGame> Returns 1 if the game is a GC
<InterventionsEnabled> </InterventionsEnabled> Returns 1 if Intervention missions are enabled
<TimeSinceStoryPopup> </TimeSinceStoryPopup> Time in seconds since last time the story box was opened
<ActiveStoryGoalCount> </ActiveStoryGoalCount> Total number of goals player has in mission holocron
<IsStoryCampaign> </IsStoryCampaign> Returns 1 if the game type is the main story campaign (not a basic GC)
<PlanetsCorrupted> </PlanetsCorrupted> Total number of planets corrupted normalized by total planets (so 0-1 range)
<PlanetsCorruptedUnnormalized> </PlanetsCorruptedUnnormalized> Total number of planets corrupted
Tokens: Planets
Used for GC-based planet evaluation. Must be called after Variable_Target when Target is a planet.

<!-- Misc planet final literals -->
<TargetPoliticalControl> </TargetPoliticalControl> Unclear- should return 1 if evaluating PlayerObject owns the planet
<StarbaseLevel> </StarbaseLevel> Returns level of starbase at planet normalized to a max of 5
<GroundbaseLevel> </GroundbaseLevel> Returns "level" of groundbase on planet (can be more than 0-5), normalized to max of 5
<DistanceToNearestFriendly> </DistanceToNearestFriendly> Distance in game map units to the nearest friendly GameObject NOT PLANET BASED!
<OpenStructureSlots> </OpenStructureSlots> TOTAL special structure slots available at a planet, normalized by ground + space total
<IsConnectedTo> </IsConnectedTo> Returns based on connected planet parameters (probably always returns 1 if there is any connection at all)

<FriendlyInternalConnectivity> </FriendlyInternalConnectivity> Possibly the level of internal cohesion of friendly planets
<FriendlyExternalConnectivity> </FriendlyExternalConnectivity> Possibly the number of external connections of friendly planets
<EnemyInternalConnectivity> </EnemyInternalConnectivity> Same as above but enemy
<EnemyExternalConnectivity> </EnemyExternalConnectivity> Same as above but enemy

<Income> </Income> Unused. Probably total planet income normalized to highest earning planet
<IncomeNBPTM> </IncomeNBPTM> Same as above but normalized by "per target max" where the target is probably a planet?
<IncomeNBTP> </IncomeNBTP> Income normalized by target planet OR total planets (probably target)
<IncomeNBTD> </IncomeNBTD> Income normalized by target disposition i.e. by allied targets only
<BaseIncome> </BaseIncome> Same as above but the raw income unmodified by mining facilities etc
<BaseIncomeNBTP> </BaseIncomeNBTP> See above
<BaseIncomeNBTD> </BaseIncomeNBTD> See above

<TradeRoutes> </TradeRoutes> Number of trade routes connected to a planet normalized by something?
<ActiveTradeRoutes> </ActiveTradeRoutes> Number of trade routes connected to a planet that provide income (i.e. friendly) normalized to the total number of connections to that planet
<IsSurfaceAccessible> </IsSurfaceAccessible> Are there ground company slots (i.e. not an asteroid field)
<RetainsResidualInfluence> </RetainsResidualInfluence> Old political control system
<IsCorrupted> </IsCorrupted> Is the planet corrupted? 1 if true
<IsCorruptionTransitionActive> </IsCorruptionTransitionActive> Is the planet being corrupted? 1 if true
<IsConnectedToCorruption> </IsConnectedToCorruption> Is the planet connected to another corrupted planet? 1 if true
<BlackMarketAbilitiesAvailable> </BlackMarketAbilitiesAvailable> Returns 1 if there are black market upgrades to buy at a location
<BlackMarketMinimumAbilityPrice> </BlackMarketMinimumAbilityPrice> Returns the minimum price in credits of a black market purchase at a location
<BlackMarketPriceModifier> </BlackMarketPriceModifier> The multiplier to black market prices at a specific planet
<TimeSinceCorruptionChange> </TimeSinceCorruptionChange> Time in seconds since a planet gained or lost corruption

<!-- (planet) type final literals -->
<MaxStarbaseLevel> </MaxStarbaseLevel> Max starbase level at a planet normalized to a max of 5
<MaxGroundbaseLevel> </MaxGroundbaseLevel> Max groundbase level at a planet (equal to special structure slots), normalized to a max of 5
<ForceAlignment> </ForceAlignment> Unused, presumably returns dark or light force alignment of a planet
<MaxStructureSlots> </MaxStructureSlots> Max number of special structures space + ground, normalized to something
<IsType> </IsType> Compares to parameter_type string and returns 1 if true
<IsFaction> </IsFaction> Compares to parameter_faction string and returns 1 if true
<IsNamedHero> </IsNamedHero> Checks to see if a unit is a Named Hero

<ConnectsIsolatedPlanetsByLength> </ConnectsIsolatedPlanetsByLength> Unused. Presumably returns 1 if the target planet connects two groups of PlayerObject planets that are furthest away
<ConnectsIsolatedPlanetsByForce> </ConnectsIsolatedPlanetsByForce> Returns 1 if the target planet connects two groups of PlayerObject planets with high combat power amount
<ConnectsLargestIslands> </ConnectsLargestIslands> Returns 1 if the target planet connects two groups of PlayerObject planets that are the largest groups (i.e. most number of planets)

<HasCreditSiphon> </HasCreditSiphon> Returns 1 if the target planet has a smuggler is present siphoning credits
<HasStructure> </HasStructure> Returns 1 if the target planet has a structure (usually scoped using Parameter_Type)
<HasIndigenousUnits> </HasIndigenousUnits> Returns 1 if the target planet has indigenous units specified
<IsHumanControlled> </IsHumanControlled> Returns 1 if the human player controls the planet
Tokens: Force, Misc
Here, Force refers to the AI Combat Power of a TaskForce of units.

<!-- Force literals --> As in, EnemyForce or FriendlyForce
<GroundTotal> </GroundTotal> Total ground forces at target planet (summed AI combat power) normalized to largest total ground force.
<SpaceTotal> </SpaceTotal> Same as above but for space
<NearbyGroundTotal> </NearbyGroundTotal> Same as above but returns values for systems one connection away
<NearbySpaceTotal> </NearbySpaceTotal> Same as above
<AntiStealthEffectiveness> </AntiStealthEffectiveness> Returns 1 if the enemy force can detect stealth units
<HasNearbyGroundForce> </HasNearbyGroundForce> Unused. Presumably returns 1 if an enemy or friendly force has any nearby ground force at all.
<HasNearbySpaceForce> </HasNearbySpaceForce> Same as above

<!-- NBTD = Normalized By Target Disposition --> Normalized relative to allied targets
<GroundTotalNBTD> </GroundTotalNBTD> See above section, with different normalizations
<SpaceTotalNBTD> </SpaceTotalNBTD>

<!-- NBPTM = Normalized By Per Target Max --> Normalized by max for the entire galaxy
<GroundTotalNBPTM> </GroundTotalNBPTM>
<SpaceTotalNBPTM> </SpaceTotalNBPTM>

<!-- NBTT = Normalized By Target Total --> Normalized by the total at the target
<GroundTotalNBTT> </GroundTotalNBTT>
<SpaceTotalNBTT> </SpaceTotalNBTT>

<!-- Quick checks for whether force are present. Result is either 1.0 or 0.0 -->
<HasSpaceForce> </HasSpaceForce>
<HasGroundForce> </HasGroundForce>

<!-- Maintenance cost normalized by income -->
<Maintenance> </Maintenance>

<TechLevel> </TechLevel> Called from Variable_Self or Variable_Human. Returns unnormalized tech level value

<!-- Income perceptions for players. Net subtracts off maintenance costs
(and is more expensive to evaluate as a result). NBPTM normalizes by max
income for the entire galaxy. NBTF normalizes by max income for player's
current planets -->
<GrossIncomeNBPTM> </GrossIncomeNBPTM>
<NetIncomeNBPTM> </NetIncomeNBPTM>
<GrossIncomeNBTF> </GrossIncomeNBTF>
<NetIncomeNBTF> </NetIncomeNBTF>

<!-- Build perceptions for planets. Depth and time are normalized by the
largest depth/time across any planet belonging to this planet's owner.
These are valid perceptions ONLY when the querying player owns the planet -->
<BuildQueueDepth> </BuildQueueDepth>
<BuildQueueTime> </BuildQueueTime>
<BuildPercentRemaining> </BuildPercentRemaining>
<IsBuilding> </IsBuilding> This one definitely works

<OpenGroundCompanySlots> </OpenGroundCompanySlots> Returns number of open ground company slots, possibly normalized to 10
<StoryTrigger> </StoryTrigger> Returns 1 if story trigger given by parameter_name has fired

Tokens: Player
Evaluated on player variables.

<!-- Various perceptions for players below -->

<!-- Perception for the proportion of income devoted to a particular goal
category. REQUIRES the goal category as a parameter -->
<BudgetAllocation> </BudgetAllocation>

<PlanetsControlled> </PlanetsControlled> Planets controlled normalized by total in GC
<AverageBuildQueueDepth> </AverageBuildQueueDepth> Build perceptions averaged across all planets
<AverageBuildTime> </AverageBuildTime>
<AverageStarbase> </AverageStarbase> Starbase level average across all planets, normalized to a max of 5
<AverageGroundbase> </AverageGroundbase> Groundbase level average across all planets, normalized to a max of 5
<SpaceDefendedPlanets> </SpaceDefendedPlanets> Planets with space units normalized by total planets
<GroundDefendedPlanets> </GroundDefendedPlanets> Same as above but ground units
<AverageAgeOfGroundIntelligence></AverageAgeOfGroundIntelligence> Average age of scouting data in seconds
<AverageAgeOfSpaceIntelligence> </AverageAgeOfSpaceIntelligence>
<HasStarbaseOfLevel> </HasStarbaseOfLevel> Returns 1 if player owns a starbase of Parameter_Level. Can be further controlled with Parameter_Count (to require a minimum count)
<HasGroundbaseOfLevel> </HasGroundbaseOfLevel> Same as above but ground
<NumStructure></NumStructure> Unused. Presumably total number of special structures (space and ground)
<NumStarbaseOfLevel></NumStarbaseOfLevel> Counts the number of starbases owned by a player with a given Parameter_Level
<NumGroundbaseOfLevel></NumGroundbaseOfLevel> Same as above but ground bases
<HomePlanet> </HomePlanet> Home Planet token as determined by GC XML
<BudgetFractionToBuild> </BudgetFractionToBuild> Fraction of a budget required to build a particular GameObjectType e.g. research facility
<CanAdvanceTech> </CanAdvanceTech> Returns 1 if the PlayerObject can tech up (Empire style tech)
<HasBeenAttackedInSpace> </HasBeenAttackedInSpace> Unused. Presumably returns 1 if a planet or player has been attacked in space
<HasBeenAttackedOnGround> </HasBeenAttackedOnGround> Same as above but ground
<TimeSinceSpaceDefender> </TimeSinceSpaceDefender> Unused. Presumably time in seconds since player/planet has been defended in space
<TimeSinceSpaceAttacker> </TimeSinceSpaceAttacker> Unused. Presumably time in seconds since player has attacked in space
<TimeSinceGroundDefender> </TimeSinceGroundDefender> Same as above but ground
<TimeSinceGroundAttacker> </TimeSinceGroundAttacker> Same as above
<WorstIslandFractionOfLargest> </WorstIslandFractionOfLargest> Of the two largest blocks of controlled planets that are not connected to each other, does the smaller have at least X% of the number of planets the larger has? This perception returns the % as a float between 0 and 1
<HasUnit> </HasUnit> Returns 1 if the PlayerObject has a unit of Parameter_Type
<StructureCount> </StructureCount> Returns count of structures for a PlayerObject or target planet. Can be limited by Parameter_Type and Parameter_Only_Consider_Complete
<IsDifficulty> </IsDifficulty> Returns 1 if the difficulty level matches the Parameter_Difficulty_Level_Type
<HasTechToProduce> </HasTechToProduce> Returns 1 if the player is at sufficient tech level to produce a given GameObjectType
<ActiveGoals> </ActiveGoals> Returns 1 if the AI player has goals active in a given Parameter_Category = AIGoalCategoryType
<AnyCurrentThreats> </AnyCurrentThreats> Returns 1 if the PlayerObject is adjacent to a human player

<!-- Proportion of a player's planets that have bases at their maximum level --> Normalized to total owned planets
<MaxedGroundbases> </MaxedGroundbases>
<MaxedStarbases> </MaxedStarbases>

<!-- Can follow certain 'final' literals to determine when that literal was last genuinely observed --> e.g. force literals
<TimeLastSeen> </TimeLastSeen> Normalized to game age!

<TimeSinceSpaceConflict> </TimeSinceSpaceConflict> Time in seconds since a planet last had space combat
<TimeSinceGroundConflict> </TimeSinceGroundConflict> Same as above but ground
<TimeSinceConversion> </TimeSinceConversion> Time in seconds since planet changed owner
Tokens: Tactical
Used in space/land battles. Will not apply to GC mode

<Owner> </Owner> Returns the faction owner of a GameObject. Usually used with IsFaction to check owner
<Location> </Location> Area "near" a given GameObjectType target. No details on what "near" is in terms of range

<Health> </Health> Health percent value of a GameObjectType target
<Shield> </Shield> Shield percent
<Energy> </Energy> Energy percent
<HardPointHealth> </HardPointHealth> Health percent of a given hardpoint connected to the target

<Force> </Force> AI combat power of player (total forces) or target
<ForceNBTP> </ForceNBTP> Same as above, but normalized by target something or total something
<ForceNBTD> </ForceNBTD> Force normalized to allied units
<FriendlyForceNBTD> </FriendlyForceNBTD> Friendly force normalized to allies
<EnemyForceNBTD> </EnemyForceNBTD> Same as above but enemy
<IsDefender> </IsDefender> Returns 1 if the playerObject is the defender in a battle (in GC mode only?)
<ForceVisibility> </ForceVisibility><!-- % of the enemies forces that are visible; always 1 with FOW off -->
<IsEnemyStartLocation> </IsEnemyStartLocation> Returns 1 if the target region (maps are divided into grids) is the enemy start location
<CanRetreat> </CanRetreat> Returns 1 if retreating is allowed
<EnemyUnitConcentration> </EnemyUnitConcentration> Returns 1 if the location target has enemy units
<FriendlyUnitConcentration> </FriendlyUnitConcentration> Same as above but friendly
<EnemyUnitConcentrationNBTD> </EnemyUnitConcentrationNBTD> Same as above but normalized to allied units
<FriendlyUnitConcentrationNBTD> </FriendlyUnitConcentrationNBTD>
<IsBombingRunAvailable> </IsBombingRunAvailable> Returns 1 if a bombing run is available to call in
<CashPointValue> </CashPointValue> Unused. Presumably returns the value of a credit dump
<IsCashPoint> </IsCashPoint> Unused. Presumably returns 1 for cash points
<ContainsHero> </ContainsHero> Returns 1 if the target has a hero in it somehow
<AreEnginesOnline> </AreEnginesOnline> Returns 1 if the target's engines are operable
<IsSetupPhase> </IsSetupPhase> Game literal, presumably returns 1 if there is a setup phase ongoing
<IsBuildPad> </IsBuildPad> Returns 1 if the target is a build pad of some type
<HasBuiltObject> </HasBuiltObject> Returns 1 if the target has a build pad object on it, can be controlled with Parameter_Type
<NearbyOpenBuildPadCount> </NearbyOpenBuildPadCount> Unused. Presumably returns the count of nearby build pads with no structure
<OpenBuildPadCount> </OpenBuildPadCount> Returns count of owned open build pads
<TacticalBuiltStructureCount> </TacticalBuiltStructureCount> Returns count of structure built on pads, controlled by Parameter_Type
<IsFriendlyStartLocation> </IsFriendlyStartLocation> Returns 1 if the target location is in the friendly start location
<HasTakenDamage> </HasTakenDamage> Returns 1 if the target has ever taken damage
<BaseLevel> </BaseLevel> Returns value of starbase or grounsbase level in tactical mode
<IsRetreating> </IsRetreating> Returns 1 if the target player is retreating
<IsInsideFriendlyShield> </IsInsideFriendlyShield> Unused, presumably returns 1 if the location is within the friendly shield generator radius
<IsInsideEnemyShield> </IsInsideEnemyShield> Same as above but enemy
<IsContestable> </IsContestable> Returns 1 if the target can be captured
<ReinforcementsUnnormalized> </ReinforcementsUnnormalized> Total AI Combat power of reinforcements waiting to enter battle
<UnitSpaceAvailable> </UnitSpaceAvailable> Unit cap available in tactical combat
<IsValidBombingTarget> </IsValidBombingTarget> Returns 1 if the target location can be bombed (i.e. not under shields)
<IsLandControlGame> </IsLandControlGame> Returns 1 if the game is land control style
<GarrisonSlotsAvailable> </GarrisonSlotsAvailable> Unused, presumably returns the number of garrison slots left in a given GameObjectType
<IsOrbitalBombardmentAvailable> </IsOrbitalBombardmentAvailable> Unused. Presumably returns 1 if the orbital bombardment is ready
<AdditionalPopulationCapacity> </AdditionalPopulationCapacity> Returns the population capacity provided by a given landing zone
Tokens: Unnormalized
Regardless of the commented warning, Petro used these anyway. These can be very useful in specific situations.

<!-- INTERNAL ONLY. DO NOT USE IN DATA. Mostly unnormalized perceptions that are used to calculate other perceptions. --> Lies. Petro used these all the time
<GroundTotalUnnormalized> </GroundTotalUnnormalized>
<SpaceTotalUnnormalized> </SpaceTotalUnnormalized>
<IncomeUnnormalized> </IncomeUnnormalized>
<IncomePotentialUnnormalized> </IncomePotentialUnnormalized>
<BaseIncomeUnnormalized> </BaseIncomeUnnormalized>
<GrossIncomeUnnormalized> </GrossIncomeUnnormalized>
<NetIncomeUnnormalized> </NetIncomeUnnormalized>

<MaxAllPlanetsIncomeUnnormalized> </MaxAllPlanetsIncomeUnnormalized>
<MaxOwnedPlanetsIncomeUnnormalized> </MaxOwnedPlanetsIncomeUnnormalized>

<MaintenanceUnnormalized> </MaintenanceUnnormalized>

<PerTargetMaxGroundForce> </PerTargetMaxGroundForce>
<PerTargetMaxSpaceForce> </PerTargetMaxSpaceForce>
<PerTargetMaxIncome> </PerTargetMaxIncome>
<PerTargetMaxBaseIncome> </PerTargetMaxBaseIncome>

<MaxBuildQueueDepth> </MaxBuildQueueDepth>
<MaxBuildQueueTime> </MaxBuildQueueTime>

<HasSpaceUnitsBitfield> </HasSpaceUnitsBitfield> Searches the enemy fleet at a target for a given GameObjectType
<HasGroundUnitsBitfield> </HasGroundUnitsBitfield> Same as above but ground

<TargetPlayerID> </TargetPlayerID> Unused and unknown

<ForceUnnormalized> </ForceUnnormalized>
<FriendlyForceUnnormalized> </FriendlyForceUnnormalized>
<EnemyForceUnnormalized> </EnemyForceUnnormalized>

<CreditsUnnormalized> </CreditsUnnormalized> Total credits owned by PlayerObject

<EnemyUnitConcentrationUnnormalized> </EnemyUnitConcentrationUnnormalized>
<FriendlyUnitConcentrationUnnormalized> </FriendlyUnitConcentrationUnnormalized>

<TimeLastSeenUnnormalized> </TimeLastSeenUnnormalized>

<GroundbaseTotalUnnormalized> </GroundbaseTotalUnnormalized> Total AI combat power provided by a target's groundbase
<StarbaseTotalUnnormalized> </StarbaseTotalUnnormalized>
<StarbaseLevelUnnormalized> </StarbaseLevelUnnormalized>
<GroundbaseLevelUnnormalized> </GroundbaseLevelUnnormalized>
<MaxStarbaseLevelUnnormalized> </MaxStarbaseLevelUnnormalized> Maximum starbase level on a planet
<MaxGroundbaseLevelUnnormalized></MaxGroundbaseLevelUnnormalized> Same as above but ground
<OpenStructureSlotsUnnormalized></OpenStructureSlotsUnnormalized>
<MaxStructureSlotsUnnormalized> </MaxStructureSlotsUnnormalized>
<PlanetsControlledUnnormalized> </PlanetsControlledUnnormalized>

<FriendlyUnitsBitfield> </FriendlyUnitsBitfield>
<EnemyUnitsBitfield> </EnemyUnitsBitfield>
<LandedForceUnnormalized> </LandedForceUnnormalized> Total force landed in a ground battle
<DistanceToNearestEnemy> </DistanceToNearestEnemy> Distance in Alamo units to nearest enemy.
The Lua Library Annotated: Overview
Location: Data/Scripts/Library

These sections will annotate the library functions written by Petroglyph as the basis for all Lua coding the game, as far as non built-in functions go. The library is essentially a Lua wrapper for hardcoded C++ functions. You can tell a C++ hardcoded function because in most cases it is preceeded by a _

Inheritence

Each Library file, except for PGDebug, has a require function to begin. This tells the Lua to include all the functions from the required file. The heirachy is:

PGDebug | PGBase | PGBaseDefinitions---------------------------- | \ \ \ PGCommands--- PGSpawnUnits-- PGMoveUnits PGInterventions | \ \ | PGAICommands PGStateMachine \ | | \ | PGTaskForce PGStoryMode | PGEvents

You can see that the only library files you should require in your scripts are PGEvents, PGInterventions, or PGStoryMode, because they encompass every other library script that is necessary for their operation.
The Lua Library Annotated: PGDebug
PGDebug contains the wrappers for debug output. These messages are only exposed in the internal builds of the game, so they are not particularly helpful for general modding use.
The Lua Library Annotated: PGBase
PGBase contains low-level functions that are used for general programming rather than being directly gameplay related.

YieldCount

This variable keeps track of the number of threads that have been "yielded", sent to the C++ code of the game to be run.

ScriptExit()

Terminates a script as soon as possible

Sleep(time)

Pauses the current script for a time in seconds

BlockOnCommand(block, max_duration, alternate_break_func)

This function "services a block" i.e. runs a function to conclusion OR until either max_duration (in seconds) is reached OR until the alternate_break_func returns true.

This is often used in AI plans to commit to move commands, production and so on.

BreakBlock()

Not directly used, possibly internal.

TestCommand(block)

Not directly used, possibly internal. Could be used to check if a command block is possible to execute.

PumpEvents

This cycles script events through the C++ code using Lua coroutines. This allows for multi-threaded (but not multi-core!) script operation.

TestValid(wrapper)

Tests for the validity (i.e. not nil) of the wrapper. A wrapper can be a GameObject, PlayerObject etc.

Clamp(value, min, max)

Mathematical clamp function. Returns min if value less than min or max if value greater than max; otherwise returns value.

Dirty_Floor(val)

Uses Lua float-to-string conversion to take decimals off floats.

Simple_Mod(a,b)

Modulus function.

Chance(seed, percent)

Return true if a d100 diceroll based on the random seed is less than the percent value.

GetCurrentMinute()

Returns the current game time since start to the nearest minute.

GetAbilityChanceSeed()

Returns current game time to use as a seed

GetChanceAllowed(difficulty)

Always returns true in its vanilla implementation; can be used to make different difficulty levels return a different chance.

PlayerSpecificName(player_object, var_name)

Concatenates strings to allow finding global variables independent of calling PlayerObject

Flush_G()

Cleans Lua tables from memory.
The Lua Library Annotated: PGBaseDefinitions
This file contains all of the game's default values for hardcoded script variables.

BAD_WEIGHT and BIG_FLOAT are global variables for use in any script.

collectgarbage(256) cleans out the Lua script memory once it reaches 256kb

Common_Base_Definitions()

Resets threads and events. Initializes variables that are used across multiple scripts for memory management (usually initializes to nil, false or 0).

Base_Definitions()

Calls Common_Base_Definitions and then the Definitions() function in a non-library script.

Evaluator_Clean_Up()

Resets Target and PlayerObject, calls a script's Clean_Up() function.

UnitListIsObscured(unit_list)

Loops through the unit_list and returns true if the unit is in a nebula, asteroid field or ion storm. Note that these check functions are hardcoded.

Cull_Unit_List(unit_list)

Removes invalid/dead units from the unit_list.
The Lua Library Annotated: PGCommands
PGCommands contains functions that give the AI...commands. However, many of the functions are lower level than that.

WaitForever()

Pumps events for ever.

Register_Timer(func, timeout, param)

Creates a Lua table entry that calls the function func after timeout seconds has passed. param is parameters passed to func.

Process_Timers()

Cycles through the table of timed events and triggers their functions.

Cancel_Timer(func)

Removes func from the list of timed functions.

Register_Death_Event(obj, func)

Calls func when obj is destroyed, as a table entry.

Process_Death_Events()

Cycles through the table entries from Register_Death_Event.

Register_Attacked_Event(obj, func)

Call func when obj is under attack, as a table entry.

Process_Attacked_Events()

Cycles through the table entries from Register_Attacked_Event and calls func when a unit is attacked.

Cancel_Attacked_Event(obj)

Clears object obj from the attacked table.

Register_Prox(obj, func, range, player_filter)

Creates a table entry for obj that triggers func when any object owned by player_filter is within range.

Process_Proximities()

Cycles through the Register_Prox table.

Pump_Service()

Calls each Process_ function and also calls Process_Reinforcements() and Story_Mode_Service() if they are defined in a script.

Try_Ability(thing, ability_name, target)

Attempts to use ability ability_name for unit thing against target.

Use_Ability_If_Able(thing, ability_name, target)

Activates ability ability_name if target is valid for object thing.

Is_A_Taskforce(thing)

Returns true if thing is both an object and a unit table (the latter implying it must be a TaskForce).

ConsiderDivertAndAOE(object, ability_name, area_of_effect, recent_enemy_units, min_threat_to_use_ability)

"This will consider diverting the passed object in order to use an area of effect ability centered on the unit." In other words, move object to a good location to use ability_name with area_of_effect, if there are recent_enemy_units with more threat (i.e. contrast) than min_threat_to_use_ability.

OneOrMoreInRange(origin_unit, target_unit_list, range)

Returns true if any of the target_unit_list are within range of origin_unit.

PruneFriendlyObjects(obj_table)

Cleans friendly objects from a table.

Try_Garrison(tf, unit, offensive_only, range)

Attempt to garrison a nearby garrisonable object with unit in TaskForce tf, releasing the unit from tf if necessary. The garrisonable object must be within range and should be capable of weapons fire unless offensive_only is false.

Try_Deploy_Garrison(object, target, health_threshold)

Attempt to deploy garrisoned units from object if the garrisoned units have health greater than health_threshold, and if they either have no target or are good against target.

Get_Special_Healer_Property_Flag(unit)

Reads from a table to tell specific units to heal at specific types of healing station.

Set_Land_AI_Targeting_Priorities(tf)

Gives TaskForce tf targeting priorities based on contained units.

Try_Weapon_Switch(object, target)

Attempts to swap the weapon of object if the target is of an appropriate type.

Determine_Magic_Wait_Duration()

Sets the time between spawns of magic defense and offense forces for the AI. Minimum time is 60s.
The Lua Library Annotated: PGAICommands
This file has a lot of definitions for AI contrast and other behaviours.

Base_Definitions()

Sets up a lot of default variables on top of those in PGCommands. Calls Commond_Base_Definitions(), cleans out TaskForce tables, calls Set_Contrast_Values, and Definitions() in scripts that require it.

Set_Contrast_Values()

Weights the contrast of particular unit classes against others. THIS IS A VERY IMPORTANT TABLE IF YOUR MOD AFFECTS UNIT CATEGORY BALANCE! It will tell the AI to bring different numbers of units vs enemies based on unit category.

The Lua Library Annotated: PGStateMachine
This file controls story event "states" and passes them to Lua. This is where OnEnter, OnUpdate, OnExit are defined.

Define_State(state, function_value)

Adds tag state to the state table with value function_value

Advance_State()

Steps to the next state in order of definition.

Set_Next_State(state)

Tells the game to move to the tag state.

Get_Current_State()

Returns the current state.

Get_Next_State()

Returns the next state in the table.

Process_States()

Cycles through the states until none are left.

Base_Definitions()

Sets up default story variables. Calls Common_Base_Definitions, Definitions() and PG_Story_Mode_Init()

main()

Calls Process_States() then exits.
The Lua Library Annotated: PGTaskForce
This is where many TaskForce functions are defined.

FindStageArea(_target, taskforce)

Calls an internal function to find where taskforce should gather.

AssembleForce(taskforce, stage_at_strongest_space)

Constructs and immediately moves taskforce to a staging area. If stage_at_strongest_space is true, the AI will try to stage units at a location with high friendly space force strength.

SynchronizedAssemble(taskforce, stage_at_strongest_space)

Same as AssembleForce, but times taskforce unit arrival to be simultaneous based on travel time.

WaitMoveForce(taskforce, planet)

Move taskforce to planet.

LandUnits(taskforce)

Lands taskforce at its current location.

LaunchUnits(taskforce)

Sends taskforce units into orbit and puts them into a single fleet slot.

Invade(taskforce)

Lands taskforce at its current location regardless of enemy or not.
Sets some default variables, and sets current taskforce goal as not system removable.

FundBases(player, target)

Increases the value of perceptual equations for player at target. In vanilla this is designed to increase the desire to build on newly conquered worlds.

Escort(taskforce, target, conservative_style)

Commands taskforce to follow target and defend it. conservative_style is a boolean and if true will avoid having escorts attack anything other than fighters or bombers.

Tf_Has_Attack_Target(tf)

If any unit in TaskForce tf has an attack target, return true, otherwise false.

Weave(tf, enemy)

Commands TaskForce tf to attack then move away out of range of enemy repeatedly.

WaitForAllReinforcements(taskforce, target)

Tells taskforce to reinforce target unit all units have reinforced.

Anti_Idle_Taskforce()

Moves units around to keep them busy. Requires definition of lib_tf_to_anti_idle and lib_enemy_to_avoid.

QuickReinforce(player, target, tf_to_reinforce, second_try_tf)

Tries to get reinforcements for player to target from tf_to_reinforce. second_try_tf tries to get other units in if there is a secondary TaskForce in the plan.

SetClassPriorities(tf, priority_set_type)

Sets the attack targeting priorities for all unit types in TaskForce tf with priority_set_type.

Obscure(tf, range, use_nebulae, path_through_fields)

Tries to obscure TaskForce tf in asteroid fields. Will try to obscure in nebulae if use_nebulae is true; will ignore pathing restrictions if path_through_fields is true.

ConsiderHeal(unit)

Tries to find a bacta tank for unit (does not work for vehicles or starships).

ConsiderRepair(unit)

Tries to find a repair station for unit (does not work for infantry)

GalacticAttackAllowed(difficulty, ai_territories_just_gained)

This function turns the AI off and on based on the difficulty and the size of ai_territories_just_gained. This is used in conquering scripts to prevent the AI from continuously attacking the player. It works with a lot of global variables.

DifficultyBasedMinPause(difficulty)

Returns integer based on difficulty. Used to further slow the AI down in its aggression.

GetGlobalValueOrZero(player, value_name)

Returns either the global value value_name for player, if it exists, or 0 if it doesn't.
The Lua Library Annotated: PGEvents
This is the ultimate Lua for TaskForces and defines many of their default functions.

GoHeal(tf, unit, healer, release)

Tries to heal unit in TaskForce tf with healing object position healer, and releases the unit from the TaskForce if release is true.

GoKite(tf, unit, kite_pos, release)

Kites unit in TaskForce tf to position kite_pos, and releases the unit from the TaskForce if release is true.

Try_Good_Ground(tf, unit)

Tries to move unit in TaskForce tf to the nearest area of good ground (from base EAW).

Respond_To_MinRange_Attacks(tf, unit)

This sends unit from TaskForce tf into the minimum range of units that have that restriction on their weapons, e.g. turbolaser towers. Alternatively, it sends the unit outside the maximum range.

Is_Type_In_List(unit_type, type_name_list)

Cycles through the typ_name_list to see if it matches unit_type and returns true if it does.

IsAbilityAllowedToRecover(ability)

Returns true if the special ability is a toggle not a cooldown.

Default TaskForce Functions

These functions are the defaults for all TaskForces. They can be overridden in a Plan by replacing Default with the plan TaskForce name e.g. MainForce_Unit_Damaged()

Default_Space_Conflict_Begin()

Sets a global value if space combat has begun.

Default_Space_Conflict_End()

Sets a global value if space combat has ended.

Default_Unit_Destroyed()

Does nothing, but triggers on the destruction of a TaskForce unit.

Default_Unit_Damaged(tf, unit, attacker, deliberate)

Default behaviour for damaged units. Doesn't care if the attacker is a structure and exits the function. Otherwise the function runs through self-preservation tests such as turning on power to shields, garrisoning structures, or similar.

Default_Original_Target_Destroyed()

Sets Attacking = false if the TaskForce target was destroyed.

Default_Current_Target_Destroyed(tf)

Sets Attacking = false and cancels the spread out ability if TaskForce tf destroys its current target.

Default_Original_Target_Owner_Changed(tf, old_player, new_player)

Exits script if the target planet has changed hands, unless an invasion is active.

Default_Unit_Move_Finished(tf, unit)

Tries to deploy garrisons once the unit in TaskForce has reached its destination.

Should_Crush(unit, target)

Returns true if target is crushable by unit and in range of unit to be crushed.

Default_Target_In_Range(tf, unit, target)

If the TaskForce target is in range of unit. Sets Attacking = true, tries to crush units, and activates offensive abilities. Sets a global value.

Default_Hardpoint_Target_In_Range(tf, unit, target)

Activates when a hardpoint target is in range of unit in TaskForce tf. Does nothing.

Default_No_Units_Remaining()

Exits script when no units are left in the TaskForce.

Default_Unit_Diversion_Finished(tf, unit)

Turns off abilities if unit in TaskForce tf has successfully completed the Divert() function.

Default_Unit_Ability_Ready(tf, unit, ability)

This fires if the countdown was going and it is now refreshed or if you come out of a nebula. Tries to refresh abilities that were interrupted.

Default_Unit_Ability_Cancelled(tf, unit, ability)

Puts cancelled abilities in a table so they can be tried again later.

Default_Unit_Ability_Finished(tf, unit)

Activates when a unit ability has naturally finished or been turned off. Does nothing.
< >
19 Comments
[JSSG] Tadd Lar'kin Mar 27, 2020 @ 8:34pm 
Didn't this guide once contain how to fix the green shaders not coming in? I really really need that right now
Kerouha Feb 8, 2019 @ 11:11am 
I stumbled upon this guide while looking for ways to suppress AI's cheating habits, particularily the "enemy" AI, which seems to be able to create armadas out of thin air in Conquest (because space stations are for losers, right?). Or spam capital ships/tanks without a single source of income in Skirmish.
I'm not very skilled at LUA/XML modding, but I figure a good start would be commenting the "Generate_Magic_" goals in XML files.
Are there ways to, maybe, make these "hacks" more tolerable, instead of removing them completely? Like, reduce the money boost or cooldown increase.
evilbobthebob  [author] Sep 3, 2018 @ 10:28am 
Players need to be in a meg file to be read properly
vjeko1701 Sep 3, 2018 @ 8:15am 
I'm having a problem adding new players to the game. I do a complete base copy of the Rebels player and turn it into Pirates player but it does nothing ingame.
Commander Cody May 4, 2018 @ 5:02am 
Actually, I did some testing and the problem are again the level 1 skirmish starbases. It seems neither FindDistanceToEnemy nor DistanceToFriendly can find them. Both tokens work correctly for the higher level bases and also with neutral objects (at least neutral buildpads) as targets (the friendly/enemy refers to the querying player, it has nothing to do with the target). They just can't find the level 1 skirmish starbases for some reason.
evilbobthebob  [author] May 3, 2018 @ 8:16am 
Then that means the target you're measuring from cant find a valid enemy starbase. It's the same as the measurement from capturable objects that I mentioned, the objects are neutral so they don't recognise the friendly or enemy status of starbases
Commander Cody May 3, 2018 @ 2:25am 
Ok, but I get those also for Distance_To_Nearest_Enemy_Starbase when there definitely exists a starbase.
evilbobthebob  [author] May 2, 2018 @ 5:26pm 
The impossibly large numbers mean that it couldn't find an enemy matching the parameters
Commander Cody May 2, 2018 @ 3:50pm 
The DistanceToEnemy token at least seems to be rather broken in general. I get impossibly large numbers out of it more often than not, even with the way it's used in the vanilla equations.
evilbobthebob  [author] Apr 29, 2018 @ 6:00pm 
Yeah the skirmish starbase stuff can be pretty broken, for example I had to write a new evaluator script to find the distance from a neutral object to a starbase because the standard DistanceToEnemy or DistanceToFriendly tokens don't work for that.