← Back to all addons

QOL Change for the "Damage" Ability Description

Posted by SvanDark on Feb 12, 2026 at 12:06 PM

3.0 Verified

What you’ll get with this

Right now your Damage Description in the Abilities tab says:

X Number Scaled Phy (0,6 Scaled phy as an example)

With this addon it’ll show the exact damage it does, with and without critical hits!

  1. Open DamageSolver.cs

Scroll to the very bottom of the DamageSolver class, right before the final closing } of the class.

And add this there:

public static void PreviewOutgoingDamageNoRng(
    CharacterBase attacker,
    DamageDescriptor input,
    out int normalDamage,
    out int criticalDamage,
    out bool canCrit,
    out bool alwaysCrit)
{
    normalDamage = 0;
    criticalDamage = 0;
    canCrit = false;
    alwaysCrit = false;

    if (!attacker)
    {
        normalDamage = input.flatDamages;
        criticalDamage = input.flatDamages;
        return;
    }

    int baseDamage = CalculateDamageOut(
        input.flatDamages,
        input.scalingFactor,
        GetOffensiveStat(attacker.currentStats.values, input.damageType));

    canCrit =
        GameManager.Exists() &&
        GameManager.Config != null &&
        GameManager.Config.canCriticalHit &&
        input.criticalBehavior != EResolutionBehavior.Never;

    alwaysCrit = canCrit && input.criticalBehavior == EResolutionBehavior.Always;

    int baseCritDamage = canCrit ? CalculateCriticalDamage(baseDamage) : baseDamage;

    // Outgoing difficulty scaling (same as SolveDamageOutput)
    GetDifficultyMultipliers(out float playerTaker, out float playerDealt, out float enemyHpMult, out float enemyDmgMult);

    if (GameManager.Exists() && attacker == GameManager.Player)
    {
        float dealtMult = playerDealt / enemyHpMult;
        normalDamage = ScaleInt(baseDamage, dealtMult);
        criticalDamage = ScaleInt(baseCritDamage, dealtMult);
    }
    else
    {
        normalDamage = ScaleInt(baseDamage, enemyDmgMult);
        criticalDamage = ScaleInt(baseCritDamage, enemyDmgMult);
    }
}
  1. Next, open the ImmediateDamageEffect.cs Script and find this field:
[SerializeField] private DamageDescriptor m_damageDescriptor;

Right under it, add this field:

public DamageDescriptor damageDescriptor => m_damageDescriptor;
  1. Open the TemporalDamageEffect.cs script and find this field:
[SerializeField] private DamageData m_damageData;

directly under it, add:

public DamageDescriptor damageDescriptor => m_damageData.damage;
public float interval => m_damageData.interval;
  1. Now the last part, open UIAbilities.cs

Start by finding this section:

List<AbilityDescriptionLine> lines = new();
sheet.GenerateAdditionalDescriptionLines(lines);

if (lines.Count > 0)
    m_description.text += "\n";

Insert this line between sheet.GenerateAdditionalDescriptionLines(lines); and the if (lines.Count > 0) lines:

ApplyLiveDamagePreviewToDescriptionLines(sheet, lines);
  1. Still in UIAbilities

Scroll to the bottom of the UIAbilities class, right before the final closing } of the class and paste this entire block:

private struct DamageLinePreview
{
    public string header;
    public string content;
}

private void ApplyLiveDamagePreviewToDescriptionLines(AbilitySheet sheet, List<AbilityDescriptionLine> lines)
{
    if (lines == null || lines.Count == 0) return;
    if (!GameManager.Exists() || GameManager.Config == null || GameManager.Player == null) return;

    try
    {
        var attacker = GameManager.Player;

        // Build previews from effects on this sheet (covers "0,6 Scaled Phy." if it's effect-based)
        List<DamageLinePreview> previews = new();
        CollectDamageLinePreviewsFromSheet(sheet, attacker, previews);

        if (previews.Count == 0)
            return;

        int previewIndex = 0;

        for (int i = 0; i < lines.Count && previewIndex < previews.Count; i++)
        {
            var l = lines[i];

            if (string.IsNullOrEmpty(l.header))
                continue;

            if (string.Equals(l.header, previews[previewIndex].header, StringComparison.OrdinalIgnoreCase))
            {
                l.content = previews[previewIndex].content;
                lines[i] = l;
                previewIndex++;
            }
        }
    }
    catch
    {
        // Never break the UI if preview fails
    }
}
private void CollectDamageLinePreviewsFromSheet(AbilitySheet sheet, CharacterBase attacker, List<DamageLinePreview> outPreviews)
{
    if (sheet == null || attacker == null || outPreviews == null) return;
    if (GameManager.Config == null) return;

    // Header names used by effect descriptions
    string damageHeader = GameManager.Config.GetTermDefinition("remove_health").shortName;
    string dotHeader = GameManager.Config.GetTermDefinition("damage_over_time").shortName;

    // This covers the common case: ActiveAbilitySheet effects generate the "Damage:" line.
    if (sheet is ActiveAbilitySheet active)
    {
        foreach (var e in active.autoAppliedEffectsToCasterOnFire)
        {
            if (e == null) continue;

            if (e is ImmediateDamageEffect ide)
            {
                outPreviews.Add(new DamageLinePreview
                {
                    header = damageHeader,
                    content = BuildImmediateDamagePreviewText(attacker, ide.damageDescriptor)
                });
            }
            else if (e is TemporalDamageEffect tde)
            {
                outPreviews.Add(new DamageLinePreview
                {
                    header = dotHeader,
                    content = BuildDotDamagePreviewText(attacker, tde.damageDescriptor, tde.interval)
                });
            }
        }
    }
}
private string BuildImmediateDamagePreviewText(CharacterBase attacker, DamageDescriptor dd)
{
    string typeShort = (GameManager.Config != null)
        ? GameManager.Config.GetTermDefinition(dd.damageType).shortName
        : dd.damageType.ToString();

    DamageSolver.PreviewOutgoingDamageNoRng(attacker, dd, out int normal, out int crit, out bool canCrit, out bool alwaysCrit);

    if (alwaysCrit)
        return $"{crit} {typeShort}";

    if (canCrit)
        return $"{normal} {typeShort} (crit {crit})";

    return $"{normal} {typeShort}";
}

private string BuildDotDamagePreviewText(CharacterBase attacker, DamageDescriptor dd, float interval)
{
    string typeShort = (GameManager.Config != null)
        ? GameManager.Config.GetTermDefinition(dd.damageType).shortName
        : dd.damageType.ToString();

    DamageSolver.PreviewOutgoingDamageNoRng(attacker, dd, out int normal, out int crit, out bool canCrit, out bool alwaysCrit);

    return $"{normal} {typeShort}/{interval:0.#}s";
}

DONE!

💬 Comments (0)

Be the first to comment! Join our Discord to share your thoughts.

Want to continue the conversation?