Skip to content

Stats and damage

All · 20 min read · No code

A character's numbers are Gameplay Ability System attributes on its own ability system. Health comes from Lyra's health set; everything else (Strength, Ether, Agility, defences, rates, weapon attack, level, talent gauge) is XenoCore's UXenoCoreStatSet. Numbers get onto a character in two layers: a statline effect that reads curve tables at the character's level, and an equipment effect that adds the stats of what it wears. Damage is calculated by one execution, UXenoCoreDamageExecution, set up as Battle/Attributes/BP_XenoCoreDamageExecution and used by Battle/Attributes/GE_XenoCore_Damage. The one idea: the formula is not symmetric. A party member hitting an enemy treats the enemy's defence as a fraction; an enemy hitting a party member subtracts the party member's defence as flat points.

At a glance

In play: the numbers over every hit, MISS, BLOCKED and CRITICAL HIT!, and the stats on the Party and Equipment screens.

What you can change, no code:

How it fits together

Layer Asset What it sets
Attribute set UXenoCoreStatSet (C++), granted by every character's ability set Which stats exist.
Health Lyra's ULyraHealthSet, created with the pawn's ability system Health, Max Health.
Statline Battle/Attributes/GE_XenoCore_BaseStats_* Base stats at a level, read from a curve table. Override modifiers. One per character (_Kael, _Mira ...) and one per enemy type.
Party curves Battle/Attributes/CT_XenoCore_PartyStatlines Each character's own rows, such as Kael.Strength, one curve per stat, X = level. Each follows the XC1 character whose skill tree it uses. CT_XenoCore_BaseStats holds the older shared Shulk.* rows the colour mannequins still read.
Enemy curves Battle/Attributes/CT_XenoCore_EnemyStats Rows such as Minion_Melee.Strength.
Level UXenoCoreMMC_EffectLevel and the pawn's C++ Character Level is set to the level the statline was applied at.
Equipment Inventory/GE_XenoCore_EquipmentStats Adds each worn item's stats through Set By Caller tags XenoCore.Stat.*.
Damage GE_XenoCore_Damage → BP_XenoCoreDamageExecution Health damage per hit.
Level difference Battle/Attributes/CT_XenoCore_DamageLevelDiff Rows Damage.LevelDiff.Physical and Damage.LevelDiff.Ether.

The attributes

Attribute Meaning Range
Strength Physical attack stat. Does nothing for ether attacks. 0 and up
Ether Ether attack stat. Does nothing for physical attacks. 0 and up
Agility Hit chance and evasion. 0 and up
Physical Defence, Ether Defence Flat points on a party member; a fraction (0 to 0.9) on an enemy. 0 and up
Critical Rate, Block Rate Chance per hit, as a fraction. 0.1 is 10%. 0 to 1
Weight Armour weight, subtracted from Agility for hit rolls only. 0 and up
Weapon Attack Min, Weapon Attack Max The weapon's damage range. 0 and up
Character Level The level used in every curve lookup. 1 to Max Character Level (99)
Experience, Experience To Next Level Written by the progression component for display.
Talent Gauge 0 to 100. Fills as auto-attacks land; spent by Talent Arts. 0 to 100
Talent Charge Per Hit Gauge percent added per landed auto-attack. Set by the statline. 0 and up
Auto Attack Speed Multiplier on swing cadence; Haste and Slow change it. 0.1 to 4

A party character has no defence, block or critical chance of its own: those come only from equipment. That is intended.

Max Character Level is a config value with no editor field. To raise the cap, close the editor, open your project's Config/DefaultGame.ini in a text editor, and add:

[/Script/XenoCoreRuntime.XenoCoreStatSet]
MaxCharacterLevel=120

and extend your curve tables, and every row of DT_XenoCore_ExperienceCurves, to the new cap.

The damage formula

Every hit runs these steps, in order:

  1. Team check. If the attacker may not damage the target (same team, or no team), the hit does nothing and a warning is logged under LogXenoDamage.
  2. School. Ether if the damage effect's spec carries XenoCore.Art.School.Ether, otherwise physical. The school decides which attack stat and which defence are used.
  3. Contact roll: miss, block or hit. Rolled by the attacking ability before the effect is applied (see below). A miss shows MISS over the target and nothing else happens: no damage, no reaction, no talent charge. A block shows BLOCKED and marks the hit as blocked for step 8.
  4. Weapon roll. A random value between Weapon Attack Min and Max, with Max capped at Min + 99.
  5. Art multiplier. The Set By Caller XenoCore.Art.Magnitude.Damage. Auto-attacks send none, so it is 1.
  6. Modifiers. 1, or the critical multiplier on a critical hit. Never below Minimum Modifiers.
  7. Level multiplier. The level-difference curve at (attacker level − target level).
  8. Block. If step 3 rolled a block, damage is multiplied by Block Multiplier: 0 by default, so a block takes no damage at all.
  9. The formula, then rounded down:
