osrlib.data
The compiled rules content, and the loaders that hand it to you.
Every id-typed argument in osrlib names an entry in one of these catalogs: a class id, a
spell id, a monster id, an equipment or magic item id, a language id, a treasure-type
letter. Each load_* function returns one catalog, and the content-id pages list every id
that ships: class ids, spell ids,
monster ids, equipment ids,
magic item ids, language ids, and
treasure types.
The path is the same for all of them. Call the loader, look one entry up by id, then hand
that entry to the kernel function that takes it: load_classes().get("fighter") gives you
the ClassDefinition that
level_up wants, and load_monsters().get("goblin") the
MonsterTemplate that
spawn_monster wants. A
GameSession loads what it needs on its own, so you
call these loaders yourself when you drive the rules without a session, or when you want
to show a player what content exists before play starts.
Each loader caches. The first call reads the data and validates it, and every later call in
the process returns that same catalog object. What comes back is frozen and shared with
every other caller, so you cannot edit a catalog in place. Play spawns mutable instances
from these templates instead, the way
spawn_monster spawns a
MonsterInstance from a template.
The data files ship inside this package, and a loader reads only what the installed package
includes. The project's compiler generates them from the Old-School Essentials SRD before
each release, and nobody edits them by hand, so a patch you apply to a JSON file in an
installed copy is gone at the next upgrade. They aren't the extension point. To add content
of your own, construct the frozen model yourself and pass it to the same kernel functions the
shipped entries go to, and bundle a custom monster or item template with the
Adventure that uses it. A file that's missing, or that
fails model validation, raises
ContentValidationError rather than returning a
half-built catalog.
The compiled data is Open Game Content under the Open Game License 1.0a. The license text
and Section 15 notice ship in this package as LICENSE-OGL.md.
Typical usage:
from osrlib.data import load_classes, load_monsters
fighter = load_classes().get("fighter")
print(fighter.name, fighter.hit_die)
# Fighter 8
goblin = load_monsters().get("goblin")
print(goblin.name, goblin.ac, goblin.morale)
# Goblin 6 7
Language
Bases: BaseModel
One spoken language from the shipped catalog.
You get one from LanguageCatalog.get, or by
reading LanguageCatalog.languages when you
want to offer a player the whole list. Its id is what
create_character and
validate_extra_languages accept in
extra_languages.
An alignment tongue isn't an entry here. Each one follows from
Alignment on its own, and a character speaks the
tongue of the alignment it has.
The model is frozen: assigning to a field raises pydantic's ValidationError.
id
instance-attribute
id: str
The id to pass wherever a language id is taken, like "gnoll" or "common".
choosable
instance-attribute
choosable: bool
Whether a character with a high enough INT may take this language as an extra.
True for the SRD's Other Languages, the pool a high-INT character chooses from. False for Common, which every character speaks already and so can never be chosen again.
LanguageCatalog
Bases: BaseModel
The whole language list, with lookup by id.
load_languages returns this catalog, and
get pulls one
Language out of it by id. Ids are unique across the catalog,
which the model checks when it validates.
The model is frozen, and the loader shares one instance with every caller, so read it and don't try to add to it.
languages
instance-attribute
Every language in the catalog, in the order the data file lists them.
The shipped file is in alphabetical order by id, and Common sits among the rest. Filter
on Language.choosable for the ones a high-INT
character may take.
get
Return the language with language_id.
Use this to turn a stored id back into a display name, or to check that a language
a player picked exists before you pass it to
create_character. To validate a whole
set of picks against a class and an INT score at once, call
validate_extra_languages
instead: it returns structured refusals rather than raising on the first bad id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
language_id
|
str
|
A language id from the language id index, such
as |
required |
Returns:
| Type | Description |
|---|---|
Language
|
The language. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no language has that id. An unknown id is programmer misuse, not a player's choice, so it raises rather than refusing. |
Examples:
load_ability_tables
cached
load_ability_tables() -> AbilityTables
Load the six ability modifier tables and the prime requisite XP table.
The returned AbilityTables answers what a
score is worth: the STR melee modifier, the STR open-doors chance, the extra languages
and literacy INT grants, the DEX missile and initiative modifiers, the CON hit point
modifier, the CHA reaction modifier and retainer limits, and the prime requisite XP
percentage. Call it when you draw a character sheet, or when you resolve the rules
without a session: create_character and
level_up read these tables for you.
Every accessor takes a score in 3 to 18 and raises stdlib ValueError outside it. No
content-id page covers this catalog, because a score is the only key it has.
Returns:
| Type | Description |
|---|---|
AbilityTables
|
The frozen tables, cached: the first call validates the data and every later call returns the same object. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_classes
cached
load_classes() -> ClassCatalog
Load the character class catalog.
Call ClassCatalog.get on the result for the
ClassDefinition that the class-level functions
take: level_up,
xp_modifier_pct,
level_title,
thief_skill_check, and
caster_profile all want the definition, not the
id. create_character is the exception: it
takes class_id and looks the definition up itself, so you need this only to show a
player the classes on offer.
The catalog contains the classes this package ships. A class you write yourself is a
ClassDefinition you build and pass to those same functions. You cannot add it
here.
Returns:
| Type | Description |
|---|---|
ClassCatalog
|
The frozen class catalog, cached: the first call validates the data and every later call returns the same object. The class id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_combat_tables
cached
load_combat_tables() -> CombatTables
Load the combat tables: the attack matrix, monster saves, XP awards, turning, and reactions.
monster_xp takes these tables and a monster's hit
dice and returns the award. CombatTables.save_band and CombatTables.xp_row take the
labels monster_save_band_label and
xp_band_label compute from hit dice, so you look a
band up by asking for its label first rather than by matching hit dice yourself.
Most of combat needs no table in hand: attack_roll
and saving_throw read what they need themselves.
Load the tables when you want to show the numbers, or to award XP outside a session.
No content-id page covers this catalog, because its rows are keyed by hit dice and armour
class rather than by id.
Returns:
| Type | Description |
|---|---|
CombatTables
|
The frozen combat tables, cached: the first call validates the data and every later call returns the same object. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_encounter_tables
cached
load_encounter_tables() -> EncounterTables
Load the dungeon encounter tables, with the NPC adventuring party tables.
EncounterTables.for_level takes a dungeon level number and returns the table the SRD
prints for it, clamping anything deeper than the last printed band onto that band. Roll
on the table for an entry, then call
select_encounter_individuals to turn
a monster entry and a count into the template ids that appear. The NPC party rows feed
generate_npc_party.
A session rolls its own wandering monsters through
wandering_check, so you load these tables
when you stock a dungeon yourself or want to show what a level can throw at a party. No
content-id page covers this catalog, because its rows are keyed by level and die roll.
Returns:
| Type | Description |
|---|---|
EncounterTables
|
The frozen encounter tables, cached: the first call validates the data and every later call returns the same object. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_equipment
cached
load_equipment() -> EquipmentCatalog
Load the mundane equipment catalog: weapons, armour, gear, ammunition, and treasure weights.
EquipmentCatalog.get looks an id up across all four item lists and returns the
template that purchase,
validate_purchase,
equip, and
validate_equip take. treasure_weights is the
separate list that treasure_weight_coins
reads to weigh coins and gems, which a character picks up rather than buys.
Magic items live in their own catalog, load_magic_items.
An adventure that ships item templates of its own keeps them in its own content, and a
session answers those ids too. This catalog contains only what the package ships.
Returns:
| Type | Description |
|---|---|
EquipmentCatalog
|
The frozen equipment catalog, cached: the first call validates the data and every later call returns the same object. The equipment id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_languages
cached
load_languages() -> LanguageCatalog
Load the language catalog.
Read LanguageCatalog.languages to offer a
player the languages a high-INT character may add, keeping the entries whose
choosable is True, and
LanguageCatalog.get to turn one id back into a
display name. Pass the chosen ids to
create_character as extra_languages, or
check them first with
validate_extra_languages, which
counts them against what the character's INT allows.
Returns:
| Type | Description |
|---|---|
LanguageCatalog
|
The frozen language catalog, cached: the first call validates the data and every later call returns the same object. The language id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_magic_items
cached
load_magic_items() -> MagicItemCatalog
Load the magic item catalog: the templates, the generation sub-tables, and the sword tables.
MagicItemCatalog.get returns the frozen
MagicItemTemplate behind an id, and
magic_item_template does the same lookup for a
MagicItemInstance you already have. The
sub-tables, the armour-type table, the scroll spell-level table, and the sentient sword
tables are what generate_magic_item rolls
on, so you rarely read them yourself.
An id names a kind of item, not a particular one. Two potions of healing share a template
and differ as instances, each with its own instance id, charges, and identification
state. Mundane gear lives in load_equipment.
Returns:
| Type | Description |
|---|---|
MagicItemCatalog
|
The frozen magic item catalog, cached: the first call validates the data and every later call returns the same object. The magic item id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_monsters
cached
load_monsters() -> MonsterCatalog
Load the monster catalog.
MonsterCatalog.get returns the frozen
MonsterTemplate that
spawn_monster turns into a mutable
MonsterInstance with rolled hit points and a
session-unique id. The instance is what you fight. The template stays shared and
unchanged however many you spawn from it.
A dungeon that keys a monster by id, and an encounter table that names one, both resolve against this catalog plus whatever templates the adventure bundles. Read the template directly when you want to show a statistic block, or to plan an encounter before any monster exists.
Returns:
| Type | Description |
|---|---|
MonsterCatalog
|
The frozen monster catalog, cached: the first call validates the data and every later call returns the same object. The monster id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_spells
cached
load_spells() -> SpellCatalog
Load the spell catalog.
memorize_spells and
add_spell_to_book take this catalog whole, so
they can check a chosen id against the caster's list and level. SpellCatalog.get
returns one SpellTemplate by id, and
SpellCatalog.by_list returns every spell on a class's list, optionally at one spell
level, which is what you show a player choosing spells to memorize.
A reversed spell has its own id. The catalog contains both forms, and the template says
which is which. A spell you write yourself is a SpellTemplate you build and pass to the
same functions.
Returns:
| Type | Description |
|---|---|
SpellCatalog
|
The frozen spell catalog, cached: the first call validates the data and every later call returns the same object. The spell id index lists every id it defines. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples:
load_treasure_tables
cached
load_treasure_tables() -> TreasureTables
Load the treasure tables: the treasure types, gem values, magic item types, stocking, and unguarded hoards.
TreasureTables.treasure_type returns the table behind a letter, which is what a
monster's treasure field names and what you read to show a player, or a referee, what a
hoard can contain before anything is rolled.
To roll an actual hoard, call generate_treasure
with the letter instead: it loads these tables itself and returns a
GeneratedTreasure with coins, valuables, and
magic items already drawn from a named stream.
roll_room_contents and
generate_unguarded_treasure do the
same for the stocking and unguarded tables.
Returns:
| Type | Description |
|---|---|
TreasureTables
|
The frozen treasure tables, cached: the first call validates the data and every later call returns the same object. The treasure type index lists every letter they key on. |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the generated data is missing or fails validation. |
Examples: