Curve tables¶
Designer · reference · No code
XenoCore keeps its level-driven numbers in five curve tables in Battle/Attributes/. Each row is a
curve: the X axis is a level (or a level difference) and the value is read at that X. Nothing in
C++ holds these numbers, so balancing is a data job.
| Table | X axis | What reads it | What it controls |
|---|---|---|---|
CT_XenoCore_PartyStatlines |
Character level | Each sample character's own stats effect (GE_XenoCore_BaseStats_Kael ... _Tamsin) |
That character's Max HP, Strength, Ether, Agility: rows <Name>.<Stat>, read at ×1.0 |
CT_XenoCore_BaseStats |
Character level | The stock members' stats effects (GE_XenoCore_BaseStats_Shulk, _Party_*) |
Their Max HP, Strength, Ether, Agility: shared rows with a coefficient per character |
CT_XenoCore_EnemyStats |
Enemy level | The enemy stats effects (GE_XenoCore_BaseStats_Minion_Melee, _Minion_Skirmisher, _Minion_Bulwark, _Minion_Brute, GE_XenoCore_BaseStats_Dummy; the other types scale the same rows by a multiplier on their effect), and the enemy's Experience Level Scale |
Enemy Max HP, Strength, Ether, Agility, defences; how kill EXP grows with the enemy's level |
CT_XenoCore_RewardLevelDiff |
Party level minus enemy level | Game/BP_XenoCore_Progression › Reward Multiplier Curve |
EXP multiplier for a kill |
CT_XenoCore_DamageLevelDiff |
Attacker level minus target level | Battle/Attributes/BP_XenoCoreDamageExecution › Level Difference Curve |
Damage multiplier by level gap |
EXP needed per level is not a curve table
It is the Data Table Game/DT_XenoCore_ExperienceCurves, one row per curve, picked per
character. See Tune levelling.
How a table is read¶
There are two ways, and they fail differently.
Stats tables (BaseStats, EnemyStats). A stats Gameplay Effect has one modifier per
attribute. Each modifier's magnitude is a scalable float: a number multiplied by a curve-table row
evaluated at the effect's level. The character's level is passed as the effect level, so the row is
read at that level. The effect is picked per character by the pawn's Stats Effect property
(XenoCore › Combatant on party pawns, XenoCore › Enemy on enemies).
Fails silently: the number in the table is not always the number in the game
A scalable float multiplies its curve by its own coefficient. If the coefficient is not
1.0, the game uses a different number from the one you see in the table, and nothing says so.
The shipped minion types use this on purpose: the melee minion reads its rows at 1.0, and the
Brute (1.45), Bulwark (1.15) and Skirmisher (0.9) scale the same rows. Before you
rebalance a row, open the effect that reads it and check the coefficient on each modifier.
Lookup tables (RewardLevelDiff, DamageLevelDiff). C++ looks the row up by name on the
table assigned in a Blueprint (Reward Multiplier Curve, Level Difference Curve) and
evaluates it at X.
| If… | You get | Logged? |
|---|---|---|
| The table property is empty | Reward and damage multipliers read 1.0. |
No |
| The table is set but the row name is wrong | Same fallbacks as above | Yes, a warning naming the row |
Fails silently: an empty curve property
If Reward Multiplier Curve is cleared, every kill pays its flat reward. If Level Difference Curve is cleared, level gaps stop affecting damage. Neither logs anything. Keep a table assigned, even if you flatten its values.
The rows¶
Row names must match exactly: C++ or the stats effects refer to them by name.
CT_XenoCore_BaseStats¶
Shulk.MaxHealth
Shulk.Strength
Shulk.Ether
Shulk.Agility
One set of rows, read by every stock party member's stats effect (GE_XenoCore_BaseStats_Shulk
and the eleven _Party_* effects). Members differ only through their own stats effect: each
_Party_* effect multiplies the same rows by its own coefficient per stat (between 0.8 and 1.2
when last measured), so the number in the table is not the number that member gets.
Scheduled for renaming
These row names are placeholders and will be renamed before release. When that happens every
stats effect that reads them is updated in the same change. If you add your own character,
add your own rows (for example MyHero.Strength) rather than editing these.
Physical and Ether Defence, critical rate and block rate are not in this table: party members have none of their own, and all of it comes from equipment.
CT_XenoCore_EnemyStats¶
Minion_Melee.MaxHealth Minion_Melee.Strength Minion_Melee.Ether
Minion_Melee.Agility Minion_Melee.PhysicalDefence Minion_Melee.EtherDefence
Minion_Melee.CharacterLevel
Minion_Ranged.* Minion_Siege.* Minion_Super.* (same seven stats each)
Minion.ExperienceScale
Enemy stats grow linearly with level. The melee minion's HP and Strength follow an XC1 field
enemy's line (Max HP 900 + 100 × (level − 1), Strength 69 + 10 × (level − 1)). Only the
Minion_Melee.* rows are read by a shipped stats effect: all seven sample enemy types read them,
through their effect's multiplier. The Minion_Ranged, Minion_Siege and Minion_Super rows are
not read by anything. *.CharacterLevel is not read by any effect.
Minion.ExperienceScale is read by the minion's Experience Level Scale (XenoCore › Enemy):
the kill's Experience Reward is multiplied by this row at the enemy's level, so a same-level
kill is worth about the same share of a level all the way up. An enemy with the property empty pays
a flat reward.
Defence means different things on the two sides
On an enemy, PhysicalDefence and EtherDefence are fractions (0 to 0.9): damage is
multiplied by 1 − defence. On a party member the same attributes are flat points
subtracted from the attacker's stat. Author enemy defence rows as fractions. The execution
clamps a fraction to Max Defence Fraction (default 0.9).
The level cap¶
The level cap is not any table's length: it is MaxCharacterLevel on UXenoCoreStatSet, default
99. It is a config value, so you can raise it without touching C++. Add this to your project's
Config/DefaultGame.ini:
[/Script/XenoCoreRuntime.XenoCoreStatSet]
MaxCharacterLevel=120
Then extend the stats rows to the new cap (past its last key a stats curve holds its final
value), and lengthen every row of the Data Table DT_XenoCore_ExperienceCurves. A character whose
EXP row is shorter than the cap stops levelling at the end of the list; validating the table warns
about a row shorter than the cap.
CT_XenoCore_RewardLevelDiff¶
Reward.LevelDiffMultiplier
X is party level minus enemy level. A positive X (you out-level the enemy) should give a multiplier below 1; a negative X above 1. Keys run from −20 to +20; outside that the curve's end values hold.
CT_XenoCore_DamageLevelDiff¶
Damage.LevelDiff.Physical
Damage.LevelDiff.Ether
X is attacker level minus target level. The execution picks the Ether row for ether attacks and the Physical row otherwise, and multiplies damage by the result.
Editing a table¶
- Duplicate the table into your own folder, for example
Content/MyGame/Data/. - Open the duplicate. Edit values in the curve table editor, or add rows with the same naming pattern.
- Point the reader at your duplicate: the stats effect's modifiers, or the Blueprint property in the table above.
Keep row names when you duplicate
The lookup tables are found by row name (Reward.LevelDiffMultiplier,
Damage.LevelDiff.Physical, Damage.LevelDiff.Ether). A duplicate with renamed rows falls back
to the defaults above, with only a warning in the log.
The shipped tables were imported from CSV files that are not part of the template, so Reimport will not find a source. Unreal then asks whether to specify a new source file: Yes lets you pick your own CSV, No skips the reimport. Edit the tables in the editor, or export to CSV and reimport from your own file.