Attacker Damage
Party member (stat + weapon) × art × (1 − defence) × modifiers × level × block, with defence capped at Max Defence Fraction
Enemy max(stat − defence, 0) × art × modifiers × level × block

The side is decided by what the attacker is, not by who is driving it, so a companion under AI control and the same character under your control deal the same damage. In order:

  1. An AXenoCoreEnemyCharacter is always an enemy.
  2. A character with a Member Definition (every party member) is always party.
  3. Otherwise, the team: on the player's team is party, on any other team is enemy. This covers a player pawn with no member definition, such as the one Lyra spawns when Starting Roster is empty.
  4. Last resort, for an actor with no team: whether a player is controlling it.

If the attacker and the target come out on the same side, a warning is logged under LogXenoDamage: the target's defence is then being read in the wrong units, which means your team setup lets a side hurt itself.

Critical hits roll against the attacker's Critical Rate. The multiplier is Critical Multiplier (1.25), or Critical Multiplier Vs Reacting (1.5) against a Toppled or Dazed target. An ether attack against a Dazed target is always critical.

A critical hit shows CRITICAL HIT! over the target, draws the damage number gold and plays the heavier critical hit sound. It also adds 10 to the Party Gauge when a party member lands it. The text, its size and punch, and an optional hit-stop are under Project Settings › Game › XenoCore Content › Critical Hit; see Tune hit feedback.

Hit rolls. The chance is 1 + (attacker − target) × Agility Hit Per Point, using Agility minus Weight on both sides, plus a level-difference modifier, clamped between Hit Chance Floor (5%) and Hit Chance Cap (100%). Equal agility and equal level always hit.

  • Ether attacks ignore Agility but still take the level modifier, so they miss enemies well above you.
  • A Toppled or Dazed target cannot be missed.
  • The level modifier comes from Physical Hit By Level Gap or Ether Hit By Level Gap, indexed by the level gap (0 to 10, the last entry covering 10 or more). It is a bonus when the attacker is higher and a penalty when it is lower. The shipped numbers are XC1's: a physical attacker 3 levels below loses 24%, and at 6 or more below it loses 120% to 200%, which puts it on the 5% floor.

Block rolls. Physical hits only; ether is never blocked. The chance is the target's Block Rate plus Block Rate Per Level (5%) for each level the attacker is below the target (a penalty when it is above), capped at ± Block Level Modifier Cap (100%). A Toppled or Dazed target does not block.

  • Party members get Block Rate only from their weapon and skills.
  • Enemies have none of their own, so an enemy blocks only when it is higher-levelled than you.
  • Block Level Dead Zone (0) ignores small gaps; set it to 2 for no level effect within ±2.

XenoCore departs from XC1 on blocks

In XC1 a block halves the damage. XenoCore's Block Multiplier defaults to 0, so a block negates the hit completely. Set it to 0.5 on BP_XenoCoreDamageExecution to restore XC1.

Fails silently: a blocked opening hit does not start the fight

An enemy is drawn into a fight by losing HP. With Block Multiplier at 0, an enemy that blocks your first hit takes no damage and stays where it is. Hit it again.

Fails silently: the team gate

Damage between actors on the same team, or where either has no team, is dropped. The target still flinches, because the hit-react event is sent separately, so "it reacted" does not prove damage landed. Type log LogXenoDamage Verbose in the console to see every hit's breakdown.

The hit roll reads its settings (Agility Hit Per Point, Hit Chance Floor, Hit Chance Cap, Miss Colour) from the execution class the attack's damage effect actually runs, so edits on BP_XenoCoreDamageExecution take effect like every other setting there. A damage effect that runs no XenoCore damage execution falls back to the C++ defaults, and a warning naming the effect is logged once under LogXenoDamage.

Lifecycle

  • Spawn and level-up. The character applies its Stats Effect at its level; every modifier is an Override, so re-applying replaces rather than stacks. The same effect also overrides Health, which is why a level-up fully heals. See Progression.
  • Equip. Equipping runs GA_XenoCore_EquipmentStats, which applies GE_XenoCore_EquipmentStats with each XenoCore.Stat.* magnitude filled from the item's own stat tags. Unequipping removes it. See Equipment.
  • Statuses such as GE_XenoCore_Status_StrengthDown or _Slow modify attributes for their duration. See Reactions and statuses.

