Understanding Traits and Conditions in OpenRA - guidebee/OpenRA GitHub Wiki
Traits are the core building blocks of actors in OpenRA. Each trait defines a specific behavior or functionality for an actor (unit, building, etc.). The modular trait system allows actors to combine multiple behaviors to create complex game mechanics.
- Modularity: Each trait handles a specific aspect of an actor's behavior.
- Composition: Actors are composed of multiple traits that work together.
-
Inheritance: Traits can inherit from common base traits using the
^
prefix in YAML. -
Trait Requirements: Some traits require other traits to function, specified using
Requires<OtherTraitInfo>
.
Traits in OpenRA can depend on other traits to function correctly. The dependency system ensures that traits are initialized in the correct order and that all required functionality is available.
-
Required Dependencies: Specified with
Requires<TraitInfo>
- these must be present for the trait to function -
Optional Dependencies: Specified with
NotBefore<TraitInfo>
- these are not required, but if present, will be initialized first
Dependencies are defined in the trait's info class using C# interfaces:
// Required dependency example
public class CargoInfo : ConditionalTraitInfo, Requires<IOccupySpaceInfo>
{
// Trait implementation...
}
// Optional dependency example
public class MobileInfo : PausableConditionalTraitInfo, Requires<IPositionableInfo>,
NotBefore<UpgradeManagerInfo>
{
// Trait implementation...
}
When the game loads, OpenRA's trait system analyzes these dependencies to establish the correct initialization order. If a required trait is missing, the game will generate an error.
The engine uses a sophisticated algorithm to:
- Identify all dependencies (both required and optional)
- Detect circular dependencies (which would cause initialization problems)
- Create a proper initialization order to ensure all dependencies are satisfied before a trait is created
-
Movement Dependencies: Mobile traits typically require a trait that implements
IPositionable
-
Combat Dependencies: Combat-related traits often depend on
Armament
orAttackBase
traits -
Conditional Traits: Many traits depend on
ConditionalTrait
to enable/disable functionality -
Physical Dependencies: Traits that affect the physical aspects of units often require
IOccupySpace
While dependencies are defined in code, they affect how you structure your YAML definitions:
UnitExample:
# Mobile trait requires IPositionable, so the trait must be present
Mobile:
Speed: 90
# Implements IPositionable, required by Mobile
Selectable:
Bounds: 20,20
# Cargo requires IOccupySpace
Cargo:
Types: Infantry
MaxWeight: 5
# Implements IOccupySpace, required by Cargo
BodyOrientation:
# Provides IOccupySpace, required by Cargo
Immobile:
In OpenRA's mods, vehicles demonstrate dependency chains clearly:
^Vehicle:
# Base orientation trait implements IFacing
ClassicFacingSpriteActor:
# Requires IFacing to rotate properly
Mobile:
Locomotor: wheeled
TurnSpeed: 20
# Targeting requires knowledge of the actor's position
Targetable:
TargetTypes: GroundActor, Vehicle
# Attack move requires Mobile
AttackMove:
# Visual representation requires IFacing
WithFacingSpriteBody:
# HitShape requires BodyOrientation (implicit dependency)
HitShape:
This chain ensures that when a vehicle moves, all the dependent systems like rendering, targeting, and combat work correctly.
If a trait is missing a required dependency, OpenRA will generate an error like:
YamlException: Missing:
IOccupySpaceInfo
Unresolved:
CargoInfo: { IOccupySpaceInfo }
To fix this, you need to add the missing trait to your actor definition. For this example, you would need to add a trait that implements IOccupySpace
.
Circular dependencies occur when trait A requires trait B, and trait B requires trait A. The OpenRA engine detects these situations and raises an error:
Error: Circular trait dependencies detected:
TraitA: { TraitB }
TraitB: { TraitA }
To resolve circular dependencies, you need to redesign your traits to break the circular reference. Options include:
- Creating a common interface that both traits implement
- Using
NotBefore<>
instead ofRequires<>
if the dependency is optional - Restructuring your code to remove the circular reference
Many traits implement common interfaces to fulfill dependencies:
Interface | Purpose | Example Implementing Traits |
---|---|---|
IPositionable |
Defines position in the world |
Mobile , Immobile
|
IOccupySpace |
Defines physical space occupation |
Building , Infantry , Vehicle
|
IFacing |
Defines actor orientation |
BodyOrientation , TurnOnIdle
|
IHealth |
Provides damage handling | Health |
INotifyAttack |
Responds to attack events |
AttackTurreted , Armament
|
IMove |
Provides movement capability |
Mobile , Aircraft
|
INotifyDamage |
Responds to damage events | GrantConditionOnDamageState |
The conditions system in OpenRA allows traits to be dynamically enabled, disabled, or modified based on game events and states. This provides a powerful way to create complex, context-sensitive behavior.
- Condition Grant/Revoke: Conditions can be granted and revoked during gameplay.
- Tokens: When a condition is granted, a unique token is returned that must be used to revoke the condition.
- Requirement Expressions: Traits can be configured to require specific conditions using boolean expressions.
- External Conditions: Special conditions that can be granted by external sources like powers, weapons, or Lua scripts.
Conditions are defined in actor YAML files. Here's a basic example:
GrantCondition@EXAMPLE:
Condition: example-condition
This trait will grant the condition "example-condition" to the actor when the trait is active.
Most traits can be made conditional using RequiresCondition
:
SomeNormalTrait:
RequiresCondition: example-condition
# other parameters...
This trait will only be active when "example-condition" is granted.
Internal conditions are granted and revoked within the same actor.
GrantCondition@RANK-ELITE:
RequiresCondition: rank-veteran >= 4
Condition: rank-elite
DamageMultiplier@RANK-ELITE:
RequiresCondition: rank-elite
Modifier: 80
External conditions can be granted by one actor to another.
Receiving External Conditions:
ExternalCondition@INVULNERABILITY:
Condition: invulnerability
DamageMultiplier@INVULNERABILITY:
RequiresCondition: invulnerability
Modifier: 0 # No damage when invulnerable
Granting External Conditions:
GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
Condition: invulnerability
Duration: 400 # Ticks
Conditions can be granted based on various triggers:
# Grant condition based on prerequisites
GrantConditionOnPrerequisite@HOSPITAL:
Condition: hospital
Prerequisites: hosp
# Grant condition based on damage
GrantConditionOnDamageState@DAMAGED:
Condition: damaged
ValidDamageStates: Light, Medium, Heavy, Critical
# Grant condition based on terrain
GrantConditionOnTerrain:
Condition: on-water
TerrainTypes: Water, River
# Grant condition based on deployment
GrantConditionOnDeploy:
DeployedCondition: deployed
UndeployedCondition: undeployed
Conditions support complex boolean expressions:
# Logical AND
RequiresCondition: condition1 && condition2
# Logical OR
RequiresCondition: condition1 || condition2
# Negation
RequiresCondition: !condition1
# Counting instances
RequiresCondition: condition1 >= 3
# Parentheses for grouping
RequiresCondition: (condition1 || condition2) && !condition3
- GrantCondition: Basic trait that grants a condition while active.
- GrantConditionOnPrerequisite: Grants a condition when specified prerequisites are available.
- GrantConditionOnDamageState: Grants a condition based on actor damage levels.
- GrantConditionOnTerrain: Grants a condition when on specific terrain types.
- GrantConditionOnTileSet: Grants a condition on specific map tilesets.
- GrantConditionOnDeploy: Grants a condition when an actor is deployed/undeployed.
- GrantConditionOnAttack: Grants a condition when attacking.
- GrantConditionOnFaction: Grants a condition for specific factions.
- GrantConditionOnPowerState: Grants a condition based on power state (low power).
- ProximityExternalCondition: Grants a condition to actors within a range.
Here's a complete example of how conditions work together:
# Hospital building grants healing to infantry
building-hospital:
GrantExternalConditionToProduced:
Condition: hospital-heal
Types: Infantry
# Infantry unit receives healing when damaged and near hospital
infantry-unit:
ExternalCondition@HOSPITAL-HEAL:
Condition: hospital-heal
GrantConditionOnDamageState@DAMAGED:
Condition: damaged
ValidDamageStates: Light, Medium, Heavy, Critical
GrantCondition@HEALING:
RequiresCondition: hospital-heal && damaged
Condition: healing
SelfHealing:
RequiresCondition: healing
Step: 5
Delay: 3
HealIfBelow: 100%
Conditions can be temporary:
GrantExternalConditionPower@IRONCURTAIN:
Duration: 400 # Condition lasts 400 ticks
Multiple instances of the same condition can be counted:
GrantConditionOnAttack:
Condition: attacking
RequiredShotsPerInstance: 5
MaximumInstances: 3 # Can stack up to 3 times
Conditions can be visualized with UI elements:
TimedConditionBar:
Condition: invulnerability # Shows a bar for timed conditions
WithDecoration@HEALING:
Image: pips
Sequence: heal
RequiresCondition: healing
The conditions system is implemented through several key classes:
- ConditionalTrait: Base class for traits that can be enabled/disabled by conditions.
- GrantCondition: Grants a condition to the actor.
- ExternalCondition: Allows the actor to receive conditions from external sources.
- VariableObserver: Monitors condition changes and triggers appropriate responses.
This modular system makes OpenRA's gameplay mechanics highly flexible and extensible.