osrlib.core.tables
The printed rules tables, and the lookups that read an answer out of them.
Two loaders get you the data. load_combat_tables
gives you a CombatTables with the attack matrix, the
monster saving throws, the experience awards, the turning-undead table, and the monster
reaction table. load_encounter_tables gives you an
EncounterTables with the wandering-monster table
for each dungeon level, along with the tables that generate an NPC adventuring party.
Both cache, so calling them repeatedly costs nothing.
Then call the lookup you want. to_hit_ac says what an
attacker must roll, reaction_result turns a 2d6
total into how a meeting starts, monster_xp says what
a monster is worth, and turning_column with
TurningTable.result says whether a cleric
drives the undead off. Most take a monster's
MonsterHitDice rather than a whole monster, so
they work on a stat block you assembled yourself.
In a game run by a GameSession you rarely call any
of this, because combat, encounters, and experience awards read the tables for you. Reach
for the module when you're building a tool, checking custom content, or running the rules
without a session.
The attack matrix as shipped matches the SRD cell for cell, and every cell works out to
THAC0 − AC kept within 2 to 20. The printed columns run −3 to 9, and an armour class
outside them follows the same arithmetic, so an armour class the page never prints still
has an answer. That clamping is what separates the matrix from the thac0_arithmetic
flag on Ruleset, and it shows only once modifiers push a
total past the ends.
A monster's stat block includes its own THAC0 and saving throws, already reflecting the rule that bonus hit points make a monster attack as though it had one more Hit Die. The Hit Dice lookups here are therefore for checking a stat block, for a monster you wrote yourself, and for the forms the shipped data expands into several templates.
TURNING_COLUMNS
module-attribute
The column labels of the turning-undead table, in the order the SRD prints them.
Each label names the Hit Dice of the undead being turned, with 2* for a two-Hit-Dice
monster that has a special ability and 7-9 covering three counts at once. Undead above 9
Hit Dice have no column and cannot be turned.
turning_column picks the right label from a
monster's Hit Dice, so read the tuple when you're drawing the table rather than to choose
a column.
EncounterEntry
module-attribute
EncounterEntry = Annotated[MonsterEncounterEntry | NpcPartyEncounterEntry, Field(discriminator='kind')]
What an encounter-table row produces: either monsters or a rival adventuring party.
Check kind to tell which you have, or test the type:
AttackMatrix
Bases: BaseModel
The SRD's attack matrix: what every class of attacker needs to roll against every armour class.
Read it off CombatTables.attack_matrix to show the
table. The rest of the time call to_hit_ac, which
gives the same answer for any THAC0 and any armour class.
rows
instance-attribute
rows: tuple[AttackMatrixRow, ...]
The rows, worst attacker first, each one better than the last.
AttackMatrixRow
Bases: BaseModel
One row of the attack matrix: everything one class of attacker needs to hit.
Read these to draw the matrix. To find a number to roll, call
to_hit_ac instead, which works for any armour class
rather than only the printed ones.
hd_label
instance-attribute
hd_label: str
Which attacker the row is for, as the SRD labels it: "NH" for a normal human, then Hit Dice bands.
thac0
class-attribute
instance-attribute
The roll this attacker needs to hit armour class 0, which is the number the whole row is derived from.
attack_bonus
class-attribute
instance-attribute
The same attacker written as an ascending-armour-class bonus, which is 19 minus the THAC0.
CombatTables
Bases: BaseModel
The five tables combat resolution reads, loaded together.
Get it from load_combat_tables, then reach into the
field you want or use one of the lookups in this module, most of which take these
tables as their first argument.
attack_matrix
instance-attribute
attack_matrix: AttackMatrix
What every attacker needs to roll against every armour class.
monster_saves
instance-attribute
monster_saves: tuple[MonsterSaveBand, ...]
The monster saving-throw bands, weakest first. Find one by label with
save_band.
xp_awards
instance-attribute
xp_awards: tuple[XpAwardRow, ...]
What a defeated monster is worth, by Hit Dice band. Find one by label with
xp_row.
turning
instance-attribute
turning: TurningTable
What a cleric can do to undead, by the cleric's level and the undead's Hit Dice.
reaction
instance-attribute
reaction: ReactionTable
How a 2d6 total decides the way a meeting with monsters starts.
save_band
save_band(label: str) -> MonsterSaveBand
Return the monster saving-throw band with this label.
Get the label from
monster_save_band_label rather than
writing it out, since the labels use en dashes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
A band label such as |
required |
Returns:
| Type | Description |
|---|---|
MonsterSaveBand
|
The band, with its five saving throw numbers. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no band has that label. |
Examples:
from osrlib.core.monsters import MonsterHitDice
from osrlib.core.tables import monster_save_band_label
from osrlib.data import load_combat_tables
tables = load_combat_tables()
# A troll, at 6+3 Hit Dice, saves on the 4 to 6 band.
troll = MonsterHitDice(count=6, modifier=3, asterisks=1)
band = tables.save_band(monster_save_band_label(troll))
assert band.label == "4–6"
assert band.saves.death == 10
xp_row
xp_row(label: str) -> XpAwardRow
Return the experience-award row with this label.
Get the label from xp_band_label. To get the
award itself, call monster_xp, which reads the
row and counts the monster's special abilities for you.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
A row label such as |
required |
Returns:
| Type | Description |
|---|---|
XpAwardRow
|
The row, with its base and per-ability amounts. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no row has that label. |
Examples:
EncounterTable
Bases: BaseModel
The wandering-monster table for one band of dungeon levels.
Get the one that fits a level with
EncounterTables.for_level, then roll
a d20 and read rows[roll - 1]. Deeper levels have nastier tables, which is how the
rules make depth dangerous.
min_level
class-attribute
instance-attribute
The shallowest dungeon level this table covers.
max_level
class-attribute
instance-attribute
max_level: int | None = None
The deepest dungeon level this table covers, or None on the last table, which covers everything below.
rows
instance-attribute
rows: tuple[EncounterTableRow, ...]
The twenty rows, in d20 order, so rows[roll - 1] is the row for a roll.
overrides_applied
class-attribute
instance-attribute
Which fields were corrected when this table was compiled from the SRD text, as dotted paths.
"rows.1.name" means the second row's name needed fixing. Empty when nothing did.
Read it when you're checking osrlib's data against the book.
EncounterTableRow
Bases: BaseModel
One d20 result on a dungeon encounter table: what appears, and how many.
Roll a d20, take rows[roll - 1], roll the count, and turn the entry into monsters
with
select_encounter_individuals.
roll
class-attribute
instance-attribute
The d20 result this row is for, from 1 to 20. Rows are stored in this order.
name
class-attribute
instance-attribute
The name printed in the table's cell, which is what to show the referee.
entry
instance-attribute
entry: EncounterEntry
What turns up: monsters, or a rival adventuring party.
count_dice
class-attribute
instance-attribute
count_dice: str | None = None
A dice expression for how many appear, or None when the row prints a flat number instead.
The table's own count wins over the number a monster's description gives, which is
what the SRD's note about the dungeon tables says to do. Exactly one of this and
count_fixed is set.
count_fixed
class-attribute
instance-attribute
count_fixed: int | None = None
A flat number of individuals, or None when the row rolls dice instead.
Exactly one of this and count_dice is set.
EncounterTables
Bases: BaseModel
Every dungeon encounter table, and the tables that build a rival adventuring party.
Get it from load_encounter_tables. The way in is
for_level, which picks the table for
the dungeon level the party is on.
A GameSession rolls wandering monsters for you,
and an adventure can bring its own table instead of these, so you reach for this
directly when you're stocking or wandering outside a session.
tables
instance-attribute
tables: tuple[EncounterTable, ...]
The level tables, shallowest first, together covering every level from 1 down with no gaps.
npc_class_levels
class-attribute
instance-attribute
npc_class_levels: tuple[NpcClassLevelRow, ...] = ()
The d8 table that gives each NPC adventurer a class and a level.
npc_alignment
class-attribute
instance-attribute
npc_alignment: tuple[NpcAlignmentBand, ...] = ()
The d6 table that gives an NPC adventuring party its alignment.
npc_compositions
class-attribute
instance-attribute
npc_compositions: tuple[NpcPartyComposition, ...] = ()
How many adventurers a party has, one entry for the basic kind and one for the expert kind.
for_level
for_level(level: int) -> EncounterTable
Return the wandering-monster table to roll on at this dungeon level.
Levels 1, 2, and 3 each have their own table. Levels 4 and 5 share one, 6 and 7 share another, and everything 8 or deeper rolls on the last, so no level is too deep to have a table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
int
|
The dungeon level, counting from 1 at the top. |
required |
Returns:
| Type | Description |
|---|---|
EncounterTable
|
The table for that level. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
MonsterEncounterEntry
Bases: BaseModel
The part of an encounter-table row that says which monsters turn up.
Turn it into the actual monsters with
select_encounter_individuals,
which handles all three shapes below, then spawn each id with
spawn_monster.
kind
class-attribute
instance-attribute
kind: Literal['monster'] = 'monster'
Always "monster". It tells this entry apart from
NpcPartyEncounterEntry when you read a row.
monster_ids
class-attribute
instance-attribute
The monster template ids this row can produce, at least one.
An id names a template in the catalog the game is playing with: one that ships with
osrlib, from load_monsters and listed in
the monster id index, or one an adventure brings with it.
One id is the ordinary case. Several means the printed row covers a spread of one monster's forms, such as a veteran at three different levels, and each individual is picked from that pool separately. The tabletop game leaves the pick to the referee. osrlib rolls it instead, so an encounter comes out the same way on a replay.
variant_dice
class-attribute
instance-attribute
variant_dice: str | None = None
A dice expression that picks one form for the whole group, or None when each individual is picked separately.
The hydra is what this exists for: the printed row rolls its Hit Dice once, and every hydra in the group has that many heads. The ids are ordered so the expression's lowest total names the first one.
MonsterSaveBand
Bases: BaseModel
One band of the monster saving-throw table, covering a run of Hit Dice.
Find the band for a monster with
monster_save_band_label and
CombatTables.save_band. A shipped
monster already has its own saving throws, so you need this for a monster you
wrote yourself or to check one you were given.
label
instance-attribute
label: str
The band as the SRD prints it: "NH" for a normal human, then "1–3" up to "22 or more".
min_hd
class-attribute
instance-attribute
min_hd: int | None = None
The lowest Hit Dice count in the band, or None on the normal-human row, which sits below 1 Hit Die.
max_hd
class-attribute
instance-attribute
max_hd: int | None = None
The highest Hit Dice count in the band, or None on the open top row, which has no ceiling.
saves
instance-attribute
saves: SavingThrows
The five saving throw numbers a monster in this band rolls against.
NpcAlignmentBand
Bases: BaseModel
One d6 band of the table that decides an NPC adventuring party's alignment.
roll_min
class-attribute
instance-attribute
The lowest d6 result in this band.
roll_max
class-attribute
instance-attribute
The highest d6 result in this band.
NpcClassLevelRow
Bases: BaseModel
One d8 result on the table that decides an NPC adventurer's class and level.
osrlib.core.npc rolls on this table for you when it builds a
party, so read the rows only to show the table or to generate a party your own way.
roll
class-attribute
instance-attribute
The d8 result this row is for. Two results give the same class with different level dice.
class_id
instance-attribute
class_id: str
The class this result generates, as an id such as "cleric" or "fighter".
See the class id index.
basic_dice
instance-attribute
basic_dice: str
The dice to roll for the NPC's level in a basic party, such as "1d3".
expert_dice
instance-attribute
expert_dice: str
The dice to roll for the NPC's level in an expert party, such as "1d6+3".
NpcPartyComposition
Bases: BaseModel
How many adventurers an NPC party of one kind has.
NpcPartyEncounterEntry
Bases: BaseModel
The part of an encounter-table row that says a rival adventuring party turns up.
A few rows of the printed tables call for other adventurers rather than monsters.
Generate the party with osrlib.core.npc, which rolls its size,
each member's class and level, and their scores and gear.
kind
class-attribute
instance-attribute
kind: Literal['npc_party'] = 'npc_party'
Always "npc_party". It tells this entry apart from
MonsterEncounterEntry when you read a row.
party_kind
instance-attribute
party_kind: Literal['basic', 'expert']
Which of the two printed party kinds to generate: "basic" for low levels, "expert" for high.
ReactionBand
Bases: BaseModel
One band of the reaction table: a range of 2d6 totals and what it means.
Read these to show the table. To resolve a roll, call
reaction_result.
label
instance-attribute
label: str
The range as the SRD prints it, such as "2 or less", "3–5", or "12 or more".
text
instance-attribute
text: str
The SRD's own wording for the band, such as "Hostile, may attack".
It's the printed English, so use it for a referee's display rather than as text for
players. Key your own player-facing wording off result instead.
min_total
class-attribute
instance-attribute
min_total: int | None = None
The lowest 2d6 total in the band, or None on the bottom band, which has no floor.
A charisma modifier can take a total below 2, and such a total still lands here.
max_total
class-attribute
instance-attribute
max_total: int | None = None
The highest 2d6 total in the band, or None on the top band, which has no ceiling.
ReactionResult
Bases: StrEnum
How a meeting with a monster starts, from the SRD's encounter rules.
reaction_result returns one from a 2d6 total.
The result says what the monsters do about the party, and nothing more: fighting,
talking, and buying them off are what you do next.
The lowercase values serialize into events and saved games. Changing one is a
schema_version bump, the version stamp that marks a serialized model's shape.
ATTACKS
class-attribute
instance-attribute
The monsters attack at once, on a total of 2 or less.
HOSTILE
class-attribute
instance-attribute
The monsters are hostile and may attack, on a total of 3 to 5.
UNCERTAIN
class-attribute
instance-attribute
The monsters are uncertain and confused, on a total of 6 to 8.
INDIFFERENT
class-attribute
instance-attribute
The monsters are indifferent and may negotiate, on a total of 9 to 11.
ReactionTable
Bases: BaseModel
The monster reaction table: how a 2d6 total decides the way a meeting starts.
Get it from CombatTables.reaction and pass it to
reaction_result with your rolled total.
bands
instance-attribute
bands: tuple[ReactionBand, ...]
The five bands, worst reaction first, covering every total with no gap between them.
TurningResult
Bases: BaseModel
What the turning table says when a cleric of a given level faces undead of a given kind.
TurningTable.result returns one. Act on
outcome: with "number" you roll 2d6 and compare it with threshold, and the other
three settle the attempt with no roll.
outcome
instance-attribute
outcome: str
What happens, as one of four words.
"fail" means the cleric cannot touch these undead. "number" means roll 2d6 and
meet threshold. "turn" means the undead flee with no roll. "destroy" means they
are annihilated outright rather than driven off.
threshold
class-attribute
instance-attribute
threshold: int | None = None
The 2d6 total the cleric must reach, on a "number" outcome only, and None on the other three.
TurningRow
Bases: BaseModel
One cleric level's row of the turning table, as printed.
Read these to draw the table. To resolve an attempt, call
TurningTable.result, which reads the cell
and hands back something you can act on.
label
instance-attribute
label: str
The cleric level the row is for, as "1" through "10", and "11+" for the open top row.
cells
instance-attribute
The row's cells, keyed by the column labels in TURNING_COLUMNS.
Each value is exactly what the page prints: an em dash for no effect, a number to roll
against, T for an automatic turn, or D for automatic destruction.
TurningTable
Bases: BaseModel
The turning-undead table: what a cleric of each level can do to undead of each kind.
Get it from CombatTables.turning and call
result. Turning is a cleric's own ability,
so a magic-user or a fighter never reads this table.
rows
instance-attribute
rows: tuple[TurningRow, ...]
One row per cleric level, from level 1 up to the open 11+ row.
result
result(cleric_level: int, column: str) -> TurningResult
Say what happens when this cleric tries to turn these undead.
Get the column from turning_column, which
returns None when the undead's Hit Dice run past the table's last column. There's
nothing to look up in that case, and the attempt fails. A cleric above level 10
reads the 11+ row, which is what the printed table intends.
This is the lookup alone. turn_undead rolls
the 2d6 a "number" outcome calls for and works out how many undead are
affected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cleric_level
|
int
|
The cleric's level, 1 or higher. |
required |
column
|
str
|
A column label from
|
required |
Returns:
| Type | Description |
|---|---|
TurningResult
|
What the cell says, ready to act on. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the level is below 1, or the column label isn't one the table prints. |
Examples:
from osrlib.data import load_combat_tables
turning = load_combat_tables().turning
# A first-level cleric needs a 7 against one-Hit-Die undead.
attempt = turning.result(1, "1")
assert (attempt.outcome, attempt.threshold) == ("number", 7)
# The same cleric cannot touch three-Hit-Dice undead.
assert turning.result(1, "3").outcome == "fail"
# At level 12 the weakest undead are destroyed outright.
assert turning.result(12, "1").outcome == "destroy"
XpAwardRow
Bases: BaseModel
One row of the experience-award table, saying what a monster of that size is worth.
monster_xp does the whole calculation, including the
special abilities, so read a row directly only to show the table.
label
instance-attribute
label: str
The Hit Dice band as the SRD prints it, from "Less than 1" up to "21–21+".
A trailing + marks a monster whose Hit Dice have a bonus, which is worth more than
the same count without one.
base
class-attribute
instance-attribute
The experience a monster in this band is worth before its special abilities are counted.
monster_save_band_label
monster_save_band_label(hit_dice: MonsterHitDice) -> str
Return which saving-throw band a monster of these Hit Dice belongs to.
Pass the label to CombatTables.save_band
to get the numbers. Every monster that ships with osrlib already carries its own saving
throws, taken from its stat block, so use this for a monster you wrote yourself or to
check one you were given.
A bonus hit-point modifier doesn't move a monster up a band, because the bands are counted in whole Hit Dice: a troll at 6+3 saves on the band for 4 to 6. Two kinds of monster save as a normal human: one whose Hit Dice count is below 1, and one whose hit die is a d4, which is how the compiled data writes half a Hit Die. A monster with flat hit points per Hit Die, such as a hydra, still saves on the band for its count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hit_dice
|
MonsterHitDice
|
The monster's Hit Dice. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A band label, from |
Examples:
from osrlib.core.monsters import MonsterHitDice
from osrlib.core.tables import monster_save_band_label
assert monster_save_band_label(MonsterHitDice(count=6, modifier=3)) == "4–6"
assert monster_save_band_label(MonsterHitDice(count=1, modifier=-1)) == "1–3"
# Half a Hit Die is a d4, and saves as a normal human.
assert monster_save_band_label(MonsterHitDice(count=1, die=4)) == "NH"
monster_xp
monster_xp(tables: CombatTables, hit_dice: MonsterHitDice) -> int
Return the experience a party earns for defeating one monster of these Hit Dice.
Multiply by how many the party defeated, add the treasure they carried off, and
divide among the survivors. A session awards experience for you when a battle ends, on
the schedule the xp_award_timing flag on Ruleset
sets, so call this when you're tallying a fight yourself or costing out an encounter
you're designing.
The award is the row's base amount, plus its per-ability amount for each special ability the monster has. Past 21 Hit Dice both amounts grow by 250 for each Hit Die above 21, which is what makes a dragon turtle at 30 Hit Dice and one special ability worth 9,000.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tables
|
CombatTables
|
The combat tables, from
|
required |
hit_dice
|
MonsterHitDice
|
The monster's Hit Dice, with its count of special abilities. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The experience for one monster. |
Examples:
from osrlib.core.monsters import MonsterHitDice
from osrlib.core.tables import monster_xp
from osrlib.data import load_combat_tables
tables = load_combat_tables()
# A goblin at 1-1 Hit Dice with no special abilities.
assert monster_xp(tables, MonsterHitDice(count=1, modifier=-1)) == 5
# A troll at 6+3 with one special ability.
assert monster_xp(tables, MonsterHitDice(count=6, modifier=3, asterisks=1)) == 650
# A dragon turtle at 30, where the amounts grow past the end of the table.
assert monster_xp(tables, MonsterHitDice(count=30, asterisks=1)) == 9000
reaction_result
reaction_result(table: ReactionTable, total: int) -> ReactionResult
Read a rolled reaction total off the table.
Roll 2d6, add the party spokesman's charisma modifier from
AbilityTables.npc_reaction_modifier,
and pass the total here. A modified total below 2 or above 12 is fine: the outer bands
are open, so nothing falls off the ends of the table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
ReactionTable
|
The reaction table, from
|
required |
total
|
int
|
The 2d6 total with any modifier already added. |
required |
Returns:
| Type | Description |
|---|---|
ReactionResult
|
How the monsters react. |
Examples:
from osrlib.core.tables import ReactionResult, reaction_result
from osrlib.data import load_combat_tables
table = load_combat_tables().reaction
assert reaction_result(table, 7) is ReactionResult.UNCERTAIN
# A charismatic spokesman can push the total past the printed top.
assert reaction_result(table, 14) is ReactionResult.FRIENDLY
select_encounter_individuals
select_encounter_individuals(entry: MonsterEncounterEntry, count: int, stream: RngStream) -> list[str]
Turn an encounter row into one monster template id per individual that appears.
Call it once you know how many appear: roll the row's count_dice with
roll, or take its count_fixed. Then spawn each id with
spawn_monster to get monsters you can fight.
Which ids come back depends on the row. A row with one id repeats it. A row with
several picks for each individual separately, so a group of veterans can come out
mixed. A row with variant_dice rolls once and gives every individual the same form,
which is how every hydra in a group ends up with the same number of heads.
Stocking a dungeon and rolling a wandering encounter both come through here, drawing in the same order from the same stream, so a room stocked from a row contains what a wander onto that row would have produced.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry
|
MonsterEncounterEntry
|
The row's monster entry. |
required |
count
|
int
|
How many individuals appear. Roll it before you call. |
required |
stream
|
RngStream
|
The stream the picks draw from, which advances. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
One template id per individual, in the order they were picked. The list is exactly |
list[str]
|
|
Examples:
from osrlib.core.rng import RngStreams
from osrlib.core.tables import select_encounter_individuals
from osrlib.data import load_encounter_tables
stream = RngStreams(master_seed=5).get("wandering")
# A row with one monster repeats it.
acolytes = load_encounter_tables().for_level(1).rows[0].entry
assert select_encounter_individuals(acolytes, 3, stream) == ["acolyte", "acolyte", "acolyte"]
thac0_for_hd
Return how well a monster of this many Hit Dice attacks.
A monster that ships with osrlib already has its THAC0, so use this for a monster you wrote yourself, to check a stat block you were given, or to work out how well a monster attacks after something drained its Hit Dice.
Bonus hit points make a monster attack as though it had one more Hit Die, which is
what bonus_modifier is for. A negative modifier changes nothing, so the goblin's 1-1
still attacks as one Hit Die. Anything below one Hit Die attacks on the lowest row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
The monster's Hit Dice count. |
required |
bonus_modifier
|
bool
|
True when the Hit Dice have a positive hit-point modifier, as the troll's 6+3 does. |
False
|
Returns:
| Type | Description |
|---|---|
tuple[int, int]
|
The THAC0 and the same thing as an ascending-armour-class bonus, in that order. |
Examples:
to_hit_ac
Return the d20 result an attacker needs to hit this armour class.
This is the attack matrix as arithmetic, and it gives the printed answer for every printed cell. Armour classes past the printed columns follow the same arithmetic, so a defender at −7 is handled like any other.
You rarely call this in play, because
resolve_attack rolls the attack, applies every
modifier, and works out whether it hit. Call this to show a player the number they
need, or to check the matrix.
The answer is kept within 2 to 20, which is what makes a natural 1 always miss and a
natural 20 always hit. Turning on thac0_arithmetic in
Ruleset drops that clamping and uses the plain
subtraction instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thac0
|
int
|
The attacker's THAC0, the roll it needs to hit armour class 0. |
required |
ac
|
int
|
The defender's descending armour class, where lower is better armoured. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The roll needed, from 2 to 20. |
Examples:
turning_column
turning_column(hit_dice: MonsterHitDice) -> str | None
Return which turning-table column undead of these Hit Dice sit in.
Pass the label to TurningTable.result. None
back means the undead are past the end of the printed table and cannot be turned at
all, so there's nothing to look up and nothing to roll.
The column is the Hit Dice count, with three exceptions the table prints: a two-Hit-Dice
monster with a special ability sits in 2*, counts 7 through 9 share one column, and
anything above 9 has no column. A hit-point modifier never moves a monster between
columns, so a mummy's 5+1 turns on column 5, and a special ability matters only at count
2, so a wight's 3* turns on column 3.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hit_dice
|
MonsterHitDice
|
The undead monster's Hit Dice. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
A column label from |
str | None
|
when the monster is beyond the table. |
Examples:
from osrlib.core.monsters import MonsterHitDice
from osrlib.core.tables import turning_column
# A skeleton, at 1 Hit Die.
assert turning_column(MonsterHitDice(count=1)) == "1"
# A ghoul, at 2 Hit Dice with a special ability, has a column of its own.
assert turning_column(MonsterHitDice(count=2, asterisks=1)) == "2*"
# Anything above 9 Hit Dice is past the table and cannot be turned.
assert turning_column(MonsterHitDice(count=10)) is None
xp_band_label
xp_band_label(hit_dice: MonsterHitDice) -> str
Return which experience-award row a monster of these Hit Dice belongs to.
Call monster_xp instead when you want the award
itself. This is the row lookup behind it, which is what you want when you're drawing
the table.
A bonus modifier moves a monster to the + version of its row, which is worth more. A
negative modifier drops it to the row below, so a goblin at 1-1 Hit Dice is awarded
from the "Less than 1" row. The rule about attacking as one Hit Die higher is for
bonuses only. A monster whose hit die is a d4, which is how the compiled data writes
half a Hit Die, is "Less than 1" whatever its count, and so is one whose count works out
below 1. A monster with flat hit points per Hit Die, such as a hydra, is awarded from
the row for its count. Above 21 Hit Dice everything lands on the last row, and
monster_xp adds to it from there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hit_dice
|
MonsterHitDice
|
The monster's Hit Dice. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A row label such as |
Examples:
from osrlib.core.monsters import MonsterHitDice
from osrlib.core.tables import xp_band_label
assert xp_band_label(MonsterHitDice(count=2)) == "2"
# A bonus moves the monster up a row, a penalty down one.
assert xp_band_label(MonsterHitDice(count=6, modifier=3)) == "6+"
assert xp_band_label(MonsterHitDice(count=1, modifier=-1)) == "Less than 1"