Understanding Traits and Conditions in OpenRA - guidebee/OpenRA GitHub Wiki

Understanding Traits and Conditions in OpenRA

Traits in OpenRA

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.

Key Concepts of Traits

  1. Modularity: Each trait handles a specific aspect of an actor's behavior.
  2. Composition: Actors are composed of multiple traits that work together.
  3. Inheritance: Traits can inherit from common base traits using the ^ prefix in YAML.
  4. Trait Requirements: Some traits require other traits to function, specified using Requires<OtherTraitInfo>.

Trait Dependencies

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.

Types of Dependencies

  1. Required Dependencies: Specified with Requires<TraitInfo> - these must be present for the trait to function
  2. Optional Dependencies: Specified with NotBefore<TraitInfo> - these are not required, but if present, will be initialized first

Defining Dependencies in Code

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.

Dependency Resolution

The engine uses a sophisticated algorithm to:

  1. Identify all dependencies (both required and optional)
  2. Detect circular dependencies (which would cause initialization problems)
  3. Create a proper initialization order to ensure all dependencies are satisfied before a trait is created

Common Dependency Patterns

  1. Movement Dependencies: Mobile traits typically require a trait that implements IPositionable
  2. Combat Dependencies: Combat-related traits often depend on Armament or AttackBase traits
  3. Conditional Traits: Many traits depend on ConditionalTrait to enable/disable functionality
  4. Physical Dependencies: Traits that affect the physical aspects of units often require IOccupySpace

Example in YAML

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:

Real-World Example: Vehicle Dependencies

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.

Troubleshooting Dependency Issues

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

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:

  1. Creating a common interface that both traits implement
  2. Using NotBefore<> instead of Requires<> if the dependency is optional
  3. Restructuring your code to remove the circular reference

Common Interface Implementations

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

Conditions System

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.

How Conditions Work

  1. Condition Grant/Revoke: Conditions can be granted and revoked during gameplay.
  2. Tokens: When a condition is granted, a unique token is returned that must be used to revoke the condition.
  3. Requirement Expressions: Traits can be configured to require specific conditions using boolean expressions.
  4. External Conditions: Special conditions that can be granted by external sources like powers, weapons, or Lua scripts.

Defining Conditions

Basic Condition Granting

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.

Conditional Traits

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.

Condition Types

1. Internal Conditions

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

2. External Conditions

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

3. Conditional Triggers

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

Condition Expressions

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

Common Condition-Granting Traits

  1. GrantCondition: Basic trait that grants a condition while active.
  2. GrantConditionOnPrerequisite: Grants a condition when specified prerequisites are available.
  3. GrantConditionOnDamageState: Grants a condition based on actor damage levels.
  4. GrantConditionOnTerrain: Grants a condition when on specific terrain types.
  5. GrantConditionOnTileSet: Grants a condition on specific map tilesets.
  6. GrantConditionOnDeploy: Grants a condition when an actor is deployed/undeployed.
  7. GrantConditionOnAttack: Grants a condition when attacking.
  8. GrantConditionOnFaction: Grants a condition for specific factions.
  9. GrantConditionOnPowerState: Grants a condition based on power state (low power).
  10. ProximityExternalCondition: Grants a condition to actors within a range.

Condition Flow Example

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%

Advanced Usage

Timed Conditions

Conditions can be temporary:

GrantExternalConditionPower@IRONCURTAIN:
    Duration: 400  # Condition lasts 400 ticks

Condition Counting

Multiple instances of the same condition can be counted:

GrantConditionOnAttack:
    Condition: attacking
    RequiredShotsPerInstance: 5
    MaximumInstances: 3  # Can stack up to 3 times

Condition Visualization

Conditions can be visualized with UI elements:

TimedConditionBar:
    Condition: invulnerability  # Shows a bar for timed conditions

WithDecoration@HEALING:
    Image: pips
    Sequence: heal
    RequiresCondition: healing

Implementation Details

The conditions system is implemented through several key classes:

  1. ConditionalTrait: Base class for traits that can be enabled/disabled by conditions.
  2. GrantCondition: Grants a condition to the actor.
  3. ExternalCondition: Allows the actor to receive conditions from external sources.
  4. VariableObserver: Monitors condition changes and triggers appropriate responses.

This modular system makes OpenRA's gameplay mechanics highly flexible and extensible.

⚠️ **GitHub.com Fallback** ⚠️