What you can change

Setting Where it lives What it does
Base stat curves CT_XenoCore_BaseStats, CT_XenoCore_EnemyStats Stat value at each level.
Which rows a character reads That character's GE_XenoCore_BaseStats_* modifiers The curve row and a scale per stat.
Level Difference Curve BP_XenoCoreDamageExecution › XenoCore › Damage The level-gap multiplier table.
Max Defence Fraction same Defence cap on the party formula (0.9).
Minimum Modifiers same Floor of the modifiers bucket (0.1).
Critical Multiplier, Critical Multiplier Vs Reacting same 1.25 and 1.5.
Block Multiplier same Damage a blocked hit keeps: 0 (XC1 is 0.5).
Agility Hit Per Point, Hit Chance Floor, Hit Chance Cap same The hit roll: 0.01, 5% and 100%.
Physical Hit By Level Gap, Ether Hit By Level Gap same › XenoCore › Damage › Level Difference Hit-chance change per level of gap, 0 to 10+.
Block Rate Per Level, Block Level Dead Zone, Block Level Modifier Cap same Block-chance change per level of gap: 5%, 0 and 100%.
Miss Colour, Critical Colour, Blocked Colour same › XenoCore › Damage › Notifications Battle-text colours.
Critical Hit Text, Critical Hit Scale, Critical Hit Punch, Critical Hit Stop Seconds, Critical Hit Stop Dilation Project Settings › Game › XenoCore Content › Critical Hit The critical line's wording, size and punch, and an optional hit-stop (0 s, off, by default).
Max Character Level DefaultGame.ini, section above Level cap.

The shipped BP_XenoCoreDamageExecution changes only Level Difference Curve; every other value above is the class default. CT_XenoCore_DamageLevelDiff multiplies damage by the gap (attacker level − target level):

Gap −6 or less −5 to −3 −2 −1 to +2 +3 +4 to +9 +10 or more
Damage.LevelDiff.Physical 0.5 0.75 0.875 1.0 1.125 1.25 2.0
Damage.LevelDiff.Ether 0.625 0.875 0.9375 1.0 1.125 1.25 1.5

Every statline modifier must be Override

A statline is re-applied on every level-up. A modifier using Add stacks on the previous application and the numbers climb with each level. Use Override on every modifier of a GE_XenoCore_BaseStats_* effect.

Enemy types are one curve times a multiplier

The minion's rows are anchored on XC1's Hand Bunnit: Minion_Melee.MaxHealth is 900 + 100 per level and Minion_Melee.Strength 69 + 10 per level. The other minion types read the same rows, and their GE_XenoCore_BaseStats_Minion_* effects scale them on each modifier: Melee 1.0, Brute 1.45, Bulwark 1.15, Skirmisher 0.9. So the number the game uses is not always the number in the table. Check the scale on every modifier of an effect you duplicate.

Every character has its own statline

Each sample character's GE_XenoCore_BaseStats_<Name> reads its own rows of CT_XenoCore_PartyStatlines at a scale of 1.0, so the table number is the game number. To give a new character its own growth, add rows for it and point its effect at them. See Give a character its own statline.

Extending it

  • A new stat: needs C++, because attributes are reflected properties. Add it to a subclass of UXenoCoreStatSet or a new set, grant that set in your ability sets, and read it in a damage execution subclass. See Add a stat to the damage formula.
  • A different formula: subclass UXenoCoreDamageExecution in C++ and point a duplicate of GE_XenoCore_Damage at it. Keep the team check.
  • Tuning only: make a child Blueprint of BP_XenoCoreDamageExecution in Content/MyGame/ and point your damage effect at the child, rather than editing the shipped one.

Why it's built this way

For programmers: the design reasons
  • Curve tables, not per-level data assets. One row per stat covers every level, and a statline effect sampled at a level is plain GAS: nothing custom stores numbers.
  • Level set by the pawn, not a curve row. An earlier design read level from a curve whose input was also level, which could silently disagree with the badge.
  • The hit and block rolls live in the attacking ability. An execution can only output attribute changes; it cannot tell the ability "you missed" or "you were blocked". Rolling first lets a miss cancel the whole hit, and lets a block stop a Topple and the rest of a multi-hit art.
  • Its own execution instead of Lyra's. Lyra's formula is a shooter's. Lyra's team check is kept, because without it arts would damage allies.