QOL Change for the "Damage" Ability Description
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!
- 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);
}
}
- Next, open the
ImmediateDamageEffect.csScript and find this field:
[SerializeField] private DamageDescriptor m_damageDescriptor;
Right under it, add this field:
public DamageDescriptor damageDescriptor => m_damageDescriptor;
- Open the
TemporalDamageEffect.csscript 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;
- 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);
- 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";
}

💬 Comments (0)
Be the first to comment! Join our Discord to share your thoughts.
Want to continue the conversation?