AdjustArmorMitigation

Tracking Issue: #1540

This feature allows mods to override the amount of armor mitigation, armor piercing, minimum/mandatory armor mitigation, and/or armor shredding for an attack on a case-by-case basis.

Normally this override would have been implemented as an event, but the implementing dev heard that events in To Hit Chance Calculation logic can cause issues, (see GetHitChanceEvents), and resorted to delegates in an attempt to resolve a crash he was unable to explain at the time.

This feature does not apply if the attack ignores armor (e.g. bIgnoreArmor is set to true). Additionally, it only applies to attacks using X2Effect_ApplyWeaponDamage, or a subclass which has not overridden its GetDamagePreview() and CalculateDamageAmount functions (except if they have included support for this feature).

Delegate structure

  • int WeaponDamage: Full damage dealt by the attack before mitigation, to help determine the results.
  • out int ArmorMitigation: Amount of armor mitigation available to reduce the attack. When first invoked, this is equal to the target's armor, but may be modified by other delegates running before yours.
  • out int ArmorPiercing: Amount of armor piercing available to reduce mitigation. When first invoked, this is equal to the attack's armor piercing, but may be modified by other delegates running before yours.
  • out int MinMitigation: Minimum threshold below which mitigation cannot be reduced. When first invoked, this is 0 (per vanilla behavior), but may be modified by other delegates running before yours. Negative values are currently permitted, but will result in undefined behavior.
  • out int ArmorShred: Amount of armor shredding inflicted by the attack. After all delegates have run, this value is capped to never exceed the target's remaining armor. Negative values are permitted, but will result in undefined behavior.
  • EffectAppliedData ApplyEffectParams: Effect context containing information such as the parent ability, source and target units, etc., to help determine the results.
  • X2Effect_ApplyWeaponDamage Source: The effect itself, to help determine the results. Do not attempt to modify it or invoke functions which would have side effects.
  • optional bool ForMinDamagePreview: If invoked by a damage preview, specifies whether WeaponDamage contains the minimum or maximum damage for the attack in the previewed hit context.
  • optional XComGameState NewGameState: The new game state after the attack resolves. If no game state was provided, then the delegate has been invoked by a damage preview (check ForMinDamagePreview to determine which one).

How to use (for end users)

Implement the following code in your mod's X2DownloadableContentInfo class:

static event OnPostTemplatesCreated()
{
    local CHHelpers CHHelpersObj;

    CHHelpersObj = class'CHHelpers'.static.GetCDO();
    if (CHHelpersObj != none)
    {
        CHHelpersObj.AddAdjustArmorMitigationCallback(AdjustArmorMitigation);
    }
}

// To avoid crashes associated with garbage collection failure when transitioning between Tactical and Strategy,
// this function must be bound to the ClassDefaultObject of your class. Having this function in a class that
// `extends X2DownloadableContentInfo` is the easiest way to ensure that.
static private function EHLDelegateReturn AdjustArmorMitigation(int WeaponDamage, out int ArmorMitigation, out int ArmorPiercing, out int MinMitigation, out int ArmorShred, EffectAppliedData ApplyEffectParams, X2Effect_ApplyWeaponDamage Source, optional bool ForMinDamagePreview, optional XComGameState NewGameState)
{
    // Detect whether it's a damage preview (and if so, which kind).
    // Then optionally modify any of ArmorMitigation, ArmorPiercing,
    // MinMitigation, and ArmorShred.

    // Return EHLDR_NoInterrupt or EHLDR_InterruptDelegates depending on
    // if you want to allow other delegates to run after yours
    // and potentially modify mitigation values further.
    return EHLDR_NoInterrupt;
}

How to use (for effect/UI devs)

Damage Previews

After all damage has been calculated for the hit context being previewed, invoke class'CHHelpers'.static.GetCDO().TriggerAdjustArmorMitigation(WeaponDamage, ArmorMitigation, ArmorPiercing, MinMitigation, ArmorShred, ApplyEffectParams, Source, ForMinDamagePreview).

You will need to make separate calls for minimum and maximum damage, using separate variables for ArmorMitigation, ArmorPiercing, MinMitigation, and ArmorShred. Set ForMinDamagePreview according to whether it is the minimum or maximum damage.

If previewing multiple hit contexts (e.g. normal and critical hits), make sure to pass total damage for that hit context into WeaponDamage for correct results. For instance, previewing minimum damage for a 3-5 (+2) weapon should be done with a WeaponDamage of 5 (i.e. 3+2), not 2.

If you are not also providing overrides for all UIs that might use your damage preview, then please store the delegate results in your WeaponDamageValue structs, so that downstream UIs will be aware of them and be able to correctly preview them:

  • ArmorMitigation --> WeaponDamageValue.Spread
  • ArmorPiercing --> WeaponDamageValue.Pierce
  • MinMitigation --> WeaponDamageValue.PlusOne

(ArmorShred is not covered in the above list, because you should already have been storing shredding since before this feature was created.)

Damage Calculations

After all damage has been calculated for the attack, invoke class'CHHelpers'.static.GetCDO().TriggerAdjustArmorMitigation(WeaponDamage, ArmorMitigation, ArmorPiercing, MinMitigation, ArmorShred, ApplyEffectParams, Source, ForMinDamagePreview, NewGameState).

The value of ForMinDamagePreview does not matter, as the delegates should ignore it whenever a NewGameState is provided. However, the Highlander implementation passes false, so it is recommended that you do the same for consistency.

Delegate Priority

You can optionally specify callback Priority.

CHHelpersObj.AddAdjustArmorMitigationCallback(AdjustArmorMitigation, 45);

Delegates with higher Priority value are executed first. Delegates with the same Priority are executed in the order they were added to CHHelpers, which would normally be the same as DLCRunOrder. This function will return true if the delegate was successfully registered.

Include Armor in Damage Preview

A new config flag has been provided in XComGame.ini which adjusts all damage previews to include armor mitigation. For instance, an attack dealing 3-5 damage with no piercing will preview 2-4 damage against a target with 1 armor pip, and report an "Armor" damage modifier of -1.

This damage modifier also includes any mitigation added by AdjustArmorMitigation. If the adjusted mitigation is not constant for the attack (e.g. it scales off the damage dealt by the attack), it will show a range, e.g. "-1-2".

Note that this flag is only guaranteed to apply to vanilla UI. Mods overriding UIUnitFlagManager, UITacticalHUD_ShotHUD, or UITacticalHUD_ShotWings may ignore this setting for one reason or another.

[XComGame.CHHelpers]
; Set to false/commented out if you do not want armor mitigation to be factored into damage previews (vanilla behavior)
; Set to true/uncomment it if you want armor mitigation to be factored into damage previews
;PREVIEW_ARMOR_MITIGATION=true

Source code references