-
Notifications
You must be signed in to change notification settings - Fork 1
Creature stats
The eight figures on a creature's line, what each one is actually read for, and what it is worth.
Checked against the tree on 2026-09-24. Every effect below was found by following the stat to the code that reads it; where a stat is read nowhere, this says so rather than inventing a purpose for it.
XStats::Id in magic/stats.h:
enum Id {UNKNOWN = -1, STR, DEX, TOU, LEN, WIL, MAN, PER, CHR, COUNT};| Keyword | Name | Enum | Read for |
|---|---|---|---|
St |
Strength | XStats::STR |
damage (through tactics), carrying, unwieldy weapons |
Dx |
Dexterity | XStats::DEX |
to-hit and dodge (through tactics), climbing out of pits |
To |
Toughness | XStats::TOU |
maximum hit points, PV, hit points gained per level |
Le |
Learning | XStats::LEN |
reading speed, skill picks per level (hero only) |
Wi |
Willpower | XStats::WIL |
spell power, and so spell range |
Ma |
Mana | XStats::MAN |
maximum power points, power points per level |
Pe |
Perception | XStats::PER |
how far the creature can see |
Ch |
Charisma | XStats::CHR |
nothing - see below |
The keyword is what content writes; helpers/keyword_dice.cpp maps the
two-letter form to the enum. Both "St 1d3+10 ..." and "St:1d3+10 ..."
parse - ParseKeywordDice() takes either.
-
Damage, but only by way of the creature's tactics
(
XCreature::GetTacticsDMGBonus). The divisor is the tactic: berserkerSTR/2, aggressiveSTR/5, normalSTR/7, defensiveSTR/10, cowardSTR/20. Note thatGetDMG()isadded_DMG + GetTacticsDMGBonus()- strength is not added to a weapon's damage directly, it arrives entirely through this bonus.
-
Heavy weapons:
GetUnwieldyHITPenalty()compares30 * strengthagainst the weapon's weight, halved again when the other hand is also full. Over that weight the to-hit penalty grows with the logarithm of how far over; under it there is no bonus. -
Carrying:
GetSpeed()slows the creature atstrength * 120,* 200and* 280of carried weight (to 110%, 130% and 200% of its turn length).
-
To-hit, through tactics (
GetTacticsHITBonus): berserkerDEX/2, aggressiveDEX/3, normalDEX/4, defensiveDEX/7, cowardDEX/10. -
Dodging, through tactics (
GetTacticsDVBonus): coward3*(DEX+tactics)/2, defensiveDEX+tactics, normal2*DEX/3 + tactics. Both bonuses also add the TACTICS skill level. -
Climbing out of a pit:
30 + climbing*5 + DEX*2per cent (map/map_objects.cpp).
The most valuable stat in the game, because it is read three times:
-
Maximum hit points:
GetMaxHP() = MAX_HP + MAX_HP * TOU / 20. At Toughness 20 a creature has twice the hit points its dice rolled. -
Protection:
GetPV() = pv + added_PV + TOU / 10. -
Growth: each level adds
vRand(TOU/5 + 1) + 1toMAX_HP. - For the hero only, it also sets the starting pool:
MAX_HP = TOU/2 + 3(creature/xhero.cpp).
Two effects, both modest, and one of them is the hero's alone:
-
Reading: a book's remaining pages fall by
skill level + LENper turn (item/xbook.cpp). -
Skill picks on levelling:
XHero::IncLevel()grantsLEN/5 + 1skill improvements, with a floor of 3. A monster'sIncLevel()does not do this - it only gains hit points and power points - so Learning does nothing at all for a monster except let it read faster, which no monster does.
It does not affect experience gained. Nothing reads it for that.
-
Spell power:
XMagic::GetSpellPower() = WIL + spell effectivity + the caster's level in that school, and spell power decides a spell's range throughXEffect::GetRange().
That is its only reader. It is not consulted for resistance to fear, confusion or charm - those are resistances, not stats.
-
Maximum power points:
GetMaxPP() = MAX_PP + MAX_PP * MAN / 10. Note the divisor is 10, not Toughness's 20, so Mana is twice as potent per point for power as Toughness is for health. -
Growth: each level adds
vRand(MAN/2 + 1) + 1toMAX_PP. - For the hero only:
MAX_PP = MAN/2 + 1at the start.
-
Sight radius, and nothing else -
XCreature::GetVisibleRadius():Perception Radius under 5 3 5-9 4 10-19 5 20-49 6 50-89 7 90 and over more This matters more than it looks: the radius is what
AnalyzeView()is given, so it sets how far a creature notices enemies, friends, items and stairs.
Read nowhere but the status line. The only GetStats(XStats::CHR) in
the tree is the one printing it in XCreature::PutStatus(). It does not
affect prices (the shop never asks), reactions, morale or anything else.
Every creature in the world still rolls one, and some roll it high -
Ahk-Ulan has Ch 7d5, the shopkeeper Ch 5d5. It is currently flavour.
Worth either wiring up or admitting to.
Stats are held multiplied by 100 and divided back on the way out:
int Get(Id s) const { return stats[s] / 100; } // stats.h
void SetStat(Id s, int val) { stats[s] = val * 100; } // stats.hSo St 2d4 rolls 2-8, is stored as 200-800, and reads back as 2-8. The
hundredths exist so that an effect can move a stat by a fraction of a
point; nothing in content can, since Stats() only takes whole dice.
Two routes in:
-
XStats::Set(const char*)parses a keyword string and adds:stats[keyword_index] += dice.Throw() * 100. It rolls the dice there and then, at world-load time - which is why adding or removing aStats()line shifts every seed downstream. -
XStatsGenerator::Generate()rolls the generator's stored dice into a freshXStatsat creature creation, throughSetStat().
XStatsGenerator::Init() merges: it only overwrites the stats named in
the string and leaves the rest as they were. That matters for a creature
inheriting from another - name a stat and you replace it, leave it out and
you keep the parent's.
-- world/creatures/rats.lua - a rat
:Stats("St 1d2 Dx 2d4 To 1d2 Le 1d1 Wi 1d1 Ma 1d1 Pe 2d4 Ch 1d1")Toughness 1-2, so it keeps roughly the hit points its dice rolled; Perception 2-8 gives it a sight radius of 3 or 4.
-- world/creatures/humans.lua - a dwarven guardian
:Stats("St 1d3+20 Dx 1d3+7 To 1d3+22 Le 2d3 Wi 1d3 Ma 1d2 Pe 1d6 Ch 1d3")Toughness 23-25 more than doubles its hit points (×(1 + 23/20)), and adds
+2 PV. Strength 21-23 on normal tactics is STR/7 = +3 damage.
-- world/uniques/ahkulan.lua - Ahk-Ulan
:Stats("St 1d8+15 Dx 1d8+40 To 1d8+10 Le 1d5+45 Wi 1d5+45 Ma 1d5+45 Pe 5d6 Ch 7d5")Willpower 46-50 is what makes his spells reach and bite; Mana 46-50 with
the /10 divisor gives him roughly six times his rolled power points.
Learning 46-50 buys him nothing - he is not the hero and he reads no books.
| What | Where |
|---|---|
The enum, Get, SetStat
|
magic/stats.h, class XStats
|
| Parsing, generation | magic/stats.cpp |
| Keyword to enum | helpers/keyword_dice.cpp |
The Lua table XStats.STR … |
XStats::RegisterLua |
| Derived combat values |
XCreature::GetHIT/GetDV/GetPV/GetDMG and the GetTactics*Bonus family, creature/creature.cpp
|
| Hit points and power points |
GetMaxHP(), GetMaxPP(), IncLevel(), creature/creature.cpp
|
| From Lua |
SetStats, AddStats, SetMaxStats, ClampStats, GetStats in lua/api_actor.cpp